Skip to content

Commit ae9bfff

Browse files
authored
Fix flake in test_pthread_proxying_refcount (#25097)
It apparently takes more than one turn of the worker thread's event loop to ensure that the notifications on the zombie task queues have been cleared so the zombies can be culled. Update the test to wait two turns of the event loop instead of one. Also add new synchronization forcing the main thread to wait until the worker thread has entered Wasm before proxying work to it. This prevents the proxied work notifications from somehow being cleared before the worker thread destroys the proxy queues, which would prevent the task queues from ever being placed on the zombie list in the first place. Finally, generally improve comments and make the test assertions more specific. Fixes #19795.
1 parent 471fe50 commit ae9bfff

File tree

1 file changed

+39
-19
lines changed

1 file changed

+39
-19
lines changed

test/pthread/test_pthread_proxying_refcount.c

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
#include <emscripten/proxying.h>
66
#include <pthread.h>
77
#include <sched.h>
8+
#include <stdatomic.h>
89
#include <stdbool.h>
910
#include <unistd.h>
1011

12+
// Test that task_queues destroyed with pending notifications are added to the
13+
// zombie list and are culled when a new task queue is allocated after their
14+
// notifications have been cleared.
15+
1116
#if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer)
1217
#define SANITIZER
1318
#endif
@@ -29,22 +34,28 @@ void __attribute__((noinline)) free(void* ptr) {
2934

3035
#endif // SANITIZER
3136

32-
_Atomic int should_execute = 0;
37+
_Atomic bool worker_started = false;
38+
_Atomic bool should_execute = false;
3339
_Atomic int executed[2] = {};
34-
_Atomic int processed = 0;
3540

36-
EMSCRIPTEN_KEEPALIVE
37-
void register_processed(void) {
38-
processed++;
39-
}
41+
#define EVENT_LOOP_TURNS 2
4042

41-
void set_flag(void* arg) { *(_Atomic int*)arg = 1; }
43+
// Delay culling until the event loop has turned enough for the notifications on
44+
// the two zombie task queues to have been received and cleared, allowing them
45+
// to be freed.
46+
void increment_flag(void* arg) {
47+
_Atomic int* flag = arg;
48+
if (atomic_fetch_add(flag, 1) + 1 < EVENT_LOOP_TURNS) {
49+
emscripten_async_call(increment_flag, arg, 0);
50+
}
51+
}
4252

43-
// Delay setting the flag until the next turn of the event loop so it can be set
44-
// after the proxying queue is destroyed.
45-
void task(void* arg) { emscripten_async_call(set_flag, arg, 0); }
53+
void task(void* arg) { emscripten_async_call(increment_flag, arg, 0); }
4654

4755
void* execute_and_free_queue(void* arg) {
56+
// Signal the main thread to proxy work to us.
57+
worker_started = true;
58+
4859
// Wait until we are signaled to execute the queue.
4960
while (!should_execute) {
5061
sched_yield();
@@ -56,10 +67,8 @@ void* execute_and_free_queue(void* arg) {
5667
em_proxying_queue_destroy(queues[i]);
5768
}
5869

59-
// Exit with a live runtime so the queued work notification is received and we
60-
// try to execute the queue again, even though we already executed all its
61-
// work and we are now just waiting for the notifications to be received so we
62-
// can free it.
70+
// Exit with a live runtime so the queued work notification can be received
71+
// and cleared, allowing the zombie task queues to be culled.
6372
emscripten_exit_with_live_runtime();
6473
}
6574

@@ -72,17 +81,25 @@ int main() {
7281
assert(queues[i]);
7382
}
7483

75-
// Create the worker and send it tasks.
84+
// Create the worker and wait for it to enter Wasm.
7685
pthread_t worker;
7786
pthread_create(&worker, NULL, execute_and_free_queue, NULL);
87+
while (!worker_started) {
88+
sched_yield();
89+
}
90+
91+
// Now that the worker has started, proxy work to it. This will allocate task
92+
// queues and send the worker notifications. The worker will execute the tasks
93+
// and destroy the proxy queues before returning to the event loop to receive
94+
// the notifications, so the task queues will end up on the zombie list.
7895
for (int i = 0; i < 2; i++) {
7996
emscripten_proxy_async(queues[i], worker, task, &executed[i]);
8097
}
81-
should_execute = 1;
98+
should_execute = true;
8299

83-
// Wait for the tasks to be executed. The queues will have been destroyed
84-
// after this.
85-
while (!executed[0] || !executed[1]) {
100+
// Wait for the queues to be destroyed and for the worker event loop to turn
101+
// enough to clear the notifications
102+
while (executed[0] < EVENT_LOOP_TURNS || executed[1] < EVENT_LOOP_TURNS) {
86103
sched_yield();
87104
}
88105

@@ -99,6 +116,9 @@ int main() {
99116
// Now they should be free.
100117
int frees_after_cull = frees;
101118
assert(frees_after_cull > frees_before_cull);
119+
// For each of the two culled task queues, the queue itself and its task
120+
// vector should have been freed.
121+
assert(frees_after_cull - frees_before_cull == 4);
102122
#endif // SANITIZER
103123

104124
// If we try again, there should be nothing left to cull.

0 commit comments

Comments
 (0)