summaryrefslogtreecommitdiff
path: root/libguile/async.c
diff options
context:
space:
mode:
authorAndy Wingo <wingo@pobox.com>2016-10-26 22:32:51 +0200
committerAndy Wingo <wingo@pobox.com>2016-10-26 22:50:26 +0200
commitc957ec7ab0f0a028910dc737e12191f7bdc1ca93 (patch)
tree5949cfd1de4a7eecfacb35611430498a53b4c2fe /libguile/async.c
parentf3bfe29235199e12b961c3fd1fa92666ad031d0d (diff)
downloadguile-c957ec7ab0f0a028910dc737e12191f7bdc1ca93.tar.gz
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.
Diffstat (limited to 'libguile/async.c')
-rw-r--r--libguile/async.c181
1 files changed, 64 insertions, 117 deletions
diff --git a/libguile/async.c b/libguile/async.c
index 1cf105881..aa4f508b7 100644
--- a/libguile/async.c
+++ b/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.
- */
-
- scm_i_scm_pthread_mutex_lock (&async_mutex);
- t->pending_asyncs = 0;
- if (t->block_asyncs == 0)
- {
- asyncs = t->active_asyncs;
- t->active_asyncs = SCM_EOL;
- }
- else
- asyncs = SCM_EOL;
- scm_i_pthread_mutex_unlock (&async_mutex);
+ if (t->block_asyncs)
+ return;
- while (scm_is_pair (asyncs))
+ asyncs = scm_atomic_swap_scm (&t->pending_asyncs, SCM_EOL);
+ while (!scm_is_null (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