view src/luan/modules/ThreadLuan.java @ 1972:253f8a23e131

threading for swing
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 18 Jun 2025 18:54:38 -0600
parents 31f006c64782
children
line wrap: on
line source

package luan.modules;

import java.io.Closeable;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import goodjava.util.WeakCacheMap;
import luan.Luan;
import luan.LuanFunction;
import luan.LuanTable;
import luan.LuanException;
import luan.LuanRuntimeException;
import luan.LuanMutable;
import luan.modules.logging.LuanLogger;
import goodjava.logging.Logger;
import goodjava.logging.LoggerFactory;


public final class ThreadLuan {
	private static final Logger logger = LoggerFactory.getLogger(ThreadLuan.class);

	public static final String CLOSEABLES = "Luan.closeables";
	public interface Closeables {
		public void addCloseable(Closeable c) throws LuanException;
	}

	private static final Executor exec = Executors.newCachedThreadPool();
	public static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

	private static Runnable runnable(final Luan luan,final LuanFunction fn) {
		return new Runnable() {
			public synchronized void run() {
				LuanLogger.startThreadLogging(luan);
				try {
					fn.call(luan);
				} catch(LuanException e) {
					e.printStackTrace();
				} finally {
					LuanLogger.endThreadLogging();
				}
			}
		};
	}

	private static Luan newLuan(Luan luan,Boolean clean) {
		if( Boolean.TRUE.equals(clean) ) {
			Luan.Security security = luan.getSecurity();
			luan = new Luan();
			if( security != null )
				Luan.setSecurity(luan,security);
			return luan;
		} else {
			return new Luan(luan);
		}
	}

	public static void run(Luan luan,LuanFunction fn,Boolean clean) throws LuanException {
		luan = newLuan(luan,clean);
		LuanMutable.makeImmutable(fn);
		exec.execute(runnable(luan,fn));
	}

	public static LuanFunction thread_safe_function(final Luan luan,final LuanFunction fn) {
		final Thread thread = Thread.currentThread();
		return new LuanFunction() {
			@Override public Object call(Luan luan2,Object[] args) throws LuanException {
				if( thread != Thread.currentThread() )
					throw new LuanException("called function from another thread");
				if( luan != luan2 )
					throw new LuanException("called function from another luan");
				return fn.call(luan,args);
			}
		};
	}

	private static void cancel(ScheduledFuture sf,String src) {
		boolean b = sf.cancel(false);
		if( !sf.isCancelled() )
			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");
		Number repeatingRate = Utils.removeNumber(options,"repeating_rate");
		String id = Utils.removeString(options,"id");
		if( id != null )
			logger.error("thread option 'id' is obsolete: "+id);
		if( repeatingDelay!=null && repeatingRate!=null )
			throw new LuanException("can't define both repeating_delay and repeating_rate");
		boolean repeating = repeatingDelay!=null || repeatingRate!=null;
		Utils.checkEmpty(options);
		final Runnable r = runnable(newLuan,fn);
		final ScheduledFuture sf;
		if( repeatingDelay != null ) {
			if( delay==null )
				delay = repeatingDelay;
			sf = scheduler.scheduleWithFixedDelay(r,delay.longValue(),repeatingDelay.longValue(),TimeUnit.MILLISECONDS);
		} else if( repeatingRate != null ) {
			if( delay==null )
				delay = repeatingRate;
			sf = scheduler.scheduleAtFixedRate(r,delay.longValue(),repeatingRate.longValue(),TimeUnit.MILLISECONDS);
		} else if( delay != null ) {
			sf = scheduler.schedule(r,delay.longValue(),TimeUnit.MILLISECONDS);
		} else {
			scheduler.schedule(r,0L,TimeUnit.MILLISECONDS);
			return;
		}
		Closeables cs = (Closeables)luan.registry.get(CLOSEABLES);
		if( cs != null ) {
			Closeable cl = new Closeable() {
				public void close() {
					cancel(sf,"close");
				}
				protected void finalize() throws Throwable {
					cancel(sf,"gc");  // cancel on gc
				}
			};
			cs.addCloseable(cl);
		}
	}


	public static void sleep(long millis) throws InterruptedException {
		Thread.sleep(millis);
	}


	public static final class CallableLuan {
		private long expires;
		private final Luan luan;
		private final LuanTable fns;

		CallableLuan(Luan luan,LuanFunction initFn) throws LuanException {
			this.luan = new Luan(luan);
			LuanMutable.makeImmutable(initFn);
			Object obj = initFn.call(luan);
			if( !(obj instanceof LuanTable) )
				throw new LuanException("global_callable init_fn must return a table");
			this.fns = (LuanTable)obj;
		}

		public synchronized Object call(String fnName,Object... args) throws LuanException {
			LuanMutable.makeImmutable(args);
			Object f = fns.get(luan,fnName);
			if( f == null )
				throw new LuanException("function '"+fnName+"' not found in global_callable");
			if( !(f instanceof LuanFunction) )
				throw new LuanException("value of '"+fnName+"' not a function in global_callable");
			LuanFunction fn = (LuanFunction)f;
			Object rtn = fn.call(luan,args);
			LuanMutable.makeImmutable(rtn);
			return rtn;
		}
	}

	private static void sweep(Map<String,CallableLuan> callableMap,long now) {
		for( Iterator<CallableLuan> iter = callableMap.values().iterator(); iter.hasNext(); ) {
			CallableLuan callable = iter.next();
			if( callable.expires < now )
				iter.remove();
		}
	}

	public static CallableLuan globalCallable(Luan luan,final String name,LuanFunction initFn,long timeout) throws LuanException {
		Map rootRegistry = luan.rootRegistry;
		synchronized(rootRegistry) {
			Map<String,CallableLuan> callableMap = (Map<String,CallableLuan>)rootRegistry.get("Luan.callableMap");
			if( callableMap == null ) {
				callableMap = new HashMap<String,CallableLuan>();
				rootRegistry.put("Luan.callableMap",callableMap);
			}
			long now = System.currentTimeMillis();
			CallableLuan callable = callableMap.get(name);
			if( callable == null ) {
				sweep(callableMap,now);
				callable = new CallableLuan(luan,initFn);
				callableMap.put(name,callable);
			}
			callable.expires = now + timeout;
			return callable;
		}
	}

	public static void removeGlobalCallable(Luan luan,String name) {
		Map rootRegistry = luan.rootRegistry;
		synchronized(rootRegistry) {
			Map<String,CallableLuan> callableMap = (Map<String,CallableLuan>)rootRegistry.get("Luan.callableMap");
			if( callableMap != null )
				callableMap.remove(name);
		}
	}


	public static Object runInLock(Luan luan,Lock lock,long timeout,LuanFunction fn,Object... args)
		throws LuanException, InterruptedException
	{
		if( !lock.tryLock(timeout,TimeUnit.MILLISECONDS) )
			throw new LuanException("failed to acquire lock");
		try {
			return fn.call(luan,args);
		} finally {
			lock.unlock();
		}
	}

	private static final Map<String,Lock> locks = new WeakCacheMap<String,Lock>();

	public static synchronized Lock getLock(String key) {
		Lock lock = locks.get(key);
		if( lock == null ) {
			lock = new ReentrantLock();
			locks.put(key,lock);
		}
		return lock;
	}


	private static Callable<Object> callable(final Luan luan,final LuanFunction fn) {
		return new Callable<Object>() {
			public Object call() {
				LuanLogger.startThreadLogging(luan);
				try {
					return fn.call(luan);
				} catch(LuanException e) {
					//e.printStackTrace();
					throw new LuanRuntimeException(e);
				} finally {
					LuanLogger.endThreadLogging();
				}
			}
		};
	}

	public static final class FutureLuan {
		public final Future<Object> future;

		private FutureLuan(Future<Object> future) {
			this.future = future;
		}

		public Object get()
			throws LuanException, InterruptedException
		{
			try {
				return future.get();
			} catch(ExecutionException e) {
				Throwable cause = e.getCause();
				if( cause instanceof LuanRuntimeException ) {
					throw (LuanException)cause.getCause();
				} else {
					throw new LuanException(cause);
				}
			}
		}

		public boolean isDone() {
			return future.isDone();
		}
	}

	public static FutureLuan newFuture(Luan luan,LuanFunction fn)
	{
		luan = new Luan(luan);
		LuanMutable.makeImmutable(fn);
		FutureTask<Object> ft = new FutureTask<Object>(callable(luan,fn));
		new Thread(ft).start();
		return new FutureLuan(ft);
	}

}