1 /*****************************************************************************
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 */
35 #ifdef HAVE_SYS_EVENTFD_H
36 # include <sys/eventfd.h>
39 #include <vlc_common.h>
40 #include <vlc_fs.h> /* vlc_pipe */
41 #include <vlc_network.h> /* vlc_accept */
43 #include "interrupt.h"
46 static thread_local vlc_interrupt_t
*vlc_interrupt_var
;
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);
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
);
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
)
79 vlc_interrupt_deinit(ctx
);
83 void vlc_interrupt_raise(vlc_interrupt_t
*ctx
)
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
;
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
)
117 assert(ctx
== vlc_interrupt_var
);
119 vlc_mutex_lock(&ctx
->lock
);
120 assert(ctx
->callback
== NULL
);
124 if (unlikely(ctx
->interrupted
))
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
)
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
)
153 ctx
->interrupted
= false;
155 vlc_mutex_unlock(&ctx
->lock
);
159 void vlc_interrupt_register(void (*cb
)(void *), void *opaque
)
161 vlc_interrupt_t
*ctx
= vlc_interrupt_var
;
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
)
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
;
201 return vlc_sem_wait(sem
), 0;
203 vlc_interrupt_prepare(ctx
, vlc_interrupt_sem
, sem
);
205 vlc_cleanup_push(vlc_interrupt_cleanup
, ctx
);
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
;
229 return vlc_tick_wait(deadline
), 0;
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);
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
;
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];
276 assert(from
->callback
== vlc_interrupt_forward_wake
);
277 assert(from
->data
== data
);
278 return vlc_interrupt_finish(from
);
282 static void vlc_poll_i11e_wake(void *opaque
)
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
;
298 vlc_interrupt_finish(ctx
);
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
)
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
);
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
)
348 read(fd
[0], &dummy
, sizeof (dummy
));
353 if (vlc_interrupt_finish(ctx
))
359 canc
= vlc_savecancel();
363 vlc_restorecancel(canc
);
367 int vlc_poll_i11e(struct pollfd
*fds
, unsigned nfds
, int timeout
)
369 vlc_interrupt_t
*ctx
= vlc_interrupt_var
;
371 return poll(fds
, nfds
, timeout
);
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
);
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
);
396 # include <sys/uio.h>
397 # ifdef HAVE_SYS_SOCKET_H
398 # include <sys/socket.h>
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
)
421 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
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
)
438 ufd
.events
= POLLOUT
;
440 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
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
)
475 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
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
= {
488 .msg_namelen
= (addrlen
!= NULL
) ? *addrlen
: 0,
493 ssize_t ret
= vlc_recvmsg_i11e(fd
, &msg
, flags
);
494 if (ret
>= 0 && addrlen
!= NULL
)
495 *addrlen
= msg
.msg_namelen
;
499 ssize_t
vlc_sendmsg_i11e(int fd
, const struct msghdr
*msg
, int flags
)
504 ufd
.events
= POLLOUT
;
506 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
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
,
523 return vlc_sendmsg_i11e(fd
, &msg
, flags
);
526 int vlc_accept_i11e(int fd
, struct sockaddr
*addr
, socklen_t
*addrlen
,
534 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
537 return vlc_accept(fd
, addr
, addrlen
, blocking
);
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
551 QueueUserAPC(vlc_poll_i11e_wake_self
, th
, 0);
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
);
566 int vlc_poll_i11e(struct pollfd
*fds
, unsigned nfds
, int timeout
)
568 vlc_interrupt_t
*ctx
= vlc_interrupt_var
;
570 return vlc_poll(fds
, nfds
, timeout
);
575 if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
576 GetCurrentProcess(), &th
, 0, FALSE
,
577 DUPLICATE_SAME_ACCESS
))
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
);
589 if (vlc_interrupt_finish(ctx
))
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
)
635 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
638 ssize_t ret
= recvfrom(fd
, buf
, len
, flags
, addr
, addrlen
);
639 if (ret
< 0 && WSAGetLastError() == WSAEWOULDBLOCK
)
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
)
656 ufd
.events
= POLLOUT
;
658 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
661 ssize_t ret
= sendto(fd
, buf
, len
, flags
, addr
, addrlen
);
662 if (ret
< 0 && WSAGetLastError() == WSAEWOULDBLOCK
)
667 int vlc_accept_i11e(int fd
, struct sockaddr
*addr
, socklen_t
*addrlen
,
675 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
678 int cfd
= vlc_accept(fd
, addr
, addrlen
, blocking
);
679 if (cfd
< 0 && WSAGetLastError() == WSAEWOULDBLOCK
)