Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.pkl.thirdparty.truffle.polyglot.PolyglotContextImpl Maven / Gradle / Ivy
Go to download
Fat Jar containing pkl-cli, pkl-codegen-java, pkl-codegen-kotlin, pkl-config-java, pkl-core, pkl-doc, and their shaded third-party dependencies.
/*
* Copyright (c) 2017, 2022, 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 org.pkl.thirdparty.truffle.polyglot;
import static org.pkl.thirdparty.truffle.api.CompilerDirectives.shouldNotReachHere;
import static org.pkl.thirdparty.truffle.polyglot.EngineAccessor.LANGUAGE;
import static org.pkl.thirdparty.truffle.polyglot.PolyglotValueDispatch.hostEnter;
import static org.pkl.thirdparty.truffle.polyglot.PolyglotValueDispatch.hostLeave;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.logging.Level;
import org.pkl.thirdparty.graalvm.options.OptionValues;
import org.pkl.thirdparty.graalvm.polyglot.Context;
import org.pkl.thirdparty.graalvm.polyglot.PolyglotException;
import org.pkl.thirdparty.graalvm.polyglot.Value;
import org.pkl.thirdparty.graalvm.polyglot.impl.AbstractPolyglotImpl.AbstractHostLanguageService;
import org.pkl.thirdparty.graalvm.polyglot.io.IOAccess;
import org.pkl.thirdparty.truffle.api.CallTarget;
import org.pkl.thirdparty.truffle.api.CompilerAsserts;
import org.pkl.thirdparty.truffle.api.CompilerDirectives;
import org.pkl.thirdparty.truffle.api.CompilerDirectives.CompilationFinal;
import org.pkl.thirdparty.truffle.api.CompilerDirectives.TruffleBoundary;
import org.pkl.thirdparty.truffle.api.ThreadLocalAction;
import org.pkl.thirdparty.truffle.api.Truffle;
import org.pkl.thirdparty.truffle.api.TruffleContext;
import org.pkl.thirdparty.truffle.api.TruffleLanguage;
import org.pkl.thirdparty.truffle.api.TruffleLogger;
import org.pkl.thirdparty.truffle.api.TruffleOptions;
import org.pkl.thirdparty.truffle.api.TruffleSafepoint;
import org.pkl.thirdparty.truffle.api.exception.AbstractTruffleException;
import org.pkl.thirdparty.truffle.api.impl.DefaultTruffleRuntime;
import org.pkl.thirdparty.truffle.api.impl.JDKAccessor;
import org.pkl.thirdparty.truffle.api.interop.InteropLibrary;
import org.pkl.thirdparty.truffle.api.interop.TruffleObject;
import org.pkl.thirdparty.truffle.api.interop.UnsupportedMessageException;
import org.pkl.thirdparty.truffle.api.nodes.LanguageInfo;
import org.pkl.thirdparty.truffle.api.nodes.Node;
import org.pkl.thirdparty.truffle.api.nodes.RootNode;
import org.pkl.thirdparty.truffle.api.source.Source;
import org.pkl.thirdparty.truffle.api.source.SourceSection;
import org.pkl.thirdparty.truffle.polyglot.FileSystems.PreInitializeContextFileSystem;
import org.pkl.thirdparty.truffle.polyglot.PolyglotContextConfig.FileSystemConfig;
import org.pkl.thirdparty.truffle.polyglot.PolyglotContextConfig.PreinitConfig;
import org.pkl.thirdparty.truffle.polyglot.PolyglotEngineImpl.CancelExecution;
import org.pkl.thirdparty.truffle.polyglot.PolyglotEngineImpl.StableLocalLocations;
import org.pkl.thirdparty.truffle.polyglot.PolyglotLanguageContext.ValueMigrationException;
import org.pkl.thirdparty.truffle.polyglot.PolyglotLocals.LocalLocation;
import org.pkl.thirdparty.truffle.polyglot.PolyglotThreadLocalActions.HandshakeConfig;
import org.pkl.thirdparty.truffle.polyglot.SystemThread.LanguageSystemThread;
final class PolyglotContextImpl implements org.pkl.thirdparty.truffle.polyglot.PolyglotImpl.VMObject {
private static final TruffleLogger LOG = TruffleLogger.getLogger(PolyglotEngineImpl.OPTION_GROUP_ENGINE, PolyglotContextImpl.class);
private static final InteropLibrary UNCACHED = InteropLibrary.getFactory().getUncached();
private static final Object[] DISPOSED_CONTEXT_THREAD_LOCALS = new Object[0];
private static final Map VALID_TRANSITIONS = new EnumMap<>(State.class);
static {
VALID_TRANSITIONS.put(State.DEFAULT, new State[]{
State.CLOSING,
State.INTERRUPTING,
State.PENDING_EXIT,
State.CANCELLING,
State.EXITING, // only for child contexts and local contexts for isolated
// contexts
});
VALID_TRANSITIONS.put(State.CLOSING, new State[]{
State.CLOSING_FINALIZING,
State.CLOSING_INTERRUPTING,
State.CLOSING_CANCELLING,
State.CLOSING_PENDING_EXIT,
State.CLOSING_EXITING, // only for child contexts and local contexts for
// isolated contexts
State.DEFAULT
});
VALID_TRANSITIONS.put(State.CLOSING_FINALIZING, new State[]{
State.CLOSED,
State.CLOSING_INTERRUPTING_FINALIZING,
State.CLOSING_CANCELLING,
State.CLOSING_EXITING, // only for child contexts and local contexts for
// isolated contexts
State.DEFAULT
});
VALID_TRANSITIONS.put(State.INTERRUPTING, new State[]{
State.DEFAULT,
State.CLOSING_INTERRUPTING,
State.CANCELLING,
State.PENDING_EXIT,
State.EXITING, // only for child contexts and local contexts for isolated
// contexts
});
VALID_TRANSITIONS.put(State.PENDING_EXIT, new State[]{
State.EXITING,
State.CANCELLING
});
VALID_TRANSITIONS.put(State.CANCELLING, new State[]{
State.CLOSING_CANCELLING
});
VALID_TRANSITIONS.put(State.CLOSING_INTERRUPTING, new State[]{
State.CLOSING_INTERRUPTING_FINALIZING,
State.CLOSING,
State.CLOSING_PENDING_EXIT,
State.CLOSING_CANCELLING,
State.CLOSING_EXITING, // only for child contexts and local contexts for
// isolate contexts
State.INTERRUPTING
});
VALID_TRANSITIONS.put(State.CLOSING_INTERRUPTING_FINALIZING, new State[]{
State.CLOSED_INTERRUPTED,
State.CLOSING_FINALIZING,
State.CLOSING_CANCELLING,
State.CLOSING_EXITING, // only for child contexts and local contexts for
// isolated contexts
State.INTERRUPTING
});
VALID_TRANSITIONS.put(State.CLOSING_CANCELLING, new State[]{
State.CLOSED_CANCELLED,
State.CANCELLING
});
VALID_TRANSITIONS.put(State.CLOSING_PENDING_EXIT, new State[]{
State.CLOSING_EXITING,
State.CLOSING_CANCELLING,
State.PENDING_EXIT
});
VALID_TRANSITIONS.put(State.CLOSING_EXITING, new State[]{
State.CLOSED_EXITED,
State.EXITING
});
VALID_TRANSITIONS.put(State.EXITING, new State[]{
State.CLOSING_EXITING
});
VALID_TRANSITIONS.put(State.CLOSED,
new State[0]);
VALID_TRANSITIONS.put(State.CLOSED_CANCELLED,
new State[0]);
VALID_TRANSITIONS.put(State.CLOSED_EXITED,
new State[0]);
}
enum State {
/*
* Initial state. Context is valid and ready for use.
*/
DEFAULT,
/*
* Interrupt operation has been started. Threads are being interrupted.
*/
INTERRUPTING,
/*
* Hard exit was called in the DEFAULT or the INTERRUPTING state and exit notifications (see
* TruffleLanguage#exitContext) are about to be executed or are already executing. the
* PENDING_EXIT state overrides the INTERRUPTING state.
*/
PENDING_EXIT,
/*
* Exit operation has been initiated after exit notifications were executed in the
* PENDING_EXIT state. Threads are being stopped.
*/
EXITING,
/*
* Cancel operation has been initiated. Threads are being stopped. The CANCELLING state
* overrides the INTERRUPTING and the PENDING_EXIT state.
*/
CANCELLING,
/*
* Close operation has been initiated in the DEFAULT state, or it has been initiated in the
* INTERRUPTING state and the interrupt operation stopped during closing. The thread that
* initiated the operation is stored in the closingThread field. The close operation either
* finishes successfully and the context goes into one of the closed states, or the close
* operation fails and the context goes back to the DEFAULT state.
*/
CLOSING,
/*
* Hard exit was called in the CLOSING or the CLOSING_INTERRUPTING state and exit
* notifications (see TruffleLanguage#exitContext) are about to be executed or are already
* executing. the CLOSING_PENDING_EXIT state overrides the CLOSING_INTERRUPTING state.
*/
CLOSING_PENDING_EXIT,
/*
* The close operation progressed to the "finalizing" stage where creation of inner contexts
* and caching of thread info is no longer allowed. Also, hard exit is no longer allowed in
* this state. The close operation either finishes successfully and the context goes into
* one of the closed states, or the close operation fails and the context goes back to the
* DEFAULT state.
*/
CLOSING_FINALIZING,
/*
* Close operation has been initiated in the INTERRUPTING state and the interrupt operation
* is still in progress, or it has been initiated in the DEFAULT state and the interrupt
* operation started during closing, i.e., the transition to this state can either be from
* the CLOSING or the INTERRUPTING state. Even if the transition is from the CLOSING state
* the closingThread is still the one that initiated the close operation, not the one that
* initiated the interrupt operation. The close operation either finishes successfully and
* the context goes into one of the closed states, or the close operation fails and the
* context goes back to the INTERRUPTING state.
*/
CLOSING_INTERRUPTING,
/*
* The close operation while interrupting progressed to the "finalizing" stage where
* creation of inner contexts is no longer allowed. Also, hard exit is no longer allowed in
* this state. The close operation either finishes successfully and the context goes into
* one of the closed states, or the close operation fails and the context goes back to the
* INTERRUPTING state.
*/
CLOSING_INTERRUPTING_FINALIZING,
/*
* Close operation has been initiated and at the same time the cancel operation is in
* progress. Transition to this state can either be from the CLOSING, the CANCELLING, or the
* CLOSING_INTERRUPTING state. Even if the transition is from one of the closing states the
* closingThread is still the one that initiated the close operation. The CLOSING_CANCELLING
* state overrides the CLOSING and the CLOSING_INTERRUPTING states. Close operation that
* started in the CLOSING_CANCELLING state must finish successfully, otherwise it is an
* internal error. Close operation that did not start in the CLOSING_CANCELLING state and
* the state was overridden by CLOSING_CANCELLING during the operation can fail in which
* case the state goes back to CANCELLING.
*/
CLOSING_CANCELLING,
/*
* Close operation has been initiated and at the same time exit operation is in progress.
* Transition to this state can be only from the EXITING state. Close operation that started
* in the CLOSING_EXITING state must finish successfully, otherwise it is an internal error.
*/
CLOSING_EXITING,
/*
* Closing operation in the CLOSING state has finished successfully via the
* CLOSING_FINALIZING state.
*/
CLOSED,
/*
* Closing operation in the CLOSING_INTERRUPTING state has finished successfully via the
* CLOSING_INTERRUPTING_FINALIZING state. Essentially the same as the CLOSED state, the only
* difference is that in the CLOSED_INTERRUPTED state, the context leave operations on
* threads that are still entered notify the thread that is waiting for the interrupting
* operation to complete.
*/
CLOSED_INTERRUPTED,
/*
* Closing operation in the CLOSING_CANCELLING state has finished successfully.
*/
CLOSED_CANCELLED,
/*
* Closing operation in the CLOSING_EXITING state has finished successfully.
*/
CLOSED_EXITED;
/*
* The context is not usable and may be in an inconsistent state.
*/
boolean isInvalidOrClosed() {
switch (this) {
case CANCELLING:
case EXITING:
case CLOSING_CANCELLING:
case CLOSING_EXITING:
case CLOSED:
case CLOSED_INTERRUPTED:
case CLOSED_CANCELLED:
case CLOSED_EXITED:
return true;
default:
return false;
}
}
boolean isInterrupting() {
switch (this) {
case INTERRUPTING:
case CLOSING_INTERRUPTING:
case CLOSING_INTERRUPTING_FINALIZING:
return true;
default:
return false;
}
}
boolean isCancelling() {
switch (this) {
case CANCELLING:
case CLOSING_CANCELLING:
return true;
default:
return false;
}
}
boolean isExiting() {
switch (this) {
case EXITING:
case CLOSING_EXITING:
return true;
default:
return false;
}
}
boolean isClosing() {
switch (this) {
case CLOSING:
case CLOSING_FINALIZING:
case CLOSING_INTERRUPTING:
case CLOSING_INTERRUPTING_FINALIZING:
case CLOSING_CANCELLING:
case CLOSING_PENDING_EXIT:
case CLOSING_EXITING:
return true;
default:
return false;
}
}
boolean isClosed() {
switch (this) {
case CLOSED:
case CLOSED_INTERRUPTED:
case CLOSED_CANCELLED:
case CLOSED_EXITED:
return true;
default:
return false;
}
}
private boolean shouldCacheThreadInfo() {
switch (this) {
case DEFAULT:
case PENDING_EXIT:
case CLOSING:
case CLOSING_PENDING_EXIT:
return true;
default:
return false;
}
}
}
volatile State state = State.DEFAULT;
final WeakAssumedValue singleThreadValue = new WeakAssumedValue<>("Single thread");
volatile boolean singleThreaded = true;
private final Map threads = new WeakHashMap<>();
/*
* Do not modify only read. Use setCachedThreadInfo to modify.
*/
private volatile PolyglotThreadInfo cachedThreadInfo = PolyglotThreadInfo.NULL;
volatile Context api;
private ExecutorService cleanupExecutorService;
private Future> cleanupFuture;
boolean skipPendingExit;
volatile int exitCode;
private volatile String exitMessage;
volatile Thread closeExitedTriggerThread;
private volatile String invalidMessage;
volatile boolean invalidResourceLimit;
volatile Thread closingThread;
private final ReentrantLock closingLock = new ReentrantLock();
private final ReentrantLock interruptingLock = new ReentrantLock();
private final ReentrantLock initiateCancelOrExitLock = new ReentrantLock();
private List> cancellationOrExitingFutures;
volatile boolean disposing;
final PolyglotEngineImpl engine;
final PolyglotSharingLayer layer;
// contexts by PolyglotLanguage.engineIndex
@CompilationFinal(dimensions = 1) final PolyglotLanguageContext[] contexts;
final TruffleContext creatorTruffleContext;
final TruffleContext currentTruffleContext;
final PolyglotContextImpl parent;
volatile Map polyglotBindings; // for direct legacy access
volatile Value polyglotHostBindings; // for accesses from the polyglot api
private final PolyglotBindings polyglotBindingsObject = new PolyglotBindings(this);
final PolyglotLanguage creator; // creator for internal contexts
final ContextWeakReference weakReference;
final Set subProcesses;
@CompilationFinal PolyglotContextConfig config; // effectively final
// map from class to language index
@CompilationFinal private volatile FinalIntMap languageIndexMap;
private final List childContexts = new ArrayList<>();
List sourcesToInvalidate; // Non null only during content pre-initialization
final AtomicLong volatileStatementCounter = new AtomicLong();
long statementCounter;
final long statementLimit;
private volatile Object contextBoundLoggers;
/*
* Initialized once per context.
*/
@CompilationFinal(dimensions = 1) Object[] contextLocals;
volatile boolean localsCleared;
private ObjectSizeCalculator objectSizeCalculator;
final PolyglotThreadLocalActions threadLocalActions;
private Collection closeables;
private final Set pauseThreadLocalActions = new LinkedHashSet<>();
@CompilationFinal private Object hostContextImpl;
final Node uncachedLocation;
private final Set activeSystemThreads = Collections.newSetFromMap(new HashMap<>());
/* Constructor for testing. */
@SuppressWarnings("unused")
private PolyglotContextImpl() {
this.engine = null;
this.contexts = null;
this.creatorTruffleContext = null;
this.currentTruffleContext = null;
this.layer = null;
this.parent = null;
this.polyglotHostBindings = null;
this.polyglotBindings = null;
this.creator = null;
this.weakReference = null;
this.statementLimit = 0;
this.threadLocalActions = null;
this.subProcesses = new HashSet<>();
this.uncachedLocation = null;
}
/*
* Constructor for outer contexts.
*/
PolyglotContextImpl(PolyglotEngineImpl engine, PolyglotContextConfig config) {
this.parent = null;
this.engine = engine;
this.layer = new PolyglotSharingLayer(engine);
this.config = config;
this.creator = null;
this.uncachedLocation = new UncachedLocationNode(layer);
this.creatorTruffleContext = EngineAccessor.LANGUAGE.createTruffleContext(this, true);
this.currentTruffleContext = EngineAccessor.LANGUAGE.createTruffleContext(this, false);
this.weakReference = new ContextWeakReference(this);
this.contexts = createContextArray();
this.subProcesses = new HashSet<>();
this.statementLimit = config.limits != null && config.limits.statementLimit != 0 ? config.limits.statementLimit : Long.MAX_VALUE - 1;
this.statementCounter = statementLimit;
this.volatileStatementCounter.set(statementLimit);
this.threadLocalActions = new PolyglotThreadLocalActions(this);
PolyglotEngineImpl.ensureInstrumentsCreated(config.getConfiguredInstruments());
/*
* Instruments can add loggers, and so configuration of loggers for this context must be
* done after instruments are created.
*/
if (!config.logLevels.isEmpty()) {
EngineAccessor.LANGUAGE.configureLoggers(this, config.logLevels, getAllLoggers());
}
}
/*
* Constructor for inner contexts.
*/
@SuppressWarnings("hiding")
PolyglotContextImpl(PolyglotLanguageContext creator, PolyglotContextConfig config) {
PolyglotContextImpl parent = creator.context;
this.parent = parent;
this.layer = new PolyglotSharingLayer(parent.engine);
this.config = config;
this.engine = parent.engine;
this.creator = creator.language;
this.uncachedLocation = new UncachedLocationNode(layer);
this.statementLimit = 0; // inner context limit must not be used anyway
this.weakReference = new ContextWeakReference(this);
this.creatorTruffleContext = EngineAccessor.LANGUAGE.createTruffleContext(this, true);
this.currentTruffleContext = EngineAccessor.LANGUAGE.createTruffleContext(this, false);
if (parent.state.isInterrupting()) {
this.state = State.INTERRUPTING;
} else if (parent.state.isCancelling()) {
this.state = State.CANCELLING;
} else if (parent.state.isExiting()) {
this.state = State.EXITING;
}
this.invalidMessage = this.parent.invalidMessage;
this.exitCode = this.parent.exitCode;
this.exitMessage = this.parent.exitMessage;
this.contextBoundLoggers = this.parent.contextBoundLoggers;
this.threadLocalActions = new PolyglotThreadLocalActions(this);
if (!parent.config.logLevels.isEmpty()) {
EngineAccessor.LANGUAGE.configureLoggers(this, parent.config.logLevels, getAllLoggers());
}
this.contexts = createContextArray();
this.subProcesses = new HashSet<>();
// notifyContextCreated() is called after spiContext.impl is set to this.
this.engine.noInnerContexts.invalidate();
}
/*
* Used only in asserts.
*/
private boolean isTransitionAllowed(State fromState, State toState) {
assert Thread.holdsLock(this);
State[] successors = VALID_TRANSITIONS.get(fromState);
for (State successor : successors) {
if (successor == toState) {
return isAdditionalTransitionConditionSatisfied(fromState, toState);
}
}
return false;
}
private boolean isAdditionalTransitionConditionSatisfied(State fromState, State toState) {
assert Thread.holdsLock(this);
if (fromState.isClosing() != toState.isClosing()) {
if (closingThread != Thread.currentThread()) {
return false;
}
}
if (!fromState.isExiting() && toState.isExiting() && fromState != State.PENDING_EXIT && fromState != State.CLOSING_PENDING_EXIT) {
if (parent == null && !skipPendingExit) {
return false;
}
}
return true;
}
private boolean shouldCacheThreadInfo() {
assert Thread.holdsLock(this);
return state.shouldCacheThreadInfo() && !disposing;
}
/**
* Claims a sharing layer for a context. This typically happens at when the first non-host
* language is initialized in a context.
*/
void claimSharingLayer(PolyglotLanguage language) {
PolyglotSharingLayer s = this.layer;
if (!s.isClaimed()) {
synchronized (engine.lock) {
if (!s.isClaimed()) {
assert !language.isHost() : "cannot claim context for a host language";
engine.claimSharingLayer(s, this, language);
assert s.isClaimed();
this.weakReference.layer = s;
}
}
}
}
boolean claimSharingLayer(PolyglotSharingLayer sharableLayer, Set languages) {
PolyglotSharingLayer s = this.layer;
synchronized (engine.lock) {
assert !s.isClaimed() : "sharing layer already claimed";
if (!s.isClaimed()) {
if (!s.claimLayerForContext(sharableLayer, this, languages)) {
return false;
}
assert s.isClaimed();
assert this.layer.equals(sharableLayer);
this.weakReference.layer = s;
}
}
return true;
}
OptionValues getInstrumentContextOptions(PolyglotInstrument instrument) {
return config.getInstrumentOptionValues(instrument);
}
public void resetLimits() {
PolyglotLanguageContext languageContext = this.getHostContext();
Object prev = hostEnter(languageContext);
try {
PolyglotLimits.reset(this);
EngineAccessor.INSTRUMENT.notifyContextResetLimit(engine, creatorTruffleContext);
} catch (Throwable e) {
throw PolyglotImpl.guestToHostException(languageContext, e, true);
} finally {
hostLeave(languageContext, prev);
}
}
public void safepoint() {
PolyglotLanguageContext languageContext = this.getHostContext();
Object prev = hostEnter(languageContext);
try {
TruffleSafepoint.poll(this.uncachedLocation);
} catch (Throwable e) {
throw PolyglotImpl.guestToHostException(languageContext, e, true);
} finally {
hostLeave(languageContext, prev);
}
}
private PolyglotLanguageContext[] createContextArray() {
Collection languages = engine.idToLanguage.values();
PolyglotLanguageContext[] newContexts = new PolyglotLanguageContext[engine.languageCount];
Iterator languageIterator = languages.iterator();
for (int i = (PolyglotEngineImpl.HOST_LANGUAGE_INDEX + 1); i < engine.languageCount; i++) {
PolyglotLanguage language = languageIterator.next();
newContexts[i] = new PolyglotLanguageContext(this, language);
}
maybeInitializeHostLanguage(newContexts);
return newContexts;
}
private void maybeInitializeHostLanguage(PolyglotLanguageContext[] contextsArray) {
PolyglotLanguage hostLanguage = engine.hostLanguage;
PolyglotLanguageContext hostContext = new PolyglotLanguageContext(this, hostLanguage);
contextsArray[PolyglotEngineImpl.HOST_LANGUAGE_INDEX] = hostContext;
if (PreInitContextHostLanguage.isInstance(hostLanguage)) {
// The host language in the image execution time may differ from host language in the
// image build time. We have to postpone the creation and initialization of the host
// language context until the patching.
assert engine.inEnginePreInitialization : "PreInitContextHostLanguage can be used only during context pre-initialization";
} else {
hostContext.ensureCreated(hostLanguage);
hostContext.ensureInitialized(null);
}
}
PolyglotLanguageContext getContext(PolyglotLanguage language) {
return contexts[language.engineIndex];
}
Object getContextImpl(PolyglotLanguage language) {
return contexts[language.engineIndex].getContextImpl();
}
PolyglotLanguageContext getContextInitialized(PolyglotLanguage language, PolyglotLanguage accessingLanguage) {
PolyglotLanguageContext context = getContext(language);
context.ensureInitialized(accessingLanguage);
return context;
}
void notifyContextCreated() {
EngineAccessor.INSTRUMENT.notifyContextCreated(engine, creatorTruffleContext);
}
void addChildContext(PolyglotContextImpl child) {
assert Thread.holdsLock(this);
assert !state.isClosed();
if (state.isClosing() && !state.shouldCacheThreadInfo()) {
throw PolyglotEngineException.illegalState("Adding child context into a closing context.");
}
childContexts.add(child);
}
/**
* May be used anywhere to lookup the context.
*
* @throws IllegalStateException when there is no current context available.
*/
static PolyglotContextImpl requireContext() {
PolyglotContextImpl context = PolyglotFastThreadLocals.getContext(null);
if (context == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
throw PolyglotEngineException.illegalState("There is no current context available.");
}
return context;
}
public synchronized void explicitEnter() {
try {
Object[] prev = engine.enter(this);
PolyglotThreadInfo current = getCurrentThreadInfo();
assert current.getThread() == Thread.currentThread();
current.explicitContextStack.addLast(prev);
} catch (Throwable t) {
throw PolyglotImpl.guestToHostException(engine, t);
}
}
public synchronized void explicitLeave() {
if (state.isClosed()) {
/*
* closeImpl leaves automatically for all explicit enters on the closingThread, so
* nothing else needs to be done if context is already closed.
*/
return;
}
try {
PolyglotThreadInfo current = getCurrentThreadInfo();
LinkedList stack = current.explicitContextStack;
if (stack.isEmpty() || current.getThread() == null) {
throw PolyglotEngineException.illegalState("The context is not entered explicity. A context can only be left if it was previously entered.");
}
engine.leave(stack.removeLast(), this);
} catch (Throwable t) {
throw PolyglotImpl.guestToHostException(engine, t);
}
}
synchronized Future pause() {
PauseThreadLocalAction pauseAction = new PauseThreadLocalAction(this);
Future future = threadLocalActions.submit(null, PolyglotEngineImpl.ENGINE_ID, pauseAction, new HandshakeConfig(true, true, false, false));
pauseThreadLocalActions.add(pauseAction);
return new ContextPauseHandle(pauseAction, future);
}
void resume(Future pauseFuture) {
if (pauseFuture instanceof ContextPauseHandle && ((ContextPauseHandle) pauseFuture).pauseThreadLocalAction.context == this) {
ContextPauseHandle pauseHandle = (ContextPauseHandle) pauseFuture;
pauseHandle.resume();
} else {
throw new IllegalArgumentException("Resume method was not passed a valid pause future!");
}
}
/**
* Use to enter context if it's guaranteed to be called rarely and configuration flexibility is
* needed. Otherwise use {@link PolyglotEngineImpl#enter(PolyglotContextImpl)}.
*/
@TruffleBoundary
Object[] enterThreadChanged(boolean notifyEnter, boolean enterReverted, boolean pollSafepoint, boolean deactivateSafepoints, boolean polyglotThreadFirstEnter) {
PolyglotThreadInfo enteredThread = null;
Object[] prev = null;
Thread current = Thread.currentThread();
if (JDKAccessor.isVirtualThread(current) && !(Truffle.getRuntime() instanceof DefaultTruffleRuntime)) {
throw PolyglotEngineException.illegalState(
"Using polyglot contexts on Java virtual threads is currently not supported with an optimizing Truffle runtime. " +
"As a workaround you may add the -Dtruffle.TruffleRuntime=org.pkl.thirdparty.truffle.api.impl.DefaultTruffleRuntime JVM argument to switch to a non-optimizing runtime when using virtual threads. " +
"Please note that performance is severly reduced in this mode. Loom support for optimizing runtimes will be added in a future release.");
}
try {
if (current instanceof SystemThread) {
throw PolyglotEngineException.illegalState("Context cannot be entered on system threads.");
}
boolean needsInitialization = false;
synchronized (this) {
PolyglotThreadInfo threadInfo = getCurrentThreadInfo();
if (enterReverted && threadInfo.getEnteredCount() == 0) {
threadLocalActions.notifyThreadActivation(threadInfo, false);
if ((state.isCancelling() || state.isExiting() || state == State.CLOSED_CANCELLED || state == State.CLOSED_EXITED) && !threadInfo.isActive()) {
notifyThreadClosed(threadInfo);
}
if ((state.isInterrupting() || state == State.CLOSED_INTERRUPTED) && !threadInfo.isActive()) {
Thread.interrupted();
notifyAll();
}
}
if (deactivateSafepoints && threadInfo != PolyglotThreadInfo.NULL) {
threadLocalActions.notifyThreadActivation(threadInfo, false);
}
checkClosedOrDisposing();
assert threadInfo != null;
threadInfo = threads.get(current);
if (threadInfo == null) {
threadInfo = createThreadInfo(current, polyglotThreadFirstEnter);
needsInitialization = true;
}
if (singleThreaded) {
/*
* If this is the only thread, then setting the cached thread info to NULL is no
* performance problem. If there is other thread that is just about to enter, we
* are making sure that it initializes multi-threading if this thread doesn't do
* it.
*/
setCachedThreadInfo(PolyglotThreadInfo.NULL);
}
boolean transitionToMultiThreading = isSingleThreaded() && hasActiveOtherThread(true);
if (transitionToMultiThreading) {
// recheck all thread accesses
checkAllThreadAccesses(Thread.currentThread(), false);
}
if (transitionToMultiThreading) {
/*
* We need to do this early (before initializeMultiThreading) as entering or
* local initialization depends on single thread per context.
*/
engine.singleThreadPerContext.invalidate();
singleThreaded = false;
}
if (needsInitialization) {
threads.put(current, threadInfo);
}
if (needsInitialization) {
/*
* Do not enter the thread before initializing thread locals. Creation of thread
* locals might fail.
*/
initializeThreadLocals(threadInfo);
}
prev = threadInfo.enterInternal();
if (needsInitialization) {
this.threadLocalActions.notifyEnterCreatedThread();
}
if (notifyEnter) {
try {
threadInfo.notifyEnter(engine, this);
} catch (Throwable t) {
threadInfo.leaveInternal(prev);
throw t;
}
}
enteredThread = threadInfo;
// new thread became active so we need to check potential active thread local
// actions and process them.
Set activatedActions = null;
if (enteredThread.getEnteredCount() == 1 && !deactivateSafepoints) {
activatedActions = threadLocalActions.notifyThreadActivation(threadInfo, true);
}
if (transitionToMultiThreading) {
// we need to verify that all languages give access
// to all threads in multi-threaded mode.
transitionToMultiThreaded();
}
if (needsInitialization) {
initializeNewThread(current);
}
if (enteredThread.getEnteredCount() == 1 && !pauseThreadLocalActions.isEmpty()) {
for (Iterator threadLocalActionIterator = pauseThreadLocalActions.iterator(); threadLocalActionIterator.hasNext();) {
PauseThreadLocalAction threadLocalAction = threadLocalActionIterator.next();
if (!threadLocalAction.isPause()) {
threadLocalActionIterator.remove();
} else {
if (activatedActions == null || !activatedActions.contains(threadLocalAction)) {
threadLocalActions.submit(new Thread[]{Thread.currentThread()}, PolyglotEngineImpl.ENGINE_ID, threadLocalAction, new HandshakeConfig(true, true, false, false));
}
}
}
}
// never cache last thread on close or when closingThread
setCachedThreadInfo(threadInfo);
}
if (needsInitialization) {
EngineAccessor.INSTRUMENT.notifyThreadStarted(engine, creatorTruffleContext, current);
}
return prev;
} finally {
/*
* We need to always poll the safepoint here in case we already submitted a thread local
* action for this thread. Not polling here would make dependencies of that event wait
* forever.
*/
if (pollSafepoint) {
try {
TruffleSafepoint.pollHere(this.uncachedLocation);
} catch (Throwable t) {
/*
* Just in case a safepoint makes the enter fail we need to leave the context
* again.
*/
if (enteredThread != null) {
this.leaveThreadChanged(prev, notifyEnter, true, polyglotThreadFirstEnter);
}
throw t;
}
}
}
}
PolyglotThreadInfo getCachedThread() {
PolyglotThreadInfo info;
if (CompilerDirectives.inCompiledCode() && CompilerDirectives.isPartialEvaluationConstant(this)) {
info = singleThreadValue.getConstant();
if (info == null) {
// this branch folds away if the thread info can be resolved as a constant
info = cachedThreadInfo;
}
} else {
info = cachedThreadInfo;
}
return info;
}
PolyglotThreadInfo getCurrentThreadInfo() {
CompilerAsserts.neverPartOfCompilation();
assert Thread.holdsLock(this);
PolyglotThreadInfo info = getCachedThread();
if (info.getThread() != Thread.currentThread()) {
info = threads.get(Thread.currentThread());
if (info == null) {
// closingThread from a thread we have never seen.
info = PolyglotThreadInfo.NULL;
}
}
assert info.getThread() == null || info.getThread() == Thread.currentThread();
return info;
}
void setCachedThreadInfo(PolyglotThreadInfo info) {
if (!shouldCacheThreadInfo() || threadLocalActions.hasActiveEvents()) {
// never set the cached thread when closed closing or invalid
cachedThreadInfo = PolyglotThreadInfo.NULL;
} else {
cachedThreadInfo = info;
}
}
synchronized void checkMultiThreadedAccess(PolyglotThread newThread) {
boolean singleThread = singleThreaded ? !isActiveNotCancelled() : false;
checkAllThreadAccesses(newThread, singleThread);
}
private void checkAllThreadAccesses(Thread enteringThread, boolean singleThread) {
assert Thread.holdsLock(this);
List deniedLanguages = null;
for (PolyglotLanguageContext context : contexts) {
if (!context.isInitialized()) {
continue;
}
boolean accessAllowed = true;
if (!LANGUAGE.isThreadAccessAllowed(context.env, enteringThread, singleThread)) {
accessAllowed = false;
}
if (accessAllowed) {
for (PolyglotThreadInfo seenThread : threads.values()) {
if (!LANGUAGE.isThreadAccessAllowed(context.env, seenThread.getThread(), singleThread)) {
accessAllowed = false;
break;
}
}
}
if (!accessAllowed) {
if (deniedLanguages == null) {
deniedLanguages = new ArrayList<>();
}
deniedLanguages.add(context.language);
}
}
if (deniedLanguages != null) {
throw throwDeniedThreadAccess(enteringThread, singleThread, deniedLanguages);
}
}
/**
* Use to leave a context if its guaranteed to be called rarely and configuration flexibility is
* needed. Otherwise use {@link PolyglotEngineImpl#leave(Object[], PolyglotContextImpl)}.
*/
@TruffleBoundary
void leaveThreadChanged(Object[] prev, boolean notifyLeft, boolean entered, boolean dispose) {
PolyglotThreadInfo info;
Throwable ex = null;
synchronized (this) {
Thread current = Thread.currentThread();
if (dispose) {
info = threads.get(current);
if (info == null) {
// already disposed
return;
}
ex = notifyThreadDisposing(current);
}
setCachedThreadInfo(PolyglotThreadInfo.NULL);
PolyglotThreadInfo threadInfo = threads.get(current);
assert threadInfo != null;
info = threadInfo;
if (entered) {
try {
if (notifyLeft) {
info.notifyLeave(engine, this);
}
} finally {
info.leaveInternal(prev);
}
}
if (threadInfo.getEnteredCount() == 0) {
threadLocalActions.notifyThreadActivation(threadInfo, false);
}
if ((state.isCancelling() || state.isExiting() || state == State.CLOSED_CANCELLED || state == State.CLOSED_EXITED) && !info.isActive()) {
notifyThreadClosed(info);
}
boolean somePauseThreadLocalActionIsActive = false;
if (threadInfo.getEnteredCount() == 0 && !pauseThreadLocalActions.isEmpty()) {
for (Iterator threadLocalActionIterator = pauseThreadLocalActions.iterator(); threadLocalActionIterator.hasNext();) {
PauseThreadLocalAction threadLocalAction = threadLocalActionIterator.next();
if (!threadLocalAction.isPause()) {
threadLocalActionIterator.remove();
} else {
somePauseThreadLocalActionIsActive = true;
}
}
}
if (entered && !somePauseThreadLocalActionIsActive) {
/*
* Must not cache thread info when this synchronized leave was called as a slow-path
* fallback (entered == false). The slow-path fallback does not perform enteredCount
* decrement and so other threads may see this thread as already left before the
* synchronized block is entered. If we cached the thread info in this case, then a
* subsequent fast-path enter would not perform operations that might be necessary,
* e.g. initialize multithreading.
*/
setCachedThreadInfo(threadInfo);
}
if ((state.isInterrupting() || state == State.CLOSED_INTERRUPTED) && !info.isActive()) {
Thread.interrupted();
notifyAll();
}
if (dispose) {
finishThreadDispose(current, info, ex);
}
}
}
private void finishThreadDispose(Thread current, PolyglotThreadInfo info, Throwable ex) {
assert !info.isActive();
if (cachedThreadInfo.getThread() == current) {
setCachedThreadInfo(PolyglotThreadInfo.NULL);
}
info.setContextThreadLocals(DISPOSED_CONTEXT_THREAD_LOCALS);
threads.remove(current);
if (ex != null) {
throw sneakyThrow(ex);
}
}
private Throwable notifyThreadDisposing(Thread current) {
Throwable ex = null;
for (PolyglotLanguageContext languageContext : contexts) {
if (languageContext.isInitialized()) {
try {
LANGUAGE.disposeThread(languageContext.env, current);
} catch (Throwable t) {
if (ex == null) {
ex = t;
} else {
ex.addSuppressed(t);
}
}
}
}
try {
EngineAccessor.INSTRUMENT.notifyThreadFinished(engine, creatorTruffleContext, current);
} catch (Throwable t) {
if (ex == null) {
ex = t;
} else {
ex.addSuppressed(t);
}
}
return ex;
}
private void initializeNewThread(Thread thread) {
for (PolyglotLanguageContext context : contexts) {
if (context.isInitialized()) {
LANGUAGE.initializeThread(context.env, thread);
}
}
}
long getStatementsExecuted() {
long count;
if (engine.singleThreadPerContext.isValid()) {
count = this.statementCounter;
} else {
count = this.volatileStatementCounter.get();
}
return statementLimit - count;
}
private void transitionToMultiThreaded() {
assert Thread.holdsLock(this);
for (PolyglotLanguageContext context : contexts) {
if (context.isInitialized()) {
context.ensureMultiThreadingInitialized();
}
}
singleThreaded = false;
singleThreadValue.invalidate();
long statementsExecuted = statementLimit - statementCounter;
volatileStatementCounter.getAndAdd(-statementsExecuted);
}
private PolyglotThreadInfo createThreadInfo(Thread current, boolean polyglotThreadFirstEnter) {
assert Thread.holdsLock(this);
PolyglotThreadInfo threadInfo = new PolyglotThreadInfo(this, current, polyglotThreadFirstEnter);
boolean singleThread = isSingleThreaded();
List deniedLanguages = null;
for (PolyglotLanguageContext context : contexts) {
if (context.isInitialized()) {
if (!EngineAccessor.LANGUAGE.isThreadAccessAllowed(context.env, current, singleThread)) {
if (deniedLanguages == null) {
deniedLanguages = new ArrayList<>();
}
deniedLanguages.add(context.language);
}
}
}
if (deniedLanguages != null) {
throw throwDeniedThreadAccess(current, singleThread, deniedLanguages);
}
singleThreadValue.update(threadInfo);
return threadInfo;
}
static RuntimeException throwDeniedThreadAccess(Thread current, boolean accessSingleThreaded, List deniedLanguages) {
String message;
StringBuilder languagesString = new StringBuilder("");
for (PolyglotLanguage language : deniedLanguages) {
if (languagesString.length() != 0) {
languagesString.append(", ");
}
languagesString.append(language.getId());
}
if (accessSingleThreaded) {
message = String.format("Single threaded access requested by thread %s but is not allowed for language(s) %s.", current, languagesString);
} else {
message = String.format("Multi threaded access requested by thread %s but is not allowed for language(s) %s.", current, languagesString);
}
throw PolyglotEngineException.illegalState(message);
}
public Value getBindings(String languageId) {
PolyglotLanguageContext languageContext = lookupLanguageContext(languageId);
assert languageContext != null;
Object prev = hostEnter(languageContext);
try {
if (!languageContext.isInitialized()) {
languageContext.ensureInitialized(null);
}
return languageContext.getHostBindings();
} catch (Throwable e) {
throw PolyglotImpl.guestToHostException(languageContext, e, true);
} finally {
hostLeave(languageContext, prev);
}
}
public Value getPolyglotBindings() {
try {
checkClosed();
Value bindings = this.polyglotHostBindings;
if (bindings == null) {
initPolyglotBindings();
bindings = this.polyglotHostBindings;
}
return bindings;
} catch (Throwable e) {
throw PolyglotImpl.guestToHostException(engine, e);
}
}
public Map getPolyglotGuestBindings() {
Map bindings = this.polyglotBindings;
if (bindings == null) {
initPolyglotBindings();
bindings = this.polyglotBindings;
}
return bindings;
}
private void initPolyglotBindings() {
synchronized (this) {
if (this.polyglotBindings == null) {
this.polyglotBindings = new ConcurrentHashMap<>();
PolyglotLanguageContext hostContext = getHostContext();
PolyglotBindings bindings = new PolyglotBindings(hostContext);
this.polyglotHostBindings = getAPIAccess().newValue(new PolyglotBindingsValue(hostContext, bindings), hostContext, bindings);
}
}
}
public Object getPolyglotBindingsObject() {
return polyglotBindingsObject;
}
void checkClosedOrDisposing() {
checkCancelled();
if (state.isClosed() || disposing) {
throw PolyglotEngineException.closedException("The Context is already closed.");
}
}
void checkClosed() {
checkCancelled();
if (state.isClosed()) {
throw PolyglotEngineException.closedException("The Context is already closed.");
}
}
private void checkCancelled() {
if (state.isInvalidOrClosed() && closingThread != Thread.currentThread() && invalidMessage != null) {
/*
* If invalidMessage == null, then invalid flag was set by close.
*/
if (exitMessage == null) {
throw createCancelException(null);
} else {
throw createExitException(null);
}
}
}
@TruffleBoundary
private RuntimeException failValueSharing() {
throw new ValueMigrationException("A value was tried to be migrated from one context to a different context. " +
"Value migration for the current context was disabled and is therefore disallowed.", this.uncachedLocation);
}
Object migrateValue(Object value, PolyglotContextImpl valueContext) {
if (!config.allowValueSharing) {
throw failValueSharing();
}
Object result = engine.host.migrateValue(this, value, valueContext);
if (result != null) {
// host made sure migration is fine
return result;
}
// guaranteed by migrateValue
assert value instanceof TruffleObject;
if (value instanceof OtherContextGuestObject) {
OtherContextGuestObject otherValue = (OtherContextGuestObject) value;
if (otherValue.receiverContext == this && otherValue.delegateContext == valueContext) {
// reuse wrapper it is already wrapped
return otherValue;
} else if (otherValue.receiverContext == valueContext && otherValue.delegateContext == this) {
// unpack foreign value it belongs to that context
return otherValue.delegate;
} else {
return new OtherContextGuestObject(this, otherValue.delegate, valueContext);
}
}
assert value instanceof TruffleObject;
return new OtherContextGuestObject(this, value, valueContext);
}
Object migrateHostWrapper(PolyglotWrapper wrapper) {
Object wrapped = wrapper.getGuestObject();
PolyglotContextImpl valueContext = wrapper.getContext();
if (valueContext != this) {
// migrate wrapped value to the context
wrapped = migrateValue(wrapped, valueContext);
}
return wrapped;
}
PolyglotLanguageContext getHostContext() {
return contexts[PolyglotEngineImpl.HOST_LANGUAGE_INDEX];
}
Object getHostContextImpl() {
return hostContextImpl;
}
@Override
public PolyglotEngineImpl getEngine() {
return engine;
}
/*
* Special version for getLanguageContext for the fast-path.
*/
PolyglotLanguageContext getLanguageContext(Class extends TruffleLanguage>> languageClass) {
if (CompilerDirectives.isPartialEvaluationConstant(this)) {
return getLanguageContextImpl(languageClass);
} else {
return getLanguageContextBoundary(languageClass);
}
}
@TruffleBoundary
private PolyglotLanguageContext getLanguageContextBoundary(Class extends TruffleLanguage>> languageClass) {
return getLanguageContextImpl(languageClass);
}
@SuppressWarnings("rawtypes")
PolyglotLanguageContext findLanguageContext(Class extends TruffleLanguage> languageClazz) {
PolyglotLanguage directLanguage = engine.getLanguage(languageClazz, false);
if (directLanguage != null) {
return getContext(directLanguage);
}
// slow language lookup - for compatibility
for (PolyglotLanguageContext lang : contexts) {
if (lang.isInitialized()) {
TruffleLanguage> language = EngineAccessor.LANGUAGE.getLanguage(lang.env);
if (languageClazz != TruffleLanguage.class && languageClazz.isInstance(language)) {
return lang;
}
}
}
Set languageNames = new HashSet<>();
for (PolyglotLanguageContext lang : contexts) {
if (lang.isInitialized()) {
languageNames.add(lang.language.cache.getClassName());
}
}
throw PolyglotEngineException.illegalState("Cannot find language " + languageClazz + " among " + languageNames);
}
private PolyglotLanguageContext getLanguageContextImpl(Class extends TruffleLanguage>> languageClass) {
FinalIntMap map = this.languageIndexMap;
int indexValue = map != null ? map.get(languageClass) : -1;
if (indexValue == -1) {
CompilerDirectives.transferToInterpreterAndInvalidate();
synchronized (this) {
if (this.languageIndexMap == null) {
this.languageIndexMap = new FinalIntMap();
}
indexValue = languageIndexMap.get(languageClass);
if (indexValue == -1) {
PolyglotLanguageContext context = findLanguageContext(languageClass);
indexValue = context.language.engineIndex;
this.languageIndexMap.put(languageClass, indexValue);
}
}
}
PolyglotLanguageContext context = contexts[indexValue];
return context;
}
void initializeInnerContextLanguage(String languageId) {
PolyglotLanguage language = engine.idToLanguage.get(languageId);
assert language != null : "language creating the inner context not be found";
Object prev = engine.enterIfNeeded(this, true);
try {
initializeLanguage(language);
} finally {
engine.leaveIfNeeded(prev, this);
}
}
private boolean initializeLanguage(PolyglotLanguage language) {
PolyglotLanguageContext languageContext = getContext(language);
assert languageContext != null;
languageContext.checkAccess(null);
if (!languageContext.isInitialized()) {
return languageContext.ensureInitialized(null);
}
return false;
}
public boolean initializeLanguage(String languageId) {
PolyglotLanguageContext languageContext = lookupLanguageContext(languageId);
Object prev = hostEnter(languageContext);
try {
return initializeLanguage(languageContext.language);
} catch (Throwable t) {
throw PolyglotImpl.guestToHostException(languageContext, t, true);
} finally {
hostLeave(languageContext, prev);
}
}
public Value parse(String languageId, org.pkl.thirdparty.graalvm.polyglot.Source source) {
PolyglotLanguageContext languageContext = lookupLanguageContext(languageId);
assert languageContext != null;
Object prev = hostEnter(languageContext);
try {
Source truffleSource = (Source) getAPIAccess().getReceiver(source);
languageContext.checkAccess(null);
languageContext.ensureInitialized(null);
CallTarget target = languageContext.parseCached(null, truffleSource, null);
return languageContext.asValue(new PolyglotParsedEval(languageContext, truffleSource, target));
} catch (Throwable e) {
throw PolyglotImpl.guestToHostException(languageContext, e, true);
} finally {
hostLeave(languageContext, prev);
}
}
private PolyglotLanguageContext lookupLanguageContext(String languageId) {
PolyglotLanguageContext languageContext;
try {
PolyglotLanguage language = requirePublicLanguage(languageId);
languageContext = getContext(language);
} catch (Throwable e) {
throw PolyglotImpl.guestToHostException(engine, e);
}
return languageContext;
}
public Value eval(String languageId, org.pkl.thirdparty.graalvm.polyglot.Source source) {
PolyglotLanguageContext languageContext = lookupLanguageContext(languageId);
assert languageContext != null;
Object prev = hostEnter(languageContext);
try {
Source truffleSource = (Source) getAPIAccess().getReceiver(source);
languageContext.checkAccess(null);
languageContext.ensureInitialized(null);
CallTarget target = languageContext.parseCached(null, truffleSource, null);
Object result = target.call(PolyglotImpl.EMPTY_ARGS);
Value hostValue;
try {
hostValue = languageContext.asValue(result);
} catch (NullPointerException | ClassCastException e) {
throw new AssertionError(String.format("Language %s returned an invalid return value %s. Must be an interop value.", languageId, result), e);
}
if (truffleSource.isInteractive()) {
printResult(languageContext, result);
}
return hostValue;
} catch (Throwable e) {
throw PolyglotImpl.guestToHostException(languageContext, e, true);
} finally {
hostLeave(languageContext, prev);
}
}
public PolyglotLanguage requirePublicLanguage(String languageId) {
PolyglotLanguage language = engine.idToLanguage.get(languageId);
if (language == null || language.cache.isInternal()) {
engine.requirePublicLanguage(languageId); // will trigger the error
assert false;
return null;
}
return language;
}
@TruffleBoundary
static void printResult(PolyglotLanguageContext languageContext, Object result) {
if (!LANGUAGE.isVisible(languageContext.env, result)) {
return;
}
String stringResult;
try {
stringResult = UNCACHED.asString(UNCACHED.toDisplayString(languageContext.getLanguageView(result), true));
} catch (UnsupportedMessageException e) {
throw shouldNotReachHere(e);
}
try {
OutputStream out = languageContext.context.config.out;
out.write(stringResult.getBytes(StandardCharsets.UTF_8));
out.write(System.getProperty("line.separator").getBytes(StandardCharsets.UTF_8));
} catch (IOException ioex) {
// out stream has problems.
throw new IllegalStateException(ioex);
}
}
/**
* Embedder close.
*/
public void close(boolean cancelIfExecuting) {
try {
clearExplicitContextStack();
if (cancelIfExecuting) {
/*
* Cancel does invalidate. We always need to invalidate before force-closing a
* context that might be active in other threads.
*/
cancel(false, null);
} else {
closeAndMaybeWait(false, null);
checkCancelled();
}
} catch (Throwable t) {
PolyglotException polyglotException = PolyglotImpl.guestToHostException(getHostContext(), t, false);
if (!cancelIfExecuting && state.isInvalidOrClosed() && (polyglotException.isCancelled() || polyglotException.isExit())) {
try {
/*
* The close operation was interrupted by cancelling or exiting, we are now in
* an invalid state. By executing the close operation again, we make sure that
* the close operation is fully completed when we return.
*/
closeAndMaybeWait(false, null);
} catch (Throwable closeFinishError) {
/*
* Close operation started when the context is already invalid should complete
* without an error. This exception indicates a bug, most probably in the
* language implementation.
*/
PolyglotException closeFinishPolyglotException = PolyglotImpl.guestToHostException(getHostContext(), t, false);
polyglotException.addSuppressed(closeFinishPolyglotException);
}
}
throw polyglotException;
}
}
void cancel(boolean resourceLimit, String message) {
String cancelMessage = message == null ? "Context execution was cancelled." : message;
if (parent == null) {
engine.polyglotHostService.notifyContextCancellingOrExiting(this, false, 0, resourceLimit, cancelMessage);
}
List> futures = setCancelling(resourceLimit, cancelMessage);
closeHereOrCancelInCleanupThread(futures);
}
void initiateCancelOrExit(boolean exit, int code, boolean resourceLimit, String message) {
assert parent == null;
initiateCancelOrExitLock.lock();
try {
List> futures;
if (exit) {
futures = setExiting(null, code, message, true);
} else {
futures = setCancelling(resourceLimit, message);
}
if (!futures.isEmpty()) {
/*
* initiateCancelOrExit keeps assigning cancellationOrExitingFutures until one of
* the other setExiting or setCancelling calls takes it and from that point
* cancellationOrExitingFutures == null. If the futures are empty, it means that
* cancelling was not initiated by this method, because it was already initiated
* before, or it is no longer possible.
*/
cancellationOrExitingFutures = futures;
}
} finally {
initiateCancelOrExitLock.unlock();
}
}
void closeAndMaybeWait(boolean force, List> futures) {
if (force) {
PolyglotEngineImpl.cancelOrExit(this, futures);
} else {
boolean closeCompleted = closeImpl(true);
if (!closeCompleted) {
throw PolyglotEngineException.illegalState(String.format("The context is currently executing on another thread. " +
"Set cancelIfExecuting to true to stop the execution on this thread."));
}
}
finishCleanup();
checkSubProcessFinished();
checkSystemThreadsFinished();
if (parent == null) {
engine.polyglotHostService.notifyContextClosed(this, force, invalidResourceLimit, invalidMessage);
}
if (engine.boundEngine && parent == null) {
engine.ensureClosed(force, true);
}
}
private void setState(State targetState) {
assert Thread.holdsLock(this);
assert isTransitionAllowed(state, targetState) : "Transition from " + state.name() + " to " + targetState.name() + " not allowed!";
state = targetState;
notifyAll();
}
private List> setInterrupting() {
assert Thread.holdsLock(this);
State targetState;
List> futures = new ArrayList<>();
if (!state.isInterrupting() && !state.isInvalidOrClosed() && state != State.PENDING_EXIT && state != State.CLOSING_PENDING_EXIT) {
switch (state) {
case CLOSING:
targetState = State.CLOSING_INTERRUPTING;
break;
case CLOSING_FINALIZING:
targetState = State.CLOSING_INTERRUPTING_FINALIZING;
break;
default:
targetState = State.INTERRUPTING;
break;
}
setState(targetState);
setCachedThreadInfo(PolyglotThreadInfo.NULL);
futures.add(threadLocalActions.submit(null, PolyglotEngineImpl.ENGINE_ID, new InterruptThreadLocalAction(), true));
maybeSendInterrupt();
}
return futures;
}
private void unsetInterrupting() {
assert Thread.holdsLock(this);
if (state.isInterrupting()) {
State targetState;
switch (state) {
case CLOSING_INTERRUPTING:
targetState = State.CLOSING;
break;
case CLOSING_INTERRUPTING_FINALIZING:
targetState = State.CLOSING_FINALIZING;
break;
default:
targetState = State.DEFAULT;
break;
}
setState(targetState);
}
}
private void finishInterruptForChildContexts() {
PolyglotContextImpl[] childContextsToInterrupt;
synchronized (this) {
unsetInterrupting();
childContextsToInterrupt = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
for (PolyglotContextImpl childCtx : childContextsToInterrupt) {
childCtx.finishInterruptForChildContexts();
}
}
private List> interruptChildContexts() {
PolyglotContextImpl[] childContextsToInterrupt = null;
List> futures;
synchronized (this) {
futures = new ArrayList<>(setInterrupting());
if (!futures.isEmpty()) {
childContextsToInterrupt = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
}
if (childContextsToInterrupt != null) {
for (PolyglotContextImpl childCtx : childContextsToInterrupt) {
futures.addAll(childCtx.interruptChildContexts());
}
}
return futures;
}
private void validateInterruptPrecondition(PolyglotContextImpl operationSource) {
PolyglotContextImpl[] childContextsToInterrupt;
synchronized (this) {
PolyglotThreadInfo info = getCurrentThreadInfo();
if (info != PolyglotThreadInfo.NULL && info.isActive()) {
throw PolyglotEngineException.illegalState(String.format("Cannot interrupt context from a thread where %s context is active.", this == operationSource ? "the" : "its child"));
}
childContextsToInterrupt = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
for (PolyglotContextImpl childCtx : childContextsToInterrupt) {
childCtx.validateInterruptPrecondition(operationSource);
}
}
public boolean interrupt(Duration timeout) {
try {
if (parent != null) {
throw PolyglotEngineException.illegalState("Cannot interrupt inner context separately.");
}
long startMillis = System.currentTimeMillis();
PolyglotContextImpl[] childContextsToInterrupt = null;
/*
* Two interrupt operations cannot be simultaneously in progress in the whole context
* hierarchy. Inner contexts cannot use interrupt separately and outer context use
* exclusive lock.
*/
interruptingLock.lock();
try {
validateInterruptPrecondition(this);
List> futures;
synchronized (this) {
if (state.isClosed()) {
// already closed
return true;
}
futures = new ArrayList<>(setInterrupting());
if (!futures.isEmpty()) {
childContextsToInterrupt = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
}
if (childContextsToInterrupt != null) {
for (PolyglotContextImpl childCtx : childContextsToInterrupt) {
futures.addAll(childCtx.interruptChildContexts());
}
}
/*
* No matter whether we successfully transitioned into one of the interrupting
* states, we wait for threads to be completed (which is done as a part of the
* cancel method) as the states that override interrupting states also lead to
* threads being stopped. If that happens before the timeout, the interrupt is
* successful.
*/
return PolyglotEngineImpl.cancelOrExitOrInterrupt(this, futures, startMillis, timeout);
} finally {
try {
if (childContextsToInterrupt != null) {
PolyglotContextImpl[] childContextsToFinishInterrupt;
synchronized (this) {
unsetInterrupting();
childContextsToFinishInterrupt = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
for (PolyglotContextImpl childCtx : childContextsToFinishInterrupt) {
childCtx.finishInterruptForChildContexts();
}
}
} finally {
interruptingLock.unlock();
}
}
} catch (Throwable thr) {
throw PolyglotImpl.guestToHostException(engine, thr);
}
}
public Value asValue(Object hostValue) {
PolyglotLanguageContext languageContext = this.getHostContext();
Object prev = hostEnter(languageContext);
try {
checkClosed();
PolyglotLanguageContext targetLanguageContext;
if (hostValue instanceof Value) {
// fast path for when no context migration is necessary
PolyglotLanguageContext valueContext = (PolyglotLanguageContext) getAPIAccess().getContext((Value) hostValue);
if (valueContext != null && valueContext.context == this) {
return (Value) hostValue;
}
targetLanguageContext = languageContext;
} else if (PolyglotWrapper.isInstance(hostValue)) {
// host wrappers can nicely reuse the associated context
targetLanguageContext = PolyglotWrapper.asInstance(hostValue).getLanguageContext();
if (this != targetLanguageContext.context) {
// this will fail later in toGuestValue when migrating
// or succeed in case of host languages.
targetLanguageContext = languageContext;
}
} else {
targetLanguageContext = languageContext;
}
return targetLanguageContext.asValue(toGuestValue(null, hostValue, true));
} catch (Throwable e) {
throw PolyglotImpl.guestToHostException(this.getHostContext(), e, true);
} finally {
hostLeave(languageContext, prev);
}
}
static PolyglotEngineImpl getConstantEngine(Node node) {
if (!CompilerDirectives.inCompiledCode() ||
!CompilerDirectives.isPartialEvaluationConstant(node)) {
return null;
}
if (node == null) {
return null;
}
RootNode root = node.getRootNode();
if (root == null) {
return null;
}
PolyglotSharingLayer layer = (PolyglotSharingLayer) EngineAccessor.NODES.getSharingLayer(root);
return layer != null ? layer.engine : null;
}
Object toGuestValue(Node node, Object hostValue, boolean asValue) {
PolyglotEngineImpl localEngine = getConstantEngine(node);
PolyglotContextImpl localContext;
if (localEngine == null) {
localEngine = this.engine;
localContext = this;
} else {
// lookup context as a constant
localContext = localEngine.singleContextValue.getConstant();
if (localContext == null) {
// not a constant use this
localContext = this;
}
}
Object value = PolyglotHostAccess.toGuestValue(localContext, hostValue);
return localEngine.host.toGuestValue(localContext.getHostContextImpl(), value, asValue);
}
boolean waitForThreads(long startMillis, long timeoutMillis) {
synchronized (this) {
long timeElapsed = System.currentTimeMillis() - startMillis;
boolean otherThreadActive;
while ((otherThreadActive = hasActiveOtherThread(true)) && (timeoutMillis == 0 || timeElapsed < timeoutMillis)) {
try {
if (timeoutMillis == 0) {
wait();
} else {
wait(timeoutMillis - timeElapsed);
}
} catch (InterruptedException e) {
}
timeElapsed = System.currentTimeMillis() - startMillis;
}
/*
* hasActiveOtherThread is racy. E.g. one of the threads might be just about to enter
* via fast path and so hasActiveOtherThread might return a different result if we
* executed it again after the while loop. The fast-path enter might not go through in
* the end, especially if this is waiting for cancellation of all threads, so it is not
* a problem that hasActiveOtherThread is racy, but it is important that waitForThreads
* does not return a wrong value. That is why we store the result in a boolean so that
* the returned value corresponds to the reason why the while loop has ended.
*/
return !otherThreadActive;
}
}
boolean isSingleThreaded() {
return singleThreaded;
}
Map getSeenThreads() {
assert Thread.holdsLock(this);
return threads;
}
private boolean isActiveNotCancelled() {
return isActiveNotCancelled(true);
}
synchronized boolean isActiveNotCancelled(boolean includePolyglotThreads) {
for (PolyglotThreadInfo seenTinfo : threads.values()) {
if ((includePolyglotThreads || !seenTinfo.isPolyglotThread(this)) && seenTinfo.isActiveNotCancelled()) {
return true;
}
}
return false;
}
synchronized boolean isActive() {
for (PolyglotThreadInfo seenTinfo : threads.values()) {
if (seenTinfo.isActive()) {
return true;
}
}
return false;
}
synchronized boolean isActive(Thread thread) {
PolyglotThreadInfo info = threads.get(thread);
if (info == null || info == PolyglotThreadInfo.NULL) {
return false;
}
return info.isActive();
}
private PolyglotThreadInfo getFirstActiveOtherThread(boolean includePolyglotThreads) {
assert Thread.holdsLock(this);
// send enters and leaves into a lock by setting the lastThread to null.
for (PolyglotThreadInfo otherInfo : threads.values()) {
if (!includePolyglotThreads && otherInfo.isPolyglotThread(this)) {
continue;
}
if (!otherInfo.isCurrent() && otherInfo.isActive()) {
return otherInfo;
}
}
return null;
}
boolean hasActiveOtherThread(boolean includePolyglotThreads) {
return getFirstActiveOtherThread(includePolyglotThreads) != null;
}
private void notifyThreadClosed(PolyglotThreadInfo info) {
assert Thread.holdsLock(this);
if (!info.cancelled) {
// clear interrupted status after closingThread
// needed because we interrupt when closingThread from another thread.
info.cancelled = true;
Thread.interrupted();
}
notifyAll();
}
long calculateHeapSize(long stopAtBytes, AtomicBoolean calculationCancelled) {
ObjectSizeCalculator localObjectSizeCalculator;
synchronized (this) {
localObjectSizeCalculator = objectSizeCalculator;
if (localObjectSizeCalculator == null) {
localObjectSizeCalculator = new ObjectSizeCalculator();
objectSizeCalculator = localObjectSizeCalculator;
}
}
return localObjectSizeCalculator.calculateObjectSize(getContextHeapRoots(), stopAtBytes, calculationCancelled);
}
private Object[] getContextHeapRoots() {
List heapRoots = new ArrayList<>();
addRootPointersForContext(heapRoots);
addRootPointersForStackFrames(heapRoots);
return heapRoots.toArray();
}
private void addRootPointersForStackFrames(List heapRoots) {
PolyglotStackFramesRetriever.populateHeapRoots(this, heapRoots);
}
private void addRootPointersForContext(List heapRoots) {
synchronized (this) {
for (PolyglotLanguageContext context : contexts) {
if (context.isCreated()) {
heapRoots.add(context.getContextImpl());
}
}
if (polyglotBindings != null) {
for (Map.Entry binding : polyglotBindings.entrySet()) {
heapRoots.add(binding.getKey());
if (binding.getValue() != null) {
heapRoots.add(getAPIAccess().getReceiver(binding.getValue()));
}
}
}
}
heapRoots.add(contextLocals);
PolyglotContextImpl[] childContextStartPoints;
synchronized (this) {
for (PolyglotThreadInfo info : threads.values()) {
heapRoots.add(info.getContextThreadLocals());
}
childContextStartPoints = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
for (PolyglotContextImpl childCtx : childContextStartPoints) {
childCtx.addRootPointersForContext(heapRoots);
}
}
/**
* @return non-empty list of thread local action futures if this method sets the cancelling
* state or obtains the futures from cancellationOrExitingFutures, empty list otherwise.
*/
private List> setCancelling(boolean resourceLimit, String message) {
assert message != null;
PolyglotContextImpl[] childContextsToCancel = null;
List> futures = new ArrayList<>();
synchronized (this) {
if (!state.isInvalidOrClosed()) {
State targetState;
if (state.isClosing()) {
targetState = State.CLOSING_CANCELLING;
} else {
targetState = State.CANCELLING;
}
invalidResourceLimit = resourceLimit;
invalidMessage = message;
exitMessage = null;
setState(targetState);
submitCancellationThreadLocalAction(futures);
maybeSendInterrupt();
childContextsToCancel = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
}
if (childContextsToCancel != null) {
assert !futures.isEmpty();
for (PolyglotContextImpl childCtx : childContextsToCancel) {
futures.addAll(childCtx.setCancelling(resourceLimit, message));
}
}
return getCancellingOrExitingFutures(futures);
}
private void submitCancellationThreadLocalAction(List> futures) {
PolyglotThreadInfo info = getCurrentThreadInfo();
futures.add(threadLocalActions.submit(null, PolyglotEngineImpl.ENGINE_ID, new CancellationThreadLocalAction(), true));
if (info != PolyglotThreadInfo.NULL) {
info.cancelled = true;
Thread.interrupted();
}
setCachedThreadInfo(PolyglotThreadInfo.NULL);
}
/**
* @return non-empty list of thread local action futures if this method sets the exiting state
* or obtains the futures from cancellationOrExitingFutures, empty list otherwise. One
* exception to this rule is when config.useSystemExit == true, in that case the
* returned futures are also empty as the context won't be closed in the standard way.
* Instead, System.exit will be used to exit the whole VM.
*/
private List> setExiting(PolyglotContextImpl triggeringParent, int code, String message, boolean skipPendingExit) {
PolyglotContextImpl[] childContextsToCancel = null;
List> futures = new ArrayList<>();
synchronized (this) {
if (!state.isInvalidOrClosed()) {
assert message != null;
State targetState;
if (state.isClosing()) {
targetState = State.CLOSING_EXITING;
} else {
targetState = State.EXITING;
}
this.skipPendingExit = skipPendingExit;
invalidMessage = message;
if (skipPendingExit) {
/*
* Setting the exiting state is supposed to match some other context we have no
* direct reference to (e.g. isolated context) that this context is driven by.
* This context is not being exited in the standard way and so it does not go
* through the PENDING_EXIT state.
*/
exitMessage = message;
exitCode = code;
}
if (triggeringParent != null) {
/*
* triggeringParent is not null (and equal to parent) if the exit was initiated
* by some ancestor of this context (not necessarily the parent) and not this
* context directly. This means that the triggeringParent can be null even if
* parent is not null.
*/
exitMessage = triggeringParent.exitMessage;
exitCode = triggeringParent.exitCode;
}
setState(targetState);
if (!config.useSystemExit) {
submitCancellationThreadLocalAction(futures);
maybeSendInterrupt();
}
childContextsToCancel = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
}
if (childContextsToCancel != null) {
for (PolyglotContextImpl childCtx : childContextsToCancel) {
futures.addAll(childCtx.setExiting(this, code, message, skipPendingExit));
}
}
return getCancellingOrExitingFutures(futures);
}
private List> getCancellingOrExitingFutures(List> futures) {
List> toRet = futures;
if (parent == null && toRet.isEmpty()) {
initiateCancelOrExitLock.lock();
try {
if (cancellationOrExitingFutures != null) {
toRet = cancellationOrExitingFutures;
cancellationOrExitingFutures = null;
}
} finally {
initiateCancelOrExitLock.unlock();
}
}
return toRet;
}
private void setClosingState() {
assert Thread.holdsLock(this);
closingThread = Thread.currentThread();
closingLock.lock();
State targetState;
switch (state) {
case CANCELLING:
targetState = State.CLOSING_CANCELLING;
break;
case INTERRUPTING:
targetState = State.CLOSING_INTERRUPTING;
break;
case EXITING:
targetState = State.CLOSING_EXITING;
break;
default:
targetState = State.CLOSING;
break;
}
setState(targetState);
}
private void setFinalizingState() {
assert Thread.holdsLock(this);
assert closingThread == Thread.currentThread();
assert closingLock.isHeldByCurrentThread();
State targetState;
switch (state) {
case CLOSING:
targetState = State.CLOSING_FINALIZING;
break;
case CLOSING_INTERRUPTING:
targetState = State.CLOSING_INTERRUPTING_FINALIZING;
break;
default:
return;
}
setState(targetState);
}
private void setClosedState() {
assert Thread.holdsLock(this);
assert state.isClosing() : state.name();
State targetState;
switch (state) {
case CLOSING_CANCELLING:
targetState = State.CLOSED_CANCELLED;
break;
case CLOSING_EXITING:
targetState = State.CLOSED_EXITED;
break;
case CLOSING_INTERRUPTING_FINALIZING:
targetState = State.CLOSED_INTERRUPTED;
break;
case CLOSING_FINALIZING:
targetState = State.CLOSED;
break;
default:
throw new IllegalStateException("Cannot close polyglot context in the current state!");
}
setState(targetState);
assert state.isClosed() : state.name();
}
private void restoreFromClosingState(boolean cancelOperation) {
assert Thread.holdsLock(this);
assert state.isClosing() : state.name();
State targetState;
assert !cancelOperation : "Close initiated for an invalid context must not fail!";
switch (state) {
case CLOSING_INTERRUPTING:
case CLOSING_INTERRUPTING_FINALIZING:
targetState = State.INTERRUPTING;
break;
case CLOSING_CANCELLING:
targetState = State.CANCELLING;
break;
case CLOSING_PENDING_EXIT:
targetState = State.PENDING_EXIT;
break;
case CLOSING_EXITING:
targetState = State.EXITING;
break;
default:
targetState = State.DEFAULT;
break;
}
setState(targetState);
}
@SuppressWarnings({"fallthrough"})
@SuppressFBWarnings("UL_UNRELEASED_LOCK_EXCEPTION_PATH")
boolean closeImpl(boolean notifyInstruments) {
/*
* Close operation initiated in the DEFAULT or INTERRUPTING state can fail in which case the
* context will go back to the corresponsing non-closing state e.g. DEFAULT ->
* CLOSING/CLOSING_FINALIZING -> DEFAULT. Please note that while the default/interrupting
* close is in progress, i.e. the context is in the CLOSING/CLOSING_FINALIZING or the
* CLOSING_INTERRUPTING/CLOSING_INTERRUPTING_FINALIZING state, the state can be overriden by
* the CLOSING_CANCELLING state. The CLOSING and CLOSING_INTERRUPTING states can also be
* overriden by the CLOSING_PENDING_EXIT and then the CLOSING_EXITING state. Even in these
* cases the default close can still fail and if that is the case the context state goes
* back to the CANCELLING or the EXITING state. The close operation is then guaranteed to be
* completed by the process that initiated cancel or exit.
*
* This block performs the following checks:
*
* 1) The close was already performed on another thread -> return true
*
* 2) The close is currently already being performed on this thread -> return true
*
* 3) The close is currently being performed on another thread -> wait for the other thread
* to finish closing and start checking again from check 1).
*
* 4) The close was not yet performed, cancelling or exiting is not in progress, the context
* is not in the PENDING_EXIT state, but other threads are still executing -> return false
*
* 5) The close was not yet performed and the context is in the PENDING_EXIT state -> wait
* for the context to go to an invalid state (CANCELLING, EXITING, or their closing or
* closed variants) and start checking from check 1).
*
* 6) The close was not yet performed and cancelling or exiting is in progress -> wait for
* other threads to complete and start checking again from check 1) skipping check 6) (this
* check) as no other threads can be executing anymore.
*
* 7) The close was not yet performed and no thread is executing -> perform close
*/
boolean waitForClose = false;
boolean finishCancelOrExit = false;
boolean cancelOrExitOperation;
acquireClosingLock: while (true) {
if (waitForClose) {
closingLock.lock();
closingLock.unlock();
waitForClose = false;
}
synchronized (this) {
switch (state) {
case CLOSED:
case CLOSED_INTERRUPTED:
case CLOSED_CANCELLED:
case CLOSED_EXITED:
return true;
case CLOSING:
case CLOSING_FINALIZING:
case CLOSING_INTERRUPTING:
case CLOSING_INTERRUPTING_FINALIZING:
case CLOSING_CANCELLING:
case CLOSING_PENDING_EXIT:
case CLOSING_EXITING:
assert closingThread != null;
if (closingThread == Thread.currentThread()) {
// currently closing recursively -> just complete
return true;
} else {
// currently closing on another thread -> wait for other thread to
// complete closing
waitForClose = true;
continue acquireClosingLock;
}
case PENDING_EXIT:
waitUntilInvalid();
continue acquireClosingLock;
case CANCELLING:
case EXITING:
assert cachedThreadInfo == PolyglotThreadInfo.NULL;
/*
* When cancelling or exiting, we have to wait for all other threads to
* complete - even for the the default close, otherwise the default close
* executed prematurely as the result of leaving the context on the main
* thread due to cancel exception could fail because of other threads still
* being active. The correct behavior is that the normal close finishes
* successfully and the cancel exception spreads further (if not caught
* before close is executed).
*/
if (!finishCancelOrExit) {
waitForThreads(0, 0);
waitForClose = true;
finishCancelOrExit = true;
/*
* During wait this thread didn't hold the polyglot context lock, so
* some other thread might have acquired closingLock in the meantime. In
* that case it wouldn't be possible to acquire the closingLock by this
* thread in the current synchronized block, because the thread that
* holds it might need to acquire the context lock before releasing the
* closingLock, but the context lock is held by this thread, and so we
* have to exit the synchronized block and try again.
*/
continue acquireClosingLock;
}
/*
* Just continue with the close if we have already waited for threads in the
* previous iteration of the main loop. We cannot wait for the close to be
* completed by the thread that executes cancelling or exiting, because it
* might be waiting for this thread which would lead to a deadlock. Default
* close is allowed to be executed when entered. Also, this might be an
* inner context, which, even if not entered, might block a parent's thread
* which could be entered on the current thread.
*/
setClosingState();
cancelOrExitOperation = true;
break acquireClosingLock;
case INTERRUPTING:
case DEFAULT:
if (hasActiveOtherThread(false)) {
/*
* We are not done executing, cannot close yet.
*/
return false;
}
setClosingState();
cancelOrExitOperation = false;
break acquireClosingLock;
default:
assert false : state.name();
}
}
}
return finishClose(cancelOrExitOperation, notifyInstruments);
}
private void waitUntilInvalid() {
while (!state.isInvalidOrClosed()) {
try {
wait();
} catch (InterruptedException ie) {
}
}
}
synchronized void clearExplicitContextStack() {
if (parent == null) {
engine.polyglotHostService.notifyClearExplicitContextStack(this);
}
if (isActive(Thread.currentThread()) && !engine.getImpl().getRootImpl().isInCurrentEngineHostCallback(engine)) {
PolyglotThreadInfo threadInfo = getCurrentThreadInfo();
if (!threadInfo.explicitContextStack.isEmpty()) {
PolyglotContextImpl c = this;
while (!threadInfo.explicitContextStack.isEmpty()) {
if (PolyglotFastThreadLocals.getContext(null) == this) {
Object[] prev = threadInfo.explicitContextStack.removeLast();
engine.leave(prev, c);
c = prev != null ? (PolyglotContextImpl) prev[PolyglotFastThreadLocals.CONTEXT_INDEX] : null;
} else {
throw PolyglotEngineException.illegalState("Unable to automatically leave an explicitly entered context, some other context was entered in the meantime.");
}
}
}
}
}
private boolean finishClose(boolean cancelOrExitOperation, boolean notifyInstruments) {
/*
* If we reach here then we can continue with the close. This means that no other concurrent
* close is running and no other thread is currently executing. Note that only the context
* and closing lock should be acquired in this area to avoid deadlocks.
*/
Thread[] remainingThreads = null;
List disposedContexts = null;
boolean success = false;
try {
assert closingThread == Thread.currentThread();
assert closingLock.isHeldByCurrentThread() : "lock is acquired";
assert !state.isClosed();
Object[] prev;
try {
prev = this.enterThreadChanged(false, false, !cancelOrExitOperation, cancelOrExitOperation, false);
} catch (Throwable t) {
synchronized (this) {
restoreFromClosingState(cancelOrExitOperation);
}
throw t;
}
if (cancelOrExitOperation) {
synchronized (this) {
/*
* Cancellation thread local action needs to be submitted here in case
* finalizeContext runs guest code.
*/
threadLocalActions.submit(new Thread[]{Thread.currentThread()}, PolyglotEngineImpl.ENGINE_ID, new CancellationThreadLocalAction(), true);
}
}
try {
if (cancelOrExitOperation) {
closeChildContexts(notifyInstruments);
} else {
exitContextNotification(TruffleLanguage.ExitMode.NATURAL, 0);
}
synchronized (this) {
assert state != State.CLOSING_FINALIZING && state != State.CLOSING_INTERRUPTING_FINALIZING;
setCachedThreadInfo(PolyglotThreadInfo.NULL);
setFinalizingState();
if (state == State.CLOSING_PENDING_EXIT) {
/*
* In case hard exit was triggered during the closing operation, we need to
* wait until the hard exit notifications are finished or cancelled by
* cancelling the whole context. Otherwise, we would execute the finalize
* notifications prematurely.
*/
waitUntilInvalid();
}
}
finalizeContext(notifyInstruments, cancelOrExitOperation);
List unclosedChildContexts;
synchronized (this) {
unclosedChildContexts = getUnclosedChildContexts();
}
for (PolyglotContextImpl childCtx : unclosedChildContexts) {
if (childCtx.isActive()) {
throw new IllegalStateException("There is an active child contexts after finalizeContext!");
}
}
if (!unclosedChildContexts.isEmpty()) {
closeChildContexts(notifyInstruments);
}
// finalization performed commit close -> no reinitialization allowed
disposedContexts = disposeContext();
success = true;
} finally {
synchronized (this) {
/*
* The assert is synchronized because all accesses to childContexts must be
* synchronized. We cannot simply assert that childContexts are empty, because
* removing the child context from its parent childContexts list can be done in
* another thread after the assertion.
*/
assert !success || getUnclosedChildContexts().isEmpty() : "Polyglot context close marked as successful, but there are unclosed child contexts.";
this.leaveThreadChanged(prev, false, true, false);
if (success) {
remainingThreads = threads.keySet().toArray(new Thread[0]);
}
if (success) {
setClosedState();
} else {
restoreFromClosingState(cancelOrExitOperation);
}
disposing = false;
// triggers a thread changed event which requires slow path enter
setCachedThreadInfo(PolyglotThreadInfo.NULL);
}
}
} finally {
synchronized (this) {
assert !state.isClosing();
closingThread = null;
closingLock.unlock();
}
}
/*
* No longer any lock is held. So we can acquire other locks to cleanup.
*/
for (PolyglotLanguageContext context : disposedContexts) {
context.notifyDisposed(notifyInstruments);
}
if (success) {
try {
/*
* We need to notify before we remove the context from engine's context list,
* otherwise we couldn't use context locals in the context closed notification. New
* instrument introducting new context locals doesn't initialize them in a context
* if it's not in the engine's context list.
*/
if (notifyInstruments) {
for (Thread thread : remainingThreads) {
EngineAccessor.INSTRUMENT.notifyThreadFinished(engine, creatorTruffleContext, thread);
}
EngineAccessor.INSTRUMENT.notifyContextClosed(engine, creatorTruffleContext);
}
} finally {
if (parent != null) {
synchronized (parent) {
parent.childContexts.remove(this);
}
} else if (notifyInstruments) {
engine.disposeContext(this);
}
}
synchronized (this) {
// sends all threads to do slow-path enter/leave
setCachedThreadInfo(PolyglotThreadInfo.NULL);
/*
* If we are closing from within an entered thread, we cannot clear locals as they
* might be needed in e.g. onLeaveThread events. Moreover, closing a non-invalid
* context does not prevent other threads from entering and, additionally, if the
* context becomes invalid after some other thread has entered, then
* PolyglotLanguageContext#dispose does not check for other entered main threads,
* and so the close operation (started when the context was still non-invalid) can
* proceed and reach this point. Therefore, we have to check for entered threads
* here, and in case there is any, we cannot close PolyglotLanguageContexts and
* clear locals.
*/
if (!isActive()) {
threadLocalActions.notifyContextClosed();
if (contexts != null) {
for (PolyglotLanguageContext langContext : contexts) {
langContext.close();
}
}
if (contextLocals != null) {
Arrays.fill(contextLocals, null);
}
for (PolyglotThreadInfo thread : threads.values()) {
Object[] threadLocals = thread.getContextThreadLocals();
if (threadLocals != null) {
Arrays.fill(threadLocals, null);
}
PolyglotFastThreadLocals.cleanup(thread.fastThreadLocals);
}
localsCleared = true;
}
}
if (parent == null) {
if (!this.config.logLevels.isEmpty()) {
EngineAccessor.LANGUAGE.configureLoggers(this, null, getAllLoggers());
}
if (this.config.logHandler != null && !PolyglotLoggers.hasSameStream(this.config.logHandler, engine.logHandler)) {
this.config.logHandler.close();
}
}
}
return true;
}
private List getUnclosedChildContexts() {
assert Thread.holdsLock(this);
List unclosedChildContexts = new ArrayList<>();
for (PolyglotContextImpl childCtx : childContexts) {
if (!childCtx.state.isClosed()) {
unclosedChildContexts.add(childCtx);
}
}
return unclosedChildContexts;
}
private void closeChildContexts(boolean notifyInstruments) {
PolyglotContextImpl[] childrenToClose;
synchronized (this) {
childrenToClose = childContexts.toArray(new PolyglotContextImpl[childContexts.size()]);
}
for (PolyglotContextImpl childContext : childrenToClose) {
childContext.closeImpl(notifyInstruments);
}
}
@SuppressWarnings("serial")
static final class ExitException extends ThreadDeath {
private static final long serialVersionUID = -4838571769179260137L;
private final Node location;
private final SourceSection sourceSection;
private final String exitMessage;
private final int exitCode;
ExitException(Node location, int exitCode, String exitMessage) {
this(location, null, exitCode, exitMessage);
}
ExitException(SourceSection sourceSection, int exitCode, String exitMessage) {
this(null, sourceSection, exitCode, exitMessage);
}
private ExitException(Node location, SourceSection sourceSection, int exitCode, String exitMessage) {
this.location = location;
this.sourceSection = sourceSection;
this.exitCode = exitCode;
this.exitMessage = exitMessage;
}
Node getLocation() {
return location;
}
SourceSection getSourceLocation() {
if (sourceSection != null) {
return sourceSection;
}
return location == null ? null : location.getEncapsulatingSourceSection();
}
@Override
public String getMessage() {
return exitMessage;
}
int getExitCode() {
return exitCode;
}
}
private boolean setPendingExit(int code) {
synchronized (this) {
State targetState;
switch (state) {
case DEFAULT:
case INTERRUPTING:
targetState = State.PENDING_EXIT;
break;
case CLOSING:
case CLOSING_INTERRUPTING:
targetState = State.CLOSING_PENDING_EXIT;
break;
default:
return false;
}
exitCode = code;
exitMessage = "Exit was called with exit code " + code + ".";
closeExitedTriggerThread = Thread.currentThread();
setState(targetState);
return true;
}
}
void closeExited(Node exitLocation, int code) {
if (setPendingExit(code)) {
/*
* If this thread set PENDING_EXIT state and ran exit notifications, it will also be the
* one to execute the transition to EXITING state, unless the exit notifications were
* cancelled by cancelling the whole context.
*/
exitContextNotification(TruffleLanguage.ExitMode.HARD, code);
if (parent == null) {
engine.polyglotHostService.notifyContextCancellingOrExiting(this, true, code, false, exitMessage);
}
List> futures = setExiting(null, code, exitMessage, false);
if (!futures.isEmpty()) {
closeHereOrCancelInCleanupThread(futures);
}
} else {
synchronized (this) {
if (!state.isInvalidOrClosed()) {
/*
* Normally, if closeExited is called more than once, the subsequent calls wait
* until the context is invalid, which means that either the first call to
* closeExited finished running the exit notifications and set the context state
* to the (invalid) state EXITING, or the context was cancelled during the exit
* notifications and it is in the (invalid) state CANCELLING. However, we cannot
* wait for the invalid state when closeExited is called from an exit
* notification because the invalid state is only set when exit notifications
* are finished or cancelled, and so in these cases, we throw the exit exception
* immediately.
*/
PolyglotThreadInfo info = getCurrentThreadInfo();
if (closeExitedTriggerThread == info.getThread() || (info.isPolyglotThread(this) && ((PolyglotThread) info.getThread()).hardExitNotificationThread)) {
throw createExitException(exitLocation);
}
}
}
}
/*
* It is possible that the context is not invalid, but the exit operation was not allowed,
* because the context is being closed in which case the state is neither invalid nor
* PENDING_EXIT. In this case the closeExited operation is a no-op.
*/
State localState = state;
Node location = exitLocation != null ? exitLocation : uncachedLocation;
if (localState == State.PENDING_EXIT || localState == State.CLOSING_PENDING_EXIT || localState.isInvalidOrClosed()) {
/*
* Wait for the context to become invalid. If this is the first call to closeExited that
* ran the exit notifications and set the exiting state, then the context is already
* invalid, otherwise we wait here until the first call to closeExited has done its job.
*/
TruffleSafepoint.setBlockedThreadInterruptible(location, new TruffleSafepoint.Interruptible() {
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
@Override
public void apply(PolyglotContextImpl ctx) throws InterruptedException {
synchronized (ctx) {
while (!ctx.state.isInvalidOrClosed()) {
ctx.wait();
}
}
}
}, this);
localState = state;
if (config.useSystemExit && (localState.isExiting() || localState == State.CLOSED_EXITED)) {
engine.host.hostExit(exitCode);
}
/*
* Poll will throw the correct exception. Either the ThreadDeath exit or the ThreadDeath
* cancel exception based on whether the exit notifications were finished and the hard
* exit can be completed, or the context was cancelled during exit notifications.
*/
TruffleSafepoint.pollHere(location);
}
}
private void closeHereOrCancelInCleanupThread(List> futures) {
boolean cancelInSeparateThread = false;
synchronized (this) {
PolyglotThreadInfo info = getCurrentThreadInfo();
Thread currentThread = Thread.currentThread();
if (info.isPolyglotThread(this) || (!singleThreaded && isActive(currentThread)) || closingThread == currentThread || currentThread instanceof SystemThread) {
/*
* Polyglot thread or system thread must not cancel a context, because cancel waits
* for polyglot threads and system threads to complete. Also, it is not allowed to
* cancel in a thread where a multi-threaded context is entered. This would lead to
* deadlock if more than one thread tried to do that as cancel waits for the context
* not to be entered in all other threads.
*/
cancelInSeparateThread = true;
}
}
if (cancelInSeparateThread) {
if (!futures.isEmpty()) {
/*
* Checking the futures for emptiness makes sure we don't register multiple cleanup
* tasks if this is called from multiple threads
*/
registerCleanupTask(new Runnable() {
@Override
public void run() {
PolyglotEngineImpl.cancelOrExit(PolyglotContextImpl.this, futures);
}
});
}
} else {
closeAndMaybeWait(true, futures);
}
}
private void registerCleanupTask(Runnable cleanupTask) {
synchronized (this) {
if (!state.isClosed()) {
if (cleanupExecutorService == null) {
cleanupExecutorService = Executors.newFixedThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
}
assert cleanupFuture == null : "Multiple cleanup tasks are currently not supported!";
cleanupFuture = cleanupExecutorService.submit(cleanupTask);
}
}
}
void finishCleanup() {
ExecutorService localCleanupService;
synchronized (this) {
if (isActive(Thread.currentThread())) {
/*
* The cleanup must be able to wait for the context to leave all threads which would
* be impossible if it is still entered in the current thread.
*/
return;
}
localCleanupService = cleanupExecutorService;
}
if (localCleanupService != null) {
try {
try {
cleanupFuture.get();
} catch (InterruptedException ie) {
engine.getEngineLogger().log(Level.INFO, "Waiting for polyglot context cleanup was interrupted!", ie);
} catch (ExecutionException ee) {
assert !(ee.getCause() instanceof AbstractTruffleException);
throw sneakyThrow(ee.getCause());
}
} finally {
localCleanupService.shutdownNow();
while (!localCleanupService.isTerminated()) {
try {
if (!localCleanupService.awaitTermination(1, TimeUnit.MINUTES)) {
throw new IllegalStateException("Context cleanup service timeout!");
}
} catch (InterruptedException ie) {
engine.getEngineLogger().log(Level.INFO, "Waiting for polyglot context cleanup was interrupted!", ie);
}
}
}
}
}
@SuppressWarnings("unchecked")
private static RuntimeException sneakyThrow(Throwable ex) throws T {
throw (T) ex;
}
private List disposeContext() {
assert !this.disposing;
this.disposing = true;
List disposedContexts = new ArrayList<>(contexts.length);
for (int i = contexts.length - 1; i >= 0; i--) {
PolyglotLanguageContext context = contexts[i];
boolean disposed = context.dispose();
if (disposed) {
disposedContexts.add(context);
}
}
Closeable[] toClose;
synchronized (this) {
toClose = closeables == null ? null : closeables.toArray(new Closeable[0]);
}
if (toClose != null) {
for (Closeable closeable : toClose) {
try {
closeable.close();
} catch (IOException ioe) {
engine.getEngineLogger().log(Level.WARNING, "Failed to close " + closeable, ioe);
}
}
}
return disposedContexts;
}
private void exitContextNotification(TruffleLanguage.ExitMode exitMode, int code) {
// we need to run exit notifications at least twice in case an exit notification run has
// initialized new contexts
boolean exitNotificationPerformed;
try {
do {
exitNotificationPerformed = false;
for (int i = contexts.length - 1; i >= 0; i--) {
PolyglotLanguageContext context = contexts[i];
if (context.isInitialized()) {
exitNotificationPerformed |= context.exitContext(exitMode, code);
}
}
} while (exitNotificationPerformed);
} catch (Throwable t) {
if (exitMode == TruffleLanguage.ExitMode.NATURAL || !(t instanceof CancelExecution)) {
throw t;
} else {
engine.getEngineLogger().log(Level.FINE, "Execution was cancelled during exit notifications!", t);
}
}
}
private void finalizeContext(boolean notifyInstruments, boolean cancelOrExitOperation) {
// we need to run finalization at least twice in case a finalization run has
// initialized new contexts
TruffleSafepoint safepoint = TruffleSafepoint.getCurrent();
boolean prevChangeAllowActions = PolyglotThreadLocalActions.TL_HANDSHAKE.setChangeAllowActions(safepoint, true);
try {
boolean finalizationPerformed;
do {
finalizationPerformed = false;
// inverse context order is already the right order for context
// disposal/finalization
for (int i = contexts.length - 1; i >= 0; i--) {
PolyglotLanguageContext context = contexts[i];
if (context.isInitialized()) {
try {
finalizationPerformed |= context.finalizeContext(cancelOrExitOperation, notifyInstruments);
} finally {
if (!PolyglotThreadLocalActions.TL_HANDSHAKE.isAllowActions(safepoint)) {
safepoint.setAllowActions(true);
throw new IllegalStateException(
"TruffleSafepoint.setAllowActions is still disabled even though finalization completed. Make sure allow actions are reset in a finally block.");
}
}
}
}
} while (finalizationPerformed);
} finally {
PolyglotThreadLocalActions.TL_HANDSHAKE.setChangeAllowActions(safepoint, prevChangeAllowActions);
}
}
synchronized void maybeSendInterrupt() {
if (!state.isInterrupting() && !state.isCancelling() && !state.isExiting()) {
return;
}
for (PolyglotThreadInfo threadInfo : threads.values()) {
if (!threadInfo.isCurrent() && threadInfo.isActiveNotCancelled()) {
/*
* We send an interrupt to the thread to wake up and to run some guest language code
* in case they are waiting in some async primitive. The interrupt is then cleared
* when the closed is performed.
*/
threadInfo.getThread().interrupt();
}
}
}
Object getLocal(LocalLocation l) {
assert l.engine == this.engine : invalidSharingError(this.engine, l.engine);
return l.readLocal(this, this.contextLocals, false);
}
private Object[] getThreadLocals(Thread thread) {
assert Thread.holdsLock(this);
PolyglotThreadInfo threadInfo = threads.get(thread);
if (threadInfo == null) {
return null;
}
return threadInfo.getContextThreadLocals();
}
/*
* Reading from a different thread than the current thread requires synchronization. as
* threadIdToThreadLocal and threadLocals are always updated on the current thread under the
* context lock.
*/
@TruffleBoundary
synchronized Object getThreadLocal(LocalLocation l, Thread t) {
assert l.engine == this.engine : invalidSharingError(this.engine, l.engine);
Object[] threadLocals = getThreadLocals(t);
if (threadLocals == null) {
return null;
}
return l.readLocal(this, threadLocals, true);
}
void initializeThreadLocals(PolyglotThreadInfo threadInfo) {
assert Thread.holdsLock(this);
assert Thread.currentThread() == threadInfo.getThread() : "thread locals must only be initialized on the current thread";
StableLocalLocations locations = engine.contextThreadLocalLocations;
Object[] locals = new Object[locations.locations.length];
Thread thread = threadInfo.getThread();
for (PolyglotInstrument instrument : engine.idToInstrument.values()) {
if (instrument.isCreated()) {
invokeContextLocalsFactory(this.contextLocals, instrument.contextLocalLocations);
invokeContextThreadFactory(locals, instrument.contextThreadLocalLocations, thread);
}
}
for (PolyglotLanguageContext language : contexts) {
if (language.isCreated()) {
invokeContextLocalsFactory(this.contextLocals, language.getLanguageInstance().contextLocalLocations);
invokeContextThreadFactory(locals, language.getLanguageInstance().contextThreadLocalLocations, thread);
}
}
threadInfo.setContextThreadLocals(locals);
}
void initializeContextLocals() {
assert Thread.holdsLock(this);
if (this.contextLocals != null) {
// Could have already been populated by resizeContextLocals.
return;
}
StableLocalLocations locations = engine.contextLocalLocations;
Object[] locals = new Object[locations.locations.length];
initializeInstrumentContextLocals(locals);
/*
* Languages will be initialized in PolyglotLanguageContext#ensureCreated().
*/
assert this.contextLocals == null;
this.contextLocals = locals;
}
void initializeInstrumentContextLocals(Object[] locals) {
for (PolyglotInstrument instrument : engine.idToInstrument.values()) {
if (instrument.isCreated()) {
invokeContextLocalsFactory(locals, instrument.contextLocalLocations);
}
}
}
void initializeInstrumentContextThreadLocals() {
for (PolyglotInstrument instrument : engine.idToInstrument.values()) {
if (instrument.isCreated()) {
invokeContextThreadLocalFactory(instrument.contextThreadLocalLocations);
}
}
}
void invokeLocalsFactories(LocalLocation[] contextLocalLocations, LocalLocation[] contextThreadLocalLocations) {
PolyglotContextImpl[] localChildContexts;
synchronized (this) {
if (localsCleared) {
return;
}
/*
* contextLocals might not be initialized yet, in which case the context local factory
* for this instrument will be invoked during contextLocals initialization.
*/
if (contextLocals != null) {
invokeContextLocalsFactory(contextLocals, contextLocalLocations);
invokeContextThreadLocalFactory(contextThreadLocalLocations);
}
localChildContexts = PolyglotContextImpl.this.childContexts.toArray(new PolyglotContextImpl[0]);
}
for (PolyglotContextImpl childCtx : localChildContexts) {
childCtx.invokeLocalsFactories(contextLocalLocations, contextThreadLocalLocations);
}
}
void resizeThreadLocals(StableLocalLocations locations) {
PolyglotContextImpl[] localChildContexts;
synchronized (this) {
if (localsCleared) {
return;
}
resizeContextThreadLocals(locations);
localChildContexts = PolyglotContextImpl.this.childContexts.toArray(new PolyglotContextImpl[0]);
}
for (PolyglotContextImpl childCtx : localChildContexts) {
childCtx.resizeThreadLocals(locations);
}
}
void resizeContextThreadLocals(StableLocalLocations locations) {
assert Thread.holdsLock(this);
for (PolyglotThreadInfo threadInfo : threads.values()) {
Object[] threadLocals = threadInfo.getContextThreadLocals();
if (threadLocals.length < locations.locations.length) {
threadInfo.setContextThreadLocals(Arrays.copyOf(threadLocals, locations.locations.length));
}
}
}
void resizeLocals(StableLocalLocations locations) {
PolyglotContextImpl[] localChildContexts;
synchronized (this) {
if (localsCleared) {
return;
}
resizeContextLocals(locations);
localChildContexts = PolyglotContextImpl.this.childContexts.toArray(new PolyglotContextImpl[0]);
}
for (PolyglotContextImpl childCtx : localChildContexts) {
childCtx.resizeLocals(locations);
}
}
void resizeContextLocals(StableLocalLocations locations) {
assert Thread.holdsLock(this);
Object[] oldLocals = this.contextLocals;
if (oldLocals != null) {
if (oldLocals.length > locations.locations.length) {
throw new AssertionError("Context locals array must never shrink.");
} else if (locations.locations.length > oldLocals.length) {
this.contextLocals = Arrays.copyOf(oldLocals, locations.locations.length);
}
} else {
this.contextLocals = new Object[locations.locations.length];
}
}
void invokeContextLocalsFactory(Object[] locals, LocalLocation[] locations) {
assert Thread.holdsLock(this);
if (locations == null) {
return;
}
try {
for (int i = 0; i < locations.length; i++) {
LocalLocation location = locations[i];
if (locals[location.index] == null) {
locals[location.index] = location.invokeFactory(this, null);
}
}
} catch (Throwable t) {
// reset values again the language failed to initialize
for (int i = 0; i < locations.length; i++) {
locals[locations[i].index] = null;
}
throw t;
}
}
void invokeContextThreadLocalFactory(LocalLocation[] locations) {
assert Thread.holdsLock(this);
if (locations == null) {
return;
}
for (PolyglotThreadInfo threadInfo : threads.values()) {
invokeContextThreadFactory(threadInfo.getContextThreadLocals(), locations, threadInfo.getThread());
}
}
private void invokeContextThreadFactory(Object[] threadLocals, LocalLocation[] locations, Thread thread) {
assert Thread.holdsLock(this);
if (locations == null) {
return;
}
try {
for (int i = 0; i < locations.length; i++) {
LocalLocation location = locations[i];
if (threadLocals[location.index] == null) {
threadLocals[location.index] = location.invokeFactory(this, thread);
}
}
} catch (Throwable t) {
// reset values again the language failed to initialize
for (int i = 0; i < locations.length; i++) {
threadLocals[locations[i].index] = null;
}
throw t;
}
}
static String invalidSharingError(PolyglotEngineImpl expectedEngine, PolyglotEngineImpl actualEngine) {
return String.format("Detected invaliding sharing of context locals between polyglot engines. Expected engine %s but was %s.", expectedEngine, actualEngine);
}
boolean patch(PolyglotContextConfig newConfig) {
CompilerAsserts.neverPartOfCompilation();
if (PreInitContextHostLanguage.isInstance(contexts[PolyglotEngineImpl.HOST_LANGUAGE_INDEX].language)) {
maybeInitializeHostLanguage(contexts);
}
this.config = newConfig;
threadLocalActions.onContextPatch();
if (!newConfig.logLevels.isEmpty()) {
EngineAccessor.LANGUAGE.configureLoggers(this, newConfig.logLevels, getAllLoggers());
}
final Object[] prev = engine.enter(this);
try {
for (int i = 0; i < this.contexts.length; i++) {
final PolyglotLanguageContext context = this.contexts[i];
if (context.language.isHost()) {
initializeHostContext(context, newConfig);
}
if (!context.patch(newConfig)) {
return false;
}
}
} finally {
engine.leave(prev, this);
}
return true;
}
@SuppressWarnings("unchecked")
void initializeHostContext(PolyglotLanguageContext context, PolyglotContextConfig newConfig) {
Object contextImpl = context.getContextImpl();
if (contextImpl == null) {
throw new AssertionError("Host context not initialized.");
}
this.hostContextImpl = contextImpl;
AbstractHostLanguageService currentHost = engine.host;
AbstractHostLanguageService newHost = context.lookupService(AbstractHostLanguageService.class);
if (newHost == null) {
throw new AssertionError("The engine host language must register a service of type:" + AbstractHostLanguageService.class);
}
if (currentHost == null) {
engine.host = newHost;
} else if (currentHost != newHost) {
throw new AssertionError("Host service must not change per engine.");
}
newHost.initializeHostContext(this, contextImpl, newConfig.hostAccess, newConfig.hostClassLoader, newConfig.classFilter, newConfig.hostClassLoadingAllowed,
newConfig.hostLookupAllowed);
}
void replayInstrumentationEvents() {
notifyContextCreated();
EngineAccessor.INSTRUMENT.notifyThreadStarted(engine, creatorTruffleContext, Thread.currentThread());
for (PolyglotLanguageContext lc : contexts) {
LanguageInfo language = lc.language.info;
if (lc.eventsEnabled && lc.env != null) {
EngineAccessor.INSTRUMENT.notifyLanguageContextCreate(this, creatorTruffleContext, language);
EngineAccessor.INSTRUMENT.notifyLanguageContextCreated(this, creatorTruffleContext, language);
if (lc.isInitialized()) {
EngineAccessor.INSTRUMENT.notifyLanguageContextInitialize(this, creatorTruffleContext, language);
EngineAccessor.INSTRUMENT.notifyLanguageContextInitialized(this, creatorTruffleContext, language);
if (lc.finalized) {
EngineAccessor.INSTRUMENT.notifyLanguageContextFinalized(this, creatorTruffleContext, language);
}
}
}
}
}
private synchronized void checkSubProcessFinished() {
ProcessHandlers.ProcessDecorator[] processes = subProcesses.toArray(new ProcessHandlers.ProcessDecorator[subProcesses.size()]);
for (ProcessHandlers.ProcessDecorator process : processes) {
if (process.isAlive()) {
throw new IllegalStateException(String.format("The context has an alive sub-process %s created by %s.",
process.getCommand(), process.getOwner().language.getId()));
}
}
}
private synchronized void checkSystemThreadsFinished() {
if (!activeSystemThreads.isEmpty()) {
LanguageSystemThread thread = activeSystemThreads.iterator().next();
throw new IllegalStateException(String.format("The context has an alive system thread %s created by language %s.", thread.getName(), thread.languageId));
}
}
static PolyglotContextImpl preinitialize(final PolyglotEngineImpl engine, final PreinitConfig preinitConfig, PolyglotSharingLayer sharableLayer, Set languagesToPreinitialize,
boolean emitWarning) {
final FileSystemConfig fileSystemConfig = new FileSystemConfig(IOAccess.ALL, new PreInitializeContextFileSystem(), new PreInitializeContextFileSystem());
final PolyglotContextConfig config = new PolyglotContextConfig(engine, fileSystemConfig, preinitConfig);
final PolyglotContextImpl context = new PolyglotContextImpl(engine, config);
synchronized (engine.lock) {
engine.addContext(context);
}
context.sourcesToInvalidate = new ArrayList<>();
try {
if (sharableLayer != null) {
if (!context.claimSharingLayer(sharableLayer, languagesToPreinitialize)) {
// could not claim layer. cannot preinitialize context.
return null;
}
}
synchronized (context) {
context.initializeContextLocals();
}
if (!languagesToPreinitialize.isEmpty()) {
Object[] prev = context.engine.enter(context);
try {
for (PolyglotLanguage language : languagesToPreinitialize) {
assert language.engine == engine : "invalid language";
if (overridesPatchContext(language.getId())) {
context.getContextInitialized(language, null);
LOG.log(Level.FINE, "Pre-initialized context for language: {0}", language.getId());
} else {
if (emitWarning) {
LOG.log(Level.WARNING, "Language {0} cannot be pre-initialized as it does not override TruffleLanguage.patchContext method.", language.getId());
}
}
}
} finally {
context.leaveThreadChanged(prev, true, true, true);
}
}
return context;
} finally {
for (PolyglotLanguage language : engine.languages) {
if (language != null) {
language.clearOptionValues();
}
}
synchronized (engine.lock) {
engine.removeContext(context);
}
for (Source sourceToInvalidate : context.sourcesToInvalidate) {
EngineAccessor.SOURCE.invalidateAfterPreinitialiation(sourceToInvalidate);
}
context.singleThreadValue.reset();
context.sourcesToInvalidate = null;
context.threadLocalActions.prepareContextStore();
((PreInitializeContextFileSystem) fileSystemConfig.fileSystem).onPreInitializeContextEnd();
((PreInitializeContextFileSystem) fileSystemConfig.internalFileSystem).onPreInitializeContextEnd();
if (!config.logLevels.isEmpty()) {
EngineAccessor.LANGUAGE.configureLoggers(context, null, context.getAllLoggers());
}
}
}
Object getOrCreateContextLoggers() {
Object res = contextBoundLoggers;
if (res == null) {
synchronized (this) {
res = contextBoundLoggers;
if (res == null) {
res = LANGUAGE.createEngineLoggers(PolyglotLoggers.LoggerCache.newContextLoggerCache(this));
if (!this.config.logLevels.isEmpty()) {
EngineAccessor.LANGUAGE.configureLoggers(this, this.config.logLevels, res);
}
contextBoundLoggers = res;
}
}
}
return res;
}
private Object[] getAllLoggers() {
Object defaultLoggers = EngineAccessor.LANGUAGE.getDefaultLoggers();
Object engineLoggers = engine.getEngineLoggers();
Object contextLoggers = contextBoundLoggers;
List allLoggers = new ArrayList<>(3);
allLoggers.add(defaultLoggers);
if (engineLoggers != null) {
allLoggers.add(engineLoggers);
}
if (contextLoggers != null) {
allLoggers.add(contextLoggers);
}
return allLoggers.toArray(new Object[allLoggers.size()]);
}
static class ContextWeakReference extends WeakReference {
volatile boolean removed = false;
volatile PolyglotSharingLayer layer;
ContextWeakReference(PolyglotContextImpl referent) {
super(referent, referent.engine.contextsReferenceQueue);
}
void freeSharing(PolyglotContextImpl context) {
if (context != null) {
assert layer == null || layer.equals(context.layer);
}
if (layer != null && layer.isClaimed()) {
layer.engine.freeSharingLayer(layer, context);
}
}
}
private CancelExecution createCancelException(Node location) {
return new CancelExecution(location, invalidMessage, invalidResourceLimit);
}
private ExitException createExitException(Node location) {
return new ExitException(location, exitCode, exitMessage);
}
private static boolean overridesPatchContext(String languageId) {
if (TruffleOptions.AOT) {
return LanguageCache.overridesPathContext(languageId);
} else {
// Used by context pre-initialization tests on HotSpot
LanguageCache cache = LanguageCache.languages().get(languageId);
for (Method m : cache.loadLanguage().getClass().getDeclaredMethods()) {
if (m.getName().equals("patchContext")) {
return true;
}
}
return false;
}
}
synchronized void registerOnDispose(Closeable closeable) {
if (disposing) {
throw new IllegalStateException("Cannot register closeable when context is being disposed.");
}
if (closeables == null) {
closeables = Collections.newSetFromMap(new WeakHashMap<>());
}
closeables.add(Objects.requireNonNull(closeable));
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("PolyglotContextImpl[");
b.append("state=");
State localState = state;
b.append(localState.name());
b.append(",disposing=");
b.append(disposing);
if (!localState.isClosed()) {
if (isActive()) {
b.append(", active");
} else {
b.append(", inactive");
}
}
b.append(" languages=[");
String sep = "";
for (PolyglotLanguageContext languageContext : contexts) {
if (languageContext.isInitialized() || languageContext.isCreated()) {
b.append(sep);
b.append(languageContext.language.getId());
sep = ", ";
}
}
b.append("]");
b.append("]");
return b.toString();
}
private static final class UncachedLocationNode extends HostToGuestRootNode {
UncachedLocationNode(PolyglotSharingLayer layer) {
super(layer);
}
@Override
protected Class> getReceiverType() {
throw CompilerDirectives.shouldNotReachHere();
}
@Override
protected Object executeImpl(PolyglotLanguageContext languageContext, Object receiver, Object[] args) {
throw CompilerDirectives.shouldNotReachHere();
}
@Override
public boolean isInternal() {
return true;
}
}
private final class CancellationThreadLocalAction extends ThreadLocalAction {
CancellationThreadLocalAction() {
super(false, false);
}
@Override
protected void perform(Access access) {
PolyglotContextImpl.this.threadLocalActions.submit(new Thread[]{access.getThread()}, PolyglotEngineImpl.ENGINE_ID, this, new HandshakeConfig(true, false, false, true));
State localState = PolyglotContextImpl.this.state;
if (localState.isCancelling() || localState.isExiting() || localState == State.CLOSED_CANCELLED || localState == State.CLOSED_EXITED) {
if (localState.isExiting() || localState == State.CLOSED_EXITED) {
throw createExitException(access.getLocation());
} else {
throw createCancelException(access.getLocation());
}
}
}
}
private final class InterruptThreadLocalAction extends ThreadLocalAction {
InterruptThreadLocalAction() {
super(true, false);
}
@Override
protected void perform(Access access) {
PolyglotContextImpl.this.threadLocalActions.submit(new Thread[]{access.getThread()}, PolyglotEngineImpl.ENGINE_ID, this, true);
State localState = state;
if (access.getThread() != PolyglotContextImpl.this.closingThread) {
if (localState.isInterrupting() || localState == State.CLOSED_INTERRUPTED) {
PolyglotContextImpl[] interruptingChildContexts;
synchronized (PolyglotContextImpl.this) {
interruptingChildContexts = PolyglotContextImpl.this.childContexts.toArray(new PolyglotContextImpl[0]);
}
for (PolyglotContextImpl childCtx : interruptingChildContexts) {
if (access.getThread() == childCtx.closingThread) {
return;
}
}
// Interrupt should never break a closing operation
throw new PolyglotEngineImpl.InterruptExecution(access.getLocation());
}
}
}
}
@TruffleBoundary
void runOnCancelled() {
Runnable onCancelledRunnable = config.onCancelled;
if (onCancelledRunnable != null) {
onCancelledRunnable.run();
}
}
@TruffleBoundary
void runOnExited(int code) {
Consumer onExitedRunnable = config.onExited;
if (onExitedRunnable != null) {
onExitedRunnable.accept(code);
}
}
@TruffleBoundary
void runOnClosed() {
Runnable onClosedRunnable = config.onClosed;
if (onClosedRunnable != null) {
onClosedRunnable.run();
}
}
synchronized void addSystemThread(LanguageSystemThread thread) {
if (!state.isClosed()) {
activeSystemThreads.add(thread);
}
}
synchronized void removeSystemThread(LanguageSystemThread thread) {
activeSystemThreads.remove(thread);
}
}