Coverage Report

Created: 2026-06-25 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/misc/thread_tools.c
Line
Count
Source
1
/* Copyright (C) 2018 the mpv developers
2
 *
3
 * Permission to use, copy, modify, and/or distribute this software for any
4
 * purpose with or without fee is hereby granted, provided that the above
5
 * copyright notice and this permission notice appear in all copies.
6
 *
7
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
 */
15
16
#include <assert.h>
17
#include <errno.h>
18
#include <stdatomic.h>
19
#include <string.h>
20
#include <sys/types.h>
21
22
#ifdef _WIN32
23
#include <windows.h>
24
#else
25
#include <poll.h>
26
#endif
27
28
#include "common/common.h"
29
#include "misc/linked_list.h"
30
#include "osdep/io.h"
31
#include "osdep/timer.h"
32
33
#include "thread_tools.h"
34
35
uintptr_t mp_waiter_wait(struct mp_waiter *waiter)
36
284k
{
37
284k
    mp_mutex_lock(&waiter->lock);
38
284k
    while (!waiter->done)
39
0
        mp_cond_wait(&waiter->wakeup, &waiter->lock);
40
284k
    mp_mutex_unlock(&waiter->lock);
41
42
284k
    uintptr_t ret = waiter->value;
43
44
    // We document that after mp_waiter_wait() the waiter object becomes
45
    // invalid. (It strictly returns only after mp_waiter_wakeup() has returned,
46
    // and the object is "single-shot".) So destroy it here.
47
48
    // Normally, we expect that the system uses futexes, in which case the
49
    // following functions will do nearly nothing. This is true for Windows
50
    // and Linux. But some lesser OSes still might allocate kernel objects
51
    // when initializing mutexes, so destroy them here.
52
284k
    mp_mutex_destroy(&waiter->lock);
53
284k
    mp_cond_destroy(&waiter->wakeup);
54
55
284k
    memset(waiter, 0xCA, sizeof(*waiter)); // for debugging
56
57
284k
    return ret;
58
284k
}
59
60
void mp_waiter_wakeup(struct mp_waiter *waiter, uintptr_t value)
61
284k
{
62
284k
    mp_mutex_lock(&waiter->lock);
63
284k
    mp_assert(!waiter->done);
64
284k
    waiter->done = true;
65
284k
    waiter->value = value;
66
284k
    mp_cond_signal(&waiter->wakeup);
67
284k
    mp_mutex_unlock(&waiter->lock);
68
284k
}
69
70
bool mp_waiter_poll(struct mp_waiter *waiter)
71
151k
{
72
151k
    mp_mutex_lock(&waiter->lock);
73
151k
    bool r = waiter->done;
74
151k
    mp_mutex_unlock(&waiter->lock);
75
151k
    return r;
76
151k
}
77
78
struct mp_cancel {
79
    mp_mutex lock;
80
    mp_cond wakeup;
81
82
    // Semaphore state and "mirrors".
83
    atomic_bool triggered;
84
    void (*cb)(void *ctx);
85
    void *cb_ctx;
86
    int wakeup_pipe[2];
87
    void *win32_event; // actually HANDLE
88
89
    // Slave list. These are automatically notified as well.
90
    struct {
91
        struct mp_cancel *head, *tail;
92
    } slaves;
93
94
    // For slaves. Synchronization is managed by parent.lock!
95
    struct mp_cancel *parent;
96
    struct {
97
        struct mp_cancel *next, *prev;
98
    } siblings;
99
};
100
101
static void cancel_destroy(void *p)
102
385k
{
103
385k
    struct mp_cancel *c = p;
104
105
385k
    mp_assert(!c->slaves.head); // API user error
106
107
385k
    mp_cancel_set_parent(c, NULL);
108
109
385k
    if (c->wakeup_pipe[0] >= 0) {
110
0
        close(c->wakeup_pipe[0]);
111
0
        close(c->wakeup_pipe[1]);
112
0
    }
113
114
#ifdef _WIN32
115
    if (c->win32_event)
116
        CloseHandle(c->win32_event);
117
#endif
118
119
385k
    mp_mutex_destroy(&c->lock);
120
385k
    mp_cond_destroy(&c->wakeup);
121
385k
}
122
123
struct mp_cancel *mp_cancel_new(void *talloc_ctx)
124
385k
{
125
385k
    struct mp_cancel *c = talloc_ptrtype(talloc_ctx, c);
126
385k
    talloc_set_destructor(c, cancel_destroy);
127
385k
    *c = (struct mp_cancel){
128
385k
        .triggered = false,
129
385k
        .wakeup_pipe = {-1, -1},
130
385k
    };
131
385k
    mp_mutex_init(&c->lock);
132
385k
    mp_cond_init(&c->wakeup);
133
385k
    return c;
134
385k
}
135
136
static void trigger_locked(struct mp_cancel *c)
137
609k
{
138
609k
    atomic_store(&c->triggered, true);
139
140
609k
    mp_cond_broadcast(&c->wakeup); // condition bound to c->triggered
141
142
609k
    if (c->cb)
143
0
        c->cb(c->cb_ctx);
144
145
610k
    for (struct mp_cancel *sub = c->slaves.head; sub; sub = sub->siblings.next)
146
485
        mp_cancel_trigger(sub);
147
148
609k
    if (c->wakeup_pipe[1] >= 0)
149
0
        (void)write(c->wakeup_pipe[1], &(char){0}, 1);
150
151
#ifdef _WIN32
152
    if (c->win32_event)
153
        SetEvent(c->win32_event);
154
#endif
155
609k
}
156
157
void mp_cancel_trigger(struct mp_cancel *c)
158
609k
{
159
609k
    mp_mutex_lock(&c->lock);
160
609k
    trigger_locked(c);
161
609k
    mp_mutex_unlock(&c->lock);
162
609k
}
163
164
void mp_cancel_reset(struct mp_cancel *c)
165
374k
{
166
374k
    mp_mutex_lock(&c->lock);
167
168
374k
    atomic_store(&c->triggered, false);
169
170
374k
    if (c->wakeup_pipe[0] >= 0) {
171
        // Flush it fully.
172
0
        while (1) {
173
0
            int r = read(c->wakeup_pipe[0], &(char[256]){0}, 256);
174
0
            if (r <= 0 && !(r < 0 && errno == EINTR))
175
0
                break;
176
0
        }
177
0
    }
178
179
#ifdef _WIN32
180
    if (c->win32_event)
181
        ResetEvent(c->win32_event);
182
#endif
183
184
374k
    mp_mutex_unlock(&c->lock);
185
374k
}
186
187
bool mp_cancel_test(struct mp_cancel *c)
188
28.0M
{
189
28.0M
    return c ? atomic_load_explicit(&c->triggered, memory_order_relaxed) : false;
190
28.0M
}
191
192
bool mp_cancel_wait(struct mp_cancel *c, double timeout)
193
0
{
194
0
    int64_t wait_until = mp_time_ns_add(mp_time_ns(), timeout);
195
0
    mp_mutex_lock(&c->lock);
196
0
    while (!mp_cancel_test(c)) {
197
0
        if (mp_cond_timedwait_until(&c->wakeup, &c->lock, wait_until))
198
0
            break;
199
0
    }
200
0
    mp_mutex_unlock(&c->lock);
201
202
0
    return mp_cancel_test(c);
203
0
}
204
205
// If a new notification mechanism was added, and the mp_cancel state was
206
// already triggered, make sure the newly added mechanism is also triggered.
207
static void retrigger_locked(struct mp_cancel *c)
208
341k
{
209
341k
    if (mp_cancel_test(c))
210
54
        trigger_locked(c);
211
341k
}
212
213
void mp_cancel_set_cb(struct mp_cancel *c, void (*cb)(void *ctx), void *ctx)
214
0
{
215
0
    mp_mutex_lock(&c->lock);
216
0
    c->cb = cb;
217
0
    c->cb_ctx = ctx;
218
0
    retrigger_locked(c);
219
0
    mp_mutex_unlock(&c->lock);
220
0
}
221
222
void mp_cancel_set_parent(struct mp_cancel *slave, struct mp_cancel *parent)
223
738k
{
224
    // We can access c->parent without synchronization, because:
225
    //  - concurrent mp_cancel_set_parent() calls to slave are not allowed
226
    //  - slave->parent needs to stay valid as long as the slave exists
227
738k
    if (slave->parent == parent)
228
139k
        return;
229
599k
    if (slave->parent) {
230
341k
        mp_mutex_lock(&slave->parent->lock);
231
341k
        LL_REMOVE(siblings, &slave->parent->slaves, slave);
232
341k
        mp_mutex_unlock(&slave->parent->lock);
233
341k
    }
234
599k
    slave->parent = parent;
235
599k
    if (slave->parent) {
236
341k
        mp_mutex_lock(&slave->parent->lock);
237
341k
        LL_APPEND(siblings, &slave->parent->slaves, slave);
238
341k
        retrigger_locked(slave->parent);
239
341k
        mp_mutex_unlock(&slave->parent->lock);
240
341k
    }
241
599k
}
242
243
int mp_cancel_get_fd(struct mp_cancel *c)
244
0
{
245
0
    mp_mutex_lock(&c->lock);
246
0
    if (c->wakeup_pipe[0] < 0) {
247
#if defined(__GNUC__) && !defined(__clang__)
248
# pragma GCC diagnostic push
249
# pragma GCC diagnostic ignored "-Wstringop-overflow="
250
#endif
251
0
        mp_make_wakeup_pipe(c->wakeup_pipe);
252
#if defined(__GNUC__) && !defined(__clang__)
253
# pragma GCC diagnostic pop
254
#endif
255
0
        retrigger_locked(c);
256
0
    }
257
0
    mp_mutex_unlock(&c->lock);
258
259
260
0
    return c->wakeup_pipe[0];
261
0
}
262
263
#ifdef _WIN32
264
void *mp_cancel_get_event(struct mp_cancel *c)
265
{
266
    mp_mutex_lock(&c->lock);
267
    if (!c->win32_event) {
268
        c->win32_event = CreateEventW(NULL, TRUE, FALSE, NULL);
269
        retrigger_locked(c);
270
    }
271
    mp_mutex_unlock(&c->lock);
272
273
    return c->win32_event;
274
}
275
#endif