{"record":{"id":"a501e38a6779fe49","repo":"influxdata/influxdb","slug":"table-snapshot-persistence-task-failed","errorCode":null,"errorMessage":"Table snapshot persistence task failed","messagePattern":"Table snapshot persistence task failed","errorType":"error_code","errorClass":"TableIndexError","httpStatus":null,"severity":"error","filePath":"influxdb3_write/src/table_index.rs","lineNumber":70,"sourceCode":"        actual: TableIndexId,\n    },\n\n    #[error(\"Failed to parse table index path from object store path\")]\n    TableIndexPath(#[source] crate::paths::PathError),\n\n    #[error(\"Failed to parse table index snapshot path from object store path\")]\n    TableIndexSnapshotPath(#[source] crate::paths::PathError),\n\n    #[error(\"Failed to update table index: join task failed\")]\n    UpdateTaskFailed(#[source] tokio::task::JoinError),\n\n    #[error(\"Object meta is missing filename\")]\n    MissingFilename,\n\n    #[error(\"Failed to parse snapshot sequence number from filename\")]\n    InvalidSnapshotSequenceNumber,\n\n    #[error(\"Table snapshot persistence task failed\")]\n    TableSnapshotPersistenceTaskFailed(#[source] tokio::task::JoinError),\n\n    #[error(\"Unexpected error\")]\n    Unexpected(#[source] anyhow::Error),\n\n    #[error(\"Object store operation failed\")]\n    ObjectStore(#[source] object_store::Error),\n\n    #[error(\"JSON serialization/deserialization failed\")]\n    Json(#[source] serde_json::Error),\n}\n\npub type Result<T> = std::result::Result<T, TableIndexError>;\n\n/// An partial, incremental index into a given database/table. Should not be used to make decisions\n/// regarding gen1 file retention -- instead, all snapshots should be aggregated into a full\n/// CoreTableIndex.\n///","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/influxdata/influxdb/blob/06200ef96ba82c5f6727e5038a83af8e722c6875/influxdb3_write/src/table_index.rs#L52-L88","documentation":"TableIndexError::TableSnapshotPersistenceTaskFailed is raised when the tokio task spawned to persist table snapshots is joined and returns a JoinError — the persistence task panicked or was cancelled. Persisting snapshots is done concurrently (bounded by a Semaphore) across tables; this error identifies which spawned work failed to complete.","triggerScenarios":"Awaiting JoinHandles from the FuturesOrdered of spawned snapshot-persist tasks; a task panics (serialization bug, poisoned lock) or the runtime/caller cancels it (drop of the handle, runtime shutdown).","commonSituations":"Panic inside serde serialization of a snapshot; runtime shutdown during shutdown-with-inflight-persists; timeouts implemented by aborting tasks; single-threaded runtime misconfiguration.","solutions":["Check JoinError::is_panic() vs is_cancelled(); for panics, extract the payload via into_panic() and match logs to the failing table.","Avoid aborting/dropping persistence task handles; use graceful shutdown that drains in-flight persists.","Retry the persistence for the affected tables — persistence is idempotent per snapshot sequence number.","If reproducible, treat as a library bug and report with the snapshot data that triggers the panic."],"exampleFix":"// before\nfor res in futures { res.await?; }\n// after\nfor res in futures {\n    res.await.map_err(|e| {\n        if e.is_panic() { error!(\"snapshot persist panicked: {:?}\", e.into_panic()); }\n        TableIndexError::TableSnapshotPersistenceTaskFailed(e)\n    })?;\n}","handlingStrategy":"try-catch","validationCode":"// bound concurrency so cancellation/panics are isolated per table\nlet sem = Arc::new(Semaphore::new(num_cpus));\nlet _permit = sem.acquire().await;","typeGuard":"fn is_cancelled_join(e: &tokio::task::JoinError) -> bool { e.is_cancelled() }","tryCatchPattern":"match handle.await {\n    Ok(()) => {},\n    Err(e) if e.is_cancelled() => {\n        warn!(\"snapshot persist cancelled; will be retried next snapshot cycle\");\n    }\n    Err(e) => return Err(TableIndexError::TableSnapshotPersistenceTaskFailed(e)),\n}","preventionTips":["Return Result from persistence task bodies instead of panicking.","Drain in-flight persist tasks during graceful shutdown.","Retry failed snapshot persists — they are idempotent per sequence number.","Bound concurrency with a Semaphore (as the module does) to limit blast radius."],"tags":["tokio","task-panic","snapshot","concurrency"],"backgroundTag":"thread-interrupted","analyzedSha":"06200ef96ba82c5f6727e5038a83af8e722c6875","analyzedAt":"2026-09-19T12:55:30.003Z","contentChangedAt":"2026-09-19T12:55:30.003Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}