]> git.eshelyaron.com Git - emacs.git/commitdiff
Implement Lisp threading on Android
authorPo Lu <luangruo@yahoo.com>
Tue, 6 Feb 2024 09:52:33 +0000 (17:52 +0800)
committerEshel Yaron <me@eshelyaron.com>
Wed, 7 Feb 2024 10:54:37 +0000 (11:54 +0100)
Much like the NS port, only the main thread receives input from
the user interface, which is fortunately not a major problem for
packages such as lsp-mode that create Lisp threads.

* configure.ac: Enable with_threads under Android.

* src/android.c (android_init_events): Set `main_thread_id' to
the ID of the main thread.
(setEmacsParams): Set new global variable `android_jvm' to the
JVM object, for the purpose of attaching Lisp threads to the
JVM.
(android_select): [THREADS_ENABLED]: If the caller isn't the
main thread, resort to pselect.  Don't check query before select
returns.
(android_check_query): Export.

* src/android.h (_ANDROID_H_): Define new macro and update
prototypes.

* src/process.c (android_select_wrapper): New function.
(wait_reading_process_output): If THREADS_ENABLED, call
thread_select through the Android select wrapper.

* src/thread.c (post_acquire_global_lock): Call
android_check_query; replace android_java_env with the incoming
Lisp thread's.
(run_thread): Attach and detach the thread created to the JVM.
(init_threads): Set the main thread's JNI environment object.

* src/thread.h (struct thread_state) <java_env>: New field.

(cherry picked from commit 42db7292c3e05920bc9f2fa5c3478eb2ba835c5c)

configure.ac
src/android.c
src/android.h
src/process.c
src/thread.c
src/thread.h

index 4249e663c7b501f45e7f5d913dfadf80186d6f2c..13c27551c7f3c33ca8f02858afef9f5cdad23e26 100644 (file)
@@ -1231,6 +1231,7 @@ package will likely install on older systems but crash on startup.])
   passthrough="$passthrough --with-mailutils=$with_mailutils"
   passthrough="$passthrough --with-pop=$with_pop"
   passthrough="$passthrough --with-harfbuzz=$with_harfbuzz"
+  passthrough="$passthrough --with-threads=$with_png"
 
   # Now pass through some checking options.
   emacs_val="--enable-check-lisp-object-type=$enable_check_lisp_object_type"
@@ -1321,6 +1322,7 @@ if test "$ANDROID" = "yes"; then
     with_pop=no
     with_harfbuzz=no
     with_native_compilation=no
+    with_threads=no
   fi
 
   with_rsvg=no
@@ -1331,7 +1333,6 @@ if test "$ANDROID" = "yes"; then
   with_gpm=no
   with_dbus=no
   with_gsettings=no
-  with_threads=no
   with_ns=no
 
   # zlib is available in android.
index 2c0e4f845f4ce2a7a79ec348dff1d19bd39e83e1..46f4dcd554660d0d43f98beb9e18c8cb52b65b25 100644 (file)
@@ -40,6 +40,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include <sys/param.h>
 #include <sys/stat.h>
+#include <sys/select.h>
 
 /* Old NDK versions lack MIN and MAX.  */
 #include <minmax.h>
@@ -152,6 +153,13 @@ static char *android_files_dir;
 /* The Java environment being used for the main thread.  */
 JNIEnv *android_java_env;
 
+#ifdef THREADS_ENABLED
+
+/* The Java VM new threads attach to.  */
+JavaVM *android_jvm;
+
+#endif /* THREADS_ENABLED */
+
 /* The EmacsGC class.  */
 static jclass emacs_gc_class;
 
@@ -496,6 +504,9 @@ android_handle_sigusr1 (int sig, siginfo_t *siginfo, void *arg)
    This should ideally be defined further down.  */
 static sem_t android_query_sem;
 
+/* ID of the Emacs thread.  */
+static pthread_t main_thread_id;
+
 /* Set up the global event queue by initializing the mutex and two
    condition variables, and the linked list of events.  This must be
    called before starting the Emacs thread.  Also, initialize the
@@ -531,6 +542,8 @@ android_init_events (void)
   event_queue.events.next = &event_queue.events;
   event_queue.events.last = &event_queue.events;
 
+  main_thread_id = pthread_self ();
+
 #if __ANDROID_API__ >= 16
 
   /* Before starting the select thread, make sure the disposition for
@@ -579,10 +592,6 @@ android_pending (void)
   return i;
 }
 
-/* Forward declaration.  */
-
-static void android_check_query (void);
-
 /* Wait for events to become available synchronously.  Return once an
    event arrives.  Also, reply to the UI thread whenever it requires a
    response.  */
@@ -732,6 +741,12 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds,
   static char byte;
 #endif
 
+#ifdef THREADS_ENABLED
+  if (!pthread_equal (pthread_self (), main_thread_id))
+    return pselect (nfds, readfds, writefds, exceptfds, timeout,
+                   NULL);
+#endif /* THREADS_ENABLED */
+
   /* Since Emacs is reading keyboard input again, signify that queries
      from input methods are no longer ``urgent''.  */
 
@@ -837,9 +852,11 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds,
   if (nfds_return < 0)
     errno = EINTR;
 
+#ifndef THREADS_ENABLED
   /* Now check for and run anything the UI thread wants to run in the
      main thread.  */
   android_check_query ();
+#endif /* THREADS_ENABLED */
 
   return nfds_return;
 }
@@ -1315,12 +1332,17 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
   const char *java_string;
   struct stat statb;
 
+#ifdef THREADS_ENABLED
+  /* Save the Java VM.  */
+  if ((*env)->GetJavaVM (env, &android_jvm))
+    emacs_abort ();
+#endif /* THREADS_ENABLED */
+
   /* Set the Android API level early, as it is used by
      `android_vfs_init'.  */
   android_api_level = api_level;
 
   /* This function should only be called from the main thread.  */
-
   android_pixel_density_x = pixel_density_x;
   android_pixel_density_y = pixel_density_y;
   android_scaled_pixel_density = scaled_density;
@@ -6717,7 +6739,7 @@ static void *android_query_context;
 /* Run any function that the UI thread has asked to run, and then
    signal its completion.  */
 
-static void
+void
 android_check_query (void)
 {
   void (*proc) (void *);
index bd19c4d9ac8afe05a5dae7326009f9f4cdb382c1..e1834cebf68b0514ace5e895cf454ae02526b3ff 100644 (file)
@@ -24,6 +24,8 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    a table of function pointers.  */
 
 #ifndef _ANDROID_H_
+#define _ANDROID_H_
+
 #ifndef ANDROID_STUBIFY
 #include <jni.h>
 #include <pwd.h>
@@ -226,6 +228,7 @@ extern void android_display_toast (const char *);
 
 /* Event loop functions.  */
 
+extern void android_check_query (void);
 extern void android_check_query_urgent (void);
 extern int android_run_in_emacs_thread (void (*) (void *), void *);
 extern void android_write_event (union android_event *);
@@ -299,6 +302,10 @@ struct android_emacs_service
 
 extern JNIEnv *android_java_env;
 
+#ifdef THREADS_ENABLED
+extern JavaVM *android_jvm;
+#endif /* THREADS_ENABLED */
+
 /* The EmacsService object.  */
 extern jobject emacs_service;
 
index ddab9ed6c01861733f00a73be4a8b1ac9083b5dd..48a2c0c8e53fa96b52558a0917a2d6eb04922300 100644 (file)
@@ -5209,6 +5209,27 @@ wait_reading_process_output_1 (void)
 {
 }
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY   \
+  && defined THREADS_ENABLED
+
+/* Wrapper around `android_select' that exposes a calling interface with
+   an extra argument for compatibility with `thread_pselect'.  */
+
+static int
+android_select_wrapper (int nfds, fd_set *readfds, fd_set *writefds,
+                       fd_set *exceptfds, const struct timespec *timeout,
+                       const sigset_t *sigmask)
+{
+  /* sigmask is not supported.  */
+  if (sigmask)
+    emacs_abort ();
+
+  return android_select (nfds, readfds, writefds, exceptfds,
+                        (struct timespec *) timeout);
+}
+
+#endif /* HAVE_ANDROID && !ANDROID_STUBIFY && THREADS_ENABLED */
+
 /* Read and dispose of subprocess output while waiting for timeout to
    elapse and/or keyboard input to be available.
 
@@ -5701,13 +5722,19 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd,
            timeout = short_timeout;
 #endif
 
-         /* Android doesn't support threads and requires using a
-            replacement for pselect in android.c to poll for
-            events.  */
+         /* Android requires using a replacement for pselect in
+            android.c to poll for events.  */
 #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+#ifndef THREADS_ENABLED
          nfds = android_select (max_desc + 1,
                                 &Available, (check_write ? &Writeok : 0),
                                 NULL, &timeout);
+#else /* THREADS_ENABLED */
+         nfds = thread_select (android_select_wrapper,
+                               max_desc + 1,
+                               &Available, (check_write ? &Writeok : 0),
+                               NULL, &timeout, NULL);
+#endif /* THREADS_ENABLED */
 #else
 
          /* Non-macOS HAVE_GLIB builds call thread_select in
index 040ca39511ea6f59aeded4129add0af246038291..2f5d7a088382684e69e420ec7da1e3da7abbb81b 100644 (file)
@@ -106,6 +106,12 @@ post_acquire_global_lock (struct thread_state *self)
 {
   struct thread_state *prev_thread = current_thread;
 
+  /* Switch the JNI interface pointer to the environment assigned to the
+     current thread.  */
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  android_java_env = self->java_env;
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
   /* Do this early on, so that code below could signal errors (e.g.,
      unbind_for_thread_switch might) correctly, because we are already
      running in the context of the thread pointed by SELF.  */
@@ -126,6 +132,12 @@ post_acquire_global_lock (struct thread_state *self)
       set_buffer_internal_2 (current_buffer);
     }
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* This step is performed in android_select when built without
+     threads.  */
+  android_check_query ();
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
    /* We could have been signaled while waiting to grab the global lock
       for the first time since this thread was created, in which case
       we didn't yet have the opportunity to set up the handlers.  Delay
@@ -756,6 +768,11 @@ run_thread (void *state)
 
   struct thread_state *self = state;
   struct thread_state **iter;
+#ifdef THREADS_ENABLED
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  jint rc;
+#endif /* #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+#endif /* THREADS_ENABLED */
 
 #ifdef HAVE_NS
   /* Allocate an autorelease pool in case this thread calls any
@@ -766,6 +783,16 @@ run_thread (void *state)
   void *pool = ns_alloc_autorelease_pool ();
 #endif
 
+#ifdef THREADS_ENABLED
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  rc
+    = (*android_jvm)->AttachCurrentThread (android_jvm, &self->java_env,
+                                          NULL);
+  if (rc != JNI_OK)
+    emacs_abort ();
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+#endif /* THREADS_ENABLED */
+
   self->m_stack_bottom = self->stack_top = &stack_pos.c;
   self->thread_id = sys_thread_self ();
 
@@ -812,6 +839,14 @@ run_thread (void *state)
   ns_release_autorelease_pool (pool);
 #endif
 
+#ifdef THREADS_ENABLED
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  rc = (*android_jvm)->DetachCurrentThread (android_jvm);
+  if (rc != JNI_OK)
+    emacs_abort ();
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+#endif /* THREADS_ENABLED */
+
   /* Unlink this thread from the list of all threads.  Note that we
      have to do this very late, after broadcasting our death.
      Otherwise the GC may decide to reap the thread_state object,
@@ -1131,6 +1166,10 @@ init_threads (void)
   sys_mutex_init (&global_lock);
   sys_mutex_lock (&global_lock);
   current_thread = &main_thread.s;
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  current_thread->java_env = android_java_env;
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
   main_thread.s.thread_id = sys_thread_self ();
   init_bc_thread (&main_thread.s.bc);
 }
index 6ce2b7f30dfb0db6479677286d2cdef32c77439f..1844cf0396778b2b61293e909fe52d8a2099208f 100644 (file)
@@ -30,6 +30,12 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include <signal.h>            /* sigset_t */
 #endif
 
+#ifdef HAVE_ANDROID
+#ifndef ANDROID_STUBIFY
+#include "android.h"
+#endif /* ANDROID_STUBIFY */
+#endif /* HAVE_ANDROID */
+
 #include "sysselect.h"         /* FIXME */
 #include "systhread.h"
 
@@ -84,6 +90,11 @@ struct thread_state
   Lisp_Object event_object;
   /* event_object must be the last Lisp field.  */
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* Pointer to an object to call Java functions through.  */
+  JNIEnv *java_env;
+#endif /* HAVE_ANDROID && !ANDROID_STUBIFY */
+
   /* An address near the bottom of the stack.
      Tells GC how to save a copy of the stack.  */
   char const *m_stack_bottom;