fix(controller): job start_time/finish_time/run_time never set by apply-pump

apply_committed_entry()'s shared WAL_JOB_START/FINISH/STOP/RESUME/RUSAGE/
CKPT/PREASON dispatch case only ever called the generic, state-only
apply_job_update_transition(j, state) -- start_time/finish_time/run_time
were never touched, so `xcecli joblist` always showed StartTime=0 for
RUN/DONE/EXIT jobs and FinishTime=0 for DONE jobs (EXIT jobs got a
finish_time only via the separate, richer WAL_JOB_FAIL dispatch path).

Fix is additive: stamp time(NULL) keyed on wal_type right after
apply_job_update_transition() succeeds, still under the same
lock(j, RTTI_LOCK_WRITE) -- WAL_JOB_START sets start_time, WAL_JOB_FINISH
sets finish_time and derives run_time. No change to
apply_job_update_transition()'s signature or the dispatch structure.

Add test_ace_job_start_sets_start_time and
test_ace_job_finish_sets_finish_time_and_run_time to
test_apply_committed_entry.c, following the existing tace_seed_job
fixture pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-08-20 04:24:44 +00:00
parent 008553e982
commit 5147396d0e
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
4 changed files with 90 additions and 1 deletions

View file

@ -3714,7 +3714,9 @@ int apply_committed_entry(persist_t* p, uint32_t wal_type, uint64_t lsn,
* apply_committed_entry for the full routing table and why this
* does not dispatch to the richer type-specific apply_job_{start,
* finish}_transition() cores (their parameter structs are not
* present on this wire format). */
* present on this wire format) — start_time/finish_time/run_time
* are instead stamped directly below, keyed on wal_type, since
* apply_job_update_transition() only ever touches `state`. */
if (len < sizeof(wal_jobstate_t))
return 0; /* undersized payload — skip, mirrors apply_wal_entry */
@ -3729,6 +3731,21 @@ int apply_committed_entry(persist_t* p, uint32_t wal_type, uint64_t lsn,
lock(j, RTTI_LOCK_WRITE);
job_transition_result_t result =
apply_job_update_transition(j, (int)js->state);
if (result == JOB_TRANSITION_APPLIED) {
/* apply_job_update_transition() only carries {jobid, state} on
* this wire shape (see the doc comment above) and never touches
* start_time/finish_time/run_time itself — stamp them here,
* keyed on wal_type, mirroring what the richer
* apply_job_start_transition()/apply_job_finish_transition()
* cores do for their own (currently unreachable on this dispatch
* path) callers. */
if (wal_type == WAL_JOB_START) {
j->job.start_time = time(NULL);
} else if (wal_type == WAL_JOB_FINISH) {
j->job.finish_time = time(NULL);
j->job.run_time = (int)(j->job.finish_time - j->job.start_time);
}
}
unlock(j);
if (result != JOB_TRANSITION_APPLIED)

View file

@ -31,6 +31,7 @@
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unity/unity.h>
@ -402,6 +403,61 @@ test_ace_job_state_transition_rejects_backward_suppresses_notify(void)
"notify: PASS\n");
}
static void test_ace_job_start_sets_start_time(void)
{
tace_setup();
job_t* j = tace_seed_job(603, JOB_STAT_PEND);
TEST_ASSERT_NOT_NULL(j);
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, (uint64_t)j->job.start_time,
"fixture must seed a job with start_time still unset");
wal_jobstate_t js = { .jobid = 603, .state = JOB_STAT_RUN };
time_t before = time(NULL);
int rc = apply_committed_entry(&g_tace_p, WAL_JOB_START, 22, &js,
sizeof(js));
time_t after = time(NULL);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_RUN, j->job.state);
TEST_ASSERT_TRUE_MESSAGE(
(j->job.start_time >= before) && (j->job.start_time <= after),
"WAL_JOB_START must stamp start_time to the apply-time clock");
tace_teardown();
printf("test_ace_job_start_sets_start_time: PASS\n");
}
static void test_ace_job_finish_sets_finish_time_and_run_time(void)
{
tace_setup();
job_t* j = tace_seed_job(604, JOB_STAT_RUN);
TEST_ASSERT_NOT_NULL(j);
j->job.start_time = 1000;
wal_jobstate_t js = { .jobid = 604, .state = JOB_STAT_DONE };
time_t before = time(NULL);
int rc = apply_committed_entry(&g_tace_p, WAL_JOB_FINISH, 23, &js,
sizeof(js));
time_t after = time(NULL);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_DONE, j->job.state);
TEST_ASSERT_TRUE_MESSAGE(
(j->job.finish_time >= before) && (j->job.finish_time <= after),
"WAL_JOB_FINISH must stamp finish_time to the apply-time clock");
TEST_ASSERT_EQUAL_INT_MESSAGE((int)(j->job.finish_time - 1000),
j->job.run_time,
"run_time must be derived from the stamped finish_time minus the "
"job's start_time");
tace_teardown();
printf("test_ace_job_finish_sets_finish_time_and_run_time: PASS\n");
}
/* ── WAL_QUEUE_CTRL ───────────────────────────────────────────────────── */
static void test_ace_queue_ctrl_updates_and_notifies(void)
@ -1041,6 +1097,8 @@ void test_apply_committed_entry(void)
RUN_TEST(test_ace_job_submit_duplicate_is_noop_no_extra_notify);
RUN_TEST(test_ace_job_state_transition_applies_forward_and_notifies);
RUN_TEST(test_ace_job_state_transition_rejects_backward_suppresses_notify);
RUN_TEST(test_ace_job_start_sets_start_time);
RUN_TEST(test_ace_job_finish_sets_finish_time_and_run_time);
RUN_TEST(test_ace_queue_ctrl_updates_and_notifies);
RUN_TEST(test_ace_worker_ctrl_join_restores_and_notifies);
RUN_TEST(test_ace_worker_ctrl_update_applies_to_existing_worker);