mirror of
https://github.com/idris-lang/Idris2.git
synced 2024-12-29 14:44:03 +03:00
Make System.Signal async-signal-safe
It is not safe to call `malloc` or `pthread_mutex_lock` from signal handler.
This commit is contained in:
parent
a07d42ac91
commit
ed40b212b2
@ -193,12 +193,8 @@ defaultSignal sig = do
|
||||
||| of your program to retrieve (and mark as handled) the next
|
||||
||| signal that was collected, if any.
|
||||
|||
|
||||
||| Multiple signals will be collected and can then be retrieved
|
||||
||| in the order they were received by multiple calls to
|
||||
||| `handleNextCollectedSignal`.
|
||||
|||
|
||||
||| You can call `handleManyCollectedSignals` to get a List of
|
||||
||| pending signals instead of retrieving them one at a time.
|
||||
||| Signals are not queued, so the return order of signals is
|
||||
||| not specified and signals may be deduplicated.
|
||||
export
|
||||
collectSignal : HasIO io => Signal -> io (Either SignalError ())
|
||||
collectSignal sig = do
|
||||
|
@ -1,81 +1,29 @@
|
||||
#include "idris_signal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
HANDLE ghMutex;
|
||||
#else
|
||||
#include <pthread.h>
|
||||
static pthread_mutex_t sig_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
#endif
|
||||
static_assert(ATOMIC_LONG_LOCK_FREE == 2,
|
||||
"when not lock free, atomic functions are not async-signal-safe");
|
||||
|
||||
// ring buffer style storage for collected
|
||||
// signals.
|
||||
static int signal_buf_cap = 0;
|
||||
static int signals_in_buf = 0;
|
||||
static int signal_buf_next_read_idx = 0;
|
||||
static int *signal_buf = NULL;
|
||||
#define N_SIGNALS 32
|
||||
static atomic_long signal_count[N_SIGNALS];
|
||||
|
||||
void _init_buf() {
|
||||
if (signal_buf == NULL) {
|
||||
signal_buf_cap = 10;
|
||||
signal_buf = malloc(sizeof(int) * signal_buf_cap);
|
||||
}
|
||||
}
|
||||
|
||||
// returns truthy or falsey (1 or 0)
|
||||
int _lock() {
|
||||
#ifdef _WIN32
|
||||
if (ghMutex == NULL) {
|
||||
ghMutex = CreateMutex(
|
||||
NULL,
|
||||
FALSE,
|
||||
NULL);
|
||||
void _collect_signal(int signum) {
|
||||
if (signum < 0 || signum >= N_SIGNALS) {
|
||||
abort();
|
||||
}
|
||||
|
||||
DWORD dwWaitResult = WaitForSingleObject(
|
||||
ghMutex,
|
||||
INFINITE);
|
||||
|
||||
switch (dwWaitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
return 1;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
return 0;
|
||||
long prev = atomic_fetch_add(&signal_count[signum], 1);
|
||||
if (prev == LONG_MAX) {
|
||||
// Practically impossible, but better crash explicitly
|
||||
fprintf(stderr, "signal count overflow\n");
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
pthread_mutex_lock(&sig_mutex);
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void _unlock() {
|
||||
#ifdef _WIN32
|
||||
ReleaseMutex(ghMutex);
|
||||
#else
|
||||
pthread_mutex_unlock(&sig_mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void _collect_signal(int signum);
|
||||
|
||||
void _collect_signal_core(int signum) {
|
||||
_init_buf();
|
||||
|
||||
// FIXME: allow for adjusting capacity of signal buffer
|
||||
// instead of ignoring new signals when at capacity.
|
||||
if (signals_in_buf == signal_buf_cap) {
|
||||
return;
|
||||
}
|
||||
|
||||
int write_idx = (signal_buf_next_read_idx + signals_in_buf) % signal_buf_cap;
|
||||
signal_buf[write_idx] = signum;
|
||||
signals_in_buf += 1;
|
||||
|
||||
#ifdef _WIN32
|
||||
//re-instate signal handler
|
||||
@ -83,13 +31,6 @@ void _collect_signal_core(int signum) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void _collect_signal(int signum) {
|
||||
if (_lock()) {
|
||||
_collect_signal_core(signum);
|
||||
_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
static inline struct sigaction _simple_handler(void (*handler)(int)) {
|
||||
struct sigaction new_action;
|
||||
@ -130,16 +71,21 @@ int collect_signal(int signum) {
|
||||
}
|
||||
|
||||
int handle_next_collected_signal() {
|
||||
if (_lock()) {
|
||||
if (signals_in_buf == 0) {
|
||||
_unlock();
|
||||
return -1;
|
||||
for (int signum = 0; signum != N_SIGNALS; ++signum) {
|
||||
for (;;) {
|
||||
long count = atomic_load(&signal_count[signum]);
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
if (count < 0) {
|
||||
// Practically impossible, but better crash explicitly
|
||||
fprintf(stderr, "signal count overflow\n");
|
||||
abort();
|
||||
}
|
||||
if (atomic_compare_exchange_strong(&signal_count[signum], &count, count - 1)) {
|
||||
return signum;
|
||||
}
|
||||
}
|
||||
int next = signal_buf[signal_buf_next_read_idx];
|
||||
signal_buf_next_read_idx = (signal_buf_next_read_idx + 1) % signal_buf_cap;
|
||||
signals_in_buf -= 1;
|
||||
_unlock();
|
||||
return next;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@ -150,6 +96,7 @@ int raise_signal(int signum) {
|
||||
|
||||
int send_signal(int pid, int signum) {
|
||||
#ifdef _WIN32
|
||||
// TODO: ignores pid
|
||||
return raise_signal(signum);
|
||||
#else
|
||||
return kill(pid, signum);
|
||||
|
Loading…
Reference in New Issue
Block a user