f53df7da64
Code submissions have continually suffered from formatting inconsistencies that constantly have to be addressed. Using clang-format simplifies this by making code formatting more consistent, and allows automation of the code formatting so that maintainers can focus more on the code itself instead of code formatting.
427 lines
10 KiB
C
427 lines
10 KiB
C
/*
|
|
* Copyright (c) 2013-2014 Hugh Bailey <obs.jim@gmail.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include "../util/darray.h"
|
|
#include "../util/threading.h"
|
|
|
|
#include "decl.h"
|
|
#include "signal.h"
|
|
|
|
struct signal_callback {
|
|
signal_callback_t callback;
|
|
void *data;
|
|
bool remove;
|
|
bool keep_ref;
|
|
};
|
|
|
|
struct signal_info {
|
|
struct decl_info func;
|
|
DARRAY(struct signal_callback) callbacks;
|
|
pthread_mutex_t mutex;
|
|
bool signalling;
|
|
|
|
struct signal_info *next;
|
|
};
|
|
|
|
static inline struct signal_info *signal_info_create(struct decl_info *info)
|
|
{
|
|
pthread_mutexattr_t attr;
|
|
struct signal_info *si;
|
|
|
|
if (pthread_mutexattr_init(&attr) != 0)
|
|
return NULL;
|
|
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
|
|
return NULL;
|
|
|
|
si = bmalloc(sizeof(struct signal_info));
|
|
|
|
si->func = *info;
|
|
si->next = NULL;
|
|
si->signalling = false;
|
|
da_init(si->callbacks);
|
|
|
|
if (pthread_mutex_init(&si->mutex, &attr) != 0) {
|
|
blog(LOG_ERROR, "Could not create signal");
|
|
|
|
decl_info_free(&si->func);
|
|
bfree(si);
|
|
return NULL;
|
|
}
|
|
|
|
return si;
|
|
}
|
|
|
|
static inline void signal_info_destroy(struct signal_info *si)
|
|
{
|
|
if (si) {
|
|
pthread_mutex_destroy(&si->mutex);
|
|
decl_info_free(&si->func);
|
|
da_free(si->callbacks);
|
|
bfree(si);
|
|
}
|
|
}
|
|
|
|
static inline size_t signal_get_callback_idx(struct signal_info *si,
|
|
signal_callback_t callback,
|
|
void *data)
|
|
{
|
|
for (size_t i = 0; i < si->callbacks.num; i++) {
|
|
struct signal_callback *sc = si->callbacks.array + i;
|
|
|
|
if (sc->callback == callback && sc->data == data)
|
|
return i;
|
|
}
|
|
|
|
return DARRAY_INVALID;
|
|
}
|
|
|
|
struct global_callback_info {
|
|
global_signal_callback_t callback;
|
|
void *data;
|
|
long signaling;
|
|
bool remove;
|
|
};
|
|
|
|
struct signal_handler {
|
|
struct signal_info *first;
|
|
pthread_mutex_t mutex;
|
|
volatile long refs;
|
|
|
|
DARRAY(struct global_callback_info) global_callbacks;
|
|
pthread_mutex_t global_callbacks_mutex;
|
|
};
|
|
|
|
static struct signal_info *getsignal(signal_handler_t *handler,
|
|
const char *name,
|
|
struct signal_info **p_last)
|
|
{
|
|
struct signal_info *signal, *last = NULL;
|
|
|
|
signal = handler->first;
|
|
while (signal != NULL) {
|
|
if (strcmp(signal->func.name, name) == 0)
|
|
break;
|
|
|
|
last = signal;
|
|
signal = signal->next;
|
|
}
|
|
|
|
if (p_last)
|
|
*p_last = last;
|
|
return signal;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
signal_handler_t *signal_handler_create(void)
|
|
{
|
|
struct signal_handler *handler = bzalloc(sizeof(struct signal_handler));
|
|
handler->first = NULL;
|
|
handler->refs = 1;
|
|
|
|
pthread_mutexattr_t attr;
|
|
if (pthread_mutexattr_init(&attr) != 0)
|
|
return NULL;
|
|
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
|
|
return NULL;
|
|
|
|
if (pthread_mutex_init(&handler->mutex, NULL) != 0) {
|
|
blog(LOG_ERROR, "Couldn't create signal handler mutex!");
|
|
bfree(handler);
|
|
return NULL;
|
|
}
|
|
if (pthread_mutex_init(&handler->global_callbacks_mutex, &attr) != 0) {
|
|
blog(LOG_ERROR, "Couldn't create signal handler global "
|
|
"callbacks mutex!");
|
|
pthread_mutex_destroy(&handler->mutex);
|
|
bfree(handler);
|
|
return NULL;
|
|
}
|
|
|
|
return handler;
|
|
}
|
|
|
|
static void signal_handler_actually_destroy(signal_handler_t *handler)
|
|
{
|
|
struct signal_info *sig = handler->first;
|
|
while (sig != NULL) {
|
|
struct signal_info *next = sig->next;
|
|
signal_info_destroy(sig);
|
|
sig = next;
|
|
}
|
|
|
|
da_free(handler->global_callbacks);
|
|
pthread_mutex_destroy(&handler->global_callbacks_mutex);
|
|
pthread_mutex_destroy(&handler->mutex);
|
|
bfree(handler);
|
|
}
|
|
|
|
void signal_handler_destroy(signal_handler_t *handler)
|
|
{
|
|
if (handler && os_atomic_dec_long(&handler->refs) == 0) {
|
|
signal_handler_actually_destroy(handler);
|
|
}
|
|
}
|
|
|
|
bool signal_handler_add(signal_handler_t *handler, const char *signal_decl)
|
|
{
|
|
struct decl_info func = {0};
|
|
struct signal_info *sig, *last;
|
|
bool success = true;
|
|
|
|
if (!parse_decl_string(&func, signal_decl)) {
|
|
blog(LOG_ERROR, "Signal declaration invalid: %s", signal_decl);
|
|
return false;
|
|
}
|
|
|
|
pthread_mutex_lock(&handler->mutex);
|
|
|
|
sig = getsignal(handler, func.name, &last);
|
|
if (sig) {
|
|
blog(LOG_WARNING, "Signal declaration '%s' exists", func.name);
|
|
decl_info_free(&func);
|
|
success = false;
|
|
} else {
|
|
sig = signal_info_create(&func);
|
|
if (!last)
|
|
handler->first = sig;
|
|
else
|
|
last->next = sig;
|
|
}
|
|
|
|
pthread_mutex_unlock(&handler->mutex);
|
|
|
|
return success;
|
|
}
|
|
|
|
static void signal_handler_connect_internal(signal_handler_t *handler,
|
|
const char *signal,
|
|
signal_callback_t callback,
|
|
void *data, bool keep_ref)
|
|
{
|
|
struct signal_info *sig, *last;
|
|
struct signal_callback cb_data = {callback, data, false, keep_ref};
|
|
size_t idx;
|
|
|
|
if (!handler)
|
|
return;
|
|
|
|
pthread_mutex_lock(&handler->mutex);
|
|
sig = getsignal(handler, signal, &last);
|
|
pthread_mutex_unlock(&handler->mutex);
|
|
|
|
if (!sig) {
|
|
blog(LOG_WARNING,
|
|
"signal_handler_connect: "
|
|
"signal '%s' not found",
|
|
signal);
|
|
return;
|
|
}
|
|
|
|
/* -------------- */
|
|
|
|
pthread_mutex_lock(&sig->mutex);
|
|
|
|
if (keep_ref)
|
|
os_atomic_inc_long(&handler->refs);
|
|
|
|
idx = signal_get_callback_idx(sig, callback, data);
|
|
if (keep_ref || idx == DARRAY_INVALID)
|
|
da_push_back(sig->callbacks, &cb_data);
|
|
|
|
pthread_mutex_unlock(&sig->mutex);
|
|
}
|
|
|
|
void signal_handler_connect(signal_handler_t *handler, const char *signal,
|
|
signal_callback_t callback, void *data)
|
|
{
|
|
signal_handler_connect_internal(handler, signal, callback, data, false);
|
|
}
|
|
|
|
void signal_handler_connect_ref(signal_handler_t *handler, const char *signal,
|
|
signal_callback_t callback, void *data)
|
|
{
|
|
signal_handler_connect_internal(handler, signal, callback, data, true);
|
|
}
|
|
|
|
static inline struct signal_info *getsignal_locked(signal_handler_t *handler,
|
|
const char *name)
|
|
{
|
|
struct signal_info *sig;
|
|
|
|
if (!handler)
|
|
return NULL;
|
|
|
|
pthread_mutex_lock(&handler->mutex);
|
|
sig = getsignal(handler, name, NULL);
|
|
pthread_mutex_unlock(&handler->mutex);
|
|
|
|
return sig;
|
|
}
|
|
|
|
void signal_handler_disconnect(signal_handler_t *handler, const char *signal,
|
|
signal_callback_t callback, void *data)
|
|
{
|
|
struct signal_info *sig = getsignal_locked(handler, signal);
|
|
bool keep_ref = false;
|
|
size_t idx;
|
|
|
|
if (!sig)
|
|
return;
|
|
|
|
pthread_mutex_lock(&sig->mutex);
|
|
|
|
idx = signal_get_callback_idx(sig, callback, data);
|
|
if (idx != DARRAY_INVALID) {
|
|
if (sig->signalling) {
|
|
sig->callbacks.array[idx].remove = true;
|
|
} else {
|
|
keep_ref = sig->callbacks.array[idx].keep_ref;
|
|
da_erase(sig->callbacks, idx);
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&sig->mutex);
|
|
|
|
if (keep_ref && os_atomic_dec_long(&handler->refs) == 0) {
|
|
signal_handler_actually_destroy(handler);
|
|
}
|
|
}
|
|
|
|
static THREAD_LOCAL struct signal_callback *current_signal_cb = NULL;
|
|
static THREAD_LOCAL struct global_callback_info *current_global_cb = NULL;
|
|
|
|
void signal_handler_remove_current(void)
|
|
{
|
|
if (current_signal_cb)
|
|
current_signal_cb->remove = true;
|
|
else if (current_global_cb)
|
|
current_global_cb->remove = true;
|
|
}
|
|
|
|
void signal_handler_signal(signal_handler_t *handler, const char *signal,
|
|
calldata_t *params)
|
|
{
|
|
struct signal_info *sig = getsignal_locked(handler, signal);
|
|
long remove_refs = 0;
|
|
|
|
if (!sig)
|
|
return;
|
|
|
|
pthread_mutex_lock(&sig->mutex);
|
|
sig->signalling = true;
|
|
|
|
for (size_t i = 0; i < sig->callbacks.num; i++) {
|
|
struct signal_callback *cb = sig->callbacks.array + i;
|
|
if (!cb->remove) {
|
|
current_signal_cb = cb;
|
|
cb->callback(cb->data, params);
|
|
current_signal_cb = NULL;
|
|
}
|
|
}
|
|
|
|
for (size_t i = sig->callbacks.num; i > 0; i--) {
|
|
struct signal_callback *cb = sig->callbacks.array + i - 1;
|
|
if (cb->remove) {
|
|
if (cb->keep_ref)
|
|
remove_refs++;
|
|
|
|
da_erase(sig->callbacks, i - 1);
|
|
}
|
|
}
|
|
|
|
sig->signalling = false;
|
|
pthread_mutex_unlock(&sig->mutex);
|
|
|
|
pthread_mutex_lock(&handler->global_callbacks_mutex);
|
|
|
|
if (handler->global_callbacks.num) {
|
|
for (size_t i = 0; i < handler->global_callbacks.num; i++) {
|
|
struct global_callback_info *cb =
|
|
handler->global_callbacks.array + i;
|
|
|
|
if (!cb->remove) {
|
|
cb->signaling++;
|
|
current_global_cb = cb;
|
|
cb->callback(cb->data, signal, params);
|
|
current_global_cb = NULL;
|
|
cb->signaling--;
|
|
}
|
|
}
|
|
|
|
for (size_t i = handler->global_callbacks.num; i > 0; i--) {
|
|
struct global_callback_info *cb =
|
|
handler->global_callbacks.array + (i - 1);
|
|
|
|
if (cb->remove && !cb->signaling)
|
|
da_erase(handler->global_callbacks, i - 1);
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&handler->global_callbacks_mutex);
|
|
|
|
if (remove_refs) {
|
|
os_atomic_set_long(&handler->refs,
|
|
os_atomic_load_long(&handler->refs) -
|
|
remove_refs);
|
|
}
|
|
}
|
|
|
|
void signal_handler_connect_global(signal_handler_t *handler,
|
|
global_signal_callback_t callback,
|
|
void *data)
|
|
{
|
|
struct global_callback_info cb_data = {callback, data, 0, false};
|
|
size_t idx;
|
|
|
|
if (!handler || !callback)
|
|
return;
|
|
|
|
pthread_mutex_lock(&handler->global_callbacks_mutex);
|
|
|
|
idx = da_find(handler->global_callbacks, &cb_data, 0);
|
|
if (idx == DARRAY_INVALID)
|
|
da_push_back(handler->global_callbacks, &cb_data);
|
|
|
|
pthread_mutex_unlock(&handler->global_callbacks_mutex);
|
|
}
|
|
|
|
void signal_handler_disconnect_global(signal_handler_t *handler,
|
|
global_signal_callback_t callback,
|
|
void *data)
|
|
{
|
|
struct global_callback_info cb_data = {callback, data, false};
|
|
size_t idx;
|
|
|
|
if (!handler || !callback)
|
|
return;
|
|
|
|
pthread_mutex_lock(&handler->global_callbacks_mutex);
|
|
|
|
idx = da_find(handler->global_callbacks, &cb_data, 0);
|
|
if (idx != DARRAY_INVALID) {
|
|
struct global_callback_info *cb =
|
|
handler->global_callbacks.array + idx;
|
|
|
|
if (cb->signaling)
|
|
cb->remove = true;
|
|
else
|
|
da_erase(handler->global_callbacks, idx);
|
|
}
|
|
|
|
pthread_mutex_unlock(&handler->global_callbacks_mutex);
|
|
}
|