asx: remove useless test
[vlc.git] / src / misc / interrupt.c
blob7004ed2c3ad93b97d24247d08723a8cdee7481f8
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);
74 vlc_mutex_destroy(&ctx->lock);
77 void vlc_interrupt_destroy(vlc_interrupt_t *ctx)
79 assert(ctx != NULL);
80 vlc_interrupt_deinit(ctx);
81 free(ctx);
84 void vlc_interrupt_raise(vlc_interrupt_t *ctx)
86 assert(ctx != NULL);
88 /* This function must be reentrant. But the callback typically is not
89 * reentrant. The lock ensures that all calls to the callback for a given
90 * context are serialized. The lock also protects against invalid memory
91 * accesses to the callback pointer proper, and the interrupted flag. */
92 vlc_mutex_lock(&ctx->lock);
93 ctx->interrupted = true;
94 if (ctx->callback != NULL)
95 ctx->callback(ctx->data);
96 vlc_mutex_unlock(&ctx->lock);
99 vlc_interrupt_t *vlc_interrupt_set(vlc_interrupt_t *newctx)
101 vlc_interrupt_t *oldctx = vlc_interrupt_var;
103 vlc_interrupt_var = newctx;
104 return oldctx;
108 * Prepares to enter interruptible wait.
109 * @param cb callback to interrupt the wait (i.e. wake up the thread)
110 * @param data opaque data pointer for the callback
111 * @note Any <b>successful</b> call <b>must</b> be paired with a call to
112 * vlc_interrupt_finish().
114 static void vlc_interrupt_prepare(vlc_interrupt_t *ctx,
115 void (*cb)(void *), void *data)
117 assert(ctx != NULL);
118 assert(ctx == vlc_interrupt_var);
120 vlc_mutex_lock(&ctx->lock);
121 assert(ctx->callback == NULL);
122 ctx->callback = cb;
123 ctx->data = data;
125 if (unlikely(ctx->interrupted))
126 cb(data);
127 vlc_mutex_unlock(&ctx->lock);
131 * Cleans up after an interruptible wait: waits for any pending invocations of
132 * the callback previously registed with vlc_interrupt_prepare(), and rechecks
133 * for any pending interruption.
135 * @warning As this function waits for ongoing callback invocation to complete,
136 * the caller must not hold any resource necessary for the callback to run.
137 * Otherwise a deadlock may occur.
139 * @return EINTR if an interruption occurred, zero otherwise
141 static int vlc_interrupt_finish(vlc_interrupt_t *ctx)
143 int ret = 0;
145 assert(ctx != NULL);
146 assert(ctx == vlc_interrupt_var);
148 /* Wait for pending callbacks to prevent access by other threads. */
149 vlc_mutex_lock(&ctx->lock);
150 ctx->callback = NULL;
151 if (ctx->interrupted)
153 ret = EINTR;
154 ctx->interrupted = false;
156 vlc_mutex_unlock(&ctx->lock);
157 return ret;
160 void vlc_interrupt_register(void (*cb)(void *), void *opaque)
162 vlc_interrupt_t *ctx = vlc_interrupt_var;
163 if (ctx != NULL)
164 vlc_interrupt_prepare(ctx, cb, opaque);
167 int vlc_interrupt_unregister(void)
169 vlc_interrupt_t *ctx = vlc_interrupt_var;
170 return (ctx != NULL) ? vlc_interrupt_finish(ctx) : 0;
173 static void vlc_interrupt_cleanup(void *opaque)
175 vlc_interrupt_finish(opaque);
178 void vlc_interrupt_kill(vlc_interrupt_t *ctx)
180 assert(ctx != NULL);
182 atomic_store(&ctx->killed, true);
183 vlc_interrupt_raise(ctx);
186 bool vlc_killed(void)
188 vlc_interrupt_t *ctx = vlc_interrupt_var;
190 return (ctx != NULL) && atomic_load(&ctx->killed);
193 static void vlc_interrupt_sem(void *opaque)
195 vlc_sem_post(opaque);
198 int vlc_sem_wait_i11e(vlc_sem_t *sem)
200 vlc_interrupt_t *ctx = vlc_interrupt_var;
201 if (ctx == NULL)
202 return vlc_sem_wait(sem), 0;
204 vlc_interrupt_prepare(ctx, vlc_interrupt_sem, sem);
206 vlc_cleanup_push(vlc_interrupt_cleanup, ctx);
207 vlc_sem_wait(sem);
208 vlc_cleanup_pop();
210 return vlc_interrupt_finish(ctx);
213 static void vlc_mwait_i11e_wake(void *opaque)
215 vlc_cond_signal(opaque);
218 static void vlc_mwait_i11e_cleanup(void *opaque)
220 vlc_interrupt_t *ctx = opaque;
221 vlc_cond_t *cond = ctx->data;
223 vlc_mutex_unlock(&ctx->lock);
224 vlc_interrupt_finish(ctx);
225 vlc_cond_destroy(cond);
228 int vlc_mwait_i11e(mtime_t deadline)
230 vlc_interrupt_t *ctx = vlc_interrupt_var;
231 if (ctx == NULL)
232 return mwait(deadline), 0;
234 vlc_cond_t wait;
235 vlc_cond_init(&wait);
237 vlc_interrupt_prepare(ctx, vlc_mwait_i11e_wake, &wait);
239 vlc_mutex_lock(&ctx->lock);
240 vlc_cleanup_push(vlc_mwait_i11e_cleanup, ctx);
241 while (!ctx->interrupted
242 && vlc_cond_timedwait(&wait, &ctx->lock, deadline) == 0);
243 vlc_cleanup_pop();
244 vlc_mutex_unlock(&ctx->lock);
246 int ret = vlc_interrupt_finish(ctx);
247 vlc_cond_destroy(&wait);
248 return ret;
251 static void vlc_interrupt_forward_wake(void *opaque)
253 void **data = opaque;
254 vlc_interrupt_t *to = data[0];
255 vlc_interrupt_t *from = data[1];
257 (atomic_load(&from->killed) ? vlc_interrupt_kill
258 : vlc_interrupt_raise)(to);
261 void vlc_interrupt_forward_start(vlc_interrupt_t *to, void *data[2])
263 data[0] = data[1] = NULL;
265 vlc_interrupt_t *from = vlc_interrupt_var;
266 if (from == NULL)
267 return;
269 assert(from != to);
270 data[0] = to;
271 data[1] = from;
272 vlc_interrupt_prepare(from, vlc_interrupt_forward_wake, data);
275 int vlc_interrupt_forward_stop(void *const data[2])
277 vlc_interrupt_t *from = data[1];
278 if (from == NULL)
279 return 0;
281 assert(from->callback == vlc_interrupt_forward_wake);
282 assert(from->data == data);
283 return vlc_interrupt_finish(from);
286 #ifndef _WIN32
287 static void vlc_poll_i11e_wake(void *opaque)
289 uint64_t value = 1;
290 int *fd = opaque;
291 int canc;
293 canc = vlc_savecancel();
294 write(fd[1], &value, sizeof (value));
295 vlc_restorecancel(canc);
298 static void vlc_poll_i11e_cleanup(void *opaque)
300 vlc_interrupt_t *ctx = opaque;
301 int *fd = ctx->data;
303 vlc_interrupt_finish(ctx);
304 if (fd[1] != fd[0])
305 vlc_close(fd[1]);
306 vlc_close(fd[0]);
309 static int vlc_poll_i11e_inner(struct pollfd *restrict fds, unsigned nfds,
310 int timeout, vlc_interrupt_t *ctx,
311 struct pollfd *restrict ufd)
313 int fd[2];
314 int ret = -1;
315 int canc;
317 /* TODO: cache this */
318 # if defined (HAVE_EVENTFD) && defined (EFD_CLOEXEC)
319 canc = vlc_savecancel();
320 fd[0] = eventfd(0, EFD_CLOEXEC);
321 vlc_restorecancel(canc);
322 if (fd[0] != -1)
323 fd[1] = fd[0];
324 else
325 # endif
326 if (vlc_pipe(fd))
328 vlc_testcancel();
329 errno = ENOMEM;
330 return -1;
333 for (unsigned i = 0; i < nfds; i++)
335 ufd[i].fd = fds[i].fd;
336 ufd[i].events = fds[i].events;
338 ufd[nfds].fd = fd[0];
339 ufd[nfds].events = POLLIN;
341 vlc_interrupt_prepare(ctx, vlc_poll_i11e_wake, fd);
343 vlc_cleanup_push(vlc_poll_i11e_cleanup, ctx);
344 ret = poll(ufd, nfds + 1, timeout);
346 for (unsigned i = 0; i < nfds; i++)
347 fds[i].revents = ufd[i].revents;
349 if (ret > 0 && ufd[nfds].revents)
351 uint64_t dummy;
353 read(fd[0], &dummy, sizeof (dummy));
354 ret--;
356 vlc_cleanup_pop();
358 if (vlc_interrupt_finish(ctx))
360 errno = EINTR;
361 ret = -1;
364 canc = vlc_savecancel();
365 if (fd[1] != fd[0])
366 vlc_close(fd[1]);
367 vlc_close(fd[0]);
368 vlc_restorecancel(canc);
369 return ret;
372 int vlc_poll_i11e(struct pollfd *fds, unsigned nfds, int timeout)
374 vlc_interrupt_t *ctx = vlc_interrupt_var;
375 if (ctx == NULL)
376 return poll(fds, nfds, timeout);
378 int ret;
380 if (likely(nfds < 255))
381 { /* Fast path with stack allocation */
382 struct pollfd ufd[nfds + 1];
384 ret = vlc_poll_i11e_inner(fds, nfds, timeout, ctx, ufd);
386 else
387 { /* Slow path but poll() is slow with large nfds anyway. */
388 struct pollfd *ufd = vlc_alloc(nfds + 1, sizeof (*ufd));
389 if (unlikely(ufd == NULL))
390 return -1; /* ENOMEM */
392 vlc_cleanup_push(free, ufd);
393 ret = vlc_poll_i11e_inner(fds, nfds, timeout, ctx, ufd);
394 vlc_cleanup_pop();
395 free(ufd);
397 return ret;
400 # include <fcntl.h>
401 # include <sys/uio.h>
402 # include <sys/socket.h>
405 /* There are currently no ways to atomically force a non-blocking read or write
406 * operations. Even for sockets, the MSG_DONTWAIT flag is non-standard.
408 * So in the event that more than one thread tries to read or write on the same
409 * file at the same time, there is a race condition where these functions might
410 * block in spite of an interruption. This should never happen in practice.
414 * Wrapper for readv() that returns the EINTR error upon VLC I/O interruption.
415 * @warning This function ignores the non-blocking file flag.
417 ssize_t vlc_readv_i11e(int fd, struct iovec *iov, int count)
419 struct pollfd ufd;
421 ufd.fd = fd;
422 ufd.events = POLLIN;
424 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
425 return -1;
426 return readv(fd, iov, count);
430 * Wrapper for writev() that returns the EINTR error upon VLC I/O interruption.
432 * @note Like writev(), once some but not all bytes are written, the function
433 * might wait for write completion, regardless of signals and interruptions.
434 * @warning This function ignores the non-blocking file flag.
436 ssize_t vlc_writev_i11e(int fd, const struct iovec *iov, int count)
438 struct pollfd ufd;
440 ufd.fd = fd;
441 ufd.events = POLLOUT;
443 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
444 return -1;
445 return writev(fd, iov, count);
449 * Wrapper for read() that returns the EINTR error upon VLC I/O interruption.
450 * @warning This function ignores the non-blocking file flag.
452 ssize_t vlc_read_i11e(int fd, void *buf, size_t count)
454 struct iovec iov = { .iov_base = buf, .iov_len = count };
455 return vlc_readv_i11e(fd, &iov, 1);
459 * Wrapper for write() that returns the EINTR error upon VLC I/O interruption.
461 * @note Like write(), once some but not all bytes are written, the function
462 * might wait for write completion, regardless of signals and interruptions.
463 * @warning This function ignores the non-blocking file flag.
465 ssize_t vlc_write_i11e(int fd, const void *buf, size_t count)
467 struct iovec iov = { .iov_base = (void*)buf, .iov_len = count };
468 return vlc_writev_i11e(fd, &iov, 1);
471 ssize_t vlc_recvmsg_i11e(int fd, struct msghdr *msg, int flags)
473 struct pollfd ufd;
475 ufd.fd = fd;
476 ufd.events = POLLIN;
478 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
479 return -1;
480 /* NOTE: MSG_OOB and MSG_PEEK should work fine here.
481 * MSG_WAITALL is not supported at this point. */
482 return recvmsg(fd, msg, flags);
485 ssize_t vlc_recvfrom_i11e(int fd, void *buf, size_t len, int flags,
486 struct sockaddr *addr, socklen_t *addrlen)
488 struct iovec iov = { .iov_base = buf, .iov_len = len };
489 struct msghdr msg = {
490 .msg_name = addr,
491 .msg_namelen = (addrlen != NULL) ? *addrlen : 0,
492 .msg_iov = &iov,
493 .msg_iovlen = 1,
496 ssize_t ret = vlc_recvmsg_i11e(fd, &msg, flags);
497 if (ret >= 0 && addrlen != NULL)
498 *addrlen = msg.msg_namelen;
499 return ret;
502 ssize_t vlc_sendmsg_i11e(int fd, const struct msghdr *msg, int flags)
504 struct pollfd ufd;
506 ufd.fd = fd;
507 ufd.events = POLLOUT;
509 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
510 return -1;
511 /* NOTE: MSG_EOR, MSG_OOB and MSG_NOSIGNAL should all work fine here. */
512 return sendmsg(fd, msg, flags);
515 ssize_t vlc_sendto_i11e(int fd, const void *buf, size_t len, int flags,
516 const struct sockaddr *addr, socklen_t addrlen)
518 struct iovec iov = { .iov_base = (void *)buf, .iov_len = len };
519 struct msghdr msg = {
520 .msg_name = (struct sockaddr *)addr,
521 .msg_namelen = addrlen,
522 .msg_iov = &iov,
523 .msg_iovlen = 1,
526 return vlc_sendmsg_i11e(fd, &msg, flags);
529 int vlc_accept_i11e(int fd, struct sockaddr *addr, socklen_t *addrlen,
530 bool blocking)
532 struct pollfd ufd;
534 ufd.fd = fd;
535 ufd.events = POLLIN;
537 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
538 return -1;
540 return vlc_accept(fd, addr, addrlen, blocking);
543 #else /* _WIN32 */
545 static void CALLBACK vlc_poll_i11e_wake_self(ULONG_PTR data)
547 (void) data; /* Nothing to do */
550 static void vlc_poll_i11e_wake(void *opaque)
552 #if !VLC_WINSTORE_APP || _WIN32_WINNT >= 0x0A00
553 HANDLE th = opaque;
554 QueueUserAPC(vlc_poll_i11e_wake_self, th, 0);
555 #else
556 (void) opaque;
557 #endif
560 static void vlc_poll_i11e_cleanup(void *opaque)
562 vlc_interrupt_t *ctx = opaque;
563 HANDLE th = ctx->data;
565 vlc_interrupt_finish(ctx);
566 CloseHandle(th);
569 int vlc_poll_i11e(struct pollfd *fds, unsigned nfds, int timeout)
571 vlc_interrupt_t *ctx = vlc_interrupt_var;
572 if (ctx == NULL)
573 return vlc_poll(fds, nfds, timeout);
575 int ret = -1;
576 HANDLE th;
578 if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
579 GetCurrentProcess(), &th, 0, FALSE,
580 DUPLICATE_SAME_ACCESS))
582 errno = ENOMEM;
583 return -1;
586 vlc_interrupt_prepare(ctx, vlc_poll_i11e_wake, th);
588 vlc_cleanup_push(vlc_poll_i11e_cleanup, ctx);
589 ret = vlc_poll(fds, nfds, timeout);
590 vlc_cleanup_pop();
592 if (vlc_interrupt_finish(ctx))
594 errno = EINTR;
595 ret = -1;
598 CloseHandle(th);
599 return ret;
602 ssize_t vlc_readv_i11e(int fd, struct iovec *iov, int count)
604 (void) fd; (void) iov; (void) count;
605 vlc_assert_unreachable();
608 ssize_t vlc_writev_i11e(int fd, const struct iovec *iov, int count)
610 (void) fd; (void) iov; (void) count;
611 vlc_assert_unreachable();
614 ssize_t vlc_read_i11e(int fd, void *buf, size_t count)
616 return read(fd, buf, count);
619 ssize_t vlc_write_i11e(int fd, const void *buf, size_t count)
621 return write(fd, buf, count);
624 ssize_t vlc_recvmsg_i11e(int fd, struct msghdr *msg, int flags)
626 (void) fd; (void) msg; (void) flags;
627 vlc_assert_unreachable();
630 ssize_t vlc_recvfrom_i11e(int fd, void *buf, size_t len, int flags,
631 struct sockaddr *addr, socklen_t *addrlen)
633 struct pollfd ufd;
635 ufd.fd = fd;
636 ufd.events = POLLIN;
638 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
639 return -1;
641 ssize_t ret = recvfrom(fd, buf, len, flags, addr, addrlen);
642 if (ret < 0 && WSAGetLastError() == WSAEWOULDBLOCK)
643 errno = EAGAIN;
644 return ret;
647 ssize_t vlc_sendmsg_i11e(int fd, const struct msghdr *msg, int flags)
649 (void) fd; (void) msg; (void) flags;
650 vlc_assert_unreachable();
653 ssize_t vlc_sendto_i11e(int fd, const void *buf, size_t len, int flags,
654 const struct sockaddr *addr, socklen_t addrlen)
656 struct pollfd ufd;
658 ufd.fd = fd;
659 ufd.events = POLLOUT;
661 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
662 return -1;
664 ssize_t ret = sendto(fd, buf, len, flags, addr, addrlen);
665 if (ret < 0 && WSAGetLastError() == WSAEWOULDBLOCK)
666 errno = EAGAIN;
667 return ret;
670 int vlc_accept_i11e(int fd, struct sockaddr *addr, socklen_t *addrlen,
671 bool blocking)
673 struct pollfd ufd;
675 ufd.fd = fd;
676 ufd.events = POLLIN;
678 if (vlc_poll_i11e(&ufd, 1, -1) < 0)
679 return -1;
681 int cfd = vlc_accept(fd, addr, addrlen, blocking);
682 if (cfd < 0 && WSAGetLastError() == WSAEWOULDBLOCK)
683 errno = EAGAIN;
684 return cfd;
687 #endif