Fixed threading and add shutdown control for threads via sleep

This commit is contained in:
gnembon committed 2020-10-24 22:18:02 -03:00
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)
+50 -2
View File
@@ -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)
{
+1 -11
View File
@@ -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)
{