Mercurial
diff mrjunejune/main.c @ 250:745fd127b2a1
[seobeo] Add bounded worker interface
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 04 Aug 2026 06:23:37 -0700 |
| parents | b8b6e726964a |
| children | 667156fcd3e3 |
line wrap: on
line diff
--- a/mrjunejune/main.c Tue Aug 04 04:16:45 2026 -0700 +++ b/mrjunejune/main.c Tue Aug 04 06:23:37 2026 -0700 @@ -16,8 +16,9 @@ volatile sig_atomic_t stop_server = 0; static _Atomic uint32 counter = 0; static _Atomic boolean g_latex_rendering = FALSE; +static Seobeo_Worker_Pool *g_media_worker_pool = NULL; -// Media Processing Context for background threads +// Media processing context owned by a background worker. typedef struct { int64 media_id; char s3_key_original[512]; @@ -31,6 +32,7 @@ typedef struct { char *input_path; char *output_path; + int result; } File_Converter_Config; // Server configuration (loaded from .config) @@ -407,8 +409,8 @@ return resp; } -// Background thread function for media processing -void *Simple_WebpConverter_Background(void *arg) +// Joinable worker function for local image conversion. +void Simple_WebpConverter_Background(void *arg) { File_Converter_Config *configuration = (File_Converter_Config *)arg; @@ -416,16 +418,21 @@ snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log", configuration->input_path, configuration->output_path); Seobeo_Log(SEOBEO_INFO, "[MEDIA] Running FFmpeg: %s\n", cmd); - int ffmpeg_result = system(cmd); + configuration->result = system(cmd); - Seobeo_Log(SEOBEO_INFO, "[MEDIA] FFmpeg result: %d\n", ffmpeg_result); - if (ffmpeg_result != 0) + Seobeo_Log( + SEOBEO_INFO, + "[MEDIA] FFmpeg result: %d\n", + configuration->result); + if (configuration->result != 0) { Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: FFmpeg conversion failed\n"); - return NULL; + return; } - Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n"); - return NULL; + Seobeo_Log( + SEOBEO_INFO, + "[MEDIA] Successfully converted to webp: %s\n", + configuration->output_path); } Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena) @@ -481,7 +488,10 @@ int open_flags = O_RDWR | O_CREAT | O_EXCL; char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); - uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + uint32 seed = + (uint32)time(NULL) ^ + (uint32)Seobeo_Thread_Current_Id() ^ + counter++; Dowa_String_UUID(seed, uuid4); char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); @@ -499,7 +509,10 @@ uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); - seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + seed = + (uint32)time(NULL) ^ + (uint32)Seobeo_Thread_Current_Id() ^ + counter++; Dowa_String_UUID(seed, uuid4); char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4); @@ -523,11 +536,15 @@ File_Converter_Config *configuration = Dowa_Arena_Allocate(arena, sizeof(File_Converter_Config)); configuration->input_path = input_path; configuration->output_path = output_path; + configuration->result = -1; - pthread_t thread_id; - int thread_result = pthread_create(&thread_id, NULL, Simple_WebpConverter_Background, (void *)configuration); - - if (thread_result != 0) + Seobeo_Thread *p_worker = Seobeo_Thread_Start( + Simple_WebpConverter_Background, + configuration, + NULL); + if (!p_worker || + Seobeo_Thread_Join(p_worker) != SEOBEO_WORKER_OK || + configuration->result != 0) { unlink(input_path); unlink(output_path); @@ -537,14 +554,7 @@ Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); return resp; } - else - { - // Detach thread so it cleans up automatically when done - pthread_detach(thread_id); - Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully spawned and detached thread\n"); - } - size_t converted_size = 0; FILE *out_file = fopen(output_path, "rb"); if (!out_file) { @@ -607,7 +617,10 @@ int open_flags = O_RDWR | O_CREAT | O_EXCL; char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); - uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + uint32 seed = + (uint32)time(NULL) ^ + (uint32)Seobeo_Thread_Current_Id() ^ + counter++; Dowa_String_UUID(seed, uuid4); char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH); snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); @@ -627,7 +640,10 @@ write(input_fd, file_data, file_size); close(input_fd); - seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + seed = + (uint32)time(NULL) ^ + (uint32)Seobeo_Thread_Current_Id() ^ + counter++; Dowa_String_UUID(seed, uuid4); char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4); @@ -953,7 +969,10 @@ // Generate unique S3 key with timestamp char s3_key[512]; char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); - uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + uint32 seed = + (uint32)time(NULL) ^ + (uint32)Seobeo_Thread_Current_Id() ^ + counter++; Dowa_String_UUID(seed, uuid); snprintf(s3_key, sizeof(s3_key), "uploads/%s/%s", uuid, filename); @@ -1336,7 +1355,10 @@ // Generate UUID for this upload char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); - uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + uint32 seed = + (uint32)time(NULL) ^ + (uint32)Seobeo_Thread_Current_Id() ^ + counter++; Dowa_String_UUID(seed, uuid); // Generate S3 keys @@ -1423,23 +1445,22 @@ return resp; } -// Background thread function for media processing -void *Media_Process_Background(void *arg) +// Worker-pool task for S3 media processing. +void Media_Process_Background(void *arg) { Media_Processing_Context *ctx = (Media_Processing_Context *)arg; - Seobeo_Log(SEOBEO_INFO, "[MEDIA] Background thread started for media_id=%lld\n", (long long)ctx->media_id); + Seobeo_Log(SEOBEO_INFO, "[MEDIA] Background worker started for media_id=%lld\n", (long long)ctx->media_id); Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key original: %s\n", ctx->s3_key_original); Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key processed: %s\n", ctx->s3_key_processed); Seobeo_Log(SEOBEO_INFO, "[MEDIA] DB path: %s\n", ctx->db_path); - // Open thread-local DB connection + // Open a worker-local DB connection. Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, ctx->db_path); if (!db_conn || !Deita_Connection_Is_Open(db_conn)) { - Seobeo_Log(SEOBEO_ERROR, "[MEDIA] Thread ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id); - free(ctx); - return NULL; + Seobeo_Log(SEOBEO_ERROR, "[MEDIA] Worker ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id); + return; } // Update status to 'processing' @@ -1465,8 +1486,7 @@ Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); S3_Presigned_URL_Destroy(&download_url); Deita_Connection_Close(db_conn); - free(ctx); - return NULL; + return; } Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generated presigned URL: %.100s...\n", download_url.url); @@ -1475,8 +1495,14 @@ char tmp_output[256]; char *uuid_input = malloc(UUID_LEN); char *uuid_output = malloc(UUID_LEN); - uint32 seed1 = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; - uint32 seed2 = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; + uint32 seed1 = + (uint32)time(NULL) ^ + (uint32)Seobeo_Thread_Current_Id() ^ + counter++; + uint32 seed2 = + (uint32)time(NULL) ^ + (uint32)Seobeo_Thread_Current_Id() ^ + counter++; Dowa_String_UUID(seed1, uuid_input); Dowa_String_UUID(seed2, uuid_output); snprintf(tmp_input, sizeof(tmp_input), "/tmp/%s", uuid_input); @@ -1505,8 +1531,7 @@ if (download_resp) Seobeo_Client_Response_Destroy(download_resp); unlink(tmp_input); Deita_Connection_Close(db_conn); - free(ctx); - return NULL; + return; } Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully downloaded file to %s\n", tmp_input); @@ -1535,8 +1560,7 @@ unlink(tmp_input); unlink(tmp_output); Deita_Connection_Close(db_conn); - free(ctx); - return NULL; + return; } Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n", tmp_output); @@ -1557,8 +1581,7 @@ unlink(tmp_input); unlink(tmp_output); Deita_Connection_Close(db_conn); - free(ctx); - return NULL; + return; } Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully uploaded processed file to S3\n"); @@ -1574,9 +1597,6 @@ unlink(tmp_input); unlink(tmp_output); Deita_Connection_Close(db_conn); - free(ctx); - - return NULL; } // Media Upload API - Mark uploaded @@ -1684,13 +1704,20 @@ Seobeo_Log(SEOBEO_INFO, "[MEDIA] Content type for media_id=%lld: '%s'\n", (long long)media_id, content_type_copy); - // If content_type starts with "image/", spawn background processing thread + // Images are processed asynchronously by the bounded media pool. if (strncmp(content_type_copy, "image/", 6) == 0) { - Seobeo_Log(SEOBEO_INFO, "[MEDIA] Detected image type, preparing to spawn background thread for media_id=%lld\n", (long long)media_id); + Seobeo_Log(SEOBEO_INFO, "[MEDIA] Queueing image processing for media_id=%lld\n", (long long)media_id); - // Create context for background thread (heap allocated) + // The pool owns this context after a successful submission. Media_Processing_Context *ctx = malloc(sizeof(Media_Processing_Context)); + if (!ctx) + { + Dowa_HashMap_Push_Arena(resp, "status", "500", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); + Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Unable to allocate media work\"}", arena); + return resp; + } ctx->media_id = media_id; strncpy(ctx->s3_key_original, s3_key_original_copy, sizeof(ctx->s3_key_original) - 1); strncpy(ctx->s3_key_processed, s3_key_processed_copy, sizeof(ctx->s3_key_processed) - 1); @@ -1704,23 +1731,42 @@ ctx->db_path[sizeof(ctx->db_path) - 1] = '\0'; ctx->s3_config = g_s3_config; - Seobeo_Log(SEOBEO_INFO, "[MEDIA] Creating pthread for media_id=%lld\n", (long long)media_id); - - // Spawn detached thread - pthread_t thread_id; - int thread_result = pthread_create(&thread_id, NULL, Media_Process_Background, ctx); - - if (thread_result != 0) + Seobeo_Worker_Result worker_result = + g_media_worker_pool + ? Seobeo_Worker_Pool_Submit( + g_media_worker_pool, + Media_Process_Background, + ctx, + free) + : SEOBEO_WORKER_STOPPED; + if (worker_result != SEOBEO_WORKER_OK) { - Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: pthread_create failed with result=%d for media_id=%lld\n", thread_result, (long long)media_id); + Seobeo_Log( + SEOBEO_ERROR, + "[MEDIA] Worker submission failed with result=%d for media_id=%lld\n", + worker_result, + (long long)media_id); free(ctx); + const char *update_error = + "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; + const char *error_params[] = { + "Media worker queue is unavailable", + media_id_str, + }; + Deita_Query_Execute_Update_Prepared( + g_db_connection, + update_error, + 2, + error_params); + Dowa_HashMap_Push_Arena(resp, "status", "503", arena); + Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); + Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Media worker queue is unavailable\"}", arena); + return resp; } - else - { - // Detach thread so it cleans up automatically when done - pthread_detach(thread_id); - Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully spawned and detached thread for media_id=%lld\n", (long long)media_id); - } + Seobeo_Log( + SEOBEO_INFO, + "[MEDIA] Submitted media_id=%lld to the worker pool\n", + (long long)media_id); } else { @@ -1952,6 +1998,14 @@ // Initialize database init_database(); + g_media_worker_pool = Seobeo_Worker_Pool_Create(2, 16); + if (!g_media_worker_pool) + { + Seobeo_Log( + SEOBEO_ERROR, + "[MEDIA] Unable to initialize the media worker pool\n"); + } + Seobeo_Router_Init(); Seobeo_Router_Register("GET", "/", GetHomePage); @@ -2015,4 +2069,6 @@ if (!server_port || server_port[0] == '\0') server_port = "6969"; Seobeo_Web_Server_Start("mrjunejune/src", server_port, SEOBEO_MODE_EDGE, 4); + Seobeo_Worker_Pool_Destroy(g_media_worker_pool); + g_media_worker_pool = NULL; }