@@ -452,10 +452,16 @@ pgraft_main(Datum main_arg)
452452 {
453453 /* Create JSON data for Raft replication using json-c */
454454 if (pgraft_json_create_kv_operation (PGRAFT_KV_PUT , cmd .kv_key , cmd .kv_value , cmd .kv_client_id , json_data , sizeof (json_data )) != 0 ) {
455- elog (ERROR , "pgraft: failed to create JSON for KV PUT operation" );
456- continue ;
455+ /* Fail just this command; do NOT elog(ERROR) here,
456+ * which would abort (and restart) the whole worker. */
457+ cmd .status = COMMAND_STATUS_FAILED ;
458+ snprintf (cmd .error_message , sizeof (cmd .error_message ),
459+ "Failed to create JSON for KV PUT operation" );
460+ elog (WARNING , "pgraft: %s" , cmd .error_message );
461+ pgraft_update_command_status (cmd .timestamp , cmd .status , cmd .error_message );
462+ break ;
457463 }
458-
464+
459465 elog (LOG , "pgraft: calling pgraft_go_append_log with data=%s" , json_data );
460466
461467 /* Replicate through Raft */
@@ -498,10 +504,16 @@ pgraft_main(Datum main_arg)
498504 {
499505 /* Create JSON data for Raft replication using json-c */
500506 if (pgraft_json_create_kv_operation (PGRAFT_KV_DELETE , cmd .kv_key , NULL , cmd .kv_client_id , json_data , sizeof (json_data )) != 0 ) {
501- elog (ERROR , "pgraft: failed to create JSON for KV DELETE operation" );
502- continue ;
507+ /* Fail just this command; do NOT elog(ERROR) here,
508+ * which would abort (and restart) the whole worker. */
509+ cmd .status = COMMAND_STATUS_FAILED ;
510+ snprintf (cmd .error_message , sizeof (cmd .error_message ),
511+ "Failed to create JSON for KV DELETE operation" );
512+ elog (WARNING , "pgraft: %s" , cmd .error_message );
513+ pgraft_update_command_status (cmd .timestamp , cmd .status , cmd .error_message );
514+ break ;
503515 }
504-
516+
505517 elog (LOG , "pgraft: calling pgraft_go_append_log with data=%s" , json_data );
506518
507519 /* Replicate through Raft */
@@ -550,6 +562,45 @@ pgraft_main(Datum main_arg)
550562 }
551563 }
552564
565+ /*
566+ * Apply committed Raft entries that the Go layer has replicated to
567+ * this node. applyEntry() in the Go library enqueues every committed
568+ * EntryNormal via pgraft_enqueue_for_apply_from_go(); here we drain
569+ * that queue and apply each entry locally. Each apply is wrapped in
570+ * PG_TRY so that a single malformed entry cannot abort (and thereby
571+ * restart) the whole background worker.
572+ *
573+ * NOTE: this worker holds BGWORKER_SHMEM_ACCESS only (no database
574+ * connection), so only shared-memory key/value operations are applied
575+ * here. SQL/DDL replication would additionally require
576+ * BackgroundWorkerInitializeConnection() and a transaction.
577+ */
578+ {
579+ pgraft_apply_entry_t apply_entry ;
580+ MemoryContext apply_loop_ctx = CurrentMemoryContext ;
581+
582+ while (pgraft_dequeue_apply_entry (& apply_entry ))
583+ {
584+ PG_TRY ();
585+ {
586+ pgraft_apply_entry_to_postgres (apply_entry .raft_index ,
587+ apply_entry .data ,
588+ apply_entry .data_len );
589+ }
590+ PG_CATCH ();
591+ {
592+ /* Recover and keep the worker alive; the entry is dropped
593+ * but the error is reported to the log. */
594+ MemoryContextSwitchTo (apply_loop_ctx );
595+ EmitErrorReport ();
596+ FlushErrorState ();
597+ elog (WARNING , "pgraft: error applying raft entry %lu, skipping" ,
598+ (unsigned long ) apply_entry .raft_index );
599+ }
600+ PG_END_TRY ();
601+ }
602+ }
603+
553604 pg_usleep (100000 );
554605 sleep_count ++ ;
555606
@@ -562,6 +613,14 @@ pgraft_main(Datum main_arg)
562613
563614 state -> status = WORKER_STATUS_STOPPED ;
564615 elog (LOG , "pgraft: background worker stopped" );
616+
617+ /*
618+ * Exit cleanly (status 0) in response to an explicit shutdown command.
619+ * A background worker that exits with status 0 is de-registered and NOT
620+ * restarted by the postmaster, so the shutdown is durable. (bgw_restart_time
621+ * remains a finite interval so that an actual *crash* is still recovered.)
622+ */
623+ proc_exit (0 );
565624}
566625
567626/*
@@ -583,14 +642,24 @@ pgraft_worker_get_state(void)
583642 worker_state -> port = 0 ;
584643 strlcpy (worker_state -> address , "127.0.0.1" , sizeof (worker_state -> address ));
585644 worker_state -> status = WORKER_STATUS_STOPPED ;
586-
645+
646+ /* Initialize the queue spinlock and command id source */
647+ SpinLockInit (& worker_state -> mutex );
648+ worker_state -> next_command_id = 1 ;
649+
587650 /* Initialize circular buffers */
588651 worker_state -> command_head = 0 ;
589652 worker_state -> command_tail = 0 ;
590653 worker_state -> command_count = 0 ;
591654 worker_state -> status_head = 0 ;
592655 worker_state -> status_tail = 0 ;
593656 worker_state -> status_count = 0 ;
657+
658+ /* Initialize apply queue */
659+ worker_state -> apply_head = 0 ;
660+ worker_state -> apply_tail = 0 ;
661+ worker_state -> apply_count = 0 ;
662+ worker_state -> last_applied_index = 0 ;
594663 }
595664 }
596665 return worker_state ;
@@ -839,6 +908,8 @@ pgraft_update_shared_memory_from_go(void)
839908 elog (LOG , "pgraft: DEBUG - Got nodes JSON from Go: '%s'" , nodes_json ? nodes_json : "NULL" );
840909 if (nodes_json && strcmp (nodes_json , "[]" ) != 0 )
841910 {
911+ /* NOTE: nodes_json is freed unconditionally below (see the
912+ * matching pgraft_go_free_string after this block). */
842913 /* Parse JSON using separate module */
843914 int node_count ;
844915 int32_t node_ids [16 ];
@@ -870,9 +941,13 @@ pgraft_update_shared_memory_from_go(void)
870941 {
871942 elog (LOG , "pgraft: DEBUG - No valid nodes parsed from JSON" );
872943 }
873-
874- pgraft_go_free_string (nodes_json );
875944 }
945+
946+ /* Free the Go-allocated string in ALL cases (including the common
947+ * "[]" steady-state path), otherwise the worker leaks one string
948+ * every iteration. */
949+ if (nodes_json )
950+ pgraft_go_free_string (nodes_json );
876951 }
877952 }
878953
0 commit comments