comparison 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
comparison
equal deleted inserted replaced
249:c5129452493e 250:745fd127b2a1
14 #define UUID_LEN 37 14 #define UUID_LEN 37
15 15
16 volatile sig_atomic_t stop_server = 0; 16 volatile sig_atomic_t stop_server = 0;
17 static _Atomic uint32 counter = 0; 17 static _Atomic uint32 counter = 0;
18 static _Atomic boolean g_latex_rendering = FALSE; 18 static _Atomic boolean g_latex_rendering = FALSE;
19 19 static Seobeo_Worker_Pool *g_media_worker_pool = NULL;
20 // Media Processing Context for background threads 20
21 // Media processing context owned by a background worker.
21 typedef struct { 22 typedef struct {
22 int64 media_id; 23 int64 media_id;
23 char s3_key_original[512]; 24 char s3_key_original[512];
24 char s3_key_processed[512]; 25 char s3_key_processed[512];
25 char content_type[128]; 26 char content_type[128];
29 } Media_Processing_Context; 30 } Media_Processing_Context;
30 31
31 typedef struct { 32 typedef struct {
32 char *input_path; 33 char *input_path;
33 char *output_path; 34 char *output_path;
35 int result;
34 } File_Converter_Config; 36 } File_Converter_Config;
35 37
36 // Server configuration (loaded from .config) 38 // Server configuration (loaded from .config)
37 static char g_upload_auth_token[256] = {0}; 39 static char g_upload_auth_token[256] = {0};
38 static char g_s3_region[64] = "us-west-2"; 40 static char g_s3_region[64] = "us-west-2";
405 Dowa_HashMap_Push_Arena(resp, "x-content-type-options", "nosniff", arena); 407 Dowa_HashMap_Push_Arena(resp, "x-content-type-options", "nosniff", arena);
406 Dowa_HashMap_Push_Arena(resp, "body", pdf, arena); 408 Dowa_HashMap_Push_Arena(resp, "body", pdf, arena);
407 return resp; 409 return resp;
408 } 410 }
409 411
410 // Background thread function for media processing 412 // Joinable worker function for local image conversion.
411 void *Simple_WebpConverter_Background(void *arg) 413 void Simple_WebpConverter_Background(void *arg)
412 { 414 {
413 File_Converter_Config *configuration = (File_Converter_Config *)arg; 415 File_Converter_Config *configuration = (File_Converter_Config *)arg;
414 416
415 char cmd[1024]; 417 char cmd[1024];
416 snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log", 418 snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log",
417 configuration->input_path, configuration->output_path); 419 configuration->input_path, configuration->output_path);
418 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Running FFmpeg: %s\n", cmd); 420 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Running FFmpeg: %s\n", cmd);
419 int ffmpeg_result = system(cmd); 421 configuration->result = system(cmd);
420 422
421 Seobeo_Log(SEOBEO_INFO, "[MEDIA] FFmpeg result: %d\n", ffmpeg_result); 423 Seobeo_Log(
422 if (ffmpeg_result != 0) 424 SEOBEO_INFO,
425 "[MEDIA] FFmpeg result: %d\n",
426 configuration->result);
427 if (configuration->result != 0)
423 { 428 {
424 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: FFmpeg conversion failed\n"); 429 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: FFmpeg conversion failed\n");
425 return NULL; 430 return;
426 } 431 }
427 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n"); 432 Seobeo_Log(
428 return NULL; 433 SEOBEO_INFO,
434 "[MEDIA] Successfully converted to webp: %s\n",
435 configuration->output_path);
429 } 436 }
430 437
431 Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena) 438 Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena)
432 { 439 {
433 Seobeo_Request_Entry *resp = NULL; 440 Seobeo_Request_Entry *resp = NULL;
479 Seobeo_Log(SEOBEO_DEBUG, "Converting image, file_size=%zu bytes\n", file_size); 486 Seobeo_Log(SEOBEO_DEBUG, "Converting image, file_size=%zu bytes\n", file_size);
480 487
481 int open_flags = O_RDWR | O_CREAT | O_EXCL; 488 int open_flags = O_RDWR | O_CREAT | O_EXCL;
482 489
483 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); 490 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
484 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; 491 uint32 seed =
492 (uint32)time(NULL) ^
493 (uint32)Seobeo_Thread_Current_Id() ^
494 counter++;
485 Dowa_String_UUID(seed, uuid4); 495 Dowa_String_UUID(seed, uuid4);
486 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; 496 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
487 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); 497 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4);
488 int input_fd = open(input_path, open_flags, 0600); 498 int input_fd = open(input_path, open_flags, 0600);
489 if (input_fd == -1) 499 if (input_fd == -1)
497 write(input_fd, file_data, file_size); 507 write(input_fd, file_data, file_size);
498 close(input_fd); 508 close(input_fd);
499 509
500 510
501 uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); 511 uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
502 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; 512 seed =
513 (uint32)time(NULL) ^
514 (uint32)Seobeo_Thread_Current_Id() ^
515 counter++;
503 Dowa_String_UUID(seed, uuid4); 516 Dowa_String_UUID(seed, uuid4);
504 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; 517 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
505 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4); 518 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4);
506 Seobeo_Log(SEOBEO_DEBUG, "output_path %s\n", output_path); 519 Seobeo_Log(SEOBEO_DEBUG, "output_path %s\n", output_path);
507 Seobeo_Log(SEOBEO_DEBUG, "open_flags: 0x%x\n", open_flags); 520 Seobeo_Log(SEOBEO_DEBUG, "open_flags: 0x%x\n", open_flags);
521 close(output_fd); 534 close(output_fd);
522 535
523 File_Converter_Config *configuration = Dowa_Arena_Allocate(arena, sizeof(File_Converter_Config)); 536 File_Converter_Config *configuration = Dowa_Arena_Allocate(arena, sizeof(File_Converter_Config));
524 configuration->input_path = input_path; 537 configuration->input_path = input_path;
525 configuration->output_path = output_path; 538 configuration->output_path = output_path;
526 539 configuration->result = -1;
527 pthread_t thread_id; 540
528 int thread_result = pthread_create(&thread_id, NULL, Simple_WebpConverter_Background, (void *)configuration); 541 Seobeo_Thread *p_worker = Seobeo_Thread_Start(
529 542 Simple_WebpConverter_Background,
530 if (thread_result != 0) 543 configuration,
544 NULL);
545 if (!p_worker ||
546 Seobeo_Thread_Join(p_worker) != SEOBEO_WORKER_OK ||
547 configuration->result != 0)
531 { 548 {
532 unlink(input_path); 549 unlink(input_path);
533 unlink(output_path); 550 unlink(output_path);
534 char *error_msg = "FFmpeg conversion failed"; 551 char *error_msg = "FFmpeg conversion failed";
535 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); 552 Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
536 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); 553 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
537 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); 554 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
538 return resp; 555 return resp;
539 } 556 }
540 else 557
541 {
542 // Detach thread so it cleans up automatically when done
543 pthread_detach(thread_id);
544 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully spawned and detached thread\n");
545 }
546
547 size_t converted_size = 0;
548 FILE *out_file = fopen(output_path, "rb"); 558 FILE *out_file = fopen(output_path, "rb");
549 if (!out_file) 559 if (!out_file)
550 { 560 {
551 unlink(input_path); 561 unlink(input_path);
552 unlink(output_path); 562 unlink(output_path);
605 printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size); 615 printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size);
606 616
607 int open_flags = O_RDWR | O_CREAT | O_EXCL; 617 int open_flags = O_RDWR | O_CREAT | O_EXCL;
608 618
609 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); 619 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
610 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; 620 uint32 seed =
621 (uint32)time(NULL) ^
622 (uint32)Seobeo_Thread_Current_Id() ^
623 counter++;
611 Dowa_String_UUID(seed, uuid4); 624 Dowa_String_UUID(seed, uuid4);
612 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH); 625 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);
613 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); 626 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4);
614 Seobeo_Log(SEOBEO_DEBUG, "Input path: %s\n", input_path); 627 Seobeo_Log(SEOBEO_DEBUG, "Input path: %s\n", input_path);
615 628
625 } 638 }
626 639
627 write(input_fd, file_data, file_size); 640 write(input_fd, file_data, file_size);
628 close(input_fd); 641 close(input_fd);
629 642
630 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; 643 seed =
644 (uint32)time(NULL) ^
645 (uint32)Seobeo_Thread_Current_Id() ^
646 counter++;
631 Dowa_String_UUID(seed, uuid4); 647 Dowa_String_UUID(seed, uuid4);
632 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; 648 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
633 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4); 649 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4);
634 int output_fd = open(output_path, open_flags, 0600); 650 int output_fd = open(output_path, open_flags, 0600);
635 if (output_fd == -1) 651 if (output_fd == -1)
951 } 967 }
952 968
953 // Generate unique S3 key with timestamp 969 // Generate unique S3 key with timestamp
954 char s3_key[512]; 970 char s3_key[512];
955 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); 971 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN);
956 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; 972 uint32 seed =
973 (uint32)time(NULL) ^
974 (uint32)Seobeo_Thread_Current_Id() ^
975 counter++;
957 Dowa_String_UUID(seed, uuid); 976 Dowa_String_UUID(seed, uuid);
958 snprintf(s3_key, sizeof(s3_key), "uploads/%s/%s", uuid, filename); 977 snprintf(s3_key, sizeof(s3_key), "uploads/%s/%s", uuid, filename);
959 978
960 // Generate presigned URL 979 // Generate presigned URL
961 S3_Presigned_URL presigned = S3_Presign_Put(&g_s3_config, s3_key, content_type, g_s3_url_expires); 980 S3_Presigned_URL presigned = S3_Presign_Put(&g_s3_config, s3_key, content_type, g_s3_url_expires);
1334 return resp; 1353 return resp;
1335 } 1354 }
1336 1355
1337 // Generate UUID for this upload 1356 // Generate UUID for this upload
1338 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); 1357 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN);
1339 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; 1358 uint32 seed =
1359 (uint32)time(NULL) ^
1360 (uint32)Seobeo_Thread_Current_Id() ^
1361 counter++;
1340 Dowa_String_UUID(seed, uuid); 1362 Dowa_String_UUID(seed, uuid);
1341 1363
1342 // Generate S3 keys 1364 // Generate S3 keys
1343 char s3_key_original[512]; 1365 char s3_key_original[512];
1344 char s3_key_processed[512]; 1366 char s3_key_processed[512];
1421 printf("[MEDIA] Created media_id=%lld, file=%s\n", (long long)media_id, filename); 1443 printf("[MEDIA] Created media_id=%lld, file=%s\n", (long long)media_id, filename);
1422 1444
1423 return resp; 1445 return resp;
1424 } 1446 }
1425 1447
1426 // Background thread function for media processing 1448 // Worker-pool task for S3 media processing.
1427 void *Media_Process_Background(void *arg) 1449 void Media_Process_Background(void *arg)
1428 { 1450 {
1429 Media_Processing_Context *ctx = (Media_Processing_Context *)arg; 1451 Media_Processing_Context *ctx = (Media_Processing_Context *)arg;
1430 1452
1431 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Background thread started for media_id=%lld\n", (long long)ctx->media_id); 1453 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Background worker started for media_id=%lld\n", (long long)ctx->media_id);
1432 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key original: %s\n", ctx->s3_key_original); 1454 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key original: %s\n", ctx->s3_key_original);
1433 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key processed: %s\n", ctx->s3_key_processed); 1455 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key processed: %s\n", ctx->s3_key_processed);
1434 Seobeo_Log(SEOBEO_INFO, "[MEDIA] DB path: %s\n", ctx->db_path); 1456 Seobeo_Log(SEOBEO_INFO, "[MEDIA] DB path: %s\n", ctx->db_path);
1435 1457
1436 // Open thread-local DB connection 1458 // Open a worker-local DB connection.
1437 Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, ctx->db_path); 1459 Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, ctx->db_path);
1438 if (!db_conn || !Deita_Connection_Is_Open(db_conn)) 1460 if (!db_conn || !Deita_Connection_Is_Open(db_conn))
1439 { 1461 {
1440 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] Thread ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id); 1462 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] Worker ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id);
1441 free(ctx); 1463 return;
1442 return NULL;
1443 } 1464 }
1444 1465
1445 // Update status to 'processing' 1466 // Update status to 'processing'
1446 const char *update_processing = 1467 const char *update_processing =
1447 "UPDATE media_uploads SET status='processing', updated_at=strftime('%s','now') WHERE id=?"; 1468 "UPDATE media_uploads SET status='processing', updated_at=strftime('%s','now') WHERE id=?";
1463 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; 1484 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?";
1464 const char *error_params[] = { error_msg, media_id_str }; 1485 const char *error_params[] = { error_msg, media_id_str };
1465 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); 1486 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params);
1466 S3_Presigned_URL_Destroy(&download_url); 1487 S3_Presigned_URL_Destroy(&download_url);
1467 Deita_Connection_Close(db_conn); 1488 Deita_Connection_Close(db_conn);
1468 free(ctx); 1489 return;
1469 return NULL;
1470 } 1490 }
1471 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generated presigned URL: %.100s...\n", download_url.url); 1491 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generated presigned URL: %.100s...\n", download_url.url);
1472 1492
1473 // Generate temp file paths 1493 // Generate temp file paths
1474 char tmp_input[256]; 1494 char tmp_input[256];
1475 char tmp_output[256]; 1495 char tmp_output[256];
1476 char *uuid_input = malloc(UUID_LEN); 1496 char *uuid_input = malloc(UUID_LEN);
1477 char *uuid_output = malloc(UUID_LEN); 1497 char *uuid_output = malloc(UUID_LEN);
1478 uint32 seed1 = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; 1498 uint32 seed1 =
1479 uint32 seed2 = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; 1499 (uint32)time(NULL) ^
1500 (uint32)Seobeo_Thread_Current_Id() ^
1501 counter++;
1502 uint32 seed2 =
1503 (uint32)time(NULL) ^
1504 (uint32)Seobeo_Thread_Current_Id() ^
1505 counter++;
1480 Dowa_String_UUID(seed1, uuid_input); 1506 Dowa_String_UUID(seed1, uuid_input);
1481 Dowa_String_UUID(seed2, uuid_output); 1507 Dowa_String_UUID(seed2, uuid_output);
1482 snprintf(tmp_input, sizeof(tmp_input), "/tmp/%s", uuid_input); 1508 snprintf(tmp_input, sizeof(tmp_input), "/tmp/%s", uuid_input);
1483 snprintf(tmp_output, sizeof(tmp_output), "/tmp/%s.webp", uuid_output); 1509 snprintf(tmp_output, sizeof(tmp_output), "/tmp/%s.webp", uuid_output);
1484 free(uuid_input); 1510 free(uuid_input);
1503 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); 1529 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params);
1504 if (download_req) Seobeo_Client_Request_Destroy(download_req); 1530 if (download_req) Seobeo_Client_Request_Destroy(download_req);
1505 if (download_resp) Seobeo_Client_Response_Destroy(download_resp); 1531 if (download_resp) Seobeo_Client_Response_Destroy(download_resp);
1506 unlink(tmp_input); 1532 unlink(tmp_input);
1507 Deita_Connection_Close(db_conn); 1533 Deita_Connection_Close(db_conn);
1508 free(ctx); 1534 return;
1509 return NULL;
1510 } 1535 }
1511 1536
1512 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully downloaded file to %s\n", tmp_input); 1537 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully downloaded file to %s\n", tmp_input);
1513 Seobeo_Client_Request_Destroy(download_req); 1538 Seobeo_Client_Request_Destroy(download_req);
1514 Seobeo_Client_Response_Destroy(download_resp); 1539 Seobeo_Client_Response_Destroy(download_resp);
1533 const char *error_params[] = { "Image conversion failed", media_id_str }; 1558 const char *error_params[] = { "Image conversion failed", media_id_str };
1534 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); 1559 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params);
1535 unlink(tmp_input); 1560 unlink(tmp_input);
1536 unlink(tmp_output); 1561 unlink(tmp_output);
1537 Deita_Connection_Close(db_conn); 1562 Deita_Connection_Close(db_conn);
1538 free(ctx); 1563 return;
1539 return NULL;
1540 } 1564 }
1541 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n", tmp_output); 1565 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n", tmp_output);
1542 1566
1543 // Upload processed file to S3 1567 // Upload processed file to S3
1544 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Uploading processed file to S3: %s -> %s\n", tmp_output, ctx->s3_key_processed); 1568 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Uploading processed file to S3: %s -> %s\n", tmp_output, ctx->s3_key_processed);
1555 const char *error_params[] = { error_msg, media_id_str }; 1579 const char *error_params[] = { error_msg, media_id_str };
1556 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); 1580 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params);
1557 unlink(tmp_input); 1581 unlink(tmp_input);
1558 unlink(tmp_output); 1582 unlink(tmp_output);
1559 Deita_Connection_Close(db_conn); 1583 Deita_Connection_Close(db_conn);
1560 free(ctx); 1584 return;
1561 return NULL;
1562 } 1585 }
1563 1586
1564 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully uploaded processed file to S3\n"); 1587 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully uploaded processed file to S3\n");
1565 1588
1566 // Update status to 'finished' 1589 // Update status to 'finished'
1572 1595
1573 // Cleanup 1596 // Cleanup
1574 unlink(tmp_input); 1597 unlink(tmp_input);
1575 unlink(tmp_output); 1598 unlink(tmp_output);
1576 Deita_Connection_Close(db_conn); 1599 Deita_Connection_Close(db_conn);
1577 free(ctx);
1578
1579 return NULL;
1580 } 1600 }
1581 1601
1582 // Media Upload API - Mark uploaded 1602 // Media Upload API - Mark uploaded
1583 // POST /api/media/:id/uploaded 1603 // POST /api/media/:id/uploaded
1584 // Headers: Authorization: Bearer <token> 1604 // Headers: Authorization: Bearer <token>
1682 return resp; 1702 return resp;
1683 } 1703 }
1684 1704
1685 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Content type for media_id=%lld: '%s'\n", (long long)media_id, content_type_copy); 1705 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Content type for media_id=%lld: '%s'\n", (long long)media_id, content_type_copy);
1686 1706
1687 // If content_type starts with "image/", spawn background processing thread 1707 // Images are processed asynchronously by the bounded media pool.
1688 if (strncmp(content_type_copy, "image/", 6) == 0) 1708 if (strncmp(content_type_copy, "image/", 6) == 0)
1689 { 1709 {
1690 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Detected image type, preparing to spawn background thread for media_id=%lld\n", (long long)media_id); 1710 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Queueing image processing for media_id=%lld\n", (long long)media_id);
1691 1711
1692 // Create context for background thread (heap allocated) 1712 // The pool owns this context after a successful submission.
1693 Media_Processing_Context *ctx = malloc(sizeof(Media_Processing_Context)); 1713 Media_Processing_Context *ctx = malloc(sizeof(Media_Processing_Context));
1714 if (!ctx)
1715 {
1716 Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
1717 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
1718 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Unable to allocate media work\"}", arena);
1719 return resp;
1720 }
1694 ctx->media_id = media_id; 1721 ctx->media_id = media_id;
1695 strncpy(ctx->s3_key_original, s3_key_original_copy, sizeof(ctx->s3_key_original) - 1); 1722 strncpy(ctx->s3_key_original, s3_key_original_copy, sizeof(ctx->s3_key_original) - 1);
1696 strncpy(ctx->s3_key_processed, s3_key_processed_copy, sizeof(ctx->s3_key_processed) - 1); 1723 strncpy(ctx->s3_key_processed, s3_key_processed_copy, sizeof(ctx->s3_key_processed) - 1);
1697 strncpy(ctx->content_type, content_type_copy, sizeof(ctx->content_type) - 1); 1724 strncpy(ctx->content_type, content_type_copy, sizeof(ctx->content_type) - 1);
1698 strncpy(ctx->access_token, token, sizeof(ctx->access_token) - 1); 1725 strncpy(ctx->access_token, token, sizeof(ctx->access_token) - 1);
1702 ctx->content_type[sizeof(ctx->content_type) - 1] = '\0'; 1729 ctx->content_type[sizeof(ctx->content_type) - 1] = '\0';
1703 ctx->access_token[sizeof(ctx->access_token) - 1] = '\0'; 1730 ctx->access_token[sizeof(ctx->access_token) - 1] = '\0';
1704 ctx->db_path[sizeof(ctx->db_path) - 1] = '\0'; 1731 ctx->db_path[sizeof(ctx->db_path) - 1] = '\0';
1705 ctx->s3_config = g_s3_config; 1732 ctx->s3_config = g_s3_config;
1706 1733
1707 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Creating pthread for media_id=%lld\n", (long long)media_id); 1734 Seobeo_Worker_Result worker_result =
1708 1735 g_media_worker_pool
1709 // Spawn detached thread 1736 ? Seobeo_Worker_Pool_Submit(
1710 pthread_t thread_id; 1737 g_media_worker_pool,
1711 int thread_result = pthread_create(&thread_id, NULL, Media_Process_Background, ctx); 1738 Media_Process_Background,
1712 1739 ctx,
1713 if (thread_result != 0) 1740 free)
1741 : SEOBEO_WORKER_STOPPED;
1742 if (worker_result != SEOBEO_WORKER_OK)
1714 { 1743 {
1715 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: pthread_create failed with result=%d for media_id=%lld\n", thread_result, (long long)media_id); 1744 Seobeo_Log(
1745 SEOBEO_ERROR,
1746 "[MEDIA] Worker submission failed with result=%d for media_id=%lld\n",
1747 worker_result,
1748 (long long)media_id);
1716 free(ctx); 1749 free(ctx);
1750 const char *update_error =
1751 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?";
1752 const char *error_params[] = {
1753 "Media worker queue is unavailable",
1754 media_id_str,
1755 };
1756 Deita_Query_Execute_Update_Prepared(
1757 g_db_connection,
1758 update_error,
1759 2,
1760 error_params);
1761 Dowa_HashMap_Push_Arena(resp, "status", "503", arena);
1762 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
1763 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Media worker queue is unavailable\"}", arena);
1764 return resp;
1717 } 1765 }
1718 else 1766 Seobeo_Log(
1719 { 1767 SEOBEO_INFO,
1720 // Detach thread so it cleans up automatically when done 1768 "[MEDIA] Submitted media_id=%lld to the worker pool\n",
1721 pthread_detach(thread_id); 1769 (long long)media_id);
1722 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully spawned and detached thread for media_id=%lld\n", (long long)media_id);
1723 }
1724 } 1770 }
1725 else 1771 else
1726 { 1772 {
1727 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Non-image file, skipping background processing for media_id=%lld\n", (long long)media_id); 1773 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Non-image file, skipping background processing for media_id=%lld\n", (long long)media_id);
1728 } 1774 }
1950 } 1996 }
1951 1997
1952 // Initialize database 1998 // Initialize database
1953 init_database(); 1999 init_database();
1954 2000
2001 g_media_worker_pool = Seobeo_Worker_Pool_Create(2, 16);
2002 if (!g_media_worker_pool)
2003 {
2004 Seobeo_Log(
2005 SEOBEO_ERROR,
2006 "[MEDIA] Unable to initialize the media worker pool\n");
2007 }
2008
1955 Seobeo_Router_Init(); 2009 Seobeo_Router_Init();
1956 2010
1957 Seobeo_Router_Register("GET", "/", GetHomePage); 2011 Seobeo_Router_Register("GET", "/", GetHomePage);
1958 Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage); 2012 Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage);
1959 2013
2013 Seobeo_Log(SEOBEO_INFO, "WTF is going on\n"); 2067 Seobeo_Log(SEOBEO_INFO, "WTF is going on\n");
2014 const char *server_port = getenv("MRJUNEJUNE_PORT"); 2068 const char *server_port = getenv("MRJUNEJUNE_PORT");
2015 if (!server_port || server_port[0] == '\0') 2069 if (!server_port || server_port[0] == '\0')
2016 server_port = "6969"; 2070 server_port = "6969";
2017 Seobeo_Web_Server_Start("mrjunejune/src", server_port, SEOBEO_MODE_EDGE, 4); 2071 Seobeo_Web_Server_Start("mrjunejune/src", server_port, SEOBEO_MODE_EDGE, 4);
2018 } 2072 Seobeo_Worker_Pool_Destroy(g_media_worker_pool);
2073 g_media_worker_pool = NULL;
2074 }