|
|
@@ -253,6 +253,45 @@ void WorkflowScheduler::notifyExecutionFinished(const std::string& workflow_id,
|
|
|
void WorkflowScheduler::checkAndExecute() {
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
|
|
|
|
+ // A slot is held on an in-memory event, and an event can be lost - the
|
|
|
+ // database going down mid-run is enough. Until this, a lost completion held
|
|
|
+ // the slot until its deadline, which for a workflow that sets no maximum is
|
|
|
+ // five times its interval: a quarter-hourly schedule stopped for 75 minutes
|
|
|
+ // and only said "still running, skipping this tick" while it did.
|
|
|
+ //
|
|
|
+ // The execution record knows the truth, so ask it. Outside the lock,
|
|
|
+ // deliberately: the case being fixed is a database that is slow or down, and
|
|
|
+ // blocking the scheduler's mutex on it would stall every other workflow too.
|
|
|
+ if (execution_finished_check_) {
|
|
|
+ std::vector<std::pair<std::string, std::string>> held; // workflow id, execution id
|
|
|
+ {
|
|
|
+ std::lock_guard<std::mutex> lock(mutex_);
|
|
|
+ for (const auto& [id, entry] : workflows_) {
|
|
|
+ for (const auto& [execution_id, deadline] : entry.active_runs) {
|
|
|
+ held.emplace_back(id, execution_id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ std::vector<std::pair<std::string, std::string>> finished;
|
|
|
+ for (const auto& [workflow_id, execution_id] : held) {
|
|
|
+ if (execution_finished_check_(execution_id)) {
|
|
|
+ finished.emplace_back(workflow_id, execution_id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!finished.empty()) {
|
|
|
+ std::lock_guard<std::mutex> lock(mutex_);
|
|
|
+ for (const auto& [workflow_id, execution_id] : finished) {
|
|
|
+ auto it = workflows_.find(workflow_id);
|
|
|
+ if (it == workflows_.end()) continue;
|
|
|
+ if (it->second.active_runs.erase(execution_id) > 0) {
|
|
|
+ spdlog::info("Workflow '{}' run {} had already finished - releasing the slot "
|
|
|
+ "its completion event never freed",
|
|
|
+ it->second.workflow_name, execution_id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// Collect workflows that need to run
|
|
|
std::vector<ScheduledWorkflow> to_execute;
|
|
|
|
|
|
@@ -313,8 +352,9 @@ void WorkflowScheduler::checkAndExecute() {
|
|
|
case OverlapPolicy::Skip:
|
|
|
default:
|
|
|
if (active > 0) {
|
|
|
- spdlog::info("Workflow '{}' still running, skipping this tick",
|
|
|
- entry.workflow_name);
|
|
|
+ spdlog::info("Workflow '{}' still running ({} in flight), skipping this "
|
|
|
+ "tick",
|
|
|
+ entry.workflow_name, active);
|
|
|
entry.next_run = now + std::chrono::minutes(entry.interval_minutes);
|
|
|
continue;
|
|
|
}
|