System#exitを呼び出してもVMを終了させない方法

こんな感じででけた。もしこのコードを試すのであれば、ラーニングテストなので、適宜org.junit.Testをインポートしてね。

@Test
public void exitしないことを確認しよう() throws Exception {
	System.setSecurityManager(new SecurityManager() {
		@Override
		public void checkExit(int status) {
			throw new SecurityException();
		}
		@Override
		public void checkPermission(final Permission perm) {
		}
		@Override
		public void checkPermission(final Permission perm, final Object context) {
		}
	});
	try {
		//  はーい、ここ注目ですよー。
		System.exit(0);
	} catch (Exception e) {
	}
	System.out.println("System#exitを実行したのに終了しなかったYO!");
}

実行してみると、こんな感じでコンソールに表示されるはず。

System#exitを実行したのに終了しなかったYO!
Exception in thread "main" java.lang.SecurityException
	at learning.Learning$1.checkExit(Learning.java:17)
	at java.lang.Runtime.exit(Runtime.java:88)
	at java.lang.System.exit(System.java:921)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:202)

SecurityExceptionが後に表示されるのは、なぜなんでしょう?そこは分かってないです。なんでこんな事を試したか、というと、アプリケーションの中にスクリプトコードでカスタマイズできる部分を用意し、ユーザに自由にカスタマイズしてもらう事を想定すると、System#exitを実行されてVMが終了されるのは非常に困るからです。実際、JAMCircleのスクリプトコンソールにこんな感じで組み込んでみると、JRubyのコードからSystem.exitを実行しても終了されないようになりました。

SecurityManager securityManager = System.getSecurityManager();
System.setSecurityManager(new SecurityManager() {
	@Override
	public void checkExit(int status) {
		throw new SecurityException();
	}
	@Override
	public void checkPermission(final Permission perm) {
	}
	@Override
	public void checkPermission(final Permission perm, final Object context) {
	}
});
// run ruby code
runtimeAdapter.eval(runtime, script);
// retrieve security manager
System.setSecurityManager(securityManager);

そういうのはpolicyに書くといいよ、というお話もありますが、JAMCircleみたいな単体アプリケーションを配布する事を考えると、ユーザにpolicyを修正して頂く事になるので現実的ではありません。本当はJRuby等のスクリプトコンテナの方で設定できるのがスクリプトコンテナの利用者からするといいと思うんです。なんとなくアプリケーションの責務ってよりも、スクリプトエンジンの責務っぽいですから。ただ、これをこのまま組み込んでしまうと、スクリプトから任意のファイルを触れると思うので、それもセキュリティ上よろしくないですよね。実際に組み込むのはもう少し考える事がありそうです。

追記

コメントでも頂きましたが、上記のコンソールに表示されるスタックとレースは、RemoteTestRunner内でSystem#exitが呼ばれているからでした。

2010/08/13追記

スクリプト実行中に他の操作で終了処理するとプロセスが落ちませんね。ちゃんと呼び出し元を判別する必要があるみたい。