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

io.trino.operator.Driver Maven / Gradle / Ivy

/*
 * 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.operator;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import io.airlift.log.Logger;
import io.airlift.units.Duration;
import io.trino.execution.ScheduledSplit;
import io.trino.execution.SplitAssignment;
import io.trino.metadata.Split;
import io.trino.spi.Page;
import io.trino.spi.TrinoException;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Throwables.throwIfUnchecked;
import static com.google.common.base.Verify.verify;
import static com.google.common.util.concurrent.Futures.nonCancellationPropagating;
import static com.google.common.util.concurrent.Futures.withTimeout;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static io.airlift.concurrent.MoreFutures.getFutureValue;
import static io.trino.operator.Operator.NOT_BLOCKED;
import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR;
import static java.lang.Boolean.TRUE;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;

//
// NOTE:  As a general strategy the methods should "stage" a change and only
// process the actual change before lock release (DriverLockResult.close()).
// The assures that only one thread will be working with the operators at a
// time and state changer threads are not blocked.
//
public class Driver
        implements Closeable
{
    private static final Logger log = Logger.get(Driver.class);

    private static final Duration UNLIMITED_DURATION = new Duration(Long.MAX_VALUE, NANOSECONDS);

    private final DriverContext driverContext;
    private final List activeOperators;
    // this is present only for debugging
    @SuppressWarnings("unused")
    private final List allOperators;
    private final Optional sourceOperator;

    // This variable acts as a staging area. When new splits (encapsulated in SplitAssignment) are
    // provided to a Driver, the Driver will not process them right away. Instead, the splits are
    // added to this staging area. This staging area will be drained asynchronously. That's when
    // the new splits get processed.
    private final AtomicReference pendingSplitAssignmentUpdates = new AtomicReference<>();
    private final Map> revokingOperators = new HashMap<>();

    private final AtomicReference state = new AtomicReference<>(State.ALIVE);
    private final SettableFuture destroyedFuture = SettableFuture.create();

    private final DriverLock exclusiveLock = new DriverLock(state, destroyedFuture);

    @GuardedBy("exclusiveLock")
    private SplitAssignment currentSplitAssignment;

    private final AtomicReference> driverBlockedFuture = new AtomicReference<>();

    private enum State
    {
        ALIVE, NEED_DESTRUCTION, DESTROYING, DESTROYED
    }

    public static Driver createDriver(DriverContext driverContext, List operators)
    {
        requireNonNull(driverContext, "driverContext is null");
        requireNonNull(operators, "operators is null");
        Driver driver = new Driver(driverContext, operators);
        driver.initialize();
        return driver;
    }

    @VisibleForTesting
    public static Driver createDriver(DriverContext driverContext, Operator firstOperator, Operator... otherOperators)
    {
        requireNonNull(driverContext, "driverContext is null");
        requireNonNull(firstOperator, "firstOperator is null");
        requireNonNull(otherOperators, "otherOperators is null");
        ImmutableList operators = ImmutableList.builder()
                .add(firstOperator)
                .add(otherOperators)
                .build();
        return createDriver(driverContext, operators);
    }

    private Driver(DriverContext driverContext, List operators)
    {
        this.driverContext = requireNonNull(driverContext, "driverContext is null");
        this.allOperators = ImmutableList.copyOf(requireNonNull(operators, "operators is null"));
        checkArgument(allOperators.size() > 1, "At least two operators are required");
        this.activeOperators = new ArrayList<>(operators);
        checkArgument(!operators.isEmpty(), "There must be at least one operator");

        Optional sourceOperator = Optional.empty();
        for (Operator operator : operators) {
            if (operator instanceof SourceOperator value) {
                checkArgument(sourceOperator.isEmpty(), "There must be at most one SourceOperator");
                sourceOperator = Optional.of(value);
            }
        }
        this.sourceOperator = sourceOperator;

        currentSplitAssignment = sourceOperator.map(operator -> new SplitAssignment(operator.getSourceId(), ImmutableSet.of(), false)).orElse(null);
        // initially the driverBlockedFuture is not blocked (it is completed)
        SettableFuture future = SettableFuture.create();
        future.set(null);
        driverBlockedFuture.set(future);
    }

    // the memory revocation request listeners are added here in a separate initialize() method
    // instead of the constructor to prevent leaking the "this" reference to
    // another thread, which will cause unsafe publication of this instance.
    private void initialize()
    {
        activeOperators.stream()
                .map(Operator::getOperatorContext)
                .forEach(operatorContext -> operatorContext.setMemoryRevocationRequestListener(() -> driverBlockedFuture.get().set(null)));
    }

    public DriverContext getDriverContext()
    {
        return driverContext;
    }

    public ListenableFuture getDestroyedFuture()
    {
        return destroyedFuture;
    }

    @Override
    public void close()
    {
        // mark the service for destruction
        if (!state.compareAndSet(State.ALIVE, State.NEED_DESTRUCTION)) {
            return;
        }

        // set the yield signal and interrupt any actively running driver to stop them as soon as possible
        driverContext.getYieldSignal().yieldImmediatelyForTermination();
        exclusiveLock.interruptCurrentOwner();

        // if we can get the lock, attempt a clean shutdown; otherwise someone else will shutdown
        tryWithLockUninterruptibly(() -> TRUE);
    }

    public boolean isFinished()
    {
        checkLockNotHeld("Cannot check finished status while holding the driver lock");

        // if we can get the lock, attempt a clean shutdown; otherwise someone else will shutdown
        Optional result = tryWithLockUninterruptibly(this::isTerminatingOrDoneInternal);
        return result.orElseGet(() -> state.get() != State.ALIVE || driverContext.isTerminatingOrDone());
    }

    @GuardedBy("exclusiveLock")
    private boolean isTerminatingOrDoneInternal()
    {
        checkLockHeld("Lock must be held to call isTerminatingOrDoneInternal");

        boolean terminatingOrDone = state.get() != State.ALIVE || activeOperators.isEmpty() || activeOperators.getLast().isFinished() || driverContext.isTerminatingOrDone();
        if (terminatingOrDone) {
            state.compareAndSet(State.ALIVE, State.NEED_DESTRUCTION);
        }
        return terminatingOrDone;
    }

    public void updateSplitAssignment(SplitAssignment splitAssignment)
    {
        checkLockNotHeld("Cannot update assignments while holding the driver lock");
        checkArgument(
                sourceOperator.isPresent() && sourceOperator.get().getSourceId().equals(splitAssignment.getPlanNodeId()),
                "splitAssignment is for a plan node that is different from this Driver's source node");

        // stage the new updates
        pendingSplitAssignmentUpdates.updateAndGet(current -> current == null ? splitAssignment : current.update(splitAssignment));

        // attempt to get the lock and process the updates we staged above
        // updates will be processed in close if and only if we got the lock
        tryWithLockUninterruptibly(() -> TRUE);
    }

    @GuardedBy("exclusiveLock")
    private void processNewSources()
    {
        checkLockHeld("Lock must be held to call processNewSources");
        try {
            // only update if the driver is still alive
            if (state.get() != State.ALIVE) {
                return;
            }

            SplitAssignment splitAssignment = pendingSplitAssignmentUpdates.getAndSet(null);
            if (splitAssignment == null) {
                return;
            }

            // merge the current assignment and the specified assignment
            SplitAssignment newAssignment = currentSplitAssignment.update(splitAssignment);

            // if the update contains no new data, just return
            if (newAssignment == currentSplitAssignment) {
                return;
            }

            // determine new splits to add
            Set newSplits = Sets.difference(newAssignment.getSplits(), currentSplitAssignment.getSplits());

            // add new splits
            SourceOperator sourceOperator = this.sourceOperator.orElseThrow(VerifyException::new);
            for (ScheduledSplit newSplit : newSplits) {
                Split split = newSplit.getSplit();

                sourceOperator.addSplit(split);
            }

            // set no more splits
            if (newAssignment.isNoMoreSplits()) {
                sourceOperator.noMoreSplits();
            }

            currentSplitAssignment = newAssignment;
        }
        catch (Throwable failure) {
            driverContext.failed(failure);
            throw failure;
        }
    }

    public ListenableFuture processForDuration(Duration duration)
    {
        return process(duration, Integer.MAX_VALUE);
    }

    public ListenableFuture processForNumberOfIterations(int maxIterations)
    {
        return process(UNLIMITED_DURATION, maxIterations);
    }

    public ListenableFuture processUntilBlocked()
    {
        return process(UNLIMITED_DURATION, Integer.MAX_VALUE);
    }

    @VisibleForTesting
    public ListenableFuture process(Duration maxRuntime, int maxIterations)
    {
        checkLockNotHeld("Cannot process for a duration while holding the driver lock");

        requireNonNull(maxRuntime, "maxRuntime is null");
        checkArgument(maxIterations > 0, "maxIterations must be greater than zero");

        // if the driver is blocked we don't need to continue
        SettableFuture blockedFuture = driverBlockedFuture.get();
        if (!blockedFuture.isDone()) {
            return blockedFuture;
        }

        long maxRuntimeInNanos = maxRuntime.roundTo(TimeUnit.NANOSECONDS);

        Optional> result = tryWithLock(100, TimeUnit.MILLISECONDS, true, () -> {
            OperationTimer operationTimer = createTimer();
            driverContext.startProcessTimer();
            driverContext.getYieldSignal().setWithDelay(maxRuntimeInNanos, driverContext.getYieldExecutor());
            try {
                long start = System.nanoTime();
                int iterations = 0;
                while (!isTerminatingOrDoneInternal()) {
                    ListenableFuture future = processInternal(operationTimer);
                    iterations++;
                    if (!future.isDone()) {
                        return updateDriverBlockedFuture(future);
                    }
                    if (System.nanoTime() - start >= maxRuntimeInNanos || iterations >= maxIterations) {
                        break;
                    }
                }
            }
            catch (Throwable t) {
                List interrupterStack = exclusiveLock.getInterrupterStack();
                if (interrupterStack == null) {
                    driverContext.failed(t);
                    throw t;
                }

                // Driver thread was interrupted which should only happen if the task is already finished.
                // If this becomes the actual cause of a failed query there is a bug in the task state machine.
                Exception exception = new Exception("Interrupted By");
                exception.setStackTrace(interrupterStack.toArray(StackTraceElement[]::new));
                TrinoException newException = new TrinoException(GENERIC_INTERNAL_ERROR, "Driver was interrupted", exception);
                newException.addSuppressed(t);
                driverContext.failed(newException);
                throw newException;
            }
            finally {
                driverContext.getYieldSignal().reset();
                driverContext.recordProcessed(operationTimer);
            }
            return NOT_BLOCKED;
        });
        return result.orElse(NOT_BLOCKED);
    }

    private OperationTimer createTimer()
    {
        return new OperationTimer(
                driverContext.isCpuTimerEnabled(),
                driverContext.isCpuTimerEnabled() && driverContext.isPerOperatorCpuTimerEnabled());
    }

    private ListenableFuture updateDriverBlockedFuture(ListenableFuture sourceBlockedFuture)
    {
        // driverBlockedFuture will be completed as soon as the sourceBlockedFuture is completed
        // or any of the operators gets a memory revocation request
        SettableFuture newDriverBlockedFuture = SettableFuture.create();
        driverBlockedFuture.set(newDriverBlockedFuture);
        sourceBlockedFuture.addListener(() -> newDriverBlockedFuture.set(null), directExecutor());

        // it's possible that memory revoking is requested for some operator
        // before we update driverBlockedFuture above and we don't want to miss that
        // notification, so we check to see whether that's the case before returning.
        boolean memoryRevokingRequested = activeOperators.stream()
                .filter(operator -> !revokingOperators.containsKey(operator))
                .map(Operator::getOperatorContext)
                .anyMatch(OperatorContext::isMemoryRevokingRequested);

        if (memoryRevokingRequested) {
            newDriverBlockedFuture.set(null);
        }

        return newDriverBlockedFuture;
    }

    @GuardedBy("exclusiveLock")
    private ListenableFuture processInternal(OperationTimer operationTimer)
    {
        checkLockHeld("Lock must be held to call processInternal");

        handleMemoryRevoke();

        processNewSources();

        // One of the operators is already finished (and removed from activeOperators list). Finish bottommost operator.
        // Some operators (LookupJoinOperator and HashBuildOperator) are broken and requires finish to be called continuously
        // TODO remove the second part of the if statement, when these operators are fixed
        // Note: finish should not be called on the natural source of the pipeline as this could cause the task to finish early
        if (!activeOperators.isEmpty() && activeOperators.size() != allOperators.size()) {
            Operator rootOperator = activeOperators.get(0);
            rootOperator.finish();
            rootOperator.getOperatorContext().recordFinish(operationTimer);
        }

        boolean movedPage = false;
        for (int i = 0; i < activeOperators.size() - 1 && !driverContext.isTerminatingOrDone(); i++) {
            Operator current = activeOperators.get(i);
            Operator next = activeOperators.get(i + 1);

            // skip blocked operator
            if (getBlockedFuture(current).isPresent()) {
                continue;
            }

            // if the current operator is not finished and next operator isn't blocked and needs input...
            if (!current.isFinished() && getBlockedFuture(next).isEmpty() && next.needsInput()) {
                // get an output page from current operator
                Page page = current.getOutput();
                current.getOperatorContext().recordGetOutput(operationTimer, page);

                // if we got an output page, add it to the next operator
                if (page != null && page.getPositionCount() != 0) {
                    next.addInput(page);
                    next.getOperatorContext().recordAddInput(operationTimer, page);
                    movedPage = true;
                }

                if (current instanceof SourceOperator) {
                    movedPage = true;
                }
            }

            // if current operator is finished...
            if (current.isFinished()) {
                // let next operator know there will be no more data
                next.finish();
                next.getOperatorContext().recordFinish(operationTimer);
            }
        }

        for (int index = activeOperators.size() - 1; index >= 0; index--) {
            if (activeOperators.get(index).isFinished()) {
                // close and remove this operator and all source operators
                List finishedOperators = this.activeOperators.subList(0, index + 1);
                Throwable throwable = closeAndDestroyOperators(finishedOperators);
                finishedOperators.clear();
                if (throwable != null) {
                    throwIfUnchecked(throwable);
                    throw new RuntimeException(throwable);
                }
                // Finish the next operator, which is now the first operator.
                if (!activeOperators.isEmpty()) {
                    Operator newRootOperator = activeOperators.get(0);
                    newRootOperator.finish();
                    newRootOperator.getOperatorContext().recordFinish(operationTimer);
                }
                break;
            }
        }

        // if we did not move any pages, check if we are blocked
        if (!movedPage) {
            List blockedOperators = new ArrayList<>();
            List> blockedFutures = new ArrayList<>();
            for (Operator operator : activeOperators) {
                Optional> blocked = getBlockedFuture(operator);
                if (blocked.isPresent()) {
                    blockedOperators.add(operator);
                    blockedFutures.add(blocked.get());
                }
            }

            if (!blockedFutures.isEmpty()) {
                // allow for operators to unblock drivers when they become finished
                for (Operator operator : activeOperators) {
                    operator.getOperatorContext().getFinishedFuture().ifPresent(blockedFutures::add);
                }

                // unblock when the first future is complete
                ListenableFuture blocked = firstFinishedFuture(blockedFutures);
                if (driverContext.getBlockedTimeout().isPresent()) {
                    blocked = withTimeout(
                            nonCancellationPropagating(blocked),
                            driverContext.getBlockedTimeout().get().toMillis(),
                            MILLISECONDS,
                            driverContext.getTimeoutExecutor());
                }
                // driver records serial blocked time
                driverContext.recordBlocked(blocked);
                // each blocked operator is responsible for blocking the execution
                // until one of the operators can continue
                for (Operator operator : blockedOperators) {
                    operator.getOperatorContext().recordBlocked(blocked);
                }
                return blocked;
            }
        }

        return NOT_BLOCKED;
    }

    @GuardedBy("exclusiveLock")
    private void handleMemoryRevoke()
    {
        for (int i = 0; i < activeOperators.size() && !driverContext.isTerminatingOrDone(); i++) {
            Operator operator = activeOperators.get(i);

            if (revokingOperators.containsKey(operator)) {
                checkOperatorFinishedRevoking(operator);
            }
            else if (operator.getOperatorContext().isMemoryRevokingRequested()) {
                ListenableFuture future = operator.startMemoryRevoke();
                revokingOperators.put(operator, future);
                checkOperatorFinishedRevoking(operator);
            }
        }
    }

    @GuardedBy("exclusiveLock")
    private void checkOperatorFinishedRevoking(Operator operator)
    {
        ListenableFuture future = revokingOperators.get(operator);
        if (future.isDone()) {
            getFutureValue(future); // propagate exception if there was some
            revokingOperators.remove(operator);
            operator.finishMemoryRevoke();
            operator.getOperatorContext().resetMemoryRevokingRequested();
        }
    }

    @GuardedBy("exclusiveLock")
    private void destroyIfNecessary()
    {
        checkLockHeld("Lock must be held to call destroyIfNecessary");

        if (!state.compareAndSet(State.NEED_DESTRUCTION, State.DESTROYING)) {
            return;
        }

        // if we get an error while closing a driver, record it and we will throw it at the end
        Throwable inFlightException = null;
        try {
            inFlightException = closeAndDestroyOperators(activeOperators);
            if (driverContext.getMemoryUsage() > 0) {
                log.error("Driver still has memory reserved after freeing all operator memory.");
            }
            if (driverContext.getRevocableMemoryUsage() > 0) {
                log.error("Driver still has revocable memory reserved after freeing all operator memory. Freeing it.");
            }
            driverContext.finished();
        }
        catch (Throwable t) {
            // this shouldn't happen but be safe
            inFlightException = addSuppressedException(
                    inFlightException,
                    t,
                    "Error destroying driver for task %s",
                    driverContext.getTaskId());
        }
        finally {
            // Mark destruction as having completed after driverContext.finished() is complete
            state.set(State.DESTROYED);
        }

        if (inFlightException != null) {
            // this will always be an Error or Runtime
            throwIfUnchecked(inFlightException);
            throw new RuntimeException(inFlightException);
        }
    }

    private Throwable closeAndDestroyOperators(List operators)
    {
        // record the current interrupted status (and clear the flag); we'll reset it later
        boolean wasInterrupted = Thread.interrupted();

        Throwable inFlightException = null;
        try {
            for (Operator operator : operators) {
                try {
                    operator.close();
                }
                catch (InterruptedException t) {
                    // don't record the stack
                    wasInterrupted = true;
                }
                catch (Throwable t) {
                    inFlightException = addSuppressedException(
                            inFlightException,
                            t,
                            "Error closing operator %s for task %s",
                            operator.getOperatorContext().getOperatorId(),
                            driverContext.getTaskId());
                }
                try {
                    operator.getOperatorContext().destroy();
                }
                catch (Throwable t) {
                    inFlightException = addSuppressedException(
                            inFlightException,
                            t,
                            "Error freeing all allocated memory for operator %s for task %s",
                            operator.getOperatorContext().getOperatorId(),
                            driverContext.getTaskId());
                }
            }
        }
        finally {
            // reset the interrupted flag
            if (wasInterrupted) {
                Thread.currentThread().interrupt();
            }
        }
        return inFlightException;
    }

    private Optional> getBlockedFuture(Operator operator)
    {
        ListenableFuture blocked = revokingOperators.get(operator);
        if (blocked != null) {
            // We mark operator as blocked regardless of blocked.isDone(), because finishMemoryRevoke has not been called yet.
            return Optional.of(blocked);
        }
        blocked = operator.isBlocked();
        if (!blocked.isDone()) {
            return Optional.of(blocked);
        }
        blocked = operator.getOperatorContext().isWaitingForMemory();
        if (!blocked.isDone()) {
            return Optional.of(blocked);
        }
        blocked = operator.getOperatorContext().isWaitingForRevocableMemory();
        if (!blocked.isDone()) {
            return Optional.of(blocked);
        }
        return Optional.empty();
    }

    @FormatMethod
    private static Throwable addSuppressedException(Throwable inFlightException, Throwable newException, String message, final Object... args)
    {
        if (newException instanceof Error) {
            if (inFlightException == null) {
                inFlightException = newException;
            }
            else {
                // Self-suppression not permitted
                if (inFlightException != newException) {
                    inFlightException.addSuppressed(newException);
                }
            }
        }
        else {
            // log normal exceptions instead of rethrowing them
            log.error(newException, message, args);
        }
        return inFlightException;
    }

    private synchronized void checkLockNotHeld(String message)
    {
        checkState(!exclusiveLock.isHeldByCurrentThread(), message);
    }

    @GuardedBy("exclusiveLock")
    private synchronized void checkLockHeld(String message)
    {
        checkState(exclusiveLock.isHeldByCurrentThread(), message);
    }

    private static ListenableFuture firstFinishedFuture(List> futures)
    {
        if (futures.size() == 1) {
            return futures.get(0);
        }

        SettableFuture result = SettableFuture.create();

        for (ListenableFuture future : futures) {
            future.addListener(() -> result.set(null), directExecutor());
        }

        return result;
    }

    /**
     * Try to acquire the {@code exclusiveLock} immediately and run a {@code task}
     * The task will not be interrupted if the {@code Driver} is closed.
     * 

* Note: task cannot return null */ private Optional tryWithLockUninterruptibly(Supplier task) { return tryWithLock(0, TimeUnit.MILLISECONDS, false, task); } /** * Try to acquire the {@code exclusiveLock} with {@code timeout} and run a {@code task}. * If the {@code interruptOnClose} flag is set to {@code true} the {@code task} will be * interrupted if the {@code Driver} is closed. *

* Note: task cannot return null */ private Optional tryWithLock(long timeout, TimeUnit unit, boolean interruptOnClose, Supplier task) { checkLockNotHeld("Lock cannot be reacquired"); boolean acquired = false; try { acquired = exclusiveLock.tryLock(timeout, unit, interruptOnClose); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } if (!acquired) { return Optional.empty(); } T result = null; Throwable failure = null; try { result = task.get(); // opportunistic check to avoid unnecessary lock reacquisition processNewSources(); destroyIfNecessary(); } catch (Throwable t) { failure = t; } finally { exclusiveLock.unlock(); } // If there are more assignment updates available, attempt to reacquire the lock and process them. // This can happen if new assignments are added while we're holding the lock here doing work. // NOTE: this is separate duplicate code to make debugging lock reacquisition easier // The first condition is for processing the pending updates if this driver is still ALIVE // The second condition is to destroy the driver if the state is NEED_DESTRUCTION while (((pendingSplitAssignmentUpdates.get() != null && state.get() == State.ALIVE) || state.get() == State.NEED_DESTRUCTION) && exclusiveLock.tryLock(interruptOnClose)) { try { try { processNewSources(); } catch (Throwable t) { if (failure == null) { failure = t; } else if (failure != t) { failure.addSuppressed(t); } } try { destroyIfNecessary(); } catch (Throwable t) { if (failure == null) { failure = t; } else if (failure != t) { failure.addSuppressed(t); } } } finally { exclusiveLock.unlock(); } } if (failure != null) { throwIfUnchecked(failure); // should never happen throw new AssertionError(failure); } verify(result != null, "result is null"); return Optional.of(result); } private static class DriverLock { private final ReentrantLock lock = new ReentrantLock(); private final AtomicReference state; private final SettableFuture destroyedFuture; private DriverLock(AtomicReference state, SettableFuture destroyedFuture) { this.state = requireNonNull(state, "state is null"); this.destroyedFuture = requireNonNull(destroyedFuture, "destroyedFuture is null"); } @GuardedBy("this") private Thread currentOwner; @GuardedBy("this") private boolean currentOwnerInterruptionAllowed; @GuardedBy("this") private List interrupterStack; public boolean isHeldByCurrentThread() { return lock.isHeldByCurrentThread(); } public boolean tryLock(boolean currentThreadInterruptionAllowed) { checkState(!lock.isHeldByCurrentThread(), "Lock is not reentrant"); boolean acquired = lock.tryLock(); if (acquired) { setOwner(currentThreadInterruptionAllowed); } return acquired; } public boolean tryLock(long timeout, TimeUnit unit, boolean currentThreadInterruptionAllowed) throws InterruptedException { checkState(!lock.isHeldByCurrentThread(), "Lock is not reentrant"); boolean acquired = lock.tryLock(timeout, unit); if (acquired) { setOwner(currentThreadInterruptionAllowed); } return acquired; } private synchronized void setOwner(boolean interruptionAllowed) { checkState(lock.isHeldByCurrentThread(), "Current thread does not hold lock"); currentOwner = Thread.currentThread(); currentOwnerInterruptionAllowed = interruptionAllowed; // NOTE: We do not use interrupted stack information to know that another // thread has attempted to interrupt the driver, and interrupt this new lock // owner. The interrupted stack information is for debugging purposes only. // In the case of interruption, the caller should (and does) have a separate // state to prevent further processing in the Driver. } public void unlock() { checkState(lock.isHeldByCurrentThread(), "Current thread does not hold lock"); synchronized (this) { currentOwner = null; currentOwnerInterruptionAllowed = false; } lock.unlock(); // Set the destroyed signal after releasing the lock since callbacks are fired synchronously and // otherwise could cause a deadlock if (state.get() == State.DESTROYED) { destroyedFuture.set(null); } } public synchronized List getInterrupterStack() { return interrupterStack; } public synchronized void interruptCurrentOwner() { if (!currentOwnerInterruptionAllowed) { return; } // there is a benign race condition here were the lock holder // can be change between attempting to get lock and grabbing // the synchronized lock here, but in either case we want to // interrupt the lock holder thread if (interrupterStack == null) { interrupterStack = ImmutableList.copyOf(Thread.currentThread().getStackTrace()); } if (currentOwner != null) { currentOwner.interrupt(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy