From ae26d626c846a996ff4a298fb6cae2b56790e35d Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 20:09:45 +0100 Subject: [PATCH] Test queued cancellation against running work and reserved cleanup --- tests/test_member_files.c | 59 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/test_member_files.c b/tests/test_member_files.c index a3d68b4d0..932fdc39a 100644 --- a/tests/test_member_files.c +++ b/tests/test_member_files.c @@ -519,6 +519,63 @@ static void worker_completion_test(void) { } #endif +struct cancel_case { + pthread_mutex_t mutex; + pthread_cond_t condition; + int started, released, worked, done, cancelled; +}; + +static void cancel_block(void* arg) { + struct cancel_case* t = arg; + pthread_mutex_lock(&t->mutex); t->started = 1; pthread_cond_signal(&t->condition); + while (!t->released) pthread_cond_wait(&t->condition, &t->mutex); + pthread_mutex_unlock(&t->mutex); t->worked++; +} +static void cancel_work(void* arg) { ((struct cancel_case*)arg)->worked++; } +static void cancel_done(void* arg, int error) { + struct cancel_case* t = arg; + EXPECT(!error || error == MF_WORK_CANCELLED, "cancel completion status"); + t->done++; t->cancelled += error == MF_WORK_CANCELLED; +} + +/* Диск стоит на барьере: отмена головы/хвоста не выполняет work и сохраняет обязательную cleanup. */ +static void worker_cancel_test(void) { + size_t before = u_get_allocated_count(); + struct cancel_case running = { .mutex = PTHREAD_MUTEX_INITIALIZER, .condition = PTHREAD_COND_INITIALIZER }; + struct cancel_case head = {0}, tail = {0}; + struct UASYNC* ua = uasync_create(); + struct mf_worker* worker = ua ? mf_worker_create(ua) : NULL; + struct mf_work* cleanup = mf_worker_reserve(); + EXPECT(worker && cleanup, "cancel worker fixture"); + if (worker && cleanup) { + mf_worker_submit(worker, cancel_block, &running, cancel_done, &running); + pthread_mutex_lock(&running.mutex); + while (!running.started) pthread_cond_wait(&running.condition, &running.mutex); + pthread_mutex_unlock(&running.mutex); + mf_worker_submit(worker, cancel_work, &head, cancel_done, &head); + mf_worker_submit_reserved(worker, cleanup, cancel_work, &head, cancel_done, &head); cleanup = NULL; + mf_worker_submit(worker, cancel_work, &tail, cancel_done, &tail); + EXPECT(mf_worker_cancel(worker, &running) == 0, "running work cannot be cancelled"); + EXPECT(mf_worker_cancel(worker, &head) == 1, "cancel head preserves reserved cleanup with same data"); + EXPECT(mf_worker_cancel(worker, &tail) == 1, "cancel tail"); + EXPECT(mf_worker_cancel(worker, &head) == 0, "cancel is idempotent"); + uasync_poll(ua, 0); + EXPECT(head.done == 1 && head.cancelled == 1 && tail.done == 1 && tail.cancelled == 1, + "cancel completion delivered while disk remains blocked"); + EXPECT(!head.worked && !tail.worked && !running.done, "cancelled work has no side effects"); + pthread_mutex_lock(&running.mutex); running.released = 1; pthread_cond_signal(&running.condition); + pthread_mutex_unlock(&running.mutex); + } + mf_worker_destroy(worker); mf_worker_unreserve(cleanup); + if (ua) { uasync_poll(ua, 0); uasync_destroy(ua, 1); } + EXPECT(running.worked == 1 && running.done == 1 && !running.cancelled, "running command finishes once"); + EXPECT(head.worked == 1 && head.done == 2 && head.cancelled == 1, "reserved cleanup finishes once"); + EXPECT(!tail.worked && tail.done == 1 && tail.cancelled == 1, "cancelled tail never executes"); + pthread_cond_destroy(&running.condition); pthread_mutex_destroy(&running.mutex); + EXPECT(u_get_allocated_count() == before, "cancelled jobs/completions freed"); + DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "worker cancellation: queued jobs skipped, running command and reserved cleanup completed"); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_DEBUG); char directory[1024] = "/tmp/utun_member_files_XXXXXX"; @@ -528,7 +585,7 @@ int main(void) { #ifndef _WIN32 local_fifo_test(directory); large_offset_test(directory); #endif - worker_test(); + worker_test(); worker_cancel_test(); #ifdef __linux__ worker_completion_test(); #endif