Siyao Meng created HDDS-17358:
---------------------------------

             Summary: Closing the OM DB during a compaction DAG prune batch 
stalls for 60 seconds and can then crash the OM when the pruner reads the freed 
DB
                 Key: HDDS-17358
                 URL: https://issues.apache.org/jira/browse/HDDS-17358
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: MC-2-stop-differ-prune-tasks-using-db-after-close.patch, 
TestBugMC2DifferPruneUseAfterClose.java

h3. Mechanism
{{RDBStore.close}} closes the checkpoint differ through 
{{RocksDBCheckpointDifferHolder.invalidateCacheEntry}} and then flushes and 
closes the DB. {{RocksDBCheckpointDiffer.close}} holds the differ monitor 
({{synchronized (this)}}) for the whole {{Scheduler.close}} call, which runs 
{{shutdownNow()}} and {{awaitTermination(60, SECONDS)}} and ignores the result.

{{pruneSstFileValues}} enters the same monitor once per compaction log entry 
and reads and writes the compaction log table through the raw 
{{ManagedRocksDB}} and column family handle set by {{RDBStore}}, without a 
{{RocksDatabase}} reference. If the closer takes the monitor between two 
entries of a batch, the pruner waits to enter it. That wait does not react to 
the interrupt from {{shutdownNow()}}, so the 60 second wait always runs out. 
The closer then releases the monitor and the pruner, which never checks 
{{closed}} or its interrupt flag, takes it and keeps reading the compaction log 
table while {{RDBStore.close}} flushes and frees the DB. If the batch still has 
entries at that point, its next {{RocksDB.get}} after the free runs on freed 
memory. A batch that ends first exits cleanly, because the scheduler is shut 
down.

{{pruneOlderSnapshotsWithCompactionHistory}} has the same exposure in 
{{getOlderFileNodes}} and {{removeKeyFromCompactionLogTable}}, with a much 
narrower window (code reading only).

h3. Trigger
# An OM that has had snapshots, so that completed compactions left entries in 
the compaction log table. Every DB open queues all entries for 
{{pruneSstFileValues}} again.
# The rocks-tools native library is loaded, so the prune queue exists. This is 
the default for Linux release binaries 
({{ozone.om.snapshot.load.native.lib=true}}, built with 
{{-Drocks_tools_native}}).
# The OM DB is closed while a prune batch is running: {{OzoneManager.stop}}, or 
{{OzoneManager.installCheckpoint}} on a follower, which closes the store and 
reopens it in the same process. Batches start every 10 minutes by default.
# For the crash only: the batch still has entries left when the DB is freed. 
This is likely when the batch rewrites backup SST files, and unlikely for a 
batch of entries that are already pruned.

No fault injection is needed.

h3. Impact
* Every close that lands in the middle of a prune batch blocks 
{{RDBStore.close}} for 60 seconds.
* After that stall the OM JVM can abort with SIGSEGV or SIGBUS in 
{{RocksDB.get}} called from {{RocksDBCheckpointDiffer.pruneSstFileValues}}, if 
the batch still has entries when the DB is freed.
* On a follower installing a checkpoint, an OM that should keep running can die 
instead (argued from the code path, not run end to end). On 
{{OzoneManager.stop}} the stop stalls and can end in a crash instead of a clean 
exit.
* No data loss is expected: the OM recovers on restart.

h3. Reproduction
Crash on unmodified source, deterministic setup with a timing race that the 
test lines up. [^TestBugMC2DifferPruneUseAfterClose.java] 
({{hadoop-hdds/framework}}) writes 100,000 already pruned compaction log 
entries through the table API, so that the batch is still running when the DB 
is freed, reopens the store with the compaction DAG enabled, holds the differ 
monitor only until both the pruner (inside {{pruneSstFileValues}}) and a thread 
calling {{RDBStore.close}} wait for it, then releases it. The only stub reports 
the native library as loaded while the store opens, because it is not built on 
the test host; the pruner never calls it for already pruned entries. Without 
the fix the forked JVM dies 60 seconds after "Shutting down 
CompactionDagPruningService" with SIGSEGV in {{RocksDB.get}} from 
{{pruneSstFileValues}}. With the fix the test passes, {{RDBStore.close}} 
returns in a few milliseconds and the pruner stops its batch. Not run on Linux 
with the real native library.

h3. Patch
[^MC-2-stop-differ-prune-tasks-using-db-after-close.patch], against 
bf3829e6c8ac69ae17585585703f91426dcdf6b3. It also applies to master at 
fa3c98f189e.

{{RocksDBCheckpointDiffer.close}} sets {{closed}} under the monitor and shuts 
the scheduler down after releasing it, so a waiting task can get the monitor. 
Every use of the DB by the scheduled tasks happens under the monitor and now 
first checks {{closed}}: {{pruneSstFileValues}} ends its batch, 
{{getOlderFileNodes}} returns nothing and {{removeKeyFromCompactionLogTable}} 
returns. No task can then touch the DB after {{close}} returns, whatever the 
bounded scheduler wait returns, and that wait now ends as soon as the task sees 
{{closed}}. It is the same {{closed}} flag the compaction listeners already 
check. Nothing changes when no task is running.

Covered by two tests in the existing {{TestRocksDBCheckpointDiffer}}. 
{{testPruneTasksDoNotUseDbAfterClose}}: after {{close}}, running the two prune 
tasks must leave the compaction log entry in the table and in the prune queue; 
without the change it fails because the entry is deleted. 
{{testCloseDoesNotWaitForTaskBlockedOnDifferLock}}: a scheduled task that tries 
to enter the differ monitor while {{close}} waits for it must not keep 
{{close}} waiting; without the change {{close}} is still waiting after 10 
seconds. With the change the differ module tests (122, 2 skipped that need the 
native library), {{TestRDBStore}} (17) and {{TestOMSnapshotDAG}} on a mini 
cluster (2) pass, checkstyle is clean.

The patch touches the same loop as the patch on HDDS-17291 (a different defect 
in {{pruneSstFileValues}}), so whichever lands second needs a small rebase.

Found by TLA+ model checking and code review of the RocksDB handle close and 
shutdown paths 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