changeset 1578:c922446f53aa

immutable threading
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 08 Feb 2021 14:16:19 -0700
parents 60e5c324adf9
children dd881eb03d87
files conv.txt src/luan/Luan.java src/luan/LuanCloneable.java src/luan/LuanCloner.java src/luan/LuanClosure.java src/luan/LuanException.java src/luan/LuanImmutabler.java src/luan/LuanMutable.java src/luan/LuanTable.java src/luan/host/WebHandler.java src/luan/impl/LuanImpl.java src/luan/impl/Pointer.java src/luan/modules/BasicLuan.java src/luan/modules/Boot.luan src/luan/modules/Io.luan src/luan/modules/IoLuan.java src/luan/modules/JavaLuan.java src/luan/modules/Luan.luan src/luan/modules/Package.luan src/luan/modules/PackageLuan.java src/luan/modules/Rpc.luan src/luan/modules/TableLuan.java src/luan/modules/Thread.luan src/luan/modules/ThreadLuan.java src/luan/modules/http/Http.luan src/luan/modules/http/LuanDomainHandler.java src/luan/modules/http/LuanHandler.java src/luan/modules/http/tools/Shell.luan src/luan/modules/logging/LuanLogger.java src/luan/modules/lucene/Lucene.luan src/luan/modules/lucene/SupplementingConfig.java src/luan/modules/parsers/LuanParser.java
diffstat 32 files changed, 486 insertions(+), 608 deletions(-) [+]
line wrap: on
line diff
--- a/conv.txt	Sun Jan 31 16:04:39 2021 -0700
+++ b/conv.txt	Mon Feb 08 14:16:19 2021 -0700
@@ -1,3 +1,5 @@
+Thread.global_callable
+
 Luan.try
 Luan.pcall
 
--- a/src/luan/Luan.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/Luan.java	Mon Feb 08 14:16:19 2021 -0700
@@ -10,6 +10,7 @@
 import java.util.Iterator;
 import java.util.Arrays;
 import java.util.Set;
+import java.util.Collection;
 import goodjava.logging.Logger;
 import goodjava.logging.LoggerFactory;
 import luan.modules.JavaLuan;
@@ -19,31 +20,117 @@
 import luan.impl.LuanCompiler;
 
 
-public final class Luan implements LuanCloneable {
+public final class Luan {
 	private static final Logger logger = LoggerFactory.getLogger(Luan.class);
 
 	private final List<LuanClosure> stack = new ArrayList<LuanClosure>();
-	private Map registry;
-	private boolean isLocked = false;
+	private final Map registry;
+	private final Map localOnly = new HashMap();
 
 	public Luan() {
 		registry = new HashMap();
 	}
 
-	private Luan(Luan luan) {}
+	public Luan(Luan luan) {
+		LuanMutable.makeImmutable(luan.registry);
+		this.registry = clone(luan.registry);
+	}
+
+	private static Object[] clone(Object[] obj) {
+		if( obj.length == 0 )
+			return obj;
+		Object[] rtn = obj.clone();
+		for( int i=0; i<rtn.length; i++ ) {
+			rtn[i] = clone(rtn[i]);
+		}
+		return rtn;
+	}
 
-	@Override public Luan shallowClone() {
-		return new Luan(this);
+	private static Map clone(Map obj) {
+		Map rtn;
+		try {
+			rtn = obj.getClass().newInstance();
+		} catch(InstantiationException e) {
+			throw new RuntimeException(e);
+		} catch(IllegalAccessException e) {
+			throw new RuntimeException(e);
+		}
+		for( Object stupid : obj.entrySet() ) {
+			Map.Entry entry = (Map.Entry)stupid;
+			rtn.put( clone(entry.getKey()), clone(entry.getValue()) );
+		}
+		return rtn;
+	}
+
+	private static Collection clone(Collection obj) {
+		Collection rtn;
+		try {
+			rtn = obj.getClass().newInstance();
+		} catch(InstantiationException e) {
+			throw new RuntimeException(e);
+		} catch(IllegalAccessException e) {
+			throw new RuntimeException(e);
+		}
+		for( Object entry : (Collection)obj ) {
+			rtn.add( clone(entry) );
+		}
+		return rtn;
 	}
 
-	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
-		Luan clone = (Luan)dc;
-		clone.registry = cloner.clone(registry);
-		if( cloner.type == LuanCloner.Type.INCREMENTAL )
-			isLocked = true;
+	private static Object clone(Object obj) {
+		if( obj instanceof Object[] )
+			return clone((Object[])obj);
+		if( obj instanceof Map )
+			return clone((Map)obj);
+		if( obj instanceof Collection )
+			return clone((Collection)obj);
+		return obj;
+	}
+
+	private Map clonedLocals(Object obj) {
+		Map locals = (Map)registry.get("Luan.local");
+		if( locals==null ) {
+			locals = new HashMap();
+			registry.put("Luan.local",locals);
+		}
+		Map local = (Map)locals.get(obj);
+		if( local==null ) {
+			local = new HashMap();
+			locals.put(obj,local);
+		}
+		return local;
 	}
 
-	@Override public void makeImmutable(LuanImmutabler immutabler) {}
+	public Object getLocalCloned(Object obj,Object key) {
+		return clonedLocals(obj).get(key);
+	}
+
+	public void setLocalCloned(Object obj,Object key,Object value) {
+		if( value==null )
+			clonedLocals(obj).remove(key);
+		else
+			clonedLocals(obj).put(key,value);
+	}
+
+	private Map onlyLocals(Object obj) {
+		Map local = (Map)localOnly.get(obj);
+		if( local==null ) {
+			local = new HashMap();
+			localOnly.put(obj,local);
+		}
+		return local;
+	}
+
+	public Object getLocalOnly(Object obj,Object key) {
+		return onlyLocals(obj).get(key);
+	}
+
+	public void setLocalOnly(Object obj,Object key,Object value) {
+		if( value==null )
+			onlyLocals(obj).remove(key);
+		else
+			onlyLocals(obj).put(key,value);
+	}
 
 	public LuanClosure peek() {
 		return peek(1);
@@ -55,8 +142,6 @@
 	}
 
 	void push(LuanClosure closure) {
-		if( isLocked )
-			throw new RuntimeException(this+" is locked "+closure);
 		stack.add(closure);
 	}
 
@@ -118,7 +203,7 @@
 		throw new LuanException( "attempt to compare " + Luan.type(o1) + " with " + Luan.type(o2) );
 	}
 
-	public static LuanFunction getBinHandler(String op,Object o1,Object o2) throws LuanException {
+	public LuanFunction getBinHandler(String op,Object o1,Object o2) throws LuanException {
 		if( o1 instanceof LuanTable ) {
 			LuanFunction f1 = getHandlerFunction(op,(LuanTable)o1);
 			if( f1 != null )
@@ -127,8 +212,8 @@
 		return o2 instanceof LuanTable ? getHandlerFunction(op,(LuanTable)o2) : null;
 	}
 
-	public static LuanFunction getHandlerFunction(String op,LuanTable t) throws LuanException {
-		Object f = t.getHandler(op);
+	public LuanFunction getHandlerFunction(String op,LuanTable t) throws LuanException {
+		Object f = t.getHandler(this,op);
 		if( f == null )
 			return null;
 		return Luan.checkFunction(f);
--- a/src/luan/LuanCloneable.java	Sun Jan 31 16:04:39 2021 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-package luan;
-
-
-public interface LuanCloneable {
-	public LuanCloneable shallowClone();
-	public void deepenClone(LuanCloneable clone,LuanCloner cloner);
-	public void makeImmutable(LuanImmutabler immutabler) throws LuanException;
-}
--- a/src/luan/LuanCloner.java	Sun Jan 31 16:04:39 2021 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-package luan;
-
-import java.util.Map;
-import java.util.Collection;
-import java.util.IdentityHashMap;
-
-
-public final class LuanCloner {
-	public enum Type { COMPLETE, INCREMENTAL }
-
-	public final Type type;
-	private final Map cloned = new IdentityHashMap();
-	private Luan luan = null;
-
-	public LuanCloner(Type type) {
-		this.type = type;
-	}
-
-	public LuanCloneable clone(LuanCloneable obj) {
-		if( obj==null )
-			return null;
-		LuanCloneable rtn = (LuanCloneable)cloned.get(obj);
-		if( rtn == null ) {
-			if( obj instanceof Luan ) {
-				if( luan != null )
-					throw new RuntimeException("2 luans in "+type+" "+this+" - "+luan+" "+obj);
-				luan = (Luan)obj;
-			}
-			rtn = obj.shallowClone();
-			cloned.put(obj,rtn);
-			obj.deepenClone(rtn,this);
-		}
-		return rtn;
-	}
-
-	public Object[] clone(Object[] obj) {
-		if( obj==null || obj.length == 0 )
-			return obj;
-		Object[] rtn = (Object[])cloned.get(obj);
-		if( rtn == null ) {
-			rtn = obj.clone();
-			cloned.put(obj,rtn);
-			for( int i=0; i<rtn.length; i++ ) {
-				rtn[i] = get(rtn[i]);
-			}
-		}
-		return rtn;
-	}
-
-	public Map clone(Map obj) {
-		if( obj==null )
-			return null;
-		Map rtn = (Map)cloned.get(obj);
-		if( rtn == null ) {
-			try {
-				rtn = obj.getClass().newInstance();
-			} catch(InstantiationException e) {
-				throw new RuntimeException(e);
-			} catch(IllegalAccessException e) {
-				throw new RuntimeException(e);
-			}
-			cloned.put(obj,rtn);
-			for( Object stupid : obj.entrySet() ) {
-				Map.Entry entry = (Map.Entry)stupid;
-				rtn.put( get(entry.getKey()), get(entry.getValue()) );
-			}
-		}
-		return rtn;
-	}
-
-	public Collection clone(Collection obj) {
-		if( obj==null )
-			return null;
-		Collection rtn = (Collection)cloned.get(obj);
-		if( rtn == null ) {
-			try {
-				rtn = obj.getClass().newInstance();
-			} catch(InstantiationException e) {
-				throw new RuntimeException(e);
-			} catch(IllegalAccessException e) {
-				throw new RuntimeException(e);
-			}
-			for( Object entry : (Collection)obj ) {
-				rtn.add( get(entry) );
-			}
-		}
-		return rtn;
-	}
-
-	public Object get(Object obj) {
-		if( obj instanceof LuanCloneable )
-			return clone((LuanCloneable)obj);
-		if( obj instanceof Object[] )
-			return clone((Object[])obj);
-		if( obj instanceof Map )
-			return clone((Map)obj);
-		if( obj instanceof Collection )
-			return clone((Collection)obj);
-		return obj;
-	}
-}
--- a/src/luan/LuanClosure.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/LuanClosure.java	Mon Feb 08 14:16:19 2021 -0700
@@ -3,12 +3,11 @@
 import luan.impl.Pointer;
 
 
-public abstract class LuanClosure extends LuanFunction implements LuanCloneable, Cloneable {
+public abstract class LuanClosure extends LuanFunction implements LuanMutable {
 	public Pointer[] upValues;
 	public boolean javaOk;
 	public final String sourceName;
-	private LuanCloner cloner;
-	private Luan luan;
+	private boolean immutable = false;
 
 	public LuanClosure(Pointer[] upValues,boolean javaOk,String sourceName) throws LuanException {
 		this.upValues = upValues;
@@ -16,55 +15,18 @@
 		this.sourceName = sourceName;
 	}
 
-	@Override public final LuanClosure shallowClone() {
-		check();
-		try {
-			return (LuanClosure)clone();
-		} catch(CloneNotSupportedException e) {
-			throw new RuntimeException(e);
-		}
-	}
-
-	private void check() {
-		if( cloner != null ) {
-			completeClone(this,cloner);
-			cloner = null;
-		}
+	@Override public final boolean isImmutable() {
+		return immutable;
 	}
 
-	private void checkLuan(Luan luan) {
-		check();
-		if( this.luan==null ) {
-			this.luan = luan;
-		} else if( this.luan != luan ) {
-			throw new RuntimeException("wrong luan");
-		}
-	}
-
-	@Override public final void deepenClone(LuanCloneable dc,LuanCloner cloner) {
-		LuanClosure clone = (LuanClosure)dc;
-		switch( cloner.type ) {
-		case COMPLETE:
-			completeClone(clone,cloner);
+	@Override public final void makeImmutable() {
+		if(immutable)
 			return;
-		case INCREMENTAL:
-			clone.cloner = cloner;
-			return;
-		}
-	}
-
-	private void completeClone(LuanClosure dc,LuanCloner cloner) {
-		LuanClosure clone = (LuanClosure)dc;
-		clone.upValues = (Pointer[])cloner.clone(upValues);
-		clone.luan = (Luan)cloner.clone(luan);
-	}
-
-	@Override public final void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-		immutabler.makeImmutable(upValues);
+		immutable = true;
+		LuanMutable.makeImmutable(upValues);
 	}
 
 	@Override public final Object call(Luan luan,Object... args) throws LuanException {
-		check();
 		luan.push(this);
 		try {
 			return doCall(luan,args);
@@ -75,7 +37,7 @@
 		}	
 	}
 
-	@Override public String toString() {
+	@Override public final String toString() {
 		return super.toString()+"="+sourceName;
 	}
 
--- a/src/luan/LuanException.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/LuanException.java	Mon Feb 08 14:16:19 2021 -0700
@@ -9,9 +9,10 @@
 import java.util.HashMap;
 
 
-public final class LuanException extends Exception implements LuanCloneable {
+public final class LuanException extends Exception implements LuanMutable {
 	private LuanTable table;
 	private Map extra = new HashMap();
+	private boolean immutable = false;
 
 	public LuanException(String msg,Throwable cause) {
 		super(msg,cause);
@@ -25,19 +26,16 @@
 		super(cause);
 	}
 
-	@Override public LuanException shallowClone() {
-		return new LuanException(getMessage(),getCause());
+	@Override public boolean isImmutable() {
+		return immutable;
 	}
 
-	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
-		LuanException clone = (LuanException)dc;
-		clone.table = (LuanTable)cloner.clone(table);
-		clone.extra = (Map)cloner.clone(extra);
-	}
-
-	@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-		immutabler.makeImmutable(table);
-		immutabler.makeImmutable(extra);
+	@Override public void makeImmutable() {
+		if(immutable)
+			return;
+		immutable = true;
+		LuanMutable.makeImmutable(table);
+		LuanMutable.makeImmutable(extra);
 	}
 
 	public void put(String key,Object value) throws LuanException {
--- a/src/luan/LuanImmutabler.java	Sun Jan 31 16:04:39 2021 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-package luan;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.IdentityHashMap;
-
-
-public final class LuanImmutabler {
-	private final Set handled = Collections.newSetFromMap(new IdentityHashMap());
-	private Luan luan = null;
-
-	public void makeImmutable(LuanCloneable obj) throws LuanException {
-		if( obj==null || !handled.add(obj) )
-			return;
-		if( obj instanceof Luan ) {
-			if( luan != null )
-				throw new RuntimeException("2 luans in "+this+" - "+luan+" "+obj);
-			luan = (Luan)obj;
-		}
-		obj.makeImmutable(this);
-	}
-
-	public void makeImmutable(Object[] obj) throws LuanException {
-		if( obj==null || !handled.add(obj) )
-			return;
-		for( Object entry : obj ) {
-			makeImmutable(entry);
-		}
-	}
-
-	public void makeImmutable(Map obj) throws LuanException {
-		if( obj==null || !handled.add(obj) )
-			return;
-		for( Object stupid : obj.entrySet() ) {
-			Map.Entry entry = (Map.Entry)stupid;
-			makeImmutable(entry.getKey());
-			makeImmutable(entry.getValue());
-		}
-	}
-
-	public void makeImmutable(Collection obj) throws LuanException {
-		if( obj==null || !handled.add(obj) )
-			return;
-		for( Object entry : (Collection)obj ) {
-			makeImmutable(entry);
-		}
-	}
-
-	public void makeImmutable(Object obj) throws LuanException {
-		if( obj instanceof LuanCloneable )
-			makeImmutable((LuanCloneable)obj);
-		else if( obj instanceof Object[] )
-			makeImmutable((Object[])obj);
-		else if( obj instanceof Map )
-			makeImmutable((Map)obj);
-		else if( obj instanceof Collection )
-			makeImmutable((Collection)obj);
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/LuanMutable.java	Mon Feb 08 14:16:19 2021 -0700
@@ -0,0 +1,53 @@
+package luan;
+
+import java.util.Map;
+import java.util.Collection;
+
+
+public interface LuanMutable {
+	public boolean isImmutable();
+	public void makeImmutable();
+
+	public static void makeImmutable(LuanMutable obj) {
+		if( obj==null )
+			return;
+		obj.makeImmutable();
+	}
+
+	public static void makeImmutable(Object[] a) {
+		if( a==null )
+			return;
+		for( Object obj : a ) {
+			makeImmutable(obj);
+		}
+	}
+
+	public static void makeImmutable(Map map) {
+		if( map==null )
+			return;
+		for( Object stupid : map.entrySet() ) {
+			Map.Entry entry = (Map.Entry)stupid;
+			makeImmutable(entry.getKey());
+			makeImmutable(entry.getValue());
+		}
+	}
+
+	public static void makeImmutable(Collection col) {
+		if( col==null )
+			return;
+		for( Object obj : col ) {
+			makeImmutable(obj);
+		}
+	}
+
+	public static void makeImmutable(Object obj) {
+		if( obj instanceof LuanMutable )
+			makeImmutable((LuanMutable)obj);
+		else if( obj instanceof Object[] )
+			makeImmutable((Object[])obj);
+		else if( obj instanceof Map )
+			makeImmutable((Map)obj);
+		else if( obj instanceof Collection )
+			makeImmutable((Collection)obj);
+	}
+}
--- a/src/luan/LuanTable.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/LuanTable.java	Mon Feb 08 14:16:19 2021 -0700
@@ -13,14 +13,12 @@
 import java.util.HashSet;
 
 
-public final class LuanTable implements LuanCloneable {
+public final class LuanTable implements LuanMutable {
 	private Map map = null;
 	private List list = null;
 	private LuanTable metatable = null;
 	public LuanClosure closure;
-	private LuanCloner cloner;
 	private boolean immutable = false;
-	private Luan luan;
 
 	public LuanTable() {}
 
@@ -71,59 +69,23 @@
 		this.metatable = tbl.metatable;
 	}
 
-	@Override public LuanTable shallowClone() {
-		if(immutable) throw new RuntimeException();
-		return new LuanTable();
-	}
-
-	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
-		check();
-		LuanTable clone = (LuanTable)dc;
-		switch( cloner.type ) {
-		case COMPLETE:
-			completeClone(clone,cloner);
-			return;
-		case INCREMENTAL:
-			clone.cloner = cloner;
-			clone.map = map;
-			clone.list = list;
-			clone.metatable = metatable;
-			clone.closure = closure;
-			return;
-		}
+	@Override public boolean isImmutable() {
+		return immutable;
 	}
 
-	private void check() {
-		if( cloner != null ) {
-			completeClone(this,cloner);
-			cloner = null;
-		}
-	}
-
-	private void checkLuan(Luan luan) {
-		check();
-		if( this.luan==null ) {
-			this.luan = luan;
-		} else if( this.luan != luan ) {
-			throw new RuntimeException("wrong luan");
-		}
+	@Override public void makeImmutable() {
+		if(immutable)
+			return;
+		immutable = true;
+		LuanMutable.makeImmutable(map);
+		LuanMutable.makeImmutable(list);
+		LuanMutable.makeImmutable(metatable);
+		LuanMutable.makeImmutable(closure);
 	}
 
-	private void completeClone(LuanTable clone,LuanCloner cloner) {
-		clone.map = cloner.clone(map);
-		clone.list = (List)cloner.clone(list);
-		clone.metatable = (LuanTable)cloner.clone(metatable);
-		clone.closure = (LuanClosure)cloner.clone(closure);
-		clone.luan = (Luan)cloner.clone(luan);
-	}
-
-	@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-		check();
-		immutabler.makeImmutable(map);
-		immutabler.makeImmutable(list);
-		immutabler.makeImmutable(metatable);
-		immutabler.makeImmutable(closure);
-		immutable = true;
+	private void checkMutable() throws LuanException {
+		if( immutable )
+			throw new LuanException("table is immutable");
 	}
 
 	public boolean isList() {
@@ -135,17 +97,15 @@
 	}
 
 	public List<Object> asList() {
-		check();
 		return list!=null ? list : Collections.emptyList();
 	}
 
 	public Map rawMap() {
-		check();
 		return map!=null ? map : Collections.emptyMap();
 	}
 
 	public String toStringLuan(Luan luan) throws LuanException {
-		Object h = getHandler("__to_string");
+		Object h = getHandler(luan,"__to_string");
 		if( h == null )
 			return rawToString();
 		LuanFunction fn = Luan.checkFunction(h);
@@ -161,7 +121,7 @@
 		Object value = rawGet(key);
 		if( value != null )
 			return value;
-		Object h = getHandler("__index");
+		Object h = getHandler(luan,"__index");
 		if( h==null )
 			return null;
 		if( h instanceof LuanFunction ) {
@@ -172,7 +132,6 @@
 	}
 
 	public Object rawGet(Object key) {
-		check();
 		if( list != null ) {
 			Integer iT = Luan.asInteger(key);
 			if( iT != null ) {
@@ -192,7 +151,7 @@
 
 	public void put(Luan luan,Object key,Object value) throws LuanException {
 		//checkLuan(luan);
-		Object h = getHandler("__new_index");
+		Object h = getHandler(luan,"__new_index");
 		if( h==null || rawGet(key)!=null ) {
 			rawPut(key,value);
 			return;
@@ -211,9 +170,7 @@
 	}
 
 	public Object rawPut(Object key,Object val) throws LuanException {
-		if( immutable )
-			throw new LuanException("table is immutable");
-		check();
+		checkMutable();
 		if( key==null )
 			throw new LuanException("table index is nil");
 		Integer iT = Luan.asInteger(key);
@@ -284,34 +241,34 @@
 		return list;
 	}
 
-	public void rawInsert(int pos,Object value) {
-		check();
+	public void rawInsert(int pos,Object value) throws LuanException {
+		checkMutable();
 		if( value==null )
 			throw new IllegalArgumentException("can't insert a nil value");
 		list().add(pos-1,value);
 		mapToList();
 	}
 
-	public void rawAdd(Object value) {
-		check();
+	public void rawAdd(Object value) throws LuanException {
+		checkMutable();
 		if( value==null )
 			throw new IllegalArgumentException("can't insert a nil value");
 		list().add(value);
 		mapToList();
 	}
 
-	public Object removeFromList(int pos) {
-		check();
+	public Object removeFromList(int pos) throws LuanException {
+		checkMutable();
 		return list().remove(pos-1);
 	}
 
-	public void rawSort(Comparator<Object> cmp) {
-		check();
+	public void rawSort(Comparator<Object> cmp) throws LuanException {
+		checkMutable();
 		Collections.sort(list(),cmp);
 	}
 
 	public int length(Luan luan) throws LuanException {
-		Object h = getHandler("__len");
+		Object h = getHandler(luan,"__len");
 		if( h != null ) {
 			LuanFunction fn = Luan.checkFunction(h);
 			return (Integer)Luan.first(fn.call(luan,this));
@@ -320,7 +277,6 @@
 	}
 
 	public int rawLength() {
-		check();
 		return list==null ? 0 : list.size();
 	}
 
@@ -343,7 +299,7 @@
 	}
 
 	public Iterator<Map.Entry> iterator(final Luan luan) throws LuanException {
-		if( getHandler("__pairs") == null )
+		if( getHandler(luan,"__pairs") == null )
 			return rawIterator();
 		final LuanFunction fn = pairs(luan);
 		return new Iterator<Map.Entry>() {
@@ -380,7 +336,7 @@
 	}
 
 	public LuanFunction pairs(Luan luan) throws LuanException {
-		Object h = getHandler("__pairs");
+		Object h = getHandler(luan,"__pairs");
 		if( h != null ) {
 			if( h instanceof LuanFunction ) {
 				LuanFunction fn = (LuanFunction)h;
@@ -408,7 +364,6 @@
 	}
 
 	public Iterator<Map.Entry> rawIterator() {
-		check();
 		if( list == null ) {
 			if( map == null )
 				return Collections.<Map.Entry>emptyList().iterator();
@@ -462,25 +417,22 @@
 	}
 
 	public LuanTable rawSubList(int from,int to) {
-		check();
 		LuanTable tbl = new LuanTable();
 		tbl.list = new ArrayList<Object>(list().subList(from-1,to-1));
 		return tbl;
 	}
 
 	public LuanTable getMetatable() {
-		check();
 		return metatable;
 	}
 
 	public void setMetatable(LuanTable metatable) throws LuanException {
-		check();
+		checkMutable();
 		this.metatable = metatable;
 	}
 
-	public Object getHandler(String op) throws LuanException {
-		check();
-		return metatable==null ? null : metatable.rawGet(op);
+	public Object getHandler(Luan luan,String op) throws LuanException {
+		return metatable==null ? null : luan==null ? metatable.rawGet(op) : metatable.get(luan,op);
 	}
 
 	private static Map<Object,Object> newMap() {
@@ -511,8 +463,8 @@
 		return map;
 	}
 
-	public void rawClear() {
-		check();
+	public void rawClear() throws LuanException {
+		checkMutable();
 		map = null;
 		list = null;
 	}
@@ -540,15 +492,14 @@
 	}
 
 	public Object remove(Object key) throws LuanException {
-		if( immutable )
-			throw new LuanException("table is immutable");
+		checkMutable();
 		Object old = rawGet(key);
 		rawPut(key,null);
 		return old;
 	}
 
 	protected void finalize() throws Throwable {
-		Object h = getHandler("__gc");
+		Object h = getHandler(null,"__gc");
 		if( h != null ) {
 			LuanFunction fn = Luan.checkFunction(h);
 			fn.call(new Luan(),this);  // ??? should be immutable
--- a/src/luan/host/WebHandler.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/host/WebHandler.java	Mon Feb 08 14:16:19 2021 -0700
@@ -59,9 +59,9 @@
 		return domainHandler.handle(request);
 	}
 
-	public static Object callSite(Luan luan,String domain,String fnName,Object... args) throws LuanException {
+	public static Object callSite(String domain,String fnName,Object... args) throws LuanException {
 		LuanHandler luanHandler = (LuanHandler)domainHandler.getHandler(domain);
-		return luanHandler.call_rpc(luan,fnName,args);
+		return luanHandler.call_rpc(fnName,args);
 	}
 
 	private static void initLuan(Luan luan,String dir,String domain) {
--- a/src/luan/impl/LuanImpl.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/impl/LuanImpl.java	Mon Feb 08 14:16:19 2021 -0700
@@ -33,7 +33,7 @@
 		if( o instanceof Number )
 			return -((Number)o).doubleValue();
 		if( o instanceof LuanTable ) {
-			LuanFunction fn = Luan.getHandlerFunction("__unm",(LuanTable)o);
+			LuanFunction fn = luan.getHandlerFunction("__unm",(LuanTable)o);
 			if( fn != null ) {
 				return Luan.first(fn.call(luan,o));
 			}
@@ -42,7 +42,7 @@
 	}
 
 	private static Object arithmetic(Luan luan,String op,Object o1,Object o2) throws LuanException {
-		LuanFunction fn = Luan.getBinHandler(op,o1,o2);
+		LuanFunction fn = luan.getBinHandler(op,o1,o2);
 		if( fn != null )
 			return Luan.first(fn.call(luan,o1,o2));
 		String type = !(o1 instanceof Number) ? Luan.type(o1) : Luan.type(o2);
@@ -89,7 +89,7 @@
 	}
 
 	public static Object concat(Luan luan,Object o1,Object o2) throws LuanException {
-		LuanFunction fn = Luan.getBinHandler("__concat",o1,o2);
+		LuanFunction fn = luan.getBinHandler("__concat",o1,o2);
 		if( fn != null )
 			return Luan.first(fn.call(luan,o1,o2));
 		String s1 = luan.luanToString(o1);
@@ -136,10 +136,10 @@
 			String s2 = (String)o2;
 			return s1.compareTo(s2) <= 0;
 		}
-		LuanFunction fn = Luan.getBinHandler("__le",o1,o2);
+		LuanFunction fn = luan.getBinHandler("__le",o1,o2);
 		if( fn != null )
 			return Luan.checkBoolean( Luan.first(fn.call(luan,o1,o2)) );
-		fn = Luan.getBinHandler("__lt",o1,o2);
+		fn = luan.getBinHandler("__lt",o1,o2);
 		if( fn != null )
 			return !Luan.checkBoolean( Luan.first(fn.call(luan,o2,o1)) );
 		throw new LuanException( "attempt to compare " + Luan.type(o1) + " with " + Luan.type(o2) );
--- a/src/luan/impl/Pointer.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/impl/Pointer.java	Mon Feb 08 14:16:19 2021 -0700
@@ -1,12 +1,10 @@
 package luan.impl;
 
-import luan.LuanCloneable;
-import luan.LuanCloner;
-import luan.LuanImmutabler;
+import luan.LuanMutable;
 import luan.LuanException;
 
 
-public final class Pointer implements LuanCloneable {
+public final class Pointer implements LuanMutable {
 	private Object o;
 	private boolean immutable = false;
 
@@ -16,17 +14,15 @@
 		this.o = o;
 	}
 
-	@Override public Pointer shallowClone() {
-		return new Pointer();
+	@Override public boolean isImmutable() {
+		return immutable;
 	}
 
-	@Override public void deepenClone(LuanCloneable clone,LuanCloner cloner) {
-		((Pointer)clone).o = cloner.get(o);
-	}
-
-	@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-		immutabler.makeImmutable(o);
+	@Override public void makeImmutable() {
+		if(immutable)
+			return;
 		immutable = true;
+		LuanMutable.makeImmutable(o);
 	}
 
 	public Object get() {
--- a/src/luan/modules/BasicLuan.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/BasicLuan.java	Mon Feb 08 14:16:19 2021 -0700
@@ -14,9 +14,7 @@
 import luan.LuanTable;
 import luan.LuanFunction;
 import luan.LuanException;
-import luan.LuanCloner;
-import luan.LuanCloneable;
-import luan.LuanImmutabler;
+import luan.LuanMutable;
 import luan.modules.parsers.LuanToString;
 
 
@@ -51,7 +49,7 @@
 		return t.pairs(luan);
 	}
 
-	private static class Ipairs extends LuanFunction implements LuanCloneable, Cloneable {
+	private static final class Ipairs extends LuanFunction implements LuanMutable {
 		List<Object> list;
 		int i = 0;
 		final int size;
@@ -68,21 +66,12 @@
 			return new Object[]{i,val};
 		}
 
-		@Override public final Ipairs shallowClone() {
-			try {
-				return (Ipairs)clone();
-			} catch(CloneNotSupportedException e) {
-				throw new RuntimeException(e);
-			}
+		@Override public boolean isImmutable() {
+			return false;
 		}
 
-		@Override public final void deepenClone(LuanCloneable dc,LuanCloner cloner) {
-			Ipairs clone = (Ipairs)dc;
-			clone.list = (List)cloner.clone(list);
-		}
-
-		@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-			throw new LuanException("ipairs cannot be made immutable");
+		@Override public void makeImmutable() {
+			throw new RuntimeException("ipairs cannot be made immutable");
 		}
 	}
 
@@ -100,9 +89,9 @@
 		return obj!=null ? obj : metatable;
 	}
 
-	public static void set_metatable(LuanTable table,LuanTable metatable) throws LuanException {
+	public static void set_metatable(Luan luan,LuanTable table,LuanTable metatable) throws LuanException {
 		Utils.checkNotNull(table);
-		if( table.getHandler("__metatable") != null )
+		if( table.getHandler(luan,"__metatable") != null )
 			throw new LuanException("cannot change a protected metatable");
 		table.setMetatable(metatable);
 	}
@@ -175,7 +164,7 @@
 		};
 	}
 
-	private static class Values extends LuanFunction implements LuanCloneable, Cloneable {
+	private static final class Values extends LuanFunction implements LuanMutable {
 		Object[] args;
 		int i = 0;
 
@@ -190,21 +179,12 @@
 			return new Object[]{i,val};
 		}
 
-		@Override public final Values shallowClone() {
-			try {
-				return (Values)clone();
-			} catch(CloneNotSupportedException e) {
-				throw new RuntimeException(e);
-			}
+		@Override public boolean isImmutable() {
+			return false;
 		}
 
-		@Override public final void deepenClone(LuanCloneable dc,LuanCloner cloner) {
-			Values clone = (Values)dc;
-			clone.args = (Object[])cloner.clone(args);
-		}
-
-		@Override public void makeImmutable(LuanImmutabler immutabler) throws LuanException {
-			throw new LuanException("values cannot be made immutable");
+		@Override public void makeImmutable() {
+			throw new RuntimeException("values cannot be made immutable");
 		}
 	}
 
@@ -248,5 +228,25 @@
 		return jts.toString(Luan.toJava(obj));
 	}
 
+	public static Object get_local_cloned(Luan luan,Object obj,Object key) {
+		return luan.getLocalCloned(obj,key);
+	}
+
+	public static void set_local_cloned(Luan luan,Object obj,Object key,Object value) {
+		luan.setLocalCloned(obj,key,value);
+	}
+
+	public static Object get_local_only(Luan luan,Object obj,Object key) {
+		return luan.getLocalOnly(obj,key);
+	}
+
+	public static void set_local_only(Luan luan,Object obj,Object key,Object value) {
+		luan.setLocalOnly(obj,key,value);
+	}
+
+	public static boolean is_immutable(LuanMutable m) {
+		return m.isImmutable();
+	}
+
 	private void BasicLuan() {}  // never
 }
--- a/src/luan/modules/Boot.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/Boot.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -14,6 +14,10 @@
 local LuanUrl = require "java:luan.modules.url.LuanUrl"
 local LuanJava = require "java:luan.Luan"
 local LuanTable = require "java:luan.LuanTable"
+local get_local_cloned = BasicLuan.get_local_cloned
+local set_local_cloned = BasicLuan.set_local_cloned
+local is_immutable = BasicLuan.is_immutable
+local raw_set = BasicLuan.raw_set
 
 
 local Boot = {}
@@ -38,6 +42,19 @@
 Boot.no_security = no_security
 
 
+local local_metatable = {
+	__index = get_local_cloned
+}
+function local_metatable.__new_index(tbl,key,value)
+	if is_immutable(tbl) then
+		set_local_cloned(tbl,key,value)
+	else
+		raw_set(tbl,key,value)
+	end
+end
+Boot.local_metatable = local_metatable
+
+
 local function new_LuanIn(io)
 	local this = {}
 	this.java = io
@@ -87,6 +104,7 @@
 end
 
 local schemes = {}
+set_metatable(schemes,local_metatable)
 --LuanTable.setSecurity(schemes,"schemes")
 
 function schemes.null(path)
--- a/src/luan/modules/Io.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/Io.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -3,9 +3,31 @@
 local System = require "java:java.lang.System"
 local Boot = require "luan:Boot.luan"
 local LuanException = require "java:luan.LuanException"
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local get_local_cloned = Luan.get_local_cloned
+local set_local_cloned = Luan.set_local_cloned
+local raw_set = Luan.raw_set
+local set_metatable = Luan.set_metatable
 
 
 local Io = {}
+local mt = {}
+function mt.__index(tbl,key)
+	if key=="stdin" or key=="stdout" or key=="stderr" then
+		return get_local_cloned(tbl,key)
+	else
+		return nil
+	end
+end
+function mt.__new_index(tbl,key,value)
+	if key=="stdin" or key=="stdout" or key=="stderr" then
+		set_local_cloned(tbl,key,value)
+	else
+		raw_set(tbl,key,value)
+	end
+end
+set_metatable(Io,mt)
 
 Io.dns_lookup = IoLuan.dns_lookup
 Io.ip = IoLuan.ip
@@ -20,8 +42,6 @@
 -- used by http and rpc
 Io.password = "password"
 
-local Luan = require "luan:Luan.luan"
-local error = Luan.error
 local to_string = Luan.to_string or error()
 local type = Luan.type or error()
 local ipairs = Luan.ipairs or error()
--- a/src/luan/modules/IoLuan.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/IoLuan.java	Mon Feb 08 14:16:19 2021 -0700
@@ -643,7 +643,7 @@
 	}
 
 	public static LuanTable dns_lookup(String domain,String type)
-		throws NamingException
+		throws NamingException, LuanException
 	{
 		LuanTable tbl = new LuanTable();
 		InitialDirContext idc = new InitialDirContext();
--- a/src/luan/modules/JavaLuan.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/JavaLuan.java	Mon Feb 08 14:16:19 2021 -0700
@@ -22,8 +22,6 @@
 import luan.LuanException;
 import luan.LuanFunction;
 import luan.LuanJavaFunction;
-import luan.LuanCloner;
-import luan.LuanImmutabler;
 
 
 public final class JavaLuan {
--- a/src/luan/modules/Luan.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/Luan.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -7,6 +7,8 @@
 
 local error = Boot.error
 Luan.error = error
+Luan.get_local_cloned = BasicLuan.get_local_cloned
+Luan.get_local_only = BasicLuan.get_local_only
 Luan.get_metatable = BasicLuan.get_metatable
 Luan.hash_code = BasicLuan.hash_code
 Luan.ipairs = BasicLuan.ipairs
@@ -21,6 +23,8 @@
 Luan.raw_get = BasicLuan.raw_get
 Luan.raw_len = BasicLuan.raw_len
 Luan.raw_set = BasicLuan.raw_set
+Luan.set_local_cloned = BasicLuan.set_local_cloned
+Luan.set_local_only = BasicLuan.set_local_only
 Luan.set_metatable = BasicLuan.set_metatable
 Luan.stringify = BasicLuan.stringify
 Luan.to_string = BasicLuan.to_string
--- a/src/luan/modules/Package.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/Package.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -1,9 +1,21 @@
 require "java"
 local PackageLuan = require "java:luan.modules.PackageLuan"
+local BasicLuan = require "java:luan.modules.BasicLuan"
+local set_metatable = BasicLuan.set_metatable
 
 local Package = {}
 
-Package.loaded = PackageLuan.loaded()
 Package.load = PackageLuan.load
 
+local loaded = PackageLuan.loaded  -- returns java map
+local mt = {}
+function mt.__index(_,key)
+	return loaded().get(key)
+end
+function mt.__new_index(_,key,value)
+	loaded().put(key,value)
+end
+Package.loaded = {}
+set_metatable(Package.loaded,mt)
+
 return Package
--- a/src/luan/modules/PackageLuan.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/PackageLuan.java	Mon Feb 08 14:16:19 2021 -0700
@@ -5,12 +5,13 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
 import goodjava.io.IoUtils;
 import luan.Luan;
 import luan.LuanTable;
 import luan.LuanFunction;
 import luan.LuanJavaFunction;
-import luan.LuanCloner;
 import luan.LuanException;
 
 
@@ -29,13 +30,13 @@
 		return fn;
 	}
 
-	public static LuanTable loaded(Luan luan) {
-		LuanTable tbl = (LuanTable)luan.registry().get("Package.loaded");
-		if( tbl == null ) {
-			tbl = new LuanTable();
-			luan.registry().put("Package.loaded",tbl);
+	public static Map loaded(Luan luan) {
+		Map map = (Map)luan.registry().get("Package.loaded");
+		if( map == null ) {
+			map = new HashMap();
+			luan.registry().put("Package.loaded",map);
 		}
-		return tbl;
+		return map;
 	}
 
 	public static Object require(Luan luan,String modName) throws LuanException {
@@ -50,8 +51,8 @@
 	}
 
 	public static Object load(Luan luan,String modName) throws LuanException {
-		LuanTable loaded = loaded(luan);
-		Object mod = loaded.rawGet(modName);
+		Map loaded = loaded(luan);
+		Object mod = loaded.get(modName);
 		if( mod == null ) {
 			if( modName.equals("luan:Boot.luan") ) {
 				String src;
@@ -82,14 +83,14 @@
 						loader.call(luan,modName)
 					);
 					if( mod == null ) {
-						mod = loaded.rawGet(modName);
+						mod = loaded.get(modName);
 						if( mod != null )
 							return mod;
 						throw new LuanException( "module '"+modName+"' returned nil" );
 					}
 				}
 			}
-			loaded.rawPut(modName,mod);
+			loaded.put(modName,mod);
 		}
 		return mod;
 	}
--- a/src/luan/modules/Rpc.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/Rpc.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -126,6 +126,7 @@
 end_function
 
 Rpc.functions = {}
+Luan.set_metatable( Rpc.functions, Boot.local_metatable )
 
 local function rpc_responder(socket,fns)
 	local server = RpcServer.new(socket)
--- a/src/luan/modules/TableLuan.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/TableLuan.java	Mon Feb 08 14:16:19 2021 -0700
@@ -112,7 +112,7 @@
 		return list.rawSubList(from,to);
 	}
 
-	public static void clear(LuanTable tbl) {
+	public static void clear(LuanTable tbl) throws LuanException {
 		tbl.rawClear();
 	}
 
--- a/src/luan/modules/Thread.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/Thread.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -34,12 +34,20 @@
 	ThreadLuan.schedule(fn,options)
 end
 
+function Thread.schedule_closure(init_fn,options)
+	local function safe_init()
+		return safe(init_fn())
+	end
+	options = options or {}
+	ThreadLuan.schedule_closure(safe_init,options)
+end
+
 
 local forever = Time.period{years=100}
 
-function Thread.global_callable(name,fns,timeout)
+function Thread.global_callable(name,init_fn,timeout)
 	timeout = timeout or forever
-	local callable = ThreadLuan.globalCallable(name,fns,timeout)
+	local callable = ThreadLuan.globalCallable(name,init_fn,timeout)
 	local mt = {}
 	function mt.__index(_,key)
 		return function(...)
@@ -54,27 +62,21 @@
 Thread.remove_global_callable = ThreadLuan.removeGlobalCallable
 
 function Thread.global_map(name,timeout)
-	timeout = timeout or forever
+	local function init()
+		local map = {}
+		local fns = {}
 
-	local map = {}
-	local fns = {}
-
-	function fns.get(key)
-		return map[key]
-	end
+		function fns.__index(_,key)
+			return map[key]
+		end
 
-	function fns.put(key,value)
-		map[key] = value
-	end
+		function fns.__new_index(_,key,value)
+			map[key] = value
+		end
 
-	local callable = ThreadLuan.globalCallable(name,fns,timeout)
-	local mt = {}
-	function mt.__index(_,key)
-		return callable.call("get",key)
+		return fns
 	end
-	function mt.__new_index(_,key,value)
-		return callable.call("put",key,value)
-	end
+	local mt = Thread.global_callable(name,init,timeout)
 	local tbl = {}
 	set_metatable(tbl,mt)
 	return tbl
@@ -94,15 +96,4 @@
 end
 
 
--- remove
-function Thread.new_synchronizer()
-	local lock = ReentrantLock.new()
-	return function(fn)
-		return function(...)
-			return run_in_lock(lock,default_time_out,fn,...)
-		end
-	end
-end
-
-
 return Thread
--- a/src/luan/modules/ThreadLuan.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/ThreadLuan.java	Mon Feb 08 14:16:19 2021 -0700
@@ -17,8 +17,7 @@
 import luan.LuanFunction;
 import luan.LuanTable;
 import luan.LuanException;
-import luan.LuanCloner;
-import luan.LuanCloneable;
+import luan.LuanMutable;
 import luan.modules.logging.LuanLogger;
 import goodjava.logging.Logger;
 import goodjava.logging.LoggerFactory;
@@ -45,10 +44,9 @@
 		};
 	}
 
-	public static void fork(Luan luan,LuanFunction fn) {
-		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-		luan = (Luan)cloner.get(luan);
-		fn = (LuanFunction)cloner.get(fn);
+	public static void fork(Luan luan,LuanFunction fn) throws LuanException {
+		luan = new Luan(luan);
+		LuanMutable.makeImmutable(fn);
 		exec.execute(runnable(luan,fn));
 	}
 
@@ -60,9 +58,26 @@
 			logger.error(src+" cancel="+b+" isCancelled="+sf.isCancelled()+" isDone="+sf.isDone()+" "+sf);
 	}
 
+	public static synchronized void schedule_closure(Luan luan,LuanFunction initFn,LuanTable options)
+		throws LuanException
+	{
+		final Luan newLuan = new Luan(luan);
+		LuanMutable.makeImmutable(initFn);
+		LuanFunction fn = (LuanFunction)initFn.call(newLuan);
+		scheduleFn(luan,newLuan,fn,options);
+	}
+
 	public static synchronized void schedule(Luan luan,LuanFunction fn,LuanTable options)
 		throws LuanException
 	{
+		final Luan newLuan = new Luan(luan);
+		LuanMutable.makeImmutable(fn);
+		scheduleFn(luan,newLuan,fn,options);
+	}
+
+	private static synchronized void scheduleFn(Luan luan,final Luan newLuan,LuanFunction fn,LuanTable options)
+		throws LuanException
+	{
 		options = new LuanTable(options);
 		Number delay = Utils.removeNumber(options,"delay");
 		Number repeatingDelay = Utils.removeNumber(options,"repeating_delay");
@@ -77,10 +92,7 @@
 			if( sf != null )
 				cancel(sf,"id "+id);
 		}
-		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-		final Luan newLuan = (Luan)cloner.clone(luan);
-		final LuanFunction newFn = (LuanFunction)cloner.get(fn);
-		final Runnable r = runnable(newLuan,newFn);
+		final Runnable r = runnable(newLuan,fn);
 		final ScheduledFuture sf;
 		if( repeatingDelay != null ) {
 			if( delay==null )
@@ -106,104 +118,24 @@
 			scheduleds.put(id,sf);
 	}
 
-/*
-	public static class GlobalMap {
-
-		private static class Value {
-			final long time = System.currentTimeMillis();
-			final Object v;
-
-			Value(Object v) {
-				this.v = v;
-			}
-		}
-
-		public long timeout = 60000L;  // one minute
-		private Map<String,Value> map = new LinkedHashMap<String,Value>() {
-			protected boolean removeEldestEntry(Map.Entry<String,Value> eldest) {
-				return eldest.getValue().time < System.currentTimeMillis() - timeout;
-			}
-		};
-
-		public synchronized Object get(String key) {
-			Value val = map.get(key);
-			return val==null ? null : val.v;
-		}
-
-		public synchronized Object put(String key,Object v) throws LuanException {
-			Value val;
-			if( v == null ) {
-				val = map.remove(key);
-			} else {
-				if( !(v instanceof String || v instanceof Boolean || v instanceof Number) )
-					throw new LuanException("can't assign type "+Luan.type(v)+" to Thread.global");
-				val = map.put(key,new Value(v));
-			}
-			return val==null ? null : val.v;
-		}
-	}
-*/
 
 	public static void sleep(long millis) throws InterruptedException {
 		Thread.sleep(millis);
 	}
 
 
-	private static class Unsafe {
-		private final String reason;
-
-		Unsafe(String reason) {
-			this.reason = reason;
-		}
-	}
-
-	private static Object makeSafe(Object v) throws LuanException {
-		if( v instanceof LuanTable ) {
-			LuanTable tbl = (LuanTable)v;
-			if( tbl.getMetatable() != null )
-				return new Unsafe("table with metatable");
-			LuanTable rtn = new LuanTable();
-			for( Map.Entry entry : tbl.rawIterable() ) {
-				Object key = makeSafe( entry.getKey() );
-				if( key instanceof Unsafe )
-					return key;
-				Object value = makeSafe( entry.getValue() );
-				if( value instanceof Unsafe )
-					return value;
-				rtn.rawPut(key,value);
-			}
-			return rtn;
-		} else if( v instanceof Object[] ) {
-			Object[] a = (Object[])v;
-			for( int i=0; i<a.length; i++ ) {
-				Object obj = makeSafe(a[i]);
-				if( obj instanceof Unsafe )
-					return obj;
-				a[i] = obj;
-			}
-			return a;
-		} else {
-			if( v instanceof LuanCloneable )
-				return new Unsafe("type "+Luan.type(v));
-			return v;
-		}
-	}
-
 	public static final class Callable {
 		private long expires;
 		private final Luan luan = new Luan();
 		private final LuanTable fns;
 
-		Callable(LuanTable fns) {
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-			this.fns = (LuanTable)cloner.get(fns);
+		Callable(LuanFunction initFn) throws LuanException {
+			LuanMutable.makeImmutable(initFn);
+			this.fns = (LuanTable)initFn.call(luan);
 		}
 
 		public synchronized Object call(Luan callerLuan,String fnName,Object... args) throws LuanException {
-			Object obj = makeSafe(args);
-			if( obj instanceof Unsafe )
-				throw new LuanException("can't pass "+((Unsafe)obj).reason+" to global_callable "+Arrays.asList(args));
-			args = (Object[])obj;
+			LuanMutable.makeImmutable(args);
 			Object f = fns.get(luan,fnName);
 			if( f == null )
 				throw new LuanException("function '"+fnName+"' not found in global_callable");
@@ -211,9 +143,7 @@
 				throw new LuanException("value of '"+fnName+"' not a function in global_callable");
 			LuanFunction fn = (LuanFunction)f;
 			Object rtn = fn.call(luan,args);
-			rtn = makeSafe(rtn);
-			if( rtn instanceof Unsafe )
-				throw new LuanException("can't return "+((Unsafe)rtn).reason+" from global_callable");
+			LuanMutable.makeImmutable(rtn);
 			return rtn;
 		}
 	}
@@ -229,11 +159,11 @@
 		}
 	}
 
-	public static synchronized Callable globalCallable(String name,LuanTable fns,long timeout) {
+	public static synchronized Callable globalCallable(String name,LuanFunction initFn,long timeout) throws LuanException {
 		Callable callable = callableMap.get(name);
 		if( callable == null ) {
 			sweep();
-			callable = new Callable(fns);
+			callable = new Callable(initFn);
 			callableMap.put(name,callable);
 		}
 		callable.expires = System.currentTimeMillis() + timeout;
--- a/src/luan/modules/http/Http.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/http/Http.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -4,6 +4,12 @@
 local ipairs = Luan.ipairs or error()
 local pairs = Luan.pairs or error()
 local type = Luan.type or error()
+local set_metatable = Luan.set_metatable or error()
+local get_local_cloned = Luan.get_local_cloned or error()
+local set_local_cloned = Luan.set_local_cloned or error()
+local get_local_only = Luan.get_local_only or error()
+local set_local_only = Luan.set_local_only or error()
+local raw_set = Luan.raw_set or error()
 local Io = require "luan:Io.luan"
 local Html = require "luan:Html.luan"
 local Table = require "luan:Table.luan"
@@ -29,6 +35,26 @@
 
 
 local Http = {}
+local mt = {}
+function mt.__index(tbl,key)
+	if key=="error_priority" then
+		return get_local_cloned(tbl,key)
+	elseif key=="request" or key=="response" then
+		return get_local_only(tbl,key)
+	else
+		return nil
+	end
+end
+function mt.__new_index(tbl,key,value)
+	if key=="error_priority" then
+		set_local_cloned(tbl,key,value)
+	elseif key=="request" or key=="response" then
+		set_local_only(tbl,key,value)
+	else
+		raw_set(tbl,key,value)
+	end
+end
+set_metatable(Http,mt)
 
 local old_java_to_table_shallow = Table.java_to_table_shallow or error()
 
--- a/src/luan/modules/http/LuanDomainHandler.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/http/LuanDomainHandler.java	Mon Feb 08 14:16:19 2021 -0700
@@ -6,7 +6,6 @@
 import goodjava.webserver.handlers.DomainHandler;
 import luan.Luan;
 import luan.LuanTable;
-import luan.LuanCloner;
 import luan.LuanFunction;
 import luan.LuanException;
 import luan.modules.logging.LuanLogger;
@@ -19,8 +18,7 @@
 
 	public LuanDomainHandler(Luan luanInit) {
 		LuanLogger.initThreadLogging();
-		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-		this.luanInit = (Luan)cloner.clone(luanInit);
+		this.luanInit = new Luan(luanInit);
 	}
 
 	@Override public Handler newHandler(String domain) {
@@ -29,8 +27,7 @@
 	}
 
 	protected Luan newLuan(final String domain) {
-		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-		Luan luan = (Luan)cloner.clone(luanInit);
+		Luan luan = new Luan(luanInit);
 		LuanFunction reset_luan = new LuanFunction() {
 			@Override public Object call(Luan luan,Object[] args) {
 				domainHandler.removeHandler(domain);
--- a/src/luan/modules/http/LuanHandler.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/http/LuanHandler.java	Mon Feb 08 14:16:19 2021 -0700
@@ -27,7 +27,6 @@
 import luan.LuanTable;
 import luan.LuanFunction;
 import luan.LuanJavaFunction;
-import luan.LuanCloner;
 import luan.LuanException;
 import luan.modules.PackageLuan;
 import luan.modules.BasicLuan;
@@ -87,8 +86,7 @@
 	private Luan newLuan() {
 		Luan luan;
 		synchronized(luanInit) {
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-			luan = (Luan)cloner.clone(luanInit);
+			luan = new Luan(luanInit);
 		}
 		LuanLogger.startThreadLogging(luan);
 		try {
@@ -138,20 +136,19 @@
 		//logger.info("close "+domain+" "+(obj!=null));
 	}
 
-	public Object call_rpc(Luan luan,String fnName,Object... args) throws LuanException {
+	public Object call_rpc(String fnName,Object... args) throws LuanException {
 		rwLock.readLock().lock();
 		LuanLogger.startThreadLogging(currentLuan);
 		try {
 			LuanFunction fn;
-			synchronized(luanInit) {
-				enableLoad("luan:Rpc.luan");
+			Luan luan;
+			synchronized(currentLuan) {
 				LuanTable rpc = (LuanTable)currentLuan.require("luan:Rpc.luan");
-				LuanTable fns = (LuanTable)rpc.get(luan,"functions");
-				fn = (LuanFunction)fns.get(luan,fnName);
+				LuanTable fns = (LuanTable)rpc.get(currentLuan,"functions");
+				fn = (LuanFunction)fns.get(currentLuan,fnName);
 				if( fn == null )
 					throw new LuanException( "function not found: " + fnName );
-				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-				fn = (LuanFunction)cloner.get(fn);
+				luan = new Luan(currentLuan);
 			}
 			return fn.call(luan,args);
 		} finally {
@@ -185,13 +182,9 @@
 	}
 
 	private void eval_in_root(String text) throws LuanException {
-		Luan luan;
-		synchronized(luanInit) {
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-			luan = (Luan)cloner.clone(currentLuan);
+		synchronized(currentLuan) {
+			currentLuan.load(text,"<eval_in_root>",false,null).call(currentLuan);
 		}
-		luan.load(text,"<eval_in_root>",false,null).call(luan);
-		currentLuan = luan;
 	}
 
 	private void dont_gc() {
@@ -249,11 +242,10 @@
 	private Response handleError(Request request,LuanException e)
 		throws LuanException
 	{
-//e.printStackTrace();
+		//e.printStackTrace();
 		Luan luan;
-		synchronized(luanInit) {
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-			luan = (Luan)cloner.clone(currentLuan);
+		synchronized(currentLuan) {
+			luan = new Luan(currentLuan);
 		}
 		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
 		return (Response)module.fn("handle_error").call( luan, request, e.table(luan) );
@@ -265,17 +257,15 @@
 		String modName = "site:" + request.path +".luan";
 		LuanFunction fn;
 		Luan luan;
-		synchronized(luanInit) {
-			enableLoad("luan:http/Http.luan",modName);
+		synchronized(currentLuan) {
  			currentLuan.require("luan:http/Http.luan");
 			Object mod = PackageLuan.load(currentLuan,modName);
 			if( mod.equals(Boolean.FALSE) )
 				return null;
 			if( !(mod instanceof LuanFunction) )
 				throw new LuanException( "module '"+modName+"' must return a function" );
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-			luan = (Luan)cloner.clone(currentLuan);
-			fn = (LuanFunction)cloner.get(mod);
+			luan = new Luan(currentLuan);
+			fn = (LuanFunction)mod;
 		}
 		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
 		module.fn("new_request").call(luan,request);
@@ -289,15 +279,12 @@
 	{
 		LuanFunction fn;
 		Luan luan;
-		synchronized(luanInit) {
-			enableLoad("luan:http/Http.luan");
+		synchronized(currentLuan) {
  			LuanTable module = (LuanTable)currentLuan.require("luan:http/Http.luan");
 			fn = module.fn("not_found_handler");
 			if( fn == null )
 				return null;
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-			luan = (Luan)cloner.clone(currentLuan);
-			fn = (LuanFunction)cloner.get(fn);
+			luan = new Luan(currentLuan);
 		}
 		LuanTable module = (LuanTable)luan.require("luan:http/Http.luan");
 		module.fn("new_request").call(luan,request);
@@ -309,15 +296,4 @@
 		return handled ? (Response)module.fn("finish").call(luan) : null;
 	}
 
-	private void enableLoad(String... mods) throws LuanException {
-		LuanTable loaded = PackageLuan.loaded(currentLuan);
-		for( String mod : mods ) {
-			if( loaded.rawGet(mod) == null ) {
-				LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-				currentLuan = (Luan)cloner.clone(currentLuan);
-				break;
-			}
-		}
-	}
-
 }
--- a/src/luan/modules/http/tools/Shell.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/http/tools/Shell.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -7,6 +7,7 @@
 local concat = Table.concat or error()
 local pack = Table.pack or error()
 local unpack = Table.unpack or error()
+local copy = Table.copy or error()
 local Time = require "luan:Time.luan"
 local Thread = require "luan:Thread.luan"
 local Html = require "luan:Html.luan"
@@ -20,36 +21,43 @@
 
 local Shell = {}
 
-local count = 0
-local new_session = Thread.global_callable("shell.new_session",{next=function()
-	count = count + 1
-	return to_string(count)
-end}).next
+local function counter()
+	local fns = {}
+	local count = 0
+	function fns.next()
+		count = count + 1
+		return to_string(count)
+	end
+	return fns
+end
+local new_session = Thread.global_callable("shell.new_session",counter).next
 
-local env = {}
-Shell.env = env
-
-local fns = {}
+Shell.env = {}
 
-function fns.run(cmd)
-	try
-		local line
+local function init_env()
+	local env = copy(Shell.env)
+	local fns = {}
+	function fns.run(cmd)
 		try
-			line = load("return "..cmd,"<web_shell>",env)
+			local line
+			try
+				line = load("return "..cmd,"<web_shell>",env)
+			catch e
+				line = load(cmd,"<web_shell>",env)
+			end
+			return line()
 		catch e
-			line = load(cmd,"<web_shell>",env)
+			--Io.print_to(Io.stderr,e)
+			return to_string(e)
 		end
-		return line()
-	catch e
---		Io.print_to(Io.stderr,e)
-		return to_string(e)
 	end
+	return fns
 end
 
 local timeout = Time.period{hours=10}
 
 local function get_session(session_id)
-	return Thread.global_callable("shell.session"..session_id,fns,timeout)
+	return Thread.global_callable("shell.session"..session_id,init_env,timeout)
 end
 
 function Shell.respond()
--- a/src/luan/modules/logging/LuanLogger.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/logging/LuanLogger.java	Mon Feb 08 14:16:19 2021 -0700
@@ -52,7 +52,10 @@
 		if( !(globalAppender instanceof ThreadLocalAppender) )
 			return;
 		ThreadLocalAppender tla = (ThreadLocalAppender)globalAppender;
-		Appender appender = (Appender)luan.registry().get(KEY);
+		Appender appender;
+		synchronized(luan) {
+			appender = (Appender)luan.registry().get(KEY);
+		}
 		if( appender == null )
 			appender = tla.defaultAppender;
 		tla.threadLocal.set(appender);
--- a/src/luan/modules/lucene/Lucene.luan	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/lucene/Lucene.luan	Mon Feb 08 14:16:19 2021 -0700
@@ -5,6 +5,8 @@
 local pairs = Luan.pairs or error()
 local type = Luan.type or error()
 local set_metatable = Luan.set_metatable or error()
+local get_local_cloned = Luan.get_local_cloned
+local set_local_cloned = Luan.set_local_cloned
 local Boot = require "luan:Boot.luan"
 local Html = require "luan:Html.luan"
 local Number = require "luan:Number.luan"
@@ -25,6 +27,11 @@
 local Lucene = {}
 
 local indexes = {}
+local indexes_mt = {
+	__index = get_local_cloned
+	__new_index = set_local_cloned
+}
+set_metatable(indexes,indexes_mt)
 
 function Rpc.functions.lucene_backup(password,name)
 	Io.password == password or error "wrong password"
@@ -62,10 +69,8 @@
 
 function Lucene.index(index_dir,options)
 	local index = {}
-	if options.name ~= nil then
-		indexes[options.name] = index
-		options.name = nil
-	end
+	local options_name = options.name
+	options.name = nil
 	index.dir = index_dir
 	index_dir = get_file(index_dir)
 	options = options or {}
@@ -74,6 +79,10 @@
 	local java_index = LuceneIndex.getLuceneIndex(index_dir,options)
 	index.java = java_index
 
+	if options_name ~= nil then
+		indexes[options_name] = index
+	end
+
 	index.indexed_fields = {}
 	local mt = {}
 	set_metatable(index.indexed_fields,mt)
--- a/src/luan/modules/lucene/SupplementingConfig.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/lucene/SupplementingConfig.java	Mon Feb 08 14:16:19 2021 -0700
@@ -13,7 +13,7 @@
 import luan.Luan;
 import luan.LuanFunction;
 import luan.LuanTable;
-import luan.LuanCloner;
+import luan.LuanMutable;
 import luan.LuanException;
 import luan.LuanRuntimeException;
 
@@ -22,11 +22,11 @@
 	private final Luan luan;
 	private final LuanFunction supplementer;
 
-	SupplementingConfig(Version luceneVersion,MultiFieldParser mfp,Luan luan,LuanFunction supplementer) {
+	SupplementingConfig(Version luceneVersion,MultiFieldParser mfp,Luan luan,LuanFunction supplementer) throws LuanException {
 		super(luceneVersion,mfp);
-		LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-		this.luan = (Luan)cloner.get(luan);
-		this.supplementer = (LuanFunction)cloner.get(supplementer);
+		this.luan = new Luan(luan);
+		LuanMutable.makeImmutable(supplementer);
+		this.supplementer = supplementer;
 	}
 
 	@Override public IndexWriterConfig newLuceneConfig() {
@@ -41,7 +41,9 @@
 			return super.getMoreFieldInfo(storedFields);
 		try {
 			LuanTable tbl = toTable(storedFields);
-			tbl = (LuanTable)supplementer.call(luan,tbl);
+			synchronized(luan) {
+				tbl = (LuanTable)supplementer.call(luan,tbl);
+			}
 			if( tbl == null ) {
 				return super.getMoreFieldInfo(storedFields);
 			} else {
--- a/src/luan/modules/parsers/LuanParser.java	Sun Jan 31 16:04:39 2021 -0700
+++ b/src/luan/modules/parsers/LuanParser.java	Mon Feb 08 14:16:19 2021 -0700
@@ -263,8 +263,13 @@
 			spaces();
 			Object obj = value();
 			if( obj != null ) {
-				if( obj != NULL )
-					tbl.rawAdd(obj);
+				if( obj != NULL ) {
+					try {
+						tbl.rawAdd(obj);
+					} catch(LuanException e) {
+						throw new RuntimeException(e);
+					}
+				}
 				spaces();
 				continue;
 			}