mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-30 03:40:34 +02:00
Use atomics for async interrupts
* libguile/__scm.h (SCM_TICK): Always define as scm_async_tick(). * libguile/error.c (scm_syserror, scm_syserror_msg): * libguile/fports.c (fport_read, fport_write): * libguile/_scm.h (SCM_SYSCALL): Replace SCM_ASYNC_TICK with scm_async_tick (). (SCM_ASYNC_TICK, SCM_ASYNC_TICK_WITH_CODE) (SCM_ASYNC_TICK_WITH_GUARD_CODE): Remove internal definitions. We inline into vm-engine.c, the only place where it matters. * libguile/async.h: * libguile/async.c (scm_async_tick, scm_i_setup_sleep): (scm_i_reset_sleep, scm_system_async_mark_for_thread): * libguile/threads.h (struct scm_thread_wake_data): * libguile/threads.h (scm_i_thread): * libguile/threads.c (block_self, guilify_self_1, scm_std_select): Rewrite to use sequentially-consistent atomic references. * libguile/atomics-internal.h (scm_atomic_set_pointer): (scm_atomic_ref_pointer): New definitions. * libguile/finalizers.c (queue_finalizer_async): We can allocate, so just use scm_system_async_mark_for_thread instead of the set-cdr! shenanigans. * libguile/scmsigs.c (take_signal): * libguile/gc.c (queue_after_gc_hook): Adapt to new asyncs mechanism. Can't allocate but we're just manipulating the current thread when no other threads are running so we should be good. * libguile/vm-engine.c (VM_HANDLE_INTERRUPTS): Inline the async_tick business.
This commit is contained in:
parent
f3bfe29235
commit
c957ec7ab0
14 changed files with 152 additions and 192 deletions
181
libguile/async.c
181
libguile/async.c
|
@ -24,6 +24,7 @@
|
|||
#endif
|
||||
|
||||
#include "libguile/_scm.h"
|
||||
#include "libguile/atomics-internal.h"
|
||||
#include "libguile/eval.h"
|
||||
#include "libguile/throw.h"
|
||||
#include "libguile/root.h"
|
||||
|
@ -50,142 +51,51 @@
|
|||
*
|
||||
* Each thread has a list of 'activated asyncs', which is a normal
|
||||
* Scheme list of procedures with zero arguments. When a thread
|
||||
* executes a SCM_ASYNC_TICK statement (which is included in SCM_TICK),
|
||||
* it will call all procedures on this list.
|
||||
* executes an scm_async_tick (), it will call all procedures on this
|
||||
* list.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
static scm_i_pthread_mutex_t async_mutex = SCM_I_PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/* System asyncs. */
|
||||
|
||||
void
|
||||
scm_async_tick (void)
|
||||
{
|
||||
scm_i_thread *t = SCM_I_CURRENT_THREAD;
|
||||
SCM asyncs;
|
||||
|
||||
/* Reset pending_asyncs even when asyncs are blocked and not really
|
||||
executed since this will avoid future futile calls to this
|
||||
function. When asyncs are unblocked again, this function is
|
||||
invoked even when pending_asyncs is zero.
|
||||
*/
|
||||
if (t->block_asyncs)
|
||||
return;
|
||||
|
||||
scm_i_scm_pthread_mutex_lock (&async_mutex);
|
||||
t->pending_asyncs = 0;
|
||||
if (t->block_asyncs == 0)
|
||||
asyncs = scm_atomic_swap_scm (&t->pending_asyncs, SCM_EOL);
|
||||
while (!scm_is_null (asyncs))
|
||||
{
|
||||
asyncs = t->active_asyncs;
|
||||
t->active_asyncs = SCM_EOL;
|
||||
}
|
||||
else
|
||||
asyncs = SCM_EOL;
|
||||
scm_i_pthread_mutex_unlock (&async_mutex);
|
||||
|
||||
while (scm_is_pair (asyncs))
|
||||
{
|
||||
SCM next = SCM_CDR (asyncs);
|
||||
SCM_SETCDR (asyncs, SCM_BOOL_F);
|
||||
scm_call_0 (SCM_CAR (asyncs));
|
||||
SCM next = scm_cdr (asyncs);
|
||||
scm_call_0 (scm_car (asyncs));
|
||||
scm_set_cdr_x (asyncs, SCM_BOOL_F);
|
||||
asyncs = next;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
scm_i_queue_async_cell (SCM c, scm_i_thread *t)
|
||||
{
|
||||
SCM sleep_object;
|
||||
scm_i_pthread_mutex_t *sleep_mutex;
|
||||
int sleep_fd;
|
||||
SCM p;
|
||||
|
||||
scm_i_scm_pthread_mutex_lock (&async_mutex);
|
||||
p = t->active_asyncs;
|
||||
SCM_SETCDR (c, SCM_EOL);
|
||||
if (!scm_is_pair (p))
|
||||
t->active_asyncs = c;
|
||||
else
|
||||
{
|
||||
SCM pp;
|
||||
while (scm_is_pair (pp = SCM_CDR (p)))
|
||||
{
|
||||
if (scm_is_eq (SCM_CAR (p), SCM_CAR (c)))
|
||||
{
|
||||
scm_i_pthread_mutex_unlock (&async_mutex);
|
||||
return;
|
||||
}
|
||||
p = pp;
|
||||
}
|
||||
SCM_SETCDR (p, c);
|
||||
}
|
||||
t->pending_asyncs = 1;
|
||||
sleep_object = t->sleep_object;
|
||||
sleep_mutex = t->sleep_mutex;
|
||||
sleep_fd = t->sleep_fd;
|
||||
scm_i_pthread_mutex_unlock (&async_mutex);
|
||||
|
||||
if (sleep_mutex)
|
||||
{
|
||||
/* By now, the thread T might be out of its sleep already, or
|
||||
might even be in the next, unrelated sleep. Interrupting it
|
||||
anyway does no harm, however.
|
||||
|
||||
The important thing to prevent here is to signal sleep_cond
|
||||
before T waits on it. This can not happen since T has
|
||||
sleep_mutex locked while setting t->sleep_mutex and will only
|
||||
unlock it again while waiting on sleep_cond.
|
||||
*/
|
||||
scm_i_scm_pthread_mutex_lock (sleep_mutex);
|
||||
scm_i_pthread_cond_signal (&t->sleep_cond);
|
||||
scm_i_pthread_mutex_unlock (sleep_mutex);
|
||||
}
|
||||
|
||||
if (sleep_fd >= 0)
|
||||
{
|
||||
char dummy = 0;
|
||||
|
||||
/* Likewise, T might already been done with sleeping here, but
|
||||
interrupting it once too often does no harm. T might also
|
||||
not yet have started sleeping, but this is no problem either
|
||||
since the data written to a pipe will not be lost, unlike a
|
||||
condition variable signal. */
|
||||
full_write (sleep_fd, &dummy, 1);
|
||||
}
|
||||
|
||||
/* This is needed to protect sleep_mutex.
|
||||
*/
|
||||
scm_remember_upto_here_1 (sleep_object);
|
||||
}
|
||||
|
||||
int
|
||||
scm_i_setup_sleep (scm_i_thread *t,
|
||||
SCM sleep_object, scm_i_pthread_mutex_t *sleep_mutex,
|
||||
int sleep_fd)
|
||||
{
|
||||
int pending;
|
||||
struct scm_thread_wake_data *wake;
|
||||
|
||||
scm_i_scm_pthread_mutex_lock (&async_mutex);
|
||||
pending = t->pending_asyncs;
|
||||
if (!pending)
|
||||
{
|
||||
t->sleep_object = sleep_object;
|
||||
t->sleep_mutex = sleep_mutex;
|
||||
t->sleep_fd = sleep_fd;
|
||||
}
|
||||
scm_i_pthread_mutex_unlock (&async_mutex);
|
||||
return pending;
|
||||
wake = scm_gc_typed_calloc (struct scm_thread_wake_data);
|
||||
wake->object = sleep_object;
|
||||
wake->mutex = sleep_mutex;
|
||||
wake->fd = sleep_fd;
|
||||
|
||||
scm_atomic_set_pointer ((void **)&t->wake, wake);
|
||||
|
||||
return !scm_is_null (scm_atomic_ref_scm (&t->pending_asyncs));
|
||||
}
|
||||
|
||||
void
|
||||
scm_i_reset_sleep (scm_i_thread *t)
|
||||
{
|
||||
scm_i_scm_pthread_mutex_lock (&async_mutex);
|
||||
t->sleep_object = SCM_BOOL_F;
|
||||
t->sleep_mutex = NULL;
|
||||
t->sleep_fd = -1;
|
||||
scm_i_pthread_mutex_unlock (&async_mutex);
|
||||
scm_atomic_set_pointer ((void **)&t->wake, NULL);
|
||||
}
|
||||
|
||||
SCM_DEFINE (scm_system_async_mark_for_thread, "system-async-mark", 1, 1, 0,
|
||||
|
@ -200,13 +110,9 @@ SCM_DEFINE (scm_system_async_mark_for_thread, "system-async-mark", 1, 1, 0,
|
|||
"signal handlers.")
|
||||
#define FUNC_NAME s_scm_system_async_mark_for_thread
|
||||
{
|
||||
/* The current thread might not have a handle yet. This can happen
|
||||
when the GC runs immediately before allocating the handle. At
|
||||
the end of that GC, a system async might be marked. Thus, we can
|
||||
not use scm_current_thread here.
|
||||
*/
|
||||
|
||||
scm_i_thread *t;
|
||||
SCM asyncs;
|
||||
struct scm_thread_wake_data *wake;
|
||||
|
||||
if (SCM_UNBNDP (thread))
|
||||
t = SCM_I_CURRENT_THREAD;
|
||||
|
@ -217,7 +123,48 @@ SCM_DEFINE (scm_system_async_mark_for_thread, "system-async-mark", 1, 1, 0,
|
|||
SCM_MISC_ERROR ("thread has already exited", SCM_EOL);
|
||||
t = SCM_I_THREAD_DATA (thread);
|
||||
}
|
||||
scm_i_queue_async_cell (scm_cons (proc, SCM_BOOL_F), t);
|
||||
|
||||
asyncs = scm_atomic_ref_scm (&t->pending_asyncs);
|
||||
do
|
||||
if (scm_is_true (scm_c_memq (proc, asyncs)))
|
||||
return SCM_UNSPECIFIED;
|
||||
while (!scm_atomic_compare_and_swap_scm (&t->pending_asyncs, &asyncs,
|
||||
scm_cons (proc, asyncs)));
|
||||
|
||||
/* At this point the async is enqueued. However if the thread is
|
||||
sleeping, we have to wake it up. */
|
||||
if ((wake = scm_atomic_ref_pointer ((void **) &t->wake)))
|
||||
{
|
||||
/* By now, the thread T might be out of its sleep already, or
|
||||
might even be in the next, unrelated sleep. Interrupting it
|
||||
anyway does no harm, however.
|
||||
|
||||
The important thing to prevent here is to signal sleep_cond
|
||||
before T waits on it. This can not happen since T has
|
||||
sleep_mutex locked while setting t->sleep_mutex and will only
|
||||
unlock it again while waiting on sleep_cond.
|
||||
*/
|
||||
scm_i_scm_pthread_mutex_lock (wake->mutex);
|
||||
scm_i_pthread_cond_signal (&t->sleep_cond);
|
||||
scm_i_pthread_mutex_unlock (wake->mutex);
|
||||
|
||||
/* This is needed to protect wake->mutex.
|
||||
*/
|
||||
scm_remember_upto_here_1 (wake->object);
|
||||
|
||||
if (wake->fd >= 0)
|
||||
{
|
||||
char dummy = 0;
|
||||
|
||||
/* Likewise, T might already been done with sleeping here, but
|
||||
interrupting it once too often does no harm. T might also
|
||||
not yet have started sleeping, but this is no problem
|
||||
either since the data written to a pipe will not be lost,
|
||||
unlike a condition variable signal. */
|
||||
full_write (wake->fd, &dummy, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return SCM_UNSPECIFIED;
|
||||
}
|
||||
#undef FUNC_NAME
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue