Add MercuryFatalError, a new Java exception that is intended to be used for a
similar purpose to the C runtime's MR_fatal_error() function.
Throw a MercuryFatalError exception instead of calling System.exit() in a spot.
Calling exit() is fine for executables, but for code that is deployed in an
application server calling exit() may shut down the entire server.
java/runtime/MercuryFatalError.java:
Add the new exception.
java/runtime/MercuryOptions.java:
Do not call System.exit() when we encounter an unrecognized
option in MERCURY_OPTIONS, throw a MercuryFatalError exception
instead.
Refactor the process() method in order to avoid indentation.
Catch NumberFormatExceptions thrown when attempting to convert
integer option values; rethrow them as MercuryFatalErrors.
(XXX the C version of the runtime just ignores this error.)
java/runtime/JavaInternal.java:
Catch and report MercuryFatalErrors in the runMain() method.
java/runtime/MercuryWorkerThread.java:
Add an XXX about a call to System.exit() here.
Discussion of these changes can be found on the Mercury developers
mailing list archives from June 2018.
COPYING.LIB:
Add a special linking exception to the LGPL.
*:
Update references to COPYING.LIB.
Clean up some minor errors that have accumulated in copyright
messages.
I noticed this bug when accidentally throwing a Mercury exception containing
some malformed Mercury data (a Java null pointer). The Java worker thread
tried to report the exception, by calling back into Mercury but this
encountered a null pointer exception itself due to the bad data and failed
to de-register itself from the thread pool. This caused the thread pool to
mistakingly think that a thread was still alive and it failed to terminate
the program.
The solution is tu ensure that the pool knows the thread has shutdown if any
exception occurs.
This bug isn't likely to occur in practice, throwing exceptions whose
message is null is unusual.
java/runtime/MercuryWorkerThread.java:
As above.
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.
If threads are blocked while there is work in the queue extra threads may be
spawned to keep the processors busy.
Beginning now, tasks created with thread.spawn are use the thread pool.
(thread.spawn_native does not use the thread pool.)
java/runtime/Semaphore.java:
Wrap Java's Semaphore class which call the current thread's blocked()
and running() methods when a thread blocks and then runs after being
blocked.
library/thread.semaphore.m:
Use our own Semaphore class.
java/runtime/MercuryThread.java:
java/runtime/MercuryWorkerThread.java:
Define blocked() and running() on our threads.
java/runtime/NativeThread.java:
This class is used by spawn_native/4 and is required to define blocked()
and running(), however it implements them as no-ops as it isn't included
in the thread pool.
java/runtime/ThreadStatus.java:
Define the BLOCKED status.
java/runtime/MercuryThreadPool.java:
Count blocked threads seperatly and allow the creation of new threads
when existing threads become blocked.
Add some tracing code to help debug the thread management code. This is
disabled by default.
library/thread.m:
Implement spawn for Java using the thread pool. This was not enabled
earlier because without using java/runtime/Semaphore.java it was
possible to deadlock the system.
java/runtime/Task.java:
Add some tracing code to debug thread state changes, this is disabled by
default.
Thread pools are often used to reduce poor performance on embarrassingly
parallel work loads. This isn't immediately necessary on the Java backend
however I've implemented it because:
+ It will be required when we implement parallel conjunctions for the
Java backend.
+ I want to implement parallel profiling on the Java backend and don't
want to have to implement this once now, and then re-implement it after
introducing thread pools later.
We want the thread pool to generally restrict the number of threads that are
in use, this reduces overheads. However, when one or more tasks become
blocked then it can be desirable to create extra threads, this helps ensure
that all processors are kept busy and that thread pooling doesn't contribute
to any deadlocks itself. The implementation is a work in prograss and
currently does not implement this second feature.
Java's API provides several different thread pools, see
java.util.concurrent.Executors, none of which are suitable. Specifically
the fixed thread pool is unsuitable as we want to be able to temporarily
exceed the normal number of threads as explained above; and the cached
thread pools, which are also very similar to the ThreadPoolExecutor class,
do not implement the correct algorithm for determining when a new thread
should be created (they can still perform poorly for embarassingly parallel
workloads). Additionally we cannot instrument this code as easily for
parallel profiling.
These changes alter the behaviour of Mercury threads on the Java backend in
two ways, they now behave more correctly and more like threads on the C
backends.
+ If a thread throws an exception it is now reported and the program is
aborted. Previously it was ignored and let pass to the Java runtime
where I assume it was reported.
+ The program now exits only after all threads have exited.
The ThreadPool will automatically detect the number of threads to use, or if
the -P flag is given in the MERCURY_OPTIONS environment variable it will
honor that.
java/runtime/MercuryThread.java:
java/runtime/MercuryThreadPool.java:
java/runtime/MercuryWorkerThread.java:
java/runtime/Task.java:
java/runtime/ThreadStatus.java:
These new classes make up the thread pool. A MercuryThread is an
abstract class for Mercury threads, MercuryWorkerThread is a concrete
subclass of MercuryThread which includes the worker thread behaviour.
A Task is a computation/closure that has not yet been started, it
provides some methods not available in Java's generic Runnable and
Callable classes. The others should be self-explanatory and all files
contain documentation.
java/runtime/Getopt.java:
java/runtime/MercuryOptions.java:
Parse the MERCURY_OPTIONS environment variable for the -P flag.
java/runtime/JavaInternal.java:
Add support for handling Mercury exceptions, this is used in case a
worker thread's task (a Mercury thread) throws an exception.
compiler/mlds_to_java.m:
The main method of the main Java class of an application now starts and
uses the thread pool to execute main/2.
library/exception.m:
Export exception reporting code to the Java runtime system.
library/thread.m:
Use the thread pool for thread.spawn.