[
https://issues.apache.org/jira/browse/HDDS-17359?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-17359:
------------------------------
Attachment: CR-1-keep-snapshot-diff-db-open-while-diff-tasks-run.patch
> OmSnapshotManager.close frees the snapshot diff DB while a snapshot diff job
> that outlived the 60 second wait still uses it
> ---------------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-17359
> URL: https://issues.apache.org/jira/browse/HDDS-17359
> Project: Apache Ozone
> Issue Type: Bug
> Affects Versions: 1.4.0, 2.0.0, 1.4.1, 2.1.0, 2.2.0, 2.1.1, 2.1.2, 2.2.1
> Reporter: Siyao Meng
> Priority: Minor
> Attachments:
> CR-1-keep-snapshot-diff-db-open-while-diff-tasks-run.patch,
> TestBugCR1SnapDiffDbFreedUnderRunningJob.java
>
>
> h3. Mechanism
> {{OmSnapshotManager.close}} first calls {{SnapshotDiffManager.close}}, which
> runs {{shutdownNow()}} on the snapshot diff executor and
> {{awaitTermination(60, SECONDS)}}, ignores the result, and returns at once if
> the closing thread is interrupted. It then closes the snapshot cache, shuts
> down {{SnapshotDiffCleanupService}} ({{BackgroundService.shutdown}}, the same
> ignored 60 second wait), closes the column family handles and closes
> {{snapshotDiffDb}}.
> Snapshot diff jobs (through {{RocksDbPersistentMap}} and the column family
> helpers) and the cleanup service (directly) use {{snapshotDiffDb}} as a raw
> {{ManagedRocksDB}}, with no reference count, and jobs never check their
> interrupt flag. A job still running when the wait ends therefore keeps using
> the DB after it is freed.
> Both callers stop the OM metadata manager before this close, so a job that
> next reads the OM DB fails and ends inside the wait. The jobs that outlive it
> are those in a long step that reads only snapshot DBs and {{snapshotDiffDb}},
> such as {{addToObjectIdMap}} over a large delta. When the wait ends, closing
> the snapshot cache makes such a job fail on its next read, and its failure
> path ({{updateJobStatusToFailed}}, then dropping its column families) races
> the free a few milliseconds later.
> h3. Trigger
> # A snapshot diff job is running on an OM, in a step that takes longer than
> the 60 second wait plus the time the OM spends before
> {{OmSnapshotManager.close}}.
> # The OM closes the snapshot manager: {{OzoneManager.stop}}, or
> {{OzoneManager.installCheckpoint}} on a follower. The latter closes snapshot
> DBs at its start, so a job there has to be in a step that reads neither
> snapshot DBs nor the OM DB for the whole install (argued from the code, not
> run).
> # The job reaches {{snapshotDiffDb}} after the free.
> No fault injection is needed, but the last step is a race.
> h3. Impact
> * The OM JVM aborts with SIGSEGV in {{RocksDB.get}} on the snapshot diff job
> thread, after {{OzoneManager.stop}} has already waited 60 seconds.
> * On a follower installing a checkpoint, an OM that should keep running can
> die instead (narrow, see above).
> * No data loss is expected: the OM recovers on restart.
> h3. Reproduction
> Crash on unmodified source, deterministic.
> [^TestBugCR1SnapDiffDbFreedUnderRunningJob.java]
> ({{hadoop-ozone/ozone-manager}}) starts one OM, creates a volume, a bucket
> and two snapshots, and submits a snapshot diff through the client protocol.
> The only stub makes the job's first metrics call wait, ignoring interrupts,
> until {{OzoneManager.stop}} returns, in place of a long step. Without the fix
> the forked JVM dies with SIGSEGV in {{RocksDB.get}} called from
> {{RocksDbPersistentMap.get}} in
> {{SnapshotDiffManager.updateJobStatusToFailed}}, 60 seconds after "Shutting
> down executorService: 'SnapDiffExecutor'". With the fix the test passes: the
> OM logs a warning and keeps the DB open, the job fails cleanly, records its
> status and ends. The test shows the free under a running job; it does not
> measure how often a real job loses the race.
> h3. Patch
> [^CR-1-keep-snapshot-diff-db-open-while-diff-tasks-run.patch], against
> bf3829e6c8ac69ae17585585703f91426dcdf6b3. It also applies to master at
> fa3c98f189e.
> {{OmSnapshotManager.close}} checks, after the existing bounded waits, whether
> the snapshot diff executor and the cleanup service executor have terminated,
> through a new {{isTerminated}} on {{SnapshotDiffManager}} and on
> {{BackgroundService}}. If either has not, it logs one warning and does not
> close the column family handles, the DB or its options.
> {{OmSnapshotLocalDataManager}} is now closed before that check so it is still
> closed in every case; it does not use the snapshot diff DB. Shutdown stays
> bounded: the patch chooses leaking an open native DB over freeing it under a
> thread that will use it. On {{OzoneManager.stop}} the process exits anyway.
> On {{installCheckpoint}} the reopen of the snapshot diff DB then fails on the
> RocksDB lock and the OM exits through its normal error path instead of
> crashing (argued from the code). Nothing changes when no task is running.
> Closing the snapshot cache before {{SnapshotDiffManager.close}} would further
> make most such jobs end inside the wait, because their snapshot DB iterators
> stop once the DB is closed; not tested and not part of this patch.
> Covered by {{testCloseKeepsSnapshotDiffDbWhileDiffJobRuns}} in the existing
> {{TestOmSnapshotManager}}: closing a snapshot manager whose snapshot diff
> executor still runs a task must leave the snapshot diff DB open, and closing
> it again after the task ends must close it. Without the change it fails
> because the DB is closed. With it {{TestOmSnapshotManager}},
> {{TestSnapshotDiffManager}}, {{TestSnapshotCache}},
> {{TestSnapshotDiffCleanupService}} and the other snapshot manager unit tests
> (121) and {{TestOzoneManagerHASnapshot}} on a mini cluster (8) pass,
> checkstyle is clean.
> HDDS-14856 covers raw {{ManagedRocksDB}} users that bypass the
> {{RocksDatabase}} reference count, including {{RocksDbPersistentMap}} and
> {{SnapshotDiffCleanupService}}. Reference counting the snapshot diff DB there
> would turn this crash into an exception and supersede this patch; this patch
> is a narrow guard for the shutdown path only and does not overlap with it.
> Found by code review of the RocksDB handle close and shutdown paths, as part
> of the TLA+ verification effort under HDDS-15926, on commit
> bf3829e6c8ac69ae17585585703f91426dcdf6b3. Checked against HDDS issues and
> apache/ozone pull requests for duplicates before filing. The attached patch
> is a proposal for review. Generated with Specula (Claude Opus 5.5).
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]