From 3100985e4c4f9d67a539bd52447633fdc2844a87 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 10:16:57 +0200 Subject: [PATCH] Specify media worker ownership errors and primitive contracts --- src/media_async/media_async.c | 15 +++++++++---- src/media_async/media_async.h | 40 +++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/media_async/media_async.c b/src/media_async/media_async.c index 429eba3c..64f8fbde 100644 --- a/src/media_async/media_async.c +++ b/src/media_async/media_async.c @@ -3,6 +3,7 @@ #include "../../lib/u_async.h" #include "../../lib/mem.h" #include "../../lib/debug_config.h" +#include "../../lib/file_io.h" #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -66,6 +68,7 @@ struct media_async* media_async_create(void) { void media_async_destroy(struct media_async* ma) { if (!ma) return; + if (ma->closing) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "media owner recursive destroy rejected"); return; } ma->closing = 1; unsigned cancelled = 0; while (ma->jobs) { @@ -82,14 +85,18 @@ void media_async_destroy(struct media_async* ma) { void media_async_submit(struct media_async* ma, struct UASYNC* ua, ma_work_fn work, void* data, ma_done_fn done, void* arg) { - if (!ma || !ua || !work || !done || ma->closing) { - DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "media task rejected: invalid arguments or stopping owner"); - if (done) done(arg, MEDIA_ASYNC_CANCELLED); + if (!ma || !ua || !work || !done) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media task rejected: invalid arguments ma=%p ua=%p work=%d done=%d", + (void*)ma, (void*)ua, work != NULL, done != NULL); + if (done) done(arg, -1); return; } + if (ma->closing) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "media task rejected: owner stopping"); done(arg, MEDIA_ASYNC_CANCELLED); return; } unsigned active = 0; for (struct ma_thread_ctx* pending = ma->jobs; pending; pending = pending->next) active++; - if (active >= 16) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "media task limit reached active=%u", active); done(arg, -1); return; } + if (active >= MEDIA_ASYNC_MAX_TASKS) { + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "media task limit reached active=%u", active); done(arg, -1); return; + } struct ma_thread_ctx* job = u_calloc(1, sizeof(*job)); if (!job) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media task allocation failed"); done(arg, -1); return; } job->owner = ma; job->ua = ua; job->work = work; job->data = data; job->done = done; job->arg = arg; diff --git a/src/media_async/media_async.h b/src/media_async/media_async.h index 4bb605cc..98b03614 100644 --- a/src/media_async/media_async.h +++ b/src/media_async/media_async.h @@ -1,7 +1,8 @@ /* media_async — владелец фоновых задач обработки файлов и криптографии. - * Каждая work выполняется в отдельном pthread; done — в потоке uasync. - * create/submit/destroy вызываются из этого потока. destroy ждёт workers и - * завершает ожидающие done с CANCELLED, пока данные вызывающего ещё существуют. */ + * Каждая work выполняется в отдельном pthread; done — в потоке владельца uasync. + * create/submit/destroy вызываются из этого потока; ua живёт до окончания destroy. + * destroy ждёт workers и завершает ожидающие done с CANCELLED, пока их данные ещё существуют. + * Отмена не прерывает work и не откатывает файловые изменения. Примитивы ниже синхронные, подходят для workers. */ #ifndef MEDIA_ASYNC_H #define MEDIA_ASYNC_H @@ -17,33 +18,46 @@ struct UASYNC; struct media_async; typedef void (*ma_work_fn)(void* data); // рабочий поток; не обращаться к состоянию uasync -typedef void (*ma_done_fn)(void* arg, int err); // 0=work завершён, -1=ошибка запуска, -2=отмена +typedef void (*ma_done_fn)(void* arg, int err); // 0=work завершён, -1=отказ запуска/аргументы, -2=остановка владельца /* Создать владельца задач; NULL при ошибке выделения памяти. */ struct media_async* media_async_create(void); #define MEDIA_ASYNC_CANCELLED (-2) +#define MEDIA_ASYNC_MAX_TASKS 16 +#define MA_WALK_MAX_DEPTH 128 /* Вызывать вне callbacks uasync. Ждёт работы, отменяет posted completion, - * вызывает ожидающие done и освобождает ma; NULL допустим. */ + * вызывает ожидающие done и освобождает ma; NULL допустим. Повторный вход во время destroy отклоняется. + * После возврата ma недействителен. Не вызывать из work или callbacks uasync, в том числе обычного done. */ void media_async_destroy(struct media_async* ma); -/* Передать задачу (не более 16 активных); data/arg остаются у вызывающего до done. При ошибке done - * может быть вызван синхронно. Ошибку самой work передают через data, а не err. */ +/* Передать задачу: ma/ua/work/done обязательны, data/arg могут быть NULL и остаются у вызывающего до done. + * Лимит 16 включает завершённые workers до обработки done. При отказе done вызывается синхронно ровно один раз; + * при done=NULL только логируется ошибка. У closing-владельца возврат через done с CANCELLED. + * err=0 означает завершение work, её собственную ошибку передают через data. После принятия done — ровно один раз. */ void media_async_submit(struct media_async* ma, struct UASYNC* ua, ma_work_fn work, void* data, ma_done_fn done, void* arg); -/* Синхронные примитивы для worker: SHA256 файла, Ed25519-подпись, копирование. 0/-1. */ +/* SHA256 обычного файла path (UTF-8); hash_out — 32 байта, обнуляется при ошибке. 0/-1. */ int ma_sha256_file(const char* path, uint8_t hash_out[32]); +/* Подписать len байт Ed25519 ключом 32 байта; sig_out — 64 байта, обнуляется при ошибке. + * data=NULL допустим только при len=0. 0/-1. */ int ma_sign_block(const uint8_t* ed25519_privkey, const uint8_t* data, size_t len, uint8_t sig_out[64]); +/* Копировать regular src в отсутствующий/regular dst через соседний temp; 0/-1. Пути UTF-8, <4096 байт. + * Один существующий файл (включая ./ и hardlink) — успешный no-op. Конечные ссылки назначения отклоняются. + * Ошибка сохраняет src и прежний dst; src/каталоги не менять во время вызова. POSIX результат имеет права 0600. + * Публикация происходит внутри вызова; последующая отмена media_async не откатывает копирование. */ int ma_copy_file(const char* src, const char* dst); -/* Размер файла в байтах (int; для файлов, помещающихся в этот тип), -1 при ошибке stat. */ -int ma_file_size(const char* path); -/* Заполнить 16 случайных байт идентификатора. */ -void ma_uuid(uint8_t uuid_out[16]); +/* Размер regular-файла path (UTF-8): 0..INT64_MAX байт; -1 при отсутствии/ошибке/другом типе. */ +int64_t ma_file_size(const char* path); +/* Заполнить 16 криптографических случайных байт; 0/-1. При отказе выход обнуляется; NULL — ошибка. */ +int ma_uuid(uint8_t uuid_out[16]); /* Рекурсивный обход каталога: для каждого regular-файла вызывается cb(full_path, size). - * Пути строятся с '/' (принимается и POSIX, и Win32 API). 0 = ok, -1 = неверные аргументы. */ + * Пути UTF-8 с '/', <4096 байт; глубина не более MA_WALK_MAX_DEPTH. Ссылки/reparse points пропускаются, + * включая корень. 0 — полный обход, -1 — неверные аргументы, отсутствие корня, I/O или превышение лимита. + * Callback синхронный, full_path заимствован до его возврата. При ошибке прежние callbacks не откатываются. */ typedef void (*ma_dir_walk_cb)(void* arg, const char* full_path, int64_t size); int ma_dir_walk(const char* dir, ma_dir_walk_cb cb, void* arg);