Siyao Meng created HDDS-17394:
---------------------------------

             Summary: Registering a reconfiguration callback while a 
reconfiguration is completing skips the remaining callbacks and the status 
listener
                 Key: HDDS-17394
                 URL: https://issues.apache.org/jira/browse/HDDS-17394
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
CR-4-thread-safe-reconfiguration-complete-callbacks.patch, 
TestBugCR4CallbackRegisteredDuringReconfiguration.java

h3. Mechanism
{{ReconfigurationHandler}} keeps its complete callbacks in a plain 
{{ArrayList}}. {{ReconfigurationHandler.registerCompleteCallback}} appends to 
it without a lock, and {{ReconfigurationHandler.triggerCompleteCallbacks}} 
iterates it on the reconfiguration task thread (while holding 
{{ReconfigurableBase}}'s {{reconfigLock}}, which registration does not take). 
An append during that loop makes the iterator throw 
{{ConcurrentModificationException}} on its next step. {{ReconfigurableBase}} 
catches it and logs "Reconfiguration complete callback threw exception" at 
WARN, so every callback after the one that was running is skipped, and so is 
the status listener.

The task has already recorded every changed property as successful and set the 
new values on the live configuration before the callbacks run. {{ozone admin 
reconfig status}} therefore reports success, and running the reconfiguration 
again finds no change, so the skipped callbacks are not run again.

The sibling collections in the class are a {{ConcurrentHashMap}} and a 
{{ConcurrentSkipListSet}}, and 
{{ReconfigurableBase.addReconfigurationCompleteCallback}} synchronizes, so the 
plain list looks like an oversight.

h3. Trigger
In the OM the only registration that can happen while the process runs is in 
the {{DirectoryDeletingService}} constructor. 
{{OzoneManager.installCheckpoint}} constructs a new one ({{keyManager.start}} 
or {{reloadOMState}}) whenever a follower installs a checkpoint from the 
leader. Nothing orders that with a reconfiguration task an admin started on 
that same follower with {{ozone admin reconfig --service=OM --address=<that OM 
host:port> start}}: the install holds the OM monitor, which the reconfiguration 
thread does not take, and stopping the RPC server does not stop a task that is 
already running.

The datanode and SCM register their callbacks before their RPC servers start, 
so they are not affected today.

h3. Impact
Which callbacks are lost depends on when the append lands. The OM list holds, 
in order, the SCM proxy reload callback (only with an SCM service ID), the 
tracing callback, and one callback per {{DirectoryDeletingService}} ever 
constructed.
* Usually the skipped callbacks are those of directory deleting services that 
the install has already stopped, plus the status listener, which only logs. 
Running those callbacks would restart the stopped services (HDDS-17296), so 
skipping them is harmless, and the new instance reads the new values from the 
OM configuration. The visible effect is the WARN with the stack trace.
* If the same reconfiguration also changes the SCM node list, and the append 
lands while the SCM proxies are being reloaded, the tracing callback is skipped 
too, so a tracing change reported as successful is not applied until the next 
restart or reconfiguration that changes a tracing key. Argued from the code and 
shown with the real handler in the reproduction, not run with a real checkpoint 
install.

h3. Reproduction
PASS, deterministic, unmodified source. 
[^TestBugCR4CallbackRegisteredDuringReconfiguration.java] 
({{hadoop-hdds/framework}}) drives a real {{ReconfigurationHandler}} and its 
reconfiguration task. The first callback registers another callback from a 
second thread and waits for it, which stands in for the 
{{DirectoryDeletingService}} constructor on the install path. The callback 
registered before the reconfiguration and the status listener are not called, 
the WARN with {{ConcurrentModificationException}} is logged, the status reports 
the property change as successful, and a second reconfiguration finds no change 
and still does not call the skipped callback. A passing test means the defect 
is present.

h3. Patch
[^CR-4-thread-safe-reconfiguration-complete-callbacks.patch], against 
9c3e5477ffb9f41da639051aaa5e9d7bd8b53fe4. It also applies to master at 
c521a16af75.

The list becomes a {{CopyOnWriteArrayList}}. Its iterator works on the 
callbacks registered when the loop started, so a concurrent registration cannot 
throw, every earlier callback runs, and the new callback runs from the next 
reconfiguration on. A service created during a reconfiguration already reads 
the new values, so it does not need the current run.

Covered by {{runsAllCallbacksWhenOneIsRegisteredDuringReconfiguration}} in the 
existing {{TestReconfigurationHandler}}: a callback registers another one from 
a second thread during a real reconfiguration; the test checks that the earlier 
callback and the status listener run, and that the new callback runs in the 
next reconfiguration. Without the change neither the earlier callback nor the 
status listener is called. With it {{TestReconfigurationHandler}} and 
{{TestOzoneIntegrationNonHA}} (340 tests, including the OM, SCM and datanode 
reconfiguration and reconfig shell suites) pass, and checkstyle is clean. With 
the patch the reproduction fails at its first assertion, because the later 
callback and the listener are called.

The race needs an install that constructs a {{DirectoryDeletingService}}; the 
patch attached to HDDS-17296 replaces that per instance registration with one 
registered at OM construction, which removes the OM trigger. This change keeps 
the handler safe for any registration at runtime.

Found by code review of the BackgroundService restart and shutdown paths and 
the reconfiguration callback handling, as part of the TLA+ verification effort 
under HDDS-15926, on commit 9c3e5477ffb9f41da639051aaa5e9d7bd8b53fe4. Checked 
against HDDS issues and apache/ozone pull requests for duplicates before 
filing. Related but not duplicates: HDDS-12554 (added the callback list), 
HDDS-17296 (stale directory deleting service callbacks after an install). 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