From 4dd926b54d7440c397a2cbf910f5bf1514d20e52 Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Mon, 26 Feb 2024 12:42:14 +1100 Subject: [PATCH] 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. --- java/runtime/MercuryThreadPool.java | 9 +- library/thread.m | 220 ++++++++++++++++++++++------ 2 files changed, 181 insertions(+), 48 deletions(-) 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 391f3e776..2c9e4bad4 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) @@ -204,6 +202,7 @@ :- pragma foreign_decl("Java", " import jmercury.runtime.JavaInternal; +import jmercury.runtime.MercuryThread; import jmercury.runtime.Task; "). @@ -231,13 +230,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, "ML_ThreadHandle"). -:- 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"). %---------------------------------------------------------------------------% @@ -380,7 +377,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); @@ -448,20 +445,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; @@ -475,19 +471,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) { @@ -539,27 +539,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.""; + } "). %---------------------------------------------------------------------------% @@ -608,9 +645,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(); + } +"). %---------------------------------------------------------------------------% @@ -960,6 +1025,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) :- @@ -973,6 +1044,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) ). @@ -1027,15 +1101,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) @@ -1048,14 +1123,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; @@ -1070,7 +1176,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); + } +} +"). %---------------------------------------------------------------------------%