[m-rev.] for review: Report more specific error messages when thread.spawn_native fails.

Peter Wang novalazy at gmail.com
Tue Apr 24 15:20:53 AEST 2018


library/thread.m:
    Return error message for errno when pthread_create() fails
    in thread.spawn_native.

    Return the exception message when thread creation fails in C#.
    Catch System.SystemException as that is seen in practice with mono
    (might not be documented).

    Return the exception message when thread creation fails in Java.
    Catch SecurityException (as documented) and OutOfMemoryError
    (seen in practice).
---
 library/thread.m | 77 +++++++++++++++++++++++++++++++++++-------------
 1 file changed, 57 insertions(+), 20 deletions(-)

diff --git a/library/thread.m b/library/thread.m
index 77505b5a5..0c3a0b2c4 100644
--- a/library/thread.m
+++ b/library/thread.m
@@ -74,15 +74,15 @@
     %
     % spawn_native exposes a low-level implementation detail, so it is more
     % likely to change with the implementation.
     %
     % Rationale: on the low-level C backend Mercury threads are multiplexed
     % onto a limited number of OS threads. A call to a blocking procedure
     % prevents that OS thread from making progress on another Mercury thread.
-    % Some foreign code depends on OS thread-local state so needs to be
+    % Also, some foreign code depends on OS thread-local state so needs to be
     % consistently executed on a dedicated OS thread to be usable.
     %
 :- pred spawn_native(pred(thread, io, io), maybe_error(thread), io, io).
 :- mode spawn_native(pred(in, di, uo) is cc_multi, out, di, uo) is cc_multi.
 
     % yield(IO0, IO) is logically equivalent to (IO = IO0) but
     % operationally, yields the Mercury engine to some other thread
@@ -307,74 +307,96 @@ spawn_context_2(_, Res, "", !IO) :-
     JavaInternal.getThreadPool().submit(task);
     Success = bool.YES;
 ").
 
 %---------------------------------------------------------------------------%
 
 spawn_native(Goal, Res, !IO) :-
-    spawn_native_2(Goal, Success, ThreadId, !IO),
+    spawn_native_2(Goal, Success, ThreadId, ErrorMsg, !IO),
     (
         Success = yes,
         Res = ok(thread(ThreadId))
     ;
         Success = no,
-        Res = error("Unable to create native thread.")
+        Res = error(ErrorMsg)
     ).
 
-:- pred spawn_native_2(pred(thread, io, io), bool, thread_id, io, io).
-:- mode spawn_native_2(pred(in, di, uo) is cc_multi, out, out, di, uo)
+:- pred spawn_native_2(pred(thread, io, io), bool, thread_id, string, io, io).
+:- mode spawn_native_2(pred(in, di, uo) is cc_multi, out, out, out, di, uo)
     is cc_multi.
 
 :- pragma foreign_proc("C",
     spawn_native_2(Goal::(pred(in, di, uo) is cc_multi), Success::out,
-        ThreadId::out, _IO0::di, _IO::uo),
+        ThreadId::out, ErrorMsg::out, _IO0::di, _IO::uo),
     [promise_pure, will_not_call_mercury, thread_safe, tabled_for_io,
         may_not_duplicate],
 "
-    ThreadId = MR_make_string_const("""");
 #ifdef MR_THREAD_SAFE
-    Success = ML_create_exclusive_thread(Goal, &ThreadId);
+    Success = ML_create_exclusive_thread(Goal, &ThreadId, &ErrorMsg,
+        MR_ALLOC_ID);
 #else
     Success = MR_FALSE;
+    ThreadId = MR_make_string_const("""");
+    ErrorMsg = MR_make_string_const(
+        ""Cannot create native thread in this grade."");
 #endif
 ").
 
 :- pragma foreign_proc("C#",
     spawn_native_2(Goal::(pred(in, di, uo) is cc_multi), Success::out,
-        ThreadId::out, _IO0::di, _IO::uo),
+        ThreadId::out, ErrorMsg::out, _IO0::di, _IO::uo),
     [promise_pure, will_not_call_mercury, thread_safe, tabled_for_io,
         may_not_duplicate],
 "
     try {
         object[] thread_locals = runtime.ThreadLocalMutables.clone();
         MercuryThread mt = new MercuryThread(Goal, thread_locals);
         System.Threading.Thread thread = new System.Threading.Thread(
             new System.Threading.ThreadStart(mt.run));
         ThreadId = thread.ManagedThreadId.ToString();
         mt.setThreadId(ThreadId);
         thread.Start();
         Success = mr_bool.YES;
+        ErrorMsg = """";
     } catch (System.Threading.ThreadStartException e) {
         Success = mr_bool.NO;
         ThreadId = """";
+        ErrorMsg = e.Message;
+    } catch (System.SystemException e) {
+        // Seen with mono.
+        Success = mr_bool.NO;
+        ThreadId = """";
+        ErrorMsg = e.Message;
     }
 ").
 
 :- pragma foreign_proc("Java",
     spawn_native_2(Goal::(pred(in, di, uo) is cc_multi), Success::out,
-        ThreadId::out, _IO0::di, _IO::uo),
+        ThreadId::out, ErrorMsg::out, _IO0::di, _IO::uo),
     [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);
     ThreadId = String.valueOf(task.getId());
     rg.setId(ThreadId);
-    JavaInternal.getThreadPool().submitExclusiveThread(task);
-    Success = bool.YES;
+    try {
+        JavaInternal.getThreadPool().submitExclusiveThread(task);
+        Success = bool.YES;
+        ErrorMsg = """";
+    } catch (java.lang.SecurityException e) {
+        Success = bool.NO;
+        ErrorMsg = e.getMessage();
+    } catch (java.lang.OutOfMemoryError e) {
+        Success = bool.NO;
+        ErrorMsg = e.getMessage();
+    }
+    if (Success == bool.NO && ErrorMsg == null) {
+        ErrorMsg = ""unable to create new native thread"";
+    }
 ").
 
 %---------------------------------------------------------------------------%
 
 :- pragma no_inline(yield/2).
 :- pragma foreign_proc("C",
     yield(_IO0::di, _IO::uo),
@@ -510,15 +532,16 @@ INIT mercury_sys_init_thread_modules
 %
 
 :- pragma foreign_decl("C", local, "
 #if defined(MR_THREAD_SAFE)
   #include  <pthread.h>
 
   static MR_bool ML_create_exclusive_thread(MR_Word goal,
-                    MR_String *thread_id);
+                    MR_String *thread_id, MR_String *error_msg,
+                    MR_AllocSiteInfoPtr alloc_id);
   static void   *ML_exclusive_thread_wrapper(void *arg);
 
   typedef struct ML_ThreadWrapperArgs ML_ThreadWrapperArgs;
   struct ML_ThreadWrapperArgs {
         MercuryLock         mutex;
         MercuryCond         cond;
         MR_Word             goal;
@@ -534,20 +557,27 @@ INIT mercury_sys_init_thread_modules
   };
 
 #endif /* MR_THREAD_SAFE */
 ").
 
 :- pragma foreign_code("C", "
 #if defined(MR_THREAD_SAFE)
-  static MR_bool ML_create_exclusive_thread(MR_Word goal, MR_String *thread_id)
+  static MR_bool
+  ML_create_exclusive_thread(MR_Word goal, MR_String *thread_id,
+        MR_String *error_msg, MR_AllocSiteInfoPtr alloc_id)
   {
     ML_ThreadWrapperArgs    args;
     pthread_t               thread;
     pthread_attr_t          attrs;
     int                     thread_err;
+    int                     thread_errno;
+    char                    errbuf[MR_STRERROR_BUF_SIZE];
+
+    *thread_id = MR_make_string_const("""");
+    *error_msg = MR_make_string_const("""");
 
     ML_incr_thread_barrier_count();
 
     /*
     ** The obvious synchronisation object to use here is a semaphore but
     ** glibc < 2.21 had a bug which could result in sem_post reading from a
     ** semaphore after (in another thread) sem_wait returns and destroys the
@@ -559,33 +589,40 @@ INIT mercury_sys_init_thread_modules
     args.thread_local_mutables =
         MR_clone_thread_local_mutables(MR_THREAD_LOCAL_MUTABLES);
     args.thread_state = ML_THREAD_NOT_READY;
     args.thread_id = NULL;
 
     pthread_attr_init(&attrs);
     pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
-    thread_err =
-        pthread_create(&thread, &attrs, ML_exclusive_thread_wrapper, &args);
+    thread_err = pthread_create(&thread, &attrs, ML_exclusive_thread_wrapper,
+        &args);
+    thread_errno = errno;
     pthread_attr_destroy(&attrs);
 
-    if (thread_err == 0) {
+    if (thread_err != 0) {
+        *error_msg = MR_make_string(alloc_id, ""pthread_create failed: %s"",
+          MR_strerror(thread_errno, errbuf, sizeof(errbuf)));
+    } else {
         MR_LOCK(&args.mutex, ""ML_create_exclusive_thread"");
         while (args.thread_state == ML_THREAD_NOT_READY) {
-            int cond_err;
-
-            cond_err = MR_COND_WAIT(&args.cond, &args.mutex,
+            int cond_err = MR_COND_WAIT(&args.cond, &args.mutex,
                 ""ML_create_exclusive_thread"");
             /* EINTR should not be possible but it has happened before. */
             if (cond_err != 0 && errno != EINTR) {
                 MR_fatal_error(
                     ""ML_create_exclusive_thread: MR_COND_WAIT error %d"",
                     errno);
             }
         }
         MR_UNLOCK(&args.mutex, ""ML_create_exclusive_thread"");
+
+        if (args.thread_state == ML_THREAD_START_ERROR) {
+            *error_msg =
+                MR_make_string_const(""Error setting up engine for thread."");
+        }
     }
 
     pthread_cond_destroy(&args.cond);
     pthread_mutex_destroy(&args.mutex);
 
     if (args.thread_state == ML_THREAD_READY) {
         *thread_id = args.thread_id;
-- 
2.17.0



More information about the reviews mailing list