improve job cancelling with new flow jobs locks

This commit is contained in:
Ruben Fiszel
2025-09-20 11:00:45 +00:00
parent 9a21107848
commit 1afb7a2156
9 changed files with 45 additions and 77 deletions
@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_status SET flow_status = JSONB_SET(JSONB_SET(\n flow_status, \n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $4), \n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $5)\n WHERE id = $2 AND flow_status->'modules'->$1::int->'flow_jobs_duration' IS NOT NULL",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Text",
"Jsonb",
"Jsonb"
]
},
"nullable": []
},
"hash": "054fc3f9d6776b9068816b7568ca83a2e1f07007b500b556b841181b3adebfb8"
}
@@ -0,0 +1,27 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE v2_job_status SET flow_status = \n JSONB_SET(JSONB_SET(JSONB_SET(JSONB_SET(\n flow_status, \n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), \n ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb),\n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $5),\n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $6)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "int4",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Text",
"Jsonb",
"Jsonb",
"Jsonb"
]
},
"nullable": [
null
]
},
"hash": "ac5dd4a4d7991159e053f1c11bce03a6d51c24fc9a2d8ac872aaf41a20af5045"
}
@@ -1,25 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE v2_job_status SET flow_status = \n JSONB_SET(JSONB_SET(\n flow_status, \n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), \n ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "int4",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Text",
"Jsonb"
]
},
"nullable": [
null
]
},
"hash": "d70ddf86eb77a1fdac1e03d2c1aa39101520409c5f241bc8d1b4190bb13260a9"
}
+11 -26
View File
@@ -28,6 +28,7 @@ use serde_json::value::RawValue;
use serde_json::{json, Value};
use sqlx::types::Json;
use sqlx::{FromRow, Postgres, Transaction};
use tokio::spawn;
use tracing::instrument;
use uuid::Uuid;
use windmill_common::auth::get_job_perms;
@@ -531,16 +532,20 @@ pub async fn update_flow_status_after_job_completion_internal(
sqlx::query_scalar!(
"
UPDATE v2_job_status SET flow_status =
JSONB_SET(JSONB_SET(
JSONB_SET(JSONB_SET(JSONB_SET(JSONB_SET(
flow_status,
ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),
ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb)
ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb),
ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $5),
ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $6)
WHERE id = $2
RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int",
old_status.step,
flow,
position as i32,
json!(success),
flow_job_duration.as_ref().map(|x| json!(x.started_at)).unwrap_or_default(),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms)).unwrap_or_default(),
)
} else {
sqlx::query_scalar!(
@@ -563,26 +568,6 @@ pub async fn update_flow_status_after_job_completion_internal(
))
})?.ok_or_else(|| Error::internal_err(format!("requiring an index in InProgress for flow {flow} at step {}", old_status.step)))?;
if let Some(position) = position {
// if we do this in a single statement, somehow cancelling the flow job will have issues because the RETURNING clause will return None
// Could not figure out why, so we do it in two steps
if let Err(e) = sqlx::query!(
"UPDATE v2_job_status SET flow_status = JSONB_SET(JSONB_SET(
flow_status,
ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $4),
ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $5)
WHERE id = $2 AND flow_status->'modules'->$1::int->'flow_jobs_duration' IS NOT NULL",
old_status.step as i32,
flow,
position as i32,
flow_job_duration.as_ref().map(|x| json!(x.started_at)),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms))
)
.execute(&mut *tx)
.await {
tracing::error!("error while updating flow jobs duration: {e:#}");
}
}
// let status_for_debug = sqlx::query!(
// "SELECT flow_status FROM v2_job_status WHERE id = $1",
// flow
@@ -636,8 +621,8 @@ pub async fn update_flow_status_after_job_completion_internal(
flow,
position as i32,
json!(success),
flow_job_duration.as_ref().map(|x| json!(x.started_at)),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms))
flow_job_duration.as_ref().map(|x| json!(x.started_at)).unwrap_or_default(),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms)).unwrap_or_default(),
)
} else {
sqlx::query_scalar!(
@@ -1617,8 +1602,8 @@ async fn set_success_and_duration_in_flow_job_success<'c>(
flow,
position as i32,
json!(success),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms)),
flow_job_duration.as_ref().map(|x| json!(x.started_at))
flow_job_duration.as_ref().map(|x| json!(x.duration_ms)).unwrap_or_default(),
flow_job_duration.as_ref().map(|x| json!(x.started_at)).unwrap_or_default()
)
.execute(&mut **tx)
.await
@@ -583,7 +583,7 @@
bind:suspendStatus
hideDownloadInGraph={customUi?.downloadLogs === false}
wideResults
bind:flowStateStore={flowStateStore.val}
bind:flowState={flowStateStore.val}
{jobId}
onDone={() => {
isRunning = false
@@ -11,7 +11,7 @@
jobId: string
initialJob?: Job | undefined
workspaceId?: string | undefined
flowStateStore?: FlowState
flowState?: FlowState
selectedJobStep?: string | undefined
hideFlowResult?: boolean
hideTimeline?: boolean
@@ -39,7 +39,7 @@
jobId,
initialJob = undefined,
workspaceId = undefined,
flowStateStore = $bindable({}),
flowState = $bindable({}),
selectedJobStep = $bindable(undefined),
hideFlowResult = false,
hideTimeline = false,
@@ -67,7 +67,7 @@
let globalRefreshes: Record<string, ((clear, root) => Promise<void>)[]> = $state({})
setContext<FlowStatusViewerContext>('FlowStatusViewer', {
flowStateStore,
flowState: flowState,
suspendStatus,
retryStatus,
hideDownloadInGraph,
@@ -88,7 +88,7 @@
retryStatus.val = {}
suspendStatus.val = {}
globalRefreshes = {}
flowStateStore.val = {}
flowState.val = {}
localDurationStatuses = {}
localModuleStates = {}
}
@@ -48,7 +48,7 @@
import JobAssetsViewer from './assets/JobAssetsViewer.svelte'
let {
flowStateStore,
flowState: flowStateStore,
retryStatus,
suspendStatus,
hideDownloadInGraph,
@@ -6,7 +6,6 @@ import { charsToNumber, numberToChars } from './idUtils'
// Computes the next available id
export function nextId(flowState: FlowState, fullFlow: OpenFlow): string {
const allIds = dfs(fullFlow.value.modules, (fm) => fm.id)
console.log('allIds', allIds)
const max = allIds.concat(Object.keys(flowState)).reduce((acc, key) => {
if (key.length >= 4) {
+1 -1
View File
@@ -28,7 +28,7 @@ export type DurationStatus = {
}
export type FlowStatusViewerContext = {
flowStateStore?: FlowState
flowState?: FlowState
retryStatus: StateStore<Record<string, number | undefined>>
suspendStatus: StateStore<Record<string, { nb: number; job: Job }>>
hideDownloadInGraph?: boolean