From 12e82afad235f507d9dc2d118914c6aa4da72118 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Sun, 10 Feb 2019 21:46:52 +0000 Subject: [PATCH] Bump Cobalt version to enable single-threading The latest version of Cobalt has several major changes, which I'm looking forward to taking advantage of in the coming months: - The Lua interpreter has been split up from the actual LuaClosure instance. It now runs multiple functions within one loop, handling pushing/popping and resuming method calls correctly. This means we have a theoretically infinite call depth, as we're no longer bounded by Java's stack size. In reality, this is limited to 32767 (Short.MAX_VALUE), as that's a mostly equivalent to the limits PUC Lua exposes. - The stack is no longer unwound in the event of errors. This both simplifies error handling (not that CC:T needs to care about that) but also means one can call debug.traceback on a now-dead coroutine (which is more useful for debugging than using xpcall). - Most significantly, coroutines are no longer each run on a dedicated thread. Instead, yielding or resuming throws an exception to unwind the Java stack and switches to a different coroutine. In order to preserve compatability with CC's assumption about LuaJ's threading model (namely that yielding blocks the thread), we also provide a yieldBlock method (which CC:T consumes). This suspends the current thread and switches execution to a new thread (see SquidDev/Cobalt@b5ddf164f1c77bc2657f096f39dd167c19270f6d for more details). While this does mean we need to use more than 1 thread, it's still /substantially/ less than would otherwise be needed. We've been running these changes on SwitchCraft for a few days now and haven't seen any issues. One nice thing to observe is that the number of CC thread has gone down from ~1.9k to ~100 (of those, ~70 are dedicated to running coroutines). Similarly, the server has gone from generating ~15k threads over its lifetime, to ~3k. While this is still a lot, it's a substantial improvement. --- build.gradle | 4 +- .../core/computer/ComputerThread.java | 42 +++++++++---- .../core/lua/CobaltLuaMachine.java | 60 +++++++++---------- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/build.gradle b/build.gradle index ccb94f3b7..5d72f5140 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ } maven { name = "squiddev" - url = "https://dl.bintray.com/squiddev/maven" + url = "https://squiddev.cc/maven" } ivy { artifactPattern "https://asie.pl/files/mods/Charset/LibOnly/[module]-[revision](-[classifier]).[ext]" } @@ -66,7 +66,7 @@ runtime "mezz.jei:jei_1.12.2:4.8.5.159" - shade 'org.squiddev:Cobalt:0.4.0' + shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT' testCompile 'junit:junit:4.11' diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java index 5cb9c6546..80bce20c5 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java @@ -16,6 +16,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.locks.LockSupport; public class ComputerThread { @@ -216,22 +217,37 @@ private void execute( BlockingQueue queue ) throws InterruptedException // Interrupt the thread if( !done ) { - StringBuilder builder = new StringBuilder( "Terminating " ); - if( computer != null ) + if( ComputerCraft.logPeripheralErrors ) { - builder.append( "computer " ).append( computer.getID() ); - } - else - { - builder.append( "unknown computer" ); - } + long time = System.nanoTime() - start; + StringBuilder builder = new StringBuilder( "Terminating " ); + if( computer != null ) + { + builder.append( "computer #" ).append( computer.getID() ); + } + else + { + builder.append( "unknown computer" ); + } - builder.append( ". Thread is currently running" ); - for( StackTraceElement element : thread.getStackTrace() ) - { - builder.append( "\n at " ).append( element ); + { + builder.append( " due to timeout (running for " ) + .append( time / 1e9 ) + .append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " ) + .append( thread.getName() ) + .append( " is currently " ) + .append( thread.getState() ); + Object blocking = LockSupport.getBlocker( thread ); + if( blocking != null ) builder.append( "\n on " ).append( blocking ); + + for( StackTraceElement element : thread.getStackTrace() ) + { + builder.append( "\n at " ).append( element ); + } + } + + ComputerCraft.log.warn( builder.toString() ); } - ComputerCraft.log.error( builder.toString() ); thread.interrupt(); thread = null; diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index e1042b4fb..90b205725 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -46,7 +46,7 @@ public class CobaltLuaMachine implements ILuaMachine { private static final ThreadPoolExecutor coroutines = new ThreadPoolExecutor( 0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, + 5L, TimeUnit.MINUTES, new SynchronousQueue<>(), ThreadUtils.factory( "Coroutine" ) ); @@ -74,12 +74,12 @@ public CobaltLuaMachine( Computer computer ) private boolean hasSoftAbort; @Override - public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError + public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable { int count = ++this.count; if( count > 100000 ) { - if( m_hardAbortMessage != null ) LuaThread.yield( m_state, NONE ); + if( m_hardAbortMessage != null ) throw HardAbortError.INSTANCE; this.count = 0; } else @@ -87,13 +87,13 @@ public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, handleSoftAbort(); } - super.onInstruction( ds, di, pc, extras, top ); + super.onInstruction( ds, di, pc ); } @Override public void poll() throws LuaError { - if( m_hardAbortMessage != null ) LuaThread.yield( m_state, NONE ); + if( m_hardAbortMessage != null ) throw HardAbortError.INSTANCE; handleSoftAbort(); } @@ -117,7 +117,7 @@ private void handleSoftAbort() throws LuaError throw new LuaError( message ); } } ) - .coroutineFactory( command -> { + .yieldThreader( command -> { Tracking.addValue( m_computer, TrackingField.COROUTINES_CREATED, 1 ); coroutines.execute( () -> { try @@ -145,7 +145,7 @@ private void handleSoftAbort() throws LuaError if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() ); // Register custom load/loadstring provider which automatically adds prefixes. - LibFunction.bind( state, m_globals, PrefixLoader.class, new String[] { "load", "loadstring" } ); + LibFunction.bind( m_globals, PrefixLoader.class, new String[] { "load", "loadstring" } ); // Remove globals we don't want to expose m_globals.rawset( "collectgarbage", Constants.NIL ); @@ -222,26 +222,18 @@ public void handleEvent( String eventName, Object[] arguments ) resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) ); } - Varargs results = m_mainRoutine.resume( resumeArgs ); - if( m_hardAbortMessage != null ) - { - throw new LuaError( m_hardAbortMessage ); - } - else if( !results.first().checkBoolean() ) - { - throw new LuaError( results.arg( 2 ).checkString() ); - } - else - { - LuaValue filter = results.arg( 2 ); - m_eventFilter = filter.isString() ? filter.toString() : null; - } + Varargs results = LuaThread.run( m_mainRoutine, resumeArgs ); + if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage ); + + LuaValue filter = results.first(); + m_eventFilter = filter.isString() ? filter.toString() : null; if( m_mainRoutine.getStatus().equals( "dead" ) ) unload(); } - catch( LuaError e ) + catch( LuaError | HardAbortError e ) { unload(); + ComputerCraft.log.warn( "Top level coroutine errored", e ); } finally { @@ -339,16 +331,12 @@ public Object[] yield( Object[] yieldArgs ) throws InterruptedException { try { - Varargs results = LuaThread.yield( state, toValues( yieldArgs ) ); + Varargs results = LuaThread.yieldBlocking( state, toValues( yieldArgs ) ); return toObjects( results, 1 ); } - catch( OrphanedThread e ) + catch( LuaError e ) { - throw new InterruptedException(); - } - catch( Throwable e ) - { - throw new RuntimeException( e ); + throw new IllegalStateException( e ); } } @@ -699,7 +687,7 @@ public int read() throws IOException LuaValue s; try { - s = OperationHelper.call( state, func ); + s = OperationHelper.noYield( state, () -> OperationHelper.call( state, func ) ); } catch( LuaError e ) { @@ -731,4 +719,16 @@ public int read() throws IOException return bytes[offset++]; } } + + private static class HardAbortError extends Error + { + private static final long serialVersionUID = 7954092008586367501L; + + public static final HardAbortError INSTANCE = new HardAbortError(); + + private HardAbortError() + { + super( "Hard Abort", null, true, false ); + } + } }