view src/luan/modules/http/LuanHandler.java @ 1264:d41997776788

fix onClose issues
author Franklin Schmidt <fschmidt@gmail.com>
date Tue, 25 Sep 2018 17:03:57 -0600
parents 382c444a6c77
children 3f4644246e39
line wrap: on
line source

package luan.modules.http;

import java.io.Closeable;
import java.io.Writer;
import java.io.PrintWriter;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.net.BindException;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import luan.webserver.Request;
import luan.webserver.Response;
import luan.webserver.Server;
import luan.webserver.Handler;
import luan.webserver.ResponseOutputStream;
import luan.Luan;
import luan.LuanState;
import luan.LuanTable;
import luan.LuanFunction;
import luan.LuanJavaFunction;
import luan.LuanCloner;
import luan.LuanException;
import luan.modules.PackageLuan;
import luan.modules.BasicLuan;


public final class LuanHandler implements Handler {

	private class Instance implements LuanState.OnClose {
		private final List<Reference<Closeable>> onClose;
		private final LuanState luan;
		private final ReadWriteLock lock = new ReentrantReadWriteLock();

		Instance() {
			onClose = new ArrayList<Reference<Closeable>>();
			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
			luan = (LuanState)cloner.clone(luanInit);
			luan.onClose = this;
			try {
				PackageLuan.load(luan,"site:/init.luan");
			} catch(LuanException e) {
				String err = e.getLuanStackTraceString();
				logger.error(err);
			}
		}

		Response handle(Request request,String modName) {
			Thread thread = Thread.currentThread();
			String oldName = thread.getName();
			thread.setName(request.headers.get("host")+request.path);
			lock.readLock().lock();
			try {
				return HttpServicer.service(luan,request,modName);
			} finally {
				lock.readLock().unlock();
				thread.setName(oldName);
			}
		}

		Object call_rpc(String fnName,Object... args) throws LuanException {
			lock.readLock().lock();
			try {
				LuanFunction fn;
				LuanState luan = this.luan;
				synchronized(luan) {
					PackageLuan.enableLoad(luan,"luan:Rpc.luan");
					LuanTable rpc = (LuanTable)PackageLuan.require(luan,"luan:Rpc.luan");
					LuanTable fns = (LuanTable)rpc.get(luan,"functions");
					fn = (LuanFunction)fns.get(luan,fnName);
					if( fn == null )
						throw new LuanException( "function not found: " + fnName );
					LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
					luan = (LuanState)cloner.clone(luan);
					fn = (LuanFunction)cloner.get(fn);
				}
				return fn.call(luan,args);
			} finally {
				lock.readLock().unlock();
			}
		}

		public Object runLuan(String sourceText,String sourceName) throws LuanException {
			lock.readLock().lock();
			try {
				LuanFunction fn = Luan.load(sourceText,sourceName);
				synchronized(luan) {
					LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
					LuanState luan = (LuanState)cloner.clone(this.luan);
					return fn.call(luan);
				}
			} finally {
				lock.readLock().unlock();
			}
		}

		public void onClose(Closeable c) {
			synchronized(onClose) {
				onClose.add(new WeakReference<Closeable>(c));
			}
		}

		public void close() {
			synchronized(onClose) {
				for( Reference<Closeable> ref : onClose ) {
					Closeable c = ref.get();
					if( c != null ) {
						try {
							c.close();
						} catch(IOException e) {
							logger.error(c.toString(),e);
						}
					}
				}
				onClose.clear();
			}
		}

		Instance cloneInstance() {
			synchronized(luan) {
				return new Instance(this);
			}
		}

		private Instance(Instance instance) {
			onClose = instance.onClose;
			LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
			luan = (LuanState)cloner.clone(instance.luan);
			luan.onClose = this;
		}
	}

	private final LuanState luanInit;
	private final Logger logger;
	private volatile Instance instance;

	private static final Method resetLuanMethod;
	private static final Method evalInRootMethod;
	static {
		try {
			resetLuanMethod = LuanHandler.class.getMethod( "reset_luan" );
			evalInRootMethod = LuanHandler.class.getMethod( "eval_in_root", String.class );
		} catch(NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}

	public LuanHandler(LuanState luanInit,String loggerRoot) {
		this.luanInit = luanInit;
		if( loggerRoot==null )
			loggerRoot = "";
		logger = LoggerFactory.getLogger(loggerRoot+LuanHandler.class.getName());
		try {
			LuanTable Http = (LuanTable)PackageLuan.require(luanInit,"luan:http/Http.luan");
			Http.rawPut( "reset_luan", new LuanJavaFunction(resetLuanMethod,this) );
			Http.rawPut( "eval_in_root", new LuanJavaFunction(evalInRootMethod,this) );
		} catch(LuanException e) {
			throw new RuntimeException(e);
		}
		instance = new Instance();
	}
/*
	public LuanState getLuan() {
		return luan;
	}
*/
	@Override public Response handle(Request request) {
		if( request.path.endsWith("/") )
			return null;
		String modName = "site:" + request.path +".luan";
		return handle(request,modName);
	}

	Response handle(Request request,String modName) {
		return instance.handle(request,modName);
	}

	public void close() {
		instance.close();
	}

	public Object call_rpc(String fnName,Object... args) throws LuanException {
		return instance.call_rpc(fnName,args);
	}

	public void reset_luan() {
		new Thread() {
			public void run() {
				instance.lock.writeLock().lock();
				try {
					instance.close();
					instance = new Instance();
				} finally {
					instance.lock.writeLock().unlock();
				}
			}
		}.start();
	}

	public Object runLuan(String sourceText,String sourceName) throws LuanException {
		return instance.runLuan(sourceText,sourceName);
	}

	public void eval_in_root(String text) throws LuanException {
		Instance newInstance = this.instance.cloneInstance();
		BasicLuan.load(text,"<eval_in_root>",null).call(newInstance.luan);
		this.instance = newInstance;
	}

	public static void start(Server server) throws Exception {
		try {
			server.start();
		} catch(BindException e) {
			throw new LuanException(e.toString());
		}
	}

}