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

org.elasticsearch.compute.operator.DriverContext Maven / Gradle / Ivy

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */

package org.elasticsearch.compute.operator;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.SubscribableListener;
import org.elasticsearch.common.breaker.CircuitBreaker;
import org.elasticsearch.common.breaker.NoopCircuitBreaker;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.compute.data.BlockFactory;
import org.elasticsearch.core.Releasable;
import org.elasticsearch.core.Releasables;

import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A driver-local context that is shared across operators.
 *
 * Operators in the same driver pipeline are executed in a single threaded fashion. A driver context
 * has a set of mutating methods that can be used to store and share values across these operators,
 * or even outside the Driver. When the Driver is finished, it finishes the context. Finishing the
 * context effectively takes a snapshot of the driver context values so that they can be exposed
 * outside the Driver. The net result of this is that the driver context can be mutated freely,
 * without contention, by the thread executing the pipeline of operators until it is finished.
 * The context must be finished by the thread running the Driver, when the Driver is finished.
 *
 * Releasables can be added and removed to the context by operators in the same driver pipeline.
 * This allows to "transfer ownership" of a shared resource across operators (and even across
 * Drivers), while ensuring that the resource can be correctly released when no longer needed.
 *
 * DriverContext can also be used to track async actions. The driver may close an operator while
 * some of its async actions are still running. To prevent the driver from finishing in this case,
 * methods {@link #addAsyncAction()} and {@link #removeAsyncAction()} are provided for tracking
 * such actions. Subsequently, the driver uses {@link #waitForAsyncActions(ActionListener)} to
 * await the completion of all async actions before finalizing the Driver.
 */
public class DriverContext {

    // Working set. Only the thread executing the driver will update this set.
    Set workingSet = Collections.newSetFromMap(new IdentityHashMap<>());

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

    private final BigArrays bigArrays;

    private final BlockFactory blockFactory;

    private final AsyncActions asyncActions = new AsyncActions();

    private final WarningsMode warningsMode;

    public DriverContext(BigArrays bigArrays, BlockFactory blockFactory) {
        this(bigArrays, blockFactory, WarningsMode.COLLECT);
    }

    private DriverContext(BigArrays bigArrays, BlockFactory blockFactory, WarningsMode warningsMode) {
        Objects.requireNonNull(bigArrays);
        Objects.requireNonNull(blockFactory);
        this.bigArrays = bigArrays;
        this.blockFactory = blockFactory;
        this.warningsMode = warningsMode;
    }

    public static DriverContext getLocalDriver() {
        return new DriverContext(
            BigArrays.NON_RECYCLING_INSTANCE,
            // TODO maybe this should have a small fixed limit?
            new BlockFactory(new NoopCircuitBreaker(CircuitBreaker.REQUEST), BigArrays.NON_RECYCLING_INSTANCE)
        );
    }

    public BigArrays bigArrays() {
        return bigArrays;
    }

    /**
     * The {@link CircuitBreaker} to use to track memory.
     */
    public CircuitBreaker breaker() {
        return blockFactory.breaker();
    }

    public BlockFactory blockFactory() {
        return blockFactory;
    }

    /** A snapshot of the driver context. */
    public record Snapshot(Set releasables) implements Releasable {
        @Override
        public void close() {
            Releasables.close(releasables);
        }
    }

    /**
     * Adds a releasable to this context. Releasables are identified by Object identity.
     * @return true if the releasable was added, otherwise false (if already present)
     */
    public boolean addReleasable(Releasable releasable) {
        return workingSet.add(releasable);
    }

    /**
     * Removes a releasable from this context. Releasables are identified by Object identity.
     * @return true if the releasable was removed, otherwise false (if not present)
     */
    public boolean removeReleasable(Releasable releasable) {
        return workingSet.remove(releasable);
    }

    /**
     * Retrieves the snapshot of the driver context after it has been finished.
     * @return the snapshot
     */
    public Snapshot getSnapshot() {
        ensureFinished();
        // should be called by the DriverRunner
        return snapshot.get();
    }

    /**
     * Tells whether this context is finished. Can be invoked from any thread.
     */
    public boolean isFinished() {
        return snapshot.get() != null;
    }

    /**
     * Finishes this context. Further mutating operations should not be performed.
     */
    public void finish() {
        if (isFinished()) {
            return;
        }
        // must be called by the thread executing the driver.
        // no more updates to this context.
        asyncActions.finish();
        var itr = workingSet.iterator();
        workingSet = null;
        Set releasableSet = Collections.newSetFromMap(new IdentityHashMap<>());
        while (itr.hasNext()) {
            var r = itr.next();
            releasableSet.add(r);
            itr.remove();
        }
        snapshot.compareAndSet(null, new Snapshot(releasableSet));
    }

    private void ensureFinished() {
        if (isFinished() == false) {
            throw new IllegalStateException("not finished");
        }
    }

    public void waitForAsyncActions(ActionListener listener) {
        asyncActions.addListener(listener);
    }

    public void addAsyncAction() {
        asyncActions.addInstance();
    }

    public void removeAsyncAction() {
        asyncActions.removeInstance();
    }

    /**
     * Evaluators should use this function to decide their warning behavior.
     * @return an appropriate {@link WarningsMode}
     */
    public WarningsMode warningsMode() {
        return warningsMode;
    }

    /**
     * Indicates the behavior Evaluators of this context should use for reporting warnings
     */
    public enum WarningsMode {
        COLLECT,
        IGNORE
    }

    private static class AsyncActions {
        private final SubscribableListener completion = new SubscribableListener<>();
        private final AtomicBoolean finished = new AtomicBoolean();
        private final AtomicInteger instances = new AtomicInteger(1);

        void addInstance() {
            if (finished.get()) {
                throw new IllegalStateException("DriverContext was finished already");
            }
            instances.incrementAndGet();
        }

        void removeInstance() {
            if (instances.decrementAndGet() == 0) {
                completion.onResponse(null);
            }
        }

        void addListener(ActionListener listener) {
            completion.addListener(listener);
        }

        void finish() {
            if (finished.compareAndSet(false, true)) {
                removeInstance();
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy