All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.trino.execution.SqlTask Maven / Gradle / Ivy

There is a newer version: 465
Show newest version
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.trino.execution;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import io.airlift.concurrent.SetThreadName;
import io.airlift.log.Logger;
import io.airlift.stats.CounterStat;
import io.airlift.units.DataSize;
import io.airlift.units.Duration;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.trino.Session;
import io.trino.exchange.ExchangeManagerRegistry;
import io.trino.execution.DynamicFiltersCollector.VersionedDynamicFilterDomains;
import io.trino.execution.StateMachine.StateChangeListener;
import io.trino.execution.buffer.BufferResult;
import io.trino.execution.buffer.LazyOutputBuffer;
import io.trino.execution.buffer.OutputBuffer;
import io.trino.execution.buffer.OutputBuffers;
import io.trino.execution.buffer.PipelinedOutputBuffers;
import io.trino.memory.QueryContext;
import io.trino.operator.PipelineContext;
import io.trino.operator.PipelineStatus;
import io.trino.operator.TaskContext;
import io.trino.operator.TaskStats;
import io.trino.spi.connector.CatalogHandle;
import io.trino.spi.predicate.Domain;
import io.trino.sql.planner.PlanFragment;
import io.trino.sql.planner.plan.DynamicFilterId;
import io.trino.sql.planner.plan.PlanNodeId;
import io.trino.tracing.TrinoAttributes;
import jakarta.annotation.Nullable;
import org.joda.time.DateTime;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static io.airlift.units.DataSize.succinctBytes;
import static io.trino.execution.DynamicFiltersCollector.INITIAL_DYNAMIC_FILTERS_VERSION;
import static io.trino.execution.DynamicFiltersCollector.INITIAL_DYNAMIC_FILTER_DOMAINS;
import static io.trino.execution.TaskState.FAILED;
import static io.trino.execution.TaskState.FAILING;
import static io.trino.execution.TaskState.FINISHED;
import static io.trino.execution.TaskState.RUNNING;
import static io.trino.util.Failures.toFailures;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

public class SqlTask
{
    private static final Logger log = Logger.get(SqlTask.class);

    private final TaskId taskId;
    private final String taskInstanceId;
    private final URI location;
    private final String nodeId;
    private final AtomicBoolean speculative = new AtomicBoolean(false);
    private final TaskStateMachine taskStateMachine;
    private final OutputBuffer outputBuffer;
    private final QueryContext queryContext;
    private final Tracer tracer;

    private final SqlTaskExecutionFactory sqlTaskExecutionFactory;
    private final Executor taskNotificationExecutor;

    private final AtomicReference lastHeartbeat = new AtomicReference<>(DateTime.now());
    private final AtomicLong taskStatusVersion = new AtomicLong(TaskStatus.STARTING_VERSION);
    private final FutureStateChange taskStatusVersionChange = new FutureStateChange<>();
    // Must be synchronized when updating the current task holder reference, but not when only reading the current reference value
    private final Object taskHolderLock = new Object();
    @GuardedBy("taskHolderLock")
    private final AtomicReference taskHolderReference = new AtomicReference<>(new TaskHolder());
    private final AtomicBoolean needsPlan = new AtomicBoolean(true);
    private final AtomicReference taskSpan = new AtomicReference<>(Span.getInvalid());
    private final AtomicReference traceToken = new AtomicReference<>();
    private final AtomicReference> catalogs = new AtomicReference<>();
    private final AtomicBoolean catalogsLoaded = new AtomicBoolean(false);

    public static SqlTask createSqlTask(
            TaskId taskId,
            URI location,
            String nodeId,
            QueryContext queryContext,
            Tracer tracer,
            SqlTaskExecutionFactory sqlTaskExecutionFactory,
            ExecutorService taskNotificationExecutor,
            Consumer onDone,
            DataSize maxBufferSize,
            DataSize maxBroadcastBufferSize,
            ExchangeManagerRegistry exchangeManagerRegistry,
            CounterStat failedTasks)
    {
        SqlTask sqlTask = new SqlTask(taskId, location, nodeId, queryContext, tracer, sqlTaskExecutionFactory, taskNotificationExecutor, maxBufferSize, maxBroadcastBufferSize, exchangeManagerRegistry);
        sqlTask.initialize(onDone, failedTasks);
        return sqlTask;
    }

    private SqlTask(
            TaskId taskId,
            URI location,
            String nodeId,
            QueryContext queryContext,
            Tracer tracer,
            SqlTaskExecutionFactory sqlTaskExecutionFactory,
            ExecutorService taskNotificationExecutor,
            DataSize maxBufferSize,
            DataSize maxBroadcastBufferSize,
            ExchangeManagerRegistry exchangeManagerRegistry)
    {
        this.taskId = requireNonNull(taskId, "taskId is null");
        this.taskInstanceId = UUID.randomUUID().toString();
        this.location = requireNonNull(location, "location is null");
        this.nodeId = requireNonNull(nodeId, "nodeId is null");
        this.queryContext = requireNonNull(queryContext, "queryContext is null");
        this.tracer = requireNonNull(tracer, "tracer is null");
        this.sqlTaskExecutionFactory = requireNonNull(sqlTaskExecutionFactory, "sqlTaskExecutionFactory is null");
        this.taskNotificationExecutor = requireNonNull(taskNotificationExecutor, "taskNotificationExecutor is null");
        requireNonNull(maxBufferSize, "maxBufferSize is null");

        outputBuffer = new LazyOutputBuffer(
                taskId,
                taskInstanceId,
                taskNotificationExecutor,
                maxBufferSize,
                maxBroadcastBufferSize,
                // Pass a memory context supplier instead of a memory context to the output buffer,
                // because we haven't created the task context that holds the memory context yet.
                () -> queryContext.getTaskContextByTaskId(taskId).localMemoryContext(),
                this::notifyStatusChanged,
                exchangeManagerRegistry);
        taskStateMachine = new TaskStateMachine(taskId, taskNotificationExecutor);
    }

    // this is a separate method to ensure that the `this` reference is not leaked during construction
    private void initialize(Consumer onDone, CounterStat failedTasks)
    {
        requireNonNull(onDone, "onDone is null");
        requireNonNull(failedTasks, "failedTasks is null");

        AtomicBoolean outputBufferCleanedUp = new AtomicBoolean();
        taskStateMachine.addStateChangeListener(newState -> {
            taskSpan.get().addEvent("task_state", Attributes.of(
                    TrinoAttributes.EVENT_STATE, newState.toString()));

            if (newState.isTerminatingOrDone()) {
                if (newState.isTerminating()) {
                    // This section must be synchronized to lock out any threads that might be attempting to create a SqlTaskExecution
                    synchronized (taskHolderLock) {
                        // If a SqlTaskExecution exists, it decides when termination is complete. Otherwise, we can mark termination completed immediately
                        if (taskHolderReference.get().getTaskExecution() == null) {
                            taskStateMachine.terminationComplete();
                        }
                    }
                }
                else if (newState.isDone()) {
                    // Update failed tasks counter
                    if (newState == FAILED) {
                        failedTasks.update(1);
                    }
                    // store final task info
                    boolean finished = false;
                    synchronized (taskHolderLock) {
                        TaskHolder taskHolder = taskHolderReference.get();
                        if (!taskHolder.isFinished()) {
                            TaskHolder newHolder = new TaskHolder(
                                    createTaskInfo(taskHolder),
                                    taskHolder.getIoStats(),
                                    taskHolder.getDynamicFilterDomains());
                            checkState(taskHolderReference.compareAndSet(taskHolder, newHolder), "unsynchronized concurrent task holder update");
                            finished = true;
                        }
                    }
                    // Successfully set the final task info, cleanup the output buffer and call the completion handler
                    if (finished) {
                        try {
                            onDone.accept(this);
                        }
                        catch (Exception e) {
                            log.warn(e, "Error running task cleanup callback %s", SqlTask.this.taskId);
                        }
                    }
                }
                // make sure buffers are cleaned up
                if (outputBufferCleanedUp.compareAndSet(false, true)) {
                    switch (newState) {
                        // don't close buffers for a failed query
                        // closed buffers signal to upstream tasks that everything finished cleanly
                        case FAILED, FAILING, ABORTED, ABORTING -> outputBuffer.abort();
                        case FINISHED, CANCELED, CANCELING -> outputBuffer.destroy();
                        default -> throw new IllegalStateException(format("Invalid state for output buffer destruction: %s", newState));
                    }
                }
            }

            // notify that task state changed (apart from initial RUNNING state notification)
            if (newState != RUNNING) {
                notifyStatusChanged();
            }

            if (newState.isDone()) {
                taskSpan.get().end();
            }
        });
    }

    public SqlTaskIoStats getIoStats()
    {
        return taskHolderReference.get().getIoStats();
    }

    public TaskState getTaskState()
    {
        return taskStateMachine.getState();
    }

    public DateTime getTaskCreatedTime()
    {
        return taskStateMachine.getCreatedTime();
    }

    public TaskId getTaskId()
    {
        return taskStateMachine.getTaskId();
    }

    public String getTaskInstanceId()
    {
        return taskInstanceId;
    }

    public void recordHeartbeat()
    {
        lastHeartbeat.set(DateTime.now());
    }

    public TaskInfo getTaskInfo()
    {
        try (SetThreadName _ = new SetThreadName("Task-%s", taskId)) {
            return createTaskInfo(taskHolderReference.get());
        }
    }

    public TaskStatus getTaskStatus()
    {
        try (SetThreadName _ = new SetThreadName("Task-%s", taskId)) {
            return createTaskStatus(taskHolderReference.get());
        }
    }

    public Optional> getCatalogs()
    {
        return Optional.ofNullable(catalogs.get());
    }

    public boolean setCatalogs(Set catalogs)
    {
        requireNonNull(catalogs, "catalogs is null");
        return this.catalogs.compareAndSet(null, requireNonNull(catalogs, "catalogs is null"));
    }

    public boolean catalogsLoaded()
    {
        return catalogsLoaded.get();
    }

    public boolean setCatalogsLoaded()
    {
        return catalogsLoaded.compareAndSet(false, true);
    }

    public VersionedDynamicFilterDomains acknowledgeAndGetNewDynamicFilterDomains(long callersDynamicFiltersVersion)
    {
        return taskHolderReference.get().acknowledgeAndGetNewDynamicFilterDomains(callersDynamicFiltersVersion);
    }

    private synchronized void notifyStatusChanged()
    {
        taskStatusVersion.incrementAndGet();
        taskStatusVersionChange.complete(null, taskNotificationExecutor);
    }

    private TaskStatus createTaskStatus(TaskHolder taskHolder)
    {
        // Obtain task status version before building actual TaskStatus object.
        // This way any task updates won't be lost since all updates happen
        // before version number is increased.
        long versionNumber = taskStatusVersion.get();

        TaskState state = taskStateMachine.getState();
        List failures = ImmutableList.of();
        if (state == FAILED || state == FAILING) {
            failures = toFailures(taskStateMachine.getFailureCauses());
        }

        int queuedPartitionedDrivers = 0;
        long queuedPartitionedSplitsWeight = 0L;
        int runningPartitionedDrivers = 0;
        long runningPartitionedSplitsWeight = 0L;
        DataSize outputDataSize = DataSize.ofBytes(0);
        DataSize writerInputDataSize = DataSize.ofBytes(0);
        DataSize physicalWrittenDataSize = DataSize.ofBytes(0);
        Optional writerCount = Optional.empty();
        DataSize userMemoryReservation = DataSize.ofBytes(0);
        DataSize peakUserMemoryReservation = DataSize.ofBytes(0);
        DataSize revocableMemoryReservation = DataSize.ofBytes(0);
        long fullGcCount = 0;
        Duration fullGcTime = new Duration(0, MILLISECONDS);
        long dynamicFiltersVersion = INITIAL_DYNAMIC_FILTERS_VERSION;
        if (taskHolder.getFinalTaskInfo() != null) {
            TaskInfo taskInfo = taskHolder.getFinalTaskInfo();
            TaskStats taskStats = taskInfo.stats();
            queuedPartitionedDrivers = taskStats.getQueuedPartitionedDrivers();
            queuedPartitionedSplitsWeight = taskStats.getQueuedPartitionedSplitsWeight();
            runningPartitionedDrivers = taskStats.getRunningPartitionedDrivers();
            runningPartitionedSplitsWeight = taskStats.getRunningPartitionedSplitsWeight();
            writerInputDataSize = taskStats.getWriterInputDataSize();
            physicalWrittenDataSize = taskStats.getPhysicalWrittenDataSize();
            writerCount = taskStats.getMaxWriterCount();
            userMemoryReservation = taskStats.getUserMemoryReservation();
            peakUserMemoryReservation = taskStats.getPeakUserMemoryReservation();
            revocableMemoryReservation = taskStats.getRevocableMemoryReservation();
            outputDataSize = taskStats.getOutputDataSize();
            fullGcCount = taskStats.getFullGcCount();
            fullGcTime = taskStats.getFullGcTime();
            dynamicFiltersVersion = taskHolder.getDynamicFiltersVersion();
        }
        else if (taskHolder.getTaskExecution() != null) {
            long physicalWrittenBytes = 0;
            TaskContext taskContext = taskHolder.getTaskExecution().getTaskContext();
            for (PipelineContext pipelineContext : taskContext.getPipelineContexts()) {
                PipelineStatus pipelineStatus = pipelineContext.getPipelineStatus();
                queuedPartitionedDrivers += pipelineStatus.getQueuedPartitionedDrivers();
                queuedPartitionedSplitsWeight += pipelineStatus.getQueuedPartitionedSplitsWeight();
                runningPartitionedDrivers += pipelineStatus.getRunningPartitionedDrivers();
                runningPartitionedSplitsWeight += pipelineStatus.getRunningPartitionedSplitsWeight();
                physicalWrittenBytes += pipelineContext.getPhysicalWrittenDataSize();
            }
            writerInputDataSize = succinctBytes(taskContext.getWriterInputDataSize());
            physicalWrittenDataSize = succinctBytes(physicalWrittenBytes);
            writerCount = taskContext.getMaxWriterCount();
            userMemoryReservation = taskContext.getMemoryReservation();
            peakUserMemoryReservation = taskContext.getPeakMemoryReservation();
            revocableMemoryReservation = taskContext.getRevocableMemoryReservation();
            outputDataSize = DataSize.ofBytes(taskContext.getOutputDataSize().getTotalCount());
            fullGcCount = taskContext.getFullGcCount();
            fullGcTime = taskContext.getFullGcTime();
            dynamicFiltersVersion = taskContext.getDynamicFiltersVersion();
        }
        else if (state == FINISHED) {
            // if task FINISHED successfully but taskHolder is not yet updated with SqlTaskExecution or FinalTaskInfo
            // we are masking the state and return RUNNING. This is important so coordinator would not consider incomplete
            // task information (e.g. missing proper dynamicFiltersVersion as final).
            // This covers only short time window between call to SqlTaskExecution.start() and updating taskHolder reference in tryCreateSqlTaskExecution,
            // so it will not add any noticable delays.
            state = RUNNING;
        }

        return new TaskStatus(
                taskStateMachine.getTaskId(),
                taskInstanceId,
                versionNumber,
                state,
                location,
                nodeId,
                speculative.get(),
                failures,
                queuedPartitionedDrivers,
                runningPartitionedDrivers,
                outputBuffer.getStatus(),
                outputDataSize,
                writerInputDataSize,
                physicalWrittenDataSize,
                writerCount,
                userMemoryReservation,
                peakUserMemoryReservation,
                revocableMemoryReservation,
                fullGcCount,
                fullGcTime,
                dynamicFiltersVersion,
                queuedPartitionedSplitsWeight,
                runningPartitionedSplitsWeight);
    }

    private TaskStats getTaskStats(TaskHolder taskHolder)
    {
        TaskInfo finalTaskInfo = taskHolder.getFinalTaskInfo();
        if (finalTaskInfo != null) {
            return finalTaskInfo.stats();
        }
        SqlTaskExecution taskExecution = taskHolder.getTaskExecution();
        if (taskExecution != null) {
            return taskExecution.getTaskContext().getTaskStats();
        }
        // if the task completed without creation, set end time
        DateTime endTime = taskStateMachine.getState().isDone() ? DateTime.now() : null;
        return new TaskStats(taskStateMachine.getCreatedTime(), endTime);
    }

    private static Set getNoMoreSplits(TaskHolder taskHolder)
    {
        TaskInfo finalTaskInfo = taskHolder.getFinalTaskInfo();
        if (finalTaskInfo != null) {
            return finalTaskInfo.noMoreSplits();
        }
        SqlTaskExecution taskExecution = taskHolder.getTaskExecution();
        if (taskExecution != null) {
            return taskExecution.getNoMoreSplits();
        }
        return ImmutableSet.of();
    }

    private TaskInfo createTaskInfo(TaskHolder taskHolder)
    {
        // create task status first to prevent potentially seeing incomplete stats for a done task state
        TaskStatus taskStatus = createTaskStatus(taskHolder);
        TaskStats taskStats = getTaskStats(taskHolder);
        Set noMoreSplits = getNoMoreSplits(taskHolder);

        return new TaskInfo(
                taskStatus,
                lastHeartbeat.get(),
                outputBuffer.getInfo(),
                noMoreSplits,
                taskStats,
                Optional.empty(),
                needsPlan.get());
    }

    public synchronized ListenableFuture getTaskStatus(long callersCurrentVersion)
    {
        if (callersCurrentVersion < taskStatusVersion.get() || taskHolderReference.get().isFinished()) {
            // return immediately if caller has older task status version or final task info is available
            return immediateFuture(getTaskStatus());
        }

        // At this point taskHolderReference.get().isFinished() might become true. However notifyStatusChanged()
        // is synchronized therefore notification for new listener won't be lost.
        return Futures.transform(taskStatusVersionChange.createNewListener(), input -> getTaskStatus(), directExecutor());
    }

    public synchronized ListenableFuture getTaskInfo(long callersCurrentVersion)
    {
        if (callersCurrentVersion < taskStatusVersion.get() || taskHolderReference.get().isFinished()) {
            // return immediately if caller has older task status version or final task info is available
            return immediateFuture(getTaskInfo());
        }

        // At this point taskHolderReference.get().isFinished() might become true. However notifyStatusChanged()
        // is synchronized therefore notification for new listener won't be lost.
        return Futures.transform(taskStatusVersionChange.createNewListener(), input -> getTaskInfo(), directExecutor());
    }

    public TaskInfo updateTask(
            Session session,
            Span stageSpan,
            Optional fragment,
            List splitAssignments,
            OutputBuffers outputBuffers,
            Map dynamicFilterDomains,
            boolean speculative)
    {
        try {
            // trace token must be set first to make sure failure injection for getTaskResults requests works as expected
            session.getTraceToken().ifPresent(traceToken::set);

            // The LazyOutput buffer does not support write methods, so the actual
            // output buffer must be established before drivers are created (e.g.
            // a VALUES query).
            outputBuffer.setOutputBuffers(outputBuffers);

            // is task already complete?
            TaskHolder taskHolder = taskHolderReference.get();
            if (taskHolder.isFinished()) {
                return taskHolder.getFinalTaskInfo();
            }

            SqlTaskExecution taskExecution = taskHolder.getTaskExecution();
            if (taskExecution == null) {
                checkState(fragment.isPresent(), "fragment must be present");
                taskExecution = tryCreateSqlTaskExecution(session, stageSpan, fragment.get());
            }
            // taskExecution can still be null if the creation was skipped
            if (taskExecution != null) {
                taskExecution.getTaskContext().addDynamicFilter(dynamicFilterDomains);
                taskExecution.addSplitAssignments(splitAssignments);
            }

            // update speculative flag
            this.speculative.set(speculative);
        }
        catch (Error e) {
            failed(e);
            throw e;
        }
        catch (RuntimeException e) {
            return failed(e);
        }

        return getTaskInfo();
    }

    @Nullable
    private SqlTaskExecution tryCreateSqlTaskExecution(Session session, Span stageSpan, PlanFragment fragment)
    {
        SqlTaskExecution execution;
        synchronized (taskHolderLock) {
            // Recheck holder for task execution after acquiring the lock
            TaskHolder taskHolder = taskHolderReference.get();
            if (taskHolder.isFinished()) {
                return null;
            }
            execution = taskHolder.getTaskExecution();
            if (execution != null) {
                return execution;
            }

            // Don't create SqlTaskExecution once termination has started
            if (taskStateMachine.getState().isTerminatingOrDone()) {
                return null;
            }

            taskSpan.set(tracer.spanBuilder("task")
                    .setParent(Context.current().with(stageSpan))
                    .setAttribute(TrinoAttributes.QUERY_ID, taskId.getQueryId().toString())
                    .setAttribute(TrinoAttributes.STAGE_ID, taskId.getStageId().toString())
                    .setAttribute(TrinoAttributes.TASK_ID, taskId.toString())
                    .startSpan());

            execution = sqlTaskExecutionFactory.create(
                    session,
                    taskSpan.get(),
                    queryContext,
                    taskStateMachine,
                    outputBuffer,
                    fragment,
                    this::notifyStatusChanged);
            needsPlan.set(false);
            execution.start();
            // this must happen after taskExecution.start(), otherwise it could become visible to a
            // concurrent update without being fully initialized
            checkState(taskHolderReference.compareAndSet(taskHolder, new TaskHolder(execution)), "unsynchronized concurrent task holder update");
        }
        if (taskStateMachine.getState() == FINISHED) {
            // Bump task status version as otherwise it could not be observed by coordinator due to
            // status masking logic in createTaskStatus for case when neither SqlExecution nor
            // FinalTaskInfo is set in TaskHolder.
            notifyStatusChanged();
        }
        return execution;
    }

    public ListenableFuture getTaskResults(PipelinedOutputBuffers.OutputBufferId bufferId, long startingSequenceId, DataSize maxSize)
    {
        requireNonNull(bufferId, "bufferId is null");
        checkArgument(maxSize.toBytes() > 0, "maxSize must be at least 1 byte");

        return outputBuffer.get(bufferId, startingSequenceId, maxSize);
    }

    public void acknowledgeTaskResults(PipelinedOutputBuffers.OutputBufferId bufferId, long sequenceId)
    {
        requireNonNull(bufferId, "bufferId is null");

        outputBuffer.acknowledge(bufferId, sequenceId);
    }

    public TaskInfo destroyTaskResults(PipelinedOutputBuffers.OutputBufferId bufferId)
    {
        requireNonNull(bufferId, "bufferId is null");

        log.debug("Aborting task %s output %s", taskId, bufferId);
        outputBuffer.destroy(bufferId);

        return getTaskInfo();
    }

    public TaskInfo failed(Throwable cause)
    {
        requireNonNull(cause, "cause is null");

        taskStateMachine.failed(cause);
        return getTaskInfo();
    }

    public TaskInfo cancel()
    {
        taskStateMachine.cancel();
        return getTaskInfo();
    }

    public TaskInfo abort()
    {
        taskStateMachine.abort();
        return getTaskInfo();
    }

    @Override
    public String toString()
    {
        return taskId.toString();
    }

    private static final class TaskHolder
    {
        private final SqlTaskExecution taskExecution;
        private final TaskInfo finalTaskInfo;
        private final SqlTaskIoStats finalIoStats;
        private final VersionedDynamicFilterDomains finalDynamicFilterDomains;

        private TaskHolder()
        {
            this.taskExecution = null;
            this.finalTaskInfo = null;
            this.finalIoStats = null;
            this.finalDynamicFilterDomains = null;
        }

        private TaskHolder(SqlTaskExecution taskExecution)
        {
            this.taskExecution = requireNonNull(taskExecution, "taskExecution is null");
            this.finalTaskInfo = null;
            this.finalIoStats = null;
            this.finalDynamicFilterDomains = null;
        }

        private TaskHolder(TaskInfo finalTaskInfo, SqlTaskIoStats finalIoStats, VersionedDynamicFilterDomains finalDynamicFilterDomains)
        {
            this.taskExecution = null;
            this.finalTaskInfo = requireNonNull(finalTaskInfo, "finalTaskInfo is null");
            this.finalIoStats = requireNonNull(finalIoStats, "finalIoStats is null");
            this.finalDynamicFilterDomains = requireNonNull(finalDynamicFilterDomains, "finalDynamicFilterDomains is null");
        }

        public boolean isFinished()
        {
            return finalTaskInfo != null;
        }

        @Nullable
        public SqlTaskExecution getTaskExecution()
        {
            return taskExecution;
        }

        @Nullable
        public TaskInfo getFinalTaskInfo()
        {
            return finalTaskInfo;
        }

        public SqlTaskIoStats getIoStats()
        {
            // if we are finished, return the final IoStats
            if (finalIoStats != null) {
                return finalIoStats;
            }
            // if we haven't started yet, return an empty IoStats
            if (taskExecution == null) {
                return new SqlTaskIoStats();
            }
            // get IoStats from the current task execution
            TaskContext taskContext = taskExecution.getTaskContext();
            return new SqlTaskIoStats(taskContext.getProcessedInputDataSize(), taskContext.getInputPositions(), taskContext.getOutputDataSize(), taskContext.getOutputPositions());
        }

        public VersionedDynamicFilterDomains acknowledgeAndGetNewDynamicFilterDomains(long callersSummaryVersion)
        {
            // if we are finished, return the final VersionedDynamicFilterDomains
            if (finalDynamicFilterDomains != null) {
                return finalDynamicFilterDomains;
            }
            // if we haven't started yet, return an empty VersionedDynamicFilterDomains
            if (taskExecution == null) {
                return INITIAL_DYNAMIC_FILTER_DOMAINS;
            }
            // get VersionedDynamicFilterDomains from the current task execution
            TaskContext taskContext = taskExecution.getTaskContext();
            return taskContext.acknowledgeAndGetNewDynamicFilterDomains(callersSummaryVersion);
        }

        public long getDynamicFiltersVersion()
        {
            // if we are finished, return the version of the final VersionedDynamicFilterDomains
            if (finalDynamicFilterDomains != null) {
                return finalDynamicFilterDomains.getVersion();
            }
            requireNonNull(taskExecution, "taskExecution is null");
            return taskExecution.getTaskContext().getDynamicFiltersVersion();
        }

        public VersionedDynamicFilterDomains getDynamicFilterDomains()
        {
            verify(finalDynamicFilterDomains == null, "finalDynamicFilterDomains has already been set");
            // Task was aborted or failed before taskExecution was created, return an empty VersionedDynamicFilterDomains
            if (taskExecution == null) {
                return INITIAL_DYNAMIC_FILTER_DOMAINS;
            }
            // get VersionedDynamicFilterDomains from the current task execution
            return taskExecution.getTaskContext().getCurrentDynamicFilterDomains();
        }
    }

    /**
     * Listener is always notified asynchronously using a dedicated notification thread pool so, care should
     * be taken to avoid leaking {@code this} when adding a listener in a constructor. Additionally, it is
     * possible notifications are observed out of order due to the asynchronous execution.
     */
    public void addStateChangeListener(StateChangeListener stateChangeListener)
    {
        taskStateMachine.addStateChangeListener(stateChangeListener);
    }

    public void addSourceTaskFailureListener(TaskFailureListener listener)
    {
        taskStateMachine.addSourceTaskFailureListener(listener);
    }

    public QueryContext getQueryContext()
    {
        return queryContext;
    }

    public Optional getTaskContext()
    {
        SqlTaskExecution taskExecution = taskHolderReference.get().getTaskExecution();
        if (taskExecution == null) {
            return Optional.empty();
        }
        return Optional.of(taskExecution.getTaskContext());
    }

    public Optional getTraceToken()
    {
        return Optional.ofNullable(traceToken.get());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy