diff --git a/include/vlc_interrupt.h b/include/vlc_interrupt.h new file mode 100644 index 0000000000..fc075a20e3 --- /dev/null +++ b/include/vlc_interrupt.h @@ -0,0 +1,90 @@ +/***************************************************************************** + * vlc_interrupt.h: + ***************************************************************************** + * Copyright (C) 2015 Remlab T:mi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/** + * @file + * This file declares interruptible sleep functions. + */ + +#ifndef VLC_INTERRUPT_H +# define VLC_INTERRUPT_H 1 +# include +/** + * @defgroup interrupt Interruptible sleep + * @{ + * @defgroup interrupt_sleep Interruptible sleep functions + * @{ + */ + +/** + * Waits on a semaphore like vlc_sem_wait(). If the calling thread has an + * interruption context (as set by vlc_interrupt_set()), and another thread + * invokes vlc_interrupt_raise() on that context, the semaphore is incremented. + * + * @warning The calling thread should be the only thread ever to wait on the + * specified semaphore. Otherwise, interruptions may not be delivered + * accurately (the wrong thread may be woken up). + * + * @note This function is (always) a cancellation point. + * + * @return EINTR if the semaphore was incremented due to an interruption, + * otherwise zero. + */ +VLC_API int vlc_sem_wait_i11e(vlc_sem_t *); + +/** + * @} + * @defgroup interrupt_context Interrupt context signaling and manipulation + * @{ + */ +typedef struct vlc_interrupt vlc_interrupt_t; + +/** + * Creates an interruption context. + */ +VLC_API vlc_interrupt_t *vlc_interrupt_create(void) VLC_USED; + +/** + * Destroys an interrupt context. + */ +VLC_API void vlc_interrupt_destroy(vlc_interrupt_t *); + +/** + * Sets the interruption context for the calling thread. + * @param newctx the interruption context to attach or NULL for none + * @return the previous interruption context or NULL if none + * + * @note This function is not a cancellation point. + * @warning A context can be attached to no more than one thread at a time. + */ +VLC_API vlc_interrupt_t *vlc_interrupt_set(vlc_interrupt_t *); + +/** + * Raises an interruption through a specified context. This is used to + * asynchronously wake a thread up while it is waiting on some other events + * (typically I/O events). + * + * @note This function is thread-safe. + * @note This function is not a cancellation point. + */ +VLC_API void vlc_interrupt_raise(vlc_interrupt_t *); + +/** @} @} */ +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 62c54a6ea8..31d45c4771 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -79,6 +79,7 @@ pluginsinclude_HEADERS = \ ../include/vlc_rand.h \ ../include/vlc_services_discovery.h \ ../include/vlc_fingerprinter.h \ + ../include/vlc_interrupt.h \ ../include/vlc_sout.h \ ../include/vlc_spu.h \ ../include/vlc_stream.h \ @@ -450,6 +451,8 @@ SOURCES_libvlc_common = \ misc/picture.h \ misc/picture_fifo.c \ misc/picture_pool.c \ + misc/interrupt.h \ + misc/interrupt.c \ modules/modules.h \ modules/modules.c \ modules/bank.c \ diff --git a/src/libvlccore.sym b/src/libvlccore.sym index 73e7dd1957..d71e43b0aa 100644 --- a/src/libvlccore.sym +++ b/src/libvlccore.sym @@ -539,6 +539,11 @@ vlc_ngettext vlc_iconv vlc_iconv_close vlc_iconv_open +vlc_sem_wait_i11e +vlc_interrupt_create +vlc_interrupt_destroy +vlc_interrupt_set +vlc_interrupt_raise vlc_join vlc_list_children vlc_list_release diff --git a/src/misc/interrupt.c b/src/misc/interrupt.c new file mode 100644 index 0000000000..ec02601353 --- /dev/null +++ b/src/misc/interrupt.c @@ -0,0 +1,233 @@ +/***************************************************************************** + * interrupt.c: + ***************************************************************************** + * Copyright (C) 2015 Rémi Denis-Courmont + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/** @ingroup interrupt */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include + +#include +#include "interrupt.h" +#include "libvlc.h" + +#ifndef NDEBUG +static void vlc_interrupt_destructor(void *data) +{ + vlc_interrupt_t *ctx = data; + + assert(ctx->attached); + ctx->attached = false; +} +#endif + +static unsigned vlc_interrupt_refs = 0; +static vlc_mutex_t vlc_interrupt_lock = VLC_STATIC_MUTEX; +static vlc_threadvar_t vlc_interrupt_var; + +/** + * Initializes an interruption context. + */ +void vlc_interrupt_init(vlc_interrupt_t *ctx) +{ + vlc_mutex_lock(&vlc_interrupt_lock); + assert(vlc_interrupt_refs < UINT_MAX); + if (vlc_interrupt_refs++ == 0) +#ifndef NDEBUG + vlc_threadvar_create(&vlc_interrupt_var, vlc_interrupt_destructor); +#else + vlc_threadvar_create(&vlc_interrupt_var, NULL); +#endif + vlc_mutex_unlock(&vlc_interrupt_lock); + + vlc_mutex_init(&ctx->lock); + ctx->interrupted = false; +#ifndef NDEBUG + ctx->attached = false; +#endif + ctx->callback = NULL; +} + +vlc_interrupt_t *vlc_interrupt_create(void) +{ + vlc_interrupt_t *ctx = malloc(sizeof (*ctx)); + if (likely(ctx != NULL)) + vlc_interrupt_init(ctx); + return ctx; +} + +/** + * Deinitializes an interruption context. + * The context shall no longer be used by any thread. + */ +void vlc_interrupt_deinit(vlc_interrupt_t *ctx) +{ + assert(ctx->callback == NULL); + assert(!ctx->attached); + vlc_mutex_destroy(&ctx->lock); + + vlc_mutex_lock(&vlc_interrupt_lock); + assert(vlc_interrupt_refs > 0); + if (--vlc_interrupt_refs == 0) + vlc_threadvar_delete(&vlc_interrupt_var); + vlc_mutex_unlock(&vlc_interrupt_lock); +} + +void vlc_interrupt_destroy(vlc_interrupt_t *ctx) +{ + assert(ctx != NULL); + vlc_interrupt_deinit(ctx); + free(ctx); +} + +void vlc_interrupt_raise(vlc_interrupt_t *ctx) +{ + assert(ctx != NULL); + + /* This function must be reentrant. But the callback typically is not + * reentrant. The lock ensures that all calls to the callback for a given + * context are serialized. The lock also protects against invalid memory + * accesses to the callback pointer proper, and the interrupted flag. */ + vlc_mutex_lock(&ctx->lock); + ctx->interrupted = true; + if (ctx->callback != NULL) + ctx->callback(ctx->data); + vlc_mutex_unlock(&ctx->lock); +} + +vlc_interrupt_t *vlc_interrupt_set(vlc_interrupt_t *newctx) +{ + vlc_interrupt_t *oldctx; + + oldctx = vlc_threadvar_get(vlc_interrupt_var); +#ifndef NDEBUG + if (oldctx != NULL) + { + assert(oldctx->attached); + oldctx->attached = false; + } + if (newctx != NULL) + { + assert(!newctx->attached); + newctx->attached = true; + } +#endif + vlc_threadvar_set(vlc_interrupt_var, newctx); + + return oldctx; +} + +/** + * Prepares to enter interruptible wait. + * @param cb callback to interrupt the wait (i.e. wake up the thread) + * @param data opaque data pointer for the callback + * @return 0 on success or EINTR if an interruption is already pending + * @note Any succesful call must be paired with a call to + * vlc_interrupt_finish(). + */ +static int vlc_interrupt_prepare(vlc_interrupt_t *ctx, + void (*cb)(void *), void *data) +{ + int ret = 0; + + assert(ctx != NULL); + assert(ctx == vlc_threadvar_get(vlc_interrupt_var)); + + vlc_mutex_lock(&ctx->lock); + assert(ctx->callback == NULL); + if (ctx->interrupted) + { + ret = EINTR; + ctx->interrupted = false; + } + else + { + ret = 0; + ctx->callback = cb; + ctx->data = data; + } + vlc_mutex_unlock(&ctx->lock); + return ret; +} + +/** + * Cleans up after an interruptible wait: waits for any pending invocations of + * the callback previously registed with vlc_interrupt_prepare(), and rechecks + * for any pending interruption. + * + * @warning As this function waits for ongoing callback invocation to complete, + * the caller must not hold any resource necessary for the callback to run. + * Otherwise a deadlock may occur. + * + * @return EINTR if an interruption occurred, zero otherwise + */ +static int vlc_interrupt_finish(vlc_interrupt_t *ctx) +{ + int ret = 0; + + assert(ctx != NULL); + assert(ctx == vlc_threadvar_get(vlc_interrupt_var)); + + /* Wait for pending callbacks to prevent access by other threads. */ + vlc_mutex_lock(&ctx->lock); + ctx->callback = NULL; + if (ctx->interrupted) + { + ret = EINTR; + ctx->interrupted = false; + } + vlc_mutex_unlock(&ctx->lock); + return ret; +} + +static void vlc_interrupt_cleanup(void *opaque) +{ + vlc_interrupt_finish(opaque); +} + +static void vlc_interrupt_sem(void *opaque) +{ + vlc_sem_post(opaque); +} + +int vlc_sem_wait_i11e(vlc_sem_t *sem) +{ + vlc_interrupt_t *ctx = vlc_threadvar_get(vlc_interrupt_var); + if (ctx == NULL) + return vlc_sem_wait(sem), 0; + + int ret = vlc_interrupt_prepare(ctx, vlc_interrupt_sem, sem); + if (ret) + { + vlc_testcancel(); + return ret; + } + + vlc_cleanup_push(vlc_interrupt_cleanup, ctx); + vlc_sem_wait(sem); + vlc_cleanup_pop(); + + return vlc_interrupt_finish(ctx); +} diff --git a/src/misc/interrupt.h b/src/misc/interrupt.h new file mode 100644 index 0000000000..061158a66a --- /dev/null +++ b/src/misc/interrupt.h @@ -0,0 +1,40 @@ +/***************************************************************************** + * interrupt.h: + ***************************************************************************** + * Copyright (C) 2015 Rémi Denis-Courmont + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/** @ingroup interrupt */ +#ifndef LIBVLC_INPUT_SIGNAL_H +# define LIBVLC_INPUT_SIGNAL_H 1 + +# include + +void vlc_interrupt_init(vlc_interrupt_t *); +void vlc_interrupt_deinit(vlc_interrupt_t *); + +struct vlc_interrupt +{ + vlc_mutex_t lock; + bool interrupted; +#ifndef NDEBUG + bool attached; +#endif + void (*callback)(void *); + void *data; +}; +#endif