/******************************************************************************* * Copyright (c) 2007-2012 LuaJ. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ package org.luaj.vm2; import java.lang.ref.WeakReference; /** * Subclass of {@link LuaValue} that implements * a lua coroutine thread using Java Threads. * <p> * A LuaThread is typically created in response to a scripted call to * {@code coroutine.create()} * <p> * The threads must be initialized with the globals, so that * the global environment may be passed along according to rules of lua. * This is done via a call to {@link #setGlobals(LuaValue)} * at some point during globals initialization. * See {@link BaseLib} for additional documentation and example code. * <p> * The utility classes {@link JsePlatform} and {@link JmePlatform} * see to it that this initialization is done properly. * For this reason it is highly recommended to use one of these classes * when initializing globals. * <p> * The behavior of coroutine threads matches closely the behavior * of C coroutine library. However, because of the use of Java threads * to manage call state, it is possible to yield from anywhere in luaj. * <p> * Each Java thread wakes up at regular intervals and checks a weak reference * to determine if it can ever be resumed. If not, it throws * {@link OrphanedThread} which is an {@link java.lang.Error}. * Applications should not catch {@link OrphanedThread}, because it can break * the thread safety of luaj. * * @see LuaValue * @see JsePlatform * @see JmePlatform * @see CoroutineLib */ public class LuaThread extends LuaValue { public static LuaValue s_metatable; public static int coroutine_count = 0; /** Interval at which to check for lua threads that are no longer referenced. * This can be changed by Java startup code if desired. */ static long thread_orphan_check_interval = 30000; public static final int STATUS_INITIAL = 0; public static final int STATUS_SUSPENDED = 1; public static final int STATUS_RUNNING = 2; public static final int STATUS_NORMAL = 3; public static final int STATUS_DEAD = 4; public static final String[] STATUS_NAMES = { "suspended", "suspended", "running", "normal", "dead",}; public final State state; public static final int MAX_CALLSTACK = 256; /** Interval to check for LuaThread dereferencing. */ public static int GC_INTERVAL = 30000; /** Thread-local used by DebugLib to store debugging state. * This is ano opaque value that should not be modified by applications. */ public Object callstack; public final Globals globals; /** Hook function control state used by debug lib. */ public LuaValue hookfunc; /** Error message handler for this thread, if any. */ public LuaValue errorfunc; public boolean hookline; public boolean hookcall; public boolean hookrtrn; public int hookcount; public boolean inhook; public int lastline; public int bytecodes; /** Private constructor for main thread only */ public LuaThread(Globals globals) { state = new State(globals, this, null); state.status = STATUS_RUNNING; this.globals = globals; } /** * Create a LuaThread around a function and environment * @param func The function to execute */ public LuaThread(Globals globals, LuaValue func) { LuaValue.assert_(func != null, "function cannot be null"); state = new State(globals, this, func); this.globals = globals; } public int type() { return LuaValue.TTHREAD; } public String typename() { return "thread"; } public boolean isthread() { return true; } public LuaThread optthread(LuaThread defval) { return this; } public LuaThread checkthread() { return this; } public LuaValue getmetatable() { return s_metatable; } public String getStatus() { return STATUS_NAMES[state.status]; } public boolean isMainThread() { return this.state.function == null; } public Varargs resume(Varargs args) { final LuaThread.State s = this.state; if (s.status > LuaThread.STATUS_SUSPENDED) return LuaValue.varargsOf(LuaValue.FALSE, LuaValue.valueOf("cannot resume "+(s.status==LuaThread.STATUS_DEAD? "dead": "non-suspended")+" coroutine")); return s.lua_resume(this, args); } public static class State implements Runnable { private final Globals globals; final WeakReference lua_thread; public final LuaValue function; Varargs args = LuaValue.NONE; Varargs result = LuaValue.NONE; String error = null; public int status = LuaThread.STATUS_INITIAL; State(Globals globals, LuaThread lua_thread, LuaValue function) { this.globals = globals; this.lua_thread = new WeakReference(lua_thread); this.function = function; } public synchronized void run() { try { Varargs a = this.args; this.args = LuaValue.NONE; this.result = function.invoke(a); } catch (Throwable t) { this.error = t.getMessage(); } finally { this.status = LuaThread.STATUS_DEAD; this.notify(); } } public synchronized Varargs lua_resume(LuaThread new_thread, Varargs args) { LuaThread previous_thread = globals.running; try { globals.running = new_thread; this.args = args; if (this.status == STATUS_INITIAL) { this.status = STATUS_RUNNING; new Thread(this, "Coroutine-"+(++coroutine_count)).start(); } else { this.notify(); } if (previous_thread != null) previous_thread.state.status = STATUS_NORMAL; this.status = STATUS_RUNNING; this.wait(); return (this.error != null? LuaValue.varargsOf(LuaValue.FALSE, LuaValue.valueOf(this.error)): LuaValue.varargsOf(LuaValue.TRUE, this.result)); } catch (InterruptedException ie) { throw new OrphanedThread(); } finally { this.args = LuaValue.NONE; this.result = LuaValue.NONE; this.error = null; globals.running = previous_thread; if (previous_thread != null) globals.running.state.status =STATUS_RUNNING; } } public synchronized Varargs lua_yield(Varargs args) { try { this.result = args; this.status = STATUS_SUSPENDED; this.notify(); do { this.wait(thread_orphan_check_interval); if (this.lua_thread.get() == null) { this.status = STATUS_DEAD; throw new OrphanedThread(); } } while (this.status == STATUS_SUSPENDED); return this.args; } catch (InterruptedException ie) { this.status = STATUS_DEAD; throw new OrphanedThread(); } finally { this.args = LuaValue.NONE; this.result = LuaValue.NONE; } } } }