Skip to content

Commit e233ae5

Browse files
committed
Add error protection to wait-for.
1 parent 0da8051 commit e233ae5

File tree

7 files changed

+99
-34
lines changed

7 files changed

+99
-34
lines changed

src/main/java/org/byteskript/skript/lang/syntax/flow/execute/WaitForEffect.java

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,16 @@
1212
import mx.kenzie.foundation.WriteInstruction;
1313
import org.byteskript.skript.api.HandlerType;
1414
import org.byteskript.skript.api.note.Documentation;
15-
import org.byteskript.skript.api.note.ForceExtract;
1615
import org.byteskript.skript.api.syntax.ControlEffect;
1716
import org.byteskript.skript.compiler.*;
1817
import org.byteskript.skript.compiler.structure.PreVariable;
19-
import org.byteskript.skript.error.ScriptRuntimeError;
2018
import org.byteskript.skript.lang.element.StandardElements;
2119
import org.byteskript.skript.lang.handler.StandardHandlers;
2220
import org.byteskript.skript.lang.syntax.flow.lambda.SupplierSection;
2321
import org.byteskript.skript.runtime.internal.ExtractedSyntaxCalls;
2422
import org.byteskript.skript.runtime.threading.ScriptThread;
2523
import org.objectweb.asm.Handle;
24+
import org.objectweb.asm.Label;
2625
import org.objectweb.asm.Opcodes;
2726

2827
import java.lang.invoke.LambdaMetafactory;
@@ -36,13 +35,14 @@
3635
name = "Wait For",
3736
description = """
3837
Runs the given executable in the background.
39-
Background processes ignore waits from the current process.
38+
The current process will wait for completion or a `wake` call.
39+
After a wake call both processes will run in parallel.
4040
""",
4141
examples = {
4242
"""
43-
run {function} in the background
44-
run {runnable} in the background
45-
"""
43+
wait for {function}
44+
wait for {runnable}
45+
"""
4646
}
4747
)
4848
public class WaitForEffect extends ControlEffect {
@@ -51,21 +51,6 @@ public WaitForEffect() {
5151
super(SkriptLangSpec.LIBRARY, StandardElements.EFFECT, "wait for %Executable%");
5252
}
5353

54-
@ForceExtract
55-
public static Object getLock() {
56-
if (!(Thread.currentThread() instanceof ScriptThread thread))
57-
throw new ScriptRuntimeError("Unable to put non-script thread to sleep.");
58-
return thread.lock;
59-
}
60-
61-
@ForceExtract
62-
public static void unlock() {
63-
final ScriptThread thread = ((ScriptThread) Thread.currentThread());
64-
synchronized (thread.lock) {
65-
thread.lock.notify();
66-
}
67-
}
68-
6954
@Override
7055
public Pattern.Match match(String thing, Context context) {
7156
if (!thing.startsWith("wait for ")) return null;
@@ -100,18 +85,40 @@ public void preCompile(Context context, Pattern.Match match) throws Throwable {
10085
.setReturnType(new Type(void.class));
10186
SupplierSection.extractVariables(context, method, child);
10287
context.setMethod(child);
88+
final Label start = new Label(), end = new Label(), after = new Label();
89+
tree.metadata.put("end", end);
90+
tree.metadata.put("after", after);
91+
child.writeCode((writer, visitor) -> {
92+
visitor.visitTryCatchBlock(start, end, after, null);
93+
visitor.visitLabel(start);
94+
});
10395
}
10496

10597
@Override
10698
public void compile(Context context, Pattern.Match match) throws Throwable {
10799
final ElementTree tree = context.getCompileCurrent();
108100
final int variable = (int) tree.metadata.get("variable");
109101
final String location = new Type(ScriptThread.class).internalName();
102+
final Label end = ((Label) tree.metadata.get("end"));
103+
final Label after = ((Label) tree.metadata.get("after"));
104+
final Label jump = new Label(), rethrow = new Label();
105+
final PreVariable store = new PreVariable("error");
106+
context.forceUnspecVariable(store);
107+
final int error = context.slotOf(store);
110108
final MethodBuilder method;
111109
{
112110
final MethodBuilder child = context.getMethod();
113111
this.writeCall(child, RunEffect.class.getMethod("run", Object.class), context);
114112
child.writeCode((writer, visitor) -> {
113+
visitor.visitJumpInsn(167, jump);
114+
visitor.visitLabel(after);
115+
visitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
116+
visitor.visitVarInsn(58, error);
117+
visitor.visitJumpInsn(167, rethrow);
118+
visitor.visitLabel(end);
119+
});
120+
child.writeCode((writer, visitor) -> {
121+
visitor.visitLabel(jump);
115122
visitor.visitVarInsn(Opcodes.ALOAD, variable);
116123
visitor.visitTypeInsn(192, location);
117124
visitor.visitFieldInsn(180, location, "lock", "Ljava/lang/Object;");
@@ -120,8 +127,13 @@ public void compile(Context context, Pattern.Match match) throws Throwable {
120127
visitor.visitInsn(Opcodes.MONITORENTER);
121128
visitor.visitMethodInsn(182, "java/lang/Object", "notify", "()V", false);
122129
visitor.visitInsn(Opcodes.MONITOREXIT);
130+
visitor.visitInsn(Opcodes.RETURN);
131+
visitor.visitLabel(rethrow);
132+
visitor.visitVarInsn(25, error);
133+
visitor.visitInsn(Opcodes.ATHROW);
134+
visitor.visitInsn(Opcodes.RETURN);
135+
123136
});
124-
this.writeCall(child, WaitForEffect.class.getMethod("unlock"), context);
125137
child.writeCode(WriteInstruction.returnEmpty());
126138
final String internal = context.getType().internalName();
127139
method = context.getTriggerMethod();

src/main/java/org/byteskript/skript/runtime/Skript.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,10 @@ public Skript(SkriptThreadProvider threadProvider, ModifiableCompiler compiler,
109109
this.compiler = compiler;
110110
this.factory = threadProvider;
111111
this.factory.setSkriptInstance(this);
112-
this.executor = Executors.newCachedThreadPool(factory);
112+
this.executor = new ScriptThreadPoolExecutor(0, Integer.MAX_VALUE,
113+
60L, TimeUnit.SECONDS,
114+
new SynchronousQueue<>(),
115+
factory);
113116
this.mainThread = main;
114117
this.scheduler = new ScheduledThreadPoolExecutor(4, factory);
115118
this.processes = new ArrayList<>();
@@ -776,6 +779,18 @@ public Script compileLoad(InputStream stream, String name) {
776779
return loadScript(compileScript(stream, name));
777780
}
778781

782+
@Description("""
783+
Loads a script from compiled source code.
784+
""")
785+
@GenerateExample
786+
public Script loadScript(final PostCompileClass[] data) {
787+
final Class<?>[] classes = new Class[data.length];
788+
for (int i = 0; i < data.length; i++) {
789+
classes[i] = this.loadClass(data[i].name(), data[i].code());
790+
}
791+
return this.loadScript(classes);
792+
}
793+
779794
@Description("""
780795
Loads a script from compiled source code.
781796
""")
@@ -784,6 +799,16 @@ public Script loadScript(final PostCompileClass datum) {
784799
return this.loadScript(this.loadClass(datum.name(), datum.code()));
785800
}
786801

802+
@Description("""
803+
Loads a script from defined classes.
804+
""")
805+
@GenerateExample
806+
public Script loadScript(final Class<?>[] loaded) {
807+
final Script script = new Script(this, null, loaded);
808+
this.scripts.add(script);
809+
return script;
810+
}
811+
787812
@Description("""
788813
Loads a script from a defined class.
789814
""")

src/main/java/org/byteskript/skript/runtime/internal/ExtractedSyntaxCalls.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,22 @@ public static void runOnAsyncThread(final Runnable runnable) {
107107
final Thread current = Thread.currentThread();
108108
if (!(current instanceof ScriptThread thread))
109109
throw new ScriptRuntimeError("Cannot create background process from non-script thread.");
110-
thread.skript.runOnAsyncThread(runnable);
110+
thread.skript.runOnAsyncThread((Runnable) () -> {
111+
((ScriptThread) Thread.currentThread()).variables.clear();
112+
runnable.run();
113+
((ScriptThread) Thread.currentThread()).variables.clear();
114+
});
111115
}
112116

113117
public static void runOnAsyncThread(final Instruction<?> runnable) {
114118
final Thread current = Thread.currentThread();
115119
if (!(current instanceof ScriptThread thread))
116120
throw new ScriptRuntimeError("Cannot create background process from non-script thread.");
117-
thread.skript.runOnAsyncThread(runnable);
121+
thread.skript.runOnAsyncThread((Instruction<?>) () -> {
122+
((ScriptThread) Thread.currentThread()).variables.clear();
123+
runnable.run();
124+
((ScriptThread) Thread.currentThread()).variables.clear();
125+
});
118126
}
119127

120128
public static Object getListSize(Object target) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2022 ByteSkript org (Moderocky)
3+
* View the full licence information and permissions:
4+
* https://github.com/Moderocky/ByteSkript/blob/master/LICENSE
5+
*/
6+
7+
package org.byteskript.skript.runtime.threading;
8+
9+
import org.jetbrains.annotations.NotNull;
10+
11+
import java.util.concurrent.BlockingQueue;
12+
import java.util.concurrent.ThreadFactory;
13+
import java.util.concurrent.ThreadPoolExecutor;
14+
import java.util.concurrent.TimeUnit;
15+
16+
public class ScriptThreadPoolExecutor extends ThreadPoolExecutor {
17+
18+
public ScriptThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue<Runnable> workQueue, @NotNull ThreadFactory threadFactory) {
19+
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
20+
}
21+
22+
}

src/test/java/org/byteskript/skript/test/SyntaxTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void all() throws Throwable {
7272
}
7373
try {
7474
final long now, then;
75-
final Script script = skript.loadScripts(classes).iterator().next();
75+
final Script script = skript.loadScript(classes);
7676
now = System.currentTimeMillis();
7777
final boolean result;
7878
final Object object = script.getFunction("test").run(skript).get();

src/test/resources/tests/threadvariable.bsk

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,11 @@ function test:
1010
assert {_blob} is null: "Thread variable unset check failed."
1111
run a new runnable:
1212
assert {_var} is 2: "Thread variable access from runnable failed."
13-
run a new runnable in the background:
13+
wait for a new runnable:
1414
assert {_var} is null: "Thread variable access from wrong thread passed."
15-
wait 1 ms
16-
wake {thread}
17-
sleep
18-
run a new runnable in the background:
15+
wait for a new runnable:
1916
run copy_threadlocals_from({thread})
2017
assert {_var} is 2: "Thread locals copying failed."
21-
wait 1 ms
22-
wake {thread}
23-
sleep
2418
delete {_var}
2519
assert {_var} is null: "Thread variable deletion failed."
2620
return true

src/test/resources/tests/waitfor.bsk

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ function test:
1717
wait for a new runnable:
1818
set {@thing} to true
1919
assert {@thing} is true
20+
set {_var} to false
21+
wait for a new runnable:
22+
set {_var} to true
23+
assert {_var} is false
2024
return true

0 commit comments

Comments
 (0)