[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