Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/jobu/scheduler_core_priv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "attempt_executor.hpp"
#include "attempt_repository_priv.hpp"
#include "database.hpp"
#include "job_lifecycle_priv.hpp"
#include "json.hpp"
#include "recurrence_priv.hpp"
#include "retry_policy_priv.hpp"
Expand Down Expand Up @@ -596,6 +597,14 @@ auto process_completion(jb::db::Database& database,
if (!successor) {
return CoreResult<CompletionEffect>::failure(std::move(successor).error());
}

// The final run and its one-time definition must commit together. Reconcile before suspension drain so a
// Suspending definition moves directly to its terminal result when this was its last outstanding run.
JobLifecycleRepository lifecycle{database, attributes};
auto finished = lifecycle.finish_after_terminal_run(completion.key.run_id, completed_at);
if (!finished) {
return CoreResult<CompletionEffect>::failure(std::move(finished).error());
}
}

auto drained = repository.complete_drained_suspensions(context->run.queue_id, context->run.job_id, completed_at);
Expand Down
12 changes: 7 additions & 5 deletions test/jobud-cli-integration-test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -933,11 +933,13 @@ TEST_CASE("real daemon keeps the Phase 8 workflow durable across retry, controls
CHECK(counts.at("attempts").as_object().at("total").as_uint() >= 2);
CHECK(counts.at("runnable_wait_ms").is_null());

// Deletion requires a fully suspended definition; terminal history retains its reference template.
auto suspended = json(fixture.control({"job", "suspend", retry_job_id, "--wait", "--json"}));
auto const deletion_revision = std::to_string(suspended.as_object().at("revision").as_uint());
CHECK(fixture.control({"job", "delete", retry_job_id, "--revision", deletion_revision, "--json"}) == "null\n");
CHECK(fixture.control({"secret", "delete", "workflow.token", "--json"}) == "null\n");
// Completion changes the one-time definition, while its symbolic secret reference remains owned by that
// definition until an explicit deletion.
auto finished_job = json(fixture.control({"job", "get", retry_job_id, "--json"}));
CHECK(finished_job.as_object().at("state").as_string() == "succeeded");
CHECK(finished_job.as_object().at("revision").as_uint() == created.as_object().at("revision").as_uint() + 1);
auto retained_reference = fixture.control({"secret", "delete", "workflow.token", "--json"}, 1);
CHECK(retained_reference.find("jobu.secret.in_use") != std::string::npos);

// A future cron occurrence remains scheduled while Run Now creates a separate manual run.
jb::test::HttpTestServer manual_server;
Expand Down
Loading