fix(publish): require submit marker before platform write
Desktop Client Build / Resolve Build Metadata (push) Successful in 20s
Frontend CI / Frontend (push) Successful in 2m57s
Desktop Client Build / Build Desktop Client (push) Successful in 25m11s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 34s
Backend CI / Backend (push) Successful in 50m56s
Desktop Client Build / Resolve Build Metadata (push) Successful in 20s
Frontend CI / Frontend (push) Successful in 2m57s
Desktop Client Build / Build Desktop Client (push) Successful in 25m11s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 34s
Backend CI / Backend (push) Successful in 50m56s
This commit is contained in:
@@ -1677,7 +1677,11 @@ function armPublishTaskDeadline(
|
||||
|
||||
function resolvePublishTaskTimeoutMs(platform: string): number {
|
||||
const envKey = `GEO_DESKTOP_PUBLISH_TIMEOUT_${platform.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_MS`
|
||||
return resolvePositiveEnvInteger(envKey) ?? resolvePositiveEnvInteger('GEO_DESKTOP_PUBLISH_TIMEOUT_MS') ?? defaultPublishTaskTimeoutMs
|
||||
return (
|
||||
resolvePositiveEnvInteger(envKey) ??
|
||||
resolvePositiveEnvInteger('GEO_DESKTOP_PUBLISH_TIMEOUT_MS') ??
|
||||
defaultPublishTaskTimeoutMs
|
||||
)
|
||||
}
|
||||
|
||||
function resolvePositiveEnvInteger(key: string): number | null {
|
||||
@@ -1695,16 +1699,30 @@ function resolvePositiveEnvInteger(key: string): number | null {
|
||||
function isIrreversiblePublishStage(stage: string): boolean {
|
||||
const normalized = stage.trim().toLowerCase()
|
||||
return (
|
||||
normalized.endsWith('.create_note') ||
|
||||
normalized.endsWith('.create_article_id') ||
|
||||
normalized.endsWith('.create_draft') ||
|
||||
normalized.endsWith('.publish') ||
|
||||
normalized.endsWith('.publish_draft') ||
|
||||
normalized.endsWith('.save_content') ||
|
||||
normalized.endsWith('.submit') ||
|
||||
normalized.endsWith('.submit_editor') ||
|
||||
normalized.endsWith('.save_draft') ||
|
||||
normalized === 'create_note' ||
|
||||
normalized === 'create_article_id' ||
|
||||
normalized === 'create_draft' ||
|
||||
normalized === 'publish' ||
|
||||
normalized === 'publish_draft' ||
|
||||
normalized === 'save_content' ||
|
||||
normalized === 'submit' ||
|
||||
normalized === 'submit_editor' ||
|
||||
normalized === 'save_draft'
|
||||
)
|
||||
}
|
||||
|
||||
// Persist a durable "submit started" marker (server-side, before/at the irreversible POST) and a
|
||||
// local flag the abort path reads. Fires at most once per execution and never blocks publishing.
|
||||
function markPublishSubmitStartedIfNeeded(taskId: string, stage: string): void {
|
||||
// Persist a durable "submit started" marker before the irreversible platform write. The local flag
|
||||
// is set only after the server confirms the marker, so recovery can trust publish_submit_started_at.
|
||||
async function markPublishSubmitStartedIfNeeded(taskId: string, stage: string): Promise<void> {
|
||||
if (!isIrreversiblePublishStage(stage)) {
|
||||
return
|
||||
}
|
||||
@@ -1712,19 +1730,31 @@ function markPublishSubmitStartedIfNeeded(taskId: string, stage: string): void {
|
||||
if (!active || active.kind !== 'publish' || active.submitStartedAt !== null) {
|
||||
return
|
||||
}
|
||||
active.submitStartedAt = Date.now()
|
||||
state.activeExecutions.set(taskId, active)
|
||||
|
||||
const leaseToken = state.tasks.get(taskId)?.leaseToken
|
||||
if (!leaseToken) {
|
||||
return
|
||||
throw new Error('publish_submit_marker_lease_missing')
|
||||
}
|
||||
await markDesktopTaskPublishSubmitStarted(taskId, leaseToken)
|
||||
|
||||
active.submitStartedAt = Date.now()
|
||||
state.activeExecutions.set(taskId, active)
|
||||
}
|
||||
|
||||
function asSubmitUncertainExecution(result: AdapterExecutionResult): AdapterExecutionResult {
|
||||
if (result.status !== 'failed') {
|
||||
return result
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
status: 'unknown',
|
||||
summary: '发布可能已提交到平台,结果待人工确认,已避免自动重复发布。',
|
||||
error: {
|
||||
...(result.error ?? {}),
|
||||
publish_submit_uncertain: true,
|
||||
original_status: 'failed',
|
||||
original_summary: result.summary,
|
||||
},
|
||||
}
|
||||
void markDesktopTaskPublishSubmitStarted(taskId, leaseToken).catch((error) => {
|
||||
console.warn('[desktop-runtime] mark publish submit started failed', {
|
||||
taskId,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function pumpExecutionLoop(): void {
|
||||
@@ -2824,11 +2854,7 @@ async function extendActiveLease(taskId: string, leaseToken: string): Promise<vo
|
||||
existing.summary = `发布任务超过 ${Math.round(idleMs / 1000)} 秒没有新进度,已中止并释放,准备由服务端重排${detail}。`
|
||||
existing.updatedAt = Date.now()
|
||||
state.tasks.set(taskId, existing)
|
||||
recordActivity(
|
||||
'warn',
|
||||
'发布任务疑似卡住',
|
||||
`${existing.title} ${existing.summary}`,
|
||||
)
|
||||
recordActivity('warn', '发布任务疑似卡住', `${existing.title} ${existing.summary}`)
|
||||
// Abort the local execution instead of merely letting the lease lapse: this
|
||||
// releases the slot immediately and, crucially, closes the window where the
|
||||
// server could re-queue the task while a stalled adapter is still mid-publish
|
||||
@@ -2914,16 +2940,19 @@ async function executeTaskAdapter(
|
||||
signal,
|
||||
phase: 'initial',
|
||||
article: await loadPublishArticle(task),
|
||||
reportProgress(stage: string) {
|
||||
async reportProgress(stage: string) {
|
||||
noteMonitorExecutionSafePoint(task.id, null)
|
||||
updateTaskProgress(task.id, `publish adapter progress: ${stage}`)
|
||||
markPublishSubmitStartedIfNeeded(task.id, stage)
|
||||
await markPublishSubmitStartedIfNeeded(task.id, stage)
|
||||
},
|
||||
},
|
||||
payload,
|
||||
)
|
||||
await maybeReportTaskAuthFailure(task, result, accountIdentity)
|
||||
return result
|
||||
const active = state.activeExecutions.get(task.id)
|
||||
const normalizedResult =
|
||||
active?.submitStartedAt != null ? asSubmitUncertainExecution(result) : result
|
||||
await maybeReportTaskAuthFailure(task, normalizedResult, accountIdentity)
|
||||
return normalizedResult
|
||||
} finally {
|
||||
await playwrightLease?.release()
|
||||
if (viewHandle) {
|
||||
@@ -3358,7 +3387,11 @@ function notifyLongRunningPublishTasks(): void {
|
||||
task.summary = `${task.summary || '发布任务仍在执行。'}(已执行 ${formatDurationText(now - active.startedAt)},如网络异常可取消后重试。)`
|
||||
task.updatedAt = now
|
||||
state.tasks.set(task.id, task)
|
||||
recordActivity('warn', '发布等待较久', `${task.title} 已执行 ${formatDurationText(now - active.startedAt)},可能存在网络异常。`)
|
||||
recordActivity(
|
||||
'warn',
|
||||
'发布等待较久',
|
||||
`${task.title} 已执行 ${formatDurationText(now - active.startedAt)},可能存在网络异常。`,
|
||||
)
|
||||
emitRuntimeInvalidated('publish-task-progress', { taskId: task.id })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user