[ 
https://issues.apache.org/jira/browse/HDDS-17394?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Siyao Meng updated HDDS-17394:
------------------------------
    Attachment: CR-4-thread-safe-reconfiguration-complete-callbacks.patch

> 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
>    Affects Versions: 2.1.0, 2.2.0, 2.1.1, 2.1.2, 2.2.1
>            Reporter: Siyao Meng
>            Priority: Minor
>         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