[m-rev.] for review: Add interface for joinable threads.
Peter Wang
novalazy at gmail.com
Fri Feb 23 17:25:38 AEDT 2024
On Fri, 23 Feb 2024 11:20:44 +1100 Julien Fischer <jfischer at opturion.com> wrote:
>
> Hi Peter,
>
> On Thu, 22 Feb 2024, Peter Wang wrote:
>
> > It is often necessary to be certain that a thread has terminated before
> > the rest of the program can continue. In some cases, signalling that a
> > thread is ABOUT to terminate (using any synchronisation device)
> > is insufficient: even after the last piece of Mercury code has run,
> > the thread still has additional cleanup code to run, which might include
> > arbitrary code in thread-specific data destructors, etc.
> >
> > Introduce a predicate to create a joinable native thread,
> > and a predicate to wait for that thread to terminate (joining).
> >
> > Joinable threads are only implemented for C backends for now.
>
> I assume you have at given some consideration that the API proposed for
> joinable threads is implementable for Java and C#?
>
Yes.
> > library/thread.m:
> > Add new predicates spawn_native_joinable and join_thread.
> >
> > Add a internal type thread_handle.
> >
> > Rename the internal type thread_id type to thread_desc,
> > as thread_id is too similar to thread_handle.
> >
> > tests/hard_coded/Mmakefile:
> > tests/hard_coded/spawn_native_joinable.exp:
> > tests/hard_coded/spawn_native_joinable.exp2:
> > tests/hard_coded/spawn_native_joinable.m:
> > Add test case.
> >
> > NEWS.md:
> > Announce changes.
>
> The diff looks fine.
Thanks.
The following is for review.
Peter
---
Support joinable threads in C# and Java backends.
library/thread.m:
Implement spawn_native_joinable and join_thread for C# and Java.
Rename the existing Java helper class RunGoal to RunGoalDetached.
Add RunGoalJoinable.
Rename the C# helper MercuryThread to RunGoalDetached, to match the
Java backend. Add RunGoalJoinable.
java/runtime/MercuryThreadPool.java:
Replace submitExclusiveThread() method with createExclusiveThread(),
which returns the newly created thread, without starting it.
diff --git a/java/runtime/MercuryThreadPool.java b/java/runtime/MercuryThreadPool.java
index a022c18d1..19d885661 100644
--- a/java/runtime/MercuryThreadPool.java
+++ b/java/runtime/MercuryThreadPool.java
@@ -1,6 +1,6 @@
// vim: ts=4 sw=4 expandtab ft=java
//
-// Copyright (C) 2014, 2016, 2018 The Mercury Team
+// Copyright (C) 2014, 2016, 2018, 2024 The Mercury team.
// This file is distributed under the terms specified in COPYING.LIB.
//
@@ -134,12 +134,11 @@ public class MercuryThreadPool
/**
* Create a new thread to execute the given task.
* @param task The task the new thread should execute.
- * @return The task.
+ * @return The new thread.
*/
- public void submitExclusiveThread(Task task)
+ public MercuryThread createExclusiveThread(Task task)
{
- Thread t = thread_factory.newThread(task);
- t.start();
+ return thread_factory.newThread(task);
}
/**
diff --git a/library/thread.m b/library/thread.m
index 19474d41c..22e9578f6 100644
--- a/library/thread.m
+++ b/library/thread.m
@@ -123,8 +123,6 @@
% The thread will continue to take up system resources until it terminates
% and has been joined by a call to join_thread/4.
%
- % The Java and C# backends do not yet support joinable threads.
- %
:- pred spawn_native_joinable(
pred(joinable_thread(T), T, io, io)::in(pred(in, out, di, uo) is cc_multi),
thread_options::in, maybe_error(joinable_thread(T))::out, io::di, io::uo)
@@ -198,6 +196,7 @@
:- pragma foreign_decl("Java", "
import jmercury.runtime.JavaInternal;
+import jmercury.runtime.MercuryThread;
import jmercury.runtime.Task;
").
@@ -225,13 +224,11 @@ import jmercury.runtime.Task;
:- type thread_desc == string.
% A thread handle from the underlying thread API.
- % The C# and Java grades currently use strings for this type but
- % that will need to change to support joinable threads in those grades.
%
:- type thread_handle.
:- pragma foreign_type("C", thread_handle, "pthread_t").
-:- pragma foreign_type("C#", thread_handle, "string").
-:- pragma foreign_type("Java", thread_handle, "java.lang.String").
+:- pragma foreign_type("C#", thread_handle, "System.Threading.Thread").
+:- pragma foreign_type("Java", thread_handle, "jmercury.runtime.MercuryThread").
%---------------------------------------------------------------------------%
@@ -374,7 +371,7 @@ spawn_context_2(_, Res, "", !IO) :-
[promise_pure, will_not_call_mercury, thread_safe, tabled_for_io,
may_not_duplicate],
"
- RunGoal rg = new RunGoal((Object[]) Goal);
+ RunGoalDetached rg = new RunGoalDetached((Object[]) Goal);
Task task = new Task(rg);
ThreadDesc = String.valueOf(task.getId());
rg.setThreadDesc(ThreadDesc);
@@ -442,20 +439,19 @@ spawn_native(Goal, Options, Res, !IO) :-
"
try {
object[] thread_locals = runtime.ThreadLocalMutables.clone();
- MercuryThread mt = new MercuryThread(Goal, thread_locals);
+ RunGoalDetached rg = new RunGoalDetached(Goal, thread_locals);
System.Threading.Thread thread = new System.Threading.Thread(
- new System.Threading.ThreadStart(mt.run));
- ThreadDesc = thread.ManagedThreadId.ToString();
- mt.setThreadDesc(ThreadDesc);
+ new System.Threading.ThreadStart(rg.run));
+ string thread_desc = thread.ManagedThreadId.ToString();
+ rg.setThreadDesc(thread_desc);
thread.Start();
+
Success = mr_bool.YES;
+ ThreadDesc = thread_desc;
ErrorMsg = """";
- } catch (System.Threading.ThreadStartException e) {
- Success = mr_bool.NO;
- ThreadDesc = """";
- ErrorMsg = e.Message;
} catch (System.SystemException e) {
- // Seen with mono.
+ // This includes System.Threading.ThreadStartException.
+ // SystemException has been seen with mono.
Success = mr_bool.NO;
ThreadDesc = """";
ErrorMsg = e.Message;
@@ -469,19 +465,23 @@ spawn_native(Goal, Options, Res, !IO) :-
[promise_pure, will_not_call_mercury, thread_safe, tabled_for_io,
may_not_duplicate],
"
- RunGoal rg = new RunGoal((Object[]) Goal);
- Task task = new Task(rg);
- ThreadDesc = String.valueOf(task.getId());
- rg.setThreadDesc(ThreadDesc);
try {
- JavaInternal.getThreadPool().submitExclusiveThread(task);
+ RunGoalDetached rg = new RunGoalDetached((Object[]) Goal);
+ Task task = new Task(rg);
+ String thread_desc = String.valueOf(task.getId());
+ rg.setThreadDesc(thread_desc);
+ JavaInternal.getThreadPool().createExclusiveThread(task).start();
+
Success = bool.YES;
+ ThreadDesc = thread_desc;
ErrorMsg = """";
} catch (java.lang.SecurityException e) {
Success = bool.NO;
+ ThreadDesc = """";
ErrorMsg = e.getMessage();
} catch (java.lang.OutOfMemoryError e) {
Success = bool.NO;
+ ThreadDesc = """";
ErrorMsg = e.getMessage();
}
if (Success == bool.NO && ErrorMsg == null) {
@@ -533,27 +533,64 @@ spawn_native_joinable(Goal, Options, Res, !IO) :-
").
:- pragma foreign_proc("C#",
- spawn_native_joinable_2(_Goal::in(pred(in, out, di, uo) is cc_multi),
- _MinStackSize::in, _OutputMutvar::in,
+ spawn_native_joinable_2(Goal::in(pred(in, out, di, uo) is cc_multi),
+ _MinStackSize::in, OutputMutvar::in,
Success::out, ThreadHandle::out, ErrorMsg::out, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, thread_safe, tabled_for_io,
may_not_duplicate],
"
- Success = mr_bool.NO;
- ThreadHandle = null;
- ErrorMsg = ""Cannot create joinable thread in this grade."";
+ try {
+ object[] thread_locals = runtime.ThreadLocalMutables.clone();
+ RunGoalJoinable rg = new RunGoalJoinable(TypeInfo_for_T, Goal,
+ thread_locals, OutputMutvar);
+ System.Threading.Thread thread = new System.Threading.Thread(
+ new System.Threading.ThreadStart(rg.run));
+ rg.setThreadHandle(thread);
+ thread.Start();
+
+ Success = mr_bool.YES;
+ ThreadHandle = thread;
+ ErrorMsg = """";
+ } catch (System.SystemException e) {
+ // This includes System.Threading.ThreadStartException.
+ // SystemException has been seen with mono.
+ Success = mr_bool.NO;
+ ThreadHandle = null;
+ ErrorMsg = e.Message;
+ }
").
:- pragma foreign_proc("Java",
- spawn_native_joinable_2(_Goal::in(pred(in, out, di, uo) is cc_multi),
- _MinStackSize::in, _OutputMutvar::in,
+ spawn_native_joinable_2(Goal::in(pred(in, out, di, uo) is cc_multi),
+ _MinStackSize::in, OutputMutvar::in,
Success::out, ThreadHandle::out, ErrorMsg::out, _IO0::di, _IO::uo),
[promise_pure, will_not_call_mercury, thread_safe, tabled_for_io,
may_not_duplicate],
"
- Success = bool.NO;
- ThreadHandle = null;
- ErrorMsg = ""Cannot create joinable thread in this grade."";
+ try {
+ RunGoalJoinable rg = new RunGoalJoinable(TypeInfo_for_T,
+ (Object[]) Goal, OutputMutvar);
+ Task task = new Task(rg);
+ MercuryThread mt = JavaInternal.getThreadPool()
+ .createExclusiveThread(task);
+ rg.setThreadHandle(mt);
+ mt.start();
+
+ Success = bool.YES;
+ ThreadHandle = mt;
+ ErrorMsg = """";
+ } catch (java.lang.SecurityException e) {
+ Success = bool.NO;
+ ThreadHandle = null;
+ ErrorMsg = e.getMessage();
+ } catch (java.lang.OutOfMemoryError e) {
+ Success = bool.NO;
+ ThreadHandle = null;
+ ErrorMsg = e.getMessage();
+ }
+ if (Success == bool.NO && ErrorMsg == null) {
+ ErrorMsg = ""Unable to create new native thread."";
+ }
").
%---------------------------------------------------------------------------%
@@ -602,9 +639,37 @@ join_thread(Thread, Res, !IO) :-
#endif
").
-join_thread_2(_ThreadHandle, Success, ErrorMsg, !IO) :-
- Success = no,
- ErrorMsg = "Joinable threads not supported in this grade.".
+:- pragma foreign_proc("C#",
+ join_thread_2(ThreadHandle::in, Success::out, ErrorMsg::out,
+ _IO0::di, _IO::uo),
+ [promise_pure, will_not_call_mercury, thread_safe, tabled_for_io,
+ may_not_duplicate],
+"
+ try {
+ ThreadHandle.Join();
+ Success = mr_bool.YES;
+ ErrorMsg = """";
+ } catch (System.SystemException e) {
+ Success = mr_bool.NO;
+ ErrorMsg = e.Message;
+ }
+").
+
+:- pragma foreign_proc("Java",
+ join_thread_2(ThreadHandle::in, Success::out, ErrorMsg::out,
+ _IO0::di, _IO::uo),
+ [promise_pure, will_not_call_mercury, thread_safe, tabled_for_io,
+ may_not_duplicate],
+"
+ try {
+ ThreadHandle.join();
+ Success = bool.YES;
+ ErrorMsg = """";
+ } catch (java.lang.InterruptedException e) {
+ Success = bool.NO;
+ ErrorMsg = e.getMessage();
+ }
+").
%---------------------------------------------------------------------------%
@@ -957,6 +1022,12 @@ call_back_to_mercury_detached(Goal, ThreadDesc, !IO) :-
:- pragma foreign_export("C",
call_back_to_mercury_joinable(in(pred(in, out, di, uo) is cc_multi),
in, in, di, uo), "ML_call_back_to_mercury_joinable_cc_multi").
+:- pragma foreign_export("C#",
+ call_back_to_mercury_joinable(in(pred(in, out, di, uo) is cc_multi),
+ in, in, di, uo), "ML_call_back_to_mercury_joinable_cc_multi").
+:- pragma foreign_export("Java",
+ call_back_to_mercury_joinable(in(pred(in, out, di, uo) is cc_multi),
+ in, in, di, uo), "ML_call_back_to_mercury_joinable_cc_multi").
:- pragma no_inline(pred(call_back_to_mercury_joinable/5)).
call_back_to_mercury_joinable(Goal, ThreadHandle, OutputMutvar, !IO) :-
@@ -970,6 +1041,9 @@ call_back_to_mercury_joinable(Goal, ThreadHandle, OutputMutvar, !IO) :-
% reference to the term resides only in some GC-inaccessible memory
% in the pthread implementation, and therefore could be collected
% before join_thread retrieves the value.
+ %
+ % The C# or Java thread APIs do not support returning a value from a
+ % joined thread anyway.
impure set_mutvar(OutputMutvar, Output)
).
@@ -1024,15 +1098,16 @@ call_back_to_mercury_joinable(Goal, ThreadHandle, OutputMutvar, !IO) :-
%---------------------------------------------------------------------------%
:- pragma foreign_code("C#", "
-private class MercuryThread {
- private object[] goal;
- private object[] thread_local_mutables;
- private string thread_desc;
+private class RunGoalDetached {
+ private object[] goal;
+ private object[] thread_local_mutables;
+ private string thread_desc;
- internal MercuryThread(object[] goal, object[] tlmuts)
+ internal RunGoalDetached(object[] goal, object[] tlmuts)
{
this.goal = goal;
this.thread_local_mutables = tlmuts;
+ this.thread_desc = null;
}
internal void setThreadDesc(string thread_desc)
@@ -1045,14 +1120,45 @@ private class MercuryThread {
runtime.ThreadLocalMutables.set_array(thread_local_mutables);
thread.ML_call_back_to_mercury_detached_cc_multi(goal, thread_desc);
}
-}").
+}
+
+private class RunGoalJoinable {
+ private runtime.TypeInfo_Struct typeinfo_for_T;
+ private object[] goal;
+ private object[] thread_local_mutables;
+ private object[] output_mutvar;
+ private System.Threading.Thread thread_handle;
+
+ internal RunGoalJoinable(runtime.TypeInfo_Struct typeinfo_for_T,
+ object[] goal, object[] tlmuts, object[] output_mutvar)
+ {
+ this.typeinfo_for_T = typeinfo_for_T;
+ this.goal = goal;
+ this.thread_local_mutables = tlmuts;
+ this.output_mutvar = output_mutvar;
+ this.thread_handle = null;
+ }
+
+ internal void setThreadHandle(System.Threading.Thread thread_handle)
+ {
+ this.thread_handle = thread_handle;
+ }
+
+ internal void run()
+ {
+ runtime.ThreadLocalMutables.set_array(thread_local_mutables);
+ thread.ML_call_back_to_mercury_joinable_cc_multi(typeinfo_for_T, goal,
+ thread_handle, output_mutvar);
+ }
+}
+").
:- pragma foreign_code("Java", "
-public static class RunGoal implements Runnable {
+public static class RunGoalDetached implements Runnable {
private final Object[] goal;
private String thread_desc;
- private RunGoal(Object[] goal)
+ private RunGoalDetached(Object[] goal)
{
this.goal = goal;
this.thread_desc = null;
@@ -1067,7 +1173,35 @@ public static class RunGoal implements Runnable {
{
thread.ML_call_back_to_mercury_detached_cc_multi(goal, thread_desc);
}
-}").
+}
+
+public static class RunGoalJoinable implements Runnable {
+ private final jmercury.runtime.TypeInfo_Struct typeinfo_for_T;
+ private final Object[] goal;
+ private final mutvar.Mutvar output_mutvar;
+ private MercuryThread thread_handle;
+
+ private RunGoalJoinable(jmercury.runtime.TypeInfo_Struct typeinfo_for_T,
+ Object[] goal, mutvar.Mutvar output_mutvar)
+ {
+ this.typeinfo_for_T = typeinfo_for_T;
+ this.goal = goal;
+ this.output_mutvar = output_mutvar;
+ this.thread_handle = null;
+ }
+
+ private void setThreadHandle(MercuryThread thread_handle)
+ {
+ this.thread_handle = thread_handle;
+ }
+
+ public void run()
+ {
+ thread.ML_call_back_to_mercury_joinable_cc_multi(typeinfo_for_T, goal,
+ thread_handle, output_mutvar);
+ }
+}
+").
%---------------------------------------------------------------------------%
--
2.43.0
More information about the reviews
mailing list