@@ -1044,38 +1044,38 @@ public CompletableFuture<Optional<Topic>> getTopic(final String topic, boolean c
1044
1044
return getTopic (TopicName .get (topic ), createIfMissing , properties );
1045
1045
}
1046
1046
1047
+ /**
1048
+ * Retrieves or creates a topic based on the specified parameters.
1049
+ * 0. If disable PersistentTopics or NonPersistentTopics, it will return a failed future with NotAllowedException.
1050
+ * 1. If topic future exists in the cache returned directly regardless of whether it fails or timeout.
1051
+ * 2. If the topic metadata exists, the topic is created regardless of {@code createIfMissing}.
1052
+ * 3. If the topic metadata not exists, and {@code createIfMissing} is false,
1053
+ * returns an empty Optional in a CompletableFuture. And this empty future not be added to the map.
1054
+ * 4. Otherwise, use computeIfAbsent. It returns the existing topic or creates and adds a new topicFuture.
1055
+ * Any exceptions will remove the topicFuture from the map.
1056
+ *
1057
+ * @param topicName The name of the topic, potentially including partition information.
1058
+ * @param createIfMissing If true, creates the topic if it does not exist.
1059
+ * @param properties Topic configuration properties used during creation.
1060
+ * @return CompletableFuture with an Optional of the topic if found or created, otherwise empty.
1061
+ */
1047
1062
public CompletableFuture <Optional <Topic >> getTopic (final TopicName topicName , boolean createIfMissing ,
1048
1063
Map <String , String > properties ) {
1049
1064
try {
1050
- CompletableFuture <Optional <Topic >> topicFuture = topics .get (topicName .toString ());
1051
- if (topicFuture != null ) {
1052
- if (topicFuture .isCompletedExceptionally ()
1053
- || (topicFuture .isDone () && !topicFuture .getNow (Optional .empty ()).isPresent ())) {
1054
- // Exceptional topics should be recreated.
1055
- topics .remove (topicName .toString (), topicFuture );
1056
- } else {
1057
- // a non-existing topic in the cache shouldn't prevent creating a topic
1058
- if (createIfMissing ) {
1059
- if (topicFuture .isDone () && topicFuture .getNow (Optional .empty ()).isPresent ()) {
1060
- return topicFuture ;
1061
- } else {
1062
- return topicFuture .thenCompose (value -> {
1063
- if (!value .isPresent ()) {
1064
- // retry and create topic
1065
- return getTopic (topicName , createIfMissing , properties );
1066
- } else {
1067
- // in-progress future completed successfully
1068
- return CompletableFuture .completedFuture (value );
1069
- }
1070
- });
1071
- }
1072
- } else {
1073
- return topicFuture ;
1074
- }
1075
- }
1065
+ // If topic future exists in the cache returned directly regardless of whether it fails or timeout.
1066
+ CompletableFuture <Optional <Topic >> tp = topics .get (topicName .toString ());
1067
+ if (tp != null ) {
1068
+ return tp ;
1076
1069
}
1077
1070
final boolean isPersistentTopic = topicName .getDomain ().equals (TopicDomain .persistent );
1078
1071
if (isPersistentTopic ) {
1072
+ if (!pulsar .getConfiguration ().isEnablePersistentTopics ()) {
1073
+ if (log .isDebugEnabled ()) {
1074
+ log .debug ("Broker is unable to load persistent topic {}" , topicName );
1075
+ }
1076
+ return FutureUtil .failedFuture (new NotAllowedException (
1077
+ "Broker is unable to load persistent topic" ));
1078
+ }
1079
1079
return pulsar .getPulsarResources ().getTopicResources ().persistentTopicExists (topicName )
1080
1080
.thenCompose (exists -> {
1081
1081
if (!exists && !createIfMissing ) {
@@ -1090,44 +1090,48 @@ public CompletableFuture<Optional<Topic>> getTopic(final TopicName topicName, bo
1090
1090
throw FutureUtil .wrapToCompletionException (new ServiceUnitNotReadyException (errorInfo ));
1091
1091
}).thenCompose (optionalTopicPolicies -> {
1092
1092
final TopicPolicies topicPolicies = optionalTopicPolicies .orElse (null );
1093
- return topics . computeIfAbsent (topicName .toString (), ( tpName ) -> {
1094
- if (topicName .isPartitioned ()) {
1095
- final TopicName topicNameEntity = TopicName . get ( topicName . getPartitionedTopicName ());
1096
- return fetchPartitionedTopicMetadataAsync ( topicNameEntity )
1097
- . thenCompose (( metadata ) -> {
1098
- // Allow crate non-partitioned persistent topic that name includes
1099
- // `partition`
1100
- if ( metadata .partitions == 0
1101
- || topicName .getPartitionIndex () < metadata . partitions ) {
1102
- return loadOrCreatePersistentTopic (tpName , createIfMissing ,
1103
- properties , topicPolicies );
1104
- }
1093
+ if (topicName .isPartitioned ()) {
1094
+ final TopicName topicNameEntity = TopicName . get (topicName .getPartitionedTopicName ());
1095
+ return fetchPartitionedTopicMetadataAsync ( topicNameEntity )
1096
+ . thenCompose (( metadata ) -> {
1097
+ // Allow crate non-partitioned persistent topic that name includes
1098
+ // `partition`
1099
+ if ( metadata . partitions == 0
1100
+ || topicName . getPartitionIndex () < metadata .partitions ) {
1101
+ return topics . computeIfAbsent ( topicName .toString (), ( tpName ) ->
1102
+ loadOrCreatePersistentTopic (tpName ,
1103
+ createIfMissing , properties , topicPolicies ) );
1104
+ } else {
1105
1105
final String errorMsg =
1106
1106
String .format ("Illegal topic partition name %s with max allowed "
1107
1107
+ "%d partitions" , topicName , metadata .partitions );
1108
1108
log .warn (errorMsg );
1109
1109
return FutureUtil .failedFuture (
1110
1110
new BrokerServiceException .NotAllowedException (errorMsg ));
1111
- });
1112
- }
1113
- return loadOrCreatePersistentTopic (tpName , createIfMissing , properties , topicPolicies );
1114
- }).thenCompose (optionalTopic -> {
1115
- if (!optionalTopic .isPresent () && createIfMissing ) {
1116
- log .warn ("[{}] Try to recreate the topic with createIfMissing=true "
1117
- + "but the returned topic is empty" , topicName );
1118
- return getTopic (topicName , createIfMissing , properties );
1119
- }
1120
- return CompletableFuture .completedFuture (optionalTopic );
1121
- });
1111
+ }
1112
+ });
1113
+ } else {
1114
+ return topics .computeIfAbsent (topicName .toString (), (tpName ) ->
1115
+ loadOrCreatePersistentTopic (tpName , createIfMissing , properties , topicPolicies ));
1116
+ }
1122
1117
});
1123
1118
});
1124
1119
} else {
1125
- return topics .computeIfAbsent (topicName .toString (), (name ) -> {
1120
+ if (!pulsar .getConfiguration ().isEnableNonPersistentTopics ()) {
1121
+ if (log .isDebugEnabled ()) {
1122
+ log .debug ("Broker is unable to load non-persistent topic {}" , topicName );
1123
+ }
1124
+ return FutureUtil .failedFuture (new NotAllowedException (
1125
+ "Broker is unable to load persistent topic" ));
1126
+ }
1127
+ if (!topics .containsKey (topicName .toString ())) {
1126
1128
topicEventsDispatcher .notify (topicName .toString (), TopicEvent .LOAD , EventStage .BEFORE );
1127
- if (topicName .isPartitioned ()) {
1128
- final TopicName partitionedTopicName = TopicName .get (topicName .getPartitionedTopicName ());
1129
- return this .fetchPartitionedTopicMetadataAsync (partitionedTopicName ).thenCompose ((metadata ) -> {
1130
- if (topicName .getPartitionIndex () < metadata .partitions ) {
1129
+ }
1130
+ if (topicName .isPartitioned ()) {
1131
+ final TopicName partitionedTopicName = TopicName .get (topicName .getPartitionedTopicName ());
1132
+ return this .fetchPartitionedTopicMetadataAsync (partitionedTopicName ).thenCompose ((metadata ) -> {
1133
+ if (topicName .getPartitionIndex () < metadata .partitions ) {
1134
+ return topics .computeIfAbsent (topicName .toString (), (name ) -> {
1131
1135
topicEventsDispatcher
1132
1136
.notify (topicName .toString (), TopicEvent .CREATE , EventStage .BEFORE );
1133
1137
@@ -1138,11 +1142,13 @@ public CompletableFuture<Optional<Topic>> getTopic(final TopicName topicName, bo
1138
1142
topicEventsDispatcher
1139
1143
.notifyOnCompletion (eventFuture , topicName .toString (), TopicEvent .LOAD );
1140
1144
return res ;
1141
- }
1142
- topicEventsDispatcher .notify (topicName .toString (), TopicEvent .LOAD , EventStage .FAILURE );
1143
- return CompletableFuture .completedFuture (Optional .empty ());
1144
- });
1145
- } else if (createIfMissing ) {
1145
+ });
1146
+ }
1147
+ topicEventsDispatcher .notify (topicName .toString (), TopicEvent .LOAD , EventStage .FAILURE );
1148
+ return CompletableFuture .completedFuture (Optional .empty ());
1149
+ });
1150
+ } else if (createIfMissing ) {
1151
+ return topics .computeIfAbsent (topicName .toString (), (name ) -> {
1146
1152
topicEventsDispatcher .notify (topicName .toString (), TopicEvent .CREATE , EventStage .BEFORE );
1147
1153
1148
1154
CompletableFuture <Optional <Topic >> res = createNonPersistentTopic (name );
@@ -1152,11 +1158,15 @@ public CompletableFuture<Optional<Topic>> getTopic(final TopicName topicName, bo
1152
1158
topicEventsDispatcher
1153
1159
.notifyOnCompletion (eventFuture , topicName .toString (), TopicEvent .LOAD );
1154
1160
return res ;
1155
- } else {
1161
+ });
1162
+ } else {
1163
+ CompletableFuture <Optional <Topic >> topicFuture = topics .get (topicName .toString ());
1164
+ if (topicFuture == null ) {
1156
1165
topicEventsDispatcher .notify (topicName .toString (), TopicEvent .LOAD , EventStage .FAILURE );
1157
- return CompletableFuture .completedFuture (Optional .empty ());
1166
+ topicFuture = CompletableFuture .completedFuture (Optional .empty ());
1158
1167
}
1159
- });
1168
+ return topicFuture ;
1169
+ }
1160
1170
}
1161
1171
} catch (IllegalArgumentException e ) {
1162
1172
log .warn ("[{}] Illegalargument exception when loading topic" , topicName , e );
@@ -1295,15 +1305,9 @@ private CompletableFuture<Optional<Topic>> createNonPersistentTopic(String topic
1295
1305
CompletableFuture <Optional <Topic >> topicFuture = new CompletableFuture <>();
1296
1306
topicFuture .exceptionally (t -> {
1297
1307
pulsarStats .recordTopicLoadFailed ();
1308
+ pulsar .getExecutor ().execute (() -> topics .remove (topic , topicFuture ));
1298
1309
return null ;
1299
1310
});
1300
- if (!pulsar .getConfiguration ().isEnableNonPersistentTopics ()) {
1301
- if (log .isDebugEnabled ()) {
1302
- log .debug ("Broker is unable to load non-persistent topic {}" , topic );
1303
- }
1304
- return FutureUtil .failedFuture (
1305
- new NotAllowedException ("Broker is not unable to load non-persistent topic" ));
1306
- }
1307
1311
final long topicCreateTimeMs = TimeUnit .NANOSECONDS .toMillis (System .nanoTime ());
1308
1312
NonPersistentTopic nonPersistentTopic ;
1309
1313
try {
@@ -1326,7 +1330,6 @@ private CompletableFuture<Optional<Topic>> createNonPersistentTopic(String topic
1326
1330
}).exceptionally (ex -> {
1327
1331
log .warn ("Replication check failed. Removing topic from topics list {}, {}" , topic , ex .getCause ());
1328
1332
nonPersistentTopic .stopReplProducers ().whenComplete ((v , exception ) -> {
1329
- pulsar .getExecutor ().execute (() -> topics .remove (topic , topicFuture ));
1330
1333
topicFuture .completeExceptionally (ex );
1331
1334
});
1332
1335
return null ;
@@ -1579,14 +1582,6 @@ protected CompletableFuture<Optional<Topic>> loadOrCreatePersistentTopic(final S
1579
1582
final CompletableFuture <Optional <Topic >> topicFuture = FutureUtil .createFutureWithTimeout (
1580
1583
Duration .ofSeconds (pulsar .getConfiguration ().getTopicLoadTimeoutSeconds ()), executor (),
1581
1584
() -> FAILED_TO_LOAD_TOPIC_TIMEOUT_EXCEPTION );
1582
- if (!pulsar .getConfiguration ().isEnablePersistentTopics ()) {
1583
- if (log .isDebugEnabled ()) {
1584
- log .debug ("Broker is unable to load persistent topic {}" , topic );
1585
- }
1586
- topicFuture .completeExceptionally (new NotAllowedException (
1587
- "Broker is unable to load persistent topic" ));
1588
- return topicFuture ;
1589
- }
1590
1585
1591
1586
checkTopicNsOwnership (topic )
1592
1587
.thenRun (() -> {
@@ -1609,6 +1604,7 @@ protected CompletableFuture<Optional<Topic>> loadOrCreatePersistentTopic(final S
1609
1604
}
1610
1605
}
1611
1606
}).exceptionally (ex -> {
1607
+ pulsar .getExecutor ().execute (() -> topics .remove (topic , topicFuture ));
1612
1608
topicFuture .completeExceptionally (ex .getCause ());
1613
1609
return null ;
1614
1610
});
@@ -1779,6 +1775,7 @@ public void openLedgerComplete(ManagedLedger ledger, Object ctx) {
1779
1775
+ " topic" , topic , FutureUtil .getException (topicFuture ));
1780
1776
executor ().submit (() -> {
1781
1777
persistentTopic .close ().whenComplete ((ignore , ex ) -> {
1778
+ topics .remove (topic , topicFuture );
1782
1779
if (ex != null ) {
1783
1780
log .warn ("[{}] Get an error when closing topic." ,
1784
1781
topic , ex );
@@ -1795,6 +1792,7 @@ public void openLedgerComplete(ManagedLedger ledger, Object ctx) {
1795
1792
+ " Removing topic from topics list {}, {}" , topic , ex );
1796
1793
executor ().submit (() -> {
1797
1794
persistentTopic .close ().whenComplete ((ignore , closeEx ) -> {
1795
+ topics .remove (topic , topicFuture );
1798
1796
if (closeEx != null ) {
1799
1797
log .warn ("[{}] Get an error when closing topic." ,
1800
1798
topic , closeEx );
0 commit comments