/*
 * Paraselene
 * Copyright (c) 2009  Akira Terasaki
 * このファイルは同梱されているLicense.txtに定めた条件に同意できる場合にのみ
 * 利用可能です。
 */
package paraselene.supervisor;


import java.util.*;
import paraselene.*;


class Child extends Thread {
	volatile Page main_page = null;
	volatile boolean call_input;
	volatile RequestParameter request = null;
	volatile Forward forward = null;
	volatile Page param_page = null;
	volatile Throwable th = null;
	volatile long	thread_id = -1;

	Child() {
		setDaemon( true );
		start();
	}

	public void run() {
		thread_id = Thread.currentThread().getId();
		try {
			synchronized( this ) {
				wait();
			}
			if ( main_page == null )	return;
			if ( call_input ) {
				forward = main_page.input( request, forward );
			}
			else {
				main_page.beginning( request );
				param_page = main_page.output( param_page, request );
			}
		}
		catch( Throwable e ) {
			th = e;
		}
	}
}

/**
 * ページ処理実行環境。<BR>
 * 各Pageインスタンスのinput()とoutput()は個別スレッドにて実行し、
 * サーブレットコンテナ提供のスレッドから切り離して実行されます。<BR>
 * 実際のリクエストに先行し、このスレッドは開始直前の状態のままで複数のスレッドが
 * プーリングされ、リクエスト発生に備えています。<BR>
 * このスレッド群は同一サーブレットコンテナ内で実行される
 * Paraseleneを使用する全てのコンテキストで共用されています。<BR>
 * スレッドプーリング数は、リクエスト処理数をモニターしながら、随時調整されています。
 * 瞬間リクエスト数が増加すると、プーリング数も増加します。
 * ただし、Java仮想マシンが使用できるメモリ容量の93.75%が使用されていると、
 * プーリングを見送ります。
 * また逆にリクエスト数が低下すると、プーリング中のスレッドを破棄します。<BR>
 * 実行されるPageインスタンスは、実行時間を監視され、規定値(デフォルト3分)を
 * 上回ると中断され、GateクラスのonErrorに{@link KilledByTimeoutException}を
 * 通知します。
 */
public class SandBox extends Thread {
	private static LinkedList<Child>	waiting = new LinkedList<Child>();
	private static ArrayList<Child>	running = new ArrayList<Child>();
	private static volatile long	wait_ms = 3 * 60 * 1000;
	private static SandBox sand_box = null;

	private static Child getMyself() {
		long	my_id = Thread.currentThread().getId();
		synchronized( running ) {
			int	cnt = running.size();
			for ( int i = 0; i < cnt; i++ ) {
				Child	child = running.get( i );
				if ( child.thread_id == my_id )	return child;
			}
		}
		return null;
	}

	/**
	 * 実行中ページの取得。
	 * 各ページの、input または output が呼ばれている時、自身のページを
	 * いつでも参照できます。<br>
	 * input や output 経由で呼ばれているのであれば、どのクラスメソッドからでも
	 * 参照可能です。
	 * @return 実行中のページ。
	 */
	public static Page getCurrentPage() {
		Child	child = getMyself();
		if ( child == null )	return null;
		return child.main_page;
	}

	/**
	 * リクエストパラメータの取得。
	 * 各ページの、input または output が呼ばれている時、現在のリクエストパラメータを
	 * いつでも参照できます。<br>
	 * input や output 経由で呼ばれているのであれば、どのクラスメソッドからでも
	 * 参照可能です。
	 * @return 現在のリクエストパラメータ。
	 */
	public static RequestParameter getCurrentRequestParameter() {
		Child	child = getMyself();
		if ( child == null )	return null;
		return child.request;
	}

	/**
	 * Input処理中であるか。
	 * 各ページの、input または output が呼ばれている時、現在どちらを
	 * 処理しているか返します。<br>
	 * input や output 経由で呼ばれているのであれば、どのクラスメソッドからでも
	 * 参照可能です。
	 * @return true:input、false:input以外 output や firstOutput。
	 */
	public static boolean isCurrentInput() {
		Child	child = getMyself();
		if ( child == null )	return false;
		return child.call_input;
	}

	private SandBox(){}

	/**
	 * 待機スレッド数の取得。
	 * @return 待機スレッド数。
	 */
	public static int getWaitChild() {
		synchronized( waiting ) {
			return waiting.size();
		}
	}

	/**
	 * 実行スレッド数。
	 * @return 実行スレッド数。
	 */
	public static int getRunningChild() {
		synchronized( running ) {
			return running.size();
		}
	}

	/**
	 * メモリ使用率の確認。
	 * 残りメモリが逼迫している場合は、ガベージコレクトをJVMに要求します。
	 * @return true:良好、false:使用可能メモリの93.75%を使っている。
	 */
	public static boolean isSafeFreeMemory() {
		MemoryInformation	mem = new MemoryInformation();
		mem.doGC();
		return mem.isSafeFreeMemory();
	}

	/**
	 * SandBoxが監視するタイムアウト分数の取得。
	 * @return タイムアウト分数。
	 */
	public static long getTimeoutMinute() {
		return wait_ms / (60 * 1000);
	}

	/**
	 * SandBoxが監視するタイムアウト分数の設定。
	 * 同一サーブレットコンテナ上で実行されるParaselene使用コンテキストの全てに
	 * 影響します。デフォルトは3分です。
	 * @param min タイムアウト分数。
	 */
	public static void setTimeoutMinute( long min ) {
		wait_ms = min * 60 * 1000;
	}

	static void doStart() {
		synchronized( waiting ) {
			if ( sand_box != null )	return;
			sand_box = new SandBox();
			sand_box.setDaemon( true );
			sand_box.setPriority( MIN_PRIORITY );
			sand_box.start();
		}
	}

	public void run() {
		int	low_cnt = 20;
		int	running_last_cnt = low_cnt;
		int include_cnt = low_cnt >> 2;
		long	max_interval = 1000 * 15;
		long	interval = 500;
		long	last_tm = new Date().getTime();

		while( true ) {
			try {
				synchronized( waiting ) {
					int cnt = waiting.size();
					if ( cnt > 0 && !Option.isReleaseMode() ) {
						while ( true ) {
							cnt = waiting.size();
							if ( cnt <= 0 )	break;
							Child	child = waiting.poll();
							synchronized( child ) {
								child.notifyAll();
							}
						}
					}
					if ( isSafeFreeMemory() && cnt < running_last_cnt && Option.isReleaseMode() ) {
						for ( int i = 0; i < include_cnt; i++ ) {
							Child	child = new Child();
							waiting.wait( 10 );
							waiting.offer( child );
						}
					}
					else if ( cnt > (running_last_cnt + include_cnt) ) {
						Child	child = waiting.poll();
						synchronized( child ) {
							child.notifyAll();
						}
					}
				}
				synchronized( this ) {
					last_tm = new Date().getTime();
					wait( interval, 1 );
				}
				long	time_sub = new Date().getTime() - last_tm;
				int	cnt = 0;
				synchronized( running ) {
					cnt = running.size() - running_last_cnt;
				}
				if ( cnt > running_last_cnt ) {
					running_last_cnt = cnt;
				}
				else if ( low_cnt < running_last_cnt ) {
					running_last_cnt--;
				}
				include_cnt = running_last_cnt >> 2;
				if ( time_sub < interval ) {
					interval >>= 1;
				}
				else if ( interval < max_interval ) {
					interval += 100;
				}
			}
			catch( Exception e ) {
				try {
					e.printStackTrace();
					Thread.sleep( 1000 );
				}
				catch( Exception ee ) {}
			}
		}
	}

	static Child getChild() {
		Child	child = null;
		synchronized( waiting ) {
			int cnt = waiting.size();
			if ( cnt > 0 )	child = waiting.poll();
		}
		if ( child == null )	child = new Child();
		while( child.getState() != Thread.State.WAITING ) {
			try {
				Thread.sleep( 100 );
			}
			catch( Exception e ) {}
		}
		return child;
	}

	@SuppressWarnings("deprecation")
	static void kick( Child child ) throws Throwable {
		synchronized( running ) {
			running.add( child );
		}
		synchronized( child ) {
			child.notifyAll();
		}
		synchronized( sand_box ) {
			sand_box.notify();
		}
		child.join( wait_ms );
		if ( !child.isAlive() ) {
			synchronized( running ) {
				running.remove( child );
			}
			if ( child.th != null )	throw child.th;
			return;
		}
		ArrayList<StackTraceElement[]>	list = new ArrayList<StackTraceElement[]>();
		StackTraceElement[]	crt = null;
		synchronized( running ) {
			int	cnt = running.size();
			for ( int i = 0; i < cnt; i++ ) {
				Child	c = running.get( i );
				if ( c == child ) {
					crt = c.getStackTrace();
					continue;
				}
				list.add( c.getStackTrace() );
			}
		}
		child.stop();
		synchronized( running ) {
			running.remove( child );
		}
		throw new KilledByTimeoutException( crt, list );
	}

	static Forward input( Page action, RequestParameter req, Forward fw )
	throws Throwable {
		Child	child = getChild();
		child.main_page = action;
		child.request = req;
		child.forward = fw;
		child.call_input = true;
		kick( child );
		return child.forward;
	}

	static Page output( Page action, Page page, RequestParameter req )
	throws Throwable {
		Child	child = getChild();
		child.main_page = action;
		child.param_page = page;
		child.request = req;
		child.call_input = false;
		kick( child );
		return child.param_page;
	}
}

