blob: f00514f8271e4d3eb8c22c94c57127b56c9ce571 [file]
#include <assert.h>
#include <emscripten/console.h>
#include <emscripten/emscripten.h>
#include <emscripten/heap.h>
#include <emscripten/proxying.h>
#include <pthread.h>
#include <sched.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <unistd.h>
// Test that task_queues destroyed with pending notifications are added to the
// zombie list and are culled when a new task queue is allocated after their
// notifications have been cleared.
#if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer)
#define SANITIZER
#endif
// Proxying queues accessed from the worker thread.
em_proxying_queue* queues[2];
#ifndef SANITIZER
// If we are not using sanitizers (which need to use their own allocators),
// override free so we can track when queues are actually freed.
_Atomic int frees = 0;
void __attribute__((noinline)) free(void* ptr) {
frees++;
emscripten_builtin_free(ptr);
}
#endif // SANITIZER
_Atomic bool worker_started = false;
_Atomic bool should_execute = false;
_Atomic int executed[2] = {};
#define EVENT_LOOP_TURNS 2
// Delay culling until the event loop has turned enough for the notifications on
// the two zombie task queues to have been received and cleared, allowing them
// to be freed.
void increment_flag(void* arg) {
_Atomic int* flag = arg;
if (atomic_fetch_add(flag, 1) + 1 < EVENT_LOOP_TURNS) {
emscripten_async_call(increment_flag, arg, 0);
}
}
void task(void* arg) { emscripten_async_call(increment_flag, arg, 0); }
void* execute_and_free_queue(void* arg) {
// Signal the main thread to proxy work to us.
worker_started = true;
// Wait until we are signaled to execute the queue.
while (!should_execute) {
sched_yield();
}
// Execute the proxied work then free the empty queues.
for (int i = 0; i < 2; i++) {
emscripten_proxy_execute_queue(queues[i]);
em_proxying_queue_destroy(queues[i]);
}
// Exit with a live runtime so the queued work notification can be received
// and cleared, allowing the zombie task queues to be culled.
emscripten_exit_with_live_runtime();
}
void nop(void* arg) {}
int main() {
emscripten_console_log("start");
for (int i = 0; i < 2; i++) {
queues[i] = em_proxying_queue_create();
assert(queues[i]);
}
// Create the worker and wait for it to enter Wasm.
pthread_t worker;
pthread_create(&worker, NULL, execute_and_free_queue, NULL);
while (!worker_started) {
sched_yield();
}
// Now that the worker has started, proxy work to it. This will allocate task
// queues and send the worker notifications. The worker will execute the tasks
// and destroy the proxy queues before returning to the event loop to receive
// the notifications, so the task queues will end up on the zombie list.
for (int i = 0; i < 2; i++) {
emscripten_proxy_async(queues[i], worker, task, &executed[i]);
}
should_execute = true;
// Wait for the queues to be destroyed and for the worker event loop to turn
// enough to clear the notifications
while (executed[0] < EVENT_LOOP_TURNS || executed[1] < EVENT_LOOP_TURNS) {
sched_yield();
}
#ifndef SANITIZER
// Our zombies should not have been freed yet.
int frees_before_cull = frees;
#endif // SANITIZER
// Cull the zombies! (by forcing a new task queue to be allocated)
em_proxying_queue* culler = em_proxying_queue_create();
emscripten_proxy_async(culler, pthread_self(), nop, NULL);
#ifndef SANITIZER
// Now they should be free.
int frees_after_cull = frees;
assert(frees_after_cull > frees_before_cull);
// For each of the two culled task queues, the queue itself and its task
// vector should have been freed.
assert(frees_after_cull - frees_before_cull == 4);
#endif // SANITIZER
// If we try again, there should be nothing left to cull.
em_proxying_queue* non_culler = em_proxying_queue_create();
emscripten_proxy_async(non_culler, pthread_self(), nop, NULL);
#ifndef SANITIZER
assert(frees == frees_after_cull);
#endif
em_proxying_queue_destroy(culler);
em_proxying_queue_destroy(non_culler);
emscripten_console_log("done");
}