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:
parent
0d2b712078
commit
42db7292c3
6 changed files with 117 additions and 10 deletions
|
@ -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.
|
||||
|
|
|
@ -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 *);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
39
src/thread.c
39
src/thread.c
|
@ -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);
|
||||
}
|
||||
|
|
11
src/thread.h
11
src/thread.h
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue