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

com.oracle.truffle.api.impl.ThreadLocalHandshake Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.oracle.truffle.api.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleSafepoint;
import com.oracle.truffle.api.nodes.Node;

/**
 * Implementation class for thread local handshakes. Contains the parts that can be shared between
 * runtimes.
 */
public abstract class ThreadLocalHandshake {

    /*
     * This map contains all state objects for all threads accessible for other threads. Since the
     * thread needs to be weak and synchronized it is less efficient to access and is only used when
     * accessing the state of other threads.
     */
    private static final Map SAFEPOINTS = Collections.synchronizedMap(new WeakHashMap<>());

    static void resetNativeImageState() {
        for (TruffleSafepointImpl impl : SAFEPOINTS.values()) {
            impl.verifyUnused();
        }
        SAFEPOINTS.clear();
    }

    protected ThreadLocalHandshake() {
    }

    public abstract void poll(Node enclosingNode);

    public abstract TruffleSafepointImpl getCurrent();

    protected boolean isSupported() {
        return true;
    }

    public void testSupport() {
        if (!isSupported()) {
            throw new UnsupportedOperationException("Thread local handshakes are not supported on this platform. " +
                            "A possible reason may be that the underlying JVMCI version is too old.");
        }
    }

    public void setChangeAllowActions(TruffleSafepoint safepoint, boolean enabled) {
        ((TruffleSafepointImpl) safepoint).setChangeAllowActions(enabled);
    }

    public boolean isAllowActions(TruffleSafepoint safepoint) {
        return ((TruffleSafepointImpl) safepoint).isAllowActions();
    }

    /**
     * If this method is invoked the thread must be guaranteed to be polled. If the thread dies and
     * {@link #poll(Node)} was not invoked then an {@link IllegalStateException} is thrown;
     */
    @TruffleBoundary
    public final > Future runThreadLocal(Thread[] threads, T onThread,
                    Consumer onDone, boolean sideEffecting, boolean syncStartOfEvent, boolean syncEndOfEvent) {
        testSupport();
        assert threads.length > 0;
        Handshake handshake = new Handshake<>(threads, onThread, onDone, sideEffecting, threads.length, syncStartOfEvent, syncEndOfEvent);
        if (syncStartOfEvent || syncEndOfEvent) {
            synchronized (ThreadLocalHandshake.class) {
                addHandshakes(threads, handshake);
            }
        } else {
            addHandshakes(threads, handshake);
        }
        return handshake;
    }

    private > void addHandshakes(Thread[] threads, Handshake handshake) {
        for (int i = 0; i < threads.length; i++) {
            Thread t = threads[i];
            if (!t.isAlive()) {
                throw new IllegalStateException("Thread no longer alive with pending handshake.");
            }
            getThreadState(t).addHandshake(t, handshake);
        }
    }

    @SuppressWarnings("static-method")
    public final boolean activateThread(TruffleSafepoint s, Future f) {
        return ((TruffleSafepointImpl) s).activateThread((Handshake) f);
    }

    @SuppressWarnings("static-method")
    public final boolean deactivateThread(TruffleSafepoint s, Future f) {
        return ((TruffleSafepointImpl) s).deactivateThread((Handshake) f);
    }

    public void ensureThreadInitialized() {
    }

    protected abstract void setFastPending(Thread t);

    @TruffleBoundary
    protected final void processHandshake(Node location) {
        TruffleSafepointImpl s = getCurrent();
        if (s.fastPendingSet) {
            s.processHandshakes(location, s.takeHandshakes());
        }
    }

    protected abstract void clearFastPending();

    private static Throwable combineThrowable(Throwable current, Throwable t) {
        if (current == null) {
            return t;
        }
        if (t instanceof ThreadDeath) {
            t.addSuppressed(current);
            return t;
        } else {
            current.addSuppressed(t);
            return current;
        }
    }

    @SuppressWarnings("unchecked")
    private static  RuntimeException sneakyThrow(Throwable ex) throws T {
        throw (T) ex;
    }

    public static final class Handshake> implements Future {

        private final boolean sideEffecting;
        private final Phaser phaser;
        private volatile boolean cancelled;
        private final T action;
        private final boolean syncStartOfEvent;
        private final boolean syncEndOfEvent;
        // avoid rescheduling processed events on the same thread
        private final Map threads;
        private final Consumer onDone;

        Handshake(Thread[] initialThreads, T action, Consumer onDone, boolean sideEffecting, int numberOfThreads, boolean syncStartOfEvent, boolean syncEndOfEvent) {
            this.action = action;
            this.onDone = onDone;
            this.sideEffecting = sideEffecting;
            this.syncStartOfEvent = syncStartOfEvent;
            this.syncEndOfEvent = syncEndOfEvent;
            this.phaser = new Phaser(numberOfThreads);
            /*
             * Mark the handshake for all initial threads as active (not deactivated).
             */
            this.threads = new ConcurrentHashMap<>(Arrays.stream(initialThreads).collect(Collectors.toMap(t -> t, t -> Boolean.FALSE)));
        }

        @Override
        public boolean isCancelled() {
            return cancelled;
        }

        void perform(Node node) {
            try {
                if (syncStartOfEvent) {
                    phaser.arriveAndAwaitAdvance();
                }
                if (!cancelled) {
                    action.accept(node);
                }
            } finally {
                phaser.arriveAndDeregister();

                if (syncEndOfEvent) {
                    phaser.awaitAdvance(syncStartOfEvent ? 1 : 0);
                    assert phaser.isTerminated();
                }

                if (phaser.isTerminated()) {
                    onDone.accept(action);
                }
            }
        }

        boolean activateThread() {
            int result = phaser.register();
            if (result != 0) {
                // did not activate on time.
                phaser.arriveAndDeregister();
                return false;
            }
            return true;
        }

        void deactivateThread() {
            phaser.arriveAndDeregister();
            if (phaser.isTerminated()) {
                onDone.accept(action);
            }
        }

        @Override
        public Void get() throws InterruptedException {
            if (syncStartOfEvent) {
                phaser.awaitAdvanceInterruptibly(0);
                phaser.awaitAdvanceInterruptibly(1);
            } else {
                phaser.awaitAdvanceInterruptibly(0);
            }
            return null;
        }

        public Void get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
            if (syncStartOfEvent) {
                phaser.awaitAdvanceInterruptibly(0, timeout, unit);
                phaser.awaitAdvanceInterruptibly(1, timeout, unit);
            } else {
                phaser.awaitAdvanceInterruptibly(0, timeout, unit);
            }
            return null;
        }

        public boolean isDone() {
            return cancelled || phaser.isTerminated();
        }

        public boolean cancel(boolean mayInterruptIfRunning) {
            if (!phaser.isTerminated()) {
                cancelled = true;
                return true;
            } else {
                return false;
            }
        }

        @Override
        public String toString() {
            return "Handshake[action=" + action + ", phaser=" + phaser + ", cancelled=" + cancelled + ", sideEffecting=" + sideEffecting + ", syncStartOfEvent=" + syncStartOfEvent +
                            ", syncEndOfEvent=" + syncEndOfEvent + "]";
        }

    }

    static final class HandshakeEntry {

        final Handshake handshake;
        final boolean reactivated;

        HandshakeEntry(Handshake handshake, boolean reactivated) {
            this.handshake = handshake;
            this.reactivated = reactivated;
        }

        @Override
        public String toString() {
            return "HandshakeEntry[" + handshake + " reactivated=" + reactivated + "]";
        }
    }

    protected final TruffleSafepointImpl getThreadState(Thread thread) {
        return SAFEPOINTS.computeIfAbsent(thread, (t) -> new TruffleSafepointImpl(this));
    }

    protected static final class TruffleSafepointImpl extends TruffleSafepoint {

        private final ReentrantLock lock = new ReentrantLock();
        private final ThreadLocalHandshake impl;
        private volatile boolean fastPendingSet;
        private boolean sideEffectsEnabled = true;
        private boolean enabled = true;
        private volatile boolean changeAllowActionsAllowed;
        private Interrupter blockedAction;
        private boolean interrupted;

        private final LinkedList handshakes = new LinkedList<>();

        TruffleSafepointImpl(ThreadLocalHandshake handshake) {
            super(DefaultRuntimeAccessor.ENGINE);
            this.impl = handshake;
        }

        void verifyUnused() throws AssertionError {
            if (this.lock.isHeldByCurrentThread() || this.lock.isLocked()) {
                throw new AssertionError("Invalid locked state for safepoint.");
            }
            this.lock.lock();
            try {
                if (this.blockedAction != null) {
                    throw new AssertionError("Invalid pending blocked action.");
                }
                if (this.interrupted) {
                    throw new AssertionError("Invalid pending interrupted state.");
                }
                if (this.isPending()) {
                    throw new AssertionError("Invalid pending handshakes.");
                }
                // correct usage always needs to reset the side-effects enabled state
                if (!this.sideEffectsEnabled) {
                    throw new AssertionError("Invalid side-effects disabled state");
                }

                if (!this.enabled) {
                    throw new AssertionError("Invalid allow actions disabled state");
                }
            } finally {
                this.lock.unlock();
            }
        }

        void processHandshakes(Node location, List toProcess) {
            if (toProcess == null) {
                return;
            }
            Throwable ex = null;
            for (HandshakeEntry current : toProcess) {
                if (claimEntry(current)) {
                    try {
                        current.handshake.perform(location);
                    } catch (Throwable e) {
                        ex = combineThrowable(ex, e);
                    }
                }
            }
            if (fastPendingSet) {
                resetPending();
            }
            if (ex != null) {
                throw sneakyThrow(ex);
            }
        }

        public boolean deactivateThread(Handshake handshake) {
            lock.lock();
            try {
                HandshakeEntry current = lookupEntry(handshake);
                if (current != null) {
                    /*
                     * We cannot guarantee that side-effecting events are processed as they can be
                     * disabled.
                     */
                    assert !current.reactivated || current.handshake.sideEffecting : "Reactivated handshake was not processed!";
                    handshake.deactivateThread();
                    claimEntry(current);
                    /*
                     * Mark the handshake for the current thread as deactivated.
                     */
                    handshake.threads.put(Thread.currentThread(), Boolean.TRUE);
                    resetPending();
                    return true;
                }

            } finally {
                lock.unlock();
            }
            return false;
        }

        public boolean activateThread(Handshake handshake) {
            if (handshake.isDone()) {
                return false;
            }
            lock.lock();
            try {
                HandshakeEntry current = lookupEntry(handshake);
                if (current != null) {
                    /*
                     * The handshake has already been put to this thread and it is ready to be
                     * processed.
                     */
                    return false;
                }
                boolean reactivated = false;
                if (handshake.threads.containsKey(Thread.currentThread())) {
                    if (!handshake.threads.get(Thread.currentThread())) {
                        /*
                         * The handshake has already been processed.
                         */
                        return false;
                    } else {
                        /*
                         * The handshake has been deactivated before it was processed and should be
                         * reactivated.
                         */
                        reactivated = true;
                    }
                }
                /*
                 * Mark the handshake for the current thread as active (not deactivated).
                 */
                handshake.threads.put(Thread.currentThread(), Boolean.FALSE);
                if (handshake.activateThread()) {
                    addHandshakeImpl(Thread.currentThread(), handshake, reactivated);
                    return true;
                }
            } finally {
                lock.unlock();
            }
            return false;
        }

        private HandshakeEntry lookupEntry(Handshake handshake) {
            assert lock.isHeldByCurrentThread();

            for (HandshakeEntry entry : handshakes) {
                if (entry.handshake == handshake) {
                    return entry;
                }
            }
            return null;
        }

        void addHandshake(Thread t, Handshake handshake) {
            lock.lock();
            try {
                addHandshakeImpl(t, handshake, false);
            } finally {
                lock.unlock();
            }
        }

        private void addHandshakeImpl(Thread t, Handshake handshake, boolean reactivated) {
            handshakes.add(new HandshakeEntry(handshake, reactivated));
            if (isPending()) {
                setFastPendingAndInterrupt(t);
            }
        }

        private void setFastPendingAndInterrupt(Thread t) {
            assert lock.isHeldByCurrentThread();
            if (!fastPendingSet) {
                fastPendingSet = true;
                impl.setFastPending(t);
            }
            Interrupter action = this.blockedAction;
            if (action != null) {
                interrupted = true;
                action.interrupt(t);
            }
        }

        List takeHandshakes() {
            lock.lock();
            try {
                if (this.interrupted) {
                    this.blockedAction.resetInterrupted();
                    this.interrupted = false;
                }
                if (isPending()) {
                    List taken = takeHandshakeImpl();
                    assert !taken.isEmpty();
                    return taken;
                }
                return null;
            } finally {
                lock.unlock();
            }
        }

        private void resetPending() {
            lock.lock();
            try {
                if (fastPendingSet && !isPending()) {
                    fastPendingSet = false;
                    impl.clearFastPending();
                }
            } finally {
                lock.unlock();
            }
        }

        private boolean claimEntry(HandshakeEntry entry) {
            lock.lock();
            try {
                return this.handshakes.removeFirstOccurrence(entry);
            } finally {
                lock.unlock();
            }
        }

        private List takeHandshakeImpl() {
            if (!enabled) {
                return Collections.emptyList();
            }
            List toProcess = new ArrayList<>(this.handshakes.size());
            for (HandshakeEntry entry : this.handshakes) {
                if (isPending(entry)) {
                    toProcess.add(entry);
                }
            }
            return toProcess;
        }

        private boolean isPending(HandshakeEntry entry) {
            if (sideEffectsEnabled || !entry.handshake.sideEffecting) {
                return true;
            }
            return false;
        }

        @Override
        public  void setBlockedWithException(Node location, Interrupter interrupter, Interruptible interruptible, T object, Runnable beforeInterrupt, Consumer afterInterrupt) {
            assert impl.getCurrent() == this : "Cannot be used from a different thread.";

            /*
             * We want to avoid to ever call the Interruptible interface on compiled code paths to
             * make native image avoid marking it as runtime compiled. It is common that
             * interruptibles are just a method reference to Lock::lockInterruptibly which could no
             * longer be used otherwise as PE would fail badly for these methods and we would get
             * black list method errors in native image.
             *
             * A good workaround is to use our own interface that is a subclass of Interruptible but
             * that must be used to opt-in to compilation.
             */
            if (CompilerDirectives.inCompiledCode() && CompilerDirectives.isPartialEvaluationConstant(interruptible) && interruptible instanceof CompiledInterruptible) {
                setBlockedCompiled(location, interrupter, (CompiledInterruptible) interruptible, object, beforeInterrupt, afterInterrupt);
            } else {
                setBlockedBoundary(location, interrupter, interruptible, object, beforeInterrupt, afterInterrupt);
            }
        }

        private  void setBlockedCompiled(Node location, Interrupter interrupter, CompiledInterruptible interruptible, T object, Runnable beforeInterrupt, Consumer afterInterrupt) {
            Interrupter prev = this.blockedAction;
            try {
                while (true) {
                    try {
                        setBlockedImpl(location, interrupter, false);
                        interruptible.apply(object);
                        break;
                    } catch (InterruptedException e) {
                        setBlockedAfterInterrupt(location, prev, beforeInterrupt, afterInterrupt);
                        continue;
                    }
                }
            } finally {
                setBlockedImpl(location, prev, false);
            }
        }

        @TruffleBoundary
        private  void setBlockedBoundary(Node location, Interrupter interrupter, Interruptible interruptible, T object, Runnable beforeInterrupt, Consumer afterInterrupt) {
            Interrupter prev = this.blockedAction;
            try {
                while (true) {
                    try {
                        setBlockedImpl(location, interrupter, false);
                        interruptible.apply(object);
                        break;
                    } catch (InterruptedException e) {
                        setBlockedAfterInterrupt(location, prev, beforeInterrupt, afterInterrupt);
                        continue;
                    }
                }
            } finally {
                setBlockedImpl(location, prev, false);
            }
        }

        @TruffleBoundary
        private void setBlockedAfterInterrupt(final Node location, final Interrupter interrupter, Runnable beforeInterrupt, Consumer afterInterrupt) {
            if (beforeInterrupt != null) {
                beforeInterrupt.run();
            }
            Throwable t = null;
            try {
                setBlockedImpl(location, interrupter, true);
            } catch (Throwable e) {
                t = e;
                throw e;
            } finally {
                if (afterInterrupt != null) {
                    afterInterrupt.accept(t);
                }
            }
        }

        @TruffleBoundary
        private void setBlockedImpl(final Node location, final Interrupter interrupter, boolean processSafepoints) {
            List toProcess = null;
            lock.lock();
            try {
                if (processSafepoints) {
                    if (isPending()) {
                        toProcess = takeHandshakeImpl();
                    }
                }
                if (interrupted) {
                    assert this.blockedAction != null;
                    this.blockedAction.resetInterrupted();
                    this.interrupted = false;
                }
                this.blockedAction = interrupter;
            } finally {
                lock.unlock();
            }

            processHandshakes(location, toProcess);

            if (interrupter != null) {
                /*
                 * We can only process once. Now we need to continue running, but interrupt.
                 */
                interruptIfPending(interrupter);
            }
        }

        private void interruptIfPending(final Interrupter interrupter) {
            lock.lock();
            try {
                if (interrupter != null && isPending()) {
                    interrupted = true;
                    interrupter.interrupt(Thread.currentThread());
                }
            } finally {
                lock.unlock();
            }
        }

        /**
         * Is a handshake really pending?
         */
        private boolean isPending() {
            assert lock.isHeldByCurrentThread();
            if (!enabled) {
                return false;
            }
            for (HandshakeEntry entry : this.handshakes) {
                if (isPending(entry)) {
                    return true;
                }
            }
            return false;
        }

        void setChangeAllowActions(boolean changeAllowActionsAllowed) {
            this.changeAllowActionsAllowed = changeAllowActionsAllowed;
        }

        boolean isAllowActions() {
            return enabled;
        }

        @Override
        @TruffleBoundary
        public boolean setAllowActions(boolean enabled) {
            assert impl.getCurrent() == this : "Cannot be used from a different thread.";
            lock.lock();
            try {
                if (!changeAllowActionsAllowed) {
                    throw new IllegalStateException("Using setAllowActions is only permitted during finalization of a language. See TruffleLanguage.finalizeContext(Object) for further details.");
                }
                boolean prev = this.enabled;
                this.enabled = enabled;
                updateFastPending();
                return prev;
            } finally {
                lock.unlock();
            }
        }

        @Override
        @TruffleBoundary
        public boolean setAllowSideEffects(boolean enabled) {
            assert impl.getCurrent() == this : "Cannot be used from a different thread.";
            lock.lock();
            try {
                boolean prev = this.sideEffectsEnabled;
                this.sideEffectsEnabled = enabled;
                updateFastPending();
                return prev;
            } finally {
                lock.unlock();
            }
        }

        @Override
        @TruffleBoundary
        public boolean hasPendingSideEffectingActions() {
            assert impl.getCurrent() == this : "Cannot be used from a different thread.";
            lock.lock();
            try {
                return !sideEffectsEnabled && hasSideEffecting();
            } finally {
                lock.unlock();
            }
        }

        private boolean hasSideEffecting() {
            assert lock.isHeldByCurrentThread();

            for (HandshakeEntry entry : this.handshakes) {
                if (entry.handshake.sideEffecting) {
                    return true;
                }
            }
            return false;
        }

        private void updateFastPending() {
            if (isPending()) {
                setFastPendingAndInterrupt(Thread.currentThread());
            } else {
                if (fastPendingSet) {
                    fastPendingSet = false;
                    impl.clearFastPending();
                }
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy