48
48
import java .util .concurrent .TimeUnit ;
49
49
import java .util .concurrent .atomic .AtomicBoolean ;
50
50
import java .util .concurrent .atomic .AtomicLong ;
51
+ import java .util .concurrent .atomic .AtomicReference ;
51
52
import java .util .function .BiFunction ;
52
53
import java .util .stream .Collectors ;
53
54
import javax .annotation .Nonnull ;
@@ -259,6 +260,37 @@ protected TopicStatsHelper initialValue() {
259
260
@ Getter
260
261
private final ExecutorService orderedExecutor ;
261
262
263
+ private volatile CloseFutures closeFutures ;
264
+
265
+ /***
266
+ * We use 2 futures to prevent a new closing if there is an in-progress deletion or closing. We make Pulsar return
267
+ * the in-progress one when it is called the second time.
268
+ *
269
+ * The topic closing will be called the below scenarios:
270
+ * 1. Calling "pulsar-admin topics unload". Relate to {@link CloseFutures#waitDisconnectClients}.
271
+ * 2. Namespace bundle unloading. The unloading topic triggered by unloading namespace bundles will not wait for
272
+ * clients disconnect. See {@link CloseFutures#notWaitDisconnectClients}.
273
+ *
274
+ * The two futures will be setting as the below rule:
275
+ * Event: Topic close.
276
+ * - If the first one closing is called by "close and not wait for clients disconnect":
277
+ * - {@link CloseFutures#waitDisconnectClients} will be initialized as "waiting for clients disconnect".
278
+ * - If the first one closing is called by "close and wait for clients disconnect", the two futures will be
279
+ * initialized as "waiting for clients disconnect".
280
+ * Event: Topic delete.
281
+ * the three futures will be initialized as "waiting for clients disconnect".
282
+ */
283
+ private class CloseFutures {
284
+ private final CompletableFuture <Void > notWaitDisconnectClients ;
285
+ private final CompletableFuture <Void > waitDisconnectClients ;
286
+
287
+ public CloseFutures (CompletableFuture <Void > waitDisconnectClients ,
288
+ CompletableFuture <Void > notWaitDisconnectClients ) {
289
+ this .waitDisconnectClients = waitDisconnectClients ;
290
+ this .notWaitDisconnectClients = notWaitDisconnectClients ;
291
+ }
292
+ }
293
+
262
294
private static class TopicStatsHelper {
263
295
public double averageMsgSize ;
264
296
public double aggMsgRateIn ;
@@ -1356,8 +1388,10 @@ private CompletableFuture<Void> delete(boolean failIfHasSubscriptions,
1356
1388
}
1357
1389
1358
1390
fenceTopicToCloseOrDelete (); // Avoid clients reconnections while deleting
1391
+ // Mark the progress of close to prevent close calling concurrently.
1392
+ this .closeFutures = new CloseFutures (new CompletableFuture (), new CompletableFuture ());
1359
1393
1360
- return getBrokerService ().getPulsar ().getPulsarResources ().getNamespaceResources ()
1394
+ CompletableFuture < Void > res = getBrokerService ().getPulsar ().getPulsarResources ().getNamespaceResources ()
1361
1395
.getPartitionedTopicResources ().runWithMarkDeleteAsync (TopicName .get (topic ), () -> {
1362
1396
CompletableFuture <Void > deleteFuture = new CompletableFuture <>();
1363
1397
@@ -1460,6 +1494,10 @@ public void deleteLedgerComplete(Object ctx) {
1460
1494
unfenceTopicToResume ();
1461
1495
}
1462
1496
});
1497
+
1498
+ FutureUtil .completeAfter (closeFutures .notWaitDisconnectClients , res );
1499
+ FutureUtil .completeAfter (closeFutures .waitDisconnectClients , res );
1500
+ return res ;
1463
1501
} finally {
1464
1502
lock .writeLock ().unlock ();
1465
1503
}
@@ -1470,6 +1508,11 @@ public CompletableFuture<Void> close() {
1470
1508
return close (false );
1471
1509
}
1472
1510
1511
+ private enum CloseTypes {
1512
+ notWaitDisconnectClients ,
1513
+ waitDisconnectClients ;
1514
+ }
1515
+
1473
1516
/**
1474
1517
* Close this topic - close all producers and subscriptions associated with this topic.
1475
1518
*
@@ -1478,19 +1521,32 @@ public CompletableFuture<Void> close() {
1478
1521
*/
1479
1522
@ Override
1480
1523
public CompletableFuture <Void > close (boolean closeWithoutWaitingClientDisconnect ) {
1481
- CompletableFuture <Void > closeFuture = new CompletableFuture <>();
1482
1524
1483
- lock .writeLock ().lock ();
1484
- try {
1525
+ CloseTypes closeType ;
1526
+ if (closeWithoutWaitingClientDisconnect ) {
1527
+ closeType = CloseTypes .notWaitDisconnectClients ;
1528
+ } else {
1485
1529
// closing managed-ledger waits until all producers/consumers/replicators get closed. Sometimes, broker
1486
1530
// forcefully wants to close managed-ledger without waiting all resources to be closed.
1487
- if (!isClosingOrDeleting || closeWithoutWaitingClientDisconnect ) {
1488
- fenceTopicToCloseOrDelete ();
1489
- } else {
1490
- log .warn ("[{}] Topic is already being closed or deleted" , topic );
1491
- closeFuture .completeExceptionally (new TopicFencedException ("Topic is already fenced" ));
1492
- return closeFuture ;
1531
+ closeType = CloseTypes .waitDisconnectClients ;
1532
+ }
1533
+
1534
+ lock .writeLock ().lock ();
1535
+ try {
1536
+ // Return in-progress future if exists.
1537
+ if (isClosingOrDeleting ) {
1538
+ switch (closeType ) {
1539
+ case notWaitDisconnectClients -> {
1540
+ return closeFutures .notWaitDisconnectClients ;
1541
+ }
1542
+ case waitDisconnectClients -> {
1543
+ return closeFutures .waitDisconnectClients ;
1544
+ }
1545
+ }
1493
1546
}
1547
+ // No in-progress closing.
1548
+ fenceTopicToCloseOrDelete ();
1549
+ this .closeFutures = new CloseFutures (new CompletableFuture (), new CompletableFuture ());
1494
1550
} finally {
1495
1551
lock .writeLock ().unlock ();
1496
1552
}
@@ -1528,11 +1584,22 @@ public CompletableFuture<Void> close(boolean closeWithoutWaitingClientDisconnect
1528
1584
}
1529
1585
}
1530
1586
1531
- CompletableFuture <Void > clientCloseFuture = closeWithoutWaitingClientDisconnect
1532
- ? CompletableFuture .completedFuture (null )
1533
- : FutureUtil .waitForAll (futures );
1587
+ CompletableFuture <Void > disconnectClientsInCurrentCall = null ;
1588
+ AtomicReference <CompletableFuture <Void >> disconnectClientsToCache = new AtomicReference <>();
1589
+ switch (closeType ) {
1590
+ case notWaitDisconnectClients -> {
1591
+ disconnectClientsInCurrentCall = CompletableFuture .completedFuture (null );
1592
+ disconnectClientsToCache .set (FutureUtil .waitForAll (futures ));
1593
+ break ;
1594
+ }
1595
+ case waitDisconnectClients -> {
1596
+ disconnectClientsInCurrentCall = FutureUtil .waitForAll (futures );
1597
+ disconnectClientsToCache .set (disconnectClientsInCurrentCall );
1598
+ }
1599
+ }
1600
+ CompletableFuture <Void > closeFuture = new CompletableFuture <>();
1534
1601
1535
- clientCloseFuture . thenRun ( () -> {
1602
+ Runnable closeLedgerAfterCloseClients = () -> {
1536
1603
// After having disconnected all producers/consumers, close the managed ledger
1537
1604
ledger .asyncClose (new CloseCallback () {
1538
1605
@ Override
@@ -1547,13 +1614,32 @@ public void closeFailed(ManagedLedgerException exception, Object ctx) {
1547
1614
disposeTopic (closeFuture );
1548
1615
}
1549
1616
}, null );
1550
- }).exceptionally (exception -> {
1617
+ };
1618
+ disconnectClientsInCurrentCall .thenRun (closeLedgerAfterCloseClients ).exceptionally (exception -> {
1551
1619
log .error ("[{}] Error closing topic" , topic , exception );
1552
1620
unfenceTopicToResume ();
1553
1621
closeFuture .completeExceptionally (exception );
1554
1622
return null ;
1555
1623
});
1556
1624
1625
+ switch (closeType ) {
1626
+ case notWaitDisconnectClients -> {
1627
+ FutureUtil .completeAfter (closeFutures .notWaitDisconnectClients , closeFuture );
1628
+ FutureUtil .completeAfterAll (closeFutures .waitDisconnectClients ,
1629
+ closeFuture .thenCompose (ignore -> disconnectClientsToCache .get ().exceptionally (ex -> {
1630
+ // Since the managed ledger has been closed, eat the error of clients disconnection.
1631
+ log .error ("[{}] Closed managed ledger, but disconnect clients failed,"
1632
+ + " this topic will be marked closed" , topic , ex );
1633
+ return null ;
1634
+ })));
1635
+ break ;
1636
+ }
1637
+ case waitDisconnectClients -> {
1638
+ FutureUtil .completeAfter (closeFutures .notWaitDisconnectClients , closeFuture );
1639
+ FutureUtil .completeAfterAll (closeFutures .waitDisconnectClients , closeFuture );
1640
+ }
1641
+ }
1642
+
1557
1643
return closeFuture ;
1558
1644
}
1559
1645
@@ -1839,10 +1925,10 @@ protected CompletableFuture<Void> addReplicationCluster(String remoteCluster, Ma
1839
1925
lock .readLock ().lock ();
1840
1926
try {
1841
1927
if (isClosingOrDeleting ) {
1842
- // Whether is "transferring" or not, do not create new replicator.
1928
+ // Do not create new replicator.
1843
1929
log .info ("[{}] Skip to create replicator because this topic is closing."
1844
- + " remote cluster: {}. State of transferring : {} " ,
1845
- topic , remoteCluster , transferring );
1930
+ + " remote cluster: {}." ,
1931
+ topic , remoteCluster );
1846
1932
return ;
1847
1933
}
1848
1934
Replicator replicator = replicators .computeIfAbsent (remoteCluster , r -> {
0 commit comments