This is an automated email from the ASF dual-hosted git repository.
sijie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git
The following commit(s) were added to refs/heads/master by this push:
new 95ebc45 ISSUE #209: Introduce Speculative Read/Read LAC policy
95ebc45 is described below
commit 95ebc45fa2d0c4028c31c66b790743f4a7909800
Author: Sijie Guo <[email protected]>
AuthorDate: Thu Jun 29 15:08:17 2017 -0700
ISSUE #209: Introduce Speculative Read/Read LAC policy
Descriptions of the changes in this PR:
- Separate out the logic that determines when speculative reads are
scheduled from the individual requests into a separate
SpeculativeRequestExecutionPolicy
- Implement the default speculative read execution policy based on the
current mechanism of using first and max timeout
- Initialize the policy once in the BookKeeper object and use it for
individual requests by keeping information about the requests in the call chain.
---
Be sure to do all of the following to help us incorporate your contribution
quickly and easily:
- [x] Make sure the PR title is formatted like:
`<Issue #>: Description of pull request`
`e.g. Issue 123: Description ...`
- [x] Make sure tests pass via `mvn clean apache-rat:check install
findbugs:check`.
- [x] Replace `<Issue #>` in the title with the actual Issue number, if
there is one.
---
Author: Sijie Guo <[email protected]>
Author: Robin Dhamankar <[email protected]>
Author: Sijie Guo <[email protected]>
Reviewers: Enrico Olivelli <[email protected]>, Jia Zhai <None>
This closes #200 from sijie/client_changes/speculative_policy, closes #209
---
.../org/apache/bookkeeper/client/BookKeeper.java | 27 ++++--
.../DefaultSpeculativeRequestExecutionPolicy.java | 102 +++++++++++++++++++++
.../apache/bookkeeper/client/PendingReadOp.java | 69 ++++++--------
.../client/SpeculativeRequestExectuor.java | 34 +++++++
.../client/SpeculativeRequestExecutionPolicy.java | 34 +++++++
.../bookkeeper/conf/ClientConfiguration.java | 66 +++++++++++++
.../bookkeeper/util/OrderedSafeExecutor.java | 1 +
7 files changed, 286 insertions(+), 47 deletions(-)
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
index 9a09084..2d509d4 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
@@ -21,6 +21,9 @@
package org.apache.bookkeeper.client;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
@@ -29,6 +32,7 @@ import io.netty.util.HashedWheelTimer;
import java.io.IOException;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -36,9 +40,6 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-
import org.apache.bookkeeper.client.AsyncCallback.CreateCallback;
import org.apache.bookkeeper.client.AsyncCallback.DeleteCallback;
import org.apache.bookkeeper.client.AsyncCallback.OpenCallback;
@@ -70,9 +71,6 @@ import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
-import java.util.concurrent.CompletableFuture;
/**
* BookKeeper client. We assume there is one single writer to a ledger at any
@@ -86,7 +84,6 @@ import java.util.concurrent.CompletableFuture;
*
*
*/
-
public class BookKeeper implements AutoCloseable {
static final Logger LOG = LoggerFactory.getLogger(BookKeeper.class);
@@ -136,6 +133,8 @@ public class BookKeeper implements AutoCloseable {
final ClientConfiguration conf;
final int explicitLacInterval;
+ final Optional<SpeculativeRequestExecutionPolicy>
readSpeculativeRequestPolicy;
+
// Close State
boolean closed = false;
final ReentrantReadWriteLock closeLock = new ReentrantReadWriteLock();
@@ -356,6 +355,16 @@ public class BookKeeper implements AutoCloseable {
this.placementPolicy = initializeEnsemblePlacementPolicy(conf,
dnsResolver, this.requestTimer, this.featureProvider,
this.statsLogger);
+ if (conf.getFirstSpeculativeReadTimeout() > 0) {
+ this.readSpeculativeRequestPolicy =
+ Optional.of(new DefaultSpeculativeRequestExecutionPolicy(
+ conf.getFirstSpeculativeReadTimeout(),
+ conf.getMaxSpeculativeReadTimeout(),
+ conf.getSpeculativeReadTimeoutBackoffMultiplier()));
+ } else {
+ this.readSpeculativeRequestPolicy =
Optional.<SpeculativeRequestExecutionPolicy>absent();
+ }
+
// initialize main worker pool
this.mainWorkerPool = OrderedSafeExecutor.newBuilder()
.name("BookKeeperClientWorker")
@@ -481,6 +490,10 @@ public class BookKeeper implements AutoCloseable {
return statsLogger;
}
+ public Optional<SpeculativeRequestExecutionPolicy>
getReadSpeculativeRequestPolicy() {
+ return readSpeculativeRequestPolicy;
+ }
+
/**
* Get the BookieClient, currently used for doing bookie recovery.
*
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/DefaultSpeculativeRequestExecutionPolicy.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/DefaultSpeculativeRequestExecutionPolicy.java
new file mode 100644
index 0000000..d0a3672
--- /dev/null
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/DefaultSpeculativeRequestExecutionPolicy.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * 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.bookkeeper.client;
+
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultSpeculativeRequestExecutionPolicy implements
SpeculativeRequestExecutionPolicy {
+ private static final Logger LOG =
LoggerFactory.getLogger(PendingReadOp.class);
+ final int firstSpeculativeRequestTimeout;
+ final int maxSpeculativeRequestTimeout;
+ final float backoffMultiplier;
+
+ public DefaultSpeculativeRequestExecutionPolicy(int
firstSpeculativeRequestTimeout, int maxSpeculativeRequestTimeout, float
backoffMultiplier) {
+ this.firstSpeculativeRequestTimeout = firstSpeculativeRequestTimeout;
+ this.maxSpeculativeRequestTimeout = maxSpeculativeRequestTimeout;
+ this.backoffMultiplier = backoffMultiplier;
+
+ if (backoffMultiplier <= 0) {
+ throw new IllegalArgumentException("Invalid value provided for
backoffMultiplier");
+ }
+
+ // Prevent potential over flow
+ if (Math.round((double)maxSpeculativeRequestTimeout *
(double)backoffMultiplier) > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Invalid values for
maxSpeculativeRequestTimeout and backoffMultiplier");
+ }
+ }
+
+ /**
+ * Initialize the speculative request execution policy
+ *
+ * @param scheduler The scheduler service to issue the speculative request
+ * @param requestExecutor The executor is used to issue the actual
speculative requests
+ */
+ @Override
+ public void initiateSpeculativeRequest(final ScheduledExecutorService
scheduler, final SpeculativeRequestExectuor requestExecutor) {
+ scheduleSpeculativeRead(scheduler, requestExecutor,
firstSpeculativeRequestTimeout);
+ }
+
+ private void scheduleSpeculativeRead(final ScheduledExecutorService
scheduler,
+ final SpeculativeRequestExectuor
requestExecutor,
+ final int speculativeRequestTimeout) {
+ try {
+ scheduler.schedule(new Runnable() {
+ @Override
+ public void run() {
+ ListenableFuture<Boolean> issueNextRequest =
requestExecutor.issueSpeculativeRequest();
+ Futures.addCallback(issueNextRequest, new
FutureCallback<Boolean>() {
+ // we want this handler to run immediately after we
push the big red button!
+ public void onSuccess(Boolean issueNextRequest) {
+ if (issueNextRequest) {
+ scheduleSpeculativeRead(scheduler,
requestExecutor, Math.min(maxSpeculativeRequestTimeout,
+
Math.round((float)speculativeRequestTimeout * backoffMultiplier)));
+ } else {
+ if(LOG.isTraceEnabled()) {
+ LOG.trace("Stopped issuing speculative
requests for {}, " +
+ "speculativeReadTimeout = {}",
requestExecutor, speculativeRequestTimeout);
+ }
+ }
+ }
+
+ public void onFailure(Throwable thrown) {
+ LOG.warn("Failed to issue speculative request for
{}, speculativeReadTimeout = {} : ",
+ new Object[] { requestExecutor,
speculativeRequestTimeout, thrown });
+ }
+ });
+ }
+ }, speculativeRequestTimeout, TimeUnit.MILLISECONDS);
+ } catch (RejectedExecutionException re) {
+ if (!scheduler.isShutdown()) {
+ LOG.warn("Failed to schedule speculative request for {},
speculativeReadTimeout = {} : ",
+ new Object[]{requestExecutor,
speculativeRequestTimeout, re});
+ }
+ }
+ }
+}
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java
index 0dd1f67..f2477c1 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java
@@ -31,12 +31,14 @@ import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import com.google.common.util.concurrent.ListenableFuture;
+
import org.apache.bookkeeper.client.AsyncCallback.ReadCallback;
import org.apache.bookkeeper.client.BKException.BKDigestMatchException;
import org.apache.bookkeeper.net.BookieSocketAddress;
@@ -57,7 +59,6 @@ import org.slf4j.LoggerFactory;
class PendingReadOp implements Enumeration<LedgerEntry>, ReadEntryCallback {
private static final Logger LOG =
LoggerFactory.getLogger(PendingReadOp.class);
- final int speculativeReadTimeout;
final private ScheduledExecutorService scheduler;
private ScheduledFuture<?> speculativeTask = null;
Queue<LedgerEntryRequest> seq;
@@ -76,7 +77,7 @@ class PendingReadOp implements Enumeration<LedgerEntry>,
ReadEntryCallback {
boolean parallelRead = false;
final AtomicBoolean complete = new AtomicBoolean(false);
- abstract class LedgerEntryRequest extends LedgerEntry {
+ abstract class LedgerEntryRequest extends LedgerEntry implements
SpeculativeRequestExectuor {
final AtomicBoolean complete = new AtomicBoolean(false);
@@ -226,6 +227,28 @@ class PendingReadOp implements Enumeration<LedgerEntry>,
ReadEntryCallback {
return String.format("L%d-E%d", ledgerId, entryId);
}
+ /**
+ * Issues a speculative request and indicates if more speculative
+ * requests should be issued
+ *
+ * @return whether more speculative requests should be issued
+ */
+ @Override
+ public ListenableFuture<Boolean> issueSpeculativeRequest() {
+ return lh.bk.mainWorkerPool.submitOrdered(lh.getId(), new
Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ if (!isComplete() && null !=
maybeSendSpeculativeRead(heardFromHostsBitSet)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Send speculative read for {}. Hosts
heard are {}, ensemble is {}.",
+ new Object[] { this, heardFromHostsBitSet,
ensemble });
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ }
}
class ParallelReadRequest extends LedgerEntryRequest {
@@ -402,7 +425,6 @@ class PendingReadOp implements Enumeration<LedgerEntry>,
ReadEntryCallback {
numPendingEntries = endEntryId - startEntryId + 1;
maxMissedReadsAllowed = getLedgerMetadata().getWriteQuorumSize()
- getLedgerMetadata().getAckQuorumSize();
- speculativeReadTimeout = lh.bk.getConf().getSpeculativeReadTimeout();
heardFromHosts = new HashSet<>();
heardFromHostsBitSet = new
BitSet(getLedgerMetadata().getEnsembleSize());
@@ -430,42 +452,6 @@ class PendingReadOp implements Enumeration<LedgerEntry>,
ReadEntryCallback {
this.requestTimeNanos = MathUtils.nowInNano();
ArrayList<BookieSocketAddress> ensemble = null;
- if (speculativeReadTimeout > 0 && !parallelRead) {
- Runnable readTask = new Runnable() {
- public void run() {
- int x = 0;
- for (LedgerEntryRequest r : seq) {
- if (!r.isComplete()) {
- if (null ==
r.maybeSendSpeculativeRead(heardFromHostsBitSet)) {
- // Subsequent speculative read will not
materialize anyway
- cancelSpeculativeTask(false);
- } else {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Send speculative read for {}.
Hosts heard are {}.", r, heardFromHostsBitSet);
- }
- ++x;
- }
- }
- }
- if (x > 0) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Send {} speculative reads for ledger {}
({}, {}). Hosts heard are {}.",
- new Object[] { x, lh.getId(),
startEntryId, endEntryId, heardFromHostsBitSet });
- }
- }
- }
- };
- try {
- speculativeTask = scheduler.scheduleWithFixedDelay(readTask,
- speculativeReadTimeout, speculativeReadTimeout,
TimeUnit.MILLISECONDS);
- } catch (RejectedExecutionException re) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Failed to schedule speculative reads for ledger
{} ({}, {}) : ", lh.getId(),
- startEntryId, endEntryId, re);
- }
- }
- }
-
do {
if (i == nextEnsembleChange) {
ensemble = getLedgerMetadata().getEnsemble(i);
@@ -483,6 +469,9 @@ class PendingReadOp implements Enumeration<LedgerEntry>,
ReadEntryCallback {
// read the entries.
for (LedgerEntryRequest entry : seq) {
entry.read();
+ if (!parallelRead &&
lh.bk.getReadSpeculativeRequestPolicy().isPresent()) {
+
lh.bk.getReadSpeculativeRequestPolicy().get().initiateSpeculativeRequest(scheduler,
entry);
+ }
}
}
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/SpeculativeRequestExectuor.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/SpeculativeRequestExectuor.java
new file mode 100644
index 0000000..45e1aeb
--- /dev/null
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/SpeculativeRequestExectuor.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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.bookkeeper.client;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+public interface SpeculativeRequestExectuor {
+
+ /**
+ * Issues a speculative request and indicates if more speculative
+ * requests should be issued
+ *
+ * @return whether more speculative requests should be issued
+ */
+ ListenableFuture<Boolean> issueSpeculativeRequest();
+}
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/SpeculativeRequestExecutionPolicy.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/SpeculativeRequestExecutionPolicy.java
new file mode 100644
index 0000000..4489d41
--- /dev/null
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/SpeculativeRequestExecutionPolicy.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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.bookkeeper.client;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+public interface SpeculativeRequestExecutionPolicy {
+
+ /**
+ * Initialize the speculative request execution policy and initiate
requests
+ *
+ * @param scheduler The scheduler service to issue the speculative request
+ * @param requestExectuor The executor is used to issue the actual
speculative requests
+ */
+ void initiateSpeculativeRequest(ScheduledExecutorService scheduler,
SpeculativeRequestExectuor requestExectuor);
+}
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ClientConfiguration.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ClientConfiguration.java
index 8b02a80..32b21aa 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ClientConfiguration.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ClientConfiguration.java
@@ -62,6 +62,9 @@ public class ClientConfiguration extends
AbstractConfiguration {
// Read Parameters
protected final static String READ_TIMEOUT = "readTimeout";
protected final static String SPECULATIVE_READ_TIMEOUT =
"speculativeReadTimeout";
+ protected final static String FIRST_SPECULATIVE_READ_TIMEOUT =
"firstSpeculativeReadTimeout";
+ protected final static String MAX_SPECULATIVE_READ_TIMEOUT =
"maxSpeculativeReadTimeout";
+ protected final static String SPECULATIVE_READ_TIMEOUT_BACKOFF_MULTIPLIER
= "speculativeReadTimeoutBackoffMultiplier";
protected final static String ENABLE_PARALLEL_RECOVERY_READ =
"enableParallelRecoveryRead";
protected final static String RECOVERY_READ_BATCH_SIZE =
"recoveryReadBatchSize";
// Timeout Setting
@@ -795,6 +798,69 @@ public class ClientConfiguration extends
AbstractConfiguration {
}
/**
+ * Get the first speculative read timeout.
+ *
+ * @return first speculative read timeout.
+ */
+ public int getFirstSpeculativeReadTimeout() {
+ return getInt(FIRST_SPECULATIVE_READ_TIMEOUT,
getSpeculativeReadTimeout());
+ }
+
+ /**
+ * Set the first speculative read timeout.
+ *
+ * @param timeout
+ * first speculative read timeout.
+ * @return client configuration.
+ */
+ public ClientConfiguration setFirstSpeculativeReadTimeout(int timeout) {
+ setProperty(FIRST_SPECULATIVE_READ_TIMEOUT, timeout);
+ return this;
+ }
+
+ /**
+ * Multipler to use when determining time between successive speculative
read requests
+ *
+ * @return speculative read timeout backoff multiplier.
+ */
+ public float getSpeculativeReadTimeoutBackoffMultiplier() {
+ return getFloat(SPECULATIVE_READ_TIMEOUT_BACKOFF_MULTIPLIER, 2.0f);
+ }
+
+ /**
+ * Set the multipler to use when determining time between successive
speculative read requests
+ *
+ * @param speculativeReadTimeoutBackoffMultiplier
+ * multipler to use when determining time between successive
speculative read requests.
+ * @return client configuration.
+ */
+ public ClientConfiguration
setSpeculativeReadTimeoutBackoffMultiplier(float
speculativeReadTimeoutBackoffMultiplier) {
+ setProperty(SPECULATIVE_READ_TIMEOUT_BACKOFF_MULTIPLIER,
speculativeReadTimeoutBackoffMultiplier);
+ return this;
+ }
+
+ /**
+ * Get the max speculative read timeout.
+ *
+ * @return max speculative read timeout.
+ */
+ public int getMaxSpeculativeReadTimeout() {
+ return getInt(MAX_SPECULATIVE_READ_TIMEOUT,
getSpeculativeReadTimeout());
+ }
+
+ /**
+ * Set the max speculative read timeout.
+ *
+ * @param timeout
+ * max speculative read timeout.
+ * @return client configuration.
+ */
+ public ClientConfiguration setMaxSpeculativeReadTimeout(int timeout) {
+ setProperty(MAX_SPECULATIVE_READ_TIMEOUT, timeout);
+ return this;
+ }
+
+ /**
* Whether to enable parallel reading in recovery read.
*
* @return true if enable parallel reading in recovery read. otherwise,
return false.
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/util/OrderedSafeExecutor.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/util/OrderedSafeExecutor.java
index c3554b4..76f0830 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/util/OrderedSafeExecutor.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/util/OrderedSafeExecutor.java
@@ -246,6 +246,7 @@ public class OrderedSafeExecutor {
}
public ListeningScheduledExecutorService chooseThread(Object orderingKey) {
+ // skip hashcode generation in this special case
if (threads.length == 1) {
return threads[0];
}
--
To stop receiving notification emails like this one, please contact
['"[email protected]" <[email protected]>'].