amaechler commented on code in PR #20476:
URL: https://github.com/apache/druid/pull/20476#discussion_r4174752803


##########
server/src/main/java/org/apache/druid/curator/announcement/NodeAnnouncer.java:
##########
@@ -94,6 +96,17 @@ public class NodeAnnouncer implements ServiceAnnouncer
   @GuardedBy("toAnnounce")
   private final List<String> parentsIBuilt = new CopyOnWriteArrayList<>();
 
+  /**
+   * Watches a node that already existed when we tried to create our 
announcement, see {@link #watchExistingNode}.
+   */
+  private final CuratorWatcher existingNodeWatcher = event -> {

Review Comment:
   Fixed in 90ba8da901: `NodeAnnouncer` now registers a connection-state 
listener that re-announces every path on `RECONNECTED`, so the create hits 
`NODEEXISTS` again and the watch is set again from the new session. Added 
`testReinstatesWhenOwnSessionExpiresWhileWatchingStaleNode`, which fails 
without the listener. In practice the cache usually recovers here because it 
still holds the stale node after reconnecting; this only matters when the cache 
missed it too. `PathChildrenAnnouncer` already re-creates its paths after a 
session loss.



##########
server/src/main/java/org/apache/druid/curator/announcement/NodeAnnouncer.java:
##########
@@ -327,7 +336,47 @@ public void update(final String path, final byte[] bytes)
 
   private void createAnnouncement(final String path, byte[] value) throws 
Exception
   {
-    
curator.create().compressed().withMode(CreateMode.EPHEMERAL).inBackground().forPath(path,
 value);
+    curator.create().compressed().withMode(CreateMode.EPHEMERAL).inBackground(
+        (client, event) -> {
+          if (event.getResultCode() == 
KeeperException.Code.NODEEXISTS.intValue()) {
+            watchExistingNode(path);
+          } else if (event.getResultCode() != 
KeeperException.Code.OK.intValue()) {
+            log.warn("Failed to announce node[%s]: %s", path, 
KeeperException.Code.get(event.getResultCode()));
+          }
+        }
+    ).forPath(path, value);
+  }
+
+  /**
+   * Watches a node that was already present when we tried to create our 
announcement at its path, for example one
+   * left behind by a previous session of this process. The announcement is 
re-created once that node is removed,
+   * even if the {@link CuratorCache} never saw it.
+   */
+  private void watchExistingNode(final String path) throws Exception
+  {
+    curator.checkExists().usingWatcher(existingNodeWatcher).inBackground(
+        (client, event) -> {
+          if (event.getResultCode() == KeeperException.Code.NONODE.intValue()) 
{
+            reinstateAnnouncement(path);
+          } else if (event.getResultCode() == 
KeeperException.Code.OK.intValue()) {
+            final long owner = event.getStat().getEphemeralOwner();
+            if (owner != 
curator.getZookeeperClient().getZooKeeper().getSessionId()) {
+              log.info("Node[%s] already exists with session[0x%x], will 
reinstate once it is removed.", path, owner);
+            }
+          } else {
+            log.warn("Failed to watch existing node[%s]: %s", path, 
KeeperException.Code.get(event.getResultCode()));
+          }
+        }
+    ).forPath(path);
+  }
+
+  private void reinstateAnnouncement(final String path) throws Exception
+  {
+    final byte[] value = announcedPaths.get(path);

Review Comment:
   Fixed in 90ba8da901: the create is now queued inside `computeIfPresent` on 
the announcement's entry, so a concurrent `unannounce` either removes the entry 
first (no create) or waits until the create is queued, and its delete follows 
the create (one session's requests are handled in order while connected). This 
race already existed in the cache's removal path; I reproduced it with a forced 
interleaving before the fix and confirmed it no longer happens after.



##########
server/src/main/java/org/apache/druid/curator/announcement/PathChildrenAnnouncer.java:
##########
@@ -388,7 +392,49 @@ public void update(final String path, final byte[] bytes)
 
   private void createAnnouncement(final String path, byte[] value) throws 
Exception
   {
-    
curator.create().compressed().withMode(CreateMode.EPHEMERAL).inBackground().forPath(path,
 value);
+    curator.create().compressed().withMode(CreateMode.EPHEMERAL).inBackground(
+        (client, event) -> {
+          if (event.getResultCode() == 
KeeperException.Code.NODEEXISTS.intValue()) {
+            watchExistingNode(path);
+          } else if (event.getResultCode() != 
KeeperException.Code.OK.intValue()) {
+            log.warn("Failed to announce node[%s]: %s", path, 
KeeperException.Code.get(event.getResultCode()));
+          }
+        }
+    ).forPath(path, value);
+  }
+
+  /**
+   * Watches a node that was already present when we tried to create our 
announcement at its path, for example one
+   * left behind by a previous session of this process. The announcement is 
re-created once that node is removed,
+   * even if the {@link PathChildrenCache} never saw it.
+   */
+  private void watchExistingNode(final String path) throws Exception
+  {
+    curator.checkExists().usingWatcher(existingNodeWatcher).inBackground(
+        (client, event) -> {
+          if (event.getResultCode() == KeeperException.Code.NONODE.intValue()) 
{
+            reinstateAnnouncement(path);
+          } else if (event.getResultCode() == 
KeeperException.Code.OK.intValue()) {
+            final long owner = event.getStat().getEphemeralOwner();
+            if (owner != 
curator.getZookeeperClient().getZooKeeper().getSessionId()) {
+              log.info("Node[%s] already exists with session[0x%x], will 
reinstate once it is removed.", path, owner);
+            }
+          } else {
+            log.warn("Failed to watch existing node[%s]: %s", path, 
KeeperException.Code.get(event.getResultCode()));
+          }
+        }
+    ).forPath(path);
+  }
+
+  private void reinstateAnnouncement(final String path) throws Exception
+  {
+    final ZKPaths.PathAndNode pathAndNode = ZKPaths.getPathAndNode(path);
+    final ConcurrentMap<String, byte[]> subPaths = 
announcements.get(pathAndNode.getPath());

Review Comment:
   Same fix as in `NodeAnnouncer` (90ba8da901): the create is queued inside 
`computeIfPresent` on the entry, so a concurrent `unannounce` either prevents 
it or deletes the node afterwards.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to