Skip to content

Commit

Permalink
Add error protection to wait-for.
Browse files Browse the repository at this point in the history
  • Loading branch information
Moderocky committed Apr 1, 2022
1 parent 0da8051 commit e233ae5
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@
import mx.kenzie.foundation.WriteInstruction;
import org.byteskript.skript.api.HandlerType;
import org.byteskript.skript.api.note.Documentation;
import org.byteskript.skript.api.note.ForceExtract;
import org.byteskript.skript.api.syntax.ControlEffect;
import org.byteskript.skript.compiler.*;
import org.byteskript.skript.compiler.structure.PreVariable;
import org.byteskript.skript.error.ScriptRuntimeError;
import org.byteskript.skript.lang.element.StandardElements;
import org.byteskript.skript.lang.handler.StandardHandlers;
import org.byteskript.skript.lang.syntax.flow.lambda.SupplierSection;
import org.byteskript.skript.runtime.internal.ExtractedSyntaxCalls;
import org.byteskript.skript.runtime.threading.ScriptThread;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;

import java.lang.invoke.LambdaMetafactory;
Expand All @@ -36,13 +35,14 @@
name = "Wait For",
description = """
Runs the given executable in the background.
Background processes ignore waits from the current process.
The current process will wait for completion or a `wake` call.
After a wake call both processes will run in parallel.
""",
examples = {
"""
run {function} in the background
run {runnable} in the background
"""
wait for {function}
wait for {runnable}
"""
}
)
public class WaitForEffect extends ControlEffect {
Expand All @@ -51,21 +51,6 @@ public WaitForEffect() {
super(SkriptLangSpec.LIBRARY, StandardElements.EFFECT, "wait for %Executable%");
}

@ForceExtract
public static Object getLock() {
if (!(Thread.currentThread() instanceof ScriptThread thread))
throw new ScriptRuntimeError("Unable to put non-script thread to sleep.");
return thread.lock;
}

@ForceExtract
public static void unlock() {
final ScriptThread thread = ((ScriptThread) Thread.currentThread());
synchronized (thread.lock) {
thread.lock.notify();
}
}

@Override
public Pattern.Match match(String thing, Context context) {
if (!thing.startsWith("wait for ")) return null;
Expand Down Expand Up @@ -100,18 +85,40 @@ public void preCompile(Context context, Pattern.Match match) throws Throwable {
.setReturnType(new Type(void.class));
SupplierSection.extractVariables(context, method, child);
context.setMethod(child);
final Label start = new Label(), end = new Label(), after = new Label();
tree.metadata.put("end", end);
tree.metadata.put("after", after);
child.writeCode((writer, visitor) -> {
visitor.visitTryCatchBlock(start, end, after, null);
visitor.visitLabel(start);
});
}

@Override
public void compile(Context context, Pattern.Match match) throws Throwable {
final ElementTree tree = context.getCompileCurrent();
final int variable = (int) tree.metadata.get("variable");
final String location = new Type(ScriptThread.class).internalName();
final Label end = ((Label) tree.metadata.get("end"));
final Label after = ((Label) tree.metadata.get("after"));
final Label jump = new Label(), rethrow = new Label();
final PreVariable store = new PreVariable("error");
context.forceUnspecVariable(store);
final int error = context.slotOf(store);
final MethodBuilder method;
{
final MethodBuilder child = context.getMethod();
this.writeCall(child, RunEffect.class.getMethod("run", Object.class), context);
child.writeCode((writer, visitor) -> {
visitor.visitJumpInsn(167, jump);
visitor.visitLabel(after);
visitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
visitor.visitVarInsn(58, error);
visitor.visitJumpInsn(167, rethrow);
visitor.visitLabel(end);
});
child.writeCode((writer, visitor) -> {
visitor.visitLabel(jump);
visitor.visitVarInsn(Opcodes.ALOAD, variable);
visitor.visitTypeInsn(192, location);
visitor.visitFieldInsn(180, location, "lock", "Ljava/lang/Object;");
Expand All @@ -120,8 +127,13 @@ public void compile(Context context, Pattern.Match match) throws Throwable {
visitor.visitInsn(Opcodes.MONITORENTER);
visitor.visitMethodInsn(182, "java/lang/Object", "notify", "()V", false);
visitor.visitInsn(Opcodes.MONITOREXIT);
visitor.visitInsn(Opcodes.RETURN);
visitor.visitLabel(rethrow);
visitor.visitVarInsn(25, error);
visitor.visitInsn(Opcodes.ATHROW);
visitor.visitInsn(Opcodes.RETURN);

});
this.writeCall(child, WaitForEffect.class.getMethod("unlock"), context);
child.writeCode(WriteInstruction.returnEmpty());
final String internal = context.getType().internalName();
method = context.getTriggerMethod();
Expand Down
27 changes: 26 additions & 1 deletion src/main/java/org/byteskript/skript/runtime/Skript.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ public Skript(SkriptThreadProvider threadProvider, ModifiableCompiler compiler,
this.compiler = compiler;
this.factory = threadProvider;
this.factory.setSkriptInstance(this);
this.executor = Executors.newCachedThreadPool(factory);
this.executor = new ScriptThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
factory);
this.mainThread = main;
this.scheduler = new ScheduledThreadPoolExecutor(4, factory);
this.processes = new ArrayList<>();
Expand Down Expand Up @@ -776,6 +779,18 @@ public Script compileLoad(InputStream stream, String name) {
return loadScript(compileScript(stream, name));
}

@Description("""
Loads a script from compiled source code.
""")
@GenerateExample
public Script loadScript(final PostCompileClass[] data) {
final Class<?>[] classes = new Class[data.length];
for (int i = 0; i < data.length; i++) {
classes[i] = this.loadClass(data[i].name(), data[i].code());
}
return this.loadScript(classes);
}

@Description("""
Loads a script from compiled source code.
""")
Expand All @@ -784,6 +799,16 @@ public Script loadScript(final PostCompileClass datum) {
return this.loadScript(this.loadClass(datum.name(), datum.code()));
}

@Description("""
Loads a script from defined classes.
""")
@GenerateExample
public Script loadScript(final Class<?>[] loaded) {
final Script script = new Script(this, null, loaded);
this.scripts.add(script);
return script;
}

@Description("""
Loads a script from a defined class.
""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,22 @@ public static void runOnAsyncThread(final Runnable runnable) {
final Thread current = Thread.currentThread();
if (!(current instanceof ScriptThread thread))
throw new ScriptRuntimeError("Cannot create background process from non-script thread.");
thread.skript.runOnAsyncThread(runnable);
thread.skript.runOnAsyncThread((Runnable) () -> {
((ScriptThread) Thread.currentThread()).variables.clear();
runnable.run();
((ScriptThread) Thread.currentThread()).variables.clear();
});
}

public static void runOnAsyncThread(final Instruction<?> runnable) {
final Thread current = Thread.currentThread();
if (!(current instanceof ScriptThread thread))
throw new ScriptRuntimeError("Cannot create background process from non-script thread.");
thread.skript.runOnAsyncThread(runnable);
thread.skript.runOnAsyncThread((Instruction<?>) () -> {
((ScriptThread) Thread.currentThread()).variables.clear();
runnable.run();
((ScriptThread) Thread.currentThread()).variables.clear();
});
}

public static Object getListSize(Object target) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2022 ByteSkript org (Moderocky)
* View the full licence information and permissions:
* https://github.com/Moderocky/ByteSkript/blob/master/LICENSE
*/

package org.byteskript.skript.runtime.threading;

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ScriptThreadPoolExecutor extends ThreadPoolExecutor {

public ScriptThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue<Runnable> workQueue, @NotNull ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}

}
2 changes: 1 addition & 1 deletion src/test/java/org/byteskript/skript/test/SyntaxTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void all() throws Throwable {
}
try {
final long now, then;
final Script script = skript.loadScripts(classes).iterator().next();
final Script script = skript.loadScript(classes);
now = System.currentTimeMillis();
final boolean result;
final Object object = script.getFunction("test").run(skript).get();
Expand Down
10 changes: 2 additions & 8 deletions src/test/resources/tests/threadvariable.bsk
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,11 @@ function test:
assert {_blob} is null: "Thread variable unset check failed."
run a new runnable:
assert {_var} is 2: "Thread variable access from runnable failed."
run a new runnable in the background:
wait for a new runnable:
assert {_var} is null: "Thread variable access from wrong thread passed."
wait 1 ms
wake {thread}
sleep
run a new runnable in the background:
wait for a new runnable:
run copy_threadlocals_from({thread})
assert {_var} is 2: "Thread locals copying failed."
wait 1 ms
wake {thread}
sleep
delete {_var}
assert {_var} is null: "Thread variable deletion failed."
return true
Expand Down
4 changes: 4 additions & 0 deletions src/test/resources/tests/waitfor.bsk
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ function test:
wait for a new runnable:
set {@thing} to true
assert {@thing} is true
set {_var} to false
wait for a new runnable:
set {_var} to true
assert {_var} is false
return true

0 comments on commit e233ae5

Please sign in to comment.