Implement Lisp threading on Android

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.
This commit is contained in:
Po Lu 2024-02-06 17:52:33 +08:00
parent 0d2b712078
commit 42db7292c3
6 changed files with 117 additions and 10 deletions

View 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.

View 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 *);

View 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;

View 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

View 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);
}

View 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;