contrib: Bump libmicrodns to 0.1.2
[vlc.git] / src / misc / interrupt.c
blob6512462ff24e928f2c80592d56a26f1b5f0db70d
1 /*****************************************************************************
2 * interrupt.c:
3 *****************************************************************************
4 * Copyright (C) 2015 RĂ©mi Denis-Courmont
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
21 /** @ingroup interrupt */
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdbool.h>
30 #include <stdlib.h>
32 #ifdef HAVE_POLL
33 #include <poll.h>
34 #endif
35 #ifdef HAVE_SYS_EVENTFD_H
36 # include <sys/eventfd.h>
37 #endif
39 #include <vlc_common.h>
40 #include <vlc_fs.h> /* vlc_pipe */
41 #include <vlc_network.h> /* vlc_accept */
43 #include "interrupt.h"
44 #include "libvlc.h"
46 static thread_local vlc_interrupt_t *vlc_interrupt_var;
48 /**
49 * Initializes an interruption context.
51 void vlc_interrupt_init(vlc_interrupt_t *ctx)
53 vlc_mutex_init(&ctx->lock);
54 ctx->interrupted = false;
55 atomic_init(&ctx->killed, false);
56 ctx->callback = NULL;
59 vlc_interrupt_t *vlc_interrupt_create(void)
61 vlc_interrupt_t *ctx = malloc(sizeof (*ctx));
62 if (likely(ctx != NULL))
63 vlc_interrupt_init(ctx);
64 return ctx;
67 /**
68 * Deinitializes an interruption context.
69 * The context shall no longer be used by any thread.
71 void vlc_interrupt_deinit(vlc_interrupt_t *ctx)
73 assert(ctx->callback == NULL);
76 void vlc_interrupt_destroy(vlc_interrupt_t *ctx)
78 assert(ctx != NULL);
79 vlc_interrupt_deinit(ctx);
80 free(ctx);
83 void vlc_interrupt_raise(vlc_interrupt_t *ctx)
85 assert(ctx != NULL);
87 /* This function must be reentrant. But the callback typically is not
88 * reentrant. The lock ensures that all calls to the callback for a given
89 * context are serialized. The lock also protects against invalid memory
90 * accesses to the callback pointer proper, and the interrupted flag. */
91 vlc_mutex_lock(&ctx->lock);
92 ctx->interrupted = true;
93 if (ctx->callback != NULL)
94 ctx->callback(ctx->data);
95 vlc_mutex_unlock(&ctx->lock);
98 vlc_interrupt_t *vlc_interrupt_set(vlc_interrupt_t *newctx)
100 vlc_interrupt_t *oldctx = vlc_interrupt_var;
102 vlc_interrupt_var = newctx;
103 return oldctx;
107 * Prepares to enter interruptible wait.
108 * @param cb callback to interrupt the wait (i.e. wake up the thread)
109 * @param data opaque data pointer for the callback
110 * @note Any <b>successful</b> call <b>must</b> be paired with a call to
111 * vlc_interrupt_finish().
113 static void vlc_interrupt_prepare(vlc_interrupt_t *ctx,
114 void (*cb)(void *), void *data)
116 assert(ctx != NULL);
117 assert(ctx == vlc_interrupt_var);
119 vlc_mutex_lock(&ctx->lock);
120 assert(ctx->callback == NULL);
121 ctx->callback = cb;
122 ctx->data = data;
124 if (unlikely(ctx->interrupted))
125 cb(data);
126 vlc_mutex_unlock(&ctx->lock);
130 * Cleans up after an interruptible wait: waits for any pending invocations of
131 * the callback previously registed with vlc_interrupt_prepare(), and rechecks
132 * for any pending interruption.
134 * @warning As this function waits for ongoing callback invocation to complete,
135 * the caller must not hold any resource necessary for the callback to run.
136 * Otherwise a deadlock may occur.
138 * @return EINTR if an interruption occurred, zero otherwise
140 static int vlc_interrupt_finish(vlc_interrupt_t *ctx)
142 int ret = 0;
144 assert(ctx != NULL);
145 assert(ctx == vlc_interrupt_var);
147 /* Wait for pending callbacks to prevent access by other threads. */
148 vlc_mutex_lock(&ctx->lock);
149 ctx->callback = NULL;
150 if (ctx->interrupted)
152 ret = EINTR;
153 ctx->interrupted = false;
155 vlc_mutex_unlock(&ctx->lock);
156 return ret;
159 void vlc_interrupt_register(void (*cb)(void *), void *opaque)
161 vlc_interrupt_t *ctx = vlc_interrupt_var;
162 if (ctx != NULL)
163 vlc_interrupt_prepare(ctx, cb, opaque);
166 int vlc_interrupt_unregister(void)
168 vlc_interrupt_t *ctx = vlc_interrupt_var;
169 return (ctx != NULL) ? vlc_interrupt_finish(ctx) : 0;
172 static void vlc_interrupt_cleanup(void *opaque)
174 vlc_interrupt_finish(opaque);
177 void vlc_interrupt_kill(vlc_interrupt_t *ctx)
179 assert(ctx != NULL);
181 atomic_store(&ctx->killed, true);
182 vlc_interrupt_raise(ctx);
185 bool vlc_killed(void)
187 vlc_interrupt_t *ctx = vlc_interrupt_var;
189 return (ctx != NULL) && atomic_load(&ctx->killed);
192 static void vlc_interrupt_sem(void *opaque)
194 vlc_sem_post(opaque);
197 int vlc_sem_wait_i11e(vlc_sem_t *sem)
199 vlc_interrupt_t *ctx = vlc_interrupt_var;
200 if (ctx == NULL)
201 return vlc_sem_wait(sem), 0;
203 vlc_interrupt_prepare(ctx, vlc_interrupt_sem, sem);
205 vlc_cleanup_push(vlc_interrupt_cleanup, ctx);
206 vlc_sem_wait(sem);
207 vlc_cleanup_pop();
209 return vlc_interrupt_finish(ctx);
212 static void vlc_mwait_i11e_wake(void *opaque)
214 vlc_cond_signal(opaque);
217 static void vlc_mwait_i11e_cleanup(void *opaque)
219 vlc_interrupt_t *ctx = opaque;
221 vlc_mutex_unlock(&ctx->lock);
222 vlc_interrupt_finish(ctx);
225 int vlc_mwait_i11e(vlc_tick_t deadline)
227 vlc_interrupt_t *ctx = vlc_interrupt_var;
228 if (ctx == NULL)
229 return vlc_tick_wait(deadline), 0;
231 vlc_cond_t wait;
232 vlc_cond_init(&wait);
234 vlc_interrupt_prepare(ctx, vlc_mwait_i11e_wake, &wait);
236 vlc_mutex_lock(&ctx->lock);
237 vlc_cleanup_push(vlc_mwait_i11e_cleanup, ctx);
238 while (!ctx->interrupted
239 && vlc_cond_timedwait(&wait, &ctx->lock, deadline) == 0);
240 vlc_cleanup_pop();
241 vlc_mutex_unlock(&ctx->lock);
243 return vlc_interrupt_finish(ctx);
246 static void vlc_interrupt_forward_wake(void *opaque)
248 void **data = opaque;
249 vlc_interrupt_t *to = data[0];
250 vlc_interrupt_t *from = data[1];
252 (atomic_load(&from->killed) ? vlc_interrupt_kill
253 : vlc_interrupt_raise)(to);
256 void vlc_interrupt_forward_start(vlc_interrupt_t *to, void *data[2])
258 data[0] = data[1] = NULL;
260 vlc_interrupt_t *from = vlc_interrupt_var;
261 if (from == NULL)
262 return;
264 assert(from != to);
265 data[0] = to;
266 data[1] = from;
267 vlc_interrupt_prepare(from, vlc_interrupt_forward_wake, data);
270 int vlc_interrupt_forward_stop(void *const data[2])
272 vlc_interrupt_t *from = data[1];
273 if (from == NULL)
274 return 0;
276 assert(from->callback == vlc_interrupt_forward_wake);
277 assert(from->data == data);
278 return vlc_interrupt_finish(from);
281 #ifndef _WIN32
282 static void vlc_poll_i11e_wake(void *opaque)
284 uint64_t value = 1;
285 int *fd = opaque;
286 int canc;
288 canc = vlc_savecancel();
289 write(fd[1], &value, sizeof (value));
290 vlc_restorecancel(canc);
293 static void vlc_poll_i11e_cleanup(void *opaque)
295 vlc_interrupt_t *ctx = opaque;
296 int *fd = ctx->data;
298 vlc_interrupt_finish(ctx);
299 if (fd[1] != fd[0])
300 vlc_close(fd[1]);
301 vlc_close(fd[0]);
304 static int vlc_poll_i11e_inner(struct pollfd *restrict fds, unsigned nfds,
305 int timeout, vlc_interrupt_t *ctx,
306 struct pollfd *restrict ufd)
308 int fd[2];
309 int ret = -1;
310 int canc;
312 /* TODO: cache this */
313 # if defined (HAVE_EVENTFD) && defined (EFD_CLOEXEC)
314 canc = vlc_savecancel();
315 fd[0] = eventfd(0, EFD_CLOEXEC);
316 vlc_restorecancel(canc);
317 if (fd[0] != -1)
318 fd[1] = fd[0];
319 else
320 # endif
321 if (vlc_pipe(fd))
323 vlc_testcancel();
324 errno = ENOMEM;
325 return -1;
328 for (unsigned i = 0; i < nfds; i++)
330 ufd[i].fd = fds[i].fd;
331 ufd[i].events = fds[i].events;
333 ufd[nfds].fd = fd[0];
334 ufd[nfds].events = POLLIN;
336 vlc_interrupt_prepare(ctx, vlc_poll_i11e_wake, fd);
338 vlc_cleanup_push(vlc_poll_i11e_cleanup, ctx);
339 ret = poll(ufd, nfds + 1, timeout);
341 for (unsigned i = 0; i < nfds; i++)
342 fds[i].revents = ufd[i].revents;
344 if (ret > 0 && ufd[nfds].revents)
346 uint64_t dummy;
348 read(fd[0], &dummy, sizeof (dummy));
349 ret--;
351 vlc_cleanup_pop();
353 if (vlc_interrupt_finish(ctx))
355 errno = EINTR;
356 ret = -1;
359 canc = vlc_savecancel();
360 if (fd[1] != fd[0])
361 vlc_close(fd[1]);
362 vlc_close(fd[0]);
363 vlc_restorecancel(canc);
364 return ret;
367 int vlc_poll_i11e(struct pollfd *fds, unsigned nfds, int timeout)
369 vlc_interrupt_t *ctx = vlc_interrupt_var;
370 if (ctx == NULL)
371 return poll(fds, nfds, timeout);
373 int ret;
375 if (likely(nfds < 255))
376 { /* Fast path with stack allocation */
377 struct pollfd ufd[nfds + 1];
379 ret = vlc_poll_i11e_inner(fds, nfds, timeout, ctx, ufd);
381 else
382 { /* Slow path but poll() is slow with large nfds anyway. */
383 struct pollfd *ufd = vlc_alloc(nfds + 1, sizeof (*ufd));
384 if (unlikely(ufd == NULL))
385 return -1; /* ENOMEM */
387 vlc_cleanup_push(free, ufd);
388 ret = vlc_poll_i11e_inner(fds, nfds, timeout, ctx, ufd);
389 vlc_cleanup_pop();
390 free(ufd);
392 return ret;
395 # include <fcntl.h>
396 # include <sys/uio.h>
397 # ifdef HAVE_SYS_SOCKET_H
398 # include <sys/socket.h>
399 # endif
402 /* There are currently no ways to atomically force a non-blocking read or write
403 * operations. Even for sockets, the MSG_DONTWAIT flag is non-standard.
405 * So in the event that more than one thread tries to read or write on the same
406 * file at the same time, there is a race condition where these functions might
407 * block in spite of an interruption. This should never happen in practice.
411 * Wrapper for readv() that returns the EINTR error upon VLC I/O interruption.
412 * @warning This function ignores the non-blocking file flag.
414 ssize_t vlc_readv_i11e(int fd, struct iovec *iov, int count)
416 struct pollfd ufd;
418 ufd.fd = fd;
419 ufd.events = POLLIN;
421 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
422 return -1;
423 return readv(fd, iov, count);
427 * Wrapper for writev() that returns the EINTR error upon VLC I/O interruption.
429 * @note Like writev(), once some but not all bytes are written, the function
430 * might wait for write completion, regardless of signals and interruptions.
431 * @warning This function ignores the non-blocking file flag.
433 ssize_t vlc_writev_i11e(int fd, const struct iovec *iov, int count)
435 struct pollfd ufd;
437 ufd.fd = fd;
438 ufd.events = POLLOUT;
440 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
441 return -1;
442 return writev(fd, iov, count);
446 * Wrapper for read() that returns the EINTR error upon VLC I/O interruption.
447 * @warning This function ignores the non-blocking file flag.
449 ssize_t vlc_read_i11e(int fd, void *buf, size_t count)
451 struct iovec iov = { .iov_base = buf, .iov_len = count };
452 return vlc_readv_i11e(fd, &iov, 1);
456 * Wrapper for write() that returns the EINTR error upon VLC I/O interruption.
458 * @note Like write(), once some but not all bytes are written, the function
459 * might wait for write completion, regardless of signals and interruptions.
460 * @warning This function ignores the non-blocking file flag.
462 ssize_t vlc_write_i11e(int fd, const void *buf, size_t count)
464 struct iovec iov = { .iov_base = (void*)buf, .iov_len = count };
465 return vlc_writev_i11e(fd, &iov, 1);
468 ssize_t vlc_recvmsg_i11e(int fd, struct msghdr *msg, int flags)
470 struct pollfd ufd;
472 ufd.fd = fd;
473 ufd.events = POLLIN;
475 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
476 return -1;
477 /* NOTE: MSG_OOB and MSG_PEEK should work fine here.
478 * MSG_WAITALL is not supported at this point. */
479 return recvmsg(fd, msg, flags);
482 ssize_t vlc_recvfrom_i11e(int fd, void *buf, size_t len, int flags,
483 struct sockaddr *addr, socklen_t *addrlen)
485 struct iovec iov = { .iov_base = buf, .iov_len = len };
486 struct msghdr msg = {
487 .msg_name = addr,
488 .msg_namelen = (addrlen != NULL) ? *addrlen : 0,
489 .msg_iov = &iov,
490 .msg_iovlen = 1,
493 ssize_t ret = vlc_recvmsg_i11e(fd, &msg, flags);
494 if (ret >= 0 && addrlen != NULL)
495 *addrlen = msg.msg_namelen;
496 return ret;
499 ssize_t vlc_sendmsg_i11e(int fd, const struct msghdr *msg, int flags)
501 struct pollfd ufd;
503 ufd.fd = fd;
504 ufd.events = POLLOUT;
506 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
507 return -1;
508 /* NOTE: MSG_EOR, MSG_OOB and MSG_NOSIGNAL should all work fine here. */
509 return sendmsg(fd, msg, flags);
512 ssize_t vlc_sendto_i11e(int fd, const void *buf, size_t len, int flags,
513 const struct sockaddr *addr, socklen_t addrlen)
515 struct iovec iov = { .iov_base = (void *)buf, .iov_len = len };
516 struct msghdr msg = {
517 .msg_name = (struct sockaddr *)addr,
518 .msg_namelen = addrlen,
519 .msg_iov = &iov,
520 .msg_iovlen = 1,
523 return vlc_sendmsg_i11e(fd, &msg, flags);
526 int vlc_accept_i11e(int fd, struct sockaddr *addr, socklen_t *addrlen,
527 bool blocking)
529 struct pollfd ufd;
531 ufd.fd = fd;
532 ufd.events = POLLIN;
534 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
535 return -1;
537 return vlc_accept(fd, addr, addrlen, blocking);
540 #else /* _WIN32 */
542 static void CALLBACK vlc_poll_i11e_wake_self(ULONG_PTR data)
544 (void) data; /* Nothing to do */
547 static void vlc_poll_i11e_wake(void *opaque)
549 #if !VLC_WINSTORE_APP || _WIN32_WINNT >= 0x0A00
550 HANDLE th = opaque;
551 QueueUserAPC(vlc_poll_i11e_wake_self, th, 0);
552 #else
553 (void) opaque;
554 #endif
557 static void vlc_poll_i11e_cleanup(void *opaque)
559 vlc_interrupt_t *ctx = opaque;
560 HANDLE th = ctx->data;
562 vlc_interrupt_finish(ctx);
563 CloseHandle(th);
566 int vlc_poll_i11e(struct pollfd *fds, unsigned nfds, int timeout)
568 vlc_interrupt_t *ctx = vlc_interrupt_var;
569 if (ctx == NULL)
570 return vlc_poll(fds, nfds, timeout);
572 int ret = -1;
573 HANDLE th;
575 if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
576 GetCurrentProcess(), &th, 0, FALSE,
577 DUPLICATE_SAME_ACCESS))
579 errno = ENOMEM;
580 return -1;
583 vlc_interrupt_prepare(ctx, vlc_poll_i11e_wake, th);
585 vlc_cleanup_push(vlc_poll_i11e_cleanup, ctx);
586 ret = vlc_poll(fds, nfds, timeout);
587 vlc_cleanup_pop();
589 if (vlc_interrupt_finish(ctx))
591 errno = EINTR;
592 ret = -1;
595 CloseHandle(th);
596 return ret;
599 ssize_t vlc_readv_i11e(int fd, struct iovec *iov, int count)
601 (void) fd; (void) iov; (void) count;
602 vlc_assert_unreachable();
605 ssize_t vlc_writev_i11e(int fd, const struct iovec *iov, int count)
607 (void) fd; (void) iov; (void) count;
608 vlc_assert_unreachable();
611 ssize_t vlc_read_i11e(int fd, void *buf, size_t count)
613 return read(fd, buf, count);
616 ssize_t vlc_write_i11e(int fd, const void *buf, size_t count)
618 return write(fd, buf, count);
621 ssize_t vlc_recvmsg_i11e(int fd, struct msghdr *msg, int flags)
623 (void) fd; (void) msg; (void) flags;
624 vlc_assert_unreachable();
627 ssize_t vlc_recvfrom_i11e(int fd, void *buf, size_t len, int flags,
628 struct sockaddr *addr, socklen_t *addrlen)
630 struct pollfd ufd;
632 ufd.fd = fd;
633 ufd.events = POLLIN;
635 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
636 return -1;
638 ssize_t ret = recvfrom(fd, buf, len, flags, addr, addrlen);
639 if (ret < 0 && WSAGetLastError() == WSAEWOULDBLOCK)
640 errno = EAGAIN;
641 return ret;
644 ssize_t vlc_sendmsg_i11e(int fd, const struct msghdr *msg, int flags)
646 (void) fd; (void) msg; (void) flags;
647 vlc_assert_unreachable();
650 ssize_t vlc_sendto_i11e(int fd, const void *buf, size_t len, int flags,
651 const struct sockaddr *addr, socklen_t addrlen)
653 struct pollfd ufd;
655 ufd.fd = fd;
656 ufd.events = POLLOUT;
658 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
659 return -1;
661 ssize_t ret = sendto(fd, buf, len, flags, addr, addrlen);
662 if (ret < 0 && WSAGetLastError() == WSAEWOULDBLOCK)
663 errno = EAGAIN;
664 return ret;
667 int vlc_accept_i11e(int fd, struct sockaddr *addr, socklen_t *addrlen,
668 bool blocking)
670 struct pollfd ufd;
672 ufd.fd = fd;
673 ufd.events = POLLIN;
675 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
676 return -1;
678 int cfd = vlc_accept(fd, addr, addrlen, blocking);
679 if (cfd < 0 && WSAGetLastError() == WSAEWOULDBLOCK)
680 errno = EAGAIN;
681 return cfd;
684 #endif