Siyao Meng created HDDS-17015:
---------------------------------
Summary: ConcurrentModificationException in
DbVolume.closeAllDbStore leaves a failed DB volume active and stops the
periodic disk check
Key: HDDS-17015
URL: https://issues.apache.org/jira/browse/HDDS-17015
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments: CR-11-use-concurrent-map-for-db-volume-store-paths.patch,
TestBugCR11DbVolumeMapRace.java
h3. Mechanism
{{DbVolume.hddsDbStorePathMap}} is a {{HashMap}}. {{HddsVolume.createDbStore}}
adds to it through {{addHddsDbStorePath}} while SchemaV3 finalization holds the
data volume set lock. {{DbVolume.failVolume}} iterates it in
{{closeAllDbStore}} while the DB volume set lock is held. No lock is shared, so
an add during the loop makes the iterator throw
{{ConcurrentModificationException}}.
The exception leaves {{MutableVolumeSet.failVolume}} before the volume is moved
to the failed set, then {{handleVolumeFailures}}, {{checkAllVolumes}} and
{{StorageVolumeChecker.checkAllVolumeSets}}, which only catches
{{IOException}}. That method is the body of the {{scheduleWithFixedDelay}} task
of the periodic disk check, and a scheduled task that throws is not run again.
h3. Trigger
A datanode with dedicated DB volumes ({{hdds.datanode.container.db.dir}})
finalizes SchemaV3 while the periodic disk check fails one of its DB volumes,
and a {{createDbStore}} adds a path to that DB volume during the close loop.
Before finalization completes each new DB is closed right after creation, so
the loop only does cache lookups and the window is very short.
h3. Impact
* The DB volume is FAILED but stays in the active DB volume set. The rest of
the finalize loop can still place new DBs on it, and the DB load that ends the
finalization ({{HddsVolumeUtil.loadAllHddsVolumeDbStore}}) maps its data
volumes back to it and reopens their DBs there (by code reading).
* The periodic disk check stops for all volume sets until the datanode
restarts, so later disk failures are not detected by it. The exception is never
logged, because nothing reads the scheduled future; the operator only sees the
earlier log lines that mark the DB volume as failed. On demand checks after I/O
errors still run.
* Rare: needs a DB volume failure during the one-time finalization, and the
window was not hit naturally.
h3. Reproduction
PASS on unmodified source. [^TestBugCR11DbVolumeMapRace.java]
({{hadoop-hdds/container-service}}, target path and Maven command in the class
comment) registers a real DB volume set with a real {{StorageVolumeChecker}}
and runs the real {{checkAllVolumeSets}} as a {{scheduleWithFixedDelay}} task.
The disk check verdict is stubbed to report the DB volume as failed, and
{{DatanodeStoreCache}} is mocked so that the first {{removeDB}} of the close
loop registers one more DB path, as a concurrent {{createDbStore}} would.
Output: {{periodic check runs=1 dbVolumeState=FAILED stillInActiveSet=true
failedSet=0}}, and the scheduled future completes with the
{{ConcurrentModificationException}} from {{DbVolume.closeAllDbStore}} and is
not run again. A passing test means the defect is present.
h3. Patch
[^CR-11-use-concurrent-map-for-db-volume-store-paths.patch], against
cce1ca28944886218132f01c0e271c1fd0700078. It also applies to master at
2536fe9cbc3.
The map becomes a {{ConcurrentHashMap}}. Its iterators do not throw on
concurrent changes, so {{failVolume}} completes and the periodic task keeps
running. Keys and values are never null, and the other uses ({{put}},
{{clear}}, {{keySet}}, {{values}}) behave the same.
Covered by {{testFailVolumeWhileDbStorePathIsAdded}} in the existing
{{TestDbVolume}}, which adds a path from the first {{removeDB}} call of
{{failVolume}} and asserts that the volume is failed and every earlier path was
closed. Without the change it fails with {{ConcurrentModificationException}}.
With it the DB volume, volume, volume set, checker and upgrade suites (83
tests) pass and checkstyle is clean. With the patch the reproduction fails
because the periodic task keeps running.
{{checkAllVolumeSets}} still lets any other {{RuntimeException}} end the
periodic check; catching it there could be a follow up.
Found by code review of the datanode volume and per-disk RocksDB lifecycle
paths, as part of the TLA+ verification effort under HDDS-15926, on commit
cce1ca28944886218132f01c0e271c1fd0700078. 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
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]