mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 17:33:38 +00:00
[java] Fix non-termination when main/2 throws an exception
When main/2 throws an exception we did not properly shutdown the thread pool
and therefore the JVM would not shut down. Simply calling shutdown() in a
finally block is insufficient because then the primordial thread may finish
before the worker thread is able to report the exception thrown by main/2.
This doesn't seem right because the JVM is supposed to wait for all the
non-daemon threads to finish before it exits. I suspect that the primordial
thread is closing stdout and stderr as it exits and therefore the exception
is never seen, but I don't know.
This change fixes the issue by ensuring that shutdown() is always called (in
a finally block) and that the main thread waits for the thread pool to
shutdown before it exits.
java/runtime/MercuryThreadPool.java:
runMain() will not exit until the worker threads have exited.
Create a new method waitForShutdown() that will wait for the thread pool
to shutdown.
Signal the main thread when a worker thread exits.
java/runtime/MercuryWorkerThread.java:
Worker threads now exit if their task raises an unhanded exception.
java/runtime/MercuryRuntime.java:
Allow standalone programs to have the same behavour as programs whose
entrypoint is written in Mercury.
This commit is contained in:
@@ -311,6 +311,8 @@ public class MercuryThreadPool
|
||||
} finally {
|
||||
threads_lock.unlock();
|
||||
}
|
||||
|
||||
signalMainLoop();
|
||||
}
|
||||
|
||||
public void taskDone(Task task)
|
||||
@@ -346,7 +348,12 @@ public class MercuryThreadPool
|
||||
{
|
||||
main_loop_lock.lock();
|
||||
try {
|
||||
main_loop_condition.signal();
|
||||
/*
|
||||
* There may be more than one thread waiting on this condition
|
||||
* such as when more than one thread calls waitForShutdown().
|
||||
* I can't imagine this happening, bit it is allowed.
|
||||
*/
|
||||
main_loop_condition.signalAll();
|
||||
} finally {
|
||||
main_loop_lock.unlock();
|
||||
}
|
||||
@@ -407,8 +414,7 @@ public class MercuryThreadPool
|
||||
*/
|
||||
threads_lock.lock();
|
||||
try {
|
||||
int num_threads = num_threads_working + num_threads_waiting +
|
||||
num_threads_blocked + num_threads_other;
|
||||
int num_threads = numThreads();
|
||||
int num_threads_limit = thread_pool_size +
|
||||
num_threads_blocked;
|
||||
// Determine the number of new threads that we want.
|
||||
@@ -451,6 +457,15 @@ public class MercuryThreadPool
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of threads.
|
||||
* Caller must hold threads lock.
|
||||
*/
|
||||
private int numThreads() {
|
||||
return num_threads_working + num_threads_waiting +
|
||||
num_threads_blocked + num_threads_other;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the thread pool.
|
||||
* The calling thread is used to "run" the thread pool. Its main job
|
||||
@@ -542,19 +557,55 @@ public class MercuryThreadPool
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Shutdown
|
||||
*/
|
||||
doShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the shutdown.
|
||||
*/
|
||||
private void doShutdown()
|
||||
{
|
||||
tasks_lock.lock();
|
||||
try {
|
||||
shutdown_now = true;
|
||||
thread_wait_for_task_condition.signalAll();
|
||||
running = false;
|
||||
thread_wait_for_task_condition.signalAll();
|
||||
} finally {
|
||||
tasks_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for all the worker threads to exit.
|
||||
* Even though the JVM is not supposed to exit until all the non-daemon
|
||||
* threads have exited the effects of some threads may get lost. I
|
||||
* suspect this may be because the main thread closes stdout and stderr.
|
||||
* Waiting for the worker threads fixes the problem - pbone.
|
||||
*/
|
||||
public boolean waitForShutdown()
|
||||
{
|
||||
boolean has_shutdown = false;
|
||||
|
||||
if (shutdown_request) {
|
||||
do {
|
||||
main_loop_lock.lock();
|
||||
try {
|
||||
has_shutdown = numThreads() == 0;
|
||||
if (!has_shutdown) {
|
||||
main_loop_condition.await();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
continue;
|
||||
} finally {
|
||||
main_loop_lock.unlock();
|
||||
}
|
||||
} while (!has_shutdown);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the thread pool in its own thread.
|
||||
* Normally the thread pool ie executed directly by the main thread.
|
||||
@@ -589,12 +640,13 @@ public class MercuryThreadPool
|
||||
|
||||
/**
|
||||
* Request that the thread pool shutdown.
|
||||
* This method does not wait for the thread pool to shutdown, it's an
|
||||
* asynchronous signal. The thread pool will shutdown if: shutdown() has
|
||||
* The thread pool will shutdown if: shutdown() has
|
||||
* been called (implicitly when main/2 is written in Mercury) and there
|
||||
* are no remaining tasks either queued or running (spawn_native tasks
|
||||
* are not included). The requirement that the process does not exit
|
||||
* until all tasks have finish is maintained by the JVM.
|
||||
* are not included).
|
||||
*
|
||||
* This method is asynchronous, it will not wait for the thread pool to
|
||||
* shutdown.
|
||||
*/
|
||||
public boolean shutdown()
|
||||
{
|
||||
@@ -624,8 +676,11 @@ public class MercuryThreadPool
|
||||
|
||||
run_main_and_shutdown = new Runnable() {
|
||||
public void run() {
|
||||
run_main.run();
|
||||
shutdown();
|
||||
try {
|
||||
run_main.run();
|
||||
} finally {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
main_task = new Task(run_main_and_shutdown);
|
||||
@@ -649,6 +704,12 @@ public class MercuryThreadPool
|
||||
*/
|
||||
run();
|
||||
jmercury.runtime.JavaInternal.run_finalisers();
|
||||
/*
|
||||
* We always wait for the thread pool to shutdown as worker
|
||||
* threads may either be completing work or reporting the reason
|
||||
* why the runtime is aborting.
|
||||
*/
|
||||
waitForShutdown();
|
||||
} catch (jmercury.runtime.Exception e) {
|
||||
JavaInternal.reportUncaughtException(e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user