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
);
74 vlc_mutex_destroy(&ctx
->lock
);
77 void vlc_interrupt_destroy(vlc_interrupt_t
*ctx
)
80 vlc_interrupt_deinit(ctx
);
84 void vlc_interrupt_raise(vlc_interrupt_t
*ctx
)
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
;
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
)
118 assert(ctx
== vlc_interrupt_var
);
120 vlc_mutex_lock(&ctx
->lock
);
121 assert(ctx
->callback
== NULL
);
125 if (unlikely(ctx
->interrupted
))
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
)
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
)
154 ctx
->interrupted
= false;
156 vlc_mutex_unlock(&ctx
->lock
);
160 void vlc_interrupt_register(void (*cb
)(void *), void *opaque
)
162 vlc_interrupt_t
*ctx
= vlc_interrupt_var
;
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
)
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
;
202 return vlc_sem_wait(sem
), 0;
204 vlc_interrupt_prepare(ctx
, vlc_interrupt_sem
, sem
);
206 vlc_cleanup_push(vlc_interrupt_cleanup
, ctx
);
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(vlc_tick_t deadline
)
230 vlc_interrupt_t
*ctx
= vlc_interrupt_var
;
232 return vlc_tick_wait(deadline
), 0;
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);
244 vlc_mutex_unlock(&ctx
->lock
);
246 int ret
= vlc_interrupt_finish(ctx
);
247 vlc_cond_destroy(&wait
);
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
;
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];
281 assert(from
->callback
== vlc_interrupt_forward_wake
);
282 assert(from
->data
== data
);
283 return vlc_interrupt_finish(from
);
287 static void vlc_poll_i11e_wake(void *opaque
)
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
;
303 vlc_interrupt_finish(ctx
);
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
)
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
);
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
)
353 read(fd
[0], &dummy
, sizeof (dummy
));
358 if (vlc_interrupt_finish(ctx
))
364 canc
= vlc_savecancel();
368 vlc_restorecancel(canc
);
372 int vlc_poll_i11e(struct pollfd
*fds
, unsigned nfds
, int timeout
)
374 vlc_interrupt_t
*ctx
= vlc_interrupt_var
;
376 return poll(fds
, nfds
, timeout
);
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
);
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
);
401 # include <sys/uio.h>
402 # ifdef HAVE_SYS_SOCKET_H
403 # include <sys/socket.h>
407 /* There are currently no ways to atomically force a non-blocking read or write
408 * operations. Even for sockets, the MSG_DONTWAIT flag is non-standard.
410 * So in the event that more than one thread tries to read or write on the same
411 * file at the same time, there is a race condition where these functions might
412 * block in spite of an interruption. This should never happen in practice.
416 * Wrapper for readv() that returns the EINTR error upon VLC I/O interruption.
417 * @warning This function ignores the non-blocking file flag.
419 ssize_t
vlc_readv_i11e(int fd
, struct iovec
*iov
, int count
)
426 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
428 return readv(fd
, iov
, count
);
432 * Wrapper for writev() that returns the EINTR error upon VLC I/O interruption.
434 * @note Like writev(), once some but not all bytes are written, the function
435 * might wait for write completion, regardless of signals and interruptions.
436 * @warning This function ignores the non-blocking file flag.
438 ssize_t
vlc_writev_i11e(int fd
, const struct iovec
*iov
, int count
)
443 ufd
.events
= POLLOUT
;
445 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
447 return writev(fd
, iov
, count
);
451 * Wrapper for read() that returns the EINTR error upon VLC I/O interruption.
452 * @warning This function ignores the non-blocking file flag.
454 ssize_t
vlc_read_i11e(int fd
, void *buf
, size_t count
)
456 struct iovec iov
= { .iov_base
= buf
, .iov_len
= count
};
457 return vlc_readv_i11e(fd
, &iov
, 1);
461 * Wrapper for write() that returns the EINTR error upon VLC I/O interruption.
463 * @note Like write(), once some but not all bytes are written, the function
464 * might wait for write completion, regardless of signals and interruptions.
465 * @warning This function ignores the non-blocking file flag.
467 ssize_t
vlc_write_i11e(int fd
, const void *buf
, size_t count
)
469 struct iovec iov
= { .iov_base
= (void*)buf
, .iov_len
= count
};
470 return vlc_writev_i11e(fd
, &iov
, 1);
473 ssize_t
vlc_recvmsg_i11e(int fd
, struct msghdr
*msg
, int flags
)
480 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
482 /* NOTE: MSG_OOB and MSG_PEEK should work fine here.
483 * MSG_WAITALL is not supported at this point. */
484 return recvmsg(fd
, msg
, flags
);
487 ssize_t
vlc_recvfrom_i11e(int fd
, void *buf
, size_t len
, int flags
,
488 struct sockaddr
*addr
, socklen_t
*addrlen
)
490 struct iovec iov
= { .iov_base
= buf
, .iov_len
= len
};
491 struct msghdr msg
= {
493 .msg_namelen
= (addrlen
!= NULL
) ? *addrlen
: 0,
498 ssize_t ret
= vlc_recvmsg_i11e(fd
, &msg
, flags
);
499 if (ret
>= 0 && addrlen
!= NULL
)
500 *addrlen
= msg
.msg_namelen
;
504 ssize_t
vlc_sendmsg_i11e(int fd
, const struct msghdr
*msg
, int flags
)
509 ufd
.events
= POLLOUT
;
511 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
513 /* NOTE: MSG_EOR, MSG_OOB and MSG_NOSIGNAL should all work fine here. */
514 return sendmsg(fd
, msg
, flags
);
517 ssize_t
vlc_sendto_i11e(int fd
, const void *buf
, size_t len
, int flags
,
518 const struct sockaddr
*addr
, socklen_t addrlen
)
520 struct iovec iov
= { .iov_base
= (void *)buf
, .iov_len
= len
};
521 struct msghdr msg
= {
522 .msg_name
= (struct sockaddr
*)addr
,
523 .msg_namelen
= addrlen
,
528 return vlc_sendmsg_i11e(fd
, &msg
, flags
);
531 int vlc_accept_i11e(int fd
, struct sockaddr
*addr
, socklen_t
*addrlen
,
539 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
542 return vlc_accept(fd
, addr
, addrlen
, blocking
);
547 static void CALLBACK
vlc_poll_i11e_wake_self(ULONG_PTR data
)
549 (void) data
; /* Nothing to do */
552 static void vlc_poll_i11e_wake(void *opaque
)
554 #if !VLC_WINSTORE_APP || _WIN32_WINNT >= 0x0A00
556 QueueUserAPC(vlc_poll_i11e_wake_self
, th
, 0);
562 static void vlc_poll_i11e_cleanup(void *opaque
)
564 vlc_interrupt_t
*ctx
= opaque
;
565 HANDLE th
= ctx
->data
;
567 vlc_interrupt_finish(ctx
);
571 int vlc_poll_i11e(struct pollfd
*fds
, unsigned nfds
, int timeout
)
573 vlc_interrupt_t
*ctx
= vlc_interrupt_var
;
575 return vlc_poll(fds
, nfds
, timeout
);
580 if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
581 GetCurrentProcess(), &th
, 0, FALSE
,
582 DUPLICATE_SAME_ACCESS
))
588 vlc_interrupt_prepare(ctx
, vlc_poll_i11e_wake
, th
);
590 vlc_cleanup_push(vlc_poll_i11e_cleanup
, ctx
);
591 ret
= vlc_poll(fds
, nfds
, timeout
);
594 if (vlc_interrupt_finish(ctx
))
604 ssize_t
vlc_readv_i11e(int fd
, struct iovec
*iov
, int count
)
606 (void) fd
; (void) iov
; (void) count
;
607 vlc_assert_unreachable();
610 ssize_t
vlc_writev_i11e(int fd
, const struct iovec
*iov
, int count
)
612 (void) fd
; (void) iov
; (void) count
;
613 vlc_assert_unreachable();
616 ssize_t
vlc_read_i11e(int fd
, void *buf
, size_t count
)
618 return read(fd
, buf
, count
);
621 ssize_t
vlc_write_i11e(int fd
, const void *buf
, size_t count
)
623 return write(fd
, buf
, count
);
626 ssize_t
vlc_recvmsg_i11e(int fd
, struct msghdr
*msg
, int flags
)
628 (void) fd
; (void) msg
; (void) flags
;
629 vlc_assert_unreachable();
632 ssize_t
vlc_recvfrom_i11e(int fd
, void *buf
, size_t len
, int flags
,
633 struct sockaddr
*addr
, socklen_t
*addrlen
)
640 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
643 ssize_t ret
= recvfrom(fd
, buf
, len
, flags
, addr
, addrlen
);
644 if (ret
< 0 && WSAGetLastError() == WSAEWOULDBLOCK
)
649 ssize_t
vlc_sendmsg_i11e(int fd
, const struct msghdr
*msg
, int flags
)
651 (void) fd
; (void) msg
; (void) flags
;
652 vlc_assert_unreachable();
655 ssize_t
vlc_sendto_i11e(int fd
, const void *buf
, size_t len
, int flags
,
656 const struct sockaddr
*addr
, socklen_t addrlen
)
661 ufd
.events
= POLLOUT
;
663 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
666 ssize_t ret
= sendto(fd
, buf
, len
, flags
, addr
, addrlen
);
667 if (ret
< 0 && WSAGetLastError() == WSAEWOULDBLOCK
)
672 int vlc_accept_i11e(int fd
, struct sockaddr
*addr
, socklen_t
*addrlen
,
680 if (vlc_poll_i11e(&ufd
, 1, -1) < 0)
683 int cfd
= vlc_accept(fd
, addr
, addrlen
, blocking
);
684 if (cfd
< 0 && WSAGetLastError() == WSAEWOULDBLOCK
)