-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[fix][broker] Fix thread unsafe access on the bundle range cache for …
…load managers ### Background Knowledge A concurrent hash map (no matter the `ConcurrentOpenHashMap` in Pulsar or the official `ConcurrentHashMap`) is not a synchronized hash map. For example, given a `ConcurrentHashMap<Integer, Integer>` object `m`, ```java synchronized (m) { m.computeIfAbsent(1, __ -> 100); // [1] m.computeIfAbsent(2, __ -> 200); // [2] } ``` ```java m.computeIfAbsent(1, __ -> 300); // [3] ``` If these two code blocks are called in two threads, `[1]->[3]->[2]` is a possible case. ### Motivation `SimpleLoadManagerImpl` and `ModularLoadManagerImpl` both maintain the bundle range cache: ```java // The 1st key is broker, the 2nd key is namespace private final ConcurrentOpenHashMap<String, ConcurrentOpenHashMap<String, ConcurrentOpenHashSet<String>>> brokerToNamespaceToBundleRange; ``` However, when accessing the `namespace -> bundle` map, it still needs a synchronized code block: https://github.com/apache/pulsar/blob/1c495e190b3c569e9dfd44acef2a697c93a1f771/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/impl/ModularLoadManagerImpl.java#L591-L595 The code above is a composite operation of `clear()` and multiple `computeIfAbsent` operations on the `ConcurrentOpenHashMap<String, ConcurrentOpenHashSet<String>>` object. So the other place that access this map also requires the same lock even if the operation itself is thread safe: https://github.com/apache/pulsar/blob/1c495e190b3c569e9dfd44acef2a697c93a1f771/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/impl/ModularLoadManagerImpl.java#L882-L886 P.S. `SimpleLoadManagerImpl` does not apply the synchronized block. However, when accessing `brokerToNamespaceToBundleRange` in the static methods of `LoadManagerShared`, they are not synchronized. So the access on the `Map<String, Set<String>>` value is not thread safe. ### Modifications Add a `BundleRangeCache` abstraction to provide some methods to support required operations on the bundle range cache.
- Loading branch information
1 parent
1c495e1
commit a86fe70
Showing
6 changed files
with
120 additions
and
178 deletions.
There are no files selected for viewing
70 changes: 70 additions & 0 deletions
70
pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/impl/BundleRangeCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
package org.apache.pulsar.broker.loadbalance.impl; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.function.BiFunction; | ||
import java.util.stream.Stream; | ||
|
||
public class BundleRangeCache { | ||
|
||
// Map from brokers to namespaces to the bundle ranges in that namespace assigned to that broker. | ||
// Used to distribute bundles within a namespace evenly across brokers. | ||
private final Map<String, Map<String, Set<String>>> data = new ConcurrentHashMap<>(); | ||
|
||
public void reloadFromBundles(String broker, Stream<String> bundles) { | ||
final var namespaceToBundleRange = new ConcurrentHashMap<String, Set<String>>(); | ||
bundles.forEach(bundleName -> { | ||
final String namespace = LoadManagerShared.getNamespaceNameFromBundleName(bundleName); | ||
final String bundleRange = LoadManagerShared.getBundleRangeFromBundleName(bundleName); | ||
initConcurrentHashSet(namespaceToBundleRange, namespace).add(bundleRange); | ||
}); | ||
data.put(broker, namespaceToBundleRange); | ||
} | ||
|
||
public void addBundleRange(String broker, String namespace, String bundleRange) { | ||
getBundleRangeSet(broker, namespace).add(bundleRange); | ||
} | ||
|
||
public int getBundles(String broker, String namespace) { | ||
return getBundleRangeSet(broker, namespace).size(); | ||
} | ||
|
||
public List<CompletableFuture<Void>> runTasks( | ||
BiFunction<String/* broker */, String/* namespace */, CompletableFuture<Void>> task) { | ||
return data.entrySet().stream().flatMap(e -> { | ||
final var broker = e.getKey(); | ||
return e.getValue().entrySet().stream().filter(__ -> !__.getValue().isEmpty()).map(Map.Entry::getKey) | ||
.map(namespace -> task.apply(broker, namespace)); | ||
}).toList(); | ||
} | ||
|
||
private Set<String> getBundleRangeSet(String broker, String namespace) { | ||
return initConcurrentHashSet(data.computeIfAbsent(broker, __ -> new ConcurrentHashMap<>()), namespace); | ||
} | ||
|
||
private static Set<String> initConcurrentHashSet(Map<String, Set<String>> namespaceToBundleRangeSet, | ||
String namespace) { | ||
return namespaceToBundleRangeSet.computeIfAbsent(namespace, __ -> ConcurrentHashMap.newKeySet()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.