Siyao Meng created HDDS-17359:
---------------------------------

             Summary: 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
            Reporter: Siyao Meng
         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]

Reply via email to