From c108563b724acaff4739d66d6b85e1b9d338a864 Mon Sep 17 00:00:00 2001 From: Block Date: Wed, 6 Mar 2019 12:58:31 +0800 Subject: [PATCH 01/31] (fix) smaller initial capacity with pendingStatuses (#9) --- .../java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java index 7187d3263..c201a51ce 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java @@ -275,7 +275,7 @@ public void onApplied(long appliedIndex) { // Find all statuses that log index less than or equal to appliedIndex. final Map> statuses = this.pendingNotifyStatus.headMap(appliedIndex, true); if (statuses != null) { - pendingStatuses = new ArrayList<>(statuses.size() * 10); + pendingStatuses = new ArrayList<>(statuses.size() << 1); final Iterator>> it = statuses.entrySet().iterator(); while (it.hasNext()) { From 616e619fb7a457d3bb777e60a6a92bf056142ed4 Mon Sep 17 00:00:00 2001 From: Block Date: Wed, 6 Mar 2019 23:24:20 +0800 Subject: [PATCH 02/31] (fix) update bolt to v1.5.3 https://github.com/alipay/sofa-jraft/issues/10 (#11) --- jraft-core/pom.xml | 9 --------- pom.xml | 16 ++-------------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/jraft-core/pom.xml b/jraft-core/pom.xml index 0e8036382..1dc126fd2 100644 --- a/jraft-core/pom.xml +++ b/jraft-core/pom.xml @@ -100,15 +100,6 @@ com.alipay.sofa bolt - - io.netty - netty-all - - - com.alipay.sofa.common - sofa-common-tools - - com.alipay.sofa hessian diff --git a/pom.xml b/pom.xml index 5d26f9352..0c9f371db 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ 3.1.7 - 1.5.2 + 1.5.3 2.4 2.6 3.3.7 @@ -70,14 +70,12 @@ 2.2 4.0.2 1.9.5 - 4.1.13.Final 1.6.0 UTF-8 3.5.1 1.6.0 5.14.2 1.7.21 - 1.0.12 jacoco reuseReports target/jacoco-it.exec @@ -126,16 +124,6 @@ bolt ${bolt.version} - - io.netty - netty-all - ${netty.version} - - - com.alipay.sofa.common - sofa-common-tools - ${sofa.common.version} - com.alipay.sofa hessian @@ -147,7 +135,7 @@ slf4j-api ${slf4j.version} - + org.apache.logging.log4j log4j-api From 5462f91a3891760e723c9374325958f9fa0301f2 Mon Sep 17 00:00:00 2001 From: Block Date: Thu, 7 Mar 2019 21:03:50 +0800 Subject: [PATCH 03/31] Fix/fix code format (#12) * (fix) missing format * (fix) shutdownContinuations is always not null * (fix) correct SuppressWarnings * (fix) correct SuppressWarnings * (fix) improve: try with resource * (fix) never return null * (fix) read requestBuilder#fields should be in lock * (fix) unnecessary SuppressWarnings * (fix) yd replace with direct object class access * (fix) remove necessary null check * (fix) remove necessary null check * (fix) SuppressWarnings("SameParameterValue") * (fix) SuppressWarnings("uncheck") * (fix) SuppressWarnings("SameParameterValue") * (fix) SuppressWarnings("SameParameterValue") * (fix) SuppressWarnings("SameParameterValue") * (fix) remove necessary null check * (fix) toArray() call style * (fix) try with resources * (fix) SuppressWarnings("SameParameterValue") * (fix) unnecessary String.valueOf() * (fix) unnecessary String.valueOf() * (fix) SuppressWarnings("SameParameterValue") * (fix) necessary generic arg * (fix) Long -> long * (fix) clear code with example * (fix) format --- .../com/alipay/sofa/jraft/core/NodeImpl.java | 2 +- .../sofa/jraft/core/StateMachineAdapter.java | 4 +- .../jraft/storage/impl/RocksDBLogStorage.java | 7 +-- .../snapshot/local/LocalSnapshotCopier.java | 5 +- .../storage/snapshot/remote/BoltSession.java | 58 +++++++++---------- .../jraft/util/timer/HashedWheelTimer.java | 1 - .../google/protobuf/ZeroByteStringHelper.java | 2 +- .../benchmark/server/BenchmarkServer.java | 2 +- .../counter/IncrementAndAddClosure.java | 2 +- .../example/rheakv/DeleteRangeExample.java | 5 -- .../sofa/jraft/example/rheakv/Server1.java | 2 +- .../sofa/jraft/example/rheakv/Server2.java | 2 +- .../sofa/jraft/example/rheakv/Server3.java | 2 +- .../jraft/rhea/DefaultRegionKVService.java | 4 +- .../alipay/sofa/jraft/rhea/RegionEngine.java | 2 +- .../alipay/sofa/jraft/rhea/StoreEngine.java | 2 +- .../sofa/jraft/rhea/StoreEngineHelper.java | 2 +- .../jraft/rhea/client/RegionRouteTable.java | 10 ++-- .../pd/AbstractPlacementDriverClient.java | 2 +- .../jraft/rhea/errors/ApiExceptionHelper.java | 5 +- .../sofa/jraft/rhea/errors/ErrorsHelper.java | 12 ++-- .../jraft/rhea/serialization/Serializers.java | 1 + .../impl/protostuff/io/NioBufOutput.java | 1 + .../sofa/jraft/rhea/storage/KVOperation.java | 1 - .../jraft/rhea/storage/MemoryRawKVStore.java | 1 + .../jraft/rhea/storage/RocksRawKVStore.java | 1 + .../sofa/jraft/rhea/util/RecycleUtil.java | 2 +- .../sofa/jraft/rhea/util/StackTraceUtil.java | 18 +++--- .../alipay/sofa/jraft/rhea/util/Strings.java | 2 +- .../sofa/jraft/rhea/util/UniqueIdUtil.java | 3 +- .../collection/NonBlockingHashMapLong.java | 1 + .../sofa/jraft/rhea/ClusterStatsManager.java | 4 +- .../sofa/jraft/rhea/DefaultMetadataStore.java | 2 +- 33 files changed, 80 insertions(+), 90 deletions(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java index efcb3507f..a0253fdab 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java @@ -1848,7 +1848,7 @@ private void afterShutdown() { List savedDones = null; writeLock.lock(); try { - if (this.shutdownContinuations != null) { + if (!this.shutdownContinuations.isEmpty()) { savedDones = new ArrayList<>(this.shutdownContinuations); } if (logStorage != null) { diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/StateMachineAdapter.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/StateMachineAdapter.java index 9be5a07b0..f4e62d933 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/StateMachineAdapter.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/StateMachineAdapter.java @@ -89,7 +89,7 @@ public void onStartFollowing(LeaderChangeContext ctx) { LOG.info("onStartFollowing: {}", ctx); } - @SuppressWarnings("all") + @SuppressWarnings("SameParameterValue") private void runClosure(Closure done, String methodName) { done.run(new Status(-1, "%s doesn't implement %s", getClassName(), methodName)); } @@ -98,7 +98,7 @@ private String getClassName() { return this.getClass().getName(); } - @SuppressWarnings("all") + @SuppressWarnings("SameParameterValue") private void error(String methodName) { this.error(methodName, ""); } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java index 542d38f24..1a061246b 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java @@ -350,18 +350,13 @@ public long getFirstLogIndex() { @Override public long getLastLogIndex() { readLock.lock(); - RocksIterator it = null; - try { - it = this.db.newIterator(this.defaultHandle, this.totalOrderReadOptions); + try (final RocksIterator it = this.db.newIterator(this.defaultHandle, this.totalOrderReadOptions)) { it.seekToLast(); if (it.isValid()) { return Bits.getLong(it.key(), 0); } return 0L; } finally { - if (it != null) { - it.close(); - } readLock.unlock(); } } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java index a6d67f084..30e528478 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java @@ -271,10 +271,7 @@ boolean filterBeforeCopy(LocalSnapshotWriter writer, SnapshotReader lastSnapshot final String destPath = writer.getPath() + File.separator + fileName; FileUtils.deleteQuietly(new File(destPath)); try { - if (Files.createLink(Paths.get(destPath), Paths.get(sourcePath)) == null) { - LOG.error("Fail to link {} to {}", sourcePath, destPath); - continue; - } + Files.createLink(Paths.get(destPath), Paths.get(sourcePath)); } catch (final IOException e) { LOG.error("Fail to link {} to {}", sourcePath, destPath, e); continue; diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSession.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSession.java index e3de07ac9..6940386ca 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSession.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSession.java @@ -66,7 +66,7 @@ public class BoltSession implements Session { private boolean finished; private ByteBufferCollector destBuf; private CopyOptions copyOptions; - private GetFileRequest.Builder requestBuilder; + private final GetFileRequest.Builder requestBuilder; private final CountDownLatch finishLatch; private OutputStream outputStream; private final Endpoint endpoint; @@ -90,7 +90,6 @@ private class GetFileResponseClosure extends RpcResponseClosureAdapter getRpcCall() { - return this.rpcCall; + return rpcCall; } @OnlyForTest ScheduledFuture getTimer() { - return this.timer; + return timer; } @Override public void close() throws IOException { - lock.lock(); + this.lock.lock(); try { if (!this.finished) { Utils.closeQuietly(this.outputStream); } } finally { - lock.unlock(); + this.lock.unlock(); } } @@ -138,7 +137,6 @@ public BoltSession(RaftClientService rpcService, TimerManager timerManager, Snap this.endpoint = ep; this.st = Status.OK(); this.finishLatch = new CountDownLatch(1); - } public void setDestBuf(ByteBufferCollector bufRef) { @@ -155,7 +153,7 @@ public void setOutputStream(OutputStream out) { @Override public void cancel() { - lock.lock(); + this.lock.lock(); try { if (this.finished) { return; @@ -166,15 +164,14 @@ public void cancel() { if (this.rpcCall != null) { this.rpcCall.cancel(true); } - if (st.isOk()) { - st.setError(RaftError.ECANCELED, RaftError.ECANCELED.name()); + if (this.st.isOk()) { + this.st.setError(RaftError.ECANCELED, RaftError.ECANCELED.name()); } this.onFinished(); } finally { - lock.unlock(); + this.lock.unlock(); } - } @Override @@ -189,7 +186,7 @@ public Status status() { private void onFinished() { if (!this.finished) { - if (outputStream != null) { + if (this.outputStream != null) { Utils.closeQuietly(this.outputStream); this.outputStream = null; } @@ -210,17 +207,17 @@ private void onTimer() { } void onRpcReturned(Status status, GetFileResponse response) { - lock.lock(); + this.lock.lock(); try { if (this.finished) { return; } if (!status.isOk()) { // Reset count to make next rpc retry the previous one - requestBuilder.setCount(0); + this.requestBuilder.setCount(0); if (status.getCode() == RaftError.ECANCELED.getNumber()) { - if (st.isOk()) { - st.setError(status.getCode(), status.getErrorMsg()); + if (this.st.isOk()) { + this.st.setError(status.getCode(), status.getErrorMsg()); this.onFinished(); return; } @@ -229,8 +226,8 @@ void onRpcReturned(Status status, GetFileResponse response) { // Throttled reading failure does not increase _retry_times if (status.getCode() != RaftError.EAGAIN.getNumber() && ++this.retryTimes >= this.copyOptions.getMaxRetry()) { - if (st.isOk()) { - st.setError(status.getCode(), status.getErrorMsg()); + if (this.st.isOk()) { + this.st.setError(status.getCode(), status.getErrorMsg()); this.onFinished(); return; } @@ -245,12 +242,12 @@ void onRpcReturned(Status status, GetFileResponse response) { if (response.hasReadSize() && response.getReadSize() != 0) { this.requestBuilder.setCount(response.getReadSize()); } - if (outputStream != null) { + if (this.outputStream != null) { try { - response.getData().writeTo(outputStream); + response.getData().writeTo(this.outputStream); } catch (final IOException e) { LOG.error("Fail to write into file {}", this.destPath); - st.setError(RaftError.EIO, RaftError.EIO.name()); + this.st.setError(RaftError.EIO, RaftError.EIO.name()); this.onFinished(); return; } @@ -263,21 +260,22 @@ void onRpcReturned(Status status, GetFileResponse response) { return; } } finally { - lock.unlock(); + this.lock.unlock(); } - this.sendNextRpc(); + sendNextRpc(); } /** * Send next RPC request to get a piece of file data. */ void sendNextRpc() { - this.timer = null; - final long offset = requestBuilder.getOffset() + requestBuilder.getCount(); - final long maxCount = this.destBuf == null ? raftOptions.getMaxByteCountPerRpc() : Integer.MAX_VALUE; - this.requestBuilder = requestBuilder.setOffset(offset).setCount(maxCount).setReadPartly(true); this.lock.lock(); try { + this.timer = null; + final long offset = this.requestBuilder.getOffset() + this.requestBuilder.getCount(); + final long maxCount = this.destBuf == null ? this.raftOptions.getMaxByteCountPerRpc() : Integer.MAX_VALUE; + this.requestBuilder.setOffset(offset).setCount(maxCount).setReadPartly(true); + if (this.finished) { return; } @@ -298,7 +296,7 @@ void sendNextRpc() { this.rpcCall = this.rpcService.getFile(endpoint, this.requestBuilder.build(), this.copyOptions.getTimeoutMs(), done); } finally { - lock.unlock(); + this.lock.unlock(); } } } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java index 6d156d29f..946633247 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java @@ -572,7 +572,6 @@ void remove() { } } - @SuppressWarnings("all") public boolean compareAndSetState(int expected, int state) { return STATE_UPDATER.compareAndSet(this, expected, state); } diff --git a/jraft-core/src/main/java/com/google/protobuf/ZeroByteStringHelper.java b/jraft-core/src/main/java/com/google/protobuf/ZeroByteStringHelper.java index 7f998e230..c49eedb34 100644 --- a/jraft-core/src/main/java/com/google/protobuf/ZeroByteStringHelper.java +++ b/jraft-core/src/main/java/com/google/protobuf/ZeroByteStringHelper.java @@ -48,7 +48,7 @@ public class ZeroByteStringHelper { // Try to get defineAnonymousClass method handle. try { final Class clazz = ByteString.class; - final Method method = clazz.getDeclaredMethod("wrap", (new byte[0]).getClass()); + final Method method = clazz.getDeclaredMethod("wrap", byte[].class); if (method != null) { WRAP_BYTES_HANDLE = MethodHandles.lookup().unreflect(method); } diff --git a/jraft-example/src/main/java/com/alipay/sofa/jraft/benchmark/server/BenchmarkServer.java b/jraft-example/src/main/java/com/alipay/sofa/jraft/benchmark/server/BenchmarkServer.java index 174fe32c3..f9bb007ff 100644 --- a/jraft-example/src/main/java/com/alipay/sofa/jraft/benchmark/server/BenchmarkServer.java +++ b/jraft-example/src/main/java/com/alipay/sofa/jraft/benchmark/server/BenchmarkServer.java @@ -52,7 +52,7 @@ public static void main(final String[] args) { .build() // .start(30, TimeUnit.SECONDS); - Runtime.getRuntime().addShutdownHook(new Thread(() -> node.stop())); + Runtime.getRuntime().addShutdownHook(new Thread(node::stop)); LOG.info("BenchmarkServer start OK, options: {}", opts); } } diff --git a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/counter/IncrementAndAddClosure.java b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/counter/IncrementAndAddClosure.java index ea7ea69bc..b01499996 100644 --- a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/counter/IncrementAndAddClosure.java +++ b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/counter/IncrementAndAddClosure.java @@ -30,7 +30,7 @@ */ public class IncrementAndAddClosure implements Closure { - @SuppressWarnings("all") + @SuppressWarnings({ "FieldCanBeLocal", "unused" }) private CounterServer counterServer; private IncrementAndGetRequest request; private ValueResponse response; diff --git a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/DeleteRangeExample.java b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/DeleteRangeExample.java index 59a0f57ba..9d56fe062 100644 --- a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/DeleteRangeExample.java +++ b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/DeleteRangeExample.java @@ -18,9 +18,6 @@ import java.util.concurrent.CompletableFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.alipay.sofa.jraft.rhea.client.FutureHelper; import com.alipay.sofa.jraft.rhea.client.RheaKVStore; @@ -32,8 +29,6 @@ */ public class DeleteRangeExample { - private static final Logger LOG = LoggerFactory.getLogger(DeleteRangeExample.class); - public static void main(final String[] args) throws Exception { final Client client = new Client(); client.init(); diff --git a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server1.java b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server1.java index 1a538af03..d007625b9 100644 --- a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server1.java +++ b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server1.java @@ -51,7 +51,7 @@ public static void main(final String[] args) throws Exception { System.out.println(opts); final Node node = new Node(opts); node.start(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> node.stop())); + Runtime.getRuntime().addShutdownHook(new Thread(node::stop)); System.out.println("server1 start OK"); } } diff --git a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server2.java b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server2.java index 6709796a2..895a36268 100644 --- a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server2.java +++ b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server2.java @@ -51,7 +51,7 @@ public static void main(final String[] args) throws Exception { System.out.println(opts); final Node node = new Node(opts); node.start(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> node.stop())); + Runtime.getRuntime().addShutdownHook(new Thread(node::stop)); System.out.println("server2 start OK"); } } diff --git a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server3.java b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server3.java index b416a5c7b..62adbbf71 100644 --- a/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server3.java +++ b/jraft-example/src/main/java/com/alipay/sofa/jraft/example/rheakv/Server3.java @@ -51,7 +51,7 @@ public static void main(final String[] args) throws Exception { System.out.println(opts); final Node node = new Node(opts); node.start(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> node.stop())); + Runtime.getRuntime().addShutdownHook(new Thread(node::stop)); System.out.println("server3 start OK"); } } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/DefaultRegionKVService.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/DefaultRegionKVService.java index cce03b78b..d4d1e76cb 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/DefaultRegionKVService.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/DefaultRegionKVService.java @@ -587,7 +587,7 @@ private static List requireNonEmpty(final List target, final String me return target; } - @SuppressWarnings("all") + @SuppressWarnings("SameParameterValue") private static int requirePositive(final int value, final String message) { if (value <= 0) { throw new InvalidParameterException(message); @@ -595,7 +595,7 @@ private static int requirePositive(final int value, final String message) { return value; } - @SuppressWarnings("all") + @SuppressWarnings("SameParameterValue") private static long requirePositive(final long value, final String message) { if (value <= 0) { throw new InvalidParameterException(message); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/RegionEngine.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/RegionEngine.java index 8dbc9152b..ef4458ebc 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/RegionEngine.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/RegionEngine.java @@ -138,7 +138,7 @@ public synchronized boolean init(final RegionEngineOptions opts) { final ScheduledExecutorService scheduler = this.storeEngine.getMetricsScheduler(); // start raft node metrics reporter this.regionMetricsReporter = Slf4jReporter.forRegistry(metricRegistry) // - .prefixedWith("region_" + String.valueOf(this.region.getId())) // + .prefixedWith("region_" + this.region.getId()) // .withLoggingLevel(Slf4jReporter.LoggingLevel.INFO) // .outputTo(LOG) // .scheduleOn(scheduler) // diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/StoreEngine.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/StoreEngine.java index b0fa896c6..56e5fb1ec 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/StoreEngine.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/StoreEngine.java @@ -557,7 +557,7 @@ private void startMetricReporters(final long metricsReportPeriod) { } // start kv store metrics reporter this.kvMetricsReporter = Slf4jReporter.forRegistry(KVMetrics.metricRegistry()) // - .prefixedWith("store_" + String.valueOf(this.storeId)) // + .prefixedWith("store_" + this.storeId) // .withLoggingLevel(Slf4jReporter.LoggingLevel.INFO) // .outputTo(LOG) // .scheduleOn(this.metricsScheduler) // diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/StoreEngineHelper.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/StoreEngineHelper.java index 6d3bd0243..5dd4eff2c 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/StoreEngineHelper.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/StoreEngineHelper.java @@ -115,7 +115,7 @@ private static ExecutorService newPool(final int coreThreads, final int maxThrea return newPool(coreThreads, maxThreads, name, defaultHandler); } - @SuppressWarnings("all") + @SuppressWarnings("SameParameterValue") private static ExecutorService newPool(final int coreThreads, final int maxThreads, final String name, final BlockingQueue workQueue) { final RejectedExecutionHandler defaultHandler = new CallerRunsPolicyWithReport(name, name); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/RegionRouteTable.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/RegionRouteTable.java index 258022b35..a82eeefd4 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/RegionRouteTable.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/RegionRouteTable.java @@ -87,13 +87,13 @@ */ public class RegionRouteTable { - private static final Logger LOG = LoggerFactory.getLogger(RegionRouteTable.class); + private static final Logger LOG = LoggerFactory.getLogger(RegionRouteTable.class); - private static final Comparator keyBytesComparator = BytesUtil.getDefaultByteArrayComparator(); + private static final Comparator keyBytesComparator = BytesUtil.getDefaultByteArrayComparator(); - private final StampedLock stampedLock = new StampedLock(); - private final NavigableMap rangeTable = new TreeMap<>(keyBytesComparator); - private final Map regionTable = Maps.newHashMap(); + private final StampedLock stampedLock = new StampedLock(); + private final NavigableMap rangeTable = new TreeMap<>(keyBytesComparator); + private final Map regionTable = Maps.newHashMap(); public Region getRegionById(final long regionId) { final StampedLock stampedLock = this.stampedLock; diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/AbstractPlacementDriverClient.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/AbstractPlacementDriverClient.java index 04137ae77..feaac663b 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/AbstractPlacementDriverClient.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/AbstractPlacementDriverClient.java @@ -323,7 +323,7 @@ public Endpoint getLuckyPeer(final long regionId, final boolean forceRefresh, fi for (int i = 0; i < size; i++) { final PeerId candidate = balancer.select(peerList); final Endpoint luckyOne = candidate.getEndpoint(); - if (unExpect == null || !luckyOne.equals(unExpect)) { + if (!luckyOne.equals(unExpect)) { return luckyOne; } } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/errors/ApiExceptionHelper.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/errors/ApiExceptionHelper.java index 8a63e342c..10413ca33 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/errors/ApiExceptionHelper.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/errors/ApiExceptionHelper.java @@ -23,8 +23,9 @@ public final class ApiExceptionHelper { // require refresh region route table public static boolean isInvalidEpoch(final Throwable cause) { - return cause != null - && (cause instanceof InvalidRegionMembershipException || cause instanceof InvalidRegionVersionException || cause instanceof InvalidRegionEpochException); + return cause instanceof InvalidRegionMembershipException // + || cause instanceof InvalidRegionVersionException // + || cause instanceof InvalidRegionEpochException; } private ApiExceptionHelper() { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/errors/ErrorsHelper.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/errors/ErrorsHelper.java index 6b4578119..88e1edb0b 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/errors/ErrorsHelper.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/errors/ErrorsHelper.java @@ -23,15 +23,17 @@ public final class ErrorsHelper { // require refresh leader or peer public static boolean isInvalidPeer(final Errors error) { - return error != null - && (error == Errors.CALL_SELF_ENDPOINT_ERROR || error == Errors.NOT_LEADER - || error == Errors.NO_REGION_FOUND || error == Errors.LEADER_NOT_AVAILABLE); + return error == Errors.CALL_SELF_ENDPOINT_ERROR // + || error == Errors.NOT_LEADER // + || error == Errors.NO_REGION_FOUND // + || error == Errors.LEADER_NOT_AVAILABLE; } // require refresh region route table public static boolean isInvalidEpoch(final Errors error) { - return error != null - && (error == Errors.INVALID_REGION_MEMBERSHIP || error == Errors.INVALID_REGION_VERSION || error == Errors.INVALID_REGION_EPOCH); + return error == Errors.INVALID_REGION_MEMBERSHIP // + || error == Errors.INVALID_REGION_VERSION // + || error == Errors.INVALID_REGION_EPOCH; } private ErrorsHelper() { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/serialization/Serializers.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/serialization/Serializers.java index bd412a29c..9ece5b3b4 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/serialization/Serializers.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/serialization/Serializers.java @@ -41,6 +41,7 @@ public static Serializer getDefault() { return serializers[PROTO_STUFF]; } + @SuppressWarnings("SameParameterValue") private static void addSerializer(final int idx, final Serializer serializer) { if (serializers.length <= idx) { final Serializer[] newSerializers = new Serializer[idx + 5]; diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/serialization/impl/protostuff/io/NioBufOutput.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/serialization/impl/protostuff/io/NioBufOutput.java index 2337af528..d13ac5e6b 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/serialization/impl/protostuff/io/NioBufOutput.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/serialization/impl/protostuff/io/NioBufOutput.java @@ -206,6 +206,7 @@ public void writeString(int fieldNumber, CharSequence value, boolean repeated) t @Override public void writeBytes(int fieldNumber, ByteString value, boolean repeated) throws IOException { + assert byteStringBytesGetter != null; writeByteArray(fieldNumber, byteStringBytesGetter.get(value), repeated); } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/KVOperation.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/KVOperation.java index 1be4858dd..50098fb2d 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/KVOperation.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/KVOperation.java @@ -285,7 +285,6 @@ public void setAcquirerPair(Pair acquirerPair this.attach = acquirerPair; } - @SuppressWarnings("unchecked") public DistributedLock.Acquirer getAcquirer() { return DistributedLock.Acquirer.class.cast(this.attach); } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java index 255fb2be3..f4f0bcffd 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java @@ -539,6 +539,7 @@ public void releaseLockWith(final byte[] key, final DistributedLock.Acquirer acq } } + @SuppressWarnings("SameParameterValue") private long getNextFencingToken(final byte[] fencingKey) { final Timer.Context timeCtx = getTimeContext("FENCING_TOKEN"); try { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java index 812cf7ba7..b5b7ac8bd 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java @@ -852,6 +852,7 @@ public void releaseLockWith(final byte[] key, final DistributedLock.Acquirer acq } } + @SuppressWarnings("SameParameterValue") private long getNextFencingToken(final byte[] fencingKey) throws RocksDBException { final Timer.Context timeCtx = getTimeContext("FENCING_TOKEN"); final Lock readLock = this.readWriteLock.readLock(); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/RecycleUtil.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/RecycleUtil.java index 2cdccec17..dd25c30e1 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/RecycleUtil.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/RecycleUtil.java @@ -27,7 +27,7 @@ public final class RecycleUtil { * Recycle designated instance. */ public static boolean recycle(final Object obj) { - return obj != null && obj instanceof Recyclable && ((Recyclable) obj).recycle(); + return obj instanceof Recyclable && ((Recyclable) obj).recycle(); } private RecycleUtil() { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/StackTraceUtil.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/StackTraceUtil.java index 2f5af3a9b..3038c346c 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/StackTraceUtil.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/StackTraceUtil.java @@ -25,24 +25,22 @@ */ public final class StackTraceUtil { + private static final String NULL_STRING = "null"; + public static String stackTrace(final Throwable t) { if (t == null) { - return "null"; + return NULL_STRING; } - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - final PrintStream ps = new PrintStream(out); + try (final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final PrintStream ps = new PrintStream(out)) { t.printStackTrace(ps); ps.flush(); return new String(out.toByteArray()); - } finally { - try { - out.close(); - } catch (final IOException ignored) { - // ignored - } + } catch (final IOException e) { + // ignored } + return NULL_STRING; } private StackTraceUtil() { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/Strings.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/Strings.java index 346997f6f..300a2751e 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/Strings.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/Strings.java @@ -142,7 +142,7 @@ public static String[] split(final String str, final char separator, final boole if (match || preserveAllTokens) { list.add(str.substring(start, i)); } - return list.toArray(new String[list.size()]); + return list.toArray(new String[0]); } private static final String[] EMPTY_STRING_ARRAY = new String[0]; diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/UniqueIdUtil.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/UniqueIdUtil.java index 1843228bd..d1a5ef8b8 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/UniqueIdUtil.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/UniqueIdUtil.java @@ -71,7 +71,7 @@ private static String getHexProcessId(int pid) { } if (pid > 65535) { String strPid = Integer.toString(pid); - strPid = strPid.substring(strPid.length() - 4, strPid.length()); + strPid = strPid.substring(strPid.length() - 4); pid = Integer.parseInt(strPid); } final StringBuilder buf = new StringBuilder(Integer.toHexString(pid)); @@ -133,6 +133,7 @@ private static String getIp16(final String ip) { return buf.toString(); } + @SuppressWarnings("SameParameterValue") private static String getId(final String ip16, final long timestamp, final long nextId) { return StringBuilderHelper.get() // .append(ip16) // diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/concurrent/collection/NonBlockingHashMapLong.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/concurrent/collection/NonBlockingHashMapLong.java index 822fdf7c7..3ff400917 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/concurrent/collection/NonBlockingHashMapLong.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/concurrent/collection/NonBlockingHashMapLong.java @@ -617,6 +617,7 @@ boolean CAS_newchm(CHM newchm) { // --- key,val ------------------------------------------------------------- // Access K,V for a given idx + @SuppressWarnings("SameParameterValue") private boolean CAS_key(int idx, long old, long key) { return unsafe.compareAndSwapLong(_keys, rawIndex(_keys, idx), old, key); } diff --git a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/ClusterStatsManager.java b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/ClusterStatsManager.java index f9753bbd9..9a8a03a7f 100644 --- a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/ClusterStatsManager.java +++ b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/ClusterStatsManager.java @@ -95,7 +95,7 @@ public void addOrUpdateLeader(final long storeId, final long regionId) { public Pair, Integer /* leaderCount */> findModelWorkerStores(final int above) { final Set>> values = this.leaderTable.entrySet(); if (values.isEmpty()) { - return Pair.of(Collections.emptySet(), 0); + return Pair.of(Collections.emptySet(), 0); } final Map.Entry> modelWorker = Collections.max(values, (o1, o2) -> { final int o1Val = o1.getValue() == null ? 0 : o1.getValue().size(); @@ -104,7 +104,7 @@ public void addOrUpdateLeader(final long storeId, final long regionId) { }); final int maxLeaderCount = modelWorker.getValue().size(); if (maxLeaderCount <= above) { - return Pair.of(Collections.emptySet(), maxLeaderCount); + return Pair.of(Collections.emptySet(), maxLeaderCount); } final Set modelWorkerStoreIds = new HashSet<>(); for (final Map.Entry> entry : values) { diff --git a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/DefaultMetadataStore.java b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/DefaultMetadataStore.java index 321afd1f1..f6465ba68 100644 --- a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/DefaultMetadataStore.java +++ b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/DefaultMetadataStore.java @@ -107,7 +107,7 @@ public Sequence getNextSequence() { storeSequence = newStoreSequence; } } - final Long newStoreId = storeSequence.next(); + final long newStoreId = storeSequence.next(); final byte[] newBytesVal = new byte[8]; Bits.putLong(newBytesVal, 0, newStoreId); final byte[] oldBytesVal = this.rheaKVStore.bPutIfAbsent(storeIdKey, newBytesVal); From f3aa4e73e912ea7696b09d55997077548a0378e4 Mon Sep 17 00:00:00 2001 From: jimin Date: Thu, 7 Mar 2019 22:00:40 +0800 Subject: [PATCH 04/31] fix #13 Change the jdk version in travis (#14) --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8854abdf9..82a70947c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,12 @@ language: java sudo: false jdk: - - oraclejdk8 +- openjdk11 +- openjdk8 + +cache: + directories: + - $HOME/.m2 install: - mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V From b34565d630bc547879626ca9bdaff771010d5c58 Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Fri, 8 Mar 2019 00:30:03 +0800 Subject: [PATCH 05/31] (fix) Should use clock time in replicator block method (#16) --- .../src/main/java/com/alipay/sofa/jraft/core/Replicator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java index 8a30173f5..0f8081ae1 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java @@ -1071,7 +1071,7 @@ static void onRpcReturned(ThreadId id, RequestType reqType, Status status, Messa r.resetInflights(); r.state = State.Probe; continueSendEntries = false; - r.block(Utils.monotonicMs(), RaftError.EREQUEST.getNumber()); + r.block(Utils.nowMs(), RaftError.EREQUEST.getNumber()); return; } try { From 5a90ee5b33f0796f80b2e6f03c5a3f512fa6a44d Mon Sep 17 00:00:00 2001 From: Block Date: Fri, 8 Mar 2019 14:56:56 +0800 Subject: [PATCH 06/31] fix/format (#18) * (fix) comma * (fix) code format * (fix) formatter source version to 1.7 --- .middleware-common/AlipayFormatter120.xml | 6 +- README.md | 2 +- .../com/alipay/sofa/jraft/JRaftUtils.java | 4 +- .../com/alipay/sofa/jraft/RouteTable.java | 9 +- .../sofa/jraft/conf/ConfigurationManager.java | 4 +- .../com/alipay/sofa/jraft/core/BallotBox.java | 6 +- .../sofa/jraft/core/CliServiceImpl.java | 6 +- .../alipay/sofa/jraft/core/FSMCallerImpl.java | 25 +-- .../com/alipay/sofa/jraft/core/NodeImpl.java | 102 ++++++------ .../sofa/jraft/core/ReadOnlyServiceImpl.java | 23 +-- .../alipay/sofa/jraft/core/Replicator.java | 141 ++++++++-------- .../sofa/jraft/core/ReplicatorGroupImpl.java | 19 ++- .../sofa/jraft/rpc/ProtobufMsgFactory.java | 6 +- .../rpc/impl/AbstractBoltClientService.java | 11 +- .../core/AppendEntriesRequestProcessor.java | 24 ++- .../rpc/impl/core/JraftRpcAddressParser.java | 21 +-- .../sofa/jraft/storage/FileService.java | 9 +- .../jraft/storage/impl/LogManagerImpl.java | 77 +++++---- .../jraft/storage/impl/RocksDBLogStorage.java | 52 +++--- .../sofa/jraft/storage/io/ProtoBufFile.java | 3 +- .../com/alipay/sofa/jraft/util/Utils.java | 17 +- .../jraft/util/timer/HashedWheelTimer.java | 155 +++++++++--------- .../jraft/conf/ConfigurationEntryTest.java | 6 +- .../sofa/jraft/core/CliServiceTest.java | 6 +- .../sofa/jraft/core/MockStateMachine.java | 16 +- .../sofa/jraft/core/ReadOnlyServiceTest.java | 3 +- .../sofa/jraft/core/ReplicatorTest.java | 88 +++++----- .../alipay/sofa/jraft/core/TestCluster.java | 13 +- .../rpc/AbstractBoltClientServiceTest.java | 15 +- .../jraft/storage/SnapshotExecutorTest.java | 23 +-- .../local/LocalSnapshotCopierTest.java | 34 ++-- .../snapshot/remote/BoltSessionTest.java | 34 ++-- .../sofa/jraft/rhea/KVCommandProcessor.java | 4 +- .../rhea/client/DefaultRheaIterator.java | 3 +- .../rhea/client/DefaultRheaKVRpcService.java | 5 +- .../jraft/rhea/client/DefaultRheaKVStore.java | 10 +- .../jraft/rhea/client/RegionRouteTable.java | 24 ++- .../pd/DefaultPlacementDriverRpcService.java | 5 +- .../jraft/rhea/client/pd/HeartbeatSender.java | 3 +- .../support/RocksStatisticsCollector.java | 8 +- .../rhea/storage/KVStoreStateMachine.java | 12 +- .../jraft/rhea/storage/MemoryRawKVStore.java | 15 +- .../jraft/rhea/storage/RocksRawKVStore.java | 24 ++- .../alipay/sofa/jraft/rhea/util/JvmTools.java | 13 +- .../alipay/sofa/jraft/rhea/util/Lists.java | 5 +- .../sofa/jraft/rhea/util/StackTraceUtil.java | 5 +- .../alipay/sofa/jraft/rhea/util/ZipUtil.java | 6 +- .../AbstractRejectedExecutionHandler.java | 3 +- .../jraft/rhea/util/internal/Updaters.java | 9 +- .../rhea/benchmark/raw/SnapshotBenchmark.java | 4 +- .../benchmark/rhea/RheaBenchmarkCluster.java | 23 ++- .../jraft/rhea/chaos/ChaosTestCluster.java | 60 ++++--- .../sofa/jraft/rhea/pd/RheaKVTestCluster.java | 13 +- .../storage/memorydb/MemoryKVStoreTest.java | 4 +- .../rhea/storage/rhea/RheaKVTestCluster.java | 11 +- .../storage/rocksdb/RocksKVStoreTest.java | 4 +- .../rhea/DefaultPlacementDriverService.java | 4 +- .../jraft/rhea/PlacementDriverProcessor.java | 4 +- .../jraft/rhea/PlacementDriverServer.java | 24 ++- .../jraft/rhea/pipeline/event/PingEvent.java | 4 +- .../alipay/sofa/jraft/rhea/BasePdServer.java | 9 +- .../test/atomic/client/AtomicClient.java | 15 +- .../atomic/command/SlotsResponseCommand.java | 2 +- .../atomic/server/AtomicStateMachine.java | 8 +- 64 files changed, 649 insertions(+), 654 deletions(-) diff --git a/.middleware-common/AlipayFormatter120.xml b/.middleware-common/AlipayFormatter120.xml index 67c5ec19d..9af7b622f 100644 --- a/.middleware-common/AlipayFormatter120.xml +++ b/.middleware-common/AlipayFormatter120.xml @@ -48,7 +48,7 @@ - + @@ -164,7 +164,7 @@ - + @@ -237,7 +237,7 @@ - + diff --git a/README.md b/README.md index 4d34f51cd..843713f68 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ SOFAJRAFT 是一个基于 [RAFT](https://raft.github.io/) 一致性算法的生 SOFAJRAFT 是从百度的 [braft](https://github.com/brpc/braft) 移植而来,做了一些优化和改进,感谢百度 braft 团队开源了优秀的 C++ RAFT 实现 ## 开源许可 -SOFAJRAFT 基于 [Apache License 2.0](https://github.com/alipay/sofa-jraft/blob/master/LICENSE) 协议,SOFAJRAFT 依赖了一些三方组件,它们的开源协议也为 Apache License 2.0, +SOFAJRAFT 基于 [Apache License 2.0](https://github.com/alipay/sofa-jraft/blob/master/LICENSE) 协议,SOFAJRAFT 依赖了一些三方组件,它们的开源协议也为 Apache License 2.0, 另外 SOFAJRAFT 也直接引用了一些开源的代码(可能有一些小小的改动)包括: - [JCTools](https://github.com/JCTools/JCTools) 中的 NonBlockingHashMap/NonBlockingHashMapLong - [Netty](https://github.com/netty/netty) 中的 HashedWheelTimer,另外还参考了 Netty 的 Pipeline 设计 diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/JRaftUtils.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/JRaftUtils.java index baaa513d6..788ccee65 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/JRaftUtils.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/JRaftUtils.java @@ -68,8 +68,8 @@ public static Executor createExecutor(final String prefix, int number) { if (number <= 0) { return null; } - return ThreadPoolUtil.newThreadPool(prefix, true, number, number, 60L, - new SynchronousQueue<>(), createThreadFactory(prefix)); + return ThreadPoolUtil.newThreadPool(prefix, true, number, number, 60L, new SynchronousQueue<>(), + createThreadFactory(prefix)); } /** diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/RouteTable.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/RouteTable.java index 8171b4c8c..fa2b8cddb 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/RouteTable.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/RouteTable.java @@ -223,8 +223,9 @@ public Configuration getConfiguration(final String groupId) { * @param timeoutMs timeout millis * @return operation status */ - public Status refreshLeader(final CliClientService cliClientService, final String groupId, - final int timeoutMs) throws InterruptedException, TimeoutException { + public Status refreshLeader(final CliClientService cliClientService, final String groupId, final int timeoutMs) + throws InterruptedException, + TimeoutException { Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id"); Requires.requireTrue(timeoutMs > 0, "Invalid timeout: " + timeoutMs); @@ -309,8 +310,8 @@ public Status refreshConfiguration(final CliClientService cliClientService, fina rb.setGroupId(groupId); rb.setLeaderId(leaderId.toString()); try { - final Message result = cliClientService.getPeers(leaderId.getEndpoint(), rb.build(), null) - .get(timeoutMs, TimeUnit.MILLISECONDS); + final Message result = cliClientService.getPeers(leaderId.getEndpoint(), rb.build(), null).get(timeoutMs, + TimeUnit.MILLISECONDS); if (result instanceof CliRequests.GetPeersResponse) { final CliRequests.GetPeersResponse resp = (CliRequests.GetPeersResponse) result; final Configuration newConf = new Configuration(); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationManager.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationManager.java index 41039624b..76d3daf50 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationManager.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationManager.java @@ -88,8 +88,8 @@ public ConfigurationEntry getLastConfiguration() { public ConfigurationEntry get(long lastIncludedIndex) { if (this.configurations.isEmpty()) { Requires.requireTrue(lastIncludedIndex >= this.snapshot.getId().getIndex(), - "lastIncludedIndex %d is less than snapshot index %d", lastIncludedIndex, - this.snapshot.getId().getIndex()); + "lastIncludedIndex %d is less than snapshot index %d", lastIncludedIndex, this.snapshot.getId() + .getIndex()); return snapshot; } ListIterator it = this.configurations.listIterator(); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java index 805acbdf3..eff5967ab 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java @@ -49,7 +49,7 @@ public class BallotBox implements Lifecycle { private FSMCaller waiter; private ClosureQueue closureQueue; private final StampedLock stampedLock = new StampedLock(); - private long lastCommittedIndex = 0; + private long lastCommittedIndex = 0; private long pendingIndex; private final ArrayDequeue pendingMetaQueue = new ArrayDequeue<>(); @@ -227,8 +227,8 @@ public boolean setLastCommittedIndex(long lastCommittedIndex) { try { if (pendingIndex != 0 || !pendingMetaQueue.isEmpty()) { Requires.requireTrue(lastCommittedIndex < this.pendingIndex, - "Node changes to leader, pendingIndex=%d, param lastCommittedIndex=%d", - pendingIndex,lastCommittedIndex); + "Node changes to leader, pendingIndex=%d, param lastCommittedIndex=%d", pendingIndex, + lastCommittedIndex); return false; } if (lastCommittedIndex < this.lastCommittedIndex) { diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/CliServiceImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/CliServiceImpl.java index 458db2fac..e0676188c 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/CliServiceImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/CliServiceImpl.java @@ -386,9 +386,9 @@ public List getPeers(String groupId, Configuration conf) { rb.setGroupId(groupId); rb.setLeaderId(leaderId.toString()); try { - final Message result = cliClientService.getPeers(leaderId.getEndpoint(), rb.build(), null) - .get(cliOptions.getTimeoutMs() <= 0 ? this.cliOptions.getRpcDefaultTimeout() - : cliOptions.getTimeoutMs(), TimeUnit.MILLISECONDS); + final Message result = cliClientService.getPeers(leaderId.getEndpoint(), rb.build(), null).get( + cliOptions.getTimeoutMs() <= 0 ? this.cliOptions.getRpcDefaultTimeout() : cliOptions.getTimeoutMs(), + TimeUnit.MILLISECONDS); if (result instanceof GetPeersResponse) { final GetPeersResponse resp = (GetPeersResponse) result; final List peerIdList = new ArrayList<>(); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/FSMCallerImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/FSMCallerImpl.java index 7ec0a6c30..453d59009 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/FSMCallerImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/FSMCallerImpl.java @@ -178,8 +178,8 @@ public boolean init(FSMCallerOptions opts) { this.lastAppliedIndex.set(opts.getBootstrapId().getIndex()); this.notifyLastAppliedIndexUpdated(lastAppliedIndex.get()); this.lastAppliedTerm = opts.getBootstrapId().getTerm(); - this.disruptor = new Disruptor<>(new ApplyTaskFactory(), opts.getDisruptorBufferSize(), - new NamedThreadFactory("Jraft-FSMCaller-disruptor-", true)); + this.disruptor = new Disruptor<>(new ApplyTaskFactory(), opts.getDisruptorBufferSize(), new NamedThreadFactory( + "Jraft-FSMCaller-disruptor-", true)); this.disruptor.handleEventsWith(new ApplyTaskHandler()); this.disruptor.setDefaultExceptionHandler(new LogExceptionHandler(this.getClass().getSimpleName())); this.disruptor.start(); @@ -468,7 +468,7 @@ private void doCommitted(long committedIndex) { if (iterImpl.entry().getType() != EnumOutter.EntryType.ENTRY_TYPE_DATA) { if (iterImpl.entry().getType() == EnumOutter.EntryType.ENTRY_TYPE_CONFIGURATION) { if (iterImpl.entry().getOldPeers() != null && !iterImpl.entry().getOldPeers().isEmpty()) { - //Joint stage is not supposed to be noticeable by end users. + // Joint stage is not supposed to be noticeable by end users. fsm.onConfigurationCommitted(new Configuration(iterImpl.entry().getPeers())); } } @@ -506,7 +506,7 @@ private void onTaskCommitted(final List closures) { final int closureListSize = closures.size(); for (int i = 0; i < closureListSize; i++) { final Closure done = closures.get(i); - if (done != null && done instanceof TaskClosure) { + if (done instanceof TaskClosure) { ((TaskClosure) done).onCommitted(); } } @@ -532,13 +532,13 @@ private void doApplyTasks(IteratorImpl iterImpl) { private void doSnapshotSave(SaveSnapshotClosure done) { Requires.requireNonNull(done, "SaveSnapshotClosure is null"); final long lastAppliedIndex = this.lastAppliedIndex.get(); - final RaftOutter.SnapshotMeta.Builder metaBuilder = RaftOutter.SnapshotMeta.newBuilder().setLastIncludedIndex(lastAppliedIndex) - .setLastIncludedTerm(this.lastAppliedTerm); + final RaftOutter.SnapshotMeta.Builder metaBuilder = RaftOutter.SnapshotMeta.newBuilder() + .setLastIncludedIndex(lastAppliedIndex).setLastIncludedTerm(this.lastAppliedTerm); final ConfigurationEntry confEntry = logManager.getConfiguration(lastAppliedIndex); if (confEntry == null || confEntry.isEmpty()) { LOG.error("Empty conf entry for lastAppliedIndex={}", lastAppliedIndex); - Utils.runClosureInThread(done, - new Status(RaftError.EINVAL, "Empty conf entry for lastAppliedIndex=%s", lastAppliedIndex)); + Utils.runClosureInThread(done, new Status(RaftError.EINVAL, "Empty conf entry for lastAppliedIndex=%s", + lastAppliedIndex)); return; } for (final PeerId peer : confEntry.getConf()) { @@ -609,7 +609,7 @@ private void doSnapshotLoad(LoadSnapshotClosure done) { done.run(new Status(RaftError.EINVAL, "SnapshotReader load meta failed")); if (reader.getRaftError() == RaftError.EIO) { final RaftException err = new RaftException(EnumOutter.ErrorType.ERROR_TYPE_SNAPSHOT, RaftError.EIO, - "Fail to load snapshot meta"); + "Fail to load snapshot meta"); setError(err); } return; @@ -617,15 +617,16 @@ private void doSnapshotLoad(LoadSnapshotClosure done) { final LogId lastAppliedId = new LogId(lastAppliedIndex.get(), lastAppliedTerm); final LogId snapshotId = new LogId(meta.getLastIncludedIndex(), meta.getLastIncludedTerm()); if (lastAppliedId.compareTo(snapshotId) > 0) { - done.run(new Status(RaftError.ESTALE, + done.run(new Status( + RaftError.ESTALE, "Loading a stale snapshot last_applied_index=%d last_applied_term=%d snapshot_index=%d snapshot_term=%d", lastAppliedId.getIndex(), lastAppliedId.getTerm(), snapshotId.getIndex(), snapshotId.getTerm())); return; } if (!this.fsm.onSnapshotLoad(reader)) { done.run(new Status(-1, "StateMachine onSnapshotLoad failed")); - final RaftException e = new RaftException(EnumOutter.ErrorType.ERROR_TYPE_STATE_MACHINE, RaftError.ESTATEMACHINE, - "StateMachine onSnapshotLoad failed"); + final RaftException e = new RaftException(EnumOutter.ErrorType.ERROR_TYPE_STATE_MACHINE, + RaftError.ESTATEMACHINE, "StateMachine onSnapshotLoad failed"); setError(e); return; } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java index a0253fdab..eaafd1c14 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java @@ -382,8 +382,8 @@ void nextStage() { final boolean shouldStepDown = !this.newPeers.contains(this.node.serverId); reset(new Status()); if (shouldStepDown) { - this.node.stepDown(this.node.currTerm, true, - new Status(RaftError.ELEADERREMOVED, "This node was removed.")); + this.node.stepDown(this.node.currTerm, true, new Status(RaftError.ELEADERREMOVED, + "This node was removed.")); } break; case STAGE_NONE: @@ -487,8 +487,7 @@ private void handleElectionTimeout() { return; } final PeerId emptyId = new PeerId(); - this.resetLeaderId(emptyId, - new Status(RaftError.ERAFTTIMEDOUT, "Lost connection from leader %s", leaderId)); + this.resetLeaderId(emptyId, new Status(RaftError.ERAFTTIMEDOUT, "Lost connection from leader %s", leaderId)); doUnlock = false; this.preVote(); } finally { @@ -704,9 +703,10 @@ protected void onTrigger() { this.configManager = new ConfigurationManager(); this.applyDisruptor = new Disruptor<>(new LogEntryAndClosureFactory(), raftOptions.getDisruptorBufferSize(), - new NamedThreadFactory("Jraft-NodeImpl-Disruptor-", true)); + new NamedThreadFactory("Jraft-NodeImpl-Disruptor-", true)); this.applyDisruptor.handleEventsWith(new LogEntryAndClosureHandler()); - this.applyDisruptor.setDefaultExceptionHandler(new LogExceptionHandler(this.getClass().getSimpleName())); + this.applyDisruptor + .setDefaultExceptionHandler(new LogExceptionHandler(this.getClass().getSimpleName())); this.applyDisruptor.start(); this.applyQueue = this.applyDisruptor.getRingBuffer(); @@ -836,7 +836,7 @@ private void electSelf() { } final PeerId emptyId = new PeerId(); resetLeaderId(emptyId, new Status(RaftError.ERAFTTIMEDOUT, - "A follower's leader_id is reset to NULL as it begins to request_vote.")); + "A follower's leader_id is reset to NULL as it begins to request_vote.")); this.state = State.STATE_CANDIDATE; this.currTerm++; this.votedId = this.serverId.copy(); @@ -1029,8 +1029,8 @@ public void run(Status status) { if (status.isOk()) { ballotBox.commitAt(this.firstLogIndex, this.firstLogIndex + this.nEntries - 1, serverId); } else { - LOG.error("Node {} append [{}, {}] failed", getNodeId(), firstLogIndex, - firstLogIndex + this.nEntries - 1); + LOG.error("Node {} append [{}, {}] failed", getNodeId(), firstLogIndex, firstLogIndex + this.nEntries + - 1); } } @@ -1200,9 +1200,9 @@ private void readFollower(ReadIndexRequest request, RpcResponseClosure this.currTerm) { this.stepDown(request.getTerm(), false, new Status(RaftError.EHIGHERTERMRESPONSE, - "Raft node receives higher term RequestVoteRequest.")); + "Raft node receives higher term RequestVoteRequest.")); } } else { // ignore older term @@ -1399,14 +1401,14 @@ public Message handleRequestVoteRequest(RequestVoteRequest request) { break; } final boolean logIsOk = new LogId(request.getLastLogIndex(), request.getLastLogTerm()) - .compareTo(lastLogId) >= 0; + .compareTo(lastLogId) >= 0; - if (logIsOk && (votedId == null || this.votedId.isEmpty())) { - this.stepDown(request.getTerm(), false, new Status(RaftError.EVOTEFORCANDIDATE, - "Raft node votes for some candidate, step down to restart election_timer.")); - this.votedId = candidateId.copy(); - this.metaStorage.setVotedFor(candidateId); - } + if (logIsOk && (votedId == null || this.votedId.isEmpty())) { + this.stepDown(request.getTerm(), false, new Status(RaftError.EVOTEFORCANDIDATE, + "Raft node votes for some candidate, step down to restart election_timer.")); + this.votedId = candidateId.copy(); + this.metaStorage.setVotedFor(candidateId); + } } while (false); @@ -1433,7 +1435,7 @@ public FollowerStableClosure(AppendEntriesRequest request, AppendEntriesResponse NodeImpl node, RpcRequestClosure done, long term) { super(null); this.committedIndex = Math.min( - // committed index is likely less than the lastLogIndex + // committed index is likely less than the lastLogIndex request.getCommittedIndex(), // The logs after the appended entries can not be trust, so we can't commit them even if their indexes are less than request's committed index. request.getPrevLogIndex() + request.getEntriesCount()); @@ -1530,8 +1532,8 @@ public Message handleAppendEntriesRequest(AppendEntriesRequest request, RpcReque serverId, this.currTerm, this.leaderId); // Increase the term by 1 and make both leaders step down to minimize the // loss of split brain - stepDown(request.getTerm() + 1, false, - new Status(RaftError.ELEADERCONFLICT, "More than one leader in the same term.")); + stepDown(request.getTerm() + 1, false, new Status(RaftError.ELEADERCONFLICT, + "More than one leader in the same term.")); responseBuilder.setSuccess(false); responseBuilder.setTerm(request.getTerm() + 1); return responseBuilder.build(); @@ -1623,7 +1625,7 @@ public Message handleAppendEntriesRequest(AppendEntriesRequest request, RpcReque } else { if (entry.getType() == EnumOutter.EntryType.ENTRY_TYPE_CONFIGURATION) { throw new IllegalStateException( - "Invalid log entry that contains zero peers but is ENTRY_TYPE_CONFIGURATION type."); + "Invalid log entry that contains zero peers but is ENTRY_TYPE_CONFIGURATION type."); } } @@ -1701,7 +1703,7 @@ private void onCaughtUp(PeerId peer, long term, long version, Status st) { } // Retry if this peer is still alive if (st.getCode() == RaftError.ETIMEDOUT.getNumber() - && Utils.nowMs() - replicatorGroup.getLastRpcSendTimestamp(peer) <= options.getElectionTimeoutMs()) { + && Utils.nowMs() - replicatorGroup.getLastRpcSendTimestamp(peer) <= options.getElectionTimeoutMs()) { LOG.debug("Node {} waits peer {} to catch up", getNodeId(), peer); final OnCaughtUp caughtUp = new OnCaughtUp(this, term, peer, version); final long dueTime = Utils.nowMs() + options.getElectionTimeoutMs(); @@ -1966,8 +1968,8 @@ public void onError(RaftException error) { // if it is leader, need to wake up a new one. // if it is follower, also step down to call on_stop_following if (state.compareTo(State.STATE_FOLLOWER) <= 0) { - stepDown(currTerm, state == State.STATE_LEADER, - new Status(RaftError.EBADNODE, "Raft node(leader or candidate) is in error.")); + stepDown(currTerm, state == State.STATE_LEADER, new Status(RaftError.EBADNODE, + "Raft node(leader or candidate) is in error.")); } if (state.compareTo(State.STATE_ERROR) < 0) { state = State.STATE_ERROR; @@ -1987,16 +1989,16 @@ public void handleRequestVoteResponse(PeerId peerId, long term, RequestVoteRespo } //check stale term if (term != this.currTerm) { - LOG.warn("Node {} received stale RequestVoteResponse from {} term {} currTerm {}", getNodeId(), peerId, - term, this.currTerm); + LOG.warn("Node {} received stale RequestVoteResponse from {} term {} currTerm {}", getNodeId(), + peerId, term, this.currTerm); return; } //check response term if (response.getTerm() > this.currTerm) { - LOG.warn("Node {} received invalid RequestVoteResponse from {} term {} expect {}", getNodeId(), peerId, - response.getTerm(), this.currTerm); - stepDown(response.getTerm(), false, - new Status(RaftError.EHIGHERTERMRESPONSE, "Raft node receives higher term request_vote_response.")); + LOG.warn("Node {} received invalid RequestVoteResponse from {} term {} expect {}", getNodeId(), + peerId, response.getTerm(), this.currTerm); + stepDown(response.getTerm(), false, new Status(RaftError.EHIGHERTERMRESPONSE, + "Raft node receives higher term request_vote_response.")); return; } // check granted quorum? @@ -2053,10 +2055,10 @@ public void handlePreVoteResponse(PeerId peerId, long term, RequestVoteResponse return; } if (response.getTerm() > this.currTerm) { - LOG.warn("Node {} received invalid PreVoteResponse from {} term {} expect {}", this.getNodeId(), peerId, - response.getTerm(), this.currTerm); - stepDown(response.getTerm(), false, - new Status(RaftError.EHIGHERTERMRESPONSE, "Raft node receives higher term pre_vote_response.")); + LOG.warn("Node {} received invalid PreVoteResponse from {} term {} expect {}", this.getNodeId(), + peerId, response.getTerm(), this.currTerm); + stepDown(response.getTerm(), false, new Status(RaftError.EHIGHERTERMRESPONSE, + "Raft node receives higher term pre_vote_response.")); return; } LOG.info("Node {} received PreVoteResponse from {} term {} granted {}", this.getNodeId(), peerId, @@ -2556,8 +2558,8 @@ public Message handleTimeoutNowRequest(TimeoutNowRequest request, RpcRequestClos if (request.getTerm() != this.currTerm) { final long savedCurrTerm = this.currTerm; if (request.getTerm() > this.currTerm) { - this.stepDown(request.getTerm(), false, - new Status(RaftError.EHIGHERTERMREQUEST, "Raft node receives higher term request")); + this.stepDown(request.getTerm(), false, new Status(RaftError.EHIGHERTERMREQUEST, + "Raft node receives higher term request")); } rb.setTerm(this.currTerm); rb.setSuccess(false); @@ -2625,8 +2627,8 @@ public Message handleInstallSnapshot(InstallSnapshotRequest request, RpcRequestC serverId, this.currTerm, this.leaderId); // Increase the term by 1 and make both leaders step down to minimize the // loss of split brain - this.stepDown(request.getTerm() + 1, false, - new Status(RaftError.ELEADERCONFLICT, "More than one leader in the same term.")); + this.stepDown(request.getTerm() + 1, false, new Status(RaftError.ELEADERCONFLICT, + "More than one leader in the same term.")); responseBuilder.setSuccess(false); responseBuilder.setTerm(request.getTerm() + 1); return responseBuilder.build(); @@ -2676,8 +2678,8 @@ public UserLog readCommittedUserLog(long index) { final long savedLastAppliedIndex = fsmCaller.getLastAppliedIndex(); if (index > savedLastAppliedIndex) { - throw new LogIndexOutOfBoundsException( - "request index " + index + " is greater than lastAppliedIndex: " + savedLastAppliedIndex); + throw new LogIndexOutOfBoundsException("request index " + index + " is greater than lastAppliedIndex: " + + savedLastAppliedIndex); } long curIndex = index; @@ -2693,8 +2695,8 @@ public UserLog readCommittedUserLog(long index) { curIndex++; } if (curIndex > savedLastAppliedIndex) { - throw new IllegalStateException( - "No user log between index:" + index + " and last_applied_index:" + savedLastAppliedIndex); + throw new IllegalStateException("No user log between index:" + index + " and last_applied_index:" + + savedLastAppliedIndex); } entry = logManager.getEntry(curIndex); } while (entry != null); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java index c201a51ce..9ca6c0d87 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java @@ -60,17 +60,18 @@ public class ReadOnlyServiceImpl implements ReadOnlyService, LastAppliedLogIndexListener { /** disruptor to run readonly service. */ - private Disruptor readIndexDisruptor; - private RingBuffer readIndexQueue; - private RaftOptions raftOptions; - private NodeImpl node; - private final Lock lock = new ReentrantLock(); - private FSMCaller fsmCaller; - private volatile CountDownLatch shutdownLatch; - - private ScheduledExecutorService scheduledExecutorService; - - private final TreeMap> pendingNotifyStatus = new TreeMap<>(); + private Disruptor readIndexDisruptor; + private RingBuffer readIndexQueue; + private RaftOptions raftOptions; + private NodeImpl node; + private final Lock lock = new ReentrantLock(); + private FSMCaller fsmCaller; + private volatile CountDownLatch shutdownLatch; + + private ScheduledExecutorService scheduledExecutorService; + + // + private final TreeMap> pendingNotifyStatus = new TreeMap<>(); private static class ReadIndexEvent { Bytes requestContext; diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java index 0f8081ae1..9a1ceba35 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java @@ -141,10 +141,10 @@ private int getAndIncrementRequiredNextSeq() { * */ public enum State { - Probe, // probe follower state - Snapshot, // installing snapshot to follower - Replicate, // replicate logs normally - Destroyed // destroyed + Probe, // probe follower state + Snapshot, // installing snapshot to follower + Replicate, // replicate logs normally + Destroyed // destroyed } public Replicator(ReplicatorOptions replicatorOptions, RaftOptions raftOptions) { @@ -190,9 +190,9 @@ public Map getMetrics() { * 2018-Apr-04 10:38:45 AM */ enum RunningState { - IDLE, // idle - BLOCKING, // blocking state - APPENDING_ENTRIES, // appending log entries + IDLE, // idle + BLOCKING, // blocking state + APPENDING_ENTRIES, // appending log entries INSTALLING_SNAPSHOT // installing snapshot } @@ -212,8 +212,8 @@ static class Stat { @Override public String toString() { return ""; + + this.lastLogIncluded + ", lastLogIndex=" + this.lastLogIndex + ", lastTermIncluded=" + + this.lastTermIncluded + ">"; } } @@ -256,7 +256,7 @@ public Inflight(RequestType requestType, long startIndex, int count, int size, i @Override public String toString() { return "Inflight [count=" + count + ", startIndex=" + startIndex + ", size=" + size + ", rpcFuture=" - + rpcFuture + ", requestType=" + requestType + ", seq=" + seq + "]"; + + rpcFuture + ", requestType=" + requestType + ", seq=" + seq + "]"; } boolean isSendingLogEntries() { @@ -291,7 +291,7 @@ public RpcResponse(final RequestType reqType, int seq, Status status, Message re @Override public String toString() { return "RpcResponse [status=" + status + ", request=" + request + ", response=" + response - + ", rpcSendTime=" + rpcSendTime + ", seq=" + seq + ", requestType=" + requestType + "]"; + + ", rpcSendTime=" + rpcSendTime + ", seq=" + seq + ", requestType=" + requestType + "]"; } /** @@ -501,11 +501,12 @@ void installSnapshot() { final Future rpcFuture = rpcService.installSnapshot(this.options.getPeerId().getEndpoint(), request, new RpcResponseClosureAdapter() { - @Override - public void run(Status status) { - onRpcReturned(id, RequestType.Snapshot, status, request, getResponse(), seq, stateVersion, sendTime); - } - }); + @Override + public void run(Status status) { + onRpcReturned(id, RequestType.Snapshot, status, request, getResponse(), seq, stateVersion, + sendTime); + } + }); addInflight(RequestType.Snapshot, this.nextIndex, 0, 0, seq, rpcFuture); } finally { if (doUnlock) { @@ -525,10 +526,10 @@ static boolean onInstallSnapshotReturned(ThreadId id, Replicator r, Status statu // noinspection ConstantConditions do { final StringBuilder sb = new StringBuilder("Node "). // - append(r.options.getGroupId()).append(":").append(r.options.getServerId()). // - append(" received InstallSnapshotResponse from ").append(r.options.getPeerId()). // - append(" lastIncludedIndex=").append(request.getMeta().getLastIncludedIndex()). // - append(" lastIncludedTerm=").append(request.getMeta().getLastIncludedTerm()); + append(r.options.getGroupId()).append(":").append(r.options.getServerId()). // + append(" received InstallSnapshotResponse from ").append(r.options.getPeerId()). // + append(" lastIncludedIndex=").append(request.getMeta().getLastIncludedIndex()). // + append(" lastIncludedTerm=").append(request.getMeta().getLastIncludedTerm()); if (!status.isOk()) { sb.append(" error:").append(status); LOG.info(sb.toString()); @@ -585,8 +586,8 @@ private void sendEmptyEntries(final boolean isHeartbeat, // id is unlock in installSnapshot this.installSnapshot(); if (isHeartbeat && heartBeatClosure != null) { - Utils.runClosureInThread(heartBeatClosure, - new Status(RaftError.EAGAIN, "Fail to send heartbeat to peer %s", this.options.getPeerId())); + Utils.runClosureInThread(heartBeatClosure, new Status(RaftError.EAGAIN, + "Fail to send heartbeat to peer %s", this.options.getPeerId())); } return; } @@ -624,18 +625,18 @@ public void run(Status status) { final Future rpcFuture = this.rpcService.appendEntries(this.options.getPeerId().getEndpoint(), request, -1, new RpcResponseClosureAdapter() { - @Override - public void run(Status status) { - onRpcReturned(id, RequestType.AppendEntries, status, request, getResponse(), seq, stateVersion, - sendTime); - } + @Override + public void run(Status status) { + onRpcReturned(id, RequestType.AppendEntries, status, request, getResponse(), seq, + stateVersion, sendTime); + } - }); + }); addInflight(RequestType.AppendEntries, this.nextIndex, 0, 0, seq, rpcFuture); } - LOG.debug("Node {} send HeartbeatRequest to {} term {} lastCommittedIndex {}", - options.getNode().getNodeId(), options.getPeerId(), options.getTerm(), request.getCommittedIndex()); + LOG.debug("Node {} send HeartbeatRequest to {} term {} lastCommittedIndex {}", options.getNode() + .getNodeId(), options.getPeerId(), options.getTerm(), request.getCommittedIndex()); } finally { id.unlock(); } @@ -665,7 +666,7 @@ boolean prepareEntry(long nextSendingIndex, int offset, RaftOutter.EntryMeta.Bui } } else { Requires.requireTrue(entry.getType() != EnumOutter.EntryType.ENTRY_TYPE_CONFIGURATION, - "Empty peers but is ENTRY_TYPE_CONFIGURATION type at logIndex=%d", logIndex); + "Empty peers but is ENTRY_TYPE_CONFIGURATION type at logIndex=%d", logIndex); } final int remaining = entry.getData() != null ? entry.getData().remaining() : 0; emb.setDataLen(remaining); @@ -743,8 +744,8 @@ public static void waitForCaughtUp(final ThreadId id, long maxMargin, long dueTi @Override public String toString() { - return "Replicator [state=" + this.state + ", statInfo=" + this.statInfo + ",peerId=" + this.options.getPeerId() - + "]"; + return "Replicator [state=" + this.state + ", statInfo=" + this.statInfo + ",peerId=" + + this.options.getPeerId() + "]"; } static void onBlockTimeoutInNewThread(ThreadId id) { @@ -951,11 +952,11 @@ static void onHeartbeatReturned(ThreadId id, Status status, AppendEntriesRequest StringBuilder sb = null; if (isLogDebugEnabled) { sb = new StringBuilder("Node "). // - append(r.options.getGroupId()).append(":").append(r.options.getServerId()). // - append(" received HeartbeatResponse from "). // - append(r.options.getPeerId()). // - append(" prevLogIndex=").append(request.getPrevLogIndex()). // - append(" prevLogTerm=").append(request.getPrevLogTerm()); + append(r.options.getGroupId()).append(":").append(r.options.getServerId()). // + append(" received HeartbeatResponse from "). // + append(r.options.getPeerId()). // + append(" prevLogIndex=").append(request.getPrevLogIndex()). // + append(" prevLogTerm=").append(request.getPrevLogTerm()); } if (!status.isOk()) { if (isLogDebugEnabled) { @@ -974,7 +975,7 @@ static void onHeartbeatReturned(ThreadId id, Status status, AppendEntriesRequest if (response.getTerm() > r.options.getTerm()) { if (isLogDebugEnabled) { sb.append(" fail, greater term ").append(response.getTerm()).append(" expect term ") - .append(r.options.getTerm()); + .append(r.options.getTerm()); LOG.debug(sb.toString()); } final NodeImpl node = r.options.getNode(); @@ -997,8 +998,8 @@ static void onHeartbeatReturned(ThreadId id, Status status, AppendEntriesRequest } @SuppressWarnings("ContinueOrBreakFromFinallyBlock") - static void onRpcReturned(ThreadId id, RequestType reqType, Status status, Message request, Message response, int seq, - int stateVersion, long rpcSendTime) { + static void onRpcReturned(ThreadId id, RequestType reqType, Status status, Message request, Message response, + int seq, int stateVersion, long rpcSendTime) { if (id == null) { return; } @@ -1059,7 +1060,7 @@ static void onRpcReturned(ThreadId id, RequestType reqType, Status status, Messa // The previous in-flight requests were cleared. if (isLogDebugEnabled) { sb.append("ignore response because request not found:").append(queuedPipelinedResponse) - .append(",\n"); + .append(",\n"); } continue; } @@ -1141,20 +1142,20 @@ private static boolean onAppendEntriesReturned(ThreadId id, Inflight inflight, S if (request.getEntriesCount() > 0) { r.nodeMetrics.recordLatency("replicate-entries", Utils.nowMs() - rpcSendTime); r.nodeMetrics.recordSize("replicate-entries-count", request.getEntriesCount()); - r.nodeMetrics.recordSize("replicate-entries-bytes", - request.getData() != null ? request.getData().size() : 0); + r.nodeMetrics.recordSize("replicate-entries-bytes", request.getData() != null ? request.getData().size() + : 0); } final boolean isLogDebugEnabled = LOG.isDebugEnabled(); StringBuilder sb = null; if (isLogDebugEnabled) { sb = new StringBuilder("Node "). // - append(r.options.getGroupId()).append(":").append(r.options.getServerId()). // - append(" received AppendEntriesResponse from "). // - append(r.options.getPeerId()). // - append(" prevLogIndex=").append(request.getPrevLogIndex()). // - append(" prevLogTerm=").append(request.getPrevLogTerm()). // - append(" count=").append(request.getEntriesCount()); + append(r.options.getGroupId()).append(":").append(r.options.getServerId()). // + append(" received AppendEntriesResponse from "). // + append(r.options.getPeerId()). // + append(" prevLogIndex=").append(request.getPrevLogIndex()). // + append(" prevLogTerm=").append(request.getPrevLogTerm()). // + append(" count=").append(request.getEntriesCount()); } if (!status.isOk()) { // If the follower crashes, any RPC to the follower fails immediately, @@ -1180,7 +1181,7 @@ private static boolean onAppendEntriesReturned(ThreadId id, Inflight inflight, S if (response.getTerm() > r.options.getTerm()) { if (isLogDebugEnabled) { sb.append(" fail, greater term ").append(response.getTerm()).append(" expect term ") - .append(r.options.getTerm()); + .append(r.options.getTerm()); LOG.debug(sb.toString()); } final NodeImpl node = r.options.getNode(); @@ -1192,7 +1193,7 @@ private static boolean onAppendEntriesReturned(ThreadId id, Inflight inflight, S } if (isLogDebugEnabled) { sb.append(" fail, find nextIndex remote lastLogIndex ").append(response.getLastLogIndex()) - .append(" local nextIndex ").append(r.nextIndex); + .append(" local nextIndex ").append(r.nextIndex); LOG.debug(sb.toString()); } if (rpcSendTime > r.lastRpcSendTimestamp) { @@ -1377,15 +1378,15 @@ private boolean sendEntries(final long nextSendingIndex) { final int v = this.version; final long sendTimeMs = Utils.nowMs(); final int seq = getAndIncrementReqSeq(); - final Future rpcFuture = this.rpcService.appendEntries(this.options.getPeerId().getEndpoint(), request, - -1, new RpcResponseClosureAdapter() { + final Future rpcFuture = this.rpcService.appendEntries(this.options.getPeerId().getEndpoint(), + request, -1, new RpcResponseClosureAdapter() { - @Override - public void run(Status status) { - onRpcReturned(id, RequestType.AppendEntries, status, request, getResponse(), seq, v, sendTimeMs); - } + @Override + public void run(Status status) { + onRpcReturned(id, RequestType.AppendEntries, status, request, getResponse(), seq, v, sendTimeMs); + } - }); + }); addInflight(RequestType.AppendEntries, nextSendingIndex, request.getEntriesCount(), request.getData().size(), seq, rpcFuture); return true; @@ -1444,19 +1445,19 @@ private Future timeoutNow(TimeoutNowRequest.Builder rb, final boolean s return this.rpcService.timeoutNow(options.getPeerId().getEndpoint(), request, timeoutMs, new RpcResponseClosureAdapter() { - @Override - public void run(Status status) { - if (id != null) { - onTimeoutNowReturned(id, status, request, getResponse(), stopAfterFinish); + @Override + public void run(Status status) { + if (id != null) { + onTimeoutNowReturned(id, status, request, getResponse(), stopAfterFinish); + } } - } - }); + }); } @SuppressWarnings("unused") - static void onTimeoutNowReturned(ThreadId id, Status status, TimeoutNowRequest request, TimeoutNowResponse response, - final boolean stopAfterFinish) { + static void onTimeoutNowReturned(ThreadId id, Status status, TimeoutNowRequest request, + TimeoutNowResponse response, final boolean stopAfterFinish) { final Replicator r = (Replicator) id.lock(); if (r == null) { return; @@ -1466,9 +1467,9 @@ static void onTimeoutNowReturned(ThreadId id, Status status, TimeoutNowRequest r StringBuilder sb = null; if (isLogDebugEnabled) { sb = new StringBuilder("Node "). // - append(r.options.getGroupId()).append(":").append(r.options.getServerId()). // - append(" received TimeoutNowResponse from "). // - append(r.options.getPeerId()); + append(r.options.getGroupId()).append(":").append(r.options.getServerId()). // + append(" received TimeoutNowResponse from "). // + append(r.options.getPeerId()); } if (!status.isOk()) { if (isLogDebugEnabled) { diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java index 2f3456d2d..c8e42028f 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java @@ -50,16 +50,17 @@ */ public class ReplicatorGroupImpl implements ReplicatorGroup { - private static final Logger LOG = LoggerFactory - .getLogger(ReplicatorGroupImpl.class); + private static final Logger LOG = LoggerFactory + .getLogger(ReplicatorGroupImpl.class); - private final ConcurrentMap replicatorMap = new ConcurrentHashMap<>(); - /** common replicator options*/ - private ReplicatorOptions commonOptions; - private int dynamicTimeoutMs = -1; - private int electionTimeoutMs = -1; - private RaftOptions raftOptions; - private final Set failureReplicators = new ConcurrentHashSet<>(); + // + private final ConcurrentMap replicatorMap = new ConcurrentHashMap<>(); + /** common replicator options */ + private ReplicatorOptions commonOptions; + private int dynamicTimeoutMs = -1; + private int electionTimeoutMs = -1; + private RaftOptions raftOptions; + private final Set failureReplicators = new ConcurrentHashSet<>(); @Override public boolean init(NodeId nodeId, ReplicatorGroupOptions opts) { diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/ProtobufMsgFactory.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/ProtobufMsgFactory.java index 8a207ae3e..c58a7c421 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/ProtobufMsgFactory.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/ProtobufMsgFactory.java @@ -50,8 +50,8 @@ public class ProtobufMsgFactory { static { try { - FileDescriptorSet descriptorSet = FileDescriptorSet - .parseFrom(ProtoBufFile.class.getResourceAsStream("/raft.desc")); + FileDescriptorSet descriptorSet = FileDescriptorSet.parseFrom(ProtoBufFile.class + .getResourceAsStream("/raft.desc")); List resolveFDs = new ArrayList<>(); for (FileDescriptorProto fdp : descriptorSet.getFileList()) { @@ -63,7 +63,7 @@ public class ProtobufMsgFactory { for (Descriptor descriptor : fd.getMessageTypes()) { String className = fdp.getOptions().getJavaPackage() + "." - + fdp.getOptions().getJavaOuterClassname() + "$" + descriptor.getName(); + + fdp.getOptions().getJavaOuterClassname() + "$" + descriptor.getName(); Class clazz = Class.forName(className); MethodHandle methodHandle = MethodHandles.lookup().findStatic(clazz, "parseFrom", methodType(clazz, byte[].class)); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractBoltClientService.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractBoltClientService.java index 2f188240c..f308494cc 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractBoltClientService.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractBoltClientService.java @@ -94,9 +94,9 @@ protected boolean initRpcClient(int rpcProcessorThreadPoolSize) { this.rpcClient = new RpcClient(); this.configRpcClient(rpcClient); this.rpcClient.init(); - this.rpcExecutor = ThreadPoolUtil.newThreadPool("JRaft-RPC-Processor", true, - rpcProcessorThreadPoolSize / 3, rpcProcessorThreadPoolSize, 60L, - new ArrayBlockingQueue<>(10000), new NamedThreadFactory("JRaft-RPC-Processor-")); + this.rpcExecutor = ThreadPoolUtil.newThreadPool("JRaft-RPC-Processor", true, rpcProcessorThreadPoolSize / 3, + rpcProcessorThreadPoolSize, 60L, new ArrayBlockingQueue<>(10000), new NamedThreadFactory( + "JRaft-RPC-Processor-")); if (this.rpcOptions.getMetricRegistry() != null) { this.rpcOptions.getMetricRegistry().register("raft-rpc-client-thread-pool", new ThreadPoolMetricSet(this.rpcExecutor)); @@ -188,9 +188,8 @@ public void onException(Throwable e) { } if (done != null) { try { - done.run(new Status( - e instanceof InvokeTimeoutException ? RaftError.ETIMEDOUT : RaftError.EINTERNAL, - "RPC exception:" + e.getMessage())); + done.run(new Status(e instanceof InvokeTimeoutException ? RaftError.ETIMEDOUT + : RaftError.EINTERNAL, "RPC exception:" + e.getMessage())); } catch (final Throwable t) { LOG.error("Fail to run RpcResponseClosure, the request is {}", request, t); } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java index d8eef3c90..8b91e6cfd 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java @@ -41,7 +41,6 @@ import com.alipay.sofa.jraft.util.Utils; import com.google.protobuf.Message; - /** * Append entries request processor. * @@ -49,8 +48,8 @@ * * 2018-Apr-04 3:00:13 PM */ -public class AppendEntriesRequestProcessor extends NodeRequestProcessor -implements ConnectionEventProcessor { +public class AppendEntriesRequestProcessor extends NodeRequestProcessor implements + ConnectionEventProcessor { static final String PEER_ATTR = "jraft-peer"; @@ -204,13 +203,12 @@ static class PeerRequestContext { private final int maxPendingResponses; - public PeerRequestContext(final String groupId, final String peerId, - int maxPendingResponses) { + public PeerRequestContext(final String groupId, final String peerId, int maxPendingResponses) { super(); this.peerId = peerId; this.groupId = groupId; - this.executor = new DefaultEventExecutor( - JRaftUtils.createThreadFactory(groupId + "/" + peerId + "-AppendEntriesThread")); + this.executor = new DefaultEventExecutor(JRaftUtils.createThreadFactory(groupId + "/" + peerId + + "-AppendEntriesThread")); this.sequence = 0; this.nextRequiredSequence = 0; @@ -276,8 +274,8 @@ PeerRequestContext getPeerRequestContext(final String groupId, final String peer assert (parsed); final Node node = NodeManager.getInstance().get(groupId, peer); assert (node != null); - peerCtx = new PeerRequestContext(groupId, peerId, - node.getRaftOptions().getMaxReplicatorInflightMsgs()); + peerCtx = new PeerRequestContext(groupId, peerId, node.getRaftOptions() + .getMaxReplicatorInflightMsgs()); groupContexts.put(peerId, peerCtx); } } @@ -310,7 +308,7 @@ void removePeerRequestContext(final String groupId, final String peerId) { /** * The executor selector to select executor for processing request. */ - private final ExecutorSelector executorSelector; + private final ExecutorSelector executorSelector; public AppendEntriesRequestProcessor(Executor executor) { super(executor); @@ -349,8 +347,8 @@ public Message processRequest0(RaftServerService service, AppendEntriesRequest r final String peerId = request.getPeerId(); final int reqSequence = getAndIncrementSequence(groupId, peerId, done.getBizContext().getConnection()); - final Message response = service.handleAppendEntriesRequest(request, - new SequenceRpcRequestClosure(done, reqSequence, groupId, peerId)); + final Message response = service.handleAppendEntriesRequest(request, new SequenceRpcRequestClosure(done, + reqSequence, groupId, peerId)); if (response != null) { sendSequenceResponse(groupId, peerId, reqSequence, done.getAsyncContext(), done.getBizContext(), response); @@ -388,7 +386,7 @@ public void onEvent(String remoteAddr, Connection conn) { if (!StringUtils.isBlank(peerAttr) && peer.parse(peerAttr)) { // Clear request context when connection disconnected. for (final Map.Entry> entry : this.peerRequestContexts - .entrySet()) { + .entrySet()) { final ConcurrentMap groupCtxs = entry.getValue(); synchronized (Utils.withLockObject(groupCtxs)) { final PeerRequestContext ctx = groupCtxs.remove(peer.toString()); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/JraftRpcAddressParser.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/JraftRpcAddressParser.java index 1830da2fd..f14d1a7ac 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/JraftRpcAddressParser.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/JraftRpcAddressParser.java @@ -36,8 +36,7 @@ public class JraftRpcAddressParser extends RpcAddressParser { @Override public Url parse(String url) { if (StringUtils.isBlank(url)) { - throw new IllegalArgumentException("Illegal format address string [" + url - + "], should not be blank! "); + throw new IllegalArgumentException("Illegal format address string [" + url + "], should not be blank! "); } Url parsedUrl = this.tryGet(url); if (null != parsedUrl) { @@ -56,14 +55,14 @@ public Url parse(String url) { // should not end with COLON if (i == size - 1) { throw new IllegalArgumentException("Illegal format address string [" + url - + "], should not end with COLON[:]! "); + + "], should not end with COLON[:]! "); } break; } // must have one COLON if (i == size - 1) { throw new IllegalArgumentException("Illegal format address string [" + url - + "], must have one COLON[:]! "); + + "], must have one COLON[:]! "); } } @@ -74,7 +73,7 @@ public Url parse(String url) { if (i == size - 1) { // should not end with QUES throw new IllegalArgumentException("Illegal format address string [" + url - + "], should not end with QUES[?]! "); + + "], should not end with QUES[?]! "); } break; } @@ -96,16 +95,15 @@ public Url parse(String url) { pos = i; if (i == size - 1) { // should not end with EQUAL - throw new IllegalArgumentException( - "Illegal format address string [" + url - + "], should not end with EQUAL[=]! "); + throw new IllegalArgumentException("Illegal format address string [" + url + + "], should not end with EQUAL[=]! "); } break; } if (i == size - 1) { // must have one EQUAL throw new IllegalArgumentException("Illegal format address string [" + url - + "], must have one EQUAL[=]! "); + + "], must have one EQUAL[=]! "); } } for (int i = pos; i < size; ++i) { @@ -114,9 +112,8 @@ public Url parse(String url) { pos = i; if (i == size - 1) { // should not end with AND - throw new IllegalArgumentException("Illegal format address string [" - + url - + "], should not end with AND[&]! "); + throw new IllegalArgumentException("Illegal format address string [" + url + + "], should not end with AND[&]! "); } break; } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/FileService.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/FileService.java index 41aa583b9..753761aef 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/FileService.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/FileService.java @@ -73,9 +73,9 @@ void clear() { private FileService() { this.nextId = new AtomicLong(); - final long initValue = Math - .abs(Utils.getProcessId(ThreadLocalRandom.current().nextLong(10000, Integer.MAX_VALUE)) << 45 - | System.nanoTime() << 17 >> 17); + final long initValue = Math.abs(Utils.getProcessId(ThreadLocalRandom.current().nextLong(10000, + Integer.MAX_VALUE)) << 45 + | System.nanoTime() << 17 >> 17); this.nextId.set(initValue); LOG.info("Initial file reader id in FileService is {}", this.nextId); } @@ -98,7 +98,8 @@ public Message handleGetFile(GetFileRequest request, RpcRequestClosure done) { final ByteBufferCollector dataBuffer = ByteBufferCollector.allocate(); final GetFileResponse.Builder responseBuilder = GetFileResponse.newBuilder(); try { - final int read = reader.readFile(dataBuffer, request.getFilename(), request.getOffset(), request.getCount()); + final int read = reader + .readFile(dataBuffer, request.getFilename(), request.getOffset(), request.getCount()); responseBuilder.setReadSize(read); if (read == -1) { responseBuilder.setEof(true); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java index 0a41afc75..ec48812b5 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java @@ -66,30 +66,31 @@ * 2018-Apr-04 4:42:20 PM */ public class LogManagerImpl implements LogManager { - private static final Logger LOG = LoggerFactory.getLogger(LogManagerImpl.class); - - private LogStorage logStorage; - private ConfigurationManager configManager; - private FSMCaller fsmCaller; - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final Lock writeLock = lock.writeLock(); - private final Lock readLock = lock.readLock(); - private volatile boolean stopped; - private volatile boolean hasError; - private long nextWaitId; - private LogId diskId = new LogId(0, 0); - private LogId appliedId = new LogId(0, 0); + private static final Logger LOG = LoggerFactory + .getLogger(LogManagerImpl.class); + + private LogStorage logStorage; + private ConfigurationManager configManager; + private FSMCaller fsmCaller; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock writeLock = lock.writeLock(); + private final Lock readLock = lock.readLock(); + private volatile boolean stopped; + private volatile boolean hasError; + private long nextWaitId; + private LogId diskId = new LogId(0, 0); + private LogId appliedId = new LogId(0, 0); //TODO use a lock-free concurrent list instead? - private ArrayDequeue logsInMemory = new ArrayDequeue<>(); - private volatile long firstLogIndex; - private volatile long lastLogIndex; - private volatile LogId lastSnapshotId = new LogId(0, 0); - private final Map waitMap = new HashMap<>(); - private Disruptor disruptor; - private RingBuffer diskQueue; - private RaftOptions raftOptions; - private volatile CountDownLatch shutDownLatch; - private NodeMetrics nodeMetrics; + private ArrayDequeue logsInMemory = new ArrayDequeue<>(); + private volatile long firstLogIndex; + private volatile long lastLogIndex; + private volatile LogId lastSnapshotId = new LogId(0, 0); + private final Map waitMap = new HashMap<>(); + private Disruptor disruptor; + private RingBuffer diskQueue; + private RaftOptions raftOptions; + private volatile CountDownLatch shutDownLatch; + private NodeMetrics nodeMetrics; private final CopyOnWriteArrayList lastLogIndexListeners = new CopyOnWriteArrayList<>(); private enum EventType { @@ -287,8 +288,8 @@ public void appendEntries(List entries, final StableClosure done) { if (entry.getOldPeers() != null) { oldConf = new Configuration(entry.getOldPeers()); } - final ConfigurationEntry conf = new ConfigurationEntry(entry.getId(), - new Configuration(entry.getPeers()), oldConf); + final ConfigurationEntry conf = new ConfigurationEntry(entry.getId(), new Configuration( + entry.getPeers()), oldConf); this.configManager.add(conf); } } @@ -328,8 +329,7 @@ private void notifyLastLogIndexListeners() { try { listener.onLastLogIndexChanged(this.lastLogIndex); } catch (final Exception e) { - LOG.error("Fail to notify LastLogIndexListener, listener={}, index={}", listener, - this.lastLogIndex); + LOG.error("Fail to notify LastLogIndexListener, listener={}, index={}", listener, this.lastLogIndex); } } } @@ -549,8 +549,8 @@ public void setSnapshot(SnapshotMeta meta) { oldConf.addPeer(peer); } - final ConfigurationEntry entry = new ConfigurationEntry( - new LogId(meta.getLastIncludedIndex(), meta.getLastIncludedTerm()), conf, oldConf); + final ConfigurationEntry entry = new ConfigurationEntry(new LogId(meta.getLastIncludedIndex(), + meta.getLastIncludedTerm()), conf, oldConf); this.configManager.setSnapshot(entry); final long term = unsafeGetTerm(meta.getLastIncludedIndex()); final long savedLastSnapshotIndex = this.lastSnapshotId.getIndex(); @@ -608,7 +608,7 @@ private String descLogsInMemory() { wasFirst = false; } sb.append(""); + .append("),type:").append(logEntry.getType()).append(">"); } return sb.toString(); } @@ -619,8 +619,7 @@ protected LogEntry getEntryFromMemory(long index) { final long firstIndex = this.logsInMemory.peekFirst().getId().getIndex(); final long lastIndex = this.logsInMemory.peekLast().getId().getIndex(); Requires.requireTrue(lastIndex - firstIndex + 1 == this.logsInMemory.size(), - "lastIndex=%d,firstIndex=%d,logsInMemory=[%s]", - lastIndex, firstIndex, descLogsInMemory()); + "lastIndex=%d,firstIndex=%d,logsInMemory=[%s]", lastIndex, firstIndex, descLogsInMemory()); if (index >= firstIndex && index <= lastIndex) { entry = logsInMemory.get((int) (index - firstIndex)); } @@ -820,7 +819,7 @@ private boolean truncatePrefix(long firstIndexKept) { //TODO maybe it's fine here Requires.requireTrue(firstIndexKept >= this.firstLogIndex, - "Try to truncate logs before %d, but the firstLogIndex is %d", firstIndexKept, firstLogIndex); + "Try to truncate logs before %d, but the firstLogIndex is %d", firstIndexKept, firstLogIndex); this.firstLogIndex = firstIndexKept; if (firstIndexKept > this.lastLogIndex) { @@ -889,9 +888,9 @@ private boolean checkAndResolveConflict(List entries, StableClosure do // should check and resolve the conflicts between the local logs and // |entries| if (firstLogEntry.getId().getIndex() > this.lastLogIndex + 1) { - Utils.runClosureInThread(done, - new Status(RaftError.EINVAL, "There's gap between first_index=%d and last_log_index=%d", - firstLogEntry.getId().getIndex(), this.lastLogIndex)); + Utils.runClosureInThread(done, new Status(RaftError.EINVAL, + "There's gap between first_index=%d and last_log_index=%d", firstLogEntry.getId().getIndex(), + this.lastLogIndex)); return false; } final long appliedIndex = appliedId.getIndex(); @@ -911,8 +910,8 @@ private boolean checkAndResolveConflict(List entries, StableClosure do // ones. int conflictingIndex = 0; for (; conflictingIndex < entries.size(); conflictingIndex++) { - if (unsafeGetTerm(entries.get(conflictingIndex).getId().getIndex()) != entries.get(conflictingIndex) - .getId().getTerm()) { + if (unsafeGetTerm(entries.get(conflictingIndex).getId().getIndex()) != entries + .get(conflictingIndex).getId().getTerm()) { break; } } @@ -924,7 +923,7 @@ private boolean checkAndResolveConflict(List entries, StableClosure do } this.lastLogIndex = lastLogEntry.getId().getIndex(); } // else this is a duplicated AppendEntriesRequest, we have - // nothing to do besides releasing all the entries + // nothing to do besides releasing all the entries if (conflictingIndex > 0) { // Remove duplication entries.subList(0, conflictingIndex).clear(); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java index 1a061246b..70bc63520 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java @@ -109,12 +109,12 @@ public RocksDBLogStorage(String path, RaftOptions raftOptions) { private static BlockBasedTableConfig createTableConfig() { return new BlockBasedTableConfig(). // - setIndexType(IndexType.kHashSearch). // use hash search(btree) for prefix scan. - setBlockSize(4 * SizeUnit.KB).// - setFilter(new BloomFilter(16, false)). // - setCacheIndexAndFilterBlocks(true). // - setBlockCacheSize(512 * SizeUnit.MB). // - setCacheNumShardBits(8); + setIndexType(IndexType.kHashSearch). // use hash search(btree) for prefix scan. + setBlockSize(4 * SizeUnit.KB).// + setFilter(new BloomFilter(16, false)). // + setCacheIndexAndFilterBlocks(true). // + setBlockCacheSize(512 * SizeUnit.MB). // + setCacheNumShardBits(8); } public static DBOptions createDBOptions() { @@ -122,11 +122,11 @@ public static DBOptions createDBOptions() { // and http://gitlab.alibaba-inc.com/aloha/aloha/blob/branch_2_5_0/jstorm-core/src/main/java/com/alibaba/jstorm/cache/rocksdb/RocksDbOptionsFactory.java final DBOptions options = new DBOptions(); return options.setCreateIfMissing(true). // - setCreateMissingColumnFamilies(true). // - setMaxOpenFiles(-1). // - setMaxLogFileSize(MAX_LOG_FILE_SIZE). // - setMaxBackgroundFlushes(1). // - setMaxBackgroundCompactions(1); + setCreateMissingColumnFamilies(true). // + setMaxOpenFiles(-1). // + setMaxLogFileSize(MAX_LOG_FILE_SIZE). // + setMaxBackgroundFlushes(1). // + setMaxBackgroundCompactions(1); } @@ -134,20 +134,20 @@ public static ColumnFamilyOptions createColumnFamilyOptions() { final BlockBasedTableConfig tconfig = createTableConfig(); final ColumnFamilyOptions options = new ColumnFamilyOptions(); return options.setMaxWriteBufferNumber(2). // - useFixedLengthPrefixExtractor(8). // - setTableFormatConfig(tconfig). // - setCompressionType(CompressionType.LZ4_COMPRESSION). // - setCompactionStyle(CompactionStyle.LEVEL). // - optimizeLevelStyleCompaction(). // - setLevel0FileNumCompactionTrigger(10). // - setLevel0SlowdownWritesTrigger(20). // - setLevel0StopWritesTrigger(40). // - setWriteBufferSize(64 * SizeUnit.MB). // - setMaxWriteBufferNumber(3). // - setTargetFileSizeBase(64 * SizeUnit.MB). // - setMaxBytesForLevelBase(512 * SizeUnit.MB). // - setMergeOperator(new StringAppendOperator()). // - setMemtablePrefixBloomSizeRatio(0.125); + useFixedLengthPrefixExtractor(8). // + setTableFormatConfig(tconfig). // + setCompressionType(CompressionType.LZ4_COMPRESSION). // + setCompactionStyle(CompactionStyle.LEVEL). // + optimizeLevelStyleCompaction(). // + setLevel0FileNumCompactionTrigger(10). // + setLevel0SlowdownWritesTrigger(20). // + setLevel0StopWritesTrigger(40). // + setWriteBufferSize(64 * SizeUnit.MB). // + setMaxWriteBufferNumber(3). // + setTargetFileSizeBase(64 * SizeUnit.MB). // + setMaxBytesForLevelBase(512 * SizeUnit.MB). // + setMergeOperator(new StringAppendOperator()). // + setMemtablePrefixBloomSizeRatio(0.125); } @@ -222,7 +222,7 @@ private void load(ConfigurationManager confManager) { this.truncatePrefixInBackground(0L, this.firstLogIndex); } else { LOG.warn("Unknown entry in configuration storage key={}, value={}", Arrays.toString(ks), - Arrays.toString(bs)); + Arrays.toString(bs)); } } it.next(); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java index 7f1d15da1..cde81b787 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java @@ -64,8 +64,7 @@ public T load() throws IOException { } byte[] lenBytes = new byte[4]; - try (FileInputStream fin = new FileInputStream(file); - BufferedInputStream input = new BufferedInputStream(fin)) { + try (FileInputStream fin = new FileInputStream(file); BufferedInputStream input = new BufferedInputStream(fin)) { readBytes(lenBytes, input); int len = Bits.getInt(lenBytes, 0); if (len <= 0) { diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/Utils.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/Utils.java index 21887ac91..0c7985822 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/Utils.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/Utils.java @@ -53,21 +53,26 @@ public class Utils { /** * Default jraft closure executor pool minimum size, CPUs by default. */ - public static final int MIN_CLOSURE_EXECUTOR_POOL_SIZE = Integer - .parseInt(System.getProperty("jraft.closure.threadpool.size.min", String.valueOf(cpus()))); + public static final int MIN_CLOSURE_EXECUTOR_POOL_SIZE = Integer.parseInt(System.getProperty( + "jraft.closure.threadpool.size.min", + String.valueOf(cpus()))); /** * Default jraft closure executor pool maximum size, 5*CPUs by default. */ - public static final int MAX_CLOSURE_EXECUTOR_POOL_SIZE = Integer - .parseInt(System.getProperty("jraft.closure.threadpool.size.max", String.valueOf(cpus() * 100))); + public static final int MAX_CLOSURE_EXECUTOR_POOL_SIZE = Integer.parseInt(System.getProperty( + "jraft.closure.threadpool.size.max", + String.valueOf(cpus() * 100))); /** * Global thread pool to run closure. */ private static ThreadPoolExecutor CLOSURE_EXECUTOR = ThreadPoolUtil.newThreadPool("CLOSURE_EXECUTOR", - true, MIN_CLOSURE_EXECUTOR_POOL_SIZE, MAX_CLOSURE_EXECUTOR_POOL_SIZE, 60L, new SynchronousQueue<>(), - new NamedThreadFactory("JRaft-Closure-Executor-", true)); + true, MIN_CLOSURE_EXECUTOR_POOL_SIZE, + MAX_CLOSURE_EXECUTOR_POOL_SIZE, 60L, + new SynchronousQueue<>(), + new NamedThreadFactory( + "JRaft-Closure-Executor-", true)); private static final Pattern GROUP_ID_PATTER = Pattern.compile("^[a-zA-Z][a-zA-Z0-9\\-_]*$"); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java index 946633247..b65311936 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java @@ -50,34 +50,37 @@ */ public class HashedWheelTimer implements Timer { - private static final Logger LOG = LoggerFactory.getLogger(HashedWheelTimer.class); - - private static final int INSTANCE_COUNT_LIMIT = 64; - private static final AtomicInteger instanceCounter = new AtomicInteger(); - private static final AtomicBoolean warnedTooManyInstances = new AtomicBoolean(); - - private static final AtomicIntegerFieldUpdater workerStateUpdater = - AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimer.class, "workerState"); - - private final Worker worker = new Worker(); - private final Thread workerThread; - - public static final int WORKER_STATE_INIT = 0; - public static final int WORKER_STATE_STARTED = 1; - public static final int WORKER_STATE_SHUTDOWN = 2; - @SuppressWarnings({"unused", "FieldMayBeFinal"}) - private volatile int workerState; // 0 - init, 1 - started, 2 - shut down - - private final long tickDuration; - private final HashedWheelBucket[] wheel; - private final int mask; - private final CountDownLatch startTimeInitialized = new CountDownLatch(1); - private final Queue timeouts = new ConcurrentLinkedQueue<>(); - private final Queue cancelledTimeouts = new ConcurrentLinkedQueue<>(); - private final AtomicLong pendingTimeouts = new AtomicLong(0); - private final long maxPendingTimeouts; - - private volatile long startTime; + private static final Logger LOG = LoggerFactory + .getLogger(HashedWheelTimer.class); + + private static final int INSTANCE_COUNT_LIMIT = 64; + private static final AtomicInteger instanceCounter = new AtomicInteger(); + private static final AtomicBoolean warnedTooManyInstances = new AtomicBoolean(); + + private static final AtomicIntegerFieldUpdater workerStateUpdater = AtomicIntegerFieldUpdater + .newUpdater( + HashedWheelTimer.class, + "workerState"); + + private final Worker worker = new Worker(); + private final Thread workerThread; + + public static final int WORKER_STATE_INIT = 0; + public static final int WORKER_STATE_STARTED = 1; + public static final int WORKER_STATE_SHUTDOWN = 2; + @SuppressWarnings({ "unused", "FieldMayBeFinal" }) + private volatile int workerState; // 0 - init, 1 - started, 2 - shut down + + private final long tickDuration; + private final HashedWheelBucket[] wheel; + private final int mask; + private final CountDownLatch startTimeInitialized = new CountDownLatch(1); + private final Queue timeouts = new ConcurrentLinkedQueue<>(); + private final Queue cancelledTimeouts = new ConcurrentLinkedQueue<>(); + private final AtomicLong pendingTimeouts = new AtomicLong(0); + private final long maxPendingTimeouts; + + private volatile long startTime; /** * Creates a new timer with the default thread factory @@ -140,8 +143,7 @@ public HashedWheelTimer(ThreadFactory threadFactory) { * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} * @throws IllegalArgumentException if {@code tickDuration} is <= 0 */ - public HashedWheelTimer( - ThreadFactory threadFactory, long tickDuration, TimeUnit unit) { + public HashedWheelTimer(ThreadFactory threadFactory, long tickDuration, TimeUnit unit) { this(threadFactory, tickDuration, unit, 512); } @@ -157,8 +159,7 @@ public HashedWheelTimer( * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 */ - public HashedWheelTimer( - ThreadFactory threadFactory, long tickDuration, TimeUnit unit, int ticksPerWheel) { + public HashedWheelTimer(ThreadFactory threadFactory, long tickDuration, TimeUnit unit, int ticksPerWheel) { this(threadFactory, tickDuration, unit, ticksPerWheel, -1); } @@ -179,8 +180,8 @@ public HashedWheelTimer( * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 */ - public HashedWheelTimer( - ThreadFactory threadFactory, long tickDuration, TimeUnit unit, int ticksPerWheel, long maxPendingTimeouts) { + public HashedWheelTimer(ThreadFactory threadFactory, long tickDuration, TimeUnit unit, int ticksPerWheel, + long maxPendingTimeouts) { if (threadFactory == null) { throw new NullPointerException("threadFactory"); @@ -205,15 +206,15 @@ public HashedWheelTimer( // Prevent overflow. if (this.tickDuration >= Long.MAX_VALUE / wheel.length) { throw new IllegalArgumentException(String.format( - "tickDuration: %d (expected: 0 < tickDuration in nanos < %d", - tickDuration, Long.MAX_VALUE / wheel.length)); + "tickDuration: %d (expected: 0 < tickDuration in nanos < %d", tickDuration, Long.MAX_VALUE + / wheel.length)); } workerThread = threadFactory.newThread(worker); this.maxPendingTimeouts = maxPendingTimeouts; - if (instanceCounter.incrementAndGet() > INSTANCE_COUNT_LIMIT && - warnedTooManyInstances.compareAndSet(false, true)) { + if (instanceCounter.incrementAndGet() > INSTANCE_COUNT_LIMIT + && warnedTooManyInstances.compareAndSet(false, true)) { reportTooManyInstances(); } } @@ -233,12 +234,10 @@ protected void finalize() throws Throwable { private static HashedWheelBucket[] createWheel(int ticksPerWheel) { if (ticksPerWheel <= 0) { - throw new IllegalArgumentException( - "ticksPerWheel must be greater than 0: " + ticksPerWheel); + throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel); } if (ticksPerWheel > 1073741824) { - throw new IllegalArgumentException( - "ticksPerWheel may not be greater than 2^30: " + ticksPerWheel); + throw new IllegalArgumentException("ticksPerWheel may not be greater than 2^30: " + ticksPerWheel); } ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel); @@ -292,10 +291,8 @@ public void start() { @Override public Set stop() { if (Thread.currentThread() == workerThread) { - throw new IllegalStateException( - HashedWheelTimer.class.getSimpleName() + - ".stop() cannot be called from " + - TimerTask.class.getSimpleName()); + throw new IllegalStateException(HashedWheelTimer.class.getSimpleName() + ".stop() cannot be called from " + + TimerTask.class.getSimpleName()); } if (!workerStateUpdater.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) { @@ -340,9 +337,9 @@ public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) { if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) { pendingTimeouts.decrementAndGet(); - throw new RejectedExecutionException("Number of pending timeouts (" - + pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending " - + "timeouts (" + maxPendingTimeouts + ")"); + throw new RejectedExecutionException("Number of pending timeouts (" + pendingTimeoutsCount + + ") is greater than or equal to maximum allowed pending " + + "timeouts (" + maxPendingTimeouts + ")"); } start(); @@ -369,14 +366,14 @@ public long pendingTimeouts() { private static void reportTooManyInstances() { String resourceType = HashedWheelTimer.class.getSimpleName(); - LOG.error("You are creating too many {} instances. {} is a shared resource that must be " + - "reused across the JVM, so that only a few instances are created.", resourceType, resourceType); + LOG.error("You are creating too many {} instances. {} is a shared resource that must be " + + "reused across the JVM, so that only a few instances are created.", resourceType, resourceType); } private final class Worker implements Runnable { private final Set unprocessedTimeouts = new HashSet<>(); - private long tick; + private long tick; @Override public void run() { @@ -395,8 +392,7 @@ public void run() { if (deadline > 0) { int idx = (int) (tick & mask); processCancelledTasks(); - HashedWheelBucket bucket = - wheel[idx]; + HashedWheelBucket bucket = wheel[idx]; transferTimeoutsToBuckets(); bucket.expireTimeouts(deadline); tick++; @@ -509,30 +505,32 @@ public Set unprocessedTimeouts() { private static final class HashedWheelTimeout implements Timeout { - private static final int ST_INIT = 0; - private static final int ST_CANCELLED = 1; - private static final int ST_EXPIRED = 2; - private static final AtomicIntegerFieldUpdater STATE_UPDATER = - AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimeout.class, "state"); + private static final int ST_INIT = 0; + private static final int ST_CANCELLED = 1; + private static final int ST_EXPIRED = 2; + private static final AtomicIntegerFieldUpdater STATE_UPDATER = AtomicIntegerFieldUpdater + .newUpdater( + HashedWheelTimeout.class, + "state"); - private final HashedWheelTimer timer; - private final TimerTask task; - private final long deadline; + private final HashedWheelTimer timer; + private final TimerTask task; + private final long deadline; - @SuppressWarnings({"unused", "FieldMayBeFinal", "RedundantFieldInitialization"}) - private volatile int state = ST_INIT; + @SuppressWarnings({ "unused", "FieldMayBeFinal", "RedundantFieldInitialization" }) + private volatile int state = ST_INIT; // remainingRounds will be calculated and set by Worker.transferTimeoutsToBuckets() before the // HashedWheelTimeout will be added to the correct HashedWheelBucket. - long remainingRounds; + long remainingRounds; // This will be used to chain timeouts in HashedWheelTimerBucket via a double-linked-list. // As only the workerThread will act on it there is no need for synchronization / volatile. - HashedWheelTimeout next; - HashedWheelTimeout prev; + HashedWheelTimeout next; + HashedWheelTimeout prev; // The bucket to which the timeout was added - HashedWheelBucket bucket; + HashedWheelBucket bucket; HashedWheelTimeout(HashedWheelTimer timer, TimerTask task, long deadline) { this.timer = timer; @@ -609,16 +607,12 @@ public String toString() { final long currentTime = System.nanoTime(); long remaining = deadline - currentTime + timer.startTime; - StringBuilder buf = new StringBuilder(192) - .append(getClass().getSimpleName()) - .append('(') - .append("deadline: "); + StringBuilder buf = new StringBuilder(192).append(getClass().getSimpleName()).append('(') + .append("deadline: "); if (remaining > 0) { - buf.append(remaining) - .append(" ns later"); + buf.append(remaining).append(" ns later"); } else if (remaining < 0) { - buf.append(-remaining) - .append(" ns ago"); + buf.append(-remaining).append(" ns ago"); } else { buf.append("now"); } @@ -627,10 +621,7 @@ public String toString() { buf.append(", cancelled"); } - return buf.append(", task: ") - .append(task()) - .append(')') - .toString(); + return buf.append(", task: ").append(task()).append(')').toString(); } } @@ -674,8 +665,8 @@ public void expireTimeouts(long deadline) { timeout.expire(); } else { // The timeout was placed into a wrong slot. This should never happen. - throw new IllegalStateException(String.format( - "timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline)); + throw new IllegalStateException(String.format("timeout.deadline (%d) > deadline (%d)", + timeout.deadline, deadline)); } } else if (timeout.isCancelled()) { next = remove(timeout); diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/conf/ConfigurationEntryTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/conf/ConfigurationEntryTest.java index f718ad071..4b0fe9faf 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/conf/ConfigurationEntryTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/conf/ConfigurationEntryTest.java @@ -37,8 +37,10 @@ public void testStuffMethods() { assertTrue(entry.contains(new PeerId("localhost", 8081))); assertTrue(entry.contains(new PeerId("localhost", 8082))); assertTrue(entry.contains(new PeerId("localhost", 8083))); - assertEquals(entry.listPeers(), new HashSet<>(Arrays.asList(new PeerId("localhost", 8081), - new PeerId("localhost", 8082), new PeerId("localhost", 8083)))); + assertEquals( + entry.listPeers(), + new HashSet<>(Arrays.asList(new PeerId("localhost", 8081), new PeerId("localhost", 8082), new PeerId( + "localhost", 8083)))); } diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/CliServiceTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/CliServiceTest.java index 72a0cd638..2ef3ab9c2 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/CliServiceTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/CliServiceTest.java @@ -193,8 +193,7 @@ public void testSnapshot() throws Exception { public void testGetPeers() throws Exception { PeerId leader = cluster.getLeader().getNodeId().getPeerId(); assertNotNull(leader); - assertArrayEquals(conf.getPeerSet().toArray(), - new HashSet<>(this.cliService.getPeers(groupId, conf)).toArray()); + assertArrayEquals(conf.getPeerSet().toArray(), new HashSet<>(this.cliService.getPeers(groupId, conf)).toArray()); //stop one peer final List peers = this.conf.getPeers(); @@ -204,8 +203,7 @@ public void testGetPeers() throws Exception { leader = cluster.getLeader().getNodeId().getPeerId(); assertNotNull(leader); - assertArrayEquals(conf.getPeerSet().toArray(), - new HashSet<>(this.cliService.getPeers(groupId, conf)).toArray()); + assertArrayEquals(conf.getPeerSet().toArray(), new HashSet<>(this.cliService.getPeers(groupId, conf)).toArray()); cluster.stopAll(); diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/MockStateMachine.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/MockStateMachine.java index 6be42f4c9..697459914 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/MockStateMachine.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/MockStateMachine.java @@ -41,15 +41,15 @@ public class MockStateMachine extends StateMachineAdapter { private final Lock lock = new ReentrantLock(); - private volatile int onStartFollowingTimes = 0; - private volatile int onStopFollowingTimes = 0; - private volatile long leaderTerm = -1; - private volatile long appliedIndex = -1; - private volatile long snapshotIndex = -1L; + private volatile int onStartFollowingTimes = 0; + private volatile int onStopFollowingTimes = 0; + private volatile long leaderTerm = -1; + private volatile long appliedIndex = -1; + private volatile long snapshotIndex = -1L; private final List logs = new ArrayList<>(); - private final Endpoint address; - private volatile int saveSnapshotTimes; - private volatile int loadSnapshotTimes; + private final Endpoint address; + private volatile int saveSnapshotTimes; + private volatile int loadSnapshotTimes; public Endpoint getAddress() { return this.address; diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReadOnlyServiceTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReadOnlyServiceTest.java index e5211e8f1..d54f1c63d 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReadOnlyServiceTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReadOnlyServiceTest.java @@ -261,8 +261,7 @@ public void run(Status status, long index, byte[] reqCtx) { state.setIndex(1); states.add(state); final ReadIndexStatus readIndexStatus = new ReadIndexStatus(states, null, 1); - this.readOnlyServiceImpl.getPendingNotifyStatus().put(1L, - Arrays.asList(readIndexStatus)); + this.readOnlyServiceImpl.getPendingNotifyStatus().put(1L, Arrays.asList(readIndexStatus)); this.readOnlyServiceImpl.onApplied(2); latch.await(); diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java index 70727c99b..165fa75d5 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java @@ -112,7 +112,7 @@ public void setup() { private void mockSendEmptyEntries() { final RpcRequests.AppendEntriesRequest request = createEmptyEntriesRequestt(); Mockito.when(this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(request), eq(-1), Mockito.any())) - .thenReturn(new FutureImpl<>()); + .thenReturn(new FutureImpl<>()); } private RpcRequests.AppendEntriesRequest createEmptyEntriesRequestt() { @@ -193,23 +193,23 @@ public void testOnRpcReturnedMoreLogs() { assertEquals(11, r.getRealNextIndex()); final RpcRequests.AppendEntriesRequest request = createEmptyEntriesRequestt(); final RpcRequests.AppendEntriesResponse response = RpcRequests.AppendEntriesResponse.newBuilder(). // - setSuccess(false). // - setLastLogIndex(12).setTerm(1).build(); + setSuccess(false). // + setLastLogIndex(12).setTerm(1).build(); id.unlock(); final Future rpcInFly = r.getRpcInFly(); assertNotNull(rpcInFly); Mockito.when(this.logManager.getTerm(9)).thenReturn(1L); final RpcRequests.AppendEntriesRequest newReq = RpcRequests.AppendEntriesRequest.newBuilder(). // - setGroupId("test"). // - setServerId(new PeerId("localhost", 8082).toString()). // - setPeerId(this.peerId.toString()). // - setTerm(1). // - setPrevLogIndex(9). // - setPrevLogTerm(1). // - setCommittedIndex(0).build(); + setGroupId("test"). // + setServerId(new PeerId("localhost", 8082).toString()). // + setPeerId(this.peerId.toString()). // + setTerm(1). // + setPrevLogIndex(9). // + setPrevLogTerm(1). // + setCommittedIndex(0).build(); Mockito.when(this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(newReq), eq(-1), Mockito.any())) - .thenReturn(new FutureImpl<>()); + .thenReturn(new FutureImpl<>()); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, Status.OK(), request, response, 0, 0, System.currentTimeMillis()); @@ -228,23 +228,23 @@ public void testOnRpcReturnedLessLogs() { assertEquals(11, r.getRealNextIndex()); final RpcRequests.AppendEntriesRequest request = createEmptyEntriesRequestt(); final RpcRequests.AppendEntriesResponse response = RpcRequests.AppendEntriesResponse.newBuilder(). // - setSuccess(false). // - setLastLogIndex(8).setTerm(1).build(); + setSuccess(false). // + setLastLogIndex(8).setTerm(1).build(); id.unlock(); final Future rpcInFly = r.getRpcInFly(); assertNotNull(rpcInFly); Mockito.when(this.logManager.getTerm(8)).thenReturn(1L); final RpcRequests.AppendEntriesRequest newReq = RpcRequests.AppendEntriesRequest.newBuilder(). // - setGroupId("test"). // - setServerId(new PeerId("localhost", 8082).toString()). // - setPeerId(this.peerId.toString()). // - setTerm(1). // - setPrevLogIndex(8). // - setPrevLogTerm(1). // - setCommittedIndex(0).build(); + setGroupId("test"). // + setServerId(new PeerId("localhost", 8082).toString()). // + setPeerId(this.peerId.toString()). // + setTerm(1). // + setPrevLogIndex(8). // + setPrevLogTerm(1). // + setCommittedIndex(0).build(); Mockito.when(this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(newReq), eq(-1), Mockito.any())) - .thenReturn(new FutureImpl<>()); + .thenReturn(new FutureImpl<>()); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, Status.OK(), request, response, 0, 0, System.currentTimeMillis()); @@ -337,13 +337,13 @@ public void testContinueSendingEntries() throws Exception { assertNotNull(rpcInFly); final RpcRequests.AppendEntriesRequest.Builder rb = RpcRequests.AppendEntriesRequest.newBuilder(). // - setGroupId("test"). // - setServerId(new PeerId("localhost", 8082).toString()). // - setPeerId(this.peerId.toString()). // - setTerm(1). // - setPrevLogIndex(10). // - setPrevLogTerm(1). // - setCommittedIndex(0); + setGroupId("test"). // + setServerId(new PeerId("localhost", 8082).toString()). // + setPeerId(this.peerId.toString()). // + setTerm(1). // + setPrevLogIndex(10). // + setPrevLogTerm(1). // + setCommittedIndex(0); int totalDataLen = 0; for (int i = 0; i < 10; i++) { @@ -353,13 +353,14 @@ public void testContinueSendingEntries() throws Exception { value.setType(EnumOutter.EntryType.ENTRY_TYPE_DATA); value.setId(new LogId(11 + i, 1)); Mockito.when(this.logManager.getEntry(11 + i)).thenReturn(value); - rb.addEntries(RaftOutter.EntryMeta.newBuilder().setTerm(1).setType(EnumOutter.EntryType.ENTRY_TYPE_DATA).setDataLen(i)); + rb.addEntries(RaftOutter.EntryMeta.newBuilder().setTerm(1).setType(EnumOutter.EntryType.ENTRY_TYPE_DATA) + .setDataLen(i)); } rb.setData(ByteString.copyFrom(new byte[totalDataLen])); final RpcRequests.AppendEntriesRequest request = rb.build(); Mockito.when(this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(request), eq(-1), Mockito.any())) - .thenReturn(new FutureImpl<>()); + .thenReturn(new FutureImpl<>()); assertEquals(11, r.statInfo.firstLogIndex); assertEquals(10, r.statInfo.lastLogIndex); @@ -379,8 +380,9 @@ public void testSetErrorTimeout() throws Exception { id.unlock(); assertNull(r.getHeartbeatInFly()); final RpcRequests.AppendEntriesRequest request = createEmptyEntriesRequestt(); - Mockito.when(this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(request), - eq(opts.getElectionTimeoutMs() / 2), Mockito.any())).thenReturn(new FutureImpl<>()); + Mockito.when( + this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(request), eq(opts.getElectionTimeoutMs() / 2), + Mockito.any())).thenReturn(new FutureImpl<>()); id.setError(RaftError.ETIMEDOUT.getNumber()); Thread.sleep(opts.getElectionTimeoutMs() + 1000); assertNotNull(r.getHeartbeatInFly()); @@ -459,8 +461,10 @@ public void testTransferLeadershipSendTimeoutNow() { assertNull(r.getTimeoutNowInFly()); final RpcRequests.TimeoutNowRequest request = createTimeoutnowRequest(); - Mockito.when(this.rpcService.timeoutNow(Matchers.eq(opts.getPeerId().getEndpoint()), eq(request), eq(-1), Mockito.any())) - .thenReturn(new FutureImpl<>()); + Mockito + .when( + this.rpcService.timeoutNow(Matchers.eq(opts.getPeerId().getEndpoint()), eq(request), eq(-1), + Mockito.any())).thenReturn(new FutureImpl<>()); assertTrue(Replicator.transferLeadership(this.id, 10)); assertEquals(0, r.getTimeoutNowIndex()); @@ -474,8 +478,9 @@ public void testSendHeartbeat() { assertNull(r.getHeartbeatInFly()); final RpcRequests.AppendEntriesRequest request = createEmptyEntriesRequestt(); - Mockito.when(this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(request), - eq(this.opts.getElectionTimeoutMs() / 2), Mockito.any())).thenReturn(new FutureImpl<>()); + Mockito.when( + this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(request), + eq(this.opts.getElectionTimeoutMs() / 2), Mockito.any())).thenReturn(new FutureImpl<>()); Replicator.sendHeartbeat(id, new RpcResponseClosureAdapter() { @Override @@ -551,7 +556,8 @@ public void testInstallSnapshot() { Mockito.when(this.snapshotStorage.open()).thenReturn(reader); final String uri = "remote://localhost:8081/99"; Mockito.when(reader.generateURIForCopy()).thenReturn(uri); - final RaftOutter.SnapshotMeta meta = RaftOutter.SnapshotMeta.newBuilder().setLastIncludedIndex(11).setLastIncludedTerm(1).build(); + final RaftOutter.SnapshotMeta meta = RaftOutter.SnapshotMeta.newBuilder().setLastIncludedIndex(11) + .setLastIncludedTerm(1).build(); Mockito.when(reader.load()).thenReturn(meta); assertEquals(0, r.statInfo.lastLogIncluded); @@ -565,8 +571,10 @@ public void testInstallSnapshot() { rb.setMeta(meta); rb.setUri(uri); - Mockito.when(this.rpcService.installSnapshot(Matchers.eq(opts.getPeerId().getEndpoint()), eq(rb.build()), Mockito.any())) - .thenReturn(new FutureImpl<>()); + Mockito + .when( + this.rpcService.installSnapshot(Matchers.eq(opts.getPeerId().getEndpoint()), eq(rb.build()), + Mockito.any())).thenReturn(new FutureImpl<>()); r.installSnapshot(); assertNotNull(r.getRpcInFly()); @@ -670,7 +678,7 @@ public void testOnRpcReturnedOutOfOrder() { private void mockSendEntries(int n) { final RpcRequests.AppendEntriesRequest request = createEntriesRequest(n); Mockito.when(this.rpcService.appendEntries(eq(peerId.getEndpoint()), eq(request), eq(-1), Mockito.any())) - .thenReturn(new FutureImpl<>()); + .thenReturn(new FutureImpl<>()); } private RpcRequests.AppendEntriesRequest createEntriesRequest(int n) { diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java index 75d3af730..2c811b34e 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java @@ -48,7 +48,7 @@ */ public class TestCluster { private final String dataPath; - private final String name; // groupId + private final String name; // groupId private final List peers; private final List nodes; private final List fsms; @@ -82,13 +82,13 @@ public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotInterv return this.start(listenAddr, emptyPeers, snapshotIntervalSecs, false); } - public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotIntervalSecs, - boolean enableMetrics) throws IOException { + public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotIntervalSecs, boolean enableMetrics) + throws IOException { return this.start(listenAddr, emptyPeers, snapshotIntervalSecs, enableMetrics, null); } - public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotIntervalSecs, - boolean enableMetrics, SnapshotThrottle snapshotThrottle) throws IOException { + public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotIntervalSecs, boolean enableMetrics, + SnapshotThrottle snapshotThrottle) throws IOException { if (this.serverMap.get(listenAddr.toString()) != null) { return true; @@ -112,7 +112,8 @@ public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotInterv } final RpcServer rpcServer = RaftRpcServerFactory.createRaftRpcServer(listenAddr); - final RaftGroupService server = new RaftGroupService(this.name, new PeerId(listenAddr, 0), nodeOptions, rpcServer); + final RaftGroupService server = new RaftGroupService(this.name, new PeerId(listenAddr, 0), nodeOptions, + rpcServer); lock.lock(); try { diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/rpc/AbstractBoltClientServiceTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/rpc/AbstractBoltClientServiceTest.java index 946ebc244..c89ef1fe9 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/rpc/AbstractBoltClientServiceTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/rpc/AbstractBoltClientServiceTest.java @@ -130,8 +130,8 @@ public void testInvokeWithDoneOK() throws Exception { MockRpcResponseClosure done = new MockRpcResponseClosure<>(); Future future = this.clientService.invokeWithDone(this.endpoint, request, done, -1); Url rpcUrl = this.rpcAddressParser.parse(this.endpoint.toString()); - Mockito.verify(this.rpcClient).invokeWithCallback(eq(rpcUrl), eq(request), - callbackArg.capture(), eq(this.rpcOptions.getRpcDefaultTimeout())); + Mockito.verify(this.rpcClient).invokeWithCallback(eq(rpcUrl), eq(request), callbackArg.capture(), + eq(this.rpcOptions.getRpcDefaultTimeout())); InvokeCallback cb = callbackArg.getValue(); assertNotNull(cb); assertNotNull(future); @@ -159,8 +159,11 @@ public void testInvokeWithDoneException() throws Exception { PingRequest request = TestUtils.createPingRequest(); Url rpcUrl = this.rpcAddressParser.parse(this.endpoint.toString()); - Mockito.doThrow(new RemotingException()).when(this.rpcClient).invokeWithCallback(eq(rpcUrl), - eq(request), callbackArg.capture(), eq(this.rpcOptions.getRpcDefaultTimeout())); + Mockito + .doThrow(new RemotingException()) + .when(this.rpcClient) + .invokeWithCallback(eq(rpcUrl), eq(request), callbackArg.capture(), + eq(this.rpcOptions.getRpcDefaultTimeout())); MockRpcResponseClosure done = new MockRpcResponseClosure<>(); Future future = this.clientService.invokeWithDone(this.endpoint, request, done, -1); @@ -190,8 +193,8 @@ public void testInvokeWithDoneOnException() throws Exception { MockRpcResponseClosure done = new MockRpcResponseClosure<>(); Future future = this.clientService.invokeWithDone(this.endpoint, request, done, -1); Url rpcUrl = this.rpcAddressParser.parse(this.endpoint.toString()); - Mockito.verify(this.rpcClient).invokeWithCallback(eq(rpcUrl), eq(request), - callbackArg.capture(), eq(this.rpcOptions.getRpcDefaultTimeout())); + Mockito.verify(this.rpcClient).invokeWithCallback(eq(rpcUrl), eq(request), callbackArg.capture(), + eq(this.rpcOptions.getRpcDefaultTimeout())); InvokeCallback cb = callbackArg.getValue(); assertNotNull(cb); assertNotNull(future); diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/SnapshotExecutorTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/SnapshotExecutorTest.java index 21c51692b..db999d584 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/SnapshotExecutorTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/SnapshotExecutorTest.java @@ -151,13 +151,14 @@ public void testInstallSnapshot() throws Exception { final FutureImpl future = new FutureImpl<>(); final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99) - .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) - .setReadPartly(true); + .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) + .setReadPartly(true); //mock get metadata ArgumentCaptor argument = ArgumentCaptor.forClass(RpcResponseClosure.class); - Mockito.when(this.raftClientService.getFile(eq(new Endpoint("localhost", 8080)), eq(rb.build()), - eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); + Mockito.when( + this.raftClientService.getFile(eq(new Endpoint("localhost", 8080)), eq(rb.build()), + eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); Utils.runInThread(new Runnable() { @Override @@ -178,8 +179,9 @@ public void run() { argument = ArgumentCaptor.forClass(RpcResponseClosure.class); rb.setFilename("testFile"); rb.setCount(this.raftOptions.getMaxByteCountPerRpc()); - Mockito.when(this.raftClientService.getFile(eq(new Endpoint("localhost", 8080)), eq(rb.build()), - eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); + Mockito.when( + this.raftClientService.getFile(eq(new Endpoint("localhost", 8080)), eq(rb.build()), + eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); closure.run(Status.OK()); @@ -217,13 +219,14 @@ public void testInterruptInstallaling() throws Exception { final FutureImpl future = new FutureImpl<>(); final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99) - .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) - .setReadPartly(true); + .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) + .setReadPartly(true); //mock get metadata final ArgumentCaptor argument = ArgumentCaptor.forClass(RpcResponseClosure.class); - Mockito.when(this.raftClientService.getFile(eq(new Endpoint("localhost", 8080)), eq(rb.build()), - eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); + Mockito.when( + this.raftClientService.getFile(eq(new Endpoint("localhost", 8080)), eq(rb.build()), + eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); Utils.runInThread(new Runnable() { @Override diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopierTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopierTest.java index 96b342d41..94bcf0267 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopierTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopierTest.java @@ -111,13 +111,14 @@ public void teardown() throws Exception { public void testCancelByRemote() throws Exception { final FutureImpl future = new FutureImpl<>(); final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99) - .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) - .setReadPartly(true); + .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) + .setReadPartly(true); //mock get metadata final ArgumentCaptor argument = ArgumentCaptor.forClass(RpcResponseClosure.class); - Mockito.when(this.raftClientService.getFile(eq(new Endpoint("localhost", 8081)), eq(rb.build()), - eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); + Mockito.when( + this.raftClientService.getFile(eq(new Endpoint("localhost", 8081)), eq(rb.build()), + eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); this.copier.start(); Thread.sleep(500); final RpcResponseClosure closure = argument.getValue(); @@ -136,13 +137,14 @@ public void testCancelByRemote() throws Exception { public void testInterrupt() throws Exception { final FutureImpl future = new FutureImpl<>(); final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99) - .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) - .setReadPartly(true); + .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) + .setReadPartly(true); //mock get metadata final ArgumentCaptor argument = ArgumentCaptor.forClass(RpcResponseClosure.class); - Mockito.when(this.raftClientService.getFile(eq(new Endpoint("localhost", 8081)), eq(rb.build()), - eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); + Mockito.when( + this.raftClientService.getFile(eq(new Endpoint("localhost", 8081)), eq(rb.build()), + eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); this.copier.start(); Thread.sleep(100); @@ -166,14 +168,14 @@ public void run() { public void testStartJoinFinishOK() throws Exception { final FutureImpl future = new FutureImpl<>(); final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99) - .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE) - .setCount(Integer.MAX_VALUE).setOffset(0).setReadPartly(true); + .setFilename(Snapshot.JRAFT_SNAPSHOT_META_FILE).setCount(Integer.MAX_VALUE).setOffset(0) + .setReadPartly(true); //mock get metadata ArgumentCaptor argument = ArgumentCaptor.forClass(RpcResponseClosure.class); - Mockito.when(this.raftClientService.getFile(eq(new Endpoint("localhost", 8081)), eq(rb.build()), - eq(this.copyOpts.getTimeoutMs()), - argument.capture())).thenReturn(future); + Mockito.when( + this.raftClientService.getFile(eq(new Endpoint("localhost", 8081)), eq(rb.build()), + eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); this.copier.start(); Thread.sleep(500); RpcResponseClosure closure = argument.getValue(); @@ -185,9 +187,9 @@ public void testStartJoinFinishOK() throws Exception { argument = ArgumentCaptor.forClass(RpcResponseClosure.class); rb.setFilename("testFile"); rb.setCount(this.raftOptions.getMaxByteCountPerRpc()); - Mockito.when(this.raftClientService.getFile(eq(new Endpoint("localhost", 8081)), eq(rb.build()), - eq(this.copyOpts.getTimeoutMs()), - argument.capture())).thenReturn(future); + Mockito.when( + this.raftClientService.getFile(eq(new Endpoint("localhost", 8081)), eq(rb.build()), + eq(this.copyOpts.getTimeoutMs()), argument.capture())).thenReturn(future); closure.run(Status.OK()); diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSessionTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSessionTest.java index 9b5fab309..f2e9ec1de 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSessionTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSessionTest.java @@ -125,14 +125,14 @@ public void testOnRpcReturnedOK() { this.session.setDestBuf(bufRef); final FutureImpl future = new FutureImpl<>(); - final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99).setFilename("data") - .setCount(Integer.MAX_VALUE).setOffset(100).setReadPartly(true); - Mockito.when(this.rpcService.getFile(this.address, rb.build(), this.copyOpts.getTimeoutMs(), session.getDone())) - .thenReturn(future); - - this.session.onRpcReturned(Status.OK(), - RpcRequests.GetFileResponse.newBuilder().setReadSize(100).setEof(false).setData(ByteString.copyFrom(new byte[100])) - .build()); + final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99) + .setFilename("data").setCount(Integer.MAX_VALUE).setOffset(100).setReadPartly(true); + Mockito + .when(this.rpcService.getFile(this.address, rb.build(), this.copyOpts.getTimeoutMs(), session.getDone())) + .thenReturn(future); + + this.session.onRpcReturned(Status.OK(), RpcRequests.GetFileResponse.newBuilder().setReadSize(100).setEof(false) + .setData(ByteString.copyFrom(new byte[100])).build()); assertEquals(100, bufRef.capacity()); assertEquals(100, bufRef.getBuffer().position()); @@ -149,10 +149,11 @@ public void testOnRpcReturnedRetry() throws Exception { this.session.setDestBuf(bufRef); final FutureImpl future = new FutureImpl<>(); - final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99).setFilename("data") - .setCount(Integer.MAX_VALUE).setOffset(0).setReadPartly(true); - Mockito.when(this.rpcService.getFile(this.address, rb.build(), this.copyOpts.getTimeoutMs(), session.getDone())) - .thenReturn(future); + final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99) + .setFilename("data").setCount(Integer.MAX_VALUE).setOffset(0).setReadPartly(true); + Mockito + .when(this.rpcService.getFile(this.address, rb.build(), this.copyOpts.getTimeoutMs(), session.getDone())) + .thenReturn(future); this.session.onRpcReturned(new Status(RaftError.EINTR, "test"), null); assertNotNull(this.session.getTimer()); @@ -165,10 +166,11 @@ public void testOnRpcReturnedRetry() throws Exception { private void sendNextRpc(int maxCount) { assertNull(this.session.getRpcCall()); final FutureImpl future = new FutureImpl<>(); - final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99).setFilename("data") - .setCount(maxCount).setOffset(0).setReadPartly(true); - Mockito.when(this.rpcService.getFile(this.address, rb.build(), this.copyOpts.getTimeoutMs(), session.getDone())) - .thenReturn(future); + final RpcRequests.GetFileRequest.Builder rb = RpcRequests.GetFileRequest.newBuilder().setReaderId(99) + .setFilename("data").setCount(maxCount).setOffset(0).setReadPartly(true); + Mockito + .when(this.rpcService.getFile(this.address, rb.build(), this.copyOpts.getTimeoutMs(), session.getDone())) + .thenReturn(future); this.session.sendNextRpc(); assertNotNull(this.session.getRpcCall()); assertSame(future, this.session.getRpcCall()); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/KVCommandProcessor.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/KVCommandProcessor.java index b9b6ff207..e45fedf08 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/KVCommandProcessor.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/KVCommandProcessor.java @@ -62,8 +62,8 @@ public KVCommandProcessor(Class reqClazz, StoreEngine storeEngine) { @Override public void handleRequest(final BizContext bizCtx, final AsyncContext asyncCtx, final T request) { Requires.requireNonNull(request, "request"); - final RequestProcessClosure> closure = - new RequestProcessClosure<>(request, bizCtx, asyncCtx); + final RequestProcessClosure> closure = new RequestProcessClosure<>(request, + bizCtx, asyncCtx); final RegionKVService regionKVService = this.storeEngine.getRegionKVService(request.getRegionId()); if (regionKVService == null) { final NoRegionFoundResponse noRegion = new NoRegionFoundResponse(); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaIterator.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaIterator.java index 8669bc31d..135c78020 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaIterator.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaIterator.java @@ -41,7 +41,8 @@ public class DefaultRheaIterator implements RheaIterator { private byte[] cursorKey; - public DefaultRheaIterator(DefaultRheaKVStore rheaKVStore, byte[] startKey, byte[] endKey, int bufSize, boolean readOnlySafe) { + public DefaultRheaIterator(DefaultRheaKVStore rheaKVStore, byte[] startKey, byte[] endKey, int bufSize, + boolean readOnlySafe) { this.rheaKVStore = rheaKVStore; this.pdClient = rheaKVStore.getPlacementDriverClient(); this.startKey = BytesUtil.nullToEmpty(startKey); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaKVRpcService.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaKVRpcService.java index 96a32c578..0cc45f287 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaKVRpcService.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaKVRpcService.java @@ -172,8 +172,7 @@ private ThreadPoolExecutor createRpcCallbackExecutor(final int corePoolSize, fin return null; } final String name = "rpc-callback"; - return ThreadPoolUtil.newThreadPool(name, true, corePoolSize, maximumPoolSize, 120L, - new ArrayBlockingQueue<>(queueCapacity), new NamedThreadFactory(name, true), - new CallerRunsPolicyWithReport(name)); + return ThreadPoolUtil.newThreadPool(name, true, corePoolSize, maximumPoolSize, 120L, new ArrayBlockingQueue<>( + queueCapacity), new NamedThreadFactory(name, true), new CallerRunsPolicyWithReport(name)); } } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaKVStore.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaKVStore.java index e31b0fc4d..cd0991a69 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaKVStore.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/DefaultRheaKVStore.java @@ -586,13 +586,12 @@ public List singleRegionScan(final byte[] startKey, final byte[] endKey checkState(); final byte[] realStartKey = BytesUtil.nullToEmpty(startKey); if (endKey != null) { - Requires.requireTrue(BytesUtil.compare(realStartKey, endKey) < 0, - "startKey must < endKey"); + Requires.requireTrue(BytesUtil.compare(realStartKey, endKey) < 0, "startKey must < endKey"); } Requires.requireTrue(limit > 0, "limit must > 0"); final CompletableFuture> future = new CompletableFuture<>(); - internalSingleRegionScan(realStartKey, endKey, limit, readOnlySafe, future, this.failoverRetries, - null, this.onlyLeaderRead); + internalSingleRegionScan(realStartKey, endKey, limit, readOnlySafe, future, this.failoverRetries, null, + this.onlyLeaderRead); return FutureHelper.get(future, this.futureTimeoutMillis); } @@ -845,8 +844,7 @@ public CompletableFuture merge(final String key, final String value) { Requires.requireNonNull(key, "key"); Requires.requireNonNull(value, "value"); final CompletableFuture future = new CompletableFuture<>(); - internalMerge(BytesUtil.writeUtf8(key), BytesUtil.writeUtf8(value), future, this.failoverRetries, - null); + internalMerge(BytesUtil.writeUtf8(key), BytesUtil.writeUtf8(value), future, this.failoverRetries, null); return future; } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/RegionRouteTable.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/RegionRouteTable.java index a82eeefd4..b33442ecb 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/RegionRouteTable.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/RegionRouteTable.java @@ -87,13 +87,13 @@ */ public class RegionRouteTable { - private static final Logger LOG = LoggerFactory.getLogger(RegionRouteTable.class); + private static final Logger LOG = LoggerFactory.getLogger(RegionRouteTable.class); - private static final Comparator keyBytesComparator = BytesUtil.getDefaultByteArrayComparator(); + private static final Comparator keyBytesComparator = BytesUtil.getDefaultByteArrayComparator(); - private final StampedLock stampedLock = new StampedLock(); - private final NavigableMap rangeTable = new TreeMap<>(keyBytesComparator); - private final Map regionTable = Maps.newHashMap(); + private final StampedLock stampedLock = new StampedLock(); + private final NavigableMap rangeTable = new TreeMap<>(keyBytesComparator); + private final Map regionTable = Maps.newHashMap(); public Region getRegionById(final long regionId) { final StampedLock stampedLock = this.stampedLock; @@ -144,15 +144,13 @@ public void splitRegion(final long leftId, final Region right) { final byte[] rightEndKey = right.getEndKey(); Requires.requireNonNull(rightStartKey, "rightStartKey"); Requires.requireTrue(BytesUtil.compare(leftStartKey, rightStartKey) < 0, - "leftStartKey must < rightStartKey"); + "leftStartKey must < rightStartKey"); if (leftEndKey == null || rightEndKey == null) { - Requires.requireTrue(leftEndKey == rightEndKey, - "leftEndKey must == rightEndKey"); + Requires.requireTrue(leftEndKey == rightEndKey, "leftEndKey must == rightEndKey"); } else { - Requires.requireTrue(BytesUtil.compare(leftEndKey, rightEndKey) == 0, - "leftEndKey must == rightEndKey"); + Requires.requireTrue(BytesUtil.compare(leftEndKey, rightEndKey) == 0, "leftEndKey must == rightEndKey"); Requires.requireTrue(BytesUtil.compare(rightStartKey, rightEndKey) < 0, - "rightStartKey must < rightEndKey"); + "rightStartKey must < rightEndKey"); } final RegionEpoch leftEpoch = left.getRegionEpoch(); leftEpoch.setVersion(leftEpoch.getVersion() + 1); @@ -198,7 +196,7 @@ private Region findRegionByKeyWithoutLock(final byte[] key) { final Map.Entry entry = this.rangeTable.floorEntry(key); if (entry == null) { reportFail(key); - throw reject(key,"fail to find region by key"); + throw reject(key, "fail to find region by key"); } return this.regionTable.get(entry.getValue()); } @@ -259,7 +257,7 @@ public List findRegionsByKeyRange(final byte[] startKey, final byte[] en final Map.Entry headEntry = this.rangeTable.floorEntry(realStartKey); if (headEntry == null) { reportFail(startKey); - throw reject(startKey,"fail to find region by startKey"); + throw reject(startKey, "fail to find region by startKey"); } regionList.add(safeCopy(this.regionTable.get(headEntry.getValue()))); for (final Long regionId : subRegionMap.values()) { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/DefaultPlacementDriverRpcService.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/DefaultPlacementDriverRpcService.java index 09d23390f..6fcbf87e3 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/DefaultPlacementDriverRpcService.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/DefaultPlacementDriverRpcService.java @@ -133,8 +133,7 @@ private ThreadPoolExecutor createRpcCallbackExecutor(final int corePoolSize, fin return null; } final String name = "pd-rpc-callback"; - return ThreadPoolUtil.newThreadPool(name, true, corePoolSize, maximumPoolSize, 120L, - new ArrayBlockingQueue<>(queueCapacity), new NamedThreadFactory(name, true), - new CallerRunsPolicyWithReport(name)); + return ThreadPoolUtil.newThreadPool(name, true, corePoolSize, maximumPoolSize, 120L, new ArrayBlockingQueue<>( + queueCapacity), new NamedThreadFactory(name, true), new CallerRunsPolicyWithReport(name)); } } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/HeartbeatSender.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/HeartbeatSender.java index 5acafd4d3..9675d43f5 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/HeartbeatSender.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/client/pd/HeartbeatSender.java @@ -99,8 +99,7 @@ public synchronized boolean init(final HeartbeatOptions opts) { } final String name = "heartbeat-callback"; this.heartbeatRpcCallbackExecutor = ThreadPoolUtil.newThreadPool(name, true, 4, 4, 120L, - new ArrayBlockingQueue<>(1024), new NamedThreadFactory(name, true), - new DiscardOldPolicyWithReport(name)); + new ArrayBlockingQueue<>(1024), new NamedThreadFactory(name, true), new DiscardOldPolicyWithReport(name)); final long storeHeartbeatIntervalSeconds = opts.getStoreHeartbeatIntervalSeconds(); final long regionHeartbeatIntervalSeconds = opts.getRegionHeartbeatIntervalSeconds(); if (storeHeartbeatIntervalSeconds <= 0) { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksStatisticsCollector.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksStatisticsCollector.java index 552675138..c24e88ccf 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksStatisticsCollector.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksStatisticsCollector.java @@ -43,15 +43,15 @@ */ public class RocksStatisticsCollector { - private final CopyOnWriteArrayList statsCollectorInputList = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList statsCollectorInputList = new CopyOnWriteArrayList<>(); private final long statsCollectionIntervalInMillis; private final ExecutorService executorService; - private volatile boolean isRunning = true; + private volatile boolean isRunning = true; public RocksStatisticsCollector(final long statsCollectionIntervalInMillis) { this.statsCollectionIntervalInMillis = statsCollectionIntervalInMillis; - this.executorService = Executors - .newSingleThreadExecutor(new NamedThreadFactory("rocks-statistics-collector", true)); + this.executorService = Executors.newSingleThreadExecutor(new NamedThreadFactory("rocks-statistics-collector", + true)); } public void start() { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/KVStoreStateMachine.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/KVStoreStateMachine.java index 41c4fb275..e9d8a03df 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/KVStoreStateMachine.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/KVStoreStateMachine.java @@ -127,9 +127,8 @@ private void batchApplyAndRecycle(final byte opByte, final KVStateOutputList kvS } // metrics: op qps - final Meter opApplyMeter = KVMetrics.meter(STATE_MACHINE_APPLY_QPS, - String.valueOf(this.regionId), - KVOperation.opName(opByte)); + final Meter opApplyMeter = KVMetrics.meter(STATE_MACHINE_APPLY_QPS, String.valueOf(this.regionId), + KVOperation.opName(opByte)); final int size = kvStates.size(); opApplyMeter.mark(size); this.batchWriteHistogram.update(size); @@ -241,8 +240,8 @@ public boolean onSnapshotLoad(final SnapshotReader reader) { private void doCompressSnapshot(final SnapshotWriter writer, final LocalFileMeta meta, final Closure done) { final String backupPath = writer.getPath() + File.separator + SNAPSHOT_DIR; try { - try (final ZipOutputStream out = new ZipOutputStream( - new FileOutputStream(writer.getPath() + File.separator + SNAPSHOT_ARCHIVE))) { + try (final ZipOutputStream out = new ZipOutputStream(new FileOutputStream(writer.getPath() + File.separator + + SNAPSHOT_ARCHIVE))) { ZipUtil.compressDirectoryToZipFile(writer.getPath(), SNAPSHOT_DIR, out); } if (writer.addFile(SNAPSHOT_ARCHIVE, meta)) { @@ -252,8 +251,7 @@ private void doCompressSnapshot(final SnapshotWriter writer, final LocalFileMeta } } catch (final Throwable t) { LOG.error("Fail to save snapshot at {}, {}.", backupPath, StackTraceUtil.stackTrace(t)); - done.run(new Status(RaftError.EIO, "Fail to save snapshot at %s, error is %s", backupPath, - t.getMessage())); + done.run(new Status(RaftError.EIO, "Fail to save snapshot at %s, error is %s", backupPath, t.getMessage())); } } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java index f4f0bcffd..483834212 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java @@ -696,10 +696,11 @@ public long getDatabaseVersion() { return this.databaseVersion.get(); } - private void writeToFile(final String rootPath, final String fileName, final Persistence persist) throws Exception { + private void writeToFile(final String rootPath, final String fileName, final Persistence persist) + throws Exception { final Path path = Paths.get(rootPath, fileName); try (final FileOutputStream out = new FileOutputStream(path.toFile()); - final BufferedOutputStream bufOutput = new BufferedOutputStream(out)) { + final BufferedOutputStream bufOutput = new BufferedOutputStream(out)) { final byte[] bytes = this.serializer.writeObject(persist); final byte[] lenBytes = new byte[4]; Bits.putInt(lenBytes, 0, bytes.length); @@ -717,19 +718,19 @@ private T readFromFile(final String rootPath, final String fileName, final C throw new NoSuchFieldException(path.toString()); } try (final FileInputStream in = new FileInputStream(file); - final BufferedInputStream bufInput = new BufferedInputStream(in)) { + final BufferedInputStream bufInput = new BufferedInputStream(in)) { final byte[] lenBytes = new byte[4]; int read = bufInput.read(lenBytes); if (read != lenBytes.length) { - throw new IOException("fail to read snapshot file length , expects " - + lenBytes.length + " bytes, but read " + read); + throw new IOException("fail to read snapshot file length, expects " + lenBytes.length + + " bytes, but read " + read); } final int len = Bits.getInt(lenBytes, 0); final byte[] bytes = new byte[len]; read = bufInput.read(bytes); if (read != bytes.length) { - throw new IOException("fail to read snapshot file , expects " - + bytes.length + " bytes, but read " + read); + throw new IOException("fail to read snapshot file, expects " + bytes.length + " bytes, but read " + + read); } return this.serializer.readObject(bytes, clazz); } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java index b5b7ac8bd..9f2705f89 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java @@ -310,8 +310,8 @@ public void scan(final byte[] startKey, final byte[] endKey, final int limit, } setSuccess(closure, entries); } catch (final Exception e) { - LOG.error("Fail to [SCAN], range: ['[{}, {})'], {}.", - Arrays.toString(startKey), Arrays.toString(endKey), StackTraceUtil.stackTrace(e)); + LOG.error("Fail to [SCAN], range: ['[{}, {})'], {}.", Arrays.toString(startKey), Arrays.toString(endKey), + StackTraceUtil.stackTrace(e)); setFailure(closure, "Fail to [SCAN]"); } finally { readLock.unlock(); @@ -1063,15 +1063,15 @@ public void createSstFiles(final EnumMap sstFileTable, fi readLock.lock(); final Snapshot snapshot = this.db.getSnapshot(); try (final ReadOptions readOptions = new ReadOptions(); - final EnvOptions envOptions = new EnvOptions(); - final Options options = new Options().setMergeOperator(this.mergeOperator)) { + final EnvOptions envOptions = new EnvOptions(); + final Options options = new Options().setMergeOperator(this.mergeOperator)) { readOptions.setSnapshot(snapshot); for (final Map.Entry entry : sstFileTable.entrySet()) { final SstColumnFamily sstColumnFamily = entry.getKey(); final File sstFile = entry.getValue(); final ColumnFamilyHandle columnFamilyHandle = findColumnFamilyHandle(sstColumnFamily); try (final RocksIterator it = this.db.newIterator(columnFamilyHandle, readOptions); - final SstFileWriter sstFileWriter = new SstFileWriter(envOptions, options)) { + final SstFileWriter sstFileWriter = new SstFileWriter(envOptions, options)) { if (startKey == null) { it.seekToFirst(); } else { @@ -1166,17 +1166,13 @@ private void restoreBackup(final String backupDBPath, final LocalFileMeta meta) writeLock.lock(); closeRocksDB(); try (final BackupableDBOptions options = createBackupDBOptions(backupDBPath); - final RestoreOptions restoreOptions = new RestoreOptions(false); - final BackupEngine backupEngine = BackupEngine.open(this.options.getEnv(), options)) { + final RestoreOptions restoreOptions = new RestoreOptions(false); + final BackupEngine backupEngine = BackupEngine.open(this.options.getEnv(), options)) { final ByteString userMeta = meta.getUserMeta(); - final RocksDBBackupInfo rocksBackupInfo = this.serializer - .readObject(userMeta.toByteArray(), RocksDBBackupInfo.class); + final RocksDBBackupInfo rocksBackupInfo = this.serializer.readObject(userMeta.toByteArray(), + RocksDBBackupInfo.class); final String dbPath = this.opts.getDbPath(); - backupEngine.restoreDbFromBackup( - rocksBackupInfo.getBackupId(), - dbPath, - dbPath, - restoreOptions); + backupEngine.restoreDbFromBackup(rocksBackupInfo.getBackupId(), dbPath, dbPath, restoreOptions); LOG.info("Restored rocksDB from {} with {}.", backupDBPath, rocksBackupInfo); // reopen the db openRocksDB(this.opts); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/JvmTools.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/JvmTools.java index 3a413dab2..d7735c55f 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/JvmTools.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/JvmTools.java @@ -43,15 +43,8 @@ public static List jStack() throws Exception { final Thread thread = entry.getKey(); final StackTraceElement[] stackTraces = entry.getValue(); - stackList.add( - String.format( - "\"%s\" tid=%s isDaemon=%s priority=%s" + Constants.NEWLINE, - thread.getName(), - thread.getId(), - thread.isDaemon(), - thread.getPriority() - ) - ); + stackList.add(String.format("\"%s\" tid=%s isDaemon=%s priority=%s" + Constants.NEWLINE, thread.getName(), + thread.getId(), thread.isDaemon(), thread.getPriority())); stackList.add("java.lang.Thread.State: " + thread.getState() + Constants.NEWLINE); @@ -73,7 +66,7 @@ public static List memoryUsage() throws Exception { final List memoryUsageList = new LinkedList<>(); memoryUsageList.add("********************************** Memory Usage **********************************" - + Constants.NEWLINE); + + Constants.NEWLINE); memoryUsageList.add("Heap Memory Usage: " + heapMemoryUsage.toString() + Constants.NEWLINE); memoryUsageList.add("NonHeap Memory Usage: " + nonHeapMemoryUsage.toString() + Constants.NEWLINE); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/Lists.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/Lists.java index ca2edd3f3..ce2c0f270 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/Lists.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/Lists.java @@ -93,9 +93,8 @@ public static ArrayList newArrayListWithCapacity(final int initialArraySi * changes to {@code fromList} will be reflected in the returned list and vice versa. */ public static List transform(final List fromList, final Function function) { - return (fromList instanceof RandomAccess) - ? new TransformingRandomAccessList<>(fromList, function) - : new TransformingSequentialList<>(fromList, function); + return (fromList instanceof RandomAccess) ? new TransformingRandomAccessList<>(fromList, function) + : new TransformingSequentialList<>(fromList, function); } private static class TransformingRandomAccessList extends AbstractList implements RandomAccess, diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/StackTraceUtil.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/StackTraceUtil.java index 3038c346c..bb8794ced 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/StackTraceUtil.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/StackTraceUtil.java @@ -32,12 +32,11 @@ public static String stackTrace(final Throwable t) { return NULL_STRING; } - try (final ByteArrayOutputStream out = new ByteArrayOutputStream(); - final PrintStream ps = new PrintStream(out)) { + try (final ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(out)) { t.printStackTrace(ps); ps.flush(); return new String(out.toByteArray()); - } catch (final IOException e) { + } catch (final IOException ignored) { // ignored } return NULL_STRING; diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/ZipUtil.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/ZipUtil.java index b1bd3b2d4..ee8f7f6c8 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/ZipUtil.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/ZipUtil.java @@ -36,8 +36,7 @@ */ public final class ZipUtil { - public static void compressDirectoryToZipFile(final String rootDir, - final String sourceDir, + public static void compressDirectoryToZipFile(final String rootDir, final String sourceDir, final ZipOutputStream zos) throws IOException { final String dir = Paths.get(rootDir, sourceDir).toString(); final File[] files = Requires.requireNonNull(new File(dir).listFiles(), "files"); @@ -46,7 +45,8 @@ public static void compressDirectoryToZipFile(final String rootDir, compressDirectoryToZipFile(rootDir, Paths.get(sourceDir, file.getName()).toString(), zos); } else { zos.putNextEntry(new ZipEntry(Paths.get(sourceDir, file.getName()).toString())); - try (final FileInputStream in = new FileInputStream(Paths.get(rootDir, sourceDir, file.getName()).toString())) { + try (final FileInputStream in = new FileInputStream(Paths.get(rootDir, sourceDir, file.getName()) + .toString())) { IOUtils.copy(in, zos); } } diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/concurrent/AbstractRejectedExecutionHandler.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/concurrent/AbstractRejectedExecutionHandler.java index c4c0c7ac2..a64661044 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/concurrent/AbstractRejectedExecutionHandler.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/concurrent/AbstractRejectedExecutionHandler.java @@ -53,7 +53,8 @@ public void dumpJvmInfoIfNeeded() { if (this.dumpNeeded.getAndSet(false)) { final String now = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date()); final String name = this.threadPoolName + "_" + now; - try (final FileOutputStream fileOutput = new FileOutputStream(new File(this.dumpPrefixName + "_dump_" + name + ".log"))) { + try (final FileOutputStream fileOutput = new FileOutputStream(new File(this.dumpPrefixName + "_dump_" + + name + ".log"))) { final List stacks = JvmTools.jStack(); for (final String s : stacks) { diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/internal/Updaters.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/internal/Updaters.java index baded4a17..85a24ff36 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/internal/Updaters.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/util/internal/Updaters.java @@ -32,7 +32,8 @@ public class Updaters { * @param tClass the class of the objects holding the field. * @param fieldName the name of the field to be updated. */ - public static UnsafeIntegerFieldUpdater newIntegerFieldUpdater(final Class tClass, final String fieldName) { + public static UnsafeIntegerFieldUpdater newIntegerFieldUpdater(final Class tClass, + final String fieldName) { try { return new UnsafeIntegerFieldUpdater<>(UnsafeUtil.getUnsafe(), tClass, fieldName); } catch (final Throwable t) { @@ -47,7 +48,8 @@ public static UnsafeIntegerFieldUpdater newIntegerFieldUpdater(final Clas * @param tClass the class of the objects holding the field. * @param fieldName the name of the field to be updated. */ - public static UnsafeLongFieldUpdater newLongFieldUpdater(final Class tClass, final String fieldName) { + public static UnsafeLongFieldUpdater newLongFieldUpdater(final Class tClass, + final String fieldName) { try { return new UnsafeLongFieldUpdater<>(UnsafeUtil.getUnsafe(), tClass, fieldName); } catch (final Throwable t) { @@ -62,7 +64,8 @@ public static UnsafeLongFieldUpdater newLongFieldUpdater(final Class UnsafeReferenceFieldUpdater newReferenceFieldUpdater(final Class tClass, final String fieldName) { + public static UnsafeReferenceFieldUpdater newReferenceFieldUpdater(final Class tClass, + final String fieldName) { try { return new UnsafeReferenceFieldUpdater<>(UnsafeUtil.getUnsafe(), tClass, fieldName); } catch (final Throwable t) { diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java index dfd80878f..f5241c59a 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java @@ -199,8 +199,8 @@ private LocalFileMetaOutter.LocalFileMeta doSlowSnapshotSave(final String snapsh private void doCompressSnapshot(final String path) { try { - try (final ZipOutputStream out = new ZipOutputStream( - new FileOutputStream(path + File.separator + SNAPSHOT_ARCHIVE))) { + try (final ZipOutputStream out = new ZipOutputStream(new FileOutputStream(path + File.separator + + SNAPSHOT_ARCHIVE))) { ZipUtil.compressDirectoryToZipFile(path, SNAPSHOT_DIR, out); } } catch (final Throwable t) { diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java index 79b6a84c3..9b51c6298 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java @@ -39,15 +39,14 @@ */ public class RheaBenchmarkCluster { - private static final String[] CONF = { + private static final String[] CONF = { "jraft-rheakv/rheakv-core/src/test/resources/benchmark/conf/rhea_cluster_1.yaml", "jraft-rheakv/rheakv-core/src/test/resources/benchmark/conf/rhea_cluster_2.yaml", - "jraft-rheakv/rheakv-core/src/test/resources/benchmark/conf/rhea_cluster_3.yaml" - }; + "jraft-rheakv/rheakv-core/src/test/resources/benchmark/conf/rhea_cluster_3.yaml" }; - private volatile String tempDbPath; - private volatile String tempRaftPath; - private CopyOnWriteArrayList stores = new CopyOnWriteArrayList<>(); + private volatile String tempDbPath; + private volatile String tempRaftPath; + private CopyOnWriteArrayList stores = new CopyOnWriteArrayList<>(); protected void start() throws IOException, InterruptedException { SystemPropertyUtil.setProperty(Configs.NETTY_BUFFER_LOW_WATERMARK, Integer.toString(256 * 1024)); @@ -72,9 +71,7 @@ protected void start() throws IOException, InterruptedException { } for (String c : CONF) { ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - final RheaKVStoreOptions opts = mapper.readValue( - new File(c), - RheaKVStoreOptions.class); + final RheaKVStoreOptions opts = mapper.readValue(new File(c), RheaKVStoreOptions.class); RheaKVStore rheaKVStore = new DefaultRheaKVStore(); if (rheaKVStore.init(opts)) { stores.add(rheaKVStore); @@ -85,10 +82,10 @@ protected void start() throws IOException, InterruptedException { PlacementDriverClient pdClient = stores.get(0).getPlacementDriverClient(); Endpoint leader1 = pdClient.getLeader(1, true, 10000); System.out.println("The region 1 leader is: " + leader1); -// Endpoint leader2 = pdClient.getLeader(2, true, 10000); -// System.out.println("The region 2 leader is: " + leader2); -// Endpoint leader3 = pdClient.getLeader(3, true, 10000); -// System.out.println("The region 3 leader is: " + leader3); + // Endpoint leader2 = pdClient.getLeader(2, true, 10000); + // System.out.println("The region 2 leader is: " + leader2); + // Endpoint leader3 = pdClient.getLeader(3, true, 10000); + // System.out.println("The region 3 leader is: " + leader3); } protected void shutdown() throws IOException { diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/chaos/ChaosTestCluster.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/chaos/ChaosTestCluster.java index bb19703ba..e7aeaf68a 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/chaos/ChaosTestCluster.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/chaos/ChaosTestCluster.java @@ -78,25 +78,23 @@ public synchronized void start() { final Configuration conf = new Configuration(this.peerIds); final String initialServerList = conf.toString(); for (final PeerId p : conf.listPeers()) { - final PlacementDriverOptions pdOpts = PlacementDriverOptionsConfigured.newConfigured() - .withFake(true) // use a fake pd - .config(); + final PlacementDriverOptions pdOpts = PlacementDriverOptionsConfigured.newConfigured().withFake(true) // use a fake pd + .config(); final StoreEngineOptions storeOpts = StoreEngineOptionsConfigured.newConfigured() // - .withStorageType(this.storageType) // - .withRocksDBOptions(RocksDBOptionsConfigured.newConfigured().withDbPath(DB_PATH).config()) // - .withRaftDataPath(RAFT_DATA_PATH) // - .withServerAddress(p.getEndpoint()) // - .withLeastKeysOnSplit(10) // - .config(); + .withStorageType(this.storageType) // + .withRocksDBOptions(RocksDBOptionsConfigured.newConfigured().withDbPath(DB_PATH).config()) // + .withRaftDataPath(RAFT_DATA_PATH) // + .withServerAddress(p.getEndpoint()) // + .withLeastKeysOnSplit(10) // + .config(); final RheaKVStoreOptions opts = RheaKVStoreOptionsConfigured.newConfigured() // - .withClusterName(CLUSTER_NAME) // - .withInitialServerList(initialServerList) - .withOnlyLeaderRead(this.onlyLeaderRead) // - .withStoreEngineOptions(storeOpts) // - .withPlacementDriverOptions(pdOpts) // - .withFailoverRetries(10) // - .withFutureTimeoutMillis(TimeUnit.SECONDS.toMillis(30)) // - .config(); + .withClusterName(CLUSTER_NAME) // + .withInitialServerList(initialServerList).withOnlyLeaderRead(this.onlyLeaderRead) // + .withStoreEngineOptions(storeOpts) // + .withPlacementDriverOptions(pdOpts) // + .withFailoverRetries(10) // + .withFutureTimeoutMillis(TimeUnit.SECONDS.toMillis(30)) // + .config(); BatchingOptions batchingOptions = opts.getBatchingOptions(); if (batchingOptions == null) { batchingOptions = new BatchingOptions(); @@ -188,21 +186,19 @@ public synchronized void addPeer(final PeerId peerId) { this.peerIds.add(peerId); final Configuration conf = new Configuration(this.peerIds); final String initialServerList = conf.toString(); - final PlacementDriverOptions pdOpts = PlacementDriverOptionsConfigured.newConfigured() - .withFake(true) // use a fake pd - .config(); + final PlacementDriverOptions pdOpts = PlacementDriverOptionsConfigured.newConfigured().withFake(true) // use a fake pd + .config(); final StoreEngineOptions storeOpts = StoreEngineOptionsConfigured.newConfigured() // - .withStorageType(this.storageType) // - .withRocksDBOptions(RocksDBOptionsConfigured.newConfigured().withDbPath(DB_PATH).config()) // - .withRaftDataPath(RAFT_DATA_PATH) // - .withServerAddress(peerId.getEndpoint()) // - .config(); + .withStorageType(this.storageType) // + .withRocksDBOptions(RocksDBOptionsConfigured.newConfigured().withDbPath(DB_PATH).config()) // + .withRaftDataPath(RAFT_DATA_PATH) // + .withServerAddress(peerId.getEndpoint()) // + .config(); final RheaKVStoreOptions opts = RheaKVStoreOptionsConfigured.newConfigured() // - .withClusterName("chaos_test") // - .withInitialServerList(initialServerList) - .withStoreEngineOptions(storeOpts) // - .withPlacementDriverOptions(pdOpts) // - .config(); + .withClusterName("chaos_test") // + .withInitialServerList(initialServerList).withStoreEngineOptions(storeOpts) // + .withPlacementDriverOptions(pdOpts) // + .config(); BatchingOptions batchingOptions = opts.getBatchingOptions(); if (batchingOptions == null) { batchingOptions = new BatchingOptions(); @@ -260,7 +256,9 @@ public synchronized void awaitLeader() { } try { Thread.sleep(100); - } catch (InterruptedException ignored) {} + } catch (InterruptedException ignored) { + // ignored + } } throw new NotLeaderException("wait leader timeout"); } diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/pd/RheaKVTestCluster.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/pd/RheaKVTestCluster.java index 072afac42..d44b8b1a2 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/pd/RheaKVTestCluster.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/pd/RheaKVTestCluster.java @@ -38,15 +38,12 @@ */ public class RheaKVTestCluster { - private static final String[] CONF = { - "/pd_conf/rhea_pd_test_1.yaml", - "/pd_conf/rhea_pd_test_2.yaml", - "/pd_conf/rhea_pd_test_3.yaml" - }; + private static final String[] CONF = { "/pd_conf/rhea_pd_test_1.yaml", + "/pd_conf/rhea_pd_test_2.yaml", "/pd_conf/rhea_pd_test_3.yaml" }; - private volatile String tempDbPath; - private volatile String tempRaftPath; - private CopyOnWriteArrayList stores = new CopyOnWriteArrayList<>(); + private volatile String tempDbPath; + private volatile String tempRaftPath; + private CopyOnWriteArrayList stores = new CopyOnWriteArrayList<>(); protected void start() throws IOException, InterruptedException { System.out.println("RheaKVTestCluster init ..."); diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/memorydb/MemoryKVStoreTest.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/memorydb/MemoryKVStoreTest.java index d60cd5251..b667b8c88 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/memorydb/MemoryKVStoreTest.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/memorydb/MemoryKVStoreTest.java @@ -541,8 +541,8 @@ public boolean doSnapshotLoad(final String path, final LocalFileMeta meta) { private void doCompressSnapshot(final String path) { try { - try (final ZipOutputStream out = new ZipOutputStream( - new FileOutputStream(path + File.separator + SNAPSHOT_ARCHIVE))) { + try (final ZipOutputStream out = new ZipOutputStream(new FileOutputStream(path + File.separator + + SNAPSHOT_ARCHIVE))) { ZipUtil.compressDirectoryToZipFile(path, SNAPSHOT_DIR, out); } } catch (final Throwable t) { diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/RheaKVTestCluster.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/RheaKVTestCluster.java index 8b9502b74..e031d3949 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/RheaKVTestCluster.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/RheaKVTestCluster.java @@ -47,11 +47,8 @@ public class RheaKVTestCluster { public static String RAFT_DATA_PATH = "rhea_raft"; public static Long[] REGION_IDS = new Long[] { 1L, 2L, 3L }; - private static final String[] CONF = { - "/conf/rhea_test_cluster_1.yaml", - "/conf/rhea_test_cluster_2.yaml", - "/conf/rhea_test_cluster_3.yaml" - }; + private static final String[] CONF = { "/conf/rhea_test_cluster_1.yaml", + "/conf/rhea_test_cluster_2.yaml", "/conf/rhea_test_cluster_3.yaml" }; private List stores = new CopyOnWriteArrayList<>(); @@ -95,7 +92,9 @@ protected RheaKVStore getLeaderStore(final long regionId) { } try { Thread.sleep(100); - } catch (InterruptedException ignored) {} + } catch (InterruptedException ignored) { + // ignored + } } throw new NotLeaderException("no leader on region: " + regionId); } diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rocksdb/RocksKVStoreTest.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rocksdb/RocksKVStoreTest.java index 0b0c256fc..514106ec6 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rocksdb/RocksKVStoreTest.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rocksdb/RocksKVStoreTest.java @@ -495,8 +495,8 @@ public boolean doSnapshotLoad(final String path, final LocalFileMeta meta) { private void doCompressSnapshot(final String path) { try { - try (final ZipOutputStream out = new ZipOutputStream( - new FileOutputStream(path + File.separator + SNAPSHOT_ARCHIVE))) { + try (final ZipOutputStream out = new ZipOutputStream(new FileOutputStream(path + File.separator + + SNAPSHOT_ARCHIVE))) { ZipUtil.compressDirectoryToZipFile(path, SNAPSHOT_DIR, out); } } catch (final Throwable t) { diff --git a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/DefaultPlacementDriverService.java b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/DefaultPlacementDriverService.java index 595101312..09c393a68 100644 --- a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/DefaultPlacementDriverService.java +++ b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/DefaultPlacementDriverService.java @@ -101,8 +101,8 @@ public synchronized boolean init(final PlacementDriverServerOptions opts) { if (corePoolSize > 0 && maximumPoolSize > 0) { final String name = "pipeline-executor"; final ThreadPoolExecutor threadPool = ThreadPoolUtil.newThreadPool(name, false, corePoolSize, - maximumPoolSize, 120L, new ArrayBlockingQueue<>(1024), - new NamedThreadFactory(name, true), new CallerRunsPolicyWithReport(name)); + maximumPoolSize, 120L, new ArrayBlockingQueue<>(1024), new NamedThreadFactory(name, true), + new CallerRunsPolicyWithReport(name)); this.pipelineInvoker = new DefaultHandlerInvoker(threadPool); } this.pipeline = new DefaultPipeline() // diff --git a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/PlacementDriverProcessor.java b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/PlacementDriverProcessor.java index daa22b814..fb74125da 100644 --- a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/PlacementDriverProcessor.java +++ b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/PlacementDriverProcessor.java @@ -68,8 +68,8 @@ public PlacementDriverProcessor(Class reqClazz, PlacementDriverService placem @Override public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, T request) { Requires.requireNonNull(request, "request"); - final RequestProcessClosure closure = - new RequestProcessClosure<>(request, bizCtx, asyncCtx); + final RequestProcessClosure closure = new RequestProcessClosure<>(request, bizCtx, + asyncCtx); switch (request.magic()) { case BaseRequest.STORE_HEARTBEAT: this.placementDriverService.handleStoreHeartbeatRequest((StoreHeartbeatRequest) request, closure); diff --git a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/PlacementDriverServer.java b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/PlacementDriverServer.java index b437f24cc..798c2f2f2 100644 --- a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/PlacementDriverServer.java +++ b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/PlacementDriverServer.java @@ -204,13 +204,20 @@ public boolean awaitReady(final long timeoutMillis) { } private void addPlacementDriverProcessor(final RpcServer rpcServer) { - rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(RegionHeartbeatRequest.class, this.placementDriverService, this.pdExecutor)); - rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(StoreHeartbeatRequest.class, this.placementDriverService, this.pdExecutor)); - rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(GetClusterInfoRequest.class, this.placementDriverService, this.pdExecutor)); - rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(GetStoreIdRequest.class, this.placementDriverService, this.pdExecutor)); - rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(GetStoreInfoRequest.class, this.placementDriverService, this.pdExecutor)); - rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(SetStoreInfoRequest.class, this.placementDriverService, this.pdExecutor)); - rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(CreateRegionIdRequest.class, this.placementDriverService, this.pdExecutor)); + rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(RegionHeartbeatRequest.class, + this.placementDriverService, this.pdExecutor)); + rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(StoreHeartbeatRequest.class, + this.placementDriverService, this.pdExecutor)); + rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(GetClusterInfoRequest.class, + this.placementDriverService, this.pdExecutor)); + rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(GetStoreIdRequest.class, + this.placementDriverService, this.pdExecutor)); + rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(GetStoreInfoRequest.class, + this.placementDriverService, this.pdExecutor)); + rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(SetStoreInfoRequest.class, + this.placementDriverService, this.pdExecutor)); + rpcServer.registerUserProcessor(new PlacementDriverProcessor<>(CreateRegionIdRequest.class, + this.placementDriverService, this.pdExecutor)); } private ThreadPoolExecutor createDefaultPdExecutor() { @@ -220,6 +227,7 @@ private ThreadPoolExecutor createDefaultPdExecutor() { final String name = "pd-executor"; final ThreadFactory threadFactory = new NamedThreadFactory(name, true); final RejectedExecutionHandler handler = new CallerRunsPolicyWithReport(name, name); - return ThreadPoolUtil.newThreadPool(name, true, corePoolSize, maximumPoolSize, 120L, workQueue, threadFactory, handler); + return ThreadPoolUtil.newThreadPool(name, true, corePoolSize, maximumPoolSize, 120L, workQueue, threadFactory, + handler); } } diff --git a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/pipeline/event/PingEvent.java b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/pipeline/event/PingEvent.java index 9f2236b64..11803c02b 100644 --- a/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/pipeline/event/PingEvent.java +++ b/jraft-rheakv/rheakv-pd/src/main/java/com/alipay/sofa/jraft/rhea/pipeline/event/PingEvent.java @@ -28,8 +28,8 @@ */ public abstract class PingEvent extends InboundMessageEvent { - private final Collection instructions = new LinkedBlockingDeque<>(); - private final MetadataStore metadataStore; + private final Collection instructions = new LinkedBlockingDeque<>(); + private final MetadataStore metadataStore; public PingEvent(T message, MetadataStore metadataStore) { super(message); diff --git a/jraft-rheakv/rheakv-pd/src/test/java/com/alipay/sofa/jraft/rhea/BasePdServer.java b/jraft-rheakv/rheakv-pd/src/test/java/com/alipay/sofa/jraft/rhea/BasePdServer.java index 9123ca1d9..cce4c5661 100644 --- a/jraft-rheakv/rheakv-pd/src/test/java/com/alipay/sofa/jraft/rhea/BasePdServer.java +++ b/jraft-rheakv/rheakv-pd/src/test/java/com/alipay/sofa/jraft/rhea/BasePdServer.java @@ -33,15 +33,12 @@ */ public class BasePdServer { - private static final String[] CONF = { - "/pd/pd_1.yaml", - "/pd/pd_2.yaml", - "/pd/pd_3.yaml" - }; + private static final String[] CONF = { "/pd/pd_1.yaml", "/pd/pd_2.yaml", + "/pd/pd_3.yaml" }; private volatile String tempDbPath; private volatile String tempRaftPath; - private CopyOnWriteArrayList pdServerList = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList pdServerList = new CopyOnWriteArrayList<>(); protected void start() throws IOException, InterruptedException { System.out.println("PlacementDriverServer init ..."); diff --git a/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/client/AtomicClient.java b/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/client/AtomicClient.java index 05d285a26..043b93d29 100644 --- a/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/client/AtomicClient.java +++ b/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/client/AtomicClient.java @@ -53,10 +53,10 @@ public class AtomicClient { static final Logger LOG = LoggerFactory.getLogger(AtomicClient.class); - private final Configuration conf; + private final Configuration conf; private final BoltCliClientService cliClientService; private RpcClient rpcClient; - private CliOptions cliOptions; + private CliOptions cliOptions; private TreeMap groups = new TreeMap<>(); public AtomicClient(String groupId, Configuration conf) { @@ -80,8 +80,8 @@ public void start() throws InterruptedException, TimeoutException { final Set peers = conf.getPeerSet(); for (final PeerId peer : peers) { try { - final BooleanCommand cmd = (BooleanCommand) this.rpcClient.invokeSync(peer.getEndpoint().toString(), - new GetSlotsCommand(), cliOptions.getRpcDefaultTimeout()); + final BooleanCommand cmd = (BooleanCommand) this.rpcClient.invokeSync( + peer.getEndpoint().toString(), new GetSlotsCommand(), cliOptions.getRpcDefaultTimeout()); if (cmd instanceof SlotsResponseCommand) { groups = ((SlotsResponseCommand) cmd).getMap(); break; @@ -142,7 +142,7 @@ private PeerId getPeer(String key) throws InterruptedException, TimeoutException } public long get(String key, boolean readFromQuorum) throws KeyNotFoundException, InterruptedException, - TimeoutException { + TimeoutException { if (readFromQuorum) { return get(getPeer(key), key, true, false); } else { @@ -151,8 +151,9 @@ public long get(String key, boolean readFromQuorum) throws KeyNotFoundException, } } - public long get(PeerId peer, String key, boolean readFromQuorum, - boolean readByStateMachine) throws KeyNotFoundException, InterruptedException { + public long get(PeerId peer, String key, boolean readFromQuorum, boolean readByStateMachine) + throws KeyNotFoundException, + InterruptedException { try { final GetCommand request = new GetCommand(key); request.setReadFromQuorum(readFromQuorum); diff --git a/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/command/SlotsResponseCommand.java b/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/command/SlotsResponseCommand.java index 185e41c2f..14f0010b0 100644 --- a/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/command/SlotsResponseCommand.java +++ b/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/command/SlotsResponseCommand.java @@ -21,7 +21,7 @@ public class SlotsResponseCommand extends BooleanCommand implements Serializable { private static final long serialVersionUID = -3155350383161976585L; - private TreeMap map = new TreeMap<>(); + private TreeMap map = new TreeMap<>(); public TreeMap getMap() { return this.map; diff --git a/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/server/AtomicStateMachine.java b/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/server/AtomicStateMachine.java index 9c245a014..41a3891f6 100644 --- a/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/server/AtomicStateMachine.java +++ b/jraft-test/src/main/java/com/alipay/sofa/jraft/test/atomic/server/AtomicStateMachine.java @@ -54,15 +54,15 @@ */ public class AtomicStateMachine extends StateMachineAdapter { - private static final Logger LOG = LoggerFactory - .getLogger(AtomicStateMachine.class); + private static final Logger LOG = LoggerFactory.getLogger(AtomicStateMachine.class); - private final ConcurrentHashMap counters = new ConcurrentHashMap<>(); + // + private final ConcurrentHashMap counters = new ConcurrentHashMap<>(); /** * leader term */ - private final AtomicLong leaderTerm = new AtomicLong(-1); + private final AtomicLong leaderTerm = new AtomicLong(-1); public boolean isLeader() { return this.leaderTerm.get() > 0; From 5d055ef182763725c0fb9890c74f78c33971d4b9 Mon Sep 17 00:00:00 2001 From: Block Date: Fri, 8 Mar 2019 17:19:30 +0800 Subject: [PATCH 07/31] (fixbug) replicator will blocked when check reader failed (#19) --- .../src/main/java/com/alipay/sofa/jraft/core/Replicator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java index 9a1ceba35..da66b75ad 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java @@ -445,9 +445,9 @@ void installSnapshot() { id.unlock(); return; } - Requires.requireTrue(this.reader == null); boolean doUnlock = true; try { + Requires.requireTrue(this.reader == null); reader = options.getSnapshotStorage().open(); if (reader == null) { final NodeImpl node = options.getNode(); From 5ab7341a8d1c397c3525d9aaf7ad107e41545efb Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Fri, 8 Mar 2019 23:48:03 +0800 Subject: [PATCH 08/31] Minor changes for replicator log and state (#21) * (fix) Should use clock time in replicator block method * (feat) Tweak replicator log and state * (fix) typo * (fix) rename MaxReplicatorInflightMsgs to maxReplicatorInflightMsgs in log --- .../alipay/sofa/jraft/core/Replicator.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java index da66b75ad..61e0d04bf 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java @@ -442,12 +442,15 @@ private void startHeartbeatTimer(long startMs) { void installSnapshot() { if (this.state == State.Snapshot) { + LOG.warn("Replicator {} is installing snapshot, ignore the new request.", this.options.getPeerId()); id.unlock(); return; } boolean doUnlock = true; try { - Requires.requireTrue(this.reader == null); + Requires.requireTrue(this.reader == null, + "Replicator %s already has a snapshot reader, current state is %s", this.options.getPeerId(), + this.state); reader = options.getSnapshotStorage().open(); if (reader == null) { final NodeImpl node = options.getNode(); @@ -553,9 +556,9 @@ static boolean onInstallSnapshotReturned(ThreadId id, Replicator r, Status statu // We don't retry installing the snapshot explicitly. // id is unlock in sendEntries if (!success) { - r.state = State.Probe; //should reset states r.resetInflights(); + r.state = State.Probe; r.block(Utils.nowMs(), status.getCode()); return false; } @@ -923,17 +926,17 @@ private static void onTimeout(ThreadId id) { void destroy() { final ThreadId savedId = this.id; LOG.info("Replicator {} is going to quit", savedId); - this.state = State.Destroyed; this.id = null; + if (reader != null) { + Utils.closeQuietly(reader); + this.reader = null; + } // Unregister replicator metric set if (this.options.getNode().getNodeMetrics().getMetricRegistry() != null) { options.getNode().getNodeMetrics().getMetricRegistry().remove(getReplicatorMetricName(this.options)); } + this.state = State.Destroyed; savedId.unlockAndDestroy(); - if (reader != null) { - Utils.closeQuietly(reader); - this.reader = null; - } } static void onHeartbeatReturned(ThreadId id, Status status, AppendEntriesRequest request, @@ -1021,6 +1024,8 @@ static void onRpcReturned(ThreadId id, RequestType reqType, Status status, Messa holdingQueue.add(new RpcResponse(reqType, seq, status, request, response, rpcSendTime)); if (holdingQueue.size() > r.raftOptions.getMaxReplicatorInflightMsgs()) { + LOG.warn("Too many pending responses {} for replicator {}, maxReplicatorInflightMsgs={}", + holdingQueue.size(), r.options.getPeerId(), r.raftOptions.getMaxReplicatorInflightMsgs()); r.resetInflights(); r.state = State.Probe; r.sendEmptyEntries(false); @@ -1166,12 +1171,12 @@ private static boolean onAppendEntriesReturned(ThreadId id, Inflight inflight, S sb.append(" fail, sleep."); LOG.debug(sb.toString()); } - r.state = State.Probe; if (++r.consecutiveErrorTimes % 10 == 0) { LOG.warn("Fail to issue RPC to {}, consecutiveErrorTimes={}, error={}", r.options.getPeerId(), r.consecutiveErrorTimes, status); } r.resetInflights(); + r.state = State.Probe; //unlock in in block r.block(startTimeMs, status.getCode()); return false; From aab1c6182b7a8a881d02b127ca16bd12016b3201 Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Sun, 10 Mar 2019 18:46:01 +0800 Subject: [PATCH 09/31] Fix/lease read stale (#26) * (fix) Ignore preVote request if there is a leader that it's lease is still valid. * (fix) Use monotonic time for lastLeaderTimestamp and refactor, #24 * (fix) typo and error log --- .../com/alipay/sofa/jraft/core/NodeImpl.java | 22 +++++++++++++++---- .../core/AppendEntriesRequestProcessor.java | 6 ++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java index eaafd1c14..660b3bbd7 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java @@ -411,7 +411,7 @@ public NodeImpl(String groupId, PeerId serverId) { this.serverId = serverId != null ? serverId.copy() : null; this.state = State.STATE_UNINITIALIZED; this.currTerm = 0; - this.lastLeaderTimestamp = System.currentTimeMillis(); + this.updateLastLeaderTimestamp(); this.confCtx = new ConfigurationCtx(this); this.wakingCandidate = null; GLOBAL_NUM_NODES.incrementAndGet(); @@ -483,7 +483,7 @@ private void handleElectionTimeout() { if (this.state != State.STATE_FOLLOWER) { return; } - if (Utils.nowMs() - this.lastLeaderTimestamp < options.getElectionTimeoutMs()) { + if (isLeaderLeaseValid()) { return; } final PeerId emptyId = new PeerId(); @@ -975,7 +975,7 @@ private void stepDown(long term, boolean wakeupCandidate, Status status) { // soft state in memory this.state = State.STATE_FOLLOWER; this.confCtx.reset(); - this.lastLeaderTimestamp = Utils.nowMs(); + this.updateLastLeaderTimestamp(); if (this.snapshotExecutor != null) { snapshotExecutor.interruptDownloadingSnapshots(term); } @@ -1309,6 +1309,12 @@ public Message handlePreVoteRequest(RequestVoteRequest request) { boolean granted = false; // noinspection ConstantConditions do { + if (this.leaderId != null && !this.leaderId.isEmpty() && isLeaderLeaseValid()) { + LOG.info( + "Node {} ignore PreVote from {} in term {} currTerm {}, because the leader {}'s lease is still valid.", + this.getNodeId(), request.getServerId(), request.getTerm(), this.currTerm, this.leaderId); + break; + } if (request.getTerm() < this.currTerm) { LOG.info("Node {} ignore PreVote from {} in term {} currTerm {}", this.getNodeId(), request.getServerId(), request.getTerm(), this.currTerm); @@ -1347,6 +1353,14 @@ public Message handlePreVoteRequest(RequestVoteRequest request) { } } + private boolean isLeaderLeaseValid() { + return Utils.monotonicMs() - this.lastLeaderTimestamp < this.options.getElectionTimeoutMs(); + } + + private void updateLastLeaderTimestamp() { + this.lastLeaderTimestamp = Utils.monotonicMs(); + } + private void checkReplicator(PeerId candidateId) { if (this.state == State.STATE_LEADER) { this.replicatorGroup.checkReplicator(candidateId, false); @@ -1539,7 +1553,7 @@ public Message handleAppendEntriesRequest(AppendEntriesRequest request, RpcReque return responseBuilder.build(); } - this.lastLeaderTimestamp = Utils.nowMs(); + this.updateLastLeaderTimestamp(); if (entriesCount > 0 && this.snapshotExecutor != null && this.snapshotExecutor.isInstallingSnapshot()) { LOG.warn("Node {} received AppendEntriesRequest while installing snapshot", getNodeId()); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java index 8b91e6cfd..3704c5ce8 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java @@ -22,8 +22,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; -import io.netty.util.concurrent.DefaultEventExecutor; - import org.apache.commons.lang.StringUtils; import com.alipay.remoting.AsyncContext; @@ -41,6 +39,8 @@ import com.alipay.sofa.jraft.util.Utils; import com.google.protobuf.Message; +import io.netty.util.concurrent.DefaultEventExecutor; + /** * Append entries request processor. * @@ -396,7 +396,7 @@ public void onEvent(String remoteAddr, Connection conn) { } } } else { - LOG.error("Fail to parse peer {}", remoteAddr); + LOG.info("Connection disconnected: {}", remoteAddr); } } } From ca0805c382a718208d1801f9a403867453b39ed9 Mon Sep 17 00:00:00 2001 From: Block Date: Mon, 11 Mar 2019 16:54:15 +0800 Subject: [PATCH 10/31] (fix) rocksdb-compression-on-windows (#22) * (fix) Seems like the rocksdb jni for Windows doesn't come linked with any of the compression type * (feat) Export RocksDB options to users https://github.com/alipay/sofa-jraft/issues/20 * (fix) use Utils#cpus() to get available processors * (fix) rm RocksConfigs * (fix) typo * (feat) speeding up the build with TESTFOLDER * (feat) simplify the rheakv unit test * (fix) rm shutdown hook with rocksdb options --- .travis.yml | 11 +- .../com/alipay/sofa/jraft/core/NodeImpl.java | 4 +- .../alipay/sofa/jraft/core/Replicator.java | 2 +- .../jraft/storage/impl/RocksDBLogStorage.java | 51 +--- .../jraft/util/StorageOptionsFactory.java | 261 ++++++++++++++++++ .../rhea/rocks/support/RocksConfigs.java | 155 ----------- .../jraft/rhea/storage/RocksRawKVStore.java | 81 ++---- .../storage/rhea/AbstractRheaKVStoreTest.java | 32 +-- .../rhea/storage/rhea/RheaKVTestCluster.java | 5 +- .../resources/conf/rhea_test_cluster_1.yaml | 3 +- .../resources/conf/rhea_test_cluster_2.yaml | 3 +- .../resources/conf/rhea_test_cluster_3.yaml | 24 -- 12 files changed, 328 insertions(+), 304 deletions(-) create mode 100644 jraft-core/src/main/java/com/alipay/sofa/jraft/util/StorageOptionsFactory.java delete mode 100644 jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksConfigs.java delete mode 100644 jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_3.yaml diff --git a/.travis.yml b/.travis.yml index 82a70947c..4fc272076 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,13 @@ language: java sudo: false jdk: -- openjdk11 -- openjdk8 + - openjdk8 + - openjdk11 + +env: + - TESTFOLDER=jraft-core + - TESTFOLDER=jraft-rheakv/rheakv-core + - TESTFOLDER=jraft-rheakv/rheakv-pd cache: directories: @@ -16,4 +21,4 @@ script: - sh ./.middleware-common/check_format.sh after_success: - - travis_retry mvn clean test \ No newline at end of file + - travis_retry mvn --projects $TESTFOLDER clean test \ No newline at end of file diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java index 660b3bbd7..6081472c7 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java @@ -2506,7 +2506,7 @@ public Status transferLeadershipTo(PeerId peer) { // elected. If no add_peer with this very |peer| is to be invoked ever // after nor this peer is to be killed, this peer will spin in the voting // procedure and make the each new leader stepped down when the peer - // reached vote timedout and it starts to vote (because it will increase + // reached vote timeout and it starts to vote (because it will increase // the term of the group) // To make things simple, refuse the operation and force users to // invoke transfer_leadership_to after configuration changing is @@ -2528,7 +2528,7 @@ public Status transferLeadershipTo(PeerId peer) { } } if (peerId.equals(this.serverId)) { - LOG.info("Node {} transfered leadership to self."); + LOG.info("Node {} transferred leadership to self."); return Status.OK(); } if (!conf.contains(peerId)) { diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java index 61e0d04bf..c41b7cddb 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java @@ -1543,7 +1543,7 @@ private boolean transferLeadership(long logIndex) { this.sendTimeoutNow(true, false); return true; } - // Register log_index so that _on_rpc_returne trigger + // Register log_index so that _on_rpc_return trigger // _send_timeout_now if _next_index reaches log_index this.timeoutNowIndex = logIndex; id.unlock(); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java index 70bc63520..384f57466 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java @@ -29,8 +29,6 @@ import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; -import org.rocksdb.CompactionStyle; -import org.rocksdb.CompressionType; import org.rocksdb.DBOptions; import org.rocksdb.IndexType; import org.rocksdb.Options; @@ -54,6 +52,7 @@ import com.alipay.sofa.jraft.option.RaftOptions; import com.alipay.sofa.jraft.storage.LogStorage; import com.alipay.sofa.jraft.util.Bits; +import com.alipay.sofa.jraft.util.StorageOptionsFactory; import com.alipay.sofa.jraft.util.Utils; /** @@ -88,16 +87,15 @@ private interface WriteBatchTemplate { private RocksDB db; private DBOptions dbOptions; private WriteOptions writeOptions; - private final List cfOptions = new ArrayList<>(); + private final List cfOptions = new ArrayList<>(); private ColumnFamilyHandle defaultHandle; private ColumnFamilyHandle confHandle; private ReadOptions totalOrderReadOptions; - private static final int MAX_LOG_FILE_SIZE = 1024 * 1024 * 1024; - private final ReadWriteLock lock = new ReentrantReadWriteLock(false); - private final Lock readLock = lock.readLock(); - private final Lock writeLock = lock.writeLock(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(false); + private final Lock readLock = lock.readLock(); + private final Lock writeLock = lock.writeLock(); - private volatile long firstLogIndex = 1; + private volatile long firstLogIndex = 1; private volatile boolean hasLoadFirstLogIndex; @@ -118,37 +116,16 @@ private static BlockBasedTableConfig createTableConfig() { } public static DBOptions createDBOptions() { - // Turn based on https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide - // and http://gitlab.alibaba-inc.com/aloha/aloha/blob/branch_2_5_0/jstorm-core/src/main/java/com/alibaba/jstorm/cache/rocksdb/RocksDbOptionsFactory.java - final DBOptions options = new DBOptions(); - return options.setCreateIfMissing(true). // - setCreateMissingColumnFamilies(true). // - setMaxOpenFiles(-1). // - setMaxLogFileSize(MAX_LOG_FILE_SIZE). // - setMaxBackgroundFlushes(1). // - setMaxBackgroundCompactions(1); - + return StorageOptionsFactory.getRocksDBOptions(RocksDBLogStorage.class); } public static ColumnFamilyOptions createColumnFamilyOptions() { - final BlockBasedTableConfig tconfig = createTableConfig(); - final ColumnFamilyOptions options = new ColumnFamilyOptions(); - return options.setMaxWriteBufferNumber(2). // - useFixedLengthPrefixExtractor(8). // - setTableFormatConfig(tconfig). // - setCompressionType(CompressionType.LZ4_COMPRESSION). // - setCompactionStyle(CompactionStyle.LEVEL). // - optimizeLevelStyleCompaction(). // - setLevel0FileNumCompactionTrigger(10). // - setLevel0SlowdownWritesTrigger(20). // - setLevel0StopWritesTrigger(40). // - setWriteBufferSize(64 * SizeUnit.MB). // - setMaxWriteBufferNumber(3). // - setTargetFileSizeBase(64 * SizeUnit.MB). // - setMaxBytesForLevelBase(512 * SizeUnit.MB). // - setMergeOperator(new StringAppendOperator()). // - setMemtablePrefixBloomSizeRatio(0.125); - + final BlockBasedTableConfig tConfig = createTableConfig(); + final ColumnFamilyOptions options = StorageOptionsFactory + .getRocksDBColumnFamilyOptions(RocksDBLogStorage.class); + return options.useFixedLengthPrefixExtractor(8). // + setTableFormatConfig(tConfig). // + setMergeOperator(new StringAppendOperator()); } @Override @@ -183,7 +160,7 @@ private boolean initAndLoad(ConfigurationManager confManager) throws RocksDBExce final ColumnFamilyOptions cfOption = createColumnFamilyOptions(); this.cfOptions.add(cfOption); columnFamilyDescriptors.add(new ColumnFamilyDescriptor("Configuration".getBytes(), cfOption)); - //default column family + // default column family columnFamilyDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOption)); openDB(columnFamilyDescriptors); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/StorageOptionsFactory.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/StorageOptionsFactory.java new file mode 100644 index 000000000..b0819ce81 --- /dev/null +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/StorageOptionsFactory.java @@ -0,0 +1,261 @@ +/* + * 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 com.alipay.sofa.jraft.util; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.rocksdb.ColumnFamilyOptions; +import org.rocksdb.CompactionStyle; +import org.rocksdb.CompressionType; +import org.rocksdb.DBOptions; +import org.rocksdb.RocksObject; +import org.rocksdb.util.SizeUnit; + +/** + * + * @author jiachun.fjc + */ +public final class StorageOptionsFactory { + + private static final Map rocksDBOptionsTable = new ConcurrentHashMap<>(); + private static final Map columnFamilyOptionsTable = new ConcurrentHashMap<>(); + + /** + * Releases all storage options from the responsibility of freeing the + * underlying native C++ object. + * + * Note, that once an instance of options has been released, calling any + * of its functions will lead to undefined behavior. + */ + public static void releaseAllOptions() { + for (final DBOptions opts : rocksDBOptionsTable.values()) { + if (opts != null) { + opts.close(); + } + } + for (final ColumnFamilyOptions opts : columnFamilyOptionsTable.values()) { + if (opts != null) { + opts.close(); + } + } + } + + /** + * Users can register a custom rocksdb dboptions, then the related + * classes will get their options by the key of their own class + * name. If the user does not register an options, a default options + * will be provided. + * + * @param cls the key of DBOptions + * @param opts the DBOptions + */ + public static void registerRocksDBOptions(final Class cls, final DBOptions opts) { + Requires.requireNonNull(cls, "cls"); + Requires.requireNonNull(opts, "opts"); + if (rocksDBOptionsTable.putIfAbsent(cls.getName(), opts) != null) { + throw new IllegalStateException("DBOptions with class key [" + cls.getName() + + "] has already been registered"); + } + } + + /** + * Get a new default DBOptions or a copy of the exist DBOptions. + * Users should call DBOptions#close() to release resources themselves. + * + * @param cls the key of DBOptions + * @return new default DBOptions or a copy of the exist DBOptions + */ + public static DBOptions getRocksDBOptions(final Class cls) { + Requires.requireNonNull(cls, "cls"); + DBOptions opts = rocksDBOptionsTable.get(cls.getName()); + if (opts == null) { + final DBOptions newOpts = getDefaultRocksDBOptions(); + opts = rocksDBOptionsTable.putIfAbsent(cls.getName(), newOpts); + if (opts == null) { + opts = newOpts; + } else { + newOpts.close(); + } + } + // NOTE: This does a shallow copy, which means env, rate_limiter, + // sst_file_manager, info_log and other pointers will be cloned! + return new DBOptions(checkInvalid(opts)); + } + + public static DBOptions getDefaultRocksDBOptions() { + // Turn based on https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide + final DBOptions opts = new DBOptions(); + + // If this value is set to true, then the database will be created if it is + // missing during {@code RocksDB.open()}. + opts.setCreateIfMissing(true); + + // If true, missing column families will be automatically created. + opts.setCreateMissingColumnFamilies(true); + + // Number of open files that can be used by the DB. You may need to increase + // this if your database has a large working set. Value -1 means files opened + // are always kept open. + opts.setMaxOpenFiles(-1); + + // The maximum number of concurrent background compactions. The default is 1, + // but to fully utilize your CPU and storage you might want to increase this + // to approximately number of cores in the system. + opts.setMaxBackgroundCompactions(Math.min(Utils.cpus(), 4)); + + // The maximum number of concurrent flush operations. It is usually good enough + // to set this to 1. + opts.setMaxBackgroundFlushes(1); + + return opts; + } + + /** + * Users can register a custom rocksdb column family options, then the + * related classes will get their options by the key of their own class + * name. If the user does not register an options, a default options + * will be provided. + * + * @param cls the key of ColumnFamilyOptions + * @param opts the ColumnFamilyOptions + */ + public static void registerRocksDBColumnFamilyOptions(final Class cls, final ColumnFamilyOptions opts) { + Requires.requireNonNull(cls, "cls"); + Requires.requireNonNull(opts, "opts"); + if (columnFamilyOptionsTable.putIfAbsent(cls.getName(), opts) != null) { + throw new IllegalStateException("ColumnFamilyOptions with class key [" + cls.getName() + + "] has already been registered"); + } + } + + /** + * Get a new default ColumnFamilyOptions or a copy of the exist + * ColumnFamilyOptions. Users should call ColumnFamilyOptions#close() + * to release resources themselves. + * + * @param cls the key of ColumnFamilyOptions + * @return new default ColumnFamilyOptions or a copy of the exist + * ColumnFamilyOptions + */ + public static ColumnFamilyOptions getRocksDBColumnFamilyOptions(final Class cls) { + Requires.requireNonNull(cls, "cls"); + ColumnFamilyOptions opts = columnFamilyOptionsTable.get(cls.getName()); + if (opts == null) { + final ColumnFamilyOptions newOpts = getDefaultRocksDBColumnFamilyOptions(); + opts = columnFamilyOptionsTable.putIfAbsent(cls.getName(), newOpts); + if (opts == null) { + opts = newOpts; + } else { + newOpts.close(); + } + } + // NOTE: This does a shallow copy, which means comparator, merge_operator, + // compaction_filter, compaction_filter_factory and other pointers will be + // cloned! + return new ColumnFamilyOptions(checkInvalid(opts)); + } + + public static ColumnFamilyOptions getDefaultRocksDBColumnFamilyOptions() { + final ColumnFamilyOptions opts = new ColumnFamilyOptions(); + + // Flushing options: + // write_buffer_size sets the size of a single mem_table. Once mem_table exceeds + // this size, it is marked immutable and a new one is created. + opts.setWriteBufferSize(64 * SizeUnit.MB); + + // Flushing options: + // max_write_buffer_number sets the maximum number of mem_tables, both active + // and immutable. If the active mem_table fills up and the total number of + // mem_tables is larger than max_write_buffer_number we stall further writes. + // This may happen if the flush process is slower than the write rate. + opts.setMaxWriteBufferNumber(3); + + // Flushing options: + // min_write_buffer_number_to_merge is the minimum number of mem_tables to be + // merged before flushing to storage. For example, if this option is set to 2, + // immutable mem_tables are only flushed when there are two of them - a single + // immutable mem_table will never be flushed. If multiple mem_tables are merged + // together, less data may be written to storage since two updates are merged to + // a single key. However, every Get() must traverse all immutable mem_tables + // linearly to check if the key is there. Setting this option too high may hurt + // read performance. + opts.setMinWriteBufferNumberToMerge(1); + + // Level Style Compaction: + // level0_file_num_compaction_trigger -- Once level 0 reaches this number of + // files, L0->L1 compaction is triggered. We can therefore estimate level 0 + // size in stable state as + // write_buffer_size * min_write_buffer_number_to_merge * level0_file_num_compaction_trigger. + opts.setLevel0FileNumCompactionTrigger(10); + + // Soft limit on number of level-0 files. We start slowing down writes at this + // point. A value 0 means that no writing slow down will be triggered by number + // of files in level-0. + opts.setLevel0SlowdownWritesTrigger(20); + + // Maximum number of level-0 files. We stop writes at this point. + opts.setLevel0StopWritesTrigger(40); + + // Level Style Compaction: + // max_bytes_for_level_base and max_bytes_for_level_multiplier + // -- max_bytes_for_level_base is total size of level 1. As mentioned, we + // recommend that this be around the size of level 0. Each subsequent level + // is max_bytes_for_level_multiplier larger than previous one. The default + // is 10 and we do not recommend changing that. + opts.setMaxBytesForLevelBase(512 * SizeUnit.MB); + + // Level Style Compaction: + // target_file_size_base and target_file_size_multiplier + // -- Files in level 1 will have target_file_size_base bytes. Each next + // level's file size will be target_file_size_multiplier bigger than previous + // one. However, by default target_file_size_multiplier is 1, so files in all + // L1..LMax levels are equal. Increasing target_file_size_base will reduce total + // number of database files, which is generally a good thing. We recommend setting + // target_file_size_base to be max_bytes_for_level_base / 10, so that there are + // 10 files in level 1. + opts.setTargetFileSizeBase(64 * SizeUnit.MB); + + // If prefix_extractor is set and memtable_prefix_bloom_size_ratio is not 0, + // create prefix bloom for memtable with the size of + // write_buffer_size * memtable_prefix_bloom_size_ratio. + // If it is larger than 0.25, it is santinized to 0.25. + opts.setMemtablePrefixBloomSizeRatio(0.125); + + // Seems like the rocksDB jni for Windows doesn't come linked with any of the + // compression type + if (!Platform.isWindows()) { + opts.setCompressionType(CompressionType.LZ4_COMPRESSION) // + .setCompactionStyle(CompactionStyle.LEVEL) // + .optimizeLevelStyleCompaction(); + } + + return opts; + } + + private static T checkInvalid(final T opts) { + if (!opts.isOwningHandle()) { + throw new IllegalStateException( + "the instance of options [" + opts + + "] has been released, calling any of its functions will lead to undefined behavior."); + } + return opts; + } + + private StorageOptionsFactory() { + } +} diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksConfigs.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksConfigs.java deleted file mode 100644 index 6bf6f1a13..000000000 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksConfigs.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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 com.alipay.sofa.jraft.rhea.rocks.support; - -import org.rocksdb.util.SizeUnit; - -import com.alipay.sofa.jraft.util.SystemPropertyUtil; - -/** - * @author jiachun.fjc - */ -public final class RocksConfigs { - - /** Env options ***************************************************************************************************/ - - // Sets the number of background worker threads of the flush pool for this - // environment. - public static int ENV_BACKGROUND_FLUSH_THREADS = SystemPropertyUtil.getInt( - "rhea.rocks.env.env_background_flush_threads", 2); - - // Sets the number of background worker threads of the compaction pool for this - // environment. - public static int ENV_BACKGROUND_COMPACTION_THREADS = SystemPropertyUtil.getInt( - "rhea.rocks.env.env_background_compaction_threads", - 4); - - /** RocksDB options ***********************************************************************************************/ - - // max_open_files -- RocksDB keeps all file descriptors in a table cache. If number - // of file descriptors exceeds max_open_files, some files are evicted from table cache - // and their file descriptors closed. This means that every read must go through the - // table cache to lookup the file needed. Set max_open_files to -1 to always keep all - // files open, which avoids expensive table cache calls. - public static final int MAX_OPEN_FILES = SystemPropertyUtil.getInt( - "rhea.rocks.options.max_open_files", -1); - - // 1. max_background_flushes: - // is the maximum number of concurrent flush operations. - // - // 2. max_background_compactions: - // is the maximum number of concurrent background compactions. The default is 1, - // but to fully utilize your CPU and storage you might want to increase this to - // approximately number of cores in the system. - // - // 3. max_background_jobs: - // is the maximum number of concurrent background jobs (both flushes and compactions - // combined). - public static final int MAX_BACKGROUND_JOBS = SystemPropertyUtil.getInt( - "rhea.rocks.options.max_background_jobs", 6); - - // Specifies the maximum size of RocksDB's info log file. If the current log - // file is larger than `max_log_file_size`, a new info log file will be created. - // If 0, all logs will be written to one log file. - public static final long MAX_LOG_FILE_SIZE = SystemPropertyUtil.getLong( - "rhea.rocks.options.max_log_file_size", - 1024 * 1024 * 1024L); - - // Flushing options: - // write_buffer_size sets the size of a single mem_table. Once mem_table exceeds - // this size, it is marked immutable and a new one is created. - public static final long WRITE_BUFFER_SIZE = SystemPropertyUtil.getLong( - "rhea.rocks.options.write_buffer_size", - 64 * SizeUnit.MB); - - // Flushing options: - // max_write_buffer_number sets the maximum number of mem_tables, both active - // and immutable. If the active mem_table fills up and the total number of - // mem_tables is larger than max_write_buffer_number we stall further writes. - // This may happen if the flush process is slower than the write rate. - public static final int MAX_WRITE_BUFFER_NUMBER = SystemPropertyUtil.getInt( - "rhea.rocks.options.max_write_buffer_number", 5); - - // Flushing options: - // min_write_buffer_number_to_merge is the minimum number of mem_tables to be - // merged before flushing to storage. For example, if this option is set to 2, - // immutable mem_tables are only flushed when there are two of them - a single - // immutable mem_table will never be flushed. If multiple mem_tables are merged - // together, less data may be written to storage since two updates are merged to - // a single key. However, every Get() must traverse all immutable mem_tables - // linearly to check if the key is there. Setting this option too high may hurt - // read performance. - public static final int MIN_WRITE_BUFFER_NUMBER_TO_MERGE = SystemPropertyUtil - .getInt( - "rhea.rocks.options.min_write_buffer_number_to_merge", - 1); - - // Level Style Compaction: - // level0_file_num_compaction_trigger -- Once level 0 reaches this number of - // files, L0->L1 compaction is triggered. We can therefore estimate level 0 - // size in stable state as - // write_buffer_size * min_write_buffer_number_to_merge * level0_file_num_compaction_trigger. - public static final int LEVEL0_FILE_NUM_COMPACTION_TRIGGER = SystemPropertyUtil - .getInt( - "rhea.rocks.options.level0_file_num_compaction_trigger", - 10); - - // Level Style Compaction: - // max_bytes_for_level_base and max_bytes_for_level_multiplier - // -- max_bytes_for_level_base is total size of level 1. As mentioned, we - // recommend that this be around the size of level 0. Each subsequent level - // is max_bytes_for_level_multiplier larger than previous one. The default - // is 10 and we do not recommend changing that. - public static final long MAX_BYTES_FOR_LEVEL_BASE = SystemPropertyUtil.getLong( - "rhea.rocks.options.max_bytes_for_level_base", - 512 * SizeUnit.MB); - - // Level Style Compaction: - // target_file_size_base and target_file_size_multiplier - // -- Files in level 1 will have target_file_size_base bytes. Each next - // level's file size will be target_file_size_multiplier bigger than previous - // one. However, by default target_file_size_multiplier is 1, so files in all - // L1..LMax levels are equal. Increasing target_file_size_base will reduce total - // number of database files, which is generally a good thing. We recommend setting - // target_file_size_base to be max_bytes_for_level_base / 10, so that there are - // 10 files in level 1. - public static final long TARGET_FILE_SIZE_BASE = SystemPropertyUtil.getLong( - "rhea.rocks.options.target_file_size_base", - 64 * SizeUnit.MB); - - // Soft limit on number of level-0 files. We start slowing down writes at this - // point. A value 0 means that no writing slow down will be triggered by number - // of files in level-0. - public static final int LEVEL0_SLOWDOWN_WRITES_TRIGGER = SystemPropertyUtil - .getInt( - "rhea.rocks.options.level0_slowdown_writes_trigger", - 20); - - // Maximum number of level-0 files. We stop writes at this point. - public static final int LEVEL0_STOP_WRITES_TRIGGER = SystemPropertyUtil - .getInt( - "rhea.rocks.options.level0_stop_writes_trigger", - 40); - - /** User-Defined options ******************************************************************************************/ - // The maximum number of keys in once batch write - public static final int MAX_BATCH_WRITE_SIZE = SystemPropertyUtil.getInt( - "rhea.rocks.user.max_batch_write_size", 128); - - private RocksConfigs() { - } -} diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java index 9f2705f89..0b0406cb6 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java @@ -40,13 +40,10 @@ import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; -import org.rocksdb.CompactionStyle; -import org.rocksdb.CompressionType; import org.rocksdb.DBOptions; import org.rocksdb.Env; import org.rocksdb.EnvOptions; import org.rocksdb.IngestExternalFileOptions; -import org.rocksdb.MergeOperator; import org.rocksdb.Options; import org.rocksdb.ReadOptions; import org.rocksdb.RestoreOptions; @@ -78,24 +75,12 @@ import com.alipay.sofa.jraft.rhea.util.concurrent.DistributedLock; import com.alipay.sofa.jraft.util.Bits; import com.alipay.sofa.jraft.util.BytesUtil; +import com.alipay.sofa.jraft.util.StorageOptionsFactory; +import com.alipay.sofa.jraft.util.SystemPropertyUtil; import com.codahale.metrics.Timer; import com.google.protobuf.ByteString; import static com.alipay.sofa.jraft.entity.LocalFileMetaOutter.LocalFileMeta; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.ENV_BACKGROUND_COMPACTION_THREADS; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.ENV_BACKGROUND_FLUSH_THREADS; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.LEVEL0_FILE_NUM_COMPACTION_TRIGGER; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.LEVEL0_SLOWDOWN_WRITES_TRIGGER; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.LEVEL0_STOP_WRITES_TRIGGER; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.MAX_BACKGROUND_JOBS; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.MAX_BYTES_FOR_LEVEL_BASE; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.MAX_BATCH_WRITE_SIZE; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.MAX_LOG_FILE_SIZE; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.MAX_OPEN_FILES; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.MAX_WRITE_BUFFER_NUMBER; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.MIN_WRITE_BUFFER_NUMBER_TO_MERGE; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.TARGET_FILE_SIZE_BASE; -import static com.alipay.sofa.jraft.rhea.rocks.support.RocksConfigs.WRITE_BUFFER_SIZE; /** * Local KV store based on RocksDB @@ -105,19 +90,23 @@ */ public class RocksRawKVStore extends BatchRawKVStore { - private static final Logger LOG = LoggerFactory.getLogger(RocksRawKVStore.class); + private static final Logger LOG = LoggerFactory.getLogger(RocksRawKVStore.class); static { RocksDB.loadLibrary(); } - private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + // The maximum number of keys in once batch write + public static final int MAX_BATCH_WRITE_SIZE = SystemPropertyUtil.getInt( + "rhea.rocksdb.user.max_batch_write_size", 128); - private final AtomicLong databaseVersion = new AtomicLong(0); - private final Serializer serializer = Serializers.getDefault(); + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - private final List cfOptionsList = Lists.newArrayList(); - private final List cfDescriptors = Lists.newArrayList(); + private final AtomicLong databaseVersion = new AtomicLong(0); + private final Serializer serializer = Serializers.getDefault(); + + private final List cfOptionsList = Lists.newArrayList(); + private final List cfDescriptors = Lists.newArrayList(); private ColumnFamilyHandle defaultHandle; private ColumnFamilyHandle sequenceHandle; @@ -129,7 +118,6 @@ public class RocksRawKVStore extends BatchRawKVStore { private RocksDBOptions opts; private DBOptions options; private WriteOptions writeOptions; - private MergeOperator mergeOperator; private Statistics statistics; private RocksStatisticsCollector statisticsCollector; @@ -142,7 +130,6 @@ public boolean init(final RocksDBOptions opts) { LOG.info("[RocksRawKVStore] already started."); return true; } - this.mergeOperator = new StringAppendOperator(); this.opts = opts; this.options = createDBOptions(); if (opts.isOpenStatisticsCollector()) { @@ -154,7 +141,7 @@ public boolean init(final RocksDBOptions opts) { this.statisticsCollector.start(); } } - final ColumnFamilyOptions cfOptions = createColumnFamilyOptions(this.mergeOperator); + final ColumnFamilyOptions cfOptions = createColumnFamilyOptions(); this.cfOptionsList.add(cfOptions); // default column family this.cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOptions)); @@ -217,9 +204,6 @@ public void shutdown() { if (this.statistics != null) { this.statistics.close(); } - if (this.mergeOperator != null) { - this.mergeOperator.close(); - } if (this.writeOptions != null) { this.writeOptions.close(); } @@ -1064,7 +1048,7 @@ public void createSstFiles(final EnumMap sstFileTable, fi final Snapshot snapshot = this.db.getSnapshot(); try (final ReadOptions readOptions = new ReadOptions(); final EnvOptions envOptions = new EnvOptions(); - final Options options = new Options().setMergeOperator(this.mergeOperator)) { + final Options options = new Options().setMergeOperator(new StringAppendOperator())) { readOptions.setSnapshot(snapshot); for (final Map.Entry entry : sstFileTable.entrySet()) { final SstColumnFamily sstColumnFamily = entry.getKey(); @@ -1275,39 +1259,18 @@ private static BlockBasedTableConfig createTableConfig() { // Creates the rocksDB options, the user must take care // to close it after closing db. private static DBOptions createDBOptions() { - Env env = Env.getDefault() // - .setBackgroundThreads(ENV_BACKGROUND_FLUSH_THREADS, Env.FLUSH_POOL) // - .setBackgroundThreads(ENV_BACKGROUND_COMPACTION_THREADS, Env.COMPACTION_POOL); - - // Turn based on https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide - return new DBOptions() // - .setEnv(env) // - .setCreateIfMissing(true) // - .setCreateMissingColumnFamilies(true) // - .setMaxOpenFiles(MAX_OPEN_FILES) // - .setMaxBackgroundJobs(MAX_BACKGROUND_JOBS) // - .setMaxLogFileSize(MAX_LOG_FILE_SIZE); + return StorageOptionsFactory.getRocksDBOptions(RocksRawKVStore.class) // + .setEnv(Env.getDefault()); } // Creates the column family options to control the behavior // of a database. - private static ColumnFamilyOptions createColumnFamilyOptions(final MergeOperator mergeOperator) { - BlockBasedTableConfig tableConfig = createTableConfig(); - return new ColumnFamilyOptions() // - .setTableFormatConfig(tableConfig) // - .setWriteBufferSize(WRITE_BUFFER_SIZE) // - .setMaxWriteBufferNumber(MAX_WRITE_BUFFER_NUMBER) // - .setMinWriteBufferNumberToMerge(MIN_WRITE_BUFFER_NUMBER_TO_MERGE) // - .setCompressionType(CompressionType.LZ4_COMPRESSION) // - .setCompactionStyle(CompactionStyle.LEVEL) // - .optimizeLevelStyleCompaction() // - .setLevel0FileNumCompactionTrigger(LEVEL0_FILE_NUM_COMPACTION_TRIGGER) // - .setLevel0SlowdownWritesTrigger(LEVEL0_SLOWDOWN_WRITES_TRIGGER) // - .setLevel0StopWritesTrigger(LEVEL0_STOP_WRITES_TRIGGER) // - .setMaxBytesForLevelBase(MAX_BYTES_FOR_LEVEL_BASE) // - .setTargetFileSizeBase(TARGET_FILE_SIZE_BASE) // - .setMergeOperator(mergeOperator) // - .setMemtablePrefixBloomSizeRatio(0.125); + private static ColumnFamilyOptions createColumnFamilyOptions() { + final BlockBasedTableConfig tConfig = createTableConfig(); + final ColumnFamilyOptions opts = StorageOptionsFactory.getRocksDBColumnFamilyOptions(RocksRawKVStore.class); + opts.setTableFormatConfig(tConfig) // + .setMergeOperator(new StringAppendOperator()); + return opts; } // Creates the backupable db options to control the behavior of diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/AbstractRheaKVStoreTest.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/AbstractRheaKVStoreTest.java index b48f3b6f0..ac36c01d6 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/AbstractRheaKVStoreTest.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/AbstractRheaKVStoreTest.java @@ -84,7 +84,7 @@ private void checkRegion(RheaKVStore store, byte[] key, long expectedRegionId) { * Test method: {@link RheaKVStore#get(byte[])} */ private void getTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) byte[] key = makeKey("a_get_test"); checkRegion(store, key, 1); byte[] value = store.bGet(key); @@ -102,7 +102,7 @@ private void getTest(RheaKVStore store) { assertArrayEquals(value, store.bGet(key)); key = makeKey("z_get_test"); - checkRegion(store, key, 3); + checkRegion(store, key, 2); value = store.bGet(key); assertNull(value); value = makeValue("z_get_test_value"); @@ -134,7 +134,7 @@ public void putByLeaderGetByFollower() { * Test method: {@link RheaKVStore#multiGet(List)} */ private void multiGetTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) List keyList = Lists.newArrayList(); List valueList = Lists.newArrayList(); for (int i = 0; i < 10; i++) { @@ -155,7 +155,7 @@ private void multiGetTest(RheaKVStore store) { } for (int i = 0; i < 10; i++) { byte[] key = makeKey("t_multi_test_key_" + i); - checkRegion(store, key, 3); + checkRegion(store, key, 2); byte[] value = makeValue("t_multi_test_value_" + i); keyList.add(key); valueList.add(value); @@ -187,7 +187,7 @@ public void multiGetByFollowerTest() { * Test method: {@link RheaKVStore#scan(byte[], byte[])} */ private void scanTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) List keyList = Lists.newArrayList(); List valueList = Lists.newArrayList(); for (int i = 0; i < 10; i++) { @@ -232,7 +232,7 @@ public void scanByFollowerTest() { * Test method: {@link RheaKVStore#scan(byte[], byte[])} */ private void iteratorTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) List keyList = Lists.newArrayList(); List valueList = Lists.newArrayList(); for (int i = 0; i < 10; i++) { @@ -288,7 +288,7 @@ public void iteratorByFollowerTest() { * Test method: {@link RheaKVStore#getSequence(byte[], int)} */ private void getSequenceTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) byte[] seqKey = makeKey("seq_test"); checkRegion(store, seqKey, 2); Sequence sequence = store.bGetSequence(seqKey, 199); @@ -324,7 +324,7 @@ public void getSequenceByFollowerTest() { * Test method: {@link RheaKVStore#put(byte[], byte[])} */ private void putTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) byte[] key = makeKey("put_test"); checkRegion(store, key, 2); byte[] value = store.bGet(key); @@ -350,7 +350,7 @@ public void putByFollowerTest() { * Test method: {@link RheaKVStore#getAndPut(byte[], byte[])} */ private void getAndPutTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) byte[] key = makeKey("put_test"); checkRegion(store, key, 2); byte[] value = store.bGet(key); @@ -404,7 +404,7 @@ public void mergeByFollowerTest() { * Test method: {@link RheaKVStore#put(List)} */ private void putListTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) List entries1 = Lists.newArrayList(); for (int i = 0; i < 3; i++) { byte[] key = makeKey("batch_put_test_key" + i); @@ -447,9 +447,9 @@ public void putListByFollowerTest1() { * Test method: {@link RheaKVStore#putIfAbsent(byte[], byte[])} */ private void putIfAbsentTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) byte[] key = makeKey("u_put_if_absent_test"); - checkRegion(store, key, 3); + checkRegion(store, key, 2); byte[] value = makeValue("put_if_absent_test_value"); byte[] newValue = makeValue("put_if_absent_test_value_1"); byte[] oldValue = store.bPutIfAbsent(key, value); @@ -472,7 +472,7 @@ public void putIfAbsentByFollowerTest() { * Test method: {@link RheaKVStore#delete(byte[])} */ private void deleteTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) List entries = Lists.newArrayList(); for (int i = 0; i < 10; i++) { byte[] key = makeKey("del_test" + i); @@ -502,7 +502,7 @@ public void deleteByFollowerTest1() { * Test method: {@link RheaKVStore#deleteRange(byte[], byte[])} */ private void deleteRangeTest(RheaKVStore store) { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) List entries = Lists.newArrayList(); for (int i = 0; i < 10; i++) { byte[] key = makeKey("del_range_test" + i); @@ -512,7 +512,7 @@ private void deleteRangeTest(RheaKVStore store) { } for (int i = 0; i < 10; i++) { byte[] key = makeKey("t_del_range_test" + i); - checkRegion(store, key, 3); + checkRegion(store, key, 2); byte[] value = makeValue("del_range_test_value" + i); entries.add(new KVEntry(key, value)); } @@ -546,7 +546,7 @@ public void deleteRangeByFollowerTest() { * Test method: {@link RheaKVStore#getDistributedLock(byte[], long, TimeUnit)} */ private void distributedLockTest(RheaKVStore store) throws InterruptedException { - // regions: 1 -> [null, g), 2 -> [g, t), 3 -> [t, null) + // regions: 1 -> [null, g), 2 -> [g, null) byte[] lockKey = makeKey("lock_test"); checkRegion(store, lockKey, 2); final DistributedLock lock = store.getDistributedLock(lockKey, 3, TimeUnit.SECONDS); diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/RheaKVTestCluster.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/RheaKVTestCluster.java index e031d3949..d889dc535 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/RheaKVTestCluster.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/storage/rhea/RheaKVTestCluster.java @@ -45,10 +45,9 @@ public class RheaKVTestCluster { public static String DB_PATH = "rhea_db"; public static String RAFT_DATA_PATH = "rhea_raft"; - public static Long[] REGION_IDS = new Long[] { 1L, 2L, 3L }; + public static Long[] REGION_IDS = new Long[] { 1L, 2L }; - private static final String[] CONF = { "/conf/rhea_test_cluster_1.yaml", - "/conf/rhea_test_cluster_2.yaml", "/conf/rhea_test_cluster_3.yaml" }; + private static final String[] CONF = { "/conf/rhea_test_cluster_1.yaml", "/conf/rhea_test_cluster_2.yaml" }; private List stores = new CopyOnWriteArrayList<>(); diff --git a/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_1.yaml b/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_1.yaml index 119ad74ff..100e5c132 100644 --- a/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_1.yaml +++ b/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_1.yaml @@ -17,8 +17,7 @@ storeEngineOptions: port: 18181 regionEngineOptionsList: - { regionId: 1, endKey: g, nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } - - { regionId: 2, startKey: g, endKey: t, nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } - - { regionId: 3, startKey: t , nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } + - { regionId: 2, startKey: g , nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } leastKeysOnSplit: 10 initialServerList: 127.0.0.1:18181,127.0.0.1:18182,127.0.0.1:18183 \ No newline at end of file diff --git a/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_2.yaml b/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_2.yaml index 753fcbe0c..3a1c87dcf 100644 --- a/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_2.yaml +++ b/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_2.yaml @@ -17,8 +17,7 @@ storeEngineOptions: port: 18182 regionEngineOptionsList: - { regionId: 1, endKey: g, nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } - - { regionId: 2, startKey: g, endKey: t, nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } - - { regionId: 3, startKey: t , nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } + - { regionId: 2, startKey: g , nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } leastKeysOnSplit: 10 initialServerList: 127.0.0.1:18181,127.0.0.1:18182,127.0.0.1:18183 \ No newline at end of file diff --git a/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_3.yaml b/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_3.yaml deleted file mode 100644 index 7c4e3d5a8..000000000 --- a/jraft-rheakv/rheakv-core/src/test/resources/conf/rhea_test_cluster_3.yaml +++ /dev/null @@ -1,24 +0,0 @@ -##RheaKVStoreOptions ---- -clusterName: rhea_test - -placementDriverOptions: - fake: true - cliOptions: - rpcProcessorThreadPoolSize: 4 - -storeEngineOptions: - rocksDBOptions: - sync: true - dbPath: rhea_db/ - raftDataPath: rhea_raft/ - serverAddress: - ip: 127.0.0.1 - port: 18183 - regionEngineOptionsList: - - { regionId: 1, endKey: g, nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } - - { regionId: 2, startKey: g, endKey: t, nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } - - { regionId: 3, startKey: t , nodeOptions: { timerPoolSize: 1, rpcProcessorThreadPoolSize: 4 } } - leastKeysOnSplit: 10 - -initialServerList: 127.0.0.1:18181,127.0.0.1:18182,127.0.0.1:18183 \ No newline at end of file From 2595b3cb6c79961e249107c8f58f2b1983c813f2 Mon Sep 17 00:00:00 2001 From: shiftyman Date: Mon, 11 Mar 2019 20:46:11 +0800 Subject: [PATCH 11/31] enhance pre-voting phase. (#29) --- .../src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java index 6081472c7..b42b802fb 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java @@ -685,7 +685,7 @@ protected int adjustTimeout(int timeoutMs) { return randomTimeout(timeoutMs); } }; - this.stepDownTimer = new RepeatedTimer("JRaft-StepDownTimer", this.options.getElectionTimeoutMs()) { + this.stepDownTimer = new RepeatedTimer("JRaft-StepDownTimer", this.options.getElectionTimeoutMs() >> 1) { @Override protected void onTrigger() { @@ -1752,6 +1752,7 @@ private void checkDeadNodes(Configuration conf, long nowMs) { } if (aliveCount >= peers.size() / 2 + 1) { + this.updateLastLeaderTimestamp(); return; } LOG.warn("Node {} term {} steps down when alive nodes don't satisfy quorum dead nodes: {} conf: {}", From 0820a3bdbeb4b18f274bf3e3621dd78c5d3e1954 Mon Sep 17 00:00:00 2001 From: Block Date: Mon, 11 Mar 2019 23:35:07 +0800 Subject: [PATCH 12/31] =?UTF-8?q?(fix)=20update=20readme=EF=BC=8CSOFAJRAFT?= =?UTF-8?q?=20->=20SOFAJRaft=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 843713f68..48879267a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# SOFAJRAFT +# SOFAJRaft [![Build Status](https://travis-ci.com/alipay/sofa-jraft.svg?branch=master)](https://travis-ci.com/alipay/sofa-jraft) ![License](https://img.shields.io/badge/license-Apache--2.0-green.svg) [![Maven Central](https://img.shields.io/maven-central/v/com.alipay.sofa/jraft-parent.svg?label=maven%20central)](https://search.maven.org/search?q=g:com.alipay.sofa%20AND%20sofa-jraft) -SOFAJRAFT 是一个基于 [RAFT](https://raft.github.io/) 一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。 -使用 SOFAJRAFT 你可以专注于自己的业务领域,由 SOFAJRAFT 负责处理所有与 RAFT 相关的技术难题,并且 SOFAJRAFT 非常易于使用,你可以通过几个示例在很短的时间内掌握它。 +SOFAJRaft 是一个基于 [RAFT](https://raft.github.io/) 一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。 +使用 SOFAJRaft 你可以专注于自己的业务领域,由 SOFAJRaft 负责处理所有与 RAFT 相关的技术难题,并且 SOFAJRaft 非常易于使用,你可以通过几个示例在很短的时间内掌握它。 ## 功能特性 - Leader 选举 @@ -33,14 +33,14 @@ SOFAJRAFT 是一个基于 [RAFT](https://raft.github.io/) 一致性算法的生 - [版本发行日志](https://github.com/alipay/sofa-jraft/wiki/%E7%89%88%E6%9C%AC%E5%8F%91%E8%A1%8C%E6%97%A5%E5%BF%97) ## 如何贡献 -[如何参与 SOFAJRAFT 代码贡献](https://github.com/alipay/sofa-jraft/wiki/%E5%A6%82%E4%BD%95%E5%8F%82%E4%B8%8E-SOFAJRAFT-%E4%BB%A3%E7%A0%81%E8%B4%A1%E7%8C%AE) +[如何参与 SOFAJRaft 代码贡献](https://github.com/alipay/sofa-jraft/wiki/%E5%A6%82%E4%BD%95%E5%8F%82%E4%B8%8E-SOFAJRaft-%E4%BB%A3%E7%A0%81%E8%B4%A1%E7%8C%AE) ## 致谢 -SOFAJRAFT 是从百度的 [braft](https://github.com/brpc/braft) 移植而来,做了一些优化和改进,感谢百度 braft 团队开源了优秀的 C++ RAFT 实现 +SOFAJRaft 是从百度的 [braft](https://github.com/brpc/braft) 移植而来,做了一些优化和改进,感谢百度 braft 团队开源了如此优秀的 C++ RAFT 实现 ## 开源许可 -SOFAJRAFT 基于 [Apache License 2.0](https://github.com/alipay/sofa-jraft/blob/master/LICENSE) 协议,SOFAJRAFT 依赖了一些三方组件,它们的开源协议也为 Apache License 2.0, -另外 SOFAJRAFT 也直接引用了一些开源的代码(可能有一些小小的改动)包括: +SOFAJRaft 基于 [Apache License 2.0](https://github.com/alipay/sofa-jraft/blob/master/LICENSE) 协议,SOFAJRaft 依赖了一些第三方组件,它们的开源协议也为 Apache License 2.0, +另外 SOFAJRaft 也直接引用了一些开源协议为 Apache License 2.0 的代码(可能有一些小小的改动)包括: - [JCTools](https://github.com/JCTools/JCTools) 中的 NonBlockingHashMap/NonBlockingHashMapLong - [Netty](https://github.com/netty/netty) 中的 HashedWheelTimer,另外还参考了 Netty 的 Pipeline 设计 - [Protobuf](https://github.com/protocolbuffers/protobuf) 中对 UTF8 String 高效的编码/解码 From d5b3feec9d6949ddb16d12b79062e23fb9c31ddb Mon Sep 17 00:00:00 2001 From: pifuant Date: Tue, 12 Mar 2019 14:15:46 +0800 Subject: [PATCH 13/31] (fix) type error (#31) --- .../src/main/java/com/alipay/sofa/jraft/core/Replicator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java index c41b7cddb..75be9a0f5 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java @@ -88,7 +88,7 @@ public class Replicator implements ThreadId.OnError { // Cached the latest RPC in-flight request. private Inflight rpcInFly; - // Heartbeat RPC futgure + // Heartbeat RPC future private Future heartbeatInFly; // Timeout request RPC future private Future timeoutNowInFly; From df5f883a4d27417b6bd7701b9f01acf7f27f5180 Mon Sep 17 00:00:00 2001 From: pifuant Date: Tue, 12 Mar 2019 15:47:23 +0800 Subject: [PATCH 14/31] remove redundant Map.get operation (#32) --- .../java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java index c8e42028f..73b4c6fc6 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java @@ -186,13 +186,12 @@ public void checkReplicator(PeerId peer, boolean lockNode) { public boolean stopReplicator(PeerId peer) { LOG.info("Stop replicator to {}", peer); this.failureReplicators.remove(peer); - final ThreadId rid = this.replicatorMap.get(peer); + final ThreadId rid = this.replicatorMap.remove(peer); if (rid == null) { return false; } // Calling ReplicatorId.stop might lead to calling stopReplicator again, // erase entry first to avoid race condition - this.replicatorMap.remove(peer); return Replicator.stop(rid); } From 0181b1e8421c25e64143b2765d413bfc67d9ff1a Mon Sep 17 00:00:00 2001 From: Block Date: Tue, 12 Mar 2019 16:47:32 +0800 Subject: [PATCH 15/31] (fix) remove unnecessary code (#33) --- jraft-example/bin/client_benchmark_start.sh | 4 ---- jraft-example/bin/server_benchmark_start.sh | 4 ---- 2 files changed, 8 deletions(-) diff --git a/jraft-example/bin/client_benchmark_start.sh b/jraft-example/bin/client_benchmark_start.sh index c304efb35..a2c2a68b2 100755 --- a/jraft-example/bin/client_benchmark_start.sh +++ b/jraft-example/bin/client_benchmark_start.sh @@ -3,10 +3,6 @@ BASE_DIR=$(dirname $0)/.. CLASSPATH=$(echo $BASE_DIR/lib/*.jar | tr ' ' ':') -if [ -z "$JAVA_HOME" ]; then - JAVA_HOME=/opt/taobao/java -fi - # get java version JAVA="$JAVA_HOME/bin/java" diff --git a/jraft-example/bin/server_benchmark_start.sh b/jraft-example/bin/server_benchmark_start.sh index 79bf6482c..0e5fb96fd 100755 --- a/jraft-example/bin/server_benchmark_start.sh +++ b/jraft-example/bin/server_benchmark_start.sh @@ -3,10 +3,6 @@ BASE_DIR=$(dirname $0)/.. CLASSPATH=$(echo $BASE_DIR/lib/*.jar | tr ' ' ':') -if [ -z "$JAVA_HOME" ]; then - JAVA_HOME=/opt/taobao/java -fi - # get java version JAVA="$JAVA_HOME/bin/java" From 29b8009fe0fccf9903f046ea89b4f3de0c8d762c Mon Sep 17 00:00:00 2001 From: Block Date: Wed, 13 Mar 2019 18:56:20 +0800 Subject: [PATCH 16/31] Fix/fix stale read (#34) * (fix) fix stale read with lease read * (fix) use monotonic clock on rpc_send_time * (fix) avoid unnecessary copy of peers * (fix) better perf for lease read * (fix) rename method name * (fix) Integer -> Long * (fix) check leader lease in readLeader() * (fix) move up the 'peers check' * (fix) add checkDeadNodes0 --- .../alipay/sofa/jraft/ReplicatorGroup.java | 2 +- .../com/alipay/sofa/jraft/core/NodeImpl.java | 96 +++++++++++++------ .../alipay/sofa/jraft/core/Replicator.java | 24 ++--- .../alipay/sofa/jraft/option/NodeOptions.java | 22 +++++ .../sofa/jraft/core/ReplicatorTest.java | 21 ++-- 5 files changed, 113 insertions(+), 52 deletions(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/ReplicatorGroup.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/ReplicatorGroup.java index 916bc7466..ecc74a117 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/ReplicatorGroup.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/ReplicatorGroup.java @@ -93,7 +93,7 @@ public interface ReplicatorGroup { boolean waitCaughtUp(PeerId peer, long maxMargin, long dueTime, CatchUpClosure done); /** - * Get peer's last rpc send timestamp. + * Get peer's last rpc send timestamp (monotonic time in milliseconds). * * @param peer the peer of replicator */ diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java index b42b802fb..157807ae1 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java @@ -67,6 +67,7 @@ import com.alipay.sofa.jraft.option.LogManagerOptions; import com.alipay.sofa.jraft.option.NodeOptions; import com.alipay.sofa.jraft.option.RaftOptions; +import com.alipay.sofa.jraft.option.ReadOnlyOption; import com.alipay.sofa.jraft.option.ReadOnlyServiceOptions; import com.alipay.sofa.jraft.option.ReplicatorGroupOptions; import com.alipay.sofa.jraft.option.SnapshotExecutorOptions; @@ -125,7 +126,7 @@ public class NodeImpl implements Node, RaftServerService { private volatile State state; private volatile CountDownLatch shutdownLatch; private long currTerm; - private long lastLeaderTimestamp; + private volatile long lastLeaderTimestamp; private PeerId leaderId = new PeerId(); private PeerId votedId; private final Ballot voteCtx = new Ballot(); @@ -411,7 +412,7 @@ public NodeImpl(String groupId, PeerId serverId) { this.serverId = serverId != null ? serverId.copy() : null; this.state = State.STATE_UNINITIALIZED; this.currTerm = 0; - this.updateLastLeaderTimestamp(); + this.updateLastLeaderTimestamp(Utils.monotonicMs()); this.confCtx = new ConfigurationCtx(this); this.wakingCandidate = null; GLOBAL_NUM_NODES.incrementAndGet(); @@ -483,7 +484,7 @@ private void handleElectionTimeout() { if (this.state != State.STATE_FOLLOWER) { return; } - if (isLeaderLeaseValid()) { + if (isCurrentLeaderValid()) { return; } final PeerId emptyId = new PeerId(); @@ -975,7 +976,7 @@ private void stepDown(long term, boolean wakeupCandidate, Status status) { // soft state in memory this.state = State.STATE_FOLLOWER; this.confCtx.reset(); - this.updateLastLeaderTimestamp(); + this.updateLastLeaderTimestamp(Utils.monotonicMs()); if (this.snapshotExecutor != null) { snapshotExecutor.interruptDownloadingSnapshots(term); } @@ -1234,14 +1235,19 @@ private void readLeader(ReadIndexRequest request, final ReadIndexResponse.Builde } } - switch (this.raftOptions.getReadOnlyOptions()) { + ReadOnlyOption readOnlyOpt = this.raftOptions.getReadOnlyOptions(); + if (readOnlyOpt == ReadOnlyOption.ReadOnlyLeaseBased && !isLeaderLeaseValid()) { + // If leader lease timeout, we must change option to ReadOnlySafe + readOnlyOpt = ReadOnlyOption.ReadOnlySafe; + } + + switch (readOnlyOpt) { case ReadOnlySafe: - final ReadIndexHeartbeatResponseClosure heartbeatDone = new ReadIndexHeartbeatResponseClosure( - closure, respBuilder, quorum, this.conf.getConf().getPeers().size()); final List peers = this.conf.getConf().getPeers(); - Requires.requireNonNull(peers, "Peer is null"); Requires.requireTrue(!peers.isEmpty(), "Peer is empty"); + final ReadIndexHeartbeatResponseClosure heartbeatDone = new ReadIndexHeartbeatResponseClosure( + closure, respBuilder, quorum, peers.size()); // Send heartbeat requests to followers for (final PeerId peer : peers) { if (peer.equals(this.serverId)) { @@ -1309,7 +1315,7 @@ public Message handlePreVoteRequest(RequestVoteRequest request) { boolean granted = false; // noinspection ConstantConditions do { - if (this.leaderId != null && !this.leaderId.isEmpty() && isLeaderLeaseValid()) { + if (this.leaderId != null && !this.leaderId.isEmpty() && isCurrentLeaderValid()) { LOG.info( "Node {} ignore PreVote from {} in term {} currTerm {}, because the leader {}'s lease is still valid.", this.getNodeId(), request.getServerId(), request.getTerm(), this.currTerm, this.leaderId); @@ -1353,12 +1359,26 @@ public Message handlePreVoteRequest(RequestVoteRequest request) { } } + // in read_lock private boolean isLeaderLeaseValid() { + final long monotonicNowMs = Utils.monotonicMs(); + if (checkLeaderLease(monotonicNowMs)) { + return true; + } + checkDeadNodes0(this.conf.getConf().getPeers(), monotonicNowMs, false, null); + return checkLeaderLease(monotonicNowMs); + } + + private boolean checkLeaderLease(long monotonicNowMs) { + return monotonicNowMs - this.lastLeaderTimestamp < this.options.getLeaderLeaseTimeoutMs(); + } + + private boolean isCurrentLeaderValid() { return Utils.monotonicMs() - this.lastLeaderTimestamp < this.options.getElectionTimeoutMs(); } - private void updateLastLeaderTimestamp() { - this.lastLeaderTimestamp = Utils.monotonicMs(); + private void updateLastLeaderTimestamp(long lastLeaderTimestamp) { + this.lastLeaderTimestamp = lastLeaderTimestamp; } private void checkReplicator(PeerId candidateId) { @@ -1553,7 +1573,7 @@ public Message handleAppendEntriesRequest(AppendEntriesRequest request, RpcReque return responseBuilder.build(); } - this.updateLastLeaderTimestamp(); + this.updateLastLeaderTimestamp(Utils.monotonicMs()); if (entriesCount > 0 && this.snapshotExecutor != null && this.snapshotExecutor.isInstallingSnapshot()) { LOG.warn("Node {} received AppendEntriesRequest while installing snapshot", getNodeId()); @@ -1717,7 +1737,8 @@ private void onCaughtUp(PeerId peer, long term, long version, Status st) { } // Retry if this peer is still alive if (st.getCode() == RaftError.ETIMEDOUT.getNumber() - && Utils.nowMs() - replicatorGroup.getLastRpcSendTimestamp(peer) <= options.getElectionTimeoutMs()) { + && Utils.monotonicMs() - replicatorGroup.getLastRpcSendTimestamp(peer) <= options + .getElectionTimeoutMs()) { LOG.debug("Node {} waits peer {} to catch up", getNodeId(), peer); final OnCaughtUp caughtUp = new OnCaughtUp(this, term, peer, version); final long dueTime = Utils.nowMs() + options.getElectionTimeoutMs(); @@ -1734,32 +1755,49 @@ private void onCaughtUp(PeerId peer, long term, long version, Status st) { } } - private void checkDeadNodes(Configuration conf, long nowMs) { + private void checkDeadNodes(Configuration conf, long monotonicNowMs) { final List peers = conf.listPeers(); - int aliveCount = 0; final Configuration deadNodes = new Configuration(); + if (checkDeadNodes0(peers, monotonicNowMs, true, deadNodes)) { + return; + } + LOG.warn("Node {} term {} steps down when alive nodes don't satisfy quorum dead nodes: {} conf: {}", + getNodeId(), this.currTerm, deadNodes, conf); + final Status status = new Status(); + status.setError(RaftError.ERAFTTIMEDOUT, "Majority of the group dies: %d/%d", deadNodes.size(), peers.size()); + stepDown(this.currTerm, false, status); + } + + private boolean checkDeadNodes0(List peers, long monotonicNowMs, boolean checkReplicator, + Configuration deadNodes) { + final int leaderLeaseTimeoutMs = this.options.getLeaderLeaseTimeoutMs(); + int aliveCount = 0; + long startLease = Long.MAX_VALUE; for (final PeerId peer : peers) { if (peer.equals(this.serverId)) { aliveCount++; continue; } - this.checkReplicator(peer); - if (nowMs - replicatorGroup.getLastRpcSendTimestamp(peer) <= options.getElectionTimeoutMs()) { + if (checkReplicator) { + checkReplicator(peer); + } + final long lastRpcSendTimestamp = this.replicatorGroup.getLastRpcSendTimestamp(peer); + if (monotonicNowMs - lastRpcSendTimestamp <= leaderLeaseTimeoutMs) { aliveCount++; + if (startLease > lastRpcSendTimestamp) { + startLease = lastRpcSendTimestamp; + } continue; } - deadNodes.addPeer(peer); + if (deadNodes != null) { + deadNodes.addPeer(peer); + } } - if (aliveCount >= peers.size() / 2 + 1) { - this.updateLastLeaderTimestamp(); - return; + this.updateLastLeaderTimestamp(startLease); + return true; } - LOG.warn("Node {} term {} steps down when alive nodes don't satisfy quorum dead nodes: {} conf: {}", - getNodeId(), this.currTerm, deadNodes, conf); - final Status status = new Status(); - status.setError(RaftError.ERAFTTIMEDOUT, "Majority of the group dies: %d/%d", deadNodes.size(), peers.size()); - stepDown(this.currTerm, false, status); + return false; } private void handleStepDownTimeout() { @@ -1769,10 +1807,10 @@ private void handleStepDownTimeout() { LOG.debug("Node {} term {} stop stepdown timer state is {}", getNodeId(), this.currTerm, this.state); return; } - final long now = Utils.nowMs(); - checkDeadNodes(this.conf.getConf(), now); + final long monotonicNowMs = Utils.monotonicMs(); + checkDeadNodes(this.conf.getConf(), monotonicNowMs); if (!conf.getOldConf().isEmpty()) { - checkDeadNodes(conf.getOldConf(), now); + checkDeadNodes(conf.getOldConf(), monotonicNowMs); } } finally { writeLock.unlock(); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java index 75be9a0f5..6d8ade989 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java @@ -498,7 +498,7 @@ void installSnapshot() { this.state = State.Snapshot; // noinspection NonAtomicOperationOnVolatileField this.installSnapshotCounter++; - final long sendTime = Utils.nowMs(); + final long monotonicSendTimeMs = Utils.monotonicMs(); final int stateVersion = this.version; final int seq = getAndIncrementReqSeq(); final Future rpcFuture = rpcService.installSnapshot(this.options.getPeerId().getEndpoint(), @@ -507,7 +507,7 @@ void installSnapshot() { @Override public void run(Status status) { onRpcReturned(id, RequestType.Snapshot, status, request, getResponse(), seq, stateVersion, - sendTime); + monotonicSendTimeMs); } }); addInflight(RequestType.Snapshot, this.nextIndex, 0, 0, seq, rpcFuture); @@ -595,7 +595,7 @@ private void sendEmptyEntries(final boolean isHeartbeat, return; } try { - final long sendTime = Utils.nowMs(); + final long monotonicSendTimeMs = Utils.monotonicMs(); final AppendEntriesRequest request = rb.build(); if (isHeartbeat) { @@ -610,7 +610,7 @@ private void sendEmptyEntries(final boolean isHeartbeat, @Override public void run(Status status) { - onHeartbeatReturned(id, status, request, getResponse(), sendTime); + onHeartbeatReturned(id, status, request, getResponse(), monotonicSendTimeMs); } }; } @@ -631,7 +631,7 @@ public void run(Status status) { @Override public void run(Status status) { onRpcReturned(id, RequestType.AppendEntries, status, request, getResponse(), seq, - stateVersion, sendTime); + stateVersion, monotonicSendTimeMs); } }); @@ -709,9 +709,8 @@ public static ThreadId start(ReplicatorOptions opts, RaftOptions raftOptions) { r.id.lock(); LOG.info("Replicator={}@{} is started", r.id, r.options.getPeerId()); r.catchUpClosure = null; - final long now = Utils.nowMs(); - r.lastRpcSendTimestamp = now; - r.startHeartbeatTimer(now); + r.lastRpcSendTimestamp = Utils.monotonicMs(); + r.startHeartbeatTimer(Utils.nowMs()); //id.unlock in sendEmptyEntries r.sendEmptyEntries(false); return r.id; @@ -985,7 +984,7 @@ static void onHeartbeatReturned(ThreadId id, Status status, AppendEntriesRequest r.notifyOnCaughtUp(RaftError.EPERM.getNumber(), true); r.destroy(); node.increaseTermTo(response.getTerm(), new Status(RaftError.EHIGHERTERMRESPONSE, - "Leader receives higher term hearbeat_response from peer:%s", r.options.getPeerId())); + "Leader receives higher term heartbeat_response from peer:%s", r.options.getPeerId())); return; } if (isLogDebugEnabled) { @@ -1145,7 +1144,7 @@ private static boolean onAppendEntriesReturned(ThreadId id, Inflight inflight, S } //record metrics if (request.getEntriesCount() > 0) { - r.nodeMetrics.recordLatency("replicate-entries", Utils.nowMs() - rpcSendTime); + r.nodeMetrics.recordLatency("replicate-entries", Utils.monotonicMs() - rpcSendTime); r.nodeMetrics.recordSize("replicate-entries-count", request.getEntriesCount()); r.nodeMetrics.recordSize("replicate-entries-bytes", request.getData() != null ? request.getData().size() : 0); @@ -1381,14 +1380,15 @@ private boolean sendEntries(final long nextSendingIndex) { statInfo.lastLogIndex = rb.getPrevLogIndex() + rb.getEntriesCount(); final int v = this.version; - final long sendTimeMs = Utils.nowMs(); + final long monotonicSendTimeMs = Utils.monotonicMs(); final int seq = getAndIncrementReqSeq(); final Future rpcFuture = this.rpcService.appendEntries(this.options.getPeerId().getEndpoint(), request, -1, new RpcResponseClosureAdapter() { @Override public void run(Status status) { - onRpcReturned(id, RequestType.AppendEntries, status, request, getResponse(), seq, v, sendTimeMs); + onRpcReturned(id, RequestType.AppendEntries, status, request, getResponse(), seq, v, + monotonicSendTimeMs); } }); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/option/NodeOptions.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/option/NodeOptions.java index 816930012..be9502722 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/option/NodeOptions.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/option/NodeOptions.java @@ -36,6 +36,12 @@ public class NodeOptions extends RpcOptions { // Default: 1000 (1s) private int electionTimeoutMs = 1000; // follower to candidate timeout + // Leader lease time's ratio of electionTimeoutMs, + // To minimize the effects of clock drift, we should make that: + // clockDrift + leaderLeaseTimeoutMs < electionTimeout + // Default: 90, Max: 100 + private int leaderLeaseTimeRatio = 90; + // A snapshot saving would be triggered every |snapshot_interval_s| seconds // if this was reset as a positive number // If |snapshot_interval_s| <= 0, the time based snapshot would be disabled. @@ -182,6 +188,22 @@ public void setElectionTimeoutMs(int electionTimeoutMs) { this.electionTimeoutMs = electionTimeoutMs; } + public int getLeaderLeaseTimeRatio() { + return leaderLeaseTimeRatio; + } + + public void setLeaderLeaseTimeRatio(int leaderLeaseTimeRatio) { + if (leaderLeaseTimeRatio <= 0 || leaderLeaseTimeRatio > 100) { + throw new IllegalArgumentException("leaderLeaseTimeRatio: " + leaderLeaseTimeRatio + + " (expected: 0 < leaderLeaseTimeRatio <= 100)"); + } + this.leaderLeaseTimeRatio = leaderLeaseTimeRatio; + } + + public int getLeaderLeaseTimeoutMs() { + return this.electionTimeoutMs * this.leaderLeaseTimeRatio / 100; + } + public int getSnapshotIntervalSecs() { return this.snapshotIntervalSecs; } diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java index 165fa75d5..b38df3738 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java @@ -51,6 +51,7 @@ import com.alipay.sofa.jraft.storage.SnapshotStorage; import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; import com.alipay.sofa.jraft.util.ThreadId; +import com.alipay.sofa.jraft.util.Utils; import com.google.protobuf.ByteString; import com.google.protobuf.Message; @@ -164,7 +165,7 @@ public void testOnRpcReturnedRpcError() { id.unlock(); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, new Status(-1, "test error"), request, - response, 0, 0, System.currentTimeMillis()); + response, 0, 0, Utils.monotonicMs()); assertEquals(r.statInfo.runningState, Replicator.RunningState.BLOCKING); assertNotNull(r.getBlockTimer()); } @@ -179,7 +180,7 @@ public void testOnRpcReturnedTermMismatch() { id.unlock(); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, Status.OK(), request, response, 0, 0, - System.currentTimeMillis()); + Utils.monotonicMs()); Mockito.verify(this.node).increaseTermTo( 2, new Status(RaftError.EHIGHERTERMRESPONSE, "Leader receives higher term hearbeat_response from peer:%s", @@ -212,7 +213,7 @@ public void testOnRpcReturnedMoreLogs() { .thenReturn(new FutureImpl<>()); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, Status.OK(), request, response, 0, 0, - System.currentTimeMillis()); + Utils.monotonicMs()); assertNotNull(r.getRpcInFly()); assertNotSame(r.getRpcInFly(), rpcInFly); @@ -247,7 +248,7 @@ public void testOnRpcReturnedLessLogs() { .thenReturn(new FutureImpl<>()); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, Status.OK(), request, response, 0, 0, - System.currentTimeMillis()); + Utils.monotonicMs()); assertNotNull(r.getRpcInFly()); assertNotSame(r.getRpcInFly(), rpcInFly); @@ -280,7 +281,7 @@ public void run(Status status) { }); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, Status.OK(), request, response, 0, 0, - System.currentTimeMillis()); + Utils.monotonicMs()); assertEquals(r.statInfo.runningState, Replicator.RunningState.IDLE); id.unlock(); @@ -395,7 +396,7 @@ public void testOnHeartbeatReturnedRpcError() { final ScheduledFuture timer = r.getHeartbeatTimer(); assertNotNull(timer); Replicator.onHeartbeatReturned(id, new Status(-1, "test"), this.createEmptyEntriesRequestt(), null, - System.currentTimeMillis()); + Utils.monotonicMs()); assertNotNull(r.getHeartbeatTimer()); assertNotSame(timer, r.getHeartbeatTimer()); } @@ -410,7 +411,7 @@ public void testOnHeartbeatReturnedOK() { setSuccess(false). // setLastLogIndex(10).setTerm(1).build(); Replicator.onHeartbeatReturned(id, Status.OK(), this.createEmptyEntriesRequestt(), response, - System.currentTimeMillis()); + Utils.monotonicMs()); assertNotNull(r.getHeartbeatTimer()); assertNotSame(timer, r.getHeartbeatTimer()); } @@ -424,7 +425,7 @@ public void testOnHeartbeatReturnedTermMismatch() { setLastLogIndex(12).setTerm(2).build(); id.unlock(); - Replicator.onHeartbeatReturned(this.id, Status.OK(), request, response, System.currentTimeMillis()); + Replicator.onHeartbeatReturned(this.id, Status.OK(), request, response, Utils.monotonicMs()); Mockito.verify(this.node).increaseTermTo( 2, new Status(RaftError.EHIGHERTERMRESPONSE, "Leader receives higher term hearbeat_response from peer:%s", @@ -665,10 +666,10 @@ public void testOnRpcReturnedOutOfOrder() { assertTrue(r.getPendingResponses().isEmpty()); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, Status.OK(), request, response, 1, 0, - System.currentTimeMillis()); + Utils.monotonicMs()); assertEquals(1, r.getPendingResponses().size()); Replicator.onRpcReturned(this.id, Replicator.RequestType.AppendEntries, Status.OK(), request, response, 0, 0, - System.currentTimeMillis()); + Utils.monotonicMs()); assertTrue(r.getPendingResponses().isEmpty()); assertEquals(0, r.getWaitId()); assertEquals(11, r.getRealNextIndex()); From d9316a212f4d8db1e6d3b0ff0e26a28f504291c4 Mon Sep 17 00:00:00 2001 From: Block Date: Wed, 13 Mar 2019 18:56:45 +0800 Subject: [PATCH 17/31] (fix) null all rocksdb's pointers on close method (#36) --- .../rhea/rocks/support/RocksStatisticsCollector.java | 2 +- .../sofa/jraft/rhea/storage/RocksRawKVStore.java | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksStatisticsCollector.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksStatisticsCollector.java index c24e88ccf..aecb18f42 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksStatisticsCollector.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/rocks/support/RocksStatisticsCollector.java @@ -69,7 +69,7 @@ public void addStatsCollectorInput(final StatsCollectorInput input) { * killing the collection process. * @throws InterruptedException thrown if Threads are interrupted. */ - public void shutDown(final int shutdownTimeout) throws InterruptedException { + public void shutdown(final int shutdownTimeout) throws InterruptedException { this.isRunning = false; this.executorService.shutdownNow(); diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java index 0b0406cb6..07ce7c745 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/RocksRawKVStore.java @@ -176,15 +176,19 @@ public void shutdown() { closeRocksDB(); if (this.defaultHandle != null) { this.defaultHandle.close(); + this.defaultHandle = null; } if (this.sequenceHandle != null) { this.sequenceHandle.close(); + this.sequenceHandle = null; } if (this.lockingHandle != null) { this.lockingHandle.close(); + this.lockingHandle = null; } if (this.fencingHandle != null) { this.fencingHandle.close(); + this.fencingHandle = null; } for (final ColumnFamilyOptions cfOptions : this.cfOptionsList) { cfOptions.close(); @@ -193,19 +197,22 @@ public void shutdown() { this.cfDescriptors.clear(); if (this.options != null) { this.options.close(); + this.options = null; } if (this.statisticsCollector != null) { try { - this.statisticsCollector.shutDown(3000); + this.statisticsCollector.shutdown(3000); } catch (final Throwable ignored) { // ignored } } if (this.statistics != null) { this.statistics.close(); + this.statistics = null; } if (this.writeOptions != null) { this.writeOptions.close(); + this.writeOptions = null; } } finally { writeLock.unlock(); @@ -1243,6 +1250,7 @@ private void openRocksDB(final RocksDBOptions opts) throws RocksDBException { private void closeRocksDB() { if (this.db != null) { this.db.close(); + this.db = null; } } From 8c109310d84f0628fa0fce6b4a020ba538d822be Mon Sep 17 00:00:00 2001 From: huangyunbin <395018778@qq.com> Date: Mon, 18 Mar 2019 19:06:15 +0800 Subject: [PATCH 18/31] #45 remove unused code (#47) --- .../test/java/com/alipay/sofa/jraft/core/FSMCallerTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/FSMCallerTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/FSMCallerTest.java index 5f8d8faa3..1f1a51ad2 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/FSMCallerTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/FSMCallerTest.java @@ -92,9 +92,6 @@ public void testShutdownJoin() throws Exception { @Test public void testOnCommittedError() throws Exception { - final LogEntry log = new LogEntry(EntryType.ENTRY_TYPE_DATA); - log.getId().setIndex(11); - log.getId().setTerm(1); Mockito.when(logManager.getTerm(10)).thenReturn(1L); Mockito.when(logManager.getEntry(11)).thenReturn(null); From 9e19b08072114d3b24366a3278bb7a7032adea18 Mon Sep 17 00:00:00 2001 From: Block Date: Tue, 19 Mar 2019 16:42:39 +0800 Subject: [PATCH 19/31] (fix) windows path (#51) --- .../test/java/com/alipay/sofa/jraft/core/TestCluster.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java index 2c811b34e..785e5800f 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java @@ -99,7 +99,7 @@ public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotInterv nodeOptions.setEnableMetrics(enableMetrics); nodeOptions.setSnapshotThrottle(snapshotThrottle); nodeOptions.setSnapshotIntervalSecs(snapshotIntervalSecs); - final String serverDataPath = this.dataPath + File.separator + listenAddr.toString(); + final String serverDataPath = this.dataPath + File.separator + listenAddr.toString().replace(':', '_'); FileUtils.forceMkdir(new File(serverDataPath)); nodeOptions.setLogUri(serverDataPath + File.separator + "logs"); nodeOptions.setRaftMetaUri(serverDataPath + File.separator + "meta"); @@ -168,8 +168,9 @@ public void stopAll() throws InterruptedException { } public void clean(Endpoint listenAddr) throws IOException { - System.out.println("Clean dir:" + (this.dataPath + File.separator + listenAddr.toString())); - FileUtils.deleteDirectory(new File(this.dataPath + File.separator + listenAddr.toString())); + final String path = this.dataPath + File.separator + listenAddr.toString().replace(':', '_'); + System.out.println("Clean dir:" + path); + FileUtils.deleteDirectory(new File(path)); } public Node getLeader() { From 82a76bf1ae7a78a0f0e29875abda8d70b75775f4 Mon Sep 17 00:00:00 2001 From: Block Date: Wed, 20 Mar 2019 19:01:45 +0800 Subject: [PATCH 20/31] (release) v1.2.4 (#57) --- jraft-core/pom.xml | 2 +- jraft-example/pom.xml | 2 +- jraft-rheakv/pom.xml | 2 +- jraft-rheakv/rheakv-core/pom.xml | 2 +- jraft-rheakv/rheakv-pd/pom.xml | 2 +- jraft-test/pom.xml | 2 +- pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jraft-core/pom.xml b/jraft-core/pom.xml index 1dc126fd2..df1313958 100644 --- a/jraft-core/pom.xml +++ b/jraft-core/pom.xml @@ -5,7 +5,7 @@ jraft-parent com.alipay.sofa - 1.2.3 + 1.2.4 jraft-core jar diff --git a/jraft-example/pom.xml b/jraft-example/pom.xml index 407aef083..c10b57828 100644 --- a/jraft-example/pom.xml +++ b/jraft-example/pom.xml @@ -5,7 +5,7 @@ jraft-parent com.alipay.sofa - 1.2.3 + 1.2.4 jraft-example jar diff --git a/jraft-rheakv/pom.xml b/jraft-rheakv/pom.xml index d60923a8b..6e67527f0 100644 --- a/jraft-rheakv/pom.xml +++ b/jraft-rheakv/pom.xml @@ -4,7 +4,7 @@ jraft-parent com.alipay.sofa - 1.2.3 + 1.2.4 jraft-rheakv diff --git a/jraft-rheakv/rheakv-core/pom.xml b/jraft-rheakv/rheakv-core/pom.xml index 511d091d9..a51c69c57 100644 --- a/jraft-rheakv/rheakv-core/pom.xml +++ b/jraft-rheakv/rheakv-core/pom.xml @@ -4,7 +4,7 @@ jraft-rheakv com.alipay.sofa - 1.2.3 + 1.2.4 jraft-rheakv-core diff --git a/jraft-rheakv/rheakv-pd/pom.xml b/jraft-rheakv/rheakv-pd/pom.xml index df4e0977e..d7b6e0070 100644 --- a/jraft-rheakv/rheakv-pd/pom.xml +++ b/jraft-rheakv/rheakv-pd/pom.xml @@ -4,7 +4,7 @@ jraft-rheakv com.alipay.sofa - 1.2.3 + 1.2.4 jraft-rheakv-pd diff --git a/jraft-test/pom.xml b/jraft-test/pom.xml index 7b4ef3d47..f03015e45 100644 --- a/jraft-test/pom.xml +++ b/jraft-test/pom.xml @@ -5,7 +5,7 @@ jraft-parent com.alipay.sofa - 1.2.3 + 1.2.4 jraft-test jar diff --git a/pom.xml b/pom.xml index 0c9f371db..2f1b8a189 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.alipay.sofa jraft-parent - 1.2.3 + 1.2.4 pom ${project.groupId}:${project.artifactId} From 19726fad18ed7281f5abcc828107346e02fe4005 Mon Sep 17 00:00:00 2001 From: block Date: Tue, 26 Mar 2019 13:40:07 +0800 Subject: [PATCH 21/31] (fixbug) https://github.com/alipay/sofa-jraft/issues/64 (#66) --- .../main/java/com/alipay/sofa/jraft/core/NodeImpl.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java index 157807ae1..783cd8067 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java @@ -1070,15 +1070,15 @@ private void executeApplyingTasks(List tasks) { } continue; } - // set task entry info before adding to list. - task.entry.getId().setTerm(currTerm); - task.entry.setType(EnumOutter.EntryType.ENTRY_TYPE_DATA); - entries.add(task.entry); if (!this.ballotBox.appendPendingTask(this.conf.getConf(), conf.isStable() ? null : conf.getOldConf(), task.done)) { Utils.runClosureInThread(task.done, new Status(RaftError.EINTERNAL, "Fail to append task.")); - return; + continue; } + // set task entry info before adding to list. + task.entry.getId().setTerm(currTerm); + task.entry.setType(EnumOutter.EntryType.ENTRY_TYPE_DATA); + entries.add(task.entry); } this.logManager.appendEntries(entries, new LeaderStableClosure(entries)); // update conf.first From d61ffeeed7aebcd5bd1f0fd7962c1f0c6b3a798b Mon Sep 17 00:00:00 2001 From: block Date: Tue, 26 Mar 2019 20:56:00 +0800 Subject: [PATCH 22/31] (fix) optimize 'delete range with memory db' (#68) --- .../com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java index 483834212..620fbb423 100644 --- a/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java +++ b/jraft-rheakv/rheakv-core/src/main/java/com/alipay/sofa/jraft/rhea/storage/MemoryRawKVStore.java @@ -576,8 +576,8 @@ public void deleteRange(final byte[] startKey, final byte[] endKey, final KVStor final Timer.Context timeCtx = getTimeContext("DELETE_RANGE"); try { final ConcurrentNavigableMap subMap = this.defaultDB.subMap(startKey, endKey); - for (final byte[] key : subMap.keySet()) { - this.defaultDB.remove(key); + if (!subMap.isEmpty()) { + subMap.clear(); } setSuccess(closure, Boolean.TRUE); } catch (final Exception e) { From e59cae61ee68d4985849f27dc37252a3d25d350c Mon Sep 17 00:00:00 2001 From: block Date: Wed, 27 Mar 2019 09:52:12 +0800 Subject: [PATCH 23/31] (fix) isolate jmh and unit_test #69 (#70) --- jraft-core/pom.xml | 10 ---------- jraft-example/pom.xml | 9 +++++++++ .../jraft/benchmark/common}/AsciiCodecBenchmark.java | 2 +- .../jraft/benchmark/common}/Utf8CodecBenchmark.java | 2 +- .../sofa/jraft/benchmark/common}/VarIntsBenchmark.java | 4 +++- .../alipay/sofa/jraft/benchmark/kv}/BenchmarkUtil.java | 2 +- .../jraft/benchmark/kv}/raw/BaseRawStoreBenchmark.java | 2 +- .../benchmark/kv}/raw/RawKVApproximateBenchmark.java | 6 +++--- .../jraft/benchmark/kv}/raw/RawKVGetBenchmark.java | 8 ++++---- .../jraft/benchmark/kv}/raw/RawKVPutBenchmark.java | 8 ++++---- .../jraft/benchmark/kv}/raw/SnapshotBenchmark.java | 6 +++--- .../jraft/benchmark/kv}/rhea/RheaBenchmarkCluster.java | 2 +- .../jraft/benchmark/kv}/rhea/RheaKVGetBenchmark.java | 8 ++++---- .../jraft/benchmark/kv}/rhea/RheaKVPutBenchmark.java | 8 ++++---- jraft-rheakv/rheakv-core/pom.xml | 9 --------- 15 files changed, 39 insertions(+), 47 deletions(-) rename {jraft-core/src/test/java/com/alipay/sofa/jraft/entity => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common}/AsciiCodecBenchmark.java (98%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common}/Utf8CodecBenchmark.java (98%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common}/VarIntsBenchmark.java (98%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/BenchmarkUtil.java (96%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/raw/BaseRawStoreBenchmark.java (98%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/raw/RawKVApproximateBenchmark.java (95%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/raw/RawKVGetBenchmark.java (94%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/raw/RawKVPutBenchmark.java (93%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/raw/SnapshotBenchmark.java (97%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/rhea/RheaBenchmarkCluster.java (99%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/rhea/RheaKVGetBenchmark.java (96%) rename {jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark => jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv}/rhea/RheaKVPutBenchmark.java (93%) diff --git a/jraft-core/pom.xml b/jraft-core/pom.xml index df1313958..ddf7b2cd1 100644 --- a/jraft-core/pom.xml +++ b/jraft-core/pom.xml @@ -110,15 +110,5 @@ io.dropwizard.metrics metrics-core - - - - org.openjdk.jmh - jmh-core - - - org.openjdk.jmh - jmh-generator-annprocess - diff --git a/jraft-example/pom.xml b/jraft-example/pom.xml index c10b57828..47ff9004d 100644 --- a/jraft-example/pom.xml +++ b/jraft-example/pom.xml @@ -40,6 +40,15 @@ log4j-jcl ${log4j.version} + + + org.openjdk.jmh + jmh-core + + + org.openjdk.jmh + jmh-generator-annprocess + diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/entity/AsciiCodecBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/AsciiCodecBenchmark.java similarity index 98% rename from jraft-core/src/test/java/com/alipay/sofa/jraft/entity/AsciiCodecBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/AsciiCodecBenchmark.java index 564f66dd4..806134364 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/entity/AsciiCodecBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/AsciiCodecBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.entity; +package com.alipay.sofa.jraft.benchmark.common; import java.util.concurrent.TimeUnit; diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/Utf8CodecBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/Utf8CodecBenchmark.java similarity index 98% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/Utf8CodecBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/Utf8CodecBenchmark.java index 7e04f8cf5..3163d273f 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/Utf8CodecBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/Utf8CodecBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.util; +package com.alipay.sofa.jraft.benchmark.common; import java.nio.charset.StandardCharsets; import java.util.UUID; diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/VarIntsBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/VarIntsBenchmark.java similarity index 98% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/VarIntsBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/VarIntsBenchmark.java index fd7a718e7..c22f47609 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/VarIntsBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/VarIntsBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.util; +package com.alipay.sofa.jraft.benchmark.common; import java.util.concurrent.TimeUnit; @@ -30,6 +30,8 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.TimeValue; +import com.alipay.sofa.jraft.rhea.util.VarInts; + /** * @author jiachun.fjc */ diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/BenchmarkUtil.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/BenchmarkUtil.java similarity index 96% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/BenchmarkUtil.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/BenchmarkUtil.java index f950375be..7e00e1b68 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/BenchmarkUtil.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/BenchmarkUtil.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark; +package com.alipay.sofa.jraft.benchmark.kv; import java.util.concurrent.ThreadLocalRandom; diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/BaseRawStoreBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/BaseRawStoreBenchmark.java similarity index 98% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/BaseRawStoreBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/BaseRawStoreBenchmark.java index d87f48d54..ba7e396cd 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/BaseRawStoreBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/BaseRawStoreBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark.raw; +package com.alipay.sofa.jraft.benchmark.kv.raw; import java.io.File; import java.io.IOException; diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVApproximateBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVApproximateBenchmark.java similarity index 95% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVApproximateBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVApproximateBenchmark.java index fda16a8e3..d59839165 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVApproximateBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVApproximateBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark.raw; +package com.alipay.sofa.jraft.benchmark.kv.raw; import java.util.concurrent.TimeUnit; @@ -34,8 +34,8 @@ import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.CONCURRENCY; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.CONCURRENCY; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVGetBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVGetBenchmark.java similarity index 94% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVGetBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVGetBenchmark.java index 8eec32ed0..56e9efac2 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVGetBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVGetBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark.raw; +package com.alipay.sofa.jraft.benchmark.kv.raw; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @@ -38,9 +38,9 @@ import com.alipay.sofa.jraft.rhea.util.Lists; import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.CONCURRENCY; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.CONCURRENCY; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVPutBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVPutBenchmark.java similarity index 93% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVPutBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVPutBenchmark.java index b705e1aa8..adb274e5a 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVPutBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVPutBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark.raw; +package com.alipay.sofa.jraft.benchmark.kv.raw; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -35,9 +35,9 @@ import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.CONCURRENCY; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.CONCURRENCY; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/SnapshotBenchmark.java similarity index 97% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/SnapshotBenchmark.java index f5241c59a..4a27c08f3 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/SnapshotBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark.raw; +package com.alipay.sofa.jraft.benchmark.kv.raw; import java.io.File; import java.io.FileOutputStream; @@ -33,8 +33,8 @@ import com.alipay.sofa.jraft.rhea.util.ZipUtil; import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaBenchmarkCluster.java similarity index 99% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaBenchmarkCluster.java index 9b51c6298..4977eb285 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaBenchmarkCluster.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark.rhea; +package com.alipay.sofa.jraft.benchmark.kv.rhea; import java.io.File; import java.io.IOException; diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVGetBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVGetBenchmark.java similarity index 96% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVGetBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVGetBenchmark.java index bf52c28be..28be34696 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVGetBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVGetBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark.rhea; +package com.alipay.sofa.jraft.benchmark.kv.rhea; import java.io.IOException; import java.util.List; @@ -35,14 +35,14 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.TimeValue; -import com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil; +import com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil; import com.alipay.sofa.jraft.rhea.client.RheaKVStore; import com.alipay.sofa.jraft.rhea.storage.KVEntry; import com.alipay.sofa.jraft.rhea.util.Lists; import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVPutBenchmark.java b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVPutBenchmark.java similarity index 93% rename from jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVPutBenchmark.java rename to jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVPutBenchmark.java index 15bd55646..60cb4ecaf 100644 --- a/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVPutBenchmark.java +++ b/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVPutBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.rhea.benchmark.rhea; +package com.alipay.sofa.jraft.benchmark.kv.rhea; import java.io.IOException; import java.util.concurrent.ThreadLocalRandom; @@ -37,9 +37,9 @@ import com.alipay.sofa.jraft.rhea.client.RheaKVStore; import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.CONCURRENCY; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.CONCURRENCY; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-rheakv/rheakv-core/pom.xml b/jraft-rheakv/rheakv-core/pom.xml index a51c69c57..280eb1c5a 100644 --- a/jraft-rheakv/rheakv-core/pom.xml +++ b/jraft-rheakv/rheakv-core/pom.xml @@ -72,14 +72,5 @@ org.mockito mockito-all - - - org.openjdk.jmh - jmh-core - - - org.openjdk.jmh - jmh-generator-annprocess - From 802310d478c31f48f1c4a1987951a4187de3a9de Mon Sep 17 00:00:00 2001 From: block Date: Wed, 27 Mar 2019 13:06:29 +0800 Subject: [PATCH 24/31] (fix) https://github.com/alipay/sofa-jraft/issues/65 (fix) #65 (#67) * (fix) https://github.com/alipay/sofa-jraft/issues/65 * (fix) optimize 'truncate log' https://github.com/alipay/sofa-jraft/issues/65 * (fix) typo and expose removeRange method * (fix) avoid unnecessary array copy * (fix) add fail status message * (fix) typo * (fix) minor fix --- .../com/alipay/sofa/jraft/core/BallotBox.java | 24 +++++------ .../alipay/sofa/jraft/core/Replicator.java | 2 +- .../jraft/storage/impl/LogManagerImpl.java | 43 +++++++++++-------- .../snapshot/local/LocalSnapshotCopier.java | 4 +- .../{ArrayDequeue.java => ArrayDeque.java} | 24 ++++++++++- .../com/alipay/sofa/jraft/core/NodeTest.java | 2 +- .../sofa/jraft/core/ReplicatorTest.java | 4 +- ...ayDequeueTest.java => ArrayDequeTest.java} | 4 +- 8 files changed, 66 insertions(+), 41 deletions(-) rename jraft-core/src/main/java/com/alipay/sofa/jraft/util/{ArrayDequeue.java => ArrayDeque.java} (70%) rename jraft-core/src/test/java/com/alipay/sofa/jraft/util/{ArrayDequeueTest.java => ArrayDequeTest.java} (96%) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java index eff5967ab..9c56b9295 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java @@ -31,7 +31,7 @@ import com.alipay.sofa.jraft.entity.Ballot; import com.alipay.sofa.jraft.entity.PeerId; import com.alipay.sofa.jraft.option.BallotBoxOptions; -import com.alipay.sofa.jraft.util.ArrayDequeue; +import com.alipay.sofa.jraft.util.ArrayDeque; import com.alipay.sofa.jraft.util.OnlyForTest; import com.alipay.sofa.jraft.util.Requires; @@ -44,14 +44,14 @@ @ThreadSafe public class BallotBox implements Lifecycle { - private static final Logger LOG = LoggerFactory.getLogger(BallotBox.class); + private static final Logger LOG = LoggerFactory.getLogger(BallotBox.class); - private FSMCaller waiter; - private ClosureQueue closureQueue; - private final StampedLock stampedLock = new StampedLock(); - private long lastCommittedIndex = 0; - private long pendingIndex; - private final ArrayDequeue pendingMetaQueue = new ArrayDequeue<>(); + private FSMCaller waiter; + private ClosureQueue closureQueue; + private final StampedLock stampedLock = new StampedLock(); + private long lastCommittedIndex = 0; + private long pendingIndex; + private final ArrayDeque pendingMetaQueue = new ArrayDeque<>(); @OnlyForTest long getPendingIndex() { @@ -59,7 +59,7 @@ long getPendingIndex() { } @OnlyForTest - ArrayDequeue getPendingMetaQueue() { + ArrayDeque getPendingMetaQueue() { return this.pendingMetaQueue; } @@ -126,10 +126,8 @@ public boolean commitAt(long firstLogIndex, long lastLogIndex, PeerId peer) { // logs, since we use the new configuration to deal the quorum of the // removal request, we think it's safe to commit all the uncommitted // previous logs, which is not well proved right now - for (long index = pendingIndex; index <= lastCommittedIndex; index++) { - pendingMetaQueue.pollFirst(); - LOG.debug("Committed log index={}", index); - } + pendingMetaQueue.removeRange(0, (int) (lastCommittedIndex - pendingIndex) + 1); + LOG.debug("Committed log fromIndex={}, toIndex={}.", pendingIndex, lastCommittedIndex); pendingIndex = lastCommittedIndex + 1; this.lastCommittedIndex = lastCommittedIndex; } finally { diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java index 6d8ade989..a19728139 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/core/Replicator.java @@ -1192,7 +1192,7 @@ private static boolean onAppendEntriesReturned(ThreadId id, Inflight inflight, S r.notifyOnCaughtUp(RaftError.EPERM.getNumber(), true); r.destroy(); node.increaseTermTo(response.getTerm(), new Status(RaftError.EHIGHERTERMRESPONSE, - "Leader receives higher term hearbeat_response from peer:%s", r.options.getPeerId())); + "Leader receives higher term heartbeat_response from peer:%s", r.options.getPeerId())); return false; } if (isLogDebugEnabled) { diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java index ec48812b5..86507128d 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; @@ -48,7 +47,7 @@ import com.alipay.sofa.jraft.option.RaftOptions; import com.alipay.sofa.jraft.storage.LogManager; import com.alipay.sofa.jraft.storage.LogStorage; -import com.alipay.sofa.jraft.util.ArrayDequeue; +import com.alipay.sofa.jraft.util.ArrayDeque; import com.alipay.sofa.jraft.util.LogExceptionHandler; import com.alipay.sofa.jraft.util.NamedThreadFactory; import com.alipay.sofa.jraft.util.Requires; @@ -81,7 +80,7 @@ public class LogManagerImpl implements LogManager { private LogId diskId = new LogId(0, 0); private LogId appliedId = new LogId(0, 0); //TODO use a lock-free concurrent list instead? - private ArrayDequeue logsInMemory = new ArrayDequeue<>(); + private ArrayDeque logsInMemory = new ArrayDeque<>(); private volatile long firstLogIndex; private volatile long lastLogIndex; private volatile LogId lastSnapshotId = new LogId(0, 0); @@ -226,13 +225,15 @@ public void shutdown() { private void clearMemoryLogs(LogId id) { writeLock.lock(); try { - final Iterator it = this.logsInMemory.iterator(); - while (it.hasNext()) { - final LogEntry entry = it.next(); + int index = 0; + for (final int size = this.logsInMemory.size(); index < size; index++) { + final LogEntry entry = this.logsInMemory.get(index); if (entry.getId().compareTo(id) > 0) { break; } - it.remove(); + } + if (index > 0) { + this.logsInMemory.removeRange(0, index); } } finally { writeLock.unlock(); @@ -618,10 +619,12 @@ protected LogEntry getEntryFromMemory(long index) { if (!this.logsInMemory.isEmpty()) { final long firstIndex = this.logsInMemory.peekFirst().getId().getIndex(); final long lastIndex = this.logsInMemory.peekLast().getId().getIndex(); - Requires.requireTrue(lastIndex - firstIndex + 1 == this.logsInMemory.size(), - "lastIndex=%d,firstIndex=%d,logsInMemory=[%s]", lastIndex, firstIndex, descLogsInMemory()); + if (lastIndex - firstIndex + 1 != this.logsInMemory.size()) { + throw new IllegalStateException(String.format("lastIndex=%d,firstIndex=%d,logsInMemory=[%s]", + lastIndex, firstIndex, descLogsInMemory())); + } if (index >= firstIndex && index <= lastIndex) { - entry = logsInMemory.get((int) (index - firstIndex)); + entry = this.logsInMemory.get((int) (index - firstIndex)); } } return entry; @@ -808,16 +811,18 @@ public void run(Status status) { } private boolean truncatePrefix(long firstIndexKept) { - while (!this.logsInMemory.isEmpty()) { - final LogEntry entry = this.logsInMemory.peekFirst(); - if (entry.getId().getIndex() < firstIndexKept) { - this.logsInMemory.pollFirst(); - } else { + int index = 0; + for (final int size = this.logsInMemory.size(); index < size; index++) { + final LogEntry entry = this.logsInMemory.get(index); + if (entry.getId().getIndex() >= firstIndexKept) { break; } } + if (index > 0) { + this.logsInMemory.removeRange(0, index); + } - //TODO maybe it's fine here + // TODO maybe it's fine here Requires.requireTrue(firstIndexKept >= this.firstLogIndex, "Try to truncate logs before %d, but the firstLogIndex is %d", firstIndexKept, firstLogIndex); @@ -836,7 +841,7 @@ private boolean truncatePrefix(long firstIndexKept) { private boolean reset(long nextLogIndex) { writeLock.lock(); try { - this.logsInMemory = new ArrayDequeue<>(); + this.logsInMemory = new ArrayDeque<>(); this.firstLogIndex = nextLogIndex; this.lastLogIndex = nextLogIndex - 1; configManager.truncatePrefix(this.firstLogIndex); @@ -874,7 +879,7 @@ private void unsafeTruncateSuffix(long lastIndexKept) { @SuppressWarnings("NonAtomicOperationOnVolatileField") private boolean checkAndResolveConflict(List entries, StableClosure done) { - final LogEntry firstLogEntry = ArrayDequeue.peekFirst(entries); + final LogEntry firstLogEntry = ArrayDeque.peekFirst(entries); if (firstLogEntry.getId().getIndex() == 0) { // Node is currently the leader and |entries| are from the user who // don't know the correct indexes the logs should assign to. So we have @@ -894,7 +899,7 @@ private boolean checkAndResolveConflict(List entries, StableClosure do return false; } final long appliedIndex = appliedId.getIndex(); - final LogEntry lastLogEntry = ArrayDequeue.peekLast(entries); + final LogEntry lastLogEntry = ArrayDeque.peekLast(entries); if (lastLogEntry.getId().getIndex() <= appliedIndex) { LOG.warn( "Received entries of which the lastLog={} is not greater than appliedIndex={}, return immediately with nothing changed.", diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java index 30e528478..303627846 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java @@ -42,7 +42,7 @@ import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; import com.alipay.sofa.jraft.storage.snapshot.remote.RemoteFileCopier; import com.alipay.sofa.jraft.storage.snapshot.remote.Session; -import com.alipay.sofa.jraft.util.ArrayDequeue; +import com.alipay.sofa.jraft.util.ArrayDeque; import com.alipay.sofa.jraft.util.ByteBufferCollector; import com.alipay.sofa.jraft.util.Requires; import com.alipay.sofa.jraft.util.Utils; @@ -222,7 +222,7 @@ private void loadMetaTable() throws InterruptedException { boolean filterBeforeCopy(LocalSnapshotWriter writer, SnapshotReader lastSnapshot) throws IOException { final Set existingFiles = writer.listFiles(); - final ArrayDequeue toRemove = new ArrayDequeue<>(); + final ArrayDeque toRemove = new ArrayDeque<>(); for (final String file : existingFiles) { if (this.remoteSnapshot.getFileMeta(file) == null) { toRemove.add(file); diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/ArrayDequeue.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/ArrayDeque.java similarity index 70% rename from jraft-core/src/main/java/com/alipay/sofa/jraft/util/ArrayDequeue.java rename to jraft-core/src/main/java/com/alipay/sofa/jraft/util/ArrayDeque.java index 35bdb1219..d281844a7 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/ArrayDequeue.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/ArrayDeque.java @@ -26,7 +26,7 @@ * 2018-Apr-11 11:14:38 AM * @param */ -public class ArrayDequeue extends java.util.ArrayList { +public class ArrayDeque extends java.util.ArrayList { private static final long serialVersionUID = -4929318149975955629L; @@ -85,4 +85,26 @@ public E pollFirst() { public E pollLast() { return pollLast(this); } + + /** + * Expose this methods so we not need to create a new subList just to + * remove a range of elements. + * + * Removes from this deque all of the elements whose index is between + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. + * Shifts any succeeding elements to the left (reduces their index). + * This call shortens the deque by {@code (toIndex - fromIndex)} elements. + * (If {@code toIndex==fromIndex}, this operation has no effect.) + * + * @throws IndexOutOfBoundsException if {@code fromIndex} or + * {@code toIndex} is out of range + * ({@code fromIndex < 0 || + * fromIndex >= size() || + * toIndex > size() || + * toIndex < fromIndex}) + */ + @Override + public void removeRange(int fromIndex, int toIndex) { + super.removeRange(fromIndex, toIndex); + } } diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java index c3ab1dae0..00b85f907 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java @@ -338,7 +338,7 @@ private void readIndexRandom(final TestCluster cluster) { @Override public void run(Status status, long index, byte[] reqCtx) { - assertTrue(status.isOk()); + assertTrue(status.toString(), status.isOk()); assertTrue(index > 0); assertArrayEquals(requestContext, reqCtx); readLatch.countDown(); diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java index b38df3738..943a4a628 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/ReplicatorTest.java @@ -183,7 +183,7 @@ public void testOnRpcReturnedTermMismatch() { Utils.monotonicMs()); Mockito.verify(this.node).increaseTermTo( 2, - new Status(RaftError.EHIGHERTERMRESPONSE, "Leader receives higher term hearbeat_response from peer:%s", + new Status(RaftError.EHIGHERTERMRESPONSE, "Leader receives higher term heartbeat_response from peer:%s", peerId)); assertNull(r.id); } @@ -428,7 +428,7 @@ public void testOnHeartbeatReturnedTermMismatch() { Replicator.onHeartbeatReturned(this.id, Status.OK(), request, response, Utils.monotonicMs()); Mockito.verify(this.node).increaseTermTo( 2, - new Status(RaftError.EHIGHERTERMRESPONSE, "Leader receives higher term hearbeat_response from peer:%s", + new Status(RaftError.EHIGHERTERMRESPONSE, "Leader receives higher term heartbeat_response from peer:%s", peerId)); assertNull(r.id); } diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/util/ArrayDequeueTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/util/ArrayDequeTest.java similarity index 96% rename from jraft-core/src/test/java/com/alipay/sofa/jraft/util/ArrayDequeueTest.java rename to jraft-core/src/test/java/com/alipay/sofa/jraft/util/ArrayDequeTest.java index 864bd01c4..4cfae040c 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/util/ArrayDequeueTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/util/ArrayDequeTest.java @@ -22,11 +22,11 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -public class ArrayDequeueTest { +public class ArrayDequeTest { @Test public void testPeekPoll() { - ArrayDequeue list = new ArrayDequeue<>(); + ArrayDeque list = new ArrayDeque<>(); for (int i = 0; i < 10; i++) { list.add(i); } From f2e9d8bc2522450568edcdb899c14918bc09291b Mon Sep 17 00:00:00 2001 From: block Date: Thu, 28 Mar 2019 11:31:51 +0800 Subject: [PATCH 25/31] fix/jmh conflict unittest (#71) * (fix) isolate jmh and unit_test #69 * (fix) update maven-compiler-plugin version to 3.8.0 #69 * (fix) update maven-compiler-plugin version to 3.8.0 #69 * (fix) recover jmh code --- jraft-core/pom.xml | 19 +++++++++---------- .../sofa/jraft/util}/AsciiCodecBenchmark.java | 4 +--- .../sofa/jraft/util}/Utf8CodecBenchmark.java | 5 +---- jraft-example/pom.xml | 9 --------- jraft-rheakv/rheakv-core/pom.xml | 9 +++++++++ .../jraft/rhea/benchmark}/BenchmarkUtil.java | 2 +- .../benchmark}/raw/BaseRawStoreBenchmark.java | 2 +- .../raw/RawKVApproximateBenchmark.java | 6 +++--- .../benchmark}/raw/RawKVGetBenchmark.java | 8 ++++---- .../benchmark}/raw/RawKVPutBenchmark.java | 8 ++++---- .../benchmark}/raw/SnapshotBenchmark.java | 6 +++--- .../benchmark}/rhea/RheaBenchmarkCluster.java | 2 +- .../benchmark}/rhea/RheaKVGetBenchmark.java | 8 ++++---- .../benchmark}/rhea/RheaKVPutBenchmark.java | 8 ++++---- .../jraft/rhea/util}/VarIntsBenchmark.java | 4 +--- pom.xml | 2 +- 16 files changed, 47 insertions(+), 55 deletions(-) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common => jraft-core/src/test/java/com/alipay/sofa/jraft/util}/AsciiCodecBenchmark.java (96%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common => jraft-core/src/test/java/com/alipay/sofa/jraft/util}/Utf8CodecBenchmark.java (96%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/BenchmarkUtil.java (96%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/raw/BaseRawStoreBenchmark.java (98%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/raw/RawKVApproximateBenchmark.java (95%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/raw/RawKVGetBenchmark.java (94%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/raw/RawKVPutBenchmark.java (93%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/raw/SnapshotBenchmark.java (97%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/rhea/RheaBenchmarkCluster.java (99%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/rhea/RheaKVGetBenchmark.java (96%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark}/rhea/RheaKVPutBenchmark.java (93%) rename {jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common => jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util}/VarIntsBenchmark.java (98%) diff --git a/jraft-core/pom.xml b/jraft-core/pom.xml index ddf7b2cd1..4cb23de05 100644 --- a/jraft-core/pom.xml +++ b/jraft-core/pom.xml @@ -12,7 +12,6 @@ jraft-core ${project.version} - junit @@ -24,18 +23,15 @@ junit-dep test - com.google.code.findbugs jsr305 - org.rocksdb rocksdbjni - org.mockito @@ -47,13 +43,11 @@ powermock-api-mockito test - org.powermock powermock-module-junit4 test - org.slf4j @@ -75,7 +69,6 @@ org.apache.logging.log4j log4j-jcl - com.lmax disruptor @@ -84,7 +77,6 @@ com.google.protobuf protobuf-java - commons-io @@ -94,7 +86,6 @@ commons-lang commons-lang - com.alipay.sofa @@ -104,11 +95,19 @@ com.alipay.sofa hessian - io.dropwizard.metrics metrics-core + + + org.openjdk.jmh + jmh-core + + + org.openjdk.jmh + jmh-generator-annprocess + diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/AsciiCodecBenchmark.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/util/AsciiCodecBenchmark.java similarity index 96% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/AsciiCodecBenchmark.java rename to jraft-core/src/test/java/com/alipay/sofa/jraft/util/AsciiCodecBenchmark.java index 806134364..be2162e1c 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/AsciiCodecBenchmark.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/util/AsciiCodecBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.common; +package com.alipay.sofa.jraft.util; import java.util.concurrent.TimeUnit; @@ -30,8 +30,6 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.TimeValue; -import com.alipay.sofa.jraft.util.AsciiStringUtil; - /** * @author jiachun.fjc */ diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/Utf8CodecBenchmark.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/util/Utf8CodecBenchmark.java similarity index 96% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/Utf8CodecBenchmark.java rename to jraft-core/src/test/java/com/alipay/sofa/jraft/util/Utf8CodecBenchmark.java index 3163d273f..ed6d6eb30 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/Utf8CodecBenchmark.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/util/Utf8CodecBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.common; +package com.alipay.sofa.jraft.util; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -33,9 +33,6 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.TimeValue; -import com.alipay.sofa.jraft.util.BytesUtil; -import com.alipay.sofa.jraft.util.Utils; - /** * @author jiachun.fjc */ diff --git a/jraft-example/pom.xml b/jraft-example/pom.xml index 47ff9004d..c10b57828 100644 --- a/jraft-example/pom.xml +++ b/jraft-example/pom.xml @@ -40,15 +40,6 @@ log4j-jcl ${log4j.version} - - - org.openjdk.jmh - jmh-core - - - org.openjdk.jmh - jmh-generator-annprocess - diff --git a/jraft-rheakv/rheakv-core/pom.xml b/jraft-rheakv/rheakv-core/pom.xml index 280eb1c5a..a51c69c57 100644 --- a/jraft-rheakv/rheakv-core/pom.xml +++ b/jraft-rheakv/rheakv-core/pom.xml @@ -72,5 +72,14 @@ org.mockito mockito-all + + + org.openjdk.jmh + jmh-core + + + org.openjdk.jmh + jmh-generator-annprocess + diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/BenchmarkUtil.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/BenchmarkUtil.java similarity index 96% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/BenchmarkUtil.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/BenchmarkUtil.java index 7e00e1b68..f950375be 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/BenchmarkUtil.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/BenchmarkUtil.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv; +package com.alipay.sofa.jraft.rhea.benchmark; import java.util.concurrent.ThreadLocalRandom; diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/BaseRawStoreBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/BaseRawStoreBenchmark.java similarity index 98% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/BaseRawStoreBenchmark.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/BaseRawStoreBenchmark.java index ba7e396cd..d87f48d54 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/BaseRawStoreBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/BaseRawStoreBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv.raw; +package com.alipay.sofa.jraft.rhea.benchmark.raw; import java.io.File; import java.io.IOException; diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVApproximateBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVApproximateBenchmark.java similarity index 95% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVApproximateBenchmark.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVApproximateBenchmark.java index d59839165..fda16a8e3 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVApproximateBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVApproximateBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv.raw; +package com.alipay.sofa.jraft.rhea.benchmark.raw; import java.util.concurrent.TimeUnit; @@ -34,8 +34,8 @@ import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.CONCURRENCY; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.CONCURRENCY; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVGetBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVGetBenchmark.java similarity index 94% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVGetBenchmark.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVGetBenchmark.java index 56e9efac2..8eec32ed0 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVGetBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVGetBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv.raw; +package com.alipay.sofa.jraft.rhea.benchmark.raw; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @@ -38,9 +38,9 @@ import com.alipay.sofa.jraft.rhea.util.Lists; import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.CONCURRENCY; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.CONCURRENCY; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVPutBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVPutBenchmark.java similarity index 93% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVPutBenchmark.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVPutBenchmark.java index adb274e5a..b705e1aa8 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/RawKVPutBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/RawKVPutBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv.raw; +package com.alipay.sofa.jraft.rhea.benchmark.raw; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -35,9 +35,9 @@ import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.CONCURRENCY; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.CONCURRENCY; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/SnapshotBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java similarity index 97% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/SnapshotBenchmark.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java index 4a27c08f3..f5241c59a 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/raw/SnapshotBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/raw/SnapshotBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv.raw; +package com.alipay.sofa.jraft.rhea.benchmark.raw; import java.io.File; import java.io.FileOutputStream; @@ -33,8 +33,8 @@ import com.alipay.sofa.jraft.rhea.util.ZipUtil; import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaBenchmarkCluster.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java similarity index 99% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaBenchmarkCluster.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java index 4977eb285..9b51c6298 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaBenchmarkCluster.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaBenchmarkCluster.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv.rhea; +package com.alipay.sofa.jraft.rhea.benchmark.rhea; import java.io.File; import java.io.IOException; diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVGetBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVGetBenchmark.java similarity index 96% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVGetBenchmark.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVGetBenchmark.java index 28be34696..bf52c28be 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVGetBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVGetBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv.rhea; +package com.alipay.sofa.jraft.rhea.benchmark.rhea; import java.io.IOException; import java.util.List; @@ -35,14 +35,14 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.TimeValue; -import com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil; +import com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil; import com.alipay.sofa.jraft.rhea.client.RheaKVStore; import com.alipay.sofa.jraft.rhea.storage.KVEntry; import com.alipay.sofa.jraft.rhea.util.Lists; import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVPutBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVPutBenchmark.java similarity index 93% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVPutBenchmark.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVPutBenchmark.java index 60cb4ecaf..15bd55646 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/kv/rhea/RheaKVPutBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/benchmark/rhea/RheaKVPutBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.kv.rhea; +package com.alipay.sofa.jraft.rhea.benchmark.rhea; import java.io.IOException; import java.util.concurrent.ThreadLocalRandom; @@ -37,9 +37,9 @@ import com.alipay.sofa.jraft.rhea.client.RheaKVStore; import com.alipay.sofa.jraft.util.BytesUtil; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.CONCURRENCY; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.KEY_COUNT; -import static com.alipay.sofa.jraft.benchmark.kv.BenchmarkUtil.VALUE_BYTES; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.CONCURRENCY; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.KEY_COUNT; +import static com.alipay.sofa.jraft.rhea.benchmark.BenchmarkUtil.VALUE_BYTES; /** * @author jiachun.fjc diff --git a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/VarIntsBenchmark.java b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/VarIntsBenchmark.java similarity index 98% rename from jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/VarIntsBenchmark.java rename to jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/VarIntsBenchmark.java index c22f47609..fd7a718e7 100644 --- a/jraft-example/src/test/java/com/alipay/sofa/jraft/benchmark/common/VarIntsBenchmark.java +++ b/jraft-rheakv/rheakv-core/src/test/java/com/alipay/sofa/jraft/rhea/util/VarIntsBenchmark.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.jraft.benchmark.common; +package com.alipay.sofa.jraft.rhea.util; import java.util.concurrent.TimeUnit; @@ -30,8 +30,6 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.TimeValue; -import com.alipay.sofa.jraft.rhea.util.VarInts; - /** * @author jiachun.fjc */ diff --git a/pom.xml b/pom.xml index 2f1b8a189..5d33d90b0 100644 --- a/pom.xml +++ b/pom.xml @@ -323,7 +323,7 @@ org.apache.maven.plugins maven-compiler-plugin - 2.3.2 + 3.8.0 ${java_source_version} ${java_target_version} From ce57d06e87b3857680446e3394b484bf07b50318 Mon Sep 17 00:00:00 2001 From: block Date: Thu, 28 Mar 2019 16:07:36 +0800 Subject: [PATCH 26/31] feat/tools (#73) * (fix) add tools dir * (fix) add .github * (fix) format * (fix) typo --- .github/ISSUE_TEMPLATE/Ask_Question.md | 27 ++ .github/ISSUE_TEMPLATE/Bug_Report.md | 25 ++ .github/PULL_REQUEST_TEMPLATE.md | 14 + .middleware-common/AlipayFormatter120.xml | 279 ------------------ .middleware-common/jmockit-coverage-1.14.jar | Bin 275079 -> 0 bytes .middleware-common/print_jmockit_result.sh | 14 - .travis.yml | 2 +- pom.xml | 11 +- {.middleware-common => tools}/check_format.sh | 0 HEADER => tools/codestyle/HEADER | 0 tools/codestyle/formatter.xml | 279 ++++++++++++++++++ 11 files changed, 352 insertions(+), 299 deletions(-) create mode 100755 .github/ISSUE_TEMPLATE/Ask_Question.md create mode 100755 .github/ISSUE_TEMPLATE/Bug_Report.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .middleware-common/AlipayFormatter120.xml delete mode 100644 .middleware-common/jmockit-coverage-1.14.jar delete mode 100644 .middleware-common/print_jmockit_result.sh rename {.middleware-common => tools}/check_format.sh (100%) rename HEADER => tools/codestyle/HEADER (100%) create mode 100644 tools/codestyle/formatter.xml diff --git a/.github/ISSUE_TEMPLATE/Ask_Question.md b/.github/ISSUE_TEMPLATE/Ask_Question.md new file mode 100755 index 000000000..5f5570890 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Ask_Question.md @@ -0,0 +1,27 @@ +--- +Name: Ask Question +About: Ask a question about usage or feature + +--- + +### Your question + +Describe your question clearly + +### Your scenes + +Describe your use scenes (why need this feature) + +### Your advice + +Describe the advice or solution you'd like + +### Environment + +- SOFAJRaft version: +- JVM version (e.g. `java -version`): +- OS version (e.g. `uname -a`): +- Maven version: +- IDE version: + + diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md new file mode 100755 index 000000000..3952cc121 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -0,0 +1,25 @@ +--- +Name: Bug Report +About: Create a report to help us improve + +--- + +### Describe the bug + +A clear and concise description of what the bug is. + +### Expected behavior + +### Actual behavior + +### Steps to reproduce + +### Minimal yet complete reproducer code (or GitHub URL to code) + +### Environment + +- SOFAJRaft version: +- JVM version (e.g. `java -version`): +- OS version (e.g. `uname -a`): +- Maven version: +- IDE version: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..f4c10ff9c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +### Motivation: + +Explain the context, and why you're making that change. +To make others understand what is the problem you're trying to solve. + +### Modification: + +Describe the idea and modifications you've done. + +### Result: + +Fixes #. + +If there is no issue then describe the changes introduced by this PR. diff --git a/.middleware-common/AlipayFormatter120.xml b/.middleware-common/AlipayFormatter120.xml deleted file mode 100644 index 9af7b622f..000000000 --- a/.middleware-common/AlipayFormatter120.xml +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.middleware-common/jmockit-coverage-1.14.jar b/.middleware-common/jmockit-coverage-1.14.jar deleted file mode 100644 index 309e0d4c8baf2304dd9fc0ced74c09c5eb38812c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275079 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/dev/null; then - alias tac="gtac" -fi - -if hash gsort 2>/dev/null; then - alias sort="gsort" -fi - -java -Dcoverage-outputDir=target -jar jmockit-coverage-1.14.jar target/coverage.ser -tac target/index.html | grep "Line segments" | head -1 | sed 's/.*Line segments: //g' | sed "s/'.*$//g" | awk 'BEGIN {FS = "/"} {print "Line Covered Percentage: "$1/$2*100"%"}' \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 4fc272076..f05e8cc25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V script: - - sh ./.middleware-common/check_format.sh + - sh ./tools/check_format.sh after_success: - travis_retry mvn --projects $TESTFOLDER clean test \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5d33d90b0..aa59cd814 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ pom ${project.groupId}:${project.artifactId} - An industrial-grade java implementation of RAFT consensus algorithm. + A production-grade java implementation of RAFT consensus algorithm. https://github.com/alipay/sofa-jraft @@ -68,6 +68,8 @@ 4.8.2 4.12 2.2 + + ${user.dir} 4.0.2 1.9.5 1.6.0 @@ -80,7 +82,6 @@ reuseReports target/jacoco-it.exec target/jacoco-ut.exec - @@ -378,7 +379,7 @@ true - + com.googlecode.maven-java-formatter-plugin maven-java-formatter-plugin @@ -391,7 +392,7 @@ - ${user.dir}/.middleware-common/AlipayFormatter120.xml + ${main.user.dir}/tools/codestyle/formatter.xml UTF-8 @@ -430,7 +431,7 @@ true -

${user.dir}/HEADER
+
${main.user.dir}/tools/codestyle/HEADER
**/src/main/java/** **/src/test/java/** diff --git a/.middleware-common/check_format.sh b/tools/check_format.sh similarity index 100% rename from .middleware-common/check_format.sh rename to tools/check_format.sh diff --git a/HEADER b/tools/codestyle/HEADER similarity index 100% rename from HEADER rename to tools/codestyle/HEADER diff --git a/tools/codestyle/formatter.xml b/tools/codestyle/formatter.xml new file mode 100644 index 000000000..c7289dd2c --- /dev/null +++ b/tools/codestyle/formatter.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9c469d9966c913cd1a0168dd57d35248da104bfa Mon Sep 17 00:00:00 2001 From: block Date: Thu, 28 Mar 2019 16:29:14 +0800 Subject: [PATCH 27/31] Update issue templates --- .github/ISSUE_TEMPLATE/Ask_Question.md | 9 +++++---- .github/ISSUE_TEMPLATE/Bug_Report.md | 7 +++++-- .github/ISSUE_TEMPLATE/ask-question.md | 28 ++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/bug-report.md | 28 ++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/ask-question.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md diff --git a/.github/ISSUE_TEMPLATE/Ask_Question.md b/.github/ISSUE_TEMPLATE/Ask_Question.md index 5f5570890..e454361e9 100755 --- a/.github/ISSUE_TEMPLATE/Ask_Question.md +++ b/.github/ISSUE_TEMPLATE/Ask_Question.md @@ -1,6 +1,9 @@ --- -Name: Ask Question -About: Ask a question about usage or feature +name: '' +about: '' +title: '' +labels: '' +assignees: '' --- @@ -23,5 +26,3 @@ Describe the advice or solution you'd like - OS version (e.g. `uname -a`): - Maven version: - IDE version: - - diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md index 3952cc121..91d4349b5 100755 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -1,6 +1,9 @@ --- -Name: Bug Report -About: Create a report to help us improve +name: '' +about: '' +title: '' +labels: '' +assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/ask-question.md b/.github/ISSUE_TEMPLATE/ask-question.md new file mode 100644 index 000000000..7beb0fdbf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ask-question.md @@ -0,0 +1,28 @@ +--- +name: Ask Question +about: Ask a question about usage or feature +title: '' +labels: '' +assignees: '' + +--- + +### Your question + +Describe your question clearly + +### Your scenes + +Describe your use scenes (why need this feature) + +### Your advice + +Describe the advice or solution you'd like + +### Environment + +- SOFAJRaft version: +- JVM version (e.g. `java -version`): +- OS version (e.g. `uname -a`): +- Maven version: +- IDE version: diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..4c32b6385 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,28 @@ +--- +name: Bug Report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +### Describe the bug + +A clear and concise description of what the bug is. + +### Expected behavior + +### Actual behavior + +### Steps to reproduce + +### Minimal yet complete reproducer code (or GitHub URL to code) + +### Environment + +- SOFAJRaft version: +- JVM version (e.g. `java -version`): +- OS version (e.g. `uname -a`): +- Maven version: +- IDE version: From f5fa9decaa751958448a5c5808a5e9e41834ac01 Mon Sep 17 00:00:00 2001 From: block Date: Thu, 28 Mar 2019 16:33:01 +0800 Subject: [PATCH 28/31] Update issue templates --- .github/ISSUE_TEMPLATE/Ask_Question.md | 28 -------------------------- .github/ISSUE_TEMPLATE/Bug_Report.md | 28 -------------------------- 2 files changed, 56 deletions(-) delete mode 100755 .github/ISSUE_TEMPLATE/Ask_Question.md delete mode 100755 .github/ISSUE_TEMPLATE/Bug_Report.md diff --git a/.github/ISSUE_TEMPLATE/Ask_Question.md b/.github/ISSUE_TEMPLATE/Ask_Question.md deleted file mode 100755 index e454361e9..000000000 --- a/.github/ISSUE_TEMPLATE/Ask_Question.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: '' -about: '' -title: '' -labels: '' -assignees: '' - ---- - -### Your question - -Describe your question clearly - -### Your scenes - -Describe your use scenes (why need this feature) - -### Your advice - -Describe the advice or solution you'd like - -### Environment - -- SOFAJRaft version: -- JVM version (e.g. `java -version`): -- OS version (e.g. `uname -a`): -- Maven version: -- IDE version: diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md deleted file mode 100755 index 91d4349b5..000000000 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: '' -about: '' -title: '' -labels: '' -assignees: '' - ---- - -### Describe the bug - -A clear and concise description of what the bug is. - -### Expected behavior - -### Actual behavior - -### Steps to reproduce - -### Minimal yet complete reproducer code (or GitHub URL to code) - -### Environment - -- SOFAJRaft version: -- JVM version (e.g. `java -version`): -- OS version (e.g. `uname -a`): -- Maven version: -- IDE version: From 688a7ece8d83f2b4687d26aac12e769c43aee2a8 Mon Sep 17 00:00:00 2001 From: block Date: Fri, 29 Mar 2019 08:07:19 +0800 Subject: [PATCH 29/31] Create CONTRIBUTING.md (#74) --- CONTRIBUTING.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..08ee8669d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +## Contributing to SOFAJRaft + +SOFAJRaft is released under the Apache 2.0 license, and follows a very +standard Github development process, using Github tracker for issues and +merging pull requests into master. If you would like to contribute something, +or simply want to hack on the code this document should help you get started. + +### Sign the Contributor License Agreement +Before we accept a non-trivial patch or pull request we will need you to +sign the Contributor License Agreement. Signing the contributor’s agreement +does not grant anyone commit rights to the main repository, but it does mean +that we can accept your contributions, and you will get an author credit if +we do. Active contributors might be asked to join the core team, and given +the ability to merge pull requests. + +### Code Conventions +None of these is essential for a pull request, but they will all help. + +1. we provided a [code formatter file](./tools/codestyle/formatter.xml), it +will formatting automatically your project when during process of building. + +2. Make sure all new `.java` files to have a simple Javadoc class comment +with at least an `@author` tag identifying you, and preferably at least a +paragraph on what the class is for. + +3. Add the ASF license header comment to all new `.java` files (copy from +existing files in the project) + +4. Add yourself as an `@author` to the `.java` files that you modify +substantially (more than cosmetic changes). + +5. Add some Javadocs. + +6. A few unit tests would help a lot as well — someone has to do it. + +7. When writing a commit message please follow [these conventions](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), +if you are fixing an existing issue please add Fixes gh-XXXX at the end of the commit message (where XXXX is the issue number). From fa23d592acb6adab8227bbc9281dc3f03390b11d Mon Sep 17 00:00:00 2001 From: block Date: Mon, 1 Apr 2019 10:55:32 +0800 Subject: [PATCH 30/31] fixbug/install snapshot bug (#80) * (fix) required eof * (fix) typo * (fix) read file bug * (fix) format * (fix) code format * (fix) NodeTest's code format * (fix) FileService unit test * (fix) large snapshot unit test * (fix) add install snapshot rpc timeout, default is 5 min * (fix) code format * (fix) an error log is needed on copy failed * (fix) code format * (fix) typo * (fix) add unit test: testInstallLargeSnapshot() * (fix) minor fix * (fix) add more detailed error log on copy failed --- .../alipay/sofa/jraft/option/RpcOptions.java | 19 +- .../rpc/impl/core/BoltRaftClientService.java | 2 +- .../sofa/jraft/storage/FileService.java | 35 +- .../sofa/jraft/storage/io/FileReader.java | 17 +- .../sofa/jraft/storage/io/LocalDirReader.java | 31 +- .../snapshot/local/SnapshotFileReader.java | 16 +- .../storage/snapshot/remote/BoltSession.java | 58 +- .../snapshot/remote/RemoteFileCopier.java | 12 +- .../sofa/jraft/util/ByteBufferCollector.java | 33 +- .../com/alipay/sofa/jraft/util/Utils.java | 2 +- .../com/alipay/sofa/jraft/core/NodeTest.java | 494 +++++++++++------- .../alipay/sofa/jraft/core/TestCluster.java | 11 +- .../sofa/jraft/storage/FileServiceTest.java | 46 +- 13 files changed, 488 insertions(+), 288 deletions(-) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/option/RpcOptions.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/option/RpcOptions.java index 6870069d0..e14fc7a85 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/option/RpcOptions.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/option/RpcOptions.java @@ -32,6 +32,12 @@ public class RpcOptions { */ private int rpcDefaultTimeout = 5000; + /** + * Install snapshot RPC request default timeout in milliseconds + * Default: 5 * 60 * 1000(5min) + */ + private int rpcInstallSnapshotTimeout = 5 * 60 * 1000; + /** * Rpc process thread pool size * Default: 80 @@ -75,10 +81,19 @@ public void setRpcDefaultTimeout(int rpcDefaultTimeout) { this.rpcDefaultTimeout = rpcDefaultTimeout; } + public int getRpcInstallSnapshotTimeout() { + return rpcInstallSnapshotTimeout; + } + + public void setRpcInstallSnapshotTimeout(int rpcInstallSnapshotTimeout) { + this.rpcInstallSnapshotTimeout = rpcInstallSnapshotTimeout; + } + @Override public String toString() { return "RpcOptions{" + "rpcConnectTimeoutMs=" + rpcConnectTimeoutMs + ", rpcDefaultTimeout=" - + rpcDefaultTimeout + ", rpcProcessorThreadPoolSize=" + rpcProcessorThreadPoolSize + ", metricRegistry=" - + metricRegistry + '}'; + + rpcDefaultTimeout + ", rpcInstallSnapshotTimeout=" + rpcInstallSnapshotTimeout + + ", rpcProcessorThreadPoolSize=" + rpcProcessorThreadPoolSize + ", metricRegistry=" + metricRegistry + + '}'; } } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/BoltRaftClientService.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/BoltRaftClientService.java index a9ed03893..bafde4c2c 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/BoltRaftClientService.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/BoltRaftClientService.java @@ -100,7 +100,7 @@ public Future getFile(Endpoint endpoint, GetFileRequest request, int ti @Override public Future installSnapshot(Endpoint endpoint, InstallSnapshotRequest request, RpcResponseClosure done) { - return invokeWithDone(endpoint, request, done, rpcOptions.getRpcDefaultTimeout()); + return invokeWithDone(endpoint, request, done, rpcOptions.getRpcInstallSnapshotTimeout()); } @Override diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/FileService.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/FileService.java index 753761aef..ac3d5f1a9 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/FileService.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/FileService.java @@ -55,7 +55,7 @@ public final class FileService { private static final FileService INSTANCE = new FileService(); private final ConcurrentMap fileReaderMap = new ConcurrentHashMap<>(); - private final AtomicLong nextId; + private final AtomicLong nextId = new AtomicLong(); /** * Retrieve the singleton instance of FileService. @@ -72,18 +72,16 @@ void clear() { } private FileService() { - this.nextId = new AtomicLong(); - final long initValue = Math.abs(Utils.getProcessId(ThreadLocalRandom.current().nextLong(10000, - Integer.MAX_VALUE)) << 45 - | System.nanoTime() << 17 >> 17); - this.nextId.set(initValue); - LOG.info("Initial file reader id in FileService is {}", this.nextId); + final long processId = Utils.getProcessId(ThreadLocalRandom.current().nextLong(10000, Integer.MAX_VALUE)); + final long initialValue = Math.abs(processId << 45 | System.nanoTime() << 17 >> 17); + this.nextId.set(initialValue); + LOG.info("Initial file reader id in FileService is {}", initialValue); } /** - * Handle GetFileRequest ,run the response or set the response with done. + * Handle GetFileRequest, run the response or set the response with done. */ - public Message handleGetFile(GetFileRequest request, RpcRequestClosure done) { + public Message handleGetFile(final GetFileRequest request, final RpcRequestClosure done) { if (request.getCount() <= 0 || request.getOffset() < 0) { return RpcResponseFactory.newResponse(RaftError.EREQUEST, "Invalid request: %s", request); } @@ -92,8 +90,11 @@ public Message handleGetFile(GetFileRequest request, RpcRequestClosure done) { return RpcResponseFactory.newResponse(RaftError.ENOENT, "Fail to find reader=%d", request.getReaderId()); } - LOG.debug("GetFile from {} path={} filename={} offset={} count={}", done.getBizContext().getRemoteAddress(), - reader.getPath(), request.getFilename(), request.getOffset(), request.getCount()); + if (LOG.isDebugEnabled()) { + LOG.debug("GetFile from {} path={} filename={} offset={} count={}", + done.getBizContext().getRemoteAddress(), reader.getPath(), request.getFilename(), request.getOffset(), + request.getCount()); + } final ByteBufferCollector dataBuffer = ByteBufferCollector.allocate(); final GetFileResponse.Builder responseBuilder = GetFileResponse.newBuilder(); @@ -101,14 +102,12 @@ public Message handleGetFile(GetFileRequest request, RpcRequestClosure done) { final int read = reader .readFile(dataBuffer, request.getFilename(), request.getOffset(), request.getCount()); responseBuilder.setReadSize(read); - if (read == -1) { - responseBuilder.setEof(true); - } + responseBuilder.setEof(read == FileReader.EOF); final ByteBuffer buf = dataBuffer.getBuffer(); buf.flip(); if (!buf.hasRemaining()) { // skip empty data - return responseBuilder.setData(ByteString.EMPTY).build(); + responseBuilder.setData(ByteString.EMPTY); } else { // TODO check hole responseBuilder.setData(ZeroByteStringHelper.wrap(buf)); @@ -128,9 +127,9 @@ public Message handleGetFile(GetFileRequest request, RpcRequestClosure done) { /** * Adds a file reader and return it's generated readerId. */ - public long addReader(FileReader reader) { + public long addReader(final FileReader reader) { final long readerId = this.nextId.getAndIncrement(); - if (fileReaderMap.putIfAbsent(readerId, reader) == null) { + if (this.fileReaderMap.putIfAbsent(readerId, reader) == null) { return readerId; } else { return -1L; @@ -140,7 +139,7 @@ public long addReader(FileReader reader) { /** * Remove the reader by readerId. */ - public boolean removeReader(long readerId) { + public boolean removeReader(final long readerId) { return this.fileReaderMap.remove(readerId) != null; } } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/FileReader.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/FileReader.java index d63592e66..f67ee963c 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/FileReader.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/FileReader.java @@ -30,6 +30,8 @@ */ public interface FileReader { + int EOF = -1; + /** * Get the file path. * @@ -39,8 +41,17 @@ public interface FileReader { /** * Read file into buf starts from offset at most maxCount. - * Returns -1 if reaches end, else return read count. + * + * @param buf read bytes into this buf + * @param fileName file name + * @param offset the offset of file + * @param maxCount max read bytes + * @return -1 if reaches end, else return read count. + * @throws IOException if some I/O error occurs + * @throws RetryAgainException if it's not allowed to read partly + * or it's allowed but throughput is throttled to 0, try again. */ - int readFile(ByteBufferCollector buf, String fileName, long offset, long maxCount) throws IOException, - RetryAgainException; + int readFile(final ByteBufferCollector buf, final String fileName, final long offset, final long maxCount) + throws IOException, + RetryAgainException; } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/LocalDirReader.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/LocalDirReader.java index e0e84aa74..affb43590 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/LocalDirReader.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/io/LocalDirReader.java @@ -29,12 +29,14 @@ import com.google.protobuf.Message; /** - * read a file data form local dir by fileName. + * Read a file data form local dir by fileName. + * * @author boyan (boyan@alibaba-inc.com) * * 2018-Apr-06 9:25:12 PM */ public class LocalDirReader implements FileReader { + private static final Logger LOG = LoggerFactory.getLogger(LocalDirReader.class); private final String path; @@ -46,32 +48,33 @@ public LocalDirReader(String path) { @Override public String getPath() { - return this.path; + return path; } @Override - public int readFile(ByteBufferCollector buf, String fileName, long offset, long maxCount) throws IOException, - RetryAgainException { - return this.readFileWithMeta(buf, fileName, null, offset, maxCount); + public int readFile(final ByteBufferCollector buf, final String fileName, final long offset, final long maxCount) + throws IOException, + RetryAgainException { + return readFileWithMeta(buf, fileName, null, offset, maxCount); } @SuppressWarnings("unused") - protected int readFileWithMeta(ByteBufferCollector buf, String fileName, Message fileMeta, long offset, - long maxCount) throws IOException, RetryAgainException { + protected int readFileWithMeta(final ByteBufferCollector buf, final String fileName, final Message fileMeta, + long offset, final long maxCount) throws IOException, RetryAgainException { buf.expandIfNecessary(); final String filePath = this.path + File.separator + fileName; final File file = new File(filePath); - try (FileInputStream input = new FileInputStream(file); FileChannel fc = input.getChannel()) { + try (final FileInputStream input = new FileInputStream(file); final FileChannel fc = input.getChannel()) { int totalRead = 0; while (true) { final int nread = fc.read(buf.getBuffer(), offset); if (nread <= 0) { - return -1; + return EOF; } totalRead += nread; if (totalRead < maxCount) { if (buf.hasRemaining()) { - return -1; + return EOF; } else { buf.expandAtMost((int) (maxCount - totalRead)); offset += nread; @@ -79,11 +82,11 @@ protected int readFileWithMeta(ByteBufferCollector buf, String fileName, Message } else { final long fsize = file.length(); if (fsize < 0) { - LOG.warn("Invlaid file length {}", filePath); - return -1; + LOG.warn("Invalid file length {}", filePath); + return EOF; } - if (fsize == offset + maxCount) { - return -1; + if (fsize == offset + nread) { + return EOF; } else { return totalRead; } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/SnapshotFileReader.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/SnapshotFileReader.java index 80b1df2ba..fcfcfd183 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/SnapshotFileReader.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/SnapshotFileReader.java @@ -54,21 +54,20 @@ public SnapshotFileReader(String path, SnapshotThrottle snapshotThrottle) { } public boolean open() { - final File file = new File(this.getPath()); + final File file = new File(getPath()); return file.exists(); } @Override - public int readFile(ByteBufferCollector metaBufferCollector, String fileName, long offset, long maxCount) - throws IOException, - RetryAgainException { + public int readFile(final ByteBufferCollector metaBufferCollector, final String fileName, final long offset, + final long maxCount) throws IOException, RetryAgainException { // read the whole meta file. if (fileName.equals(Snapshot.JRAFT_SNAPSHOT_META_FILE)) { final ByteBuffer metaBuf = this.metaTable.saveToByteBufferAsRemote(); - //because bufRef will flip the buffer before using, so we must set the meta buffer position to it's limit. + // because bufRef will flip the buffer before using, so we must set the meta buffer position to it's limit. metaBuf.position(metaBuf.limit()); metaBufferCollector.setBuffer(metaBuf); - return -1; + return EOF; } final LocalFileMeta fileMeta = this.metaTable.getFileMeta(fileName); if (fileMeta == null) { @@ -78,7 +77,7 @@ public int readFile(ByteBufferCollector metaBufferCollector, String fileName, lo // go through throttle long newMaxCount = maxCount; if (this.snapshotThrottle != null) { - newMaxCount = snapshotThrottle.throttledByThroughput(maxCount); + newMaxCount = this.snapshotThrottle.throttledByThroughput(maxCount); if (newMaxCount < maxCount) { // if it's not allowed to read partly or it's allowed but // throughput is throttled to 0, try again. @@ -88,7 +87,6 @@ public int readFile(ByteBufferCollector metaBufferCollector, String fileName, lo } } - return this.readFileWithMeta(metaBufferCollector, fileName, fileMeta, offset, newMaxCount); + return readFileWithMeta(metaBufferCollector, fileName, fileMeta, offset, newMaxCount); } - } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSession.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSession.java index 6940386ca..f3e957147 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSession.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/BoltSession.java @@ -37,6 +37,7 @@ import com.alipay.sofa.jraft.option.CopyOptions; import com.alipay.sofa.jraft.option.RaftOptions; import com.alipay.sofa.jraft.rpc.RaftClientService; +import com.alipay.sofa.jraft.rpc.RpcRequests; import com.alipay.sofa.jraft.rpc.RpcRequests.GetFileRequest; import com.alipay.sofa.jraft.rpc.RpcRequests.GetFileResponse; import com.alipay.sofa.jraft.rpc.RpcResponseClosureAdapter; @@ -57,32 +58,31 @@ @ThreadSafe public class BoltSession implements Session { - private static final Logger LOG = LoggerFactory.getLogger(BoltSession.class); + private static final Logger LOG = LoggerFactory.getLogger(BoltSession.class); - private final Lock lock; - private final Status st; + private final Lock lock = new ReentrantLock(); + private final Status st = Status.OK(); + private final CountDownLatch finishLatch = new CountDownLatch(1); + private final GetFileResponseClosure done = new GetFileResponseClosure(); private final RaftClientService rpcService; - private int retryTimes; + private final GetFileRequest.Builder requestBuilder; + private final Endpoint endpoint; + private final TimerManager timerManager; + private final SnapshotThrottle snapshotThrottle; + private final RaftOptions raftOptions; + private int retryTimes = 0; private boolean finished; private ByteBufferCollector destBuf; - private CopyOptions copyOptions; - private final GetFileRequest.Builder requestBuilder; - private final CountDownLatch finishLatch; + private CopyOptions copyOptions = new CopyOptions(); private OutputStream outputStream; - private final Endpoint endpoint; - private final GetFileResponseClosure done = new GetFileResponseClosure(); private ScheduledFuture timer; private String destPath; - private final RaftOptions raftOptions; private Future rpcCall; - private final TimerManager timerManager; - private final SnapshotThrottle snapshotThrottle; /** * Get file response closure to answer client. - * @author boyan (boyan@alibaba-inc.com) * - * 2018-Apr-13 4:50:21 PM + * @author boyan (boyan@alibaba-inc.com) */ private class GetFileResponseClosure extends RpcResponseClosureAdapter { @@ -131,12 +131,7 @@ public BoltSession(RaftClientService rpcService, TimerManager timerManager, Snap this.timerManager = timerManager; this.rpcService = rpcService; this.requestBuilder = rb; - this.retryTimes = 0; - this.copyOptions = new CopyOptions(); - this.lock = new ReentrantLock(); this.endpoint = ep; - this.st = Status.OK(); - this.finishLatch = new CountDownLatch(1); } public void setDestBuf(ByteBufferCollector bufRef) { @@ -168,7 +163,7 @@ public void cancel() { this.st.setError(RaftError.ECANCELED, RaftError.ECANCELED.name()); } - this.onFinished(); + onFinished(); } finally { this.lock.unlock(); } @@ -186,6 +181,11 @@ public Status status() { private void onFinished() { if (!this.finished) { + if (!this.st.isOk()) { + LOG.error("Fail to copy data, readerId={} fileName={} offset={} status={}", + this.requestBuilder.getReaderId(), this.requestBuilder.getFilename(), + this.requestBuilder.getOffset(), this.st); + } if (this.outputStream != null) { Utils.closeQuietly(this.outputStream); this.outputStream = null; @@ -206,7 +206,7 @@ private void onTimer() { Utils.runInThread(this::sendNextRpc); } - void onRpcReturned(Status status, GetFileResponse response) { + void onRpcReturned(final Status status, final GetFileResponse response) { this.lock.lock(); try { if (this.finished) { @@ -218,7 +218,7 @@ void onRpcReturned(Status status, GetFileResponse response) { if (status.getCode() == RaftError.ECANCELED.getNumber()) { if (this.st.isOk()) { this.st.setError(status.getCode(), status.getErrorMsg()); - this.onFinished(); + onFinished(); return; } } @@ -228,7 +228,7 @@ void onRpcReturned(Status status, GetFileResponse response) { && ++this.retryTimes >= this.copyOptions.getMaxRetry()) { if (this.st.isOk()) { this.st.setError(status.getCode(), status.getErrorMsg()); - this.onFinished(); + onFinished(); return; } } @@ -239,7 +239,7 @@ void onRpcReturned(Status status, GetFileResponse response) { this.retryTimes = 0; Requires.requireNonNull(response, "response"); // Reset count to |real_read_size| to make next rpc get the right offset - if (response.hasReadSize() && response.getReadSize() != 0) { + if (!response.getEof()) { this.requestBuilder.setCount(response.getReadSize()); } if (this.outputStream != null) { @@ -248,7 +248,7 @@ void onRpcReturned(Status status, GetFileResponse response) { } catch (final IOException e) { LOG.error("Fail to write into file {}", this.destPath); this.st.setError(RaftError.EIO, RaftError.EIO.name()); - this.onFinished(); + onFinished(); return; } } else { @@ -282,7 +282,7 @@ void sendNextRpc() { // throttle long newMaxCount = maxCount; if (this.snapshotThrottle != null) { - newMaxCount = snapshotThrottle.throttledByThroughput(maxCount); + newMaxCount = this.snapshotThrottle.throttledByThroughput(maxCount); if (newMaxCount == 0) { // Reset count to make next rpc retry the previous one this.requestBuilder.setCount(0); @@ -292,9 +292,9 @@ void sendNextRpc() { } } this.requestBuilder.setCount(newMaxCount); - LOG.debug("Send get file request {} to peer {}", this.requestBuilder.build(), this.endpoint); - this.rpcCall = this.rpcService.getFile(endpoint, this.requestBuilder.build(), - this.copyOptions.getTimeoutMs(), done); + final RpcRequests.GetFileRequest request = this.requestBuilder.build(); + LOG.debug("Send get file request {} to peer {}", request, this.endpoint); + this.rpcCall = this.rpcService.getFile(this.endpoint, request, this.copyOptions.getTimeoutMs(), this.done); } finally { this.lock.unlock(); } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/RemoteFileCopier.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/RemoteFileCopier.java index 737539bb0..876074a47 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/RemoteFileCopier.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/RemoteFileCopier.java @@ -100,9 +100,9 @@ public boolean init(String uri, SnapshotThrottle snapshotThrottle, SnapshotCopie /** * Copy `source` from remote to local dest. * - * @param source source from remote - * @param destPath local path - * @param opts options of copy + * @param source source from remote + * @param destPath local path + * @param opts options of copy * @return true if copy success */ public boolean copyToFile(String source, String destPath, CopyOptions opts) throws IOException, @@ -152,9 +152,9 @@ private BoltSession newBoltSession(String source) { /** * Copy `source` from remote to buffer. - * @param source source from remote - * @param destBuf buffer of dest - * @param opt options of copy + * @param source source from remote + * @param destBuf buffer of dest + * @param opt options of copy * @return true if copy success */ public boolean copy2IoBuffer(String source, ByteBufferCollector destBuf, CopyOptions opt) diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/ByteBufferCollector.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/ByteBufferCollector.java index d53407d21..7264c5044 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/ByteBufferCollector.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/ByteBufferCollector.java @@ -24,37 +24,38 @@ * @author dennis */ public final class ByteBufferCollector { + private ByteBuffer buffer; public int capacity() { - return buffer != null ? buffer.capacity() : 0; + return this.buffer != null ? this.buffer.capacity() : 0; } public void expandIfNecessary() { - if (!this.hasRemaining()) { + if (!hasRemaining()) { getBuffer(Utils.RAFT_DATA_BUF_SIZE); } } - public void expandAtMost(int atMostBytes) { + public void expandAtMost(final int atMostBytes) { if (this.buffer == null) { this.buffer = Utils.allocate(atMostBytes); } else { - buffer = Utils.expandByteBufferAtMost(buffer, atMostBytes); + this.buffer = Utils.expandByteBufferAtMost(this.buffer, atMostBytes); } } public boolean hasRemaining() { - return buffer != null && buffer.hasRemaining(); + return this.buffer != null && this.buffer.hasRemaining(); } - private ByteBufferCollector(int size) { + private ByteBufferCollector(final int size) { if (size > 0) { this.buffer = Utils.allocate(size); } } - public static ByteBufferCollector allocate(int size) { + public static ByteBufferCollector allocate(final int size) { return new ByteBufferCollector(size); } @@ -62,24 +63,24 @@ public static ByteBufferCollector allocate() { return new ByteBufferCollector(Utils.RAFT_DATA_BUF_SIZE); } - private ByteBuffer getBuffer(int expectSize) { - if (buffer == null) { - buffer = Utils.allocate(expectSize); - } else if (buffer.remaining() < expectSize) { - buffer = Utils.expandByteBufferAtLeast(buffer, expectSize); + private ByteBuffer getBuffer(final int expectSize) { + if (this.buffer == null) { + this.buffer = Utils.allocate(expectSize); + } else if (this.buffer.remaining() < expectSize) { + this.buffer = Utils.expandByteBufferAtLeast(this.buffer, expectSize); } - return buffer; + return this.buffer; } - public void put(ByteBuffer buf) { + public void put(final ByteBuffer buf) { getBuffer(buf.remaining()).put(buf); } - public void put(byte[] bs) { + public void put(final byte[] bs) { getBuffer(bs.length).put(bs); } - public void setBuffer(ByteBuffer buffer) { + public void setBuffer(final ByteBuffer buffer) { this.buffer = buffer; } diff --git a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/Utils.java b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/Utils.java index 0c7985822..40b290e00 100644 --- a/jraft-core/src/main/java/com/alipay/sofa/jraft/util/Utils.java +++ b/jraft-core/src/main/java/com/alipay/sofa/jraft/util/Utils.java @@ -172,7 +172,7 @@ public static long getProcessId(final long fallback) { } /** - * Default init and expand buffer size, it can be set by -Draft.byte_buf.size=n, default 1024. + * Default init and expand buffer size, it can be set by -Djraft.byte_buf.size=n, default 1024. */ public static final int RAFT_DATA_BUF_SIZE = Integer.parseInt(System.getProperty("jraft.byte_buf.size", "1024")); diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java index 00b85f907..5a75956f5 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java @@ -19,7 +19,7 @@ import java.io.File; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -55,6 +55,8 @@ import com.alipay.sofa.jraft.error.RaftException; import com.alipay.sofa.jraft.option.BootstrapOptions; import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.option.RaftOptions; +import com.alipay.sofa.jraft.storage.SnapshotThrottle; import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; import com.alipay.sofa.jraft.storage.snapshot.ThroughputSnapshotThrottle; import com.alipay.sofa.jraft.test.TestUtils; @@ -124,7 +126,7 @@ public void testSingleNode() throws Exception { nodeOptions.setLogUri(this.dataPath + File.separator + "log"); nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta"); nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot"); - nodeOptions.setInitialConf(new Configuration(Arrays.asList(peer))); + nodeOptions.setInitialConf(new Configuration(Collections.singletonList(peer))); final Node node = new NodeImpl("unittest", peer); assertTrue(node.init(nodeOptions)); @@ -155,14 +157,14 @@ public void testNoLeader() throws Exception { final Node follower = followers.get(0); sendTestTaskAndWait(follower, 0, RaftError.EPERM); - //adds a peer3 + // adds a peer3 final PeerId peer3 = new PeerId(TestUtils.getMyIp(), TestUtils.INIT_PORT + 3); CountDownLatch latch = new CountDownLatch(1); follower.addPeer(peer3, new ExpectClosure(RaftError.EPERM, latch)); waitLatch(latch); - //remove the peer0 - final PeerId peer0 = new PeerId(TestUtils.getMyIp(), TestUtils.INIT_PORT + 0); + // remove the peer0 + final PeerId peer0 = new PeerId(TestUtils.getMyIp(), TestUtils.INIT_PORT); latch = new CountDownLatch(1); follower.removePeer(peer0, new ExpectClosure(RaftError.EPERM, latch)); waitLatch(latch); @@ -188,6 +190,7 @@ private void sendTestTaskAndWait(Node node, int start, RaftError err) throws Int waitLatch(latch); } + @SuppressWarnings("SameParameterValue") private void sendTestTaskAndWait(String prefix, Node node, int code) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(10); for (int i = 0; i < 10; i++) { @@ -207,14 +210,14 @@ public void testTripleNodes() throws Exception { assertTrue(cluster.start(peer.getEndpoint())); } - //elect leader + // elect leader cluster.waitLeader(); - //get leader + // get leader final Node leader = cluster.getLeader(); assertNotNull(leader); assertEquals(3, leader.listPeers().size()); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); { @@ -224,7 +227,7 @@ public void testTripleNodes() throws Exception { } { - //task with TaskClosure + // task with TaskClosure final ByteBuffer data = ByteBuffer.wrap("task closure".getBytes()); final Vector cbs = new Vector<>(); final CountDownLatch latch = new CountDownLatch(1); @@ -263,25 +266,25 @@ public void testReadIndex() throws Exception { assertTrue(cluster.start(peer.getEndpoint(), false, 300, true)); } - //elect leader + // elect leader cluster.waitLeader(); - //get leader + // get leader final Node leader = cluster.getLeader(); assertNotNull(leader); assertEquals(3, leader.listPeers().size()); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); assertReadIndex(leader, 11); - //read from follower + // read from follower for (final Node follower : cluster.getFollowers()) { assertNotNull(follower); assertReadIndex(follower, 11); } - //read with null request context + // read with null request context final CountDownLatch latch = new CountDownLatch(1); leader.readIndex(null, new ReadIndexClosure() { @@ -306,10 +309,10 @@ public void testReadIndexChaos() throws Exception { assertTrue(cluster.start(peer.getEndpoint(), false, 300, true)); } - //elect leader + // elect leader cluster.waitLeader(); - //get leader + // get leader final Node leader = cluster.getLeader(); assertNotNull(leader); assertEquals(3, leader.listPeers().size()); @@ -364,6 +367,7 @@ public void run(Status status, long index, byte[] reqCtx) { cluster.stopAll(); } + @SuppressWarnings({ "unused", "SameParameterValue" }) private void assertReadIndex(final Node node, int index) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final byte[] requestContext = TestUtils.getRandomBytes(); @@ -389,14 +393,14 @@ public void testNodeMetrics() throws Exception { assertTrue(cluster.start(peer.getEndpoint(), false, 300, true)); } - //elect leader + // elect leader cluster.waitLeader(); - //get leader + // get leader final Node leader = cluster.getLeader(); assertNotNull(leader); assertEquals(3, leader.listPeers().size()); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); { @@ -414,7 +418,7 @@ public void testNodeMetrics() throws Exception { reporter.close(); System.out.println(); } - //TODO check http status + // TODO check http status assertEquals(2, cluster.getFollowers().size()); cluster.stopAll(); // System.out.println(node.getNodeMetrics().getMetrics()); @@ -429,17 +433,17 @@ public void testLeaderFail() throws Exception { assertTrue(cluster.start(peer.getEndpoint())); } - //elect leader + // elect leader cluster.waitLeader(); - //get leader + // get leader Node leader = cluster.getLeader(); assertNotNull(leader); LOG.info("Current leader is {}", leader.getLeaderId()); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); - //stop leader + // stop leader LOG.warn("Stop leader {}", leader.getNodeId().getPeerId()); final PeerId oldLeader = leader.getNodeId().getPeerId(); assertTrue(cluster.stop(leader.getNodeId().getPeerId().getEndpoint())); @@ -449,11 +453,11 @@ public void testLeaderFail() throws Exception { assertFalse(followers.isEmpty()); this.sendTestTaskAndWait("follower apply ", followers.get(0), -1); - //elect new leader + // elect new leader cluster.waitLeader(); leader = cluster.getLeader(); LOG.info("Eelect new leader is {}", leader.getLeaderId()); - //apply tasks to new leader + // apply tasks to new leader CountDownLatch latch = new CountDownLatch(10); for (int i = 10; i < 20; i++) { final ByteBuffer data = ByteBuffer.wrap(("hello" + i).getBytes()); @@ -462,10 +466,10 @@ public void testLeaderFail() throws Exception { } waitLatch(latch); - //restart old leader + // restart old leader LOG.info("restart old leader {}", oldLeader); assertTrue(cluster.start(oldLeader.getEndpoint())); - //apply something + // apply something latch = new CountDownLatch(10); for (int i = 20; i < 30; i++) { final ByteBuffer data = ByteBuffer.wrap(("hello" + i).getBytes()); @@ -474,11 +478,11 @@ public void testLeaderFail() throws Exception { } waitLatch(latch); - //stop and clean old leader + // stop and clean old leader cluster.stop(oldLeader.getEndpoint()); cluster.clean(oldLeader.getEndpoint()); - //restart old leader + // restart old leader LOG.info("restart old leader {}", oldLeader); assertTrue(cluster.start(oldLeader.getEndpoint())); assertTrue(cluster.ensureSame(-1)); @@ -498,7 +502,7 @@ public void testJoinNodes() throws Exception { final ArrayList peers = new ArrayList<>(); peers.add(peer0); - //start single cluster + // start single cluster final TestCluster cluster = new TestCluster("unittest", dataPath, peers); assertTrue(cluster.start(peer0.getEndpoint())); @@ -509,9 +513,9 @@ public void testJoinNodes() throws Exception { Assert.assertEquals(leader.getNodeId().getPeerId(), peer0); this.sendTestTaskAndWait(leader); - //start peer1 + // start peer1 assertTrue(cluster.start(peer1.getEndpoint(), true, 300)); - //add peer1 + // add peer1 CountDownLatch latch = new CountDownLatch(1); peers.add(peer1); leader.addPeer(peer1, new ExpectClosure(latch)); @@ -523,26 +527,26 @@ public void testJoinNodes() throws Exception { assertEquals(10, fsm.getLogs().size()); } - //add peer2 but not start + // add peer2 but not start peers.add(peer2); latch = new CountDownLatch(1); leader.addPeer(peer2, new ExpectClosure(RaftError.ECATCHUP, latch)); waitLatch(latch); - //start peer2 after 2 seconds + // start peer2 after 2 seconds Thread.sleep(2000); assertTrue(cluster.start(peer2.getEndpoint(), true, 300)); Thread.sleep(10000); - //re-add peer2 + // re-add peer2 latch = new CountDownLatch(2); leader.addPeer(peer2, new ExpectClosure(latch)); // concurrent configuration change leader.addPeer(peer3, new ExpectClosure(RaftError.EBUSY, latch)); waitLatch(latch); - //re-add peer2 directly + // re-add peer2 directly try { leader.addPeer(peer2, new ExpectClosure(latch)); @@ -573,13 +577,13 @@ public void testRemoveFollower() throws Exception { assertTrue(cluster.start(peer.getEndpoint())); } - //elect leader + // elect leader cluster.waitLeader(); - //get leader + // get leader final Node leader = cluster.getLeader(); assertNotNull(leader); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); cluster.ensureSame(); @@ -590,12 +594,12 @@ public void testRemoveFollower() throws Exception { final PeerId followerPeer = followers.get(0).getNodeId().getPeerId(); final Endpoint followerAddr = followerPeer.getEndpoint(); - //stop and clean follower + // stop and clean follower LOG.info("Stop and clean follower {}", followerPeer); assertTrue(cluster.stop(followerAddr)); cluster.clean(followerAddr); - //remove follower + // remove follower LOG.info("Remove follower {}", followerPeer); CountDownLatch latch = new CountDownLatch(1); leader.removePeer(followerPeer, new ExpectClosure(latch)); @@ -608,10 +612,10 @@ public void testRemoveFollower() throws Exception { peers = TestUtils.generatePeers(3); assertTrue(peers.remove(followerPeer)); - //start follower + // start follower LOG.info("Start and add follower {}", followerPeer); assertTrue(cluster.start(followerAddr)); - //re-add follower + // re-add follower latch = new CountDownLatch(1); leader.addPeer(followerPeer, new ExpectClosure(latch)); waitLatch(latch); @@ -636,13 +640,13 @@ public void testRemoveLeader() throws Exception { assertTrue(cluster.start(peer.getEndpoint())); } - //elect leader + // elect leader cluster.waitLeader(); - //get leader + // get leader Node leader = cluster.getLeader(); assertNotNull(leader); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); cluster.ensureSame(); @@ -653,26 +657,26 @@ public void testRemoveLeader() throws Exception { final PeerId oldLeader = leader.getNodeId().getPeerId().copy(); final Endpoint oldLeaderAddr = oldLeader.getEndpoint(); - //remove old leader + // remove old leader LOG.info("Remove old leader {}", oldLeader); CountDownLatch latch = new CountDownLatch(1); leader.removePeer(oldLeader, new ExpectClosure(latch)); waitLatch(latch); - //elect new leader + // elect new leader cluster.waitLeader(); leader = cluster.getLeader(); LOG.info("New leader is {}", leader); assertNotNull(leader); - //apply tasks to new leader + // apply tasks to new leader this.sendTestTaskAndWait(leader, 10, RaftError.SUCCESS); - //stop and clean old leader + // stop and clean old leader LOG.info("Stop and clean old leader {}", oldLeader); assertTrue(cluster.stop(oldLeaderAddr)); cluster.clean(oldLeaderAddr); - //Add and start old leader + // Add and start old leader LOG.info("Start and add old leader {}", oldLeader); assertTrue(cluster.start(oldLeaderAddr)); @@ -703,11 +707,11 @@ public void testPreVote() throws Exception { } cluster.waitLeader(); - //get leader + // get leader Node leader = cluster.getLeader(); final long savedTerm = ((NodeImpl) leader).getCurrentTerm(); assertNotNull(leader); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); cluster.ensureSame(); @@ -718,7 +722,7 @@ public void testPreVote() throws Exception { final PeerId followerPeer = followers.get(0).getNodeId().getPeerId(); final Endpoint followerAddr = followerPeer.getEndpoint(); - //remove follower + // remove follower LOG.info("Remove follower {}", followerPeer); CountDownLatch latch = new CountDownLatch(1); leader.removePeer(followerPeer, new ExpectClosure(latch)); @@ -728,7 +732,7 @@ public void testPreVote() throws Exception { Thread.sleep(2000); - //add follower + // add follower LOG.info("Add follower {}", followerAddr); peers = TestUtils.generatePeers(3); assertTrue(peers.remove(followerPeer)); @@ -737,7 +741,7 @@ public void testPreVote() throws Exception { waitLatch(latch); leader = cluster.getLeader(); assertNotNull(leader); - //leader term should not be changed. + // leader term should not be changed. assertEquals(savedTerm, ((NodeImpl) leader).getCurrentTerm()); cluster.stopAll(); } @@ -753,7 +757,7 @@ public void testSetPeer1() throws Exception { final List peers = new ArrayList<>(); peers.add(bootPeer); - //reset peers from empty + // reset peers from empty assertTrue(nodes.get(0).resetPeers(new Configuration(peers)).isOk()); cluster.waitLeader(); assertNotNull(cluster.getLeader()); @@ -772,10 +776,10 @@ public void testSetPeer2() throws Exception { } cluster.waitLeader(); - //get leader + // get leader Node leader = cluster.getLeader(); assertNotNull(leader); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); cluster.ensureSame(); @@ -792,9 +796,9 @@ public void testSetPeer2() throws Exception { assertTrue(cluster.stop(followerAddr1)); cluster.clean(followerAddr1); - //apply tasks to leader again + // apply tasks to leader again this.sendTestTaskAndWait(leader, 10, RaftError.SUCCESS); - //set peer when no quorum die + // set peer when no quorum die final Endpoint leaderAddr = leader.getLeaderId().getEndpoint().copy(); LOG.info("Set peers to {}", leaderAddr); final List newPeers = TestUtils.generatePeers(3); @@ -804,14 +808,14 @@ public void testSetPeer2() throws Exception { assertTrue(cluster.stop(followerAddr2)); cluster.clean(followerAddr2); - //leader will stepdown, become follower + // leader will step-down, become follower Thread.sleep(2000); newPeers.clear(); newPeers.add(new PeerId(leaderAddr, 0)); - //new peers equal to current conf + // new peers equal to current conf assertTrue(leader.resetPeers(new Configuration(peers)).isOk()); - //set peer when quorum die + // set peer when quorum die LOG.warn("Set peers to {}", leaderAddr); assertTrue(leader.resetPeers(new Configuration(newPeers)).isOk()); @@ -857,21 +861,21 @@ public void testRestoreSnasphot() throws Exception { } cluster.waitLeader(); - //get leader + // get leader final Node leader = cluster.getLeader(); assertNotNull(leader); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); cluster.ensureSame(); triggerLeaderSnapshot(cluster, leader); - //stop leader + // stop leader final Endpoint leaderAddr = leader.getNodeId().getPeerId().getEndpoint().copy(); assertTrue(cluster.stop(leaderAddr)); Thread.sleep(2000); - //restart leader + // restart leader assertEquals(0, cluster.getLeaderFsm().getLoadSnapshotTimes()); assertTrue(cluster.start(leaderAddr)); cluster.ensureSame(); @@ -885,7 +889,7 @@ private void triggerLeaderSnapshot(TestCluster cluster, Node leader) throws Inte } private void triggerLeaderSnapshot(TestCluster cluster, Node leader, int times) throws InterruptedException { - //trigger leader snapshot + // trigger leader snapshot assertEquals(times - 1, cluster.getLeaderFsm().getSaveSnapshotTimes()); final CountDownLatch latch = new CountDownLatch(1); leader.snapshot(new ExpectClosure(latch)); @@ -904,37 +908,37 @@ public void testInstallSnapshotWithThrottle() throws Exception { } cluster.waitLeader(); - //get leader + // get leader final Node leader = cluster.getLeader(); assertNotNull(leader); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); cluster.ensureSame(); - //stop follower1 + // stop follower1 final List followers = cluster.getFollowers(); assertEquals(2, followers.size()); final Endpoint followerAddr = followers.get(0).getNodeId().getPeerId().getEndpoint(); assertTrue(cluster.stop(followerAddr)); - //apply something more + // apply something more this.sendTestTaskAndWait(leader, 10, RaftError.SUCCESS); Thread.sleep(1000); - //trigger leader snapshot + // trigger leader snapshot triggerLeaderSnapshot(cluster, leader); - //apply something more + // apply something more this.sendTestTaskAndWait(leader, 20, RaftError.SUCCESS); - //trigger leader snapshot + // trigger leader snapshot triggerLeaderSnapshot(cluster, leader, 2); - //wait leader to compact logs + // wait leader to compact logs Thread.sleep(1000); - //restart follower. + // restart follower. cluster.clean(followerAddr); assertTrue(cluster.start(followerAddr, true, 300, false, new ThroughputSnapshotThrottle(1024, 1))); @@ -949,6 +953,129 @@ public void testInstallSnapshotWithThrottle() throws Exception { cluster.stopAll(); } + @Test + public void testInstallLargeSnapshotWithThrottle() throws Exception { + final List peers = TestUtils.generatePeers(4); + final TestCluster cluster = new TestCluster("unitest", dataPath, peers.subList(0, 3)); + for (int i = 0; i < peers.size() - 1; i++) { + final PeerId peer = peers.get(i); + final boolean started = cluster.start(peer.getEndpoint(), false, 200, false); + assertTrue(started); + } + cluster.waitLeader(); + // get leader + final Node leader = cluster.getLeader(); + assertNotNull(leader); + // apply tasks to leader + sendTestTaskAndWait(leader, 0, RaftError.SUCCESS); + + cluster.ensureSame(); + + // apply something more + for (int i = 1; i < 100; i++) { + sendTestTaskAndWait(leader, i * 10, RaftError.SUCCESS); + } + + Thread.sleep(1000); + + // trigger leader snapshot + triggerLeaderSnapshot(cluster, leader); + + // apply something more + for (int i = 100; i < 200; i++) { + sendTestTaskAndWait(leader, i * 10, RaftError.SUCCESS); + } + // trigger leader snapshot + triggerLeaderSnapshot(cluster, leader, 2); + + // wait leader to compact logs + Thread.sleep(1000); + + // add follower + final PeerId newPeer = peers.get(3); + final SnapshotThrottle snapshotThrottle = new ThroughputSnapshotThrottle(128, 1); + final boolean started = cluster.start(newPeer.getEndpoint(), true, 300, false, snapshotThrottle); + assertTrue(started); + + final CountDownLatch latch = new CountDownLatch(1); + leader.addPeer(newPeer, status -> { + assertTrue(status.toString(), status.isOk()); + latch.countDown(); + }); + waitLatch(latch); + + cluster.ensureSame(); + + assertEquals(4, cluster.getFsms().size()); + for (final MockStateMachine fsm : cluster.getFsms()) { + assertEquals(2000, fsm.getLogs().size()); + } + + cluster.stopAll(); + } + + @Test + public void testInstallLargeSnapshot() throws Exception { + final List peers = TestUtils.generatePeers(4); + final TestCluster cluster = new TestCluster("unitest", dataPath, peers.subList(0, 3)); + for (int i = 0; i < peers.size() - 1; i++) { + final PeerId peer = peers.get(i); + final boolean started = cluster.start(peer.getEndpoint(), false, 200, false); + assertTrue(started); + } + cluster.waitLeader(); + // get leader + final Node leader = cluster.getLeader(); + assertNotNull(leader); + // apply tasks to leader + sendTestTaskAndWait(leader, 0, RaftError.SUCCESS); + + cluster.ensureSame(); + + // apply something more + for (int i = 1; i < 100; i++) { + sendTestTaskAndWait(leader, i * 10, RaftError.SUCCESS); + } + + Thread.sleep(1000); + + // trigger leader snapshot + triggerLeaderSnapshot(cluster, leader); + + // apply something more + for (int i = 100; i < 200; i++) { + sendTestTaskAndWait(leader, i * 10, RaftError.SUCCESS); + } + // trigger leader snapshot + triggerLeaderSnapshot(cluster, leader, 2); + + // wait leader to compact logs + Thread.sleep(1000); + + // add follower + final PeerId newPeer = peers.get(3); + final RaftOptions raftOptions = new RaftOptions(); + raftOptions.setMaxByteCountPerRpc(128); + final boolean started = cluster.start(newPeer.getEndpoint(), true, 300, false, null, raftOptions); + assertTrue(started); + + final CountDownLatch latch = new CountDownLatch(1); + leader.addPeer(newPeer, status -> { + assertTrue(status.toString(), status.isOk()); + latch.countDown(); + }); + waitLatch(latch); + + cluster.ensureSame(); + + assertEquals(4, cluster.getFsms().size()); + for (final MockStateMachine fsm : cluster.getFsms()) { + assertEquals(2000, fsm.getLogs().size()); + } + + cluster.stopAll(); + } + @Test public void testInstallSnapshot() throws Exception { final List peers = TestUtils.generatePeers(3); @@ -960,31 +1087,31 @@ public void testInstallSnapshot() throws Exception { } cluster.waitLeader(); - //get leader + // get leader final Node leader = cluster.getLeader(); assertNotNull(leader); - //apply tasks to leader + // apply tasks to leader this.sendTestTaskAndWait(leader); cluster.ensureSame(); - //stop follower1 + // stop follower1 final List followers = cluster.getFollowers(); assertEquals(2, followers.size()); final Endpoint followerAddr = followers.get(0).getNodeId().getPeerId().getEndpoint(); assertTrue(cluster.stop(followerAddr)); - //apply something more + // apply something more this.sendTestTaskAndWait(leader, 10, RaftError.SUCCESS); - //trigger leader snapshot + // trigger leader snapshot triggerLeaderSnapshot(cluster, leader); - //apply something more + // apply something more this.sendTestTaskAndWait(leader, 20, RaftError.SUCCESS); triggerLeaderSnapshot(cluster, leader, 2); - //wait leader to compact logs + // wait leader to compact logs Thread.sleep(50); //restart follower. @@ -1011,11 +1138,11 @@ public void testNoSnapshot() throws Exception { nodeOptions.setFsm(fsm); nodeOptions.setLogUri(this.dataPath + File.separator + "log"); nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta"); - nodeOptions.setInitialConf(new Configuration(Arrays.asList(new PeerId(addr, 0)))); + nodeOptions.setInitialConf(new Configuration(Collections.singletonList(new PeerId(addr, 0)))); final Node node = new NodeImpl("unittest", new PeerId(addr, 0)); assertTrue(node.init(nodeOptions)); - //wait node elect self as leader + // wait node elect self as leader Thread.sleep(2000); @@ -1045,18 +1172,18 @@ public void testAutoSnapshot() throws Exception { nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot"); nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta"); nodeOptions.setSnapshotIntervalSecs(10); - nodeOptions.setInitialConf(new Configuration(Arrays.asList(new PeerId(addr, 0)))); + nodeOptions.setInitialConf(new Configuration(Collections.singletonList(new PeerId(addr, 0)))); final Node node = new NodeImpl("unittest", new PeerId(addr, 0)); assertTrue(node.init(nodeOptions)); - //wait node elect self as leader + // wait node elect self as leader Thread.sleep(2000); this.sendTestTaskAndWait(node); assertEquals(-1, fsm.getSnapshotIndex()); assertEquals(0, fsm.getSaveSnapshotTimes()); - //wait for auto snapshot + // wait for auto snapshot Thread.sleep(10000); assertEquals(1, fsm.getSaveSnapshotTimes()); assertTrue(fsm.getSnapshotIndex() > 0); @@ -1078,7 +1205,7 @@ public void testLeaderShouldNotChange() throws Exception { } cluster.waitLeader(); - //get leader + // get leader final Node leader0 = cluster.getLeader(); assertNotNull(leader0); final long savedTerm = ((NodeImpl) leader0).getCurrentTerm(); @@ -1124,7 +1251,7 @@ public void testRecoverFollower() throws Exception { } // wait leader to compact logs Thread.sleep(5000); - //restart follower + // restart follower assertTrue(cluster.start(followerAddr)); assertTrue(cluster.ensureSame(30)); assertEquals(3, cluster.getFsms().size()); @@ -1242,10 +1369,10 @@ public void testLeaderTransferResumeOnFailure() throws Exception { leader = cluster.getLeader(); assertSame(leader, savedLeader); - //restart target peer + // restart target peer assertTrue(cluster.start(targetPeer.getEndpoint())); Thread.sleep(100); - //retry apply task + // retry apply task latch = new CountDownLatch(1); task = new Task(ByteBuffer.wrap("aaaaa".getBytes()), new ExpectClosure(latch)); leader.apply(task); @@ -1290,14 +1417,14 @@ public void testShutdownAndJoinWorkAfterInitFails() throws Exception { nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot"); nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta"); nodeOptions.setSnapshotIntervalSecs(10); - nodeOptions.setInitialConf(new Configuration(Arrays.asList(new PeerId(addr, 0)))); + nodeOptions.setInitialConf(new Configuration(Collections.singletonList(new PeerId(addr, 0)))); final Node node = new NodeImpl("unittest", new PeerId(addr, 0)); assertTrue(node.init(nodeOptions)); Thread.sleep(1000); this.sendTestTaskAndWait(node); - //save snapshot + // save snapshot final CountDownLatch latch = new CountDownLatch(1); node.snapshot(new ExpectClosure(latch)); waitLatch(latch); @@ -1312,7 +1439,7 @@ public void testShutdownAndJoinWorkAfterInitFails() throws Exception { nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot"); nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta"); nodeOptions.setSnapshotIntervalSecs(10); - nodeOptions.setInitialConf(new Configuration(Arrays.asList(new PeerId(addr, 0)))); + nodeOptions.setInitialConf(new Configuration(Collections.singletonList(new PeerId(addr, 0)))); final Node node = new NodeImpl("unittest", new PeerId(addr, 0)); assertFalse(node.init(nodeOptions)); @@ -1410,7 +1537,7 @@ public void testTransferShouldWorkAfterInstallSnapshot() throws Exception { leader.snapshot(new ExpectClosure(latch)); waitLatch(latch); - //start the last peer which should be recover with snapshot. + // start the last peer which should be recover with snapshot. final PeerId lastPeer = peers.get(2); assertTrue(cluster.start(lastPeer.getEndpoint())); Thread.sleep(5000); @@ -1428,7 +1555,7 @@ public void testTransferShouldWorkAfterInstallSnapshot() throws Exception { @Test public void testAppendEntriesWhenFollowerIsInErrorState() throws Exception { - //start five nodes + // start five nodes final List peers = TestUtils.generatePeers(5); final TestCluster cluster = new TestCluster("unitest", dataPath, peers, 1000); @@ -1440,10 +1567,10 @@ public void testAppendEntriesWhenFollowerIsInErrorState() throws Exception { cluster.waitLeader(); final Node oldLeader = cluster.getLeader(); assertNotNull(oldLeader); - //apply something + // apply something this.sendTestTaskAndWait(oldLeader); - //set one follower into error state + // set one follower into error state final List followers = cluster.getFollowers(); assertEquals(4, followers.size()); final Node errorNode = followers.get(0); @@ -1460,14 +1587,14 @@ public void testAppendEntriesWhenFollowerIsInErrorState() throws Exception { final Node leader = cluster.getLeader(); assertNotNull(leader); LOG.info("Elect a new leader {}", leader); - //apply something again + // apply something again this.sendTestTaskAndWait(leader, 10, RaftError.SUCCESS); - //stop error follower + // stop error follower Thread.sleep(20); LOG.info("Stop error follower {}", errorNode); assertTrue(cluster.stop(errorFollowerAddr)); - //restart error and old leader + // restart error and old leader LOG.info("Restart error follower {} and old leader {}", errorFollowerAddr, oldLeaderAddr); assertTrue(cluster.start(errorFollowerAddr)); @@ -1483,7 +1610,7 @@ public void testAppendEntriesWhenFollowerIsInErrorState() throws Exception { @Test public void testFollowerStartStopFollowing() throws Exception { - //start five nodes + // start five nodes final List peers = TestUtils.generatePeers(5); final TestCluster cluster = new TestCluster("unitest", dataPath, peers, 1000); @@ -1494,10 +1621,10 @@ public void testFollowerStartStopFollowing() throws Exception { cluster.waitLeader(); final Node firstLeader = cluster.getLeader(); assertNotNull(firstLeader); - //apply something + // apply something this.sendTestTaskAndWait(firstLeader); - //assert follow times + // assert follow times final List firstFollowers = cluster.getFollowers(); assertEquals(4, firstFollowers.size()); for (final Node node : firstFollowers) { @@ -1505,7 +1632,7 @@ public void testFollowerStartStopFollowing() throws Exception { assertEquals(0, ((MockStateMachine) node.getOptions().getFsm()).getOnStopFollowingTimes()); } - //stop leader and elect new one + // stop leader and elect new one final Endpoint fstLeaderAddr = firstLeader.getNodeId().getPeerId().getEndpoint(); assertTrue(cluster.stop(fstLeaderAddr)); cluster.waitLeader(); @@ -1513,7 +1640,7 @@ public void testFollowerStartStopFollowing() throws Exception { assertNotNull(secondLeader); this.sendTestTaskAndWait(secondLeader, 10, RaftError.SUCCESS); - //ensure start/stop following times + // ensure start/stop following times final List secondFollowers = cluster.getFollowers(); assertEquals(3, secondFollowers.size()); for (final Node node : secondFollowers) { @@ -1549,8 +1676,8 @@ public void testFollowerStartStopFollowing() throws Exception { } @Test - public void readCommitedUserLog() throws Exception { - //setup cluster + public void readCommittedUserLog() throws Exception { + // setup cluster final List peers = TestUtils.generatePeers(3); final TestCluster cluster = new TestCluster("unitest", dataPath, peers, 1000); @@ -1570,7 +1697,7 @@ public void readCommitedUserLog() throws Exception { assertEquals(2, userLog.getIndex()); assertEquals("hello0", new String(userLog.getData().array())); - //index == 5 is a DATA log(a user log) + // index == 5 is a DATA log(a user log) userLog = leader.readCommittedUserLog(5); assertNotNull(userLog); assertEquals(5, userLog.getIndex()); @@ -1726,7 +1853,7 @@ public void testBootStrapWithoutSnapshot() throws Exception { @Test public void testChangePeers() throws Exception { final PeerId peer0 = new PeerId(TestUtils.getMyIp(), TestUtils.INIT_PORT); - final TestCluster cluster = new TestCluster("testChangePeers", dataPath, Arrays.asList(peer0)); + final TestCluster cluster = new TestCluster("testChangePeers", dataPath, Collections.singletonList(peer0)); assertTrue(cluster.start(peer0.getEndpoint())); cluster.waitLeader(); @@ -1745,7 +1872,7 @@ public void testChangePeers() throws Exception { Assert.assertEquals(peer, leader.getNodeId().getPeerId()); peer = new PeerId(TestUtils.getMyIp(), peer0.getEndpoint().getPort() + i + 1); final SynchronizedClosure done = new SynchronizedClosure(); - leader.changePeers(new Configuration(Arrays.asList(peer)), done); + leader.changePeers(new Configuration(Collections.singletonList(peer)), done); assertTrue(done.await().isOk()); } assertTrue(cluster.ensureSame()); @@ -1756,7 +1883,7 @@ public void testChangePeers() throws Exception { @Test public void testChangePeersAddMultiNodes() throws Exception { final PeerId peer0 = new PeerId(TestUtils.getMyIp(), TestUtils.INIT_PORT); - final TestCluster cluster = new TestCluster("testChangePeers", dataPath, Arrays.asList(peer0)); + final TestCluster cluster = new TestCluster("testChangePeers", dataPath, Collections.singletonList(peer0)); assertTrue(cluster.start(peer0.getEndpoint())); cluster.waitLeader(); @@ -1770,22 +1897,22 @@ public void testChangePeersAddMultiNodes() throws Exception { } PeerId peer = new PeerId(TestUtils.getMyIp(), peer0.getEndpoint().getPort() + 1); - //fail, because the peers are not started. + // fail, because the peers are not started. final SynchronizedClosure done = new SynchronizedClosure(); - leader.changePeers(new Configuration(Arrays.asList(peer)), done); + leader.changePeers(new Configuration(Collections.singletonList(peer)), done); Assert.assertEquals(RaftError.ECATCHUP, done.await().getRaftError()); - //start peer1 + // start peer1 assertTrue(cluster.start(peer.getEndpoint())); - //still fail, because peer2 is not started + // still fail, because peer2 is not started done.reset(); leader.changePeers(conf, done); Assert.assertEquals(RaftError.ECATCHUP, done.await().getRaftError()); - //start peer2 + // start peer2 peer = new PeerId(TestUtils.getMyIp(), peer0.getEndpoint().getPort() + 2); assertTrue(cluster.start(peer.getEndpoint())); done.reset(); - //works + // works leader.changePeers(conf, done); assertTrue(done.await().isOk()); @@ -1806,7 +1933,7 @@ public void testChangePeersStepsDownInJointConsensus() throws Exception { final PeerId peer2 = JRaftUtils.getPeerId("127.0.0.1:5008"); final PeerId peer3 = JRaftUtils.getPeerId("127.0.0.1:5009"); - //start single cluster + // start single cluster peers.add(peer0); final TestCluster cluster = new TestCluster("testChangePeersStepsDownInJointConsensus", dataPath, peers); assertTrue(cluster.start(peer0.getEndpoint())); @@ -1816,7 +1943,7 @@ public void testChangePeersStepsDownInJointConsensus() throws Exception { assertNotNull(leader); this.sendTestTaskAndWait(leader); - //start peer1-3 + // start peer1-3 assertTrue(cluster.start(peer1.getEndpoint())); assertTrue(cluster.start(peer2.getEndpoint())); assertTrue(cluster.start(peer3.getEndpoint())); @@ -1827,12 +1954,12 @@ public void testChangePeersStepsDownInJointConsensus() throws Exception { conf.addPeer(peer2); conf.addPeer(peer3); - //change peers + // change peers final SynchronizedClosure done = new SynchronizedClosure(); leader.changePeers(conf, done); assertTrue(done.await().isOk()); - //stop peer3 + // stop peer3 assertTrue(cluster.stop(peer3.getEndpoint())); conf.removePeer(peer0); @@ -1883,53 +2010,49 @@ private Future startChangePeersThread(ChangeArg arg) { expectedErrors.add(RaftError.EPERM); expectedErrors.add(RaftError.ECATCHUP); - return Utils.runInThread(new Runnable() { - - @Override - public void run() { - try { - while (!arg.stop) { - arg.c.waitLeader(); - final Node leader = arg.c.getLeader(); - if (leader == null) { - continue; - } - //select peers in random - final Configuration conf = new Configuration(); - if (arg.dontRemoveFirstPeer) { - conf.addPeer(arg.peers.get(0)); - } - for (int i = 0; i < arg.peers.size(); i++) { - final boolean select = ThreadLocalRandom.current().nextInt(64) < 32; - if (select && !conf.contains(arg.peers.get(i))) { - conf.addPeer(arg.peers.get(i)); - } - } - if (conf.isEmpty()) { - LOG.warn("No peer has been selected"); - continue; + return Utils.runInThread(() -> { + try { + while (!arg.stop) { + arg.c.waitLeader(); + final Node leader = arg.c.getLeader(); + if (leader == null) { + continue; + } + // select peers in random + final Configuration conf = new Configuration(); + if (arg.dontRemoveFirstPeer) { + conf.addPeer(arg.peers.get(0)); + } + for (int i = 0; i < arg.peers.size(); i++) { + final boolean select = ThreadLocalRandom.current().nextInt(64) < 32; + if (select && !conf.contains(arg.peers.get(i))) { + conf.addPeer(arg.peers.get(i)); } - final SynchronizedClosure done = new SynchronizedClosure(); - leader.changePeers(conf, done); - done.await(); - assertTrue(done.getStatus().toString(), - done.getStatus().isOk() || expectedErrors.contains(done.getStatus().getRaftError())); } - } catch (final InterruptedException e) { - LOG.error("ChangePeersThread is interrupted", e); + if (conf.isEmpty()) { + LOG.warn("No peer has been selected"); + continue; + } + final SynchronizedClosure done = new SynchronizedClosure(); + leader.changePeers(conf, done); + done.await(); + assertTrue(done.getStatus().toString(), + done.getStatus().isOk() || expectedErrors.contains(done.getStatus().getRaftError())); } + } catch (final InterruptedException e) { + LOG.error("ChangePeersThread is interrupted", e); } }); } @Test public void testChangePeersChaosWithSnapshot() throws Exception { - //start cluster + // start cluster final List peers = new ArrayList<>(); peers.add(new PeerId("127.0.0.1", TestUtils.INIT_PORT)); final TestCluster cluster = new TestCluster("unittest", dataPath, peers, 1000); assertTrue(cluster.start(peers.get(0).getEndpoint(), false, 1)); - //start other peers + // start other peers for (int i = 1; i < 10; i++) { final PeerId peer = new PeerId("127.0.0.1", TestUtils.INIT_PORT + i); peers.add(peer); @@ -1972,12 +2095,12 @@ public void testChangePeersChaosWithSnapshot() throws Exception { @Test public void testChangePeersChaosWithoutSnapshot() throws Exception { - //start cluster + // start cluster final List peers = new ArrayList<>(); peers.add(new PeerId("127.0.0.1", TestUtils.INIT_PORT)); final TestCluster cluster = new TestCluster("unittest", dataPath, peers, 1000); assertTrue(cluster.start(peers.get(0).getEndpoint(), false, 100000)); - //start other peers + // start other peers for (int i = 1; i < 10; i++) { final PeerId peer = new PeerId("127.0.0.1", TestUtils.INIT_PORT + i); peers.add(peer); @@ -2021,12 +2144,12 @@ public void testChangePeersChaosWithoutSnapshot() throws Exception { @Test public void testChangePeersChaosApplyTasks() throws Exception { - //start cluster + // start cluster final List peers = new ArrayList<>(); peers.add(new PeerId("127.0.0.1", TestUtils.INIT_PORT)); final TestCluster cluster = new TestCluster("unittest", dataPath, peers, 1000); assertTrue(cluster.start(peers.get(0).getEndpoint(), false, 100000)); - //start other peers + // start other peers for (int i = 1; i < 10; i++) { final PeerId peer = new PeerId("127.0.0.1", TestUtils.INIT_PORT + i); peers.add(peer); @@ -2042,31 +2165,28 @@ public void testChangePeersChaosApplyTasks() throws Exception { args.add(arg); futures.add(startChangePeersThread(arg)); - Utils.runInThread(new Runnable() { - @Override - public void run() { - try { - for (int i = 0; i < 5000;) { - cluster.waitLeader(); - final Node leader = cluster.getLeader(); - if (leader == null) { - continue; - } - final SynchronizedClosure done = new SynchronizedClosure(); - final Task task = new Task(ByteBuffer.wrap(("hello" + i).getBytes()), done); - leader.apply(task); - final Status status = done.await(); - if (status.isOk()) { - LOG.info("Progress:" + (++i)); - } else { - assertEquals(RaftError.EPERM, status.getRaftError()); - } + Utils.runInThread(() -> { + try { + for (int i = 0; i < 5000;) { + cluster.waitLeader(); + final Node leader = cluster.getLeader(); + if (leader == null) { + continue; + } + final SynchronizedClosure done = new SynchronizedClosure(); + final Task task = new Task(ByteBuffer.wrap(("hello" + i).getBytes()), done); + leader.apply(task); + final Status status = done.await(); + if (status.isOk()) { + LOG.info("Progress:" + (++i)); + } else { + assertEquals(RaftError.EPERM, status.getRaftError()); } - } catch (final Exception e) { - e.printStackTrace(); - } finally { - latch.countDown(); } + } catch (final Exception e) { + e.printStackTrace(); + } finally { + latch.countDown(); } }); } diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java index 785e5800f..19a98a10f 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/core/TestCluster.java @@ -36,6 +36,7 @@ import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.entity.PeerId; import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.option.RaftOptions; import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory; import com.alipay.sofa.jraft.storage.SnapshotThrottle; import com.alipay.sofa.jraft.util.Endpoint; @@ -84,11 +85,16 @@ public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotInterv public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotIntervalSecs, boolean enableMetrics) throws IOException { - return this.start(listenAddr, emptyPeers, snapshotIntervalSecs, enableMetrics, null); + return this.start(listenAddr, emptyPeers, snapshotIntervalSecs, enableMetrics, null, null); } public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotIntervalSecs, boolean enableMetrics, SnapshotThrottle snapshotThrottle) throws IOException { + return this.start(listenAddr, emptyPeers, snapshotIntervalSecs, enableMetrics, snapshotThrottle, null); + } + + public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotIntervalSecs, boolean enableMetrics, + SnapshotThrottle snapshotThrottle, RaftOptions raftOptions) throws IOException { if (this.serverMap.get(listenAddr.toString()) != null) { return true; @@ -99,6 +105,9 @@ public boolean start(Endpoint listenAddr, boolean emptyPeers, int snapshotInterv nodeOptions.setEnableMetrics(enableMetrics); nodeOptions.setSnapshotThrottle(snapshotThrottle); nodeOptions.setSnapshotIntervalSecs(snapshotIntervalSecs); + if (raftOptions != null) { + nodeOptions.setRaftOptions(raftOptions); + } final String serverDataPath = this.dataPath + File.separator + listenAddr.toString().replace(':', '_'); FileUtils.forceMkdir(new File(serverDataPath)); nodeOptions.setLogUri(serverDataPath + File.separator + "logs"); diff --git a/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/FileServiceTest.java b/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/FileServiceTest.java index 52bbea4e5..cc1d157e2 100644 --- a/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/FileServiceTest.java +++ b/jraft-core/src/test/java/com/alipay/sofa/jraft/storage/FileServiceTest.java @@ -35,6 +35,7 @@ import com.alipay.sofa.jraft.test.TestUtils; import com.google.protobuf.Message; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -107,8 +108,51 @@ public void testGetFileData() throws IOException { Message msg = FileService.getInstance().handleGetFile(request, new RpcRequestClosure(bizContext, asyncContext)); assertTrue(msg instanceof RpcRequests.GetFileResponse); RpcRequests.GetFileResponse response = (RpcRequests.GetFileResponse) msg; - assertEquals(response.getEof(), true); + assertTrue(response.getEof()); assertEquals("jraft is great!", new String(response.getData().toByteArray())); assertEquals(-1, response.getReadSize()); } + + private String writeLargeData() throws IOException { + File file = new File(this.path + File.separator + "data"); + String data = "jraft is great!"; + for (int i = 0; i < 1000; i++) { + FileUtils.writeStringToFile(file, data, true); + } + return data; + } + + @Test + public void testGetLargeFileData() throws IOException { + final String data = writeLargeData(); + final long readerId = FileService.getInstance().addReader(this.fileReader); + int fileOffset = 0; + while (true) { + final RpcRequests.GetFileRequest request = RpcRequests.GetFileRequest.newBuilder() // + .setCount(4096).setFilename("data") // + .setOffset(fileOffset) // + .setReaderId(readerId) // + .build(); + final BizContext bizContext = Mockito.mock(BizContext.class); + final AsyncContext asyncContext = Mockito.mock(AsyncContext.class); + final Message msg = FileService.getInstance() // + .handleGetFile(request, new RpcRequestClosure(bizContext, asyncContext)); + assertTrue(msg instanceof RpcRequests.GetFileResponse); + final RpcRequests.GetFileResponse response = (RpcRequests.GetFileResponse) msg; + final byte[] sourceArray = data.getBytes(); + final byte[] respData = response.getData().toByteArray(); + final int length = sourceArray.length; + int offset = 0; + while (offset + length <= respData.length) { + final byte[] respArray = new byte[length]; + System.arraycopy(respData, offset, respArray, 0, length); + assertArrayEquals(sourceArray, respArray); + offset += length; + } + fileOffset += offset; + if (response.getEof()) { + break; + } + } + } } From 09fb200d39c56a9bc0adb853a24104bd172920d5 Mon Sep 17 00:00:00 2001 From: block Date: Mon, 1 Apr 2019 14:03:42 +0800 Subject: [PATCH 31/31] (release) v1.2.5 (#82) --- jraft-core/pom.xml | 2 +- jraft-example/pom.xml | 2 +- jraft-rheakv/pom.xml | 2 +- jraft-rheakv/rheakv-core/pom.xml | 2 +- jraft-rheakv/rheakv-pd/pom.xml | 2 +- jraft-test/pom.xml | 2 +- pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jraft-core/pom.xml b/jraft-core/pom.xml index 4cb23de05..a20c939d9 100644 --- a/jraft-core/pom.xml +++ b/jraft-core/pom.xml @@ -5,7 +5,7 @@ jraft-parent com.alipay.sofa - 1.2.4 + 1.2.5 jraft-core jar diff --git a/jraft-example/pom.xml b/jraft-example/pom.xml index c10b57828..7cc7ced08 100644 --- a/jraft-example/pom.xml +++ b/jraft-example/pom.xml @@ -5,7 +5,7 @@ jraft-parent com.alipay.sofa - 1.2.4 + 1.2.5 jraft-example jar diff --git a/jraft-rheakv/pom.xml b/jraft-rheakv/pom.xml index 6e67527f0..ba4acb750 100644 --- a/jraft-rheakv/pom.xml +++ b/jraft-rheakv/pom.xml @@ -4,7 +4,7 @@ jraft-parent com.alipay.sofa - 1.2.4 + 1.2.5 jraft-rheakv diff --git a/jraft-rheakv/rheakv-core/pom.xml b/jraft-rheakv/rheakv-core/pom.xml index a51c69c57..83d647e01 100644 --- a/jraft-rheakv/rheakv-core/pom.xml +++ b/jraft-rheakv/rheakv-core/pom.xml @@ -4,7 +4,7 @@ jraft-rheakv com.alipay.sofa - 1.2.4 + 1.2.5 jraft-rheakv-core diff --git a/jraft-rheakv/rheakv-pd/pom.xml b/jraft-rheakv/rheakv-pd/pom.xml index d7b6e0070..fb4048aa3 100644 --- a/jraft-rheakv/rheakv-pd/pom.xml +++ b/jraft-rheakv/rheakv-pd/pom.xml @@ -4,7 +4,7 @@ jraft-rheakv com.alipay.sofa - 1.2.4 + 1.2.5 jraft-rheakv-pd diff --git a/jraft-test/pom.xml b/jraft-test/pom.xml index f03015e45..842e9052d 100644 --- a/jraft-test/pom.xml +++ b/jraft-test/pom.xml @@ -5,7 +5,7 @@ jraft-parent com.alipay.sofa - 1.2.4 + 1.2.5 jraft-test jar diff --git a/pom.xml b/pom.xml index aa59cd814..dc010f847 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.alipay.sofa jraft-parent - 1.2.4 + 1.2.5 pom ${project.groupId}:${project.artifactId}