Skip to content

Commit

Permalink
Attempt at bandaid fixing concurrency issues II. Removed Script and C…
Browse files Browse the repository at this point in the history
…ontinuation native objects
  • Loading branch information
LatvianModder committed Dec 9, 2023
1 parent 278a047 commit 91af158
Show file tree
Hide file tree
Showing 15 changed files with 99 additions and 944 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public ArrowFunction(Context cx, Scriptable scope, Callable targetFunction, Scri
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Scriptable callThis = boundThis != null ? boundThis : cx.getTopCallOrThrow();
return targetFunction.call(cx, scope, callThis, args);
return cx.callSync(targetFunction, scope, callThis, args);
}

@Override
Expand Down
236 changes: 83 additions & 153 deletions common/src/main/java/dev/latvian/mods/rhino/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -57,30 +54,10 @@

@SuppressWarnings("ThrowableNotThrown")
public class Context {
public static final String errorReporterProperty = "error reporter";

public static Context enter() {
return new Context();
}

/**
* Call {@link
* Callable#call(Context cx, Scriptable scope, Scriptable thisObj,
* Object[] args)}
* using the Context instance associated with the current thread.
* If no Context is associated with the thread, then
* {@link ContextFactory#makeContext()} will be called to construct
* new Context instance. The instance will be temporary associated
* with the thread during call to {@link ContextAction#run(Context)}.
* <p>
* It is allowed but not advisable to use null for <code>factory</code>
* argument in which case the global static singleton ContextFactory
* instance will be used to create new context instances.
*/
public static Object call(Context cx, final Callable callable, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
return callable.call(cx, scope, thisObj, args);
}

/**
* Report a warning using the error reporter for the current thread.
*
Expand Down Expand Up @@ -108,17 +85,6 @@ public static void reportWarning(String message, Context cx) {
Context.reportWarning(cx, message, filename, linep[0], null, 0);
}

public static void reportWarning(Context cx, String message, Throwable t) {
int[] linep = {0};
String filename = getSourcePositionFromStack(cx, linep);
Writer sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println(message);
t.printStackTrace(pw);
pw.flush();
Context.reportWarning(cx, sw.toString(), filename, linep[0], null, 0);
}

/**
* Report an error using the error reporter for the current thread.
*
Expand Down Expand Up @@ -336,6 +302,8 @@ public static String getSourcePositionFromStack(Context cx, int[] linep) {
return null;
}

public final Object lock = new Object();

// Generate an observer count on compiled code
public boolean generateObserverCount = false;
private Scriptable topCallScope;
Expand Down Expand Up @@ -631,95 +599,6 @@ public final Object evaluateReader(Scriptable scope, Reader in, String sourceNam
return null;
}

/**
* Execute script that may pause execution by capturing a continuation.
* Caller must be prepared to catch a ContinuationPending exception
* and resume execution by calling
* {@link #resumeContinuation(Object, Scriptable, Object)}.
*
* @param script The script to execute. Script must have been compiled
* with interpreted mode (optimization level -1)
* @param scope The scope to execute the script against
* @throws ContinuationPending if the script calls a function that results
* in a call to {@link #captureContinuation()}
* @since 1.7 Release 2
*/
public Object executeScriptWithContinuations(Script script, Scriptable scope) throws ContinuationPending {
if (!(script instanceof InterpretedFunction) || !((InterpretedFunction) script).isScript()) {
// Can only be applied to scripts
throw new IllegalArgumentException("Script argument was not" + " a script or was not created by interpreted mode ");
}
return callFunctionWithContinuations((InterpretedFunction) script, scope, ScriptRuntime.EMPTY_OBJECTS);
}

/**
* Call function that may pause execution by capturing a continuation.
* Caller must be prepared to catch a ContinuationPending exception
* and resume execution by calling
* {@link #resumeContinuation(Object, Scriptable, Object)}.
*
* @param function The function to call. The function must have been
* compiled with interpreted mode (optimization level -1)
* @param scope The scope to execute the script against
* @param args The arguments for the function
* @throws ContinuationPending if the script calls a function that results
* in a call to {@link #captureContinuation()}
* @since 1.7 Release 2
*/
public Object callFunctionWithContinuations(Callable function, Scriptable scope, Object[] args) throws ContinuationPending {
if (!(function instanceof InterpretedFunction)) {
// Can only be applied to scripts
throw new IllegalArgumentException("Function argument was not" + " created by interpreted mode ");
}
if (hasTopCallScope()) {
throw new IllegalStateException("Cannot have any pending top " + "calls when executing a script with continuations");
}
// Annotate so we can check later to ensure no java code in
// intervening frames
isContinuationsTopCall = true;
return ScriptRuntime.doTopCall(this, scope, function, scope, args, isTopLevelStrict);
}

/**
* Capture a continuation from the current execution. The execution must
* have been started via a call to
* {@link #executeScriptWithContinuations(Script, Scriptable)} or
* {@link #callFunctionWithContinuations(Callable, Scriptable, Object[])}.
* This implies that the code calling
* this method must have been called as a function from the
* JavaScript script. Also, there cannot be any non-JavaScript code
* between the JavaScript frames (e.g., a call to eval()). The
* ContinuationPending exception returned must be thrown.
*
* @return A ContinuationPending exception that must be thrown
* @since 1.7 Release 2
*/
public ContinuationPending captureContinuation() {
return new ContinuationPending(Interpreter.captureContinuation(this));
}

/**
* Restarts execution of the JavaScript suspended at the call
* to {@link #captureContinuation()}. Execution of the code will resume
* with the functionResult as the result of the call that captured the
* continuation.
* Execution of the script will either conclude normally and the
* result returned, another continuation will be captured and
* thrown, or the script will terminate abnormally and throw an exception.
*
* @param continuation The value returned by
* {@link ContinuationPending#getContinuation()}
* @param functionResult This value will appear to the code being resumed
* as the result of the function that captured the continuation
* @throws ContinuationPending if another continuation is captured before
* the code terminates
* @since 1.7 Release 2
*/
public Object resumeContinuation(Object continuation, Scriptable scope, Object functionResult) throws ContinuationPending {
Object[] args = {functionResult};
return Interpreter.restartContinuation((NativeContinuation) continuation, this, scope, args);
}

/**
* Compiles the source in the given reader.
* <p>
Expand Down Expand Up @@ -1176,18 +1055,6 @@ public void addToScope(Scriptable scope, String name, Object value) {
}
}

/**
* Execute top call to script or function.
* When the runtime is about to execute a script or function that will
* create the first stack frame with scriptable code, it calls this method
* to perform the real call. In this way execution of any script
* happens inside this function.
*/
protected Object doTopCall(Callable callable, Scriptable scope, Scriptable thisObj, Object[] args) {
Object result = callable.call(this, scope, thisObj, args);
return result instanceof ConsString ? result.toString() : result;
}

// custom data

/**
Expand Down Expand Up @@ -1359,37 +1226,100 @@ public final void setWrapFactory(WrapFactory wrapFactory) {
this.wrapFactory = wrapFactory;
}

public synchronized boolean hasTopCallScope() {
return topCallScope != null;
public boolean hasTopCallScope() {
synchronized (lock) {
return topCallScope != null;
}
}

public synchronized Scriptable getTopCallScope() {
return topCallScope;
public Scriptable getTopCallScope() {
synchronized (lock) {
return topCallScope;
}
}

public synchronized Scriptable getTopCallOrThrow() {
if (topCallScope == null) {
throw new IllegalStateException();
public Scriptable getTopCallOrThrow() {
synchronized (lock) {
if (topCallScope == null) {
throw new IllegalStateException();
}

return topCallScope;
}
}

return topCallScope;
public void setTopCall(Scriptable scope) {
synchronized (lock) {
topCallScope = scope;
}
}

public synchronized void setTopCall(Scriptable scope) {
topCallScope = scope;
public void storeScriptable(Scriptable value) {
synchronized (lock) {
// The previously stored scratchScriptable should be consumed
if (scratchScriptable != null) {
throw new IllegalStateException();
}
scratchScriptable = value;
}
}

public synchronized void storeScriptable(Scriptable value) {
// The previously stored scratchScriptable should be consumed
if (scratchScriptable != null) {
throw new IllegalStateException();
public Scriptable lastStoredScriptable() {
synchronized (lock) {
Scriptable result = scratchScriptable;
scratchScriptable = null;
return result;
}
}

/**
* Call {@link
* Callable#call(Context cx, Scriptable scope, Scriptable thisObj,
* Object[] args)}
* using the Context instance associated with the current thread.
* If no Context is associated with the thread, then makeContext() will be called to construct
* new Context instance. The instance will be temporary associated
* with the thread during call to {@link ContextAction#run(Context)}.
* <p>
* It is allowed but not advisable to use null for <code>factory</code>
* argument in which case the global static singleton ContextFactory
* instance will be used to create new context instances.
*/
public Object callSync(Callable callable, Scriptable scope, Scriptable thisObj, Object[] args) {
synchronized (lock) {
return callable.call(this, scope, thisObj, args);
}
scratchScriptable = value;
}

public synchronized Scriptable lastStoredScriptable() {
Scriptable result = scratchScriptable;
scratchScriptable = null;
public Object doTopCall(Scriptable scope, Callable callable, Scriptable thisObj, Object[] args, boolean isTopLevelStrict) {
if (scope == null) {
throw new IllegalArgumentException();
}
if (hasTopCallScope()) {
throw new IllegalStateException();
}

Object result;
setTopCall(ScriptableObject.getTopLevelScope(scope));
boolean previousTopLevelStrict = this.isTopLevelStrict;
this.isTopLevelStrict = isTopLevelStrict;
try {
result = callSync(callable, scope, thisObj, args);

if (result instanceof ConsString) {
result = result.toString();
}
} finally {
setTopCall(null);
// Cleanup cached references
this.isTopLevelStrict = previousTopLevelStrict;

if (currentActivationCall != null) {
// Function should always call exitActivationFunction
// if it creates activation record
throw new IllegalStateException();
}
}
return result;
}
}

This file was deleted.

Loading

0 comments on commit 91af158

Please sign in to comment.