* src/thread.h (struct thread_state): New member not_holding_lock.
(maybe_reacquire_global_lock): Add prototype.
* src/thread.c: Include syssignal.h.
(maybe_reacquire_global_lock): New function.
(really_call_select): Set the not_holding_lock member of the
thread state before releasing the lock, and rest it after
re-acquiring the lock when the select function returns. Block
SIGINT while doing this to make sure we are not interrupted on TTY
frames.
* src/sysdep.c (block_interrupt_signal, restore_signal_mask): New
functions.
* src/syssignal.h (block_interrupt_signal, restore_signal_mask):
Add prototypes.
* src/keyboard.c (read_char) [THREADS_ENABLED]: Call
maybe_reacquire_global_lock. (Bug#25178)
so restore it now. */
restore_getcjmp (save_jump);
pthread_sigmask (SIG_SETMASK, &empty_mask, 0);
+#if THREADS_ENABLED
+ maybe_reacquire_global_lock ();
+#endif
unbind_to (jmpcount, Qnil);
XSETINT (c, quit_char);
internal_last_event_frame = selected_frame;
pthread_sigmask (SIG_SETMASK, oldset, 0);
}
+/* Block SIGINT. */
+void
+block_interrupt_signal (sigset_t *oldset)
+{
+ sigset_t blocked;
+ sigemptyset (&blocked);
+ sigaddset (&blocked, SIGINT);
+ pthread_sigmask (SIG_BLOCK, &blocked, oldset);
+}
+
+/* Restore previously saved signal mask. */
+void
+restore_signal_mask (sigset_t const *oldset)
+{
+ pthread_sigmask (SIG_SETMASK, oldset, 0);
+}
+
#endif /* !MSDOS */
\f
/* Saving and restoring the process group of Emacs's terminal. */
extern void init_signals (bool);
extern void block_child_signal (sigset_t *);
extern void unblock_child_signal (sigset_t const *);
+extern void block_interrupt_signal (sigset_t *);
+extern void restore_signal_mask (sigset_t const *);
extern void block_tty_out_signal (sigset_t *);
extern void unblock_tty_out_signal (sigset_t const *);
#include "buffer.h"
#include "process.h"
#include "coding.h"
+#include "syssignal.h"
static struct thread_state primary_thread;
post_acquire_global_lock (self);
}
+/* This is called from keyboard.c when it detects that SIGINT
+ interrupted thread_select before the current thread could acquire
+ the lock. We must acquire the lock to prevent a thread from
+ running without holding the global lock, and to avoid repeated
+ calls to sys_mutex_unlock, which invokes undefined behavior. */
+void
+maybe_reacquire_global_lock (void)
+{
+ if (current_thread->not_holding_lock)
+ {
+ struct thread_state *self = current_thread;
+
+ acquire_global_lock (self);
+ current_thread->not_holding_lock = 0;
+ }
+}
+
\f
static void
{
struct select_args *sa = arg;
struct thread_state *self = current_thread;
+ sigset_t oldset;
+ block_interrupt_signal (&oldset);
+ self->not_holding_lock = 1;
release_global_lock ();
+ restore_signal_mask (&oldset);
+
sa->result = (sa->func) (sa->max_fds, sa->rfds, sa->wfds, sa->efds,
sa->timeout, sa->sigmask);
+
+ block_interrupt_signal (&oldset);
acquire_global_lock (self);
+ self->not_holding_lock = 0;
+ restore_signal_mask (&oldset);
}
int
interrupter should broadcast to this condition. */
sys_cond_t *wait_condvar;
+ /* This thread might have released the global lock. If so, this is
+ non-zero. When a thread runs outside thread_select with this
+ flag non-zero, it means it has been interrupted by SIGINT while
+ in thread_select, and didn't have a chance of acquiring the lock.
+ It must do so ASAP. */
+ int not_holding_lock;
+
/* Threads are kept on a linked list. */
struct thread_state *next_thread;
};
extern void finalize_one_thread (struct thread_state *state);
extern void finalize_one_mutex (struct Lisp_Mutex *);
extern void finalize_one_condvar (struct Lisp_CondVar *);
+extern void maybe_reacquire_global_lock (void);
extern void init_threads_once (void);
extern void init_threads (void);