Siyao Meng created HDDS-17355:
---------------------------------

             Summary: Datanode stop aborts halfway when a schema V1/V2 
container DB is still in use, ContainerCache.shutdownCache throws 
IllegalArgumentException
                 Key: HDDS-17355
                 URL: https://issues.apache.org/jira/browse/HDDS-17355
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: CR-8-skip-container-dbs-in-use-on-cache-shutdown.patch, 
TestBugCR8ShutdownCacheAbortsStop.java

h3. Mechanism
{{ContainerCache.shutdownCache}} runs 
{{Preconditions.checkArgument(cleanupDb(db), "refCount: %s", ...)}} for every 
cached schema V1/V2 container DB, and {{ReferenceCountedDB.cleanup}} returns 
false while the DB is referenced. Since HDDS-6540 this is on the datanode stop 
path: {{OzoneContainer.stop}} calls {{KeyValueHandler.stop}}, then 
{{BlockManagerImpl.shutdown}} and {{BlockUtils.shutdownCache}}. Nothing on the 
way out catches the {{IllegalArgumentException}} 
({{DatanodeStateMachine.stopDaemon}} only catches {{IOException}}), so the rest 
of the shutdown is skipped: the remaining cached V1/V2 DBs, 
{{DatanodeStoreCache.shutdownCache}} (the schema V3 per volume DBs), the volume 
sets and the other services after the handlers in {{OzoneContainer.stop}}, and 
the remainder of {{DatanodeStateMachine.close}} and 
{{HddsDatanodeService.stop}}. Nothing retries the skipped steps. The message is 
{{refCount: 1}} at this commit and {{refCount: [1]}} in released versions, 
whose format string has no placeholder.

h3. Trigger
A datanode holding schema V1/V2 containers (upgraded from a release before 
schema V3, or with schema V3 disabled) stops while a thread holds a handle from 
{{BlockUtils.getDB}}. {{OzoneContainer.stop}} stops the handlers before 
{{blockDeletingService}}, so a running {{BlockDeletingTask}} is a natural 
holder. HDDS-16131 hit the same exception in a test teardown that called 
{{BlockUtils.shutdownCache}} while a {{BlockDeletingTask}} still held a DB, and 
was fixed in the test only. {{DeleteBlocksCommandHandler}} workers are another 
holder, since their pool is stopped after {{container.stop()}} (code reading, 
not run).

h3. Impact
* On a normal SIGTERM the shutdown hook logs the exception and the JVM exits, 
so the skipped DBs are closed by process exit and RocksDB recovers on the next 
start.
* When the datanode stops itself through 
{{HddsDatanodeService.terminateDatanode}} ({{stop(); terminate(1);}}, used when 
Ratis closes a container state machine in {{ContainerStateMachine}}, for too 
many failed volumes, a critical state machine error, a failed certificate 
rollback and a failure to persist a new certificate ID in {{saveNewCertId}}), 
the exception escapes {{stop()}} and {{terminate(1)}} is not reached. Checked 
locally with a test that starts an {{HddsDatanodeService}} with a schema V2 
container, holds a {{BlockUtils.getDB}} handle and calls {{terminateDatanode}}: 
{{stop()}} throws {{IllegalArgumentException}}, {{terminate(1)}} is not called, 
{{isStopped}} is true, and two non daemon threads (one of them Jetty's 
scheduler) stay alive, so the JVM does not exit. With the handle released, or 
with the patch, {{terminate(1)}} is called and no extra non daemon thread is 
left. The datanode is meant to exit there so that a supervisor restarts it; 
instead it stays up with its services stopped until someone restarts it by hand.

h3. Reproduction
PASS on unmodified source, deterministic. 
[^TestBugCR8ShutdownCacheAbortsStop.java] ({{hadoop-hdds/container-service}}) 
starts a real {{OzoneContainer}} with schema V3 enabled, creates two schema V2 
containers, holds a handle on one of them through {{BlockUtils.getDB}} and 
calls {{OzoneContainer.stop}} on the same thread. {{stop}} throws 
{{IllegalArgumentException: refCount: 1}}; afterwards the other V2 DB and the 
schema V3 per volume DB are still open and the data volume was not shut down. A 
second {{stop}} after the handle is released completes the shutdown. A passing 
test means the defect is present. The {{terminateDatanode}} check above is a 
separate local test, not attached.

h3. Patch
[^CR-8-skip-container-dbs-in-use-on-cache-shutdown.patch], against 
bf3829e6c8ac69ae17585585703f91426dcdf6b3. It also applies to master at 
4fb68e96831.

{{shutdownCache}} closes and removes every DB it can close and leaves a DB that 
is still referenced open and in the cache, with one WARN line naming it, 
instead of throwing. Closing it would make the user's next DB call fail and 
break the reference count contract. A skipped DB is closed by a later eviction, 
{{removeDB}} or {{shutdownCache}} once released, or by process exit on a real 
stop, which is what happens to every DB today after the exception. Nothing 
changes when no DB is referenced. Reordering {{OzoneContainer.stop}} to stop 
background services first would only narrow the window, so it is not part of 
the patch.

Covered by {{testShutdownCacheKeepsDBInUseOpen}} in the existing 
{{TestContainerCache}}, which fails without the change with 
{{IllegalArgumentException: refCount: 1}}. With the change 
{{TestContainerCache}}, {{TestOzoneContainer}}, {{TestBlockDeletingService}}, 
{{TestContainerPersistence}}, the schema one and two backwards compatibility 
suites, {{TestBlockManagerImpl}}, {{TestContainerReader}}, 
{{TestDatanodeStoreCache}}, {{TestKeyValueBlockIterator}} and 
{{TestStaleRecoveringContainerScrubbingService}} pass (586 tests), and 
checkstyle is clean. With the patch the reproduction fails at its first 
assertion because {{stop}} no longer throws.

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