Fixed threading and add shutdown control for threads via sleep
This commit is contained in:
1 parent
e50298a306
commit
1268af4131
8 files changed
+162
-107
No files matched your search
@@ -337,6 +337,7 @@ public class CarpetScriptHost extends ScriptHost
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
inTermination = true;
|
||||
FunctionValue closing = getFunction("__on_close");
|
||||
if (closing != null)
|
||||
{
|
||||
|
||||
@@ -304,7 +304,6 @@ public class CarpetScriptServer
|
||||
{
|
||||
host.onClose();
|
||||
}
|
||||
ThreadValue.shutdown();
|
||||
}
|
||||
|
||||
public void reload(MinecraftServer server)
|
||||
|
||||
@@ -9,14 +9,20 @@ import carpet.script.value.Value;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class ScriptHost
|
||||
@@ -24,6 +30,10 @@ public abstract class ScriptHost
|
||||
public static Map<Value, Value> systemGlobals = new ConcurrentHashMap<>();
|
||||
private static final Map<Long, Random> randomizers = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
private final Map<Value, ThreadPoolExecutor> executorServices = new HashMap<>();
|
||||
private final Map<Value, Object> locks = new ConcurrentHashMap<>();
|
||||
protected boolean inTermination = false;
|
||||
|
||||
public Random getRandom(long aLong)
|
||||
{
|
||||
if (randomizers.size() > 1024)
|
||||
@@ -31,7 +41,6 @@ public abstract class ScriptHost
|
||||
return randomizers.computeIfAbsent(aLong, Random::new);
|
||||
}
|
||||
|
||||
|
||||
public static class ModuleData
|
||||
{
|
||||
Module parent;
|
||||
@@ -325,7 +334,46 @@ public abstract class ScriptHost
|
||||
|
||||
protected abstract ScriptHost duplicate();
|
||||
|
||||
public void onClose() { }
|
||||
public Object getLock(Value name)
|
||||
{
|
||||
return locks.computeIfAbsent(name, (n) -> new Object());
|
||||
}
|
||||
|
||||
public ThreadPoolExecutor getExecutor(Value pool)
|
||||
{
|
||||
if (inTermination) return null;
|
||||
return executorServices.computeIfAbsent(pool, (v) -> (ThreadPoolExecutor) Executors.newCachedThreadPool());
|
||||
}
|
||||
|
||||
public int taskCount()
|
||||
{
|
||||
return executorServices.values().stream().map(ThreadPoolExecutor::getActiveCount).reduce(0, Integer::sum);
|
||||
}
|
||||
public int taskCount(Value pool)
|
||||
{
|
||||
if (executorServices.containsKey(pool))
|
||||
{
|
||||
return executorServices.get(pool).getActiveCount();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void onClose()
|
||||
{
|
||||
inTermination = true;
|
||||
List<ExecutorService> toStop = new ArrayList<>(executorServices.values());
|
||||
for (ScriptHost uh : userHosts.values()) toStop.addAll(uh.executorServices.values());
|
||||
if (!toStop.isEmpty())
|
||||
{
|
||||
ThreadPoolExecutor shutdownService = (ThreadPoolExecutor) Executors.newCachedThreadPool();
|
||||
List<CompletableFuture<?>> terminators = toStop.stream().map(exec ->
|
||||
CompletableFuture.supplyAsync(() -> { exec.shutdown(); exec.shutdownNow(); return null; }, shutdownService)).collect(Collectors.toList()
|
||||
);
|
||||
terminators.forEach(CompletableFuture::join);
|
||||
shutdownService.shutdown();
|
||||
shutdownService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPerPlayer(boolean isPerUser)
|
||||
{
|
||||
|
||||
@@ -294,17 +294,7 @@ public class Sys {
|
||||
System.out.println(v.getString());
|
||||
return v; // pass through for variables
|
||||
});
|
||||
expression.addUnaryFunction("sleep", (v) ->
|
||||
{
|
||||
long time = NumericValue.asNumber(v).getLong();
|
||||
try
|
||||
{
|
||||
Thread.sleep(time);
|
||||
Thread.yield();
|
||||
}
|
||||
catch (InterruptedException ignored) { }
|
||||
return v; // pass through for variables
|
||||
});
|
||||
|
||||
expression.addLazyFunction("time", 0, (c, t, lv) ->
|
||||
{
|
||||
Value time = new NumericValue((System.nanoTime() / 1000) / 1000.0);
|
||||
|
||||
@@ -2,7 +2,9 @@ package carpet.script.language;
|
||||
|
||||
import carpet.script.Expression;
|
||||
import carpet.script.argument.FunctionArgument;
|
||||
import carpet.script.exception.ExitStatement;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.value.FunctionValue;
|
||||
import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.ThreadValue;
|
||||
import carpet.script.value.Value;
|
||||
@@ -33,13 +35,12 @@ public class Threading
|
||||
});
|
||||
|
||||
|
||||
expression.addFunction("task_count", (lv) ->
|
||||
expression.addLazyFunction("task_count", -1, (c, t, lv) ->
|
||||
{
|
||||
if (lv.size() > 0)
|
||||
{
|
||||
return new NumericValue(ThreadValue.taskCount(lv.get(0)));
|
||||
}
|
||||
return new NumericValue(ThreadValue.taskCount());
|
||||
Value ret = (lv.size() > 0)?
|
||||
new NumericValue(c.host.taskCount(lv.get(0).evalValue(c))):
|
||||
new NumericValue(c.host.taskCount());
|
||||
return (cc, tt) -> ret;
|
||||
});
|
||||
|
||||
expression.addUnaryFunction("task_value", (v) ->
|
||||
@@ -80,11 +81,39 @@ public class Threading
|
||||
lockValue = lv.get(0).evalValue(c);
|
||||
ind = 1;
|
||||
}
|
||||
synchronized (ThreadValue.getLock(lockValue))
|
||||
synchronized (c.host.getLock(lockValue))
|
||||
{
|
||||
Value ret = lv.get(ind).evalValue(c, t);
|
||||
return (_c, _t) -> ret;
|
||||
}
|
||||
});
|
||||
|
||||
expression.addLazyFunctionWithDelegation("sleep", -1, (c, t, expr, tok, lv) ->
|
||||
{
|
||||
long time = lv.isEmpty()?0L:NumericValue.asNumber(lv.get(0).evalValue(c)).getLong();
|
||||
boolean interrupted = false;
|
||||
try
|
||||
{
|
||||
if (Thread.interrupted()) interrupted = true;
|
||||
if (time > 0) Thread.sleep(time);
|
||||
Thread.yield();
|
||||
}
|
||||
catch (InterruptedException ignored)
|
||||
{
|
||||
interrupted = true;
|
||||
}
|
||||
if (interrupted)
|
||||
{
|
||||
Value exceptionally = Value.NULL;
|
||||
if (lv.size() > 1)
|
||||
{
|
||||
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 1, false);
|
||||
FunctionValue fun = functionArgument.function;
|
||||
exceptionally = fun.callInContext(expr, c, t, fun.getExpression(), fun.getToken(), functionArgument.args).evalValue(c);
|
||||
}
|
||||
throw new ExitStatement(exceptionally);
|
||||
}
|
||||
return (cc, tt) -> new NumericValue(time); // pass through for variables
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,47 +4,55 @@ import carpet.script.Context;
|
||||
import carpet.script.Expression;
|
||||
import carpet.script.LazyValue;
|
||||
import carpet.script.Tokenizer;
|
||||
import carpet.script.exception.ExitStatement;
|
||||
import carpet.script.exception.ExpressionException;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import net.minecraft.nbt.Tag;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ThreadValue extends Value
|
||||
{
|
||||
private CompletableFuture<Value> taskFuture;
|
||||
private long id;
|
||||
private final CompletableFuture<Value> taskFuture;
|
||||
private final long id;
|
||||
private static long sequence = 0L;
|
||||
private static final Map<Value,ThreadPoolExecutor> executorServices = new HashMap<>();
|
||||
private static final Map<Value, Object> locks = new ConcurrentHashMap<>();
|
||||
|
||||
public ThreadValue(Value pool, FunctionValue function, Expression expr, Tokenizer.Token token, Context ctx, List<LazyValue> args)
|
||||
{
|
||||
id = sequence++;
|
||||
taskFuture = CompletableFuture.supplyAsync(
|
||||
() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return function.lazyEval(ctx, Context.NONE, expr, token, args).evalValue(ctx);
|
||||
}
|
||||
catch (ExpressionException exc)
|
||||
{
|
||||
ctx.host.handleExpressionException("Thread failed", exc);
|
||||
return Value.NULL;
|
||||
}
|
||||
},
|
||||
executorServices.computeIfAbsent(pool, (v) -> (ThreadPoolExecutor)Executors.newCachedThreadPool())
|
||||
);
|
||||
ExecutorService executor = ctx.host.getExecutor(pool);
|
||||
if (executor == null)
|
||||
{
|
||||
// app is shutting down - no more threads can be spawned.
|
||||
taskFuture = CompletableFuture.completedFuture(Value.NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
taskFuture = CompletableFuture.supplyAsync(
|
||||
() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return function.lazyEval(ctx, Context.NONE, expr, token, args).evalValue(ctx);
|
||||
}
|
||||
catch (ExitStatement exit)
|
||||
{
|
||||
// app stopped
|
||||
return exit.retval;
|
||||
}
|
||||
catch (ExpressionException exc)
|
||||
{
|
||||
ctx.host.handleExpressionException("Thread failed", exc);
|
||||
return Value.NULL;
|
||||
}
|
||||
|
||||
},
|
||||
ctx.host.getExecutor(pool)
|
||||
);
|
||||
}
|
||||
Thread.yield();
|
||||
}
|
||||
|
||||
@@ -71,6 +79,11 @@ public class ThreadValue extends Value
|
||||
{
|
||||
return taskFuture.get();
|
||||
}
|
||||
catch (ExitStatement exit)
|
||||
{
|
||||
taskFuture.complete(exit.retval);
|
||||
return exit.retval;
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e)
|
||||
{
|
||||
return Value.NULL;
|
||||
@@ -104,47 +117,6 @@ public class ThreadValue extends Value
|
||||
return Long.hashCode(id);
|
||||
}
|
||||
|
||||
public static int taskCount()
|
||||
{
|
||||
return executorServices.values().stream().map(ThreadPoolExecutor::getActiveCount).reduce(0, Integer::sum);
|
||||
}
|
||||
|
||||
public static int taskCount(Value pool)
|
||||
{
|
||||
if (executorServices.containsKey(pool))
|
||||
{
|
||||
return executorServices.get(pool).getActiveCount();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Object getLock(Value name)
|
||||
{
|
||||
return locks.computeIfAbsent(name, (n) -> new Object());
|
||||
}
|
||||
|
||||
|
||||
public static void shutdown()
|
||||
{
|
||||
for (ExecutorService exec : executorServices.values())
|
||||
{
|
||||
exec.shutdown();
|
||||
try
|
||||
{
|
||||
exec.awaitTermination(5000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (InterruptedException ignored)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
exec.shutdownNow();
|
||||
}
|
||||
}
|
||||
executorServices.clear();
|
||||
locks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag toTag(boolean force)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user