You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by as...@apache.org on 2020/12/29 10:27:27 UTC

[ignite-3] branch ignite-13885 updated (9b5a16b -> 9df4773)

This is an automated email from the ASF dual-hosted git repository.

ascherbakov pushed a change to branch ignite-13885
in repository https://gitbox.apache.org/repos/asf/ignite-3.git.


    from 9b5a16b  IGNITE-13875 Update .asf.yaml once more to enable config
     new 899f44a  IGNITE-13885 wip.
     new 9df4773  IGNITE-13885 compiling

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../pom.xml                                        |  109 +-
 .../java/com/alipay/sofa/jraft/CliService.java     |  192 ++
 .../main/java/com/alipay/sofa/jraft/Closure.java}  |   21 +-
 .../main/java/com/alipay/sofa/jraft/FSMCaller.java |  123 +
 .../main/java/com/alipay/sofa/jraft/Iterator.java  |   75 +
 .../com/alipay/sofa/jraft/JRaftServiceFactory.java |   62 +
 .../java/com/alipay/sofa/jraft/JRaftUtils.java     |  152 +
 .../java/com/alipay/sofa/jraft/Lifecycle.java}     |   31 +-
 .../src/main/java/com/alipay/sofa/jraft/Node.java  |  327 ++
 .../sofa/jraft/NodeDescribeSignalHandler.java      |   68 +
 .../java/com/alipay/sofa/jraft/NodeManager.java    |  148 +
 .../sofa/jraft/NodeMetricsSignalHandler.java       |   77 +
 .../com/alipay/sofa/jraft/RaftGroupService.java    |  256 ++
 .../com/alipay/sofa/jraft/RaftServiceFactory.java  |   70 +
 .../com/alipay/sofa/jraft/ReadOnlyService.java     |   53 +
 .../com/alipay/sofa/jraft/ReplicatorGroup.java     |  229 ++
 .../java/com/alipay/sofa/jraft/RouteTable.java     |  384 +++
 .../java/com/alipay/sofa/jraft/StateMachine.java   |  144 +
 .../main/java/com/alipay/sofa/jraft/Status.java    |  233 ++
 .../sofa/jraft/ThreadPoolMetricsSignalHandler.java |   60 +
 .../alipay/sofa/jraft/closure/CatchUpClosure.java  |   72 +
 .../alipay/sofa/jraft/closure/ClosureQueue.java    |   80 +
 .../sofa/jraft/closure/ClosureQueueImpl.java       |  145 +
 .../alipay/sofa/jraft/closure/JoinableClosure.java |   58 +
 .../sofa/jraft/closure/LoadSnapshotClosure.java}   |   29 +-
 .../sofa/jraft/closure/ReadIndexClosure.java       |  166 +
 .../sofa/jraft/closure/SaveSnapshotClosure.java}   |   37 +-
 .../sofa/jraft/closure/SynchronizedClosure.java    |   83 +
 .../alipay/sofa/jraft/closure/TaskClosure.java}    |   31 +-
 .../com/alipay/sofa/jraft/conf/Configuration.java  |  324 ++
 .../alipay/sofa/jraft/conf/ConfigurationEntry.java |  129 +
 .../sofa/jraft/conf/ConfigurationManager.java      |  110 +
 .../java/com/alipay/sofa/jraft/core/BallotBox.java |  283 ++
 .../com/alipay/sofa/jraft/core/CliServiceImpl.java |  687 ++++
 .../jraft/core/DefaultJRaftServiceFactory.java     |   69 +
 .../alipay/sofa/jraft/core/ElectionPriority.java}  |   34 +-
 .../com/alipay/sofa/jraft/core/FSMCallerImpl.java  |  729 +++++
 .../com/alipay/sofa/jraft/core/IteratorImpl.java   |  161 +
 .../alipay/sofa/jraft/core/IteratorWrapper.java    |   75 +
 .../java/com/alipay/sofa/jraft/core/NodeImpl.java  | 3460 ++++++++++++++++++++
 .../com/alipay/sofa/jraft/core/NodeMetrics.java    |  107 +
 .../sofa/jraft/core/ReadOnlyServiceImpl.java       |  422 +++
 .../com/alipay/sofa/jraft/core/Replicator.java     | 1788 ++++++++++
 .../sofa/jraft/core/ReplicatorGroupImpl.java       |  312 ++
 .../alipay/sofa/jraft/core/ReplicatorType.java}    |   22 +-
 .../java/com/alipay/sofa/jraft/core/Scheduler.java |   88 +
 .../java/com/alipay/sofa/jraft/core/State.java}    |   29 +-
 .../sofa/jraft/core/StateMachineAdapter.java       |  109 +
 .../com/alipay/sofa/jraft/core/TimerManager.java   |   71 +
 .../java/com/alipay/sofa/jraft/entity/Ballot.java  |  141 +
 .../com/alipay/sofa/jraft/entity/Checksum.java}    |   35 +-
 .../com/alipay/sofa/jraft/entity/EnumOutter.java   |  149 +
 .../sofa/jraft/entity/LeaderChangeContext.java     |  114 +
 .../sofa/jraft/entity/LocalFileMetaOutter.java     |   92 +
 .../sofa/jraft/entity/LocalStorageOutter.java      |  112 +
 .../com/alipay/sofa/jraft/entity/LogEntry.java     |  291 ++
 .../java/com/alipay/sofa/jraft/entity/LogId.java   |  125 +
 .../java/com/alipay/sofa/jraft/entity/NodeId.java  |   95 +
 .../java/com/alipay/sofa/jraft/entity/PeerId.java  |  278 ++
 .../com/alipay/sofa/jraft/entity/RaftOutter.java   |  132 +
 .../alipay/sofa/jraft/entity/ReadIndexState.java   |   65 +
 .../alipay/sofa/jraft/entity/ReadIndexStatus.java  |   57 +
 .../java/com/alipay/sofa/jraft/entity/Task.java    |  174 +
 .../java/com/alipay/sofa/jraft/entity/UserLog.java |   66 +
 .../sofa/jraft/entity/codec/AutoDetectDecoder.java |   50 +
 .../entity/codec/DefaultLogEntryCodecFactory.java  |   63 +
 .../jraft/entity/codec/LogEntryCodecFactory.java}  |   29 +-
 .../sofa/jraft/entity/codec/LogEntryDecoder.java}  |   20 +-
 .../sofa/jraft/entity/codec/LogEntryEncoder.java}  |   23 +-
 .../entity/codec/v1/LogEntryV1CodecFactory.java    |   57 +
 .../sofa/jraft/entity/codec/v1/V1Decoder.java      |  116 +
 .../sofa/jraft/entity/codec/v1/V1Encoder.java      |  131 +
 .../sofa/jraft/error/InvokeTimeoutException.java}  |   28 +-
 .../alipay/sofa/jraft/error/JRaftException.java}   |   25 +-
 .../jraft/error/LogEntryCorruptedException.java    |   53 +
 .../jraft/error/LogIndexOutOfBoundsException.java  |   56 +
 .../sofa/jraft/error/LogNotFoundException.java}    |   32 +-
 .../jraft/error/MessageClassNotFoundException.java |   49 +
 .../com/alipay/sofa/jraft/error/RaftError.java     |  284 ++
 .../com/alipay/sofa/jraft/error/RaftException.java |   82 +
 .../sofa/jraft/error/RemotingException.java}       |   30 +-
 .../sofa/jraft/error/RetryAgainException.java}     |   38 +-
 .../sofa/jraft/option/BallotBoxOptions.java}       |   37 +-
 .../alipay/sofa/jraft/option/BootstrapOptions.java |  129 +
 .../com/alipay/sofa/jraft/option/CliOptions.java}  |   34 +-
 .../com/alipay/sofa/jraft/option/CopyOptions.java  |   55 +
 .../alipay/sofa/jraft/option/FSMCallerOptions.java |  100 +
 .../sofa/jraft/option/LogManagerOptions.java       |   99 +
 .../sofa/jraft/option/LogStorageOptions.java       |   49 +
 .../com/alipay/sofa/jraft/option/NodeOptions.java  |  443 +++
 .../sofa/jraft/option/RaftMetaStorageOptions.java} |   20 +-
 .../com/alipay/sofa/jraft/option/RaftOptions.java  |  270 ++
 .../alipay/sofa/jraft/option/ReadOnlyOption.java}  |   32 +-
 .../sofa/jraft/option/ReadOnlyServiceOptions.java  |   56 +
 .../sofa/jraft/option/ReplicatorGroupOptions.java  |  124 +
 .../sofa/jraft/option/ReplicatorOptions.java       |  218 ++
 .../com/alipay/sofa/jraft/option/RpcOptions.java   |  113 +
 .../sofa/jraft/option/SnapshotCopierOptions.java   |   80 +
 .../sofa/jraft/option/SnapshotExecutorOptions.java |  107 +
 .../alipay/sofa/jraft/rpc/CliClientService.java    |  156 +
 .../com/alipay/sofa/jraft/rpc/CliRequests.java     |  529 +++
 .../com/alipay/sofa/jraft/rpc/ClientService.java   |   78 +
 .../java/com/alipay/sofa/jraft/rpc/Connection.java |   56 +
 .../alipay/sofa/jraft/rpc/HasErrorResponse.java    |    5 +
 .../com/alipay/sofa/jraft/rpc/InvokeCallback.java} |   16 +-
 .../com/alipay/sofa/jraft/rpc/InvokeContext.java   |   56 +
 .../java/com/alipay/sofa/jraft/rpc/Message.java    |    4 +
 .../sofa/jraft/rpc/MessageBuilderFactory.java      |    9 +
 .../alipay/sofa/jraft/rpc/RaftClientService.java   |  112 +
 .../com/alipay/sofa/jraft/rpc/RaftRpcFactory.java  |  108 +
 .../sofa/jraft/rpc/RaftRpcServerFactory.java       |  142 +
 .../alipay/sofa/jraft/rpc/RaftServerService.java   |   88 +
 .../java/com/alipay/sofa/jraft/rpc/RpcClient.java  |  110 +
 .../com/alipay/sofa/jraft/rpc/RpcContext.java}     |   38 +-
 .../com/alipay/sofa/jraft/rpc/RpcProcessor.java    |   75 +
 .../alipay/sofa/jraft/rpc/RpcRequestClosure.java   |   79 +
 .../alipay/sofa/jraft/rpc/RpcRequestProcessor.java |   73 +
 .../com/alipay/sofa/jraft/rpc/RpcRequests.java     |  499 +++
 .../alipay/sofa/jraft/rpc/RpcResponseClosure.java} |   30 +-
 .../sofa/jraft/rpc/RpcResponseClosureAdapter.java} |   31 +-
 .../alipay/sofa/jraft/rpc/RpcResponseFactory.java  |   82 +
 .../java/com/alipay/sofa/jraft/rpc/RpcServer.java} |   39 +-
 .../java/com/alipay/sofa/jraft/rpc/RpcUtils.java   |  134 +
 .../sofa/jraft/rpc/impl/AbstractClientService.java |  296 ++
 .../rpc/impl/ConnectionClosedEventListener.java}   |   13 +-
 .../com/alipay/sofa/jraft/rpc/impl/FutureImpl.java |  242 ++
 .../sofa/jraft/rpc/impl/LocalRaftRpcFactory.java   |   46 +
 .../alipay/sofa/jraft/rpc/impl/LocalRpcClient.java |   64 +
 .../sofa/jraft/rpc/impl/LocalRpcServer.java}       |   35 +-
 .../sofa/jraft/rpc/impl/PingRequestProcessor.java  |   45 +
 .../rpc/impl/cli/AddLearnersRequestProcessor.java  |  101 +
 .../rpc/impl/cli/AddPeerRequestProcessor.java      |   93 +
 .../rpc/impl/cli/BaseCliRequestProcessor.java      |  148 +
 .../rpc/impl/cli/ChangePeersRequestProcessor.java  |   91 +
 .../jraft/rpc/impl/cli/CliClientServiceImpl.java   |  129 +
 .../rpc/impl/cli/GetLeaderRequestProcessor.java    |  107 +
 .../rpc/impl/cli/GetPeersRequestProcessor.java     |   75 +
 .../impl/cli/RemoveLearnersRequestProcessor.java   |   96 +
 .../rpc/impl/cli/RemovePeerRequestProcessor.java   |   87 +
 .../impl/cli/ResetLearnersRequestProcessor.java    |   97 +
 .../rpc/impl/cli/ResetPeerRequestProcessor.java    |   80 +
 .../rpc/impl/cli/SnapshotRequestProcessor.java     |   61 +
 .../impl/cli/TransferLeaderRequestProcessor.java   |   74 +
 .../impl/core/AppendEntriesRequestProcessor.java   |  517 +++
 .../rpc/impl/core/DefaultRaftClientService.java    |  158 +
 .../rpc/impl/core/GetFileRequestProcessor.java     |   50 +
 .../impl/core/InstallSnapshotRequestProcessor.java |   60 +
 .../jraft/rpc/impl/core/NodeRequestProcessor.java  |   73 +
 .../rpc/impl/core/ReadIndexRequestProcessor.java   |   73 +
 .../rpc/impl/core/RequestVoteRequestProcessor.java |   64 +
 .../rpc/impl/core/TimeoutNowRequestProcessor.java  |   59 +
 .../rpc/message/DefaultMessageBuilderFactory.java  |   50 +
 .../com/alipay/sofa/jraft/storage/FileService.java |  151 +
 .../com/alipay/sofa/jraft/storage/LogManager.java  |  241 ++
 .../com/alipay/sofa/jraft/storage/LogStorage.java  |   83 +
 .../alipay/sofa/jraft/storage/RaftMetaStorage.java |   56 +
 .../sofa/jraft/storage/SnapshotExecutor.java       |   98 +
 .../alipay/sofa/jraft/storage/SnapshotStorage.java |   74 +
 .../sofa/jraft/storage/SnapshotThrottle.java}      |   21 +-
 .../com/alipay/sofa/jraft/storage/Storage.java}    |   18 +-
 .../sofa/jraft/storage/impl/LocalLogStorage.java   |  311 ++
 .../jraft/storage/impl/LocalRaftMetaStorage.java   |  195 ++
 .../sofa/jraft/storage/impl/LogManagerImpl.java    | 1190 +++++++
 .../alipay/sofa/jraft/storage/io/FileReader.java   |   57 +
 .../sofa/jraft/storage/io/LocalDirReader.java      |   97 +
 .../alipay/sofa/jraft/storage/io/ProtoBufFile.java |  141 +
 .../sofa/jraft/storage/snapshot/Snapshot.java      |   58 +
 .../jraft/storage/snapshot/SnapshotCopier.java     |   53 +
 .../storage/snapshot/SnapshotExecutorImpl.java     |  743 +++++
 .../jraft/storage/snapshot/SnapshotReader.java}    |   33 +-
 .../jraft/storage/snapshot/SnapshotWriter.java     |   77 +
 .../snapshot/ThroughputSnapshotThrottle.java       |   82 +
 .../storage/snapshot/local/LocalSnapshot.java      |   62 +
 .../snapshot/local/LocalSnapshotCopier.java        |  439 +++
 .../snapshot/local/LocalSnapshotMetaTable.java     |  185 ++
 .../snapshot/local/LocalSnapshotReader.java        |  167 +
 .../snapshot/local/LocalSnapshotStorage.java       |  352 ++
 .../snapshot/local/LocalSnapshotWriter.java        |  140 +
 .../storage/snapshot/local/SnapshotFileReader.java |   92 +
 .../jraft/storage/snapshot/remote/CopySession.java |  302 ++
 .../storage/snapshot/remote/RemoteFileCopier.java  |  192 ++
 .../jraft/storage/snapshot/remote/Session.java}    |   39 +-
 .../sofa/jraft/util/AdaptiveBufAllocator.java      |  203 ++
 .../com/alipay/sofa/jraft/util/ArrayDeque.java     |  110 +
 .../alipay/sofa/jraft/util/AsciiStringUtil.java    |   60 +
 .../main/java/com/alipay/sofa/jraft/util/Bits.java |   50 +
 .../sofa/jraft/util/ByteBufferCollector.java       |  145 +
 .../com/alipay/sofa/jraft/util/ByteString.java     |   39 +
 .../java/com/alipay/sofa/jraft/util/Bytes.java     |  138 +
 .../java/com/alipay/sofa/jraft/util/BytesUtil.java |  181 +
 .../java/com/alipay/sofa/jraft/util/CRC64.java     |  128 +
 .../java/com/alipay/sofa/jraft/util/Copiable.java} |   20 +-
 .../com/alipay/sofa/jraft/util/CountDownEvent.java |   75 +
 .../java/com/alipay/sofa/jraft/util/CrcUtil.java   |   84 +
 .../java/com/alipay/sofa/jraft/util/Describer.java |   70 +
 .../alipay/sofa/jraft/util/DirectExecutor.java}    |   26 +-
 .../alipay/sofa/jraft/util/DisruptorBuilder.java   |   98 +
 .../alipay/sofa/jraft/util/DisruptorMetricSet.java |   50 +
 .../java/com/alipay/sofa/jraft/util/Endpoint.java  |   97 +
 .../sofa/jraft/util/ExecutorServiceHelper.java     |   73 +
 .../sofa/jraft/util/FileOutputSignalHandler.java   |   52 +
 .../alipay/sofa/jraft/util/HeapByteBufUtil.java    |  133 +
 .../main/java/com/alipay/sofa/jraft/util/Ints.java |   84 +
 .../com/alipay/sofa/jraft/util/JDKMarshaller.java  |   32 +
 .../alipay/sofa/jraft/util/JRaftServiceLoader.java |  357 ++
 .../sofa/jraft/util/JRaftSignalHandler.java}       |   15 +-
 .../sofa/jraft/util/LogExceptionHandler.java       |   70 +
 .../jraft/util/LogScheduledThreadPoolExecutor.java |   94 +
 .../sofa/jraft/util/LogThreadPoolExecutor.java     |   99 +
 .../com/alipay/sofa/jraft/util/Marshaller.java     |   11 +
 .../com/alipay/sofa/jraft/util/MetricReporter.java |  436 +++
 .../util/MetricScheduledThreadPoolExecutor.java    |   75 +
 .../sofa/jraft/util/MetricThreadPoolExecutor.java  |   80 +
 .../main/java/com/alipay/sofa/jraft/util/Mpsc.java |   50 +
 .../alipay/sofa/jraft/util/NamedThreadFactory.java |   70 +
 .../alipay/sofa/jraft/util/NonReentrantLock.java   |   92 +
 .../com/alipay/sofa/jraft/util/OnlyForTest.java}   |   32 +-
 .../java/com/alipay/sofa/jraft/util/Platform.java  |   69 +
 .../com/alipay/sofa/jraft/util/Recyclable.java}    |   18 +-
 .../sofa/jraft/util/RecyclableByteBufferList.java  |  136 +
 .../com/alipay/sofa/jraft/util/RecycleUtil.java}   |   21 +-
 .../java/com/alipay/sofa/jraft/util/Recyclers.java |  414 +++
 .../com/alipay/sofa/jraft/util/RepeatedTimer.java  |  295 ++
 .../java/com/alipay/sofa/jraft/util/Requires.java  |  107 +
 .../alipay/sofa/jraft/util/RpcFactoryHelper.java}  |   33 +-
 .../main/java/com/alipay/sofa/jraft/util/SPI.java} |   23 +-
 .../com/alipay/sofa/jraft/util/SegmentList.java    |  394 +++
 .../com/alipay/sofa/jraft/util/StringUtils.java    |  105 +
 .../alipay/sofa/jraft/util/SystemPropertyUtil.java |  188 ++
 .../com/alipay/sofa/jraft/util/ThreadHelper.java}  |   49 +-
 .../java/com/alipay/sofa/jraft/util/ThreadId.java  |  173 +
 .../sofa/jraft/util/ThreadPoolMetricRegistry.java} |   32 +-
 .../sofa/jraft/util/ThreadPoolMetricSet.java       |   52 +
 .../com/alipay/sofa/jraft/util/ThreadPoolUtil.java |  283 ++
 .../java/com/alipay/sofa/jraft/util/Utils.java     |  451 +++
 .../jraft/util/concurrent/AdjustableSemaphore.java |  129 +
 .../jraft/util/concurrent/ConcurrentHashSet.java   |   95 +
 .../concurrent/DefaultExecutorChooserFactory.java  |   81 +
 .../DefaultFixedThreadsExecutorGroup.java          |  115 +
 .../DefaultFixedThreadsExecutorGroupFactory.java   |   81 +
 .../concurrent/DefaultSingleThreadExecutor.java    |  117 +
 .../util/concurrent/ExecutorChooserFactory.java}   |   39 +-
 .../util/concurrent/FixedThreadsExecutorGroup.java |   59 +
 .../FixedThreadsExecutorGroupFactory.java          |   43 +
 .../concurrent/LongHeldDetectingReadWriteLock.java |  153 +
 .../util/concurrent/MpscSingleThreadExecutor.java  |  401 +++
 .../util/concurrent/RejectedExecutionHandler.java} |   30 +-
 .../concurrent/RejectedExecutionHandlers.java}     |   35 +-
 .../util/concurrent/SingleThreadExecutor.java}     |   36 +-
 .../jraft/util/internal/IntegerFieldUpdater.java}  |   14 +-
 .../jraft/util/internal/LongFieldUpdater.java}     |   14 +-
 .../util/internal/ReferenceFieldUpdater.java}      |   14 +-
 .../internal/ReflectionIntegerFieldUpdater.java    |   51 +
 .../util/internal/ReflectionLongFieldUpdater.java  |   51 +
 .../internal/ReflectionReferenceFieldUpdater.java  |   52 +
 .../alipay/sofa/jraft/util/internal/ThrowUtil.java |   73 +
 .../sofa/jraft/util/internal/UnsafeUtil.java       |  629 ++++
 .../alipay/sofa/jraft/util/internal/Updaters.java  |   87 +
 .../jraft/util/timer/DefaultRaftTimerFactory.java  |  242 ++
 .../alipay/sofa/jraft/util/timer/DefaultTimer.java |  123 +
 .../sofa/jraft/util/timer/HashedWheelTimer.java    |  758 +++++
 .../sofa/jraft/util/timer/RaftTimerFactory.java}   |   32 +-
 .../com/alipay/sofa/jraft/util/timer/Timeout.java  |   55 +
 .../com/alipay/sofa/jraft/util/timer/Timer.java    |   51 +
 .../alipay/sofa/jraft/util/timer/TimerTask.java    |   32 +
 pom.xml                                            |    1 +
 266 files changed, 38270 insertions(+), 659 deletions(-)
 copy modules/{configuration-annotation-processor => raft}/pom.xml (51%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/CliService.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/Closure.java} (69%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/FSMCaller.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/Iterator.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/JRaftServiceFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/JRaftUtils.java
 copy modules/{configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java => raft/src/main/java/com/alipay/sofa/jraft/Lifecycle.java} (64%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/Node.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/NodeDescribeSignalHandler.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/NodeManager.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/NodeMetricsSignalHandler.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/RaftGroupService.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/RaftServiceFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/ReadOnlyService.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/ReplicatorGroup.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/RouteTable.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/StateMachine.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/Status.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/ThreadPoolMetricsSignalHandler.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/closure/CatchUpClosure.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ClosureQueue.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ClosureQueueImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/closure/JoinableClosure.java
 copy modules/{configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java => raft/src/main/java/com/alipay/sofa/jraft/closure/LoadSnapshotClosure.java} (61%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ReadIndexClosure.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/closure/SaveSnapshotClosure.java} (56%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/closure/SynchronizedClosure.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/closure/TaskClosure.java} (61%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/conf/Configuration.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationEntry.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationManager.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/CliServiceImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/DefaultJRaftServiceFactory.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/core/ElectionPriority.java} (59%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/FSMCallerImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/IteratorImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/IteratorWrapper.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeMetrics.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/Replicator.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/ReplicatorGroupImpl.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java => raft/src/main/java/com/alipay/sofa/jraft/core/ReplicatorType.java} (68%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/Scheduler.java
 copy modules/{configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java => raft/src/main/java/com/alipay/sofa/jraft/core/State.java} (54%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/StateMachineAdapter.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/core/TimerManager.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/Ballot.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/entity/Checksum.java} (59%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/EnumOutter.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LeaderChangeContext.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalFileMetaOutter.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalStorageOutter.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LogEntry.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LogId.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/NodeId.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/PeerId.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/RaftOutter.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/ReadIndexState.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/ReadIndexStatus.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/Task.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/UserLog.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/AutoDetectDecoder.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/DefaultLogEntryCodecFactory.java
 copy modules/{configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java => raft/src/main/java/com/alipay/sofa/jraft/entity/codec/LogEntryCodecFactory.java} (61%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/entity/codec/LogEntryDecoder.java} (64%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java => raft/src/main/java/com/alipay/sofa/jraft/entity/codec/LogEntryEncoder.java} (65%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/v1/LogEntryV1CodecFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/v1/V1Decoder.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/v1/V1Encoder.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/error/InvokeTimeoutException.java} (54%)
 copy modules/{cli-demo/cli/src/main/java/org/apache/ignite/cli/IgniteCLIException.java => raft/src/main/java/com/alipay/sofa/jraft/error/JRaftException.java} (60%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/error/LogEntryCorruptedException.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/error/LogIndexOutOfBoundsException.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/error/LogNotFoundException.java} (57%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/error/MessageClassNotFoundException.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/error/RaftError.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/error/RaftException.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/error/RemotingException.java} (54%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/error/RetryAgainException.java} (50%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/option/BallotBoxOptions.java} (52%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/BootstrapOptions.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/option/CliOptions.java} (57%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/CopyOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/FSMCallerOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/LogManagerOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/LogStorageOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/NodeOptions.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java => raft/src/main/java/com/alipay/sofa/jraft/option/RaftMetaStorageOptions.java} (67%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/RaftOptions.java
 copy modules/{ignite-runner/src/main/java/org/apache/ignite/configuration/extended/LocalConfigurationSchema.java => raft/src/main/java/com/alipay/sofa/jraft/option/ReadOnlyOption.java} (52%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/ReadOnlyServiceOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/ReplicatorGroupOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/ReplicatorOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/RpcOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/SnapshotCopierOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/option/SnapshotExecutorOptions.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/CliClientService.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/CliRequests.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/ClientService.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/Connection.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/HasErrorResponse.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/rpc/InvokeCallback.java} (71%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/InvokeContext.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/Message.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/MessageBuilderFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RaftClientService.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RaftRpcFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RaftRpcServerFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RaftServerService.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcClient.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcContext.java} (59%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcRequestClosure.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcRequests.java
 copy modules/{configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java => raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcResponseClosure.java} (61%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcResponseClosureAdapter.java} (59%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcResponseFactory.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcServer.java} (53%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcUtils.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractClientService.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/ConnectionClosedEventListener.java} (73%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/FutureImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRaftRpcFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRpcClient.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java => raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRpcServer.java} (54%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/PingRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/AddLearnersRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/AddPeerRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/BaseCliRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/ChangePeersRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/CliClientServiceImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/GetLeaderRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/GetPeersRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/RemoveLearnersRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/RemovePeerRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/ResetLearnersRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/ResetPeerRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/SnapshotRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/cli/TransferLeaderRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/AppendEntriesRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/DefaultRaftClientService.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/GetFileRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/InstallSnapshotRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/NodeRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/ReadIndexRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/RequestVoteRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/TimeoutNowRequestProcessor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/message/DefaultMessageBuilderFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/FileService.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/LogManager.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/LogStorage.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/RaftMetaStorage.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/SnapshotExecutor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/SnapshotStorage.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/storage/SnapshotThrottle.java} (64%)
 copy modules/{cli-demo/demo-module-all/demo-module/src/main/java/org/apache/ignite/snapshot/IgniteSnapshot.java => raft/src/main/java/com/alipay/sofa/jraft/storage/Storage.java} (76%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/LocalLogStorage.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/LocalRaftMetaStorage.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/LogManagerImpl.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/io/FileReader.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/io/LocalDirReader.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/Snapshot.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/SnapshotCopier.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/SnapshotExecutorImpl.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/SnapshotReader.java} (55%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/SnapshotWriter.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/ThroughputSnapshotThrottle.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshot.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotCopier.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotMetaTable.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotReader.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotStorage.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotWriter.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/SnapshotFileReader.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/CopySession.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/RemoteFileCopier.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/remote/Session.java} (55%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/AdaptiveBufAllocator.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/ArrayDeque.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/AsciiStringUtil.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Bits.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/ByteBufferCollector.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/ByteString.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Bytes.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/BytesUtil.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/CRC64.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/util/Copiable.java} (71%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/CountDownEvent.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/CrcUtil.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Describer.java
 copy modules/{configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java => raft/src/main/java/com/alipay/sofa/jraft/util/DirectExecutor.java} (64%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/DisruptorBuilder.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/DisruptorMetricSet.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Endpoint.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/ExecutorServiceHelper.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/FileOutputSignalHandler.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/HeapByteBufUtil.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Ints.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/JDKMarshaller.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/JRaftServiceLoader.java
 copy modules/{cli-demo/demo-module-all/demo-module/src/main/java/org/apache/ignite/snapshot/IgniteSnapshot.java => raft/src/main/java/com/alipay/sofa/jraft/util/JRaftSignalHandler.java} (79%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/LogExceptionHandler.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/LogScheduledThreadPoolExecutor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/LogThreadPoolExecutor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Marshaller.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/MetricReporter.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/MetricScheduledThreadPoolExecutor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/MetricThreadPoolExecutor.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Mpsc.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/NamedThreadFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/NonReentrantLock.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java => raft/src/main/java/com/alipay/sofa/jraft/util/OnlyForTest.java} (57%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Platform.java
 copy modules/{cli-demo/demo-module-all/demo-module/src/main/java/org/apache/ignite/snapshot/IgniteSnapshot.java => raft/src/main/java/com/alipay/sofa/jraft/util/Recyclable.java} (77%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/RecyclableByteBufferList.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java => raft/src/main/java/com/alipay/sofa/jraft/util/RecycleUtil.java} (65%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Recyclers.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/RepeatedTimer.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Requires.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/util/RpcFactoryHelper.java} (56%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java => raft/src/main/java/com/alipay/sofa/jraft/util/SPI.java} (61%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/SegmentList.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/StringUtils.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/SystemPropertyUtil.java
 copy modules/{ignite-runner/src/main/java/org/apache/ignite/configuration/extended/LocalConfigurationSchema.java => raft/src/main/java/com/alipay/sofa/jraft/util/ThreadHelper.java} (54%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/ThreadId.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/util/ThreadPoolMetricRegistry.java} (55%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/ThreadPoolMetricSet.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/ThreadPoolUtil.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/Utils.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/AdjustableSemaphore.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/ConcurrentHashSet.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/DefaultExecutorChooserFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/DefaultFixedThreadsExecutorGroup.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/DefaultFixedThreadsExecutorGroupFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/DefaultSingleThreadExecutor.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/ExecutorChooserFactory.java} (55%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/FixedThreadsExecutorGroup.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/FixedThreadsExecutorGroupFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/LongHeldDetectingReadWriteLock.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/MpscSingleThreadExecutor.java
 copy modules/{ignite-runner/src/main/java/org/apache/ignite/configuration/extended/LocalConfigurationSchema.java => raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/RejectedExecutionHandler.java} (58%)
 copy modules/{configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java => raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/RejectedExecutionHandlers.java} (52%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java => raft/src/main/java/com/alipay/sofa/jraft/util/concurrent/SingleThreadExecutor.java} (50%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/util/internal/IntegerFieldUpdater.java} (75%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/util/internal/LongFieldUpdater.java} (75%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java => raft/src/main/java/com/alipay/sofa/jraft/util/internal/ReferenceFieldUpdater.java} (75%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/ReflectionIntegerFieldUpdater.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/ReflectionLongFieldUpdater.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/ReflectionReferenceFieldUpdater.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/ThrowUtil.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeUtil.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/Updaters.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/timer/DefaultRaftTimerFactory.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/timer/DefaultTimer.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/timer/HashedWheelTimer.java
 copy modules/{configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/BaselineConfigurationSchema.java => raft/src/main/java/com/alipay/sofa/jraft/util/timer/RaftTimerFactory.java} (53%)
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/timer/Timeout.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/timer/Timer.java
 create mode 100644 modules/raft/src/main/java/com/alipay/sofa/jraft/util/timer/TimerTask.java


[ignite-3] 01/02: IGNITE-13885 wip.

Posted by as...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ascherbakov pushed a commit to branch ignite-13885
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 899f44af160ae8e73dafc482f97b027a086a0e1c
Author: Alexey Scherbakov <al...@gmail.com>
AuthorDate: Mon Dec 28 13:06:10 2020 +0300

    IGNITE-13885 wip.
---
 modules/raft/pom.xml                               |  118 +
 .../java/com/alipay/sofa/jraft/CliService.java     |  192 ++
 .../main/java/com/alipay/sofa/jraft/Closure.java   |   34 +
 .../main/java/com/alipay/sofa/jraft/FSMCaller.java |  123 +
 .../main/java/com/alipay/sofa/jraft/Iterator.java  |   75 +
 .../com/alipay/sofa/jraft/JRaftServiceFactory.java |   62 +
 .../java/com/alipay/sofa/jraft/JRaftUtils.java     |  152 +
 .../main/java/com/alipay/sofa/jraft/Lifecycle.java |   39 +
 .../src/main/java/com/alipay/sofa/jraft/Node.java  |  327 ++
 .../sofa/jraft/NodeDescribeSignalHandler.java      |   68 +
 .../java/com/alipay/sofa/jraft/NodeManager.java    |  148 +
 .../sofa/jraft/NodeMetricsSignalHandler.java       |   77 +
 .../com/alipay/sofa/jraft/RaftGroupService.java    |  257 ++
 .../com/alipay/sofa/jraft/RaftServiceFactory.java  |   70 +
 .../com/alipay/sofa/jraft/ReadOnlyService.java     |   53 +
 .../com/alipay/sofa/jraft/ReplicatorGroup.java     |  229 ++
 .../java/com/alipay/sofa/jraft/RouteTable.java     |  384 +++
 .../java/com/alipay/sofa/jraft/StateMachine.java   |  144 +
 .../main/java/com/alipay/sofa/jraft/Status.java    |  233 ++
 .../sofa/jraft/ThreadPoolMetricsSignalHandler.java |   60 +
 .../alipay/sofa/jraft/closure/CatchUpClosure.java  |   72 +
 .../alipay/sofa/jraft/closure/ClosureQueue.java    |   80 +
 .../sofa/jraft/closure/ClosureQueueImpl.java       |  145 +
 .../alipay/sofa/jraft/closure/JoinableClosure.java |   58 +
 .../sofa/jraft/closure/LoadSnapshotClosure.java    |   37 +
 .../sofa/jraft/closure/ReadIndexClosure.java       |  166 +
 .../sofa/jraft/closure/SaveSnapshotClosure.java    |   39 +
 .../sofa/jraft/closure/SynchronizedClosure.java    |   83 +
 .../com/alipay/sofa/jraft/closure/TaskClosure.java |   35 +
 .../com/alipay/sofa/jraft/conf/Configuration.java  |  324 ++
 .../alipay/sofa/jraft/conf/ConfigurationEntry.java |  129 +
 .../sofa/jraft/conf/ConfigurationManager.java      |  110 +
 .../java/com/alipay/sofa/jraft/core/BallotBox.java |  283 ++
 .../com/alipay/sofa/jraft/core/CliServiceImpl.java |  687 ++++
 .../jraft/core/DefaultJRaftServiceFactory.java     |   69 +
 .../alipay/sofa/jraft/core/ElectionPriority.java   |   40 +
 .../com/alipay/sofa/jraft/core/FSMCallerImpl.java  |  729 ++++
 .../com/alipay/sofa/jraft/core/IteratorImpl.java   |  161 +
 .../alipay/sofa/jraft/core/IteratorWrapper.java    |   75 +
 .../java/com/alipay/sofa/jraft/core/NodeImpl.java  | 3464 ++++++++++++++++++++
 .../com/alipay/sofa/jraft/core/NodeMetrics.java    |  107 +
 .../sofa/jraft/core/ReadOnlyServiceImpl.java       |  423 +++
 .../com/alipay/sofa/jraft/core/Replicator.java     | 1788 ++++++++++
 .../sofa/jraft/core/ReplicatorGroupImpl.java       |  312 ++
 .../com/alipay/sofa/jraft/core/ReplicatorType.java |   34 +
 .../java/com/alipay/sofa/jraft/core/Scheduler.java |   88 +
 .../java/com/alipay/sofa/jraft/core/State.java     |   39 +
 .../sofa/jraft/core/StateMachineAdapter.java       |  109 +
 .../com/alipay/sofa/jraft/core/TimerManager.java   |   71 +
 .../java/com/alipay/sofa/jraft/entity/Ballot.java  |  141 +
 .../com/alipay/sofa/jraft/entity/Checksum.java     |   43 +
 .../com/alipay/sofa/jraft/entity/EnumOutter.java   |  149 +
 .../sofa/jraft/entity/LeaderChangeContext.java     |  114 +
 .../sofa/jraft/entity/LocalFileMetaOutter.java     |   81 +
 .../sofa/jraft/entity/LocalStorageOutter.java      |   62 +
 .../com/alipay/sofa/jraft/entity/LogEntry.java     |  291 ++
 .../java/com/alipay/sofa/jraft/entity/LogId.java   |  125 +
 .../java/com/alipay/sofa/jraft/entity/NodeId.java  |   95 +
 .../java/com/alipay/sofa/jraft/entity/PeerId.java  |  278 ++
 .../com/alipay/sofa/jraft/entity/RaftOutter.java   |  116 +
 .../alipay/sofa/jraft/entity/ReadIndexState.java   |   65 +
 .../alipay/sofa/jraft/entity/ReadIndexStatus.java  |   57 +
 .../java/com/alipay/sofa/jraft/entity/Task.java    |  174 +
 .../java/com/alipay/sofa/jraft/entity/UserLog.java |   66 +
 .../sofa/jraft/entity/codec/AutoDetectDecoder.java |   50 +
 .../entity/codec/DefaultLogEntryCodecFactory.java  |   63 +
 .../jraft/entity/codec/LogEntryCodecFactory.java   |   37 +
 .../sofa/jraft/entity/codec/LogEntryDecoder.java   |   35 +
 .../sofa/jraft/entity/codec/LogEntryEncoder.java   |   35 +
 .../entity/codec/v1/LogEntryV1CodecFactory.java    |   57 +
 .../sofa/jraft/entity/codec/v1/V1Decoder.java      |  115 +
 .../sofa/jraft/entity/codec/v1/V1Encoder.java      |  131 +
 .../sofa/jraft/error/InvokeTimeoutException.java   |   44 +
 .../alipay/sofa/jraft/error/JRaftException.java    |   45 +
 .../jraft/error/LogEntryCorruptedException.java    |   53 +
 .../jraft/error/LogIndexOutOfBoundsException.java  |   56 +
 .../sofa/jraft/error/LogNotFoundException.java     |   44 +
 .../jraft/error/MessageClassNotFoundException.java |   49 +
 .../com/alipay/sofa/jraft/error/RaftError.java     |  284 ++
 .../com/alipay/sofa/jraft/error/RaftException.java |   82 +
 .../alipay/sofa/jraft/error/RemotingException.java |   46 +
 .../sofa/jraft/error/RetryAgainException.java      |   52 +
 .../alipay/sofa/jraft/option/BallotBoxOptions.java |   49 +
 .../alipay/sofa/jraft/option/BootstrapOptions.java |  129 +
 .../com/alipay/sofa/jraft/option/CliOptions.java   |   46 +
 .../com/alipay/sofa/jraft/option/CopyOptions.java  |   55 +
 .../alipay/sofa/jraft/option/FSMCallerOptions.java |  100 +
 .../sofa/jraft/option/LogManagerOptions.java       |   99 +
 .../sofa/jraft/option/LogStorageOptions.java       |   49 +
 .../com/alipay/sofa/jraft/option/NodeOptions.java  |  443 +++
 .../sofa/jraft/option/RaftMetaStorageOptions.java  |   36 +
 .../com/alipay/sofa/jraft/option/RaftOptions.java  |  270 ++
 .../alipay/sofa/jraft/option/ReadOnlyOption.java   |   35 +
 .../sofa/jraft/option/ReadOnlyServiceOptions.java  |   56 +
 .../sofa/jraft/option/ReplicatorGroupOptions.java  |  124 +
 .../sofa/jraft/option/ReplicatorOptions.java       |  218 ++
 .../com/alipay/sofa/jraft/option/RpcOptions.java   |  113 +
 .../sofa/jraft/option/SnapshotCopierOptions.java   |   80 +
 .../sofa/jraft/option/SnapshotExecutorOptions.java |  107 +
 .../alipay/sofa/jraft/rpc/CliClientService.java    |  156 +
 .../com/alipay/sofa/jraft/rpc/CliRequests.java     |  531 +++
 .../com/alipay/sofa/jraft/rpc/ClientService.java   |   78 +
 .../java/com/alipay/sofa/jraft/rpc/Connection.java |   56 +
 .../alipay/sofa/jraft/rpc/HasErrorResponse.java    |    5 +
 .../com/alipay/sofa/jraft/rpc/InvokeCallback.java  |   31 +
 .../com/alipay/sofa/jraft/rpc/InvokeContext.java   |   56 +
 .../java/com/alipay/sofa/jraft/rpc/Message.java    |    4 +
 .../alipay/sofa/jraft/rpc/RaftClientService.java   |  112 +
 .../com/alipay/sofa/jraft/rpc/RaftRpcFactory.java  |  108 +
 .../sofa/jraft/rpc/RaftRpcServerFactory.java       |  146 +
 .../alipay/sofa/jraft/rpc/RaftServerService.java   |   88 +
 .../java/com/alipay/sofa/jraft/rpc/RpcClient.java  |  110 +
 .../java/com/alipay/sofa/jraft/rpc/RpcContext.java |   44 +
 .../com/alipay/sofa/jraft/rpc/RpcProcessor.java    |   75 +
 .../alipay/sofa/jraft/rpc/RpcRequestClosure.java   |   79 +
 .../alipay/sofa/jraft/rpc/RpcRequestProcessor.java |   73 +
 .../com/alipay/sofa/jraft/rpc/RpcRequests.java     |  461 +++
 .../alipay/sofa/jraft/rpc/RpcResponseClosure.java  |   38 +
 .../sofa/jraft/rpc/RpcResponseClosureAdapter.java  |   41 +
 .../alipay/sofa/jraft/rpc/RpcResponseFactory.java  |   82 +
 .../java/com/alipay/sofa/jraft/rpc/RpcServer.java  |   47 +
 .../java/com/alipay/sofa/jraft/rpc/RpcUtils.java   |  134 +
 .../sofa/jraft/rpc/impl/AbstractClientService.java |  297 ++
 .../sofa/jraft/rpc/impl/BoltRaftRpcFactory.java    |   98 +
 .../alipay/sofa/jraft/rpc/impl/BoltRpcClient.java  |  193 ++
 .../alipay/sofa/jraft/rpc/impl/BoltRpcServer.java  |  179 +
 .../rpc/impl/ConnectionClosedEventListener.java    |   28 +
 .../com/alipay/sofa/jraft/rpc/impl/FutureImpl.java |  242 ++
 .../sofa/jraft/rpc/impl/PingRequestProcessor.java  |   45 +
 .../rpc/impl/cli/AddLearnersRequestProcessor.java  |  101 +
 .../rpc/impl/cli/AddPeerRequestProcessor.java      |   93 +
 .../rpc/impl/cli/BaseCliRequestProcessor.java      |  148 +
 .../rpc/impl/cli/ChangePeersRequestProcessor.java  |   91 +
 .../jraft/rpc/impl/cli/CliClientServiceImpl.java   |  129 +
 .../rpc/impl/cli/GetLeaderRequestProcessor.java    |  107 +
 .../rpc/impl/cli/GetPeersRequestProcessor.java     |   75 +
 .../impl/cli/RemoveLearnersRequestProcessor.java   |   96 +
 .../rpc/impl/cli/RemovePeerRequestProcessor.java   |   87 +
 .../impl/cli/ResetLearnersRequestProcessor.java    |   97 +
 .../rpc/impl/cli/ResetPeerRequestProcessor.java    |   80 +
 .../rpc/impl/cli/SnapshotRequestProcessor.java     |   61 +
 .../impl/cli/TransferLeaderRequestProcessor.java   |   74 +
 .../impl/core/AppendEntriesRequestProcessor.java   |  517 +++
 .../ClientServiceConnectionEventProcessor.java     |   56 +
 .../rpc/impl/core/DefaultRaftClientService.java    |  158 +
 .../rpc/impl/core/GetFileRequestProcessor.java     |   50 +
 .../impl/core/InstallSnapshotRequestProcessor.java |   60 +
 .../jraft/rpc/impl/core/NodeRequestProcessor.java  |   73 +
 .../rpc/impl/core/ReadIndexRequestProcessor.java   |   73 +
 .../rpc/impl/core/RequestVoteRequestProcessor.java |   64 +
 .../rpc/impl/core/TimeoutNowRequestProcessor.java  |   59 +
 .../com/alipay/sofa/jraft/storage/FileService.java |  153 +
 .../com/alipay/sofa/jraft/storage/LogManager.java  |  241 ++
 .../com/alipay/sofa/jraft/storage/LogStorage.java  |   83 +
 .../alipay/sofa/jraft/storage/RaftMetaStorage.java |   56 +
 .../sofa/jraft/storage/SnapshotExecutor.java       |   98 +
 .../alipay/sofa/jraft/storage/SnapshotStorage.java |   74 +
 .../sofa/jraft/storage/SnapshotThrottle.java       |   34 +
 .../com/alipay/sofa/jraft/storage/Storage.java     |   27 +
 .../jraft/storage/impl/LocalRaftMetaStorage.java   |  195 ++
 .../sofa/jraft/storage/impl/LogManagerImpl.java    | 1190 +++++++
 .../sofa/jraft/storage/impl/RocksDBLogStorage.java |  743 +++++
 .../alipay/sofa/jraft/storage/io/FileReader.java   |   57 +
 .../sofa/jraft/storage/io/LocalDirReader.java      |   97 +
 .../alipay/sofa/jraft/storage/io/ProtoBufFile.java |  126 +
 .../alipay/sofa/jraft/storage/log/AbortFile.java   |   74 +
 .../sofa/jraft/storage/log/CheckpointFile.java     |  119 +
 .../com/alipay/sofa/jraft/storage/log/LibC.java    |   60 +
 .../storage/log/RocksDBSegmentLogStorage.java      | 1216 +++++++
 .../alipay/sofa/jraft/storage/log/SegmentFile.java |  905 +++++
 .../sofa/jraft/storage/snapshot/Snapshot.java      |   58 +
 .../jraft/storage/snapshot/SnapshotCopier.java     |   53 +
 .../storage/snapshot/SnapshotExecutorImpl.java     |  743 +++++
 .../jraft/storage/snapshot/SnapshotReader.java     |   43 +
 .../jraft/storage/snapshot/SnapshotWriter.java     |   77 +
 .../snapshot/ThroughputSnapshotThrottle.java       |   82 +
 .../storage/snapshot/local/LocalSnapshot.java      |   62 +
 .../snapshot/local/LocalSnapshotCopier.java        |  439 +++
 .../snapshot/local/LocalSnapshotMetaTable.java     |  186 ++
 .../snapshot/local/LocalSnapshotReader.java        |  167 +
 .../snapshot/local/LocalSnapshotStorage.java       |  352 ++
 .../snapshot/local/LocalSnapshotWriter.java        |  140 +
 .../storage/snapshot/local/SnapshotFileReader.java |   92 +
 .../jraft/storage/snapshot/remote/CopySession.java |  302 ++
 .../storage/snapshot/remote/RemoteFileCopier.java  |  192 ++
 .../jraft/storage/snapshot/remote/Session.java     |   49 +
 .../sofa/jraft/util/AdaptiveBufAllocator.java      |  203 ++
 .../com/alipay/sofa/jraft/util/ArrayDeque.java     |  110 +
 .../alipay/sofa/jraft/util/AsciiStringUtil.java    |   59 +
 .../main/java/com/alipay/sofa/jraft/util/Bits.java |   50 +
 .../sofa/jraft/util/ByteBufferCollector.java       |  145 +
 .../com/alipay/sofa/jraft/util/ByteString.java     |   25 +
 .../java/com/alipay/sofa/jraft/util/Bytes.java     |  138 +
 .../java/com/alipay/sofa/jraft/util/BytesUtil.java |  183 ++
 .../java/com/alipay/sofa/jraft/util/CRC64.java     |  128 +
 .../java/com/alipay/sofa/jraft/util/Copiable.java  |   33 +
 .../com/alipay/sofa/jraft/util/CountDownEvent.java |   75 +
 .../java/com/alipay/sofa/jraft/util/CrcUtil.java   |   84 +
 .../alipay/sofa/jraft/util/DebugStatistics.java    |   36 +
 .../java/com/alipay/sofa/jraft/util/Describer.java |   70 +
 .../com/alipay/sofa/jraft/util/DirectExecutor.java |   38 +
 .../alipay/sofa/jraft/util/DisruptorBuilder.java   |   98 +
 .../alipay/sofa/jraft/util/DisruptorMetricSet.java |   50 +
 .../java/com/alipay/sofa/jraft/util/Endpoint.java  |   97 +
 .../sofa/jraft/util/ExecutorServiceHelper.java     |   73 +
 .../sofa/jraft/util/FileOutputSignalHandler.java   |   52 +
 .../alipay/sofa/jraft/util/HeapByteBufUtil.java    |  133 +
 .../main/java/com/alipay/sofa/jraft/util/Ints.java |   84 +
 .../alipay/sofa/jraft/util/JRaftServiceLoader.java |  357 ++
 .../alipay/sofa/jraft/util/JRaftSignalHandler.java |   26 +
 .../sofa/jraft/util/LogExceptionHandler.java       |   70 +
 .../jraft/util/LogScheduledThreadPoolExecutor.java |   94 +
 .../sofa/jraft/util/LogThreadPoolExecutor.java     |   99 +
 .../com/alipay/sofa/jraft/util/MetricReporter.java |  436 +++
 .../util/MetricScheduledThreadPoolExecutor.java    |   75 +
 .../sofa/jraft/util/MetricThreadPoolExecutor.java  |   80 +
 .../main/java/com/alipay/sofa/jraft/util/Mpsc.java |   46 +
 .../alipay/sofa/jraft/util/NamedThreadFactory.java |   70 +
 .../alipay/sofa/jraft/util/NonReentrantLock.java   |   92 +
 .../com/alipay/sofa/jraft/util/OnlyForTest.java    |   37 +
 .../java/com/alipay/sofa/jraft/util/Platform.java  |   69 +
 .../com/alipay/sofa/jraft/util/Recyclable.java     |   29 +
 .../sofa/jraft/util/RecyclableByteBufferList.java  |  136 +
 .../com/alipay/sofa/jraft/util/RecycleUtil.java    |   35 +
 .../java/com/alipay/sofa/jraft/util/Recyclers.java |  414 +++
 .../com/alipay/sofa/jraft/util/RepeatedTimer.java  |  295 ++
 .../java/com/alipay/sofa/jraft/util/Requires.java  |  107 +
 .../alipay/sofa/jraft/util/RpcFactoryHelper.java   |   37 +
 .../main/java/com/alipay/sofa/jraft/util/SPI.java  |   37 +
 .../com/alipay/sofa/jraft/util/SegmentList.java    |  392 +++
 .../com/alipay/sofa/jraft/util/SignalHelper.java   |  114 +
 .../sofa/jraft/util/StorageOptionsFactory.java     |  350 ++
 .../com/alipay/sofa/jraft/util/StringUtils.java    |  105 +
 .../alipay/sofa/jraft/util/SystemPropertyUtil.java |  188 ++
 .../com/alipay/sofa/jraft/util/ThreadHelper.java   |   52 +
 .../java/com/alipay/sofa/jraft/util/ThreadId.java  |  173 +
 .../sofa/jraft/util/ThreadPoolMetricRegistry.java  |   40 +
 .../sofa/jraft/util/ThreadPoolMetricSet.java       |   52 +
 .../com/alipay/sofa/jraft/util/ThreadPoolUtil.java |  283 ++
 .../java/com/alipay/sofa/jraft/util/Utils.java     |  451 +++
 .../jraft/util/concurrent/AdjustableSemaphore.java |  129 +
 .../jraft/util/concurrent/ConcurrentHashSet.java   |   95 +
 .../concurrent/DefaultExecutorChooserFactory.java  |   81 +
 .../DefaultFixedThreadsExecutorGroup.java          |  115 +
 .../DefaultFixedThreadsExecutorGroupFactory.java   |   81 +
 .../concurrent/DefaultSingleThreadExecutor.java    |  117 +
 .../util/concurrent/ExecutorChooserFactory.java    |   43 +
 .../util/concurrent/FixedThreadsExecutorGroup.java |   59 +
 .../FixedThreadsExecutorGroupFactory.java          |   43 +
 .../concurrent/LongHeldDetectingReadWriteLock.java |  153 +
 .../util/concurrent/MpscSingleThreadExecutor.java  |  401 +++
 .../util/concurrent/RejectedExecutionHandler.java  |   33 +
 .../util/concurrent/RejectedExecutionHandlers.java |   45 +
 .../util/concurrent/SingleThreadExecutor.java      |   44 +
 .../jraft/util/internal/IntegerFieldUpdater.java   |   27 +
 .../sofa/jraft/util/internal/LongFieldUpdater.java |   27 +
 .../jraft/util/internal/ReferenceFieldUpdater.java |   27 +
 .../internal/ReflectionIntegerFieldUpdater.java    |   51 +
 .../util/internal/ReflectionLongFieldUpdater.java  |   51 +
 .../internal/ReflectionReferenceFieldUpdater.java  |   52 +
 .../alipay/sofa/jraft/util/internal/ThrowUtil.java |   71 +
 .../util/internal/UnsafeIntegerFieldUpdater.java   |   49 +
 .../util/internal/UnsafeLongFieldUpdater.java      |   49 +
 .../util/internal/UnsafeReferenceFieldUpdater.java |   50 +
 .../sofa/jraft/util/internal/UnsafeUtf8Util.java   |  481 +++
 .../sofa/jraft/util/internal/UnsafeUtil.java       |  629 ++++
 .../alipay/sofa/jraft/util/internal/Updaters.java  |   81 +
 .../jraft/util/timer/DefaultRaftTimerFactory.java  |  242 ++
 .../alipay/sofa/jraft/util/timer/DefaultTimer.java |  123 +
 .../sofa/jraft/util/timer/HashedWheelTimer.java    |  758 +++++
 .../sofa/jraft/util/timer/RaftTimerFactory.java    |   39 +
 .../com/alipay/sofa/jraft/util/timer/Timeout.java  |   55 +
 .../com/alipay/sofa/jraft/util/timer/Timer.java    |   51 +
 .../alipay/sofa/jraft/util/timer/TimerTask.java    |   32 +
 pom.xml                                            |    1 +
 275 files changed, 43507 insertions(+)

diff --git a/modules/raft/pom.xml b/modules/raft/pom.xml
new file mode 100644
index 0000000..a01aefb
--- /dev/null
+++ b/modules/raft/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!--
+    POM file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.ignite</groupId>
+    <artifactId>raft</artifactId>
+    <version>3.0-SNAPSHOT</version>
+    <url>http://ignite.apache.org</url>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.4.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.lmax</groupId>
+            <artifactId>disruptor</artifactId>
+            <version>3.3.7</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- logging -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.21</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <version>2.13.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>2.13.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+            <version>2.13.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-jcl</artifactId>
+            <version>2.13.2</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- commons -->
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+            <version>3.0.2</version>
+        </dependency>
+        <dependency>
+            <groupId>io.dropwizard.metrics</groupId>
+            <artifactId>metrics-core</artifactId>
+            <version>4.0.2</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/CliService.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/CliService.java
new file mode 100644
index 0000000..ca8461d
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/CliService.java
@@ -0,0 +1,192 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.option.CliOptions;
+
+/**
+ * Client command-line service
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-09 4:05:35 PM
+ */
+public interface CliService extends Lifecycle<CliOptions> {
+
+    /**
+     * Add a new peer into the replicating group which consists of |conf|.
+     * return OK status when success.
+     *
+     * @param groupId the raft group id
+     * @param conf    current configuration
+     * @param peer    peer to add
+     * @return operation status
+     */
+    Status addPeer(final String groupId, final Configuration conf, final PeerId peer);
+
+    /**
+     * Remove a peer from the replicating group which consists of |conf|.
+     * return OK status when success.
+     *
+     * @param groupId the raft group id
+     * @param conf    current configuration
+     * @param peer    peer to remove
+     * @return operation status
+     */
+    Status removePeer(final String groupId, final Configuration conf, final PeerId peer);
+
+    /**
+     * Gracefully change the peers of the replication group.
+     *
+     * @param groupId  the raft group id
+     * @param conf     current configuration
+     * @param newPeers new peers to change
+     * @return operation status
+     */
+    Status changePeers(final String groupId, final Configuration conf, final Configuration newPeers);
+
+    /**
+     * Reset the peer set of the target peer.
+     *
+     * @param groupId  the raft group id
+     * @param peer     target peer
+     * @param newPeers new peers to reset
+     * @return operation status
+     */
+    Status resetPeer(final String groupId, final PeerId peer, final Configuration newPeers);
+
+    /**
+     * Add some new learners into the replicating group which consists of |conf|.
+     * return OK status when success.
+     *
+     * @param groupId  the raft group id
+     * @param conf     current configuration
+     * @param learners learner peers to add
+     * @return operation status
+     * @since 1.3.0
+     *
+     */
+    Status addLearners(final String groupId, final Configuration conf, final List<PeerId> learners);
+
+    /**
+     * Remove some learners from the replicating group which consists of |conf|.
+     * return OK status when success.
+     *
+     * @param groupId  the raft group id
+     * @param conf     current configuration
+     * @param learners learner peers to remove
+     * @return operation status
+     * @since 1.3.0
+     *
+     */
+    Status removeLearners(final String groupId, final Configuration conf, final List<PeerId> learners);
+
+    /**
+     * Update learners set in the replicating group which consists of |conf|.
+     * return OK status when success.
+     *
+     * @param groupId  the raft group id
+     * @param conf     current configuration
+     * @param learners learner peers to set
+     * @return operation status
+     * @since 1.3.0
+     *
+     */
+    Status resetLearners(final String groupId, final Configuration conf, final List<PeerId> learners);
+
+    /**
+     * Transfer the leader of the replication group to the target peer
+     *
+     * @param groupId the raft group id
+     * @param conf    current configuration
+     * @param peer    target peer of new leader
+     * @return operation status
+     */
+    Status transferLeader(final String groupId, final Configuration conf, final PeerId peer);
+
+    /**
+     * Ask the peer to dump a snapshot immediately.
+     *
+     * @param groupId the raft group id
+     * @param peer    target peer
+     * @return operation status
+     */
+    Status snapshot(final String groupId, final PeerId peer);
+
+    /**
+     * Get the leader of the replication group.
+     * @param groupId  the raft group id
+     * @param conf     configuration
+     * @param leaderId id of leader
+     * @return operation status
+     */
+    Status getLeader(final String groupId, final Configuration conf, final PeerId leaderId);
+
+    /**
+     * Ask all peers of the replication group.
+     *
+     * @param groupId the raft group id
+     * @param conf    target peers configuration
+     * @return all peers of the replication group
+     */
+    List<PeerId> getPeers(final String groupId, final Configuration conf);
+
+    /**
+     * Ask all alive peers of the replication group.
+     *
+     * @param groupId the raft group id
+     * @param conf    target peers configuration
+     * @return all alive peers of the replication group
+     */
+    List<PeerId> getAlivePeers(final String groupId, final Configuration conf);
+
+    /**
+     * Ask all learners of the replication group.
+     *
+     * @param groupId the raft group id
+     * @param conf    target peers configuration
+     * @return all learners of the replication group
+     * @since 1.3.0
+     */
+    List<PeerId> getLearners(final String groupId, final Configuration conf);
+
+    /**
+     * Ask all alive learners of the replication group.
+     *
+     * @param groupId the raft group id
+     * @param conf    target peers configuration
+     * @return all alive learners of the replication group
+     */
+    List<PeerId> getAliveLearners(final String groupId, final Configuration conf);
+
+    /**
+     * Balance the number of leaders.
+     *
+     * @param balanceGroupIds   all raft group ids to balance
+     * @param conf              configuration of all nodes
+     * @param balancedLeaderIds the result of all balanced leader ids
+     * @return operation status
+     */
+    Status rebalance(final Set<String> balanceGroupIds, final Configuration conf,
+                     final Map<String, PeerId> balancedLeaderIds);
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/Closure.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/Closure.java
new file mode 100644
index 0000000..7d51264
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/Closure.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alipay.sofa.jraft;
+
+/**
+ * Callback closure.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-03 11:07:05 AM
+ */
+public interface Closure {
+
+    /**
+     * Called when task is done.
+     *
+     * @param status the task status.
+     */
+    void run(final Status status);
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/FSMCaller.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/FSMCaller.java
new file mode 100644
index 0000000..71b003e
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/FSMCaller.java
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+import com.alipay.sofa.jraft.closure.LoadSnapshotClosure;
+import com.alipay.sofa.jraft.closure.SaveSnapshotClosure;
+import com.alipay.sofa.jraft.entity.LeaderChangeContext;
+import com.alipay.sofa.jraft.error.RaftException;
+import com.alipay.sofa.jraft.option.FSMCallerOptions;
+import com.alipay.sofa.jraft.util.Describer;
+
+/**
+ * Finite state machine caller.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-03 11:07:52 AM
+ */
+public interface FSMCaller extends Lifecycle<FSMCallerOptions>, Describer {
+
+    /**
+     * Listen on lastAppliedLogIndex update events.
+     *
+     * @author dennis
+     */
+    interface LastAppliedLogIndexListener {
+
+        /**
+         * Called when lastAppliedLogIndex updated.
+         *
+         * @param lastAppliedLogIndex the log index of last applied
+         */
+        void onApplied(final long lastAppliedLogIndex);
+    }
+
+    /**
+     * Adds a LastAppliedLogIndexListener.
+     */
+    void addLastAppliedLogIndexListener(final LastAppliedLogIndexListener listener);
+
+    /**
+     * Called when log entry committed
+     *
+     * @param committedIndex committed log index
+     */
+    boolean onCommitted(final long committedIndex);
+
+    /**
+     * Called after loading snapshot.
+     *
+     * @param done callback
+     */
+    boolean onSnapshotLoad(final LoadSnapshotClosure done);
+
+    /**
+     * Called after saving snapshot.
+     *
+     * @param done callback
+     */
+    boolean onSnapshotSave(final SaveSnapshotClosure done);
+
+    /**
+     * Called when the leader stops.
+     *
+     * @param status status info
+     */
+    boolean onLeaderStop(final Status status);
+
+    /**
+     * Called when the leader starts.
+     *
+     * @param term current term
+     */
+    boolean onLeaderStart(final long term);
+
+    /**
+     * Called when start following a leader.
+     *
+     * @param ctx context of leader change
+     */
+    boolean onStartFollowing(final LeaderChangeContext ctx);
+
+    /**
+     * Called when stop following a leader.
+     *
+     * @param ctx context of leader change
+     */
+    boolean onStopFollowing(final LeaderChangeContext ctx);
+
+    /**
+     * Called when error happens.
+     *
+     * @param error error info
+     */
+    boolean onError(final RaftException error);
+
+    /**
+     * Returns the last log entry index to apply state machine.
+     */
+    long getLastAppliedIndex();
+
+    /**
+     * Called after shutdown to wait it terminates.
+     *
+     * @throws InterruptedException if the current thread is interrupted
+     *         while waiting
+     */
+    void join() throws InterruptedException;
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/Iterator.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/Iterator.java
new file mode 100644
index 0000000..c80de1f
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/Iterator.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Iterator over a batch of committed tasks.
+ * @see StateMachine#onApply(Iterator)
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-03 3:20:15 PM
+ */
+public interface Iterator extends java.util.Iterator<ByteBuffer> {
+
+    /**
+     * Return the data whose content is the same as what was passed to
+     * Node#apply(Task) in the leader node.
+     */
+    ByteBuffer getData();
+
+    /**
+     * Return a unique and monotonically increasing identifier of the current task:
+     * - Uniqueness guarantees that committed tasks in different peers with
+     *    the same index are always the same and kept unchanged.
+     * - Monotonicity guarantees that for any index pair i, j (i < j), task
+     *    at index |i| must be applied before task at index |j| in all the
+     *    peers from the group.
+     */
+    long getIndex();
+
+    /**
+     * Returns the term of the leader which to task was applied to.
+     */
+    long getTerm();
+
+    /**
+     * If done() is non-NULL, you must call done()->Run() after applying this
+     * task no matter this operation succeeds or fails, otherwise the
+     * corresponding resources would leak.
+     *
+     * If this task is proposed by this Node when it was the leader of this
+     * group and the leadership has not changed before this point, done() is
+     * exactly what was passed to Node#apply(Task) which may stand for some
+     * continuation (such as respond to the client) after updating the
+     * StateMachine with the given task. Otherwise done() must be NULL.
+     * */
+    Closure done();
+
+    /**
+     * Invoked when some critical error occurred. And we will consider the last
+     * |ntail| tasks (starting from the last iterated one) as not applied. After
+     * this point, no further changes on the StateMachine as well as the Node
+     * would be allowed and you should try to repair this replica or just drop it.
+     *
+     * @param ntail the number of tasks (starting from the last iterated one)  considered as not to be applied.
+     * @param st    Status to describe the detail of the error.
+     */
+    void setErrorAndRollback(final long ntail, final Status st);
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/JRaftServiceFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/JRaftServiceFactory.java
new file mode 100644
index 0000000..74fa168
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/JRaftServiceFactory.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+import com.alipay.sofa.jraft.entity.codec.LogEntryCodecFactory;
+import com.alipay.sofa.jraft.option.NodeOptions;
+import com.alipay.sofa.jraft.option.RaftOptions;
+import com.alipay.sofa.jraft.storage.LogStorage;
+import com.alipay.sofa.jraft.storage.RaftMetaStorage;
+import com.alipay.sofa.jraft.storage.SnapshotStorage;
+
+/**
+ * Abstract factory to create services for SOFAJRaft.
+ * @author boyan(boyan@antfin.com)
+ * @since  1.2.6
+ */
+public interface JRaftServiceFactory {
+
+    /**
+     * Creates a raft log storage.
+     * @param uri  The log storage uri from {@link NodeOptions#getSnapshotUri()}
+     * @param raftOptions  the raft options.
+     * @return storage to store raft log entires.
+     */
+    LogStorage createLogStorage(final String uri, final RaftOptions raftOptions);
+
+    /**
+     * Creates a raft snapshot storage
+     * @param uri  The snapshot storage uri from {@link NodeOptions#getSnapshotUri()}
+     * @param raftOptions  the raft options.
+     * @return storage to store state machine snapshot.
+     */
+    SnapshotStorage createSnapshotStorage(final String uri, final RaftOptions raftOptions);
+
+    /**
+     * Creates a raft meta storage.
+     * @param uri  The meta storage uri from {@link NodeOptions#getRaftMetaUri()}
+     * @param raftOptions  the raft options.
+     * @return meta storage to store raft meta info.
+     */
+    RaftMetaStorage createRaftMetaStorage(final String uri, final RaftOptions raftOptions);
+
+    /**
+     * Creates a log entry codec factory.
+     * @return a codec factory to create encoder/decoder for raft log entry.
+     */
+    LogEntryCodecFactory createLogEntryCodecFactory();
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/JRaftUtils.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/JRaftUtils.java
new file mode 100644
index 0000000..020137f
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/JRaftUtils.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.core.NodeImpl;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.option.BootstrapOptions;
+import com.alipay.sofa.jraft.util.Endpoint;
+import com.alipay.sofa.jraft.util.JRaftServiceLoader;
+import com.alipay.sofa.jraft.util.NamedThreadFactory;
+import com.alipay.sofa.jraft.util.ThreadPoolUtil;
+import com.alipay.sofa.jraft.util.timer.RaftTimerFactory;
+
+/**
+ * Some helper methods for jraft usage.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-23 3:48:45 PM
+ */
+public final class JRaftUtils {
+
+    private final static RaftTimerFactory TIMER_FACTORY = JRaftServiceLoader.load(RaftTimerFactory.class) //
+                                                            .first();
+
+    /**
+     * Get raft timer factory.
+     *
+     * @return {@link RaftTimerFactory}
+     */
+    public static RaftTimerFactory raftTimerFactory() {
+        return TIMER_FACTORY;
+    }
+
+    /**
+     * Bootstrap a non-empty raft node.
+     *
+     * @param opts options of bootstrap
+     * @return true if bootstrap success
+     */
+    public static boolean bootstrap(final BootstrapOptions opts) throws InterruptedException {
+        final NodeImpl node = new NodeImpl();
+        final boolean ret = node.bootstrap(opts);
+        node.shutdown();
+        node.join();
+        return ret;
+    }
+
+    /**
+     * Create a executor with size.
+     *
+     * @param prefix thread name prefix
+     * @param number thread number
+     * @return a new {@link ThreadPoolExecutor} instance
+     */
+    public static Executor createExecutor(final String prefix, final int number) {
+        if (number <= 0) {
+            return null;
+        }
+        return ThreadPoolUtil.newBuilder() //
+            .poolName(prefix) //
+            .enableMetric(true) //
+            .coreThreads(number) //
+            .maximumThreads(number) //
+            .keepAliveSeconds(60L) //
+            .workQueue(new SynchronousQueue<>()) //
+            .threadFactory(createThreadFactory(prefix)) //
+            .build();
+    }
+
+    /**
+     * Create a thread factory.
+     *
+     * @param prefixName the prefix name of thread
+     * @return a new {@link ThreadFactory} instance
+     *
+     * @since 0.0.3
+     */
+    public static ThreadFactory createThreadFactory(final String prefixName) {
+        return new NamedThreadFactory(prefixName, true);
+    }
+
+    /**
+     * Create a configuration from a string in the form of "host1:port1[:idx],host2:port2[:idx]......",
+     * returns a empty configuration when string is blank.
+     */
+    public static Configuration getConfiguration(final String s) {
+        final Configuration conf = new Configuration();
+        if (StringUtils.isBlank(s)) {
+            return conf;
+        }
+        if (conf.parse(s)) {
+            return conf;
+        }
+        throw new IllegalArgumentException("Invalid conf str:" + s);
+    }
+
+    /**
+     * Create a peer from a string in the form of "host:port[:idx]",
+     * returns a empty peer when string is blank.
+     */
+    public static PeerId getPeerId(final String s) {
+        final PeerId peer = new PeerId();
+        if (StringUtils.isBlank(s)) {
+            return peer;
+        }
+        if (peer.parse(s)) {
+            return peer;
+        }
+        throw new IllegalArgumentException("Invalid peer str:" + s);
+    }
+
+    /**
+     * Create a Endpoint instance from  a string in the form of "host:port",
+     * returns null when string is blank.
+     */
+    public static Endpoint getEndPoint(final String s) {
+        if (StringUtils.isBlank(s)) {
+            return null;
+        }
+        final String[] tmps = StringUtils.split(s, ':');
+        if (tmps.length != 2) {
+            throw new IllegalArgumentException("Invalid endpoint string: " + s);
+        }
+        return new Endpoint(tmps[0], Integer.parseInt(tmps[1]));
+    }
+
+    private JRaftUtils() {
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/Lifecycle.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/Lifecycle.java
new file mode 100644
index 0000000..85bff23
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/Lifecycle.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Service life cycle mark interface.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Mar-12 3:47:04 PM
+ */
+public interface Lifecycle<T> {
+
+    /**
+     * Initialize the service.
+     *
+     * @return true when successes.
+     */
+    boolean init(final T opts);
+
+    /**
+     * Dispose the resources for service.
+     */
+    void shutdown();
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/Node.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/Node.java
new file mode 100644
index 0000000..2eab9a3
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/Node.java
@@ -0,0 +1,327 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import com.alipay.sofa.jraft.closure.ReadIndexClosure;
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.core.NodeMetrics;
+import com.alipay.sofa.jraft.core.Replicator;
+import com.alipay.sofa.jraft.entity.NodeId;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.entity.Task;
+import com.alipay.sofa.jraft.entity.UserLog;
+import com.alipay.sofa.jraft.error.LogIndexOutOfBoundsException;
+import com.alipay.sofa.jraft.error.LogNotFoundException;
+import com.alipay.sofa.jraft.option.NodeOptions;
+import com.alipay.sofa.jraft.option.RaftOptions;
+import com.alipay.sofa.jraft.util.Describer;
+
+/**
+ * A raft replica node.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-03 4:06:55 PM
+ */
+public interface Node extends Lifecycle<NodeOptions>, Describer {
+
+    /**
+     * Get the leader peer id for redirect, null if absent.
+     */
+    PeerId getLeaderId();
+
+    /**
+     * Get current node id.
+     */
+    NodeId getNodeId();
+
+    /**
+     * Get the node metrics, only valid when node option {@link NodeOptions#isEnableMetrics()} is true.
+     */
+    NodeMetrics getNodeMetrics();
+
+    /**
+     * Get the raft group id.
+     */
+    String getGroupId();
+
+    /**
+     * Get the node options.
+     */
+    NodeOptions getOptions();
+
+    /**
+     * Get the raft options
+     */
+    RaftOptions getRaftOptions();
+
+    /**
+     * Returns true when the node is leader.
+     */
+    boolean isLeader();
+
+    /**
+     * Returns true when the node is leader.
+     * @param blocking if true, will be blocked until the node finish it's state change
+     */
+    boolean isLeader(final boolean blocking);
+
+    /**
+     * Shutdown local replica node.
+     *
+     * @param done callback
+     */
+    void shutdown(final Closure done);
+
+    /**
+     * Block the thread until the node is successfully stopped.
+     *
+     * @throws InterruptedException if the current thread is interrupted
+     *         while waiting
+     */
+    void join() throws InterruptedException;
+
+    /**
+     * [Thread-safe and wait-free]
+     *
+     * Apply task to the replicated-state-machine
+     *
+     * About the ownership:
+     * |task.data|: for the performance consideration, we will take away the
+     *               content. If you want keep the content, copy it before call
+     *               this function
+     * |task.done|: If the data is successfully committed to the raft group. We
+     *              will pass the ownership to #{@link StateMachine#onApply(Iterator)}.
+     *              Otherwise we will specify the error and call it.
+     *
+     * @param task task to apply
+     */
+    void apply(final Task task);
+
+    /**
+     * [Thread-safe and wait-free]
+     *
+     * Starts a linearizable read-only query request with request context(optional,
+     * such as request id etc.) and closure.  The closure will be called when the
+     * request is completed, and user can read data from state machine if the result
+     * status is OK.
+     *
+     * @param requestContext the context of request
+     * @param done           callback
+     *
+     * @since 0.0.3
+     */
+    void readIndex(final byte[] requestContext, final ReadIndexClosure done);
+
+    /**
+     * List peers of this raft group, only leader returns.
+     *
+     * [NOTE] <strong>when list_peers concurrency with {@link #addPeer(PeerId, Closure)}/{@link #removePeer(PeerId, Closure)},
+     * maybe return peers is staled.  Because {@link #addPeer(PeerId, Closure)}/{@link #removePeer(PeerId, Closure)}
+     * immediately modify configuration in memory</strong>
+     *
+     * @return the peer list
+     */
+    List<PeerId> listPeers();
+
+    /**
+     * List all alive peers of this raft group, only leader returns.</p>
+     *
+     * [NOTE] <strong>list_alive_peers is just a transient data (snapshot)
+     * and a short-term loss of response by the follower will cause it to
+     * temporarily not exist in this list.</strong>
+     *
+     * @return the alive peer list
+     * @since 1.2.6
+     */
+    List<PeerId> listAlivePeers();
+
+    /**
+     * List all learners of this raft group, only leader returns.</p>
+     *
+     * [NOTE] <strong>when listLearners concurrency with {@link #addLearners(List, Closure)}/{@link #removeLearners(List, Closure)}/{@link #resetLearners(List, Closure)},
+     * maybe return peers is staled.  Because {@link #addLearners(List, Closure)}/{@link #removeLearners(List, Closure)}/{@link #resetLearners(List, Closure)}
+     * immediately modify configuration in memory</strong>
+     *
+     * @return the learners set
+     * @since 1.3.0
+     */
+    List<PeerId> listLearners();
+
+    /**
+     * List all alive learners of this raft group, only leader returns.</p>
+     *
+     * [NOTE] <strong>when listAliveLearners concurrency with {@link #addLearners(List, Closure)}/{@link #removeLearners(List, Closure)}/{@link #resetLearners(List, Closure)},
+     * maybe return peers is staled.  Because {@link #addLearners(List, Closure)}/{@link #removeLearners(List, Closure)}/{@link #resetLearners(List, Closure)}
+     * immediately modify configuration in memory</strong>
+     *
+     * @return the  alive learners set
+     * @since 1.3.0
+     */
+    List<PeerId> listAliveLearners();
+
+    /**
+     * Add a new peer to the raft group. done.run() would be invoked after this
+     * operation finishes, describing the detailed result.
+     *
+     * @param peer peer to add
+     * @param done callback
+     */
+    void addPeer(final PeerId peer, final Closure done);
+
+    /**
+     * Remove the peer from the raft group. done.run() would be invoked after
+     * operation finishes, describing the detailed result.
+     *
+     * @param peer peer to remove
+     * @param done callback
+     */
+    void removePeer(final PeerId peer, final Closure done);
+
+    /**
+     * Change the configuration of the raft group to |newPeers| , done.un()
+     * would be invoked after this operation finishes, describing the detailed result.
+     *
+     * @param newPeers new peers to change
+     * @param done     callback
+     */
+    void changePeers(final Configuration newPeers, final Closure done);
+
+    /**
+     * Reset the configuration of this node individually, without any replication
+     * to other peers before this node becomes the leader. This function is
+     * supposed to be invoked when the majority of the replication group are
+     * dead and you'd like to revive the service in the consideration of
+     * availability.
+     * Notice that neither consistency nor consensus are guaranteed in this
+     * case, BE CAREFULE when dealing with this method.
+     *
+     * @param newPeers new peers
+     */
+    Status resetPeers(final Configuration newPeers);
+
+    /**
+     * Add some new learners to the raft group. done.run() will be invoked after this
+     * operation finishes, describing the detailed result.
+     *
+     * @param learners learners to add
+     * @param done     callback
+     * @since 1.3.0
+     */
+    void addLearners(final List<PeerId> learners, final Closure done);
+
+    /**
+     * Remove some learners from the raft group. done.run() will be invoked after this
+     * operation finishes, describing the detailed result.
+     *
+     * @param learners learners to remove
+     * @param done     callback
+     * @since 1.3.0
+     */
+    void removeLearners(final List<PeerId> learners, final Closure done);
+
+    /**
+     * Reset learners in the raft group. done.run() will be invoked after this
+     * operation finishes, describing the detailed result.
+     *
+     * @param learners learners to set
+     * @param done     callback
+     * @since 1.3.0
+     */
+    void resetLearners(final List<PeerId> learners, final Closure done);
+
+    /**
+     * Start a snapshot immediately if possible. done.run() would be invoked when
+     * the snapshot finishes, describing the detailed result.
+     *
+     * @param done callback
+     */
+    void snapshot(final Closure done);
+
+    /**
+     * Reset the election_timeout for the every node.
+     *
+     * @param electionTimeoutMs the timeout millis of election
+     */
+    void resetElectionTimeoutMs(final int electionTimeoutMs);
+
+    /**
+     * Try transferring leadership to |peer|. If peer is ANY_PEER, a proper follower
+     * will be chosen as the leader for the next term.
+     * Returns 0 on success, -1 otherwise.
+     *
+     * @param peer the target peer of new leader
+     * @return operation status
+     */
+    Status transferLeadershipTo(final PeerId peer);
+
+    /**
+     * Read the first committed user log from the given index.
+     *   Return OK on success and user_log is assigned with the very data. Be awared
+     *   that the user_log may be not the exact log at the given index, but the
+     *   first available user log from the given index to lastCommittedIndex.
+     *   Otherwise, appropriate errors are returned:
+     *        - return ELOGDELETED when the log has been deleted;
+     *        - return ENOMOREUSERLOG when we can't get a user log even reaching lastCommittedIndex.
+     * [NOTE] in consideration of safety, we use lastAppliedIndex instead of lastCommittedIndex
+     * in code implementation.
+     *
+     * @param index log index
+     * @return user log entry
+     * @throws LogNotFoundException  the user log is deleted at index.
+     * @throws LogIndexOutOfBoundsException  the special index is out of bounds.
+     */
+    UserLog readCommittedUserLog(final long index);
+
+    /**
+     * SOFAJRaft users can implement the ReplicatorStateListener interface by themselves.
+     * So users can do their own logical operator in this listener when replicator created, destroyed or had some errors.
+     *
+     * @param replicatorStateListener added ReplicatorStateListener which is implemented by users.
+     */
+    void addReplicatorStateListener(final Replicator.ReplicatorStateListener replicatorStateListener);
+
+    /**
+     * End User can remove their implement the ReplicatorStateListener interface by themselves.
+     *
+     * @param replicatorStateListener need to remove the ReplicatorStateListener which has been added by users.
+     */
+    void removeReplicatorStateListener(final Replicator.ReplicatorStateListener replicatorStateListener);
+
+    /**
+     * Remove all the ReplicatorStateListeners which have been added by users.
+     *
+     */
+    void clearReplicatorStateListeners();
+
+    /**
+     * Get the ReplicatorStateListeners which have been added by users.
+     *
+     * @return node's replicatorStatueListeners which have been added by users.
+     */
+    List<Replicator.ReplicatorStateListener> getReplicatorStatueListeners();
+
+    /**
+     * Get the node's target election priority value.
+     *
+     * @return node's target election priority value.
+     * @since 1.3.0
+     */
+    int getNodeTargetPriority();
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeDescribeSignalHandler.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeDescribeSignalHandler.java
new file mode 100644
index 0000000..42f6910
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeDescribeSignalHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.util.Describer;
+import com.alipay.sofa.jraft.util.FileOutputSignalHandler;
+import com.alipay.sofa.jraft.util.SystemPropertyUtil;
+
+/**
+ *
+ * @author jiachun.fjc
+ */
+public class NodeDescribeSignalHandler extends FileOutputSignalHandler {
+
+    private static Logger       LOG       = LoggerFactory.getLogger(NodeDescribeSignalHandler.class);
+
+    private static final String DIR       = SystemPropertyUtil.get("jraft.signal.node.describe.dir", "");
+    private static final String BASE_NAME = "node_describe.log";
+
+    @Override
+    public void handle(final String signalName) {
+        final List<Node> nodes = NodeManager.getInstance().getAllNodes();
+        if (nodes.isEmpty()) {
+            return;
+        }
+
+        try {
+            final File file = getOutputFile(DIR, BASE_NAME);
+
+            LOG.info("Describing raft nodes with signal: {} to file: {}.", signalName, file);
+
+            try (final PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file, true),
+                StandardCharsets.UTF_8))) {
+                final Describer.Printer printer = new Describer.DefaultPrinter(out);
+                for (final Node node : nodes) {
+                    node.describe(printer);
+                }
+            }
+        } catch (final IOException e) {
+            LOG.error("Fail to describe nodes: {}.", nodes, e);
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeManager.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeManager.java
new file mode 100644
index 0000000..236ebf7
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeManager.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+import com.alipay.sofa.jraft.entity.NodeId;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.util.Endpoint;
+import com.alipay.sofa.jraft.util.OnlyForTest;
+import com.alipay.sofa.jraft.util.Utils;
+import com.alipay.sofa.jraft.util.concurrent.ConcurrentHashSet;
+
+/**
+ * Raft nodes manager.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Mar-22 5:58:23 PM
+ */
+public class NodeManager {
+
+    private static final NodeManager                INSTANCE = new NodeManager();
+
+    private final ConcurrentMap<NodeId, Node>       nodeMap  = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, List<Node>> groupMap = new ConcurrentHashMap<>();
+    private final ConcurrentHashSet<Endpoint>       addrSet  = new ConcurrentHashSet<>();
+
+    public static NodeManager getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Return true when RPC service is registered.
+     */
+    public boolean serverExists(final Endpoint addr) {
+        if (addr.getIp().equals(Utils.IP_ANY)) {
+            return this.addrSet.contains(new Endpoint(Utils.IP_ANY, addr.getPort()));
+        }
+        return this.addrSet.contains(addr);
+    }
+
+    /**
+     * Remove a RPC service address.
+     */
+    public boolean removeAddress(final Endpoint addr) {
+        return this.addrSet.remove(addr);
+    }
+
+    /**
+     * Adds a RPC service address.
+     */
+    public void addAddress(final Endpoint addr) {
+        this.addrSet.add(addr);
+    }
+
+    /**
+     * Adds a node.
+     */
+    public boolean add(final Node node) {
+        // check address ok?
+        if (!serverExists(node.getNodeId().getPeerId().getEndpoint())) {
+            return false;
+        }
+        final NodeId nodeId = node.getNodeId();
+        if (this.nodeMap.putIfAbsent(nodeId, node) == null) {
+            final String groupId = node.getGroupId();
+            List<Node> nodes = this.groupMap.get(groupId);
+            if (nodes == null) {
+                nodes = Collections.synchronizedList(new ArrayList<>());
+                List<Node> existsNode = this.groupMap.putIfAbsent(groupId, nodes);
+                if (existsNode != null) {
+                    nodes = existsNode;
+                }
+            }
+            nodes.add(node);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Clear the states, for test
+     */
+    @OnlyForTest
+    public void clear() {
+        this.groupMap.clear();
+        this.nodeMap.clear();
+        this.addrSet.clear();
+    }
+
+    /**
+     * Remove a node.
+     */
+    public boolean remove(final Node node) {
+        if (this.nodeMap.remove(node.getNodeId(), node)) {
+            final List<Node> nodes = this.groupMap.get(node.getGroupId());
+            if (nodes != null) {
+                return nodes.remove(node);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get node by groupId and peer.
+     */
+    public Node get(final String groupId, final PeerId peerId) {
+        return this.nodeMap.get(new NodeId(groupId, peerId));
+    }
+
+    /**
+     * Get all nodes in a raft group.
+     */
+    public List<Node> getNodesByGroupId(final String groupId) {
+        return this.groupMap.get(groupId);
+    }
+
+    /**
+     * Get all nodes
+     */
+    public List<Node> getAllNodes() {
+        return this.groupMap.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
+    }
+
+    private NodeManager() {
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeMetricsSignalHandler.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeMetricsSignalHandler.java
new file mode 100644
index 0000000..2d128cb
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/NodeMetricsSignalHandler.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.core.NodeMetrics;
+import com.alipay.sofa.jraft.util.FileOutputSignalHandler;
+import com.alipay.sofa.jraft.util.MetricReporter;
+import com.alipay.sofa.jraft.util.SystemPropertyUtil;
+import com.codahale.metrics.MetricRegistry;
+
+/**
+ *
+ * @author jiachun.fjc
+ */
+public class NodeMetricsSignalHandler extends FileOutputSignalHandler {
+
+    private static Logger       LOG       = LoggerFactory.getLogger(NodeMetricsSignalHandler.class);
+
+    private static final String DIR       = SystemPropertyUtil.get("jraft.signal.node.metrics.dir", "");
+    private static final String BASE_NAME = "node_metrics.log";
+
+    @Override
+    public void handle(final String signalName) {
+        final List<Node> nodes = NodeManager.getInstance().getAllNodes();
+        if (nodes.isEmpty()) {
+            return;
+        }
+
+        try {
+            final File file = getOutputFile(DIR, BASE_NAME);
+
+            LOG.info("Printing raft nodes metrics with signal: {} to file: {}.", signalName, file);
+
+            try (final PrintStream out = new PrintStream(new FileOutputStream(file, true))) {
+                for (final Node node : nodes) {
+                    final NodeMetrics nodeMetrics = node.getNodeMetrics();
+                    final MetricRegistry registry = nodeMetrics.getMetricRegistry();
+                    if (registry == null) {
+                        LOG.warn("Node: {} received a signal to print metric, but it does not have metric enabled.",
+                            node);
+                        continue;
+                    }
+                    final MetricReporter reporter = MetricReporter.forRegistry(registry) //
+                        .outputTo(out) //
+                        .prefixedWith("-- " + node.getNodeId()) //
+                        .build();
+                    reporter.report();
+                }
+            }
+        } catch (final IOException e) {
+            LOG.error("Fail to print nodes metrics: {}.", nodes, e);
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftGroupService.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftGroupService.java
new file mode 100644
index 0000000..df9f9d1
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftGroupService.java
@@ -0,0 +1,257 @@
+/*
+ * 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;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.option.NodeOptions;
+import com.alipay.sofa.jraft.option.RpcOptions;
+import com.alipay.sofa.jraft.rpc.ProtobufMsgFactory;
+import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory;
+import com.alipay.sofa.jraft.rpc.RpcServer;
+import com.alipay.sofa.jraft.util.Endpoint;
+import com.alipay.sofa.jraft.util.Utils;
+
+/**
+ * A framework to implement a raft group service.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-08 7:53:03 PM
+ */
+public class RaftGroupService {
+
+    private static final Logger LOG     = LoggerFactory.getLogger(RaftGroupService.class);
+
+    static {
+        ProtobufMsgFactory.load();
+    }
+
+    private volatile boolean    started = false;
+
+    /**
+     * This node serverId
+     */
+    private PeerId              serverId;
+
+    /**
+     * Node options
+     */
+    private NodeOptions         nodeOptions;
+
+    /**
+     * The raft RPC server
+     */
+    private RpcServer           rpcServer;
+
+    /**
+     * If we want to share the rpcServer instance, then we can't stop it when shutdown.
+     */
+    private final boolean       sharedRpcServer;
+
+    /**
+     * The raft group id
+     */
+    private String              groupId;
+    /**
+     * The raft node.
+     */
+    private Node                node;
+
+    public RaftGroupService(final String groupId, final PeerId serverId, final NodeOptions nodeOptions) {
+        this(groupId, serverId, nodeOptions, RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint(),
+            JRaftUtils.createExecutor("RAFT-RPC-executor-", nodeOptions.getRaftRpcThreadPoolSize()),
+            JRaftUtils.createExecutor("CLI-RPC-executor-", nodeOptions.getCliRpcThreadPoolSize())));
+    }
+
+    public RaftGroupService(final String groupId, final PeerId serverId, final NodeOptions nodeOptions,
+                            final RpcServer rpcServer) {
+        this(groupId, serverId, nodeOptions, rpcServer, false);
+    }
+
+    public RaftGroupService(final String groupId, final PeerId serverId, final NodeOptions nodeOptions,
+                            final RpcServer rpcServer, final boolean sharedRpcServer) {
+        super();
+        this.groupId = groupId;
+        this.serverId = serverId;
+        this.nodeOptions = nodeOptions;
+        this.rpcServer = rpcServer;
+        this.sharedRpcServer = sharedRpcServer;
+    }
+
+    public synchronized Node getRaftNode() {
+        return this.node;
+    }
+
+    /**
+     * Starts the raft group service, returns the raft node.
+     */
+    public synchronized Node start() {
+        return start(true);
+    }
+
+    /**
+     * Starts the raft group service, returns the raft node.
+     *
+     * @param startRpcServer whether to start RPC server.
+     */
+    public synchronized Node start(final boolean startRpcServer) {
+        if (this.started) {
+            return this.node;
+        }
+        if (this.serverId == null || this.serverId.getEndpoint() == null
+            || this.serverId.getEndpoint().equals(new Endpoint(Utils.IP_ANY, 0))) {
+            throw new IllegalArgumentException("Blank serverId:" + this.serverId);
+        }
+        if (StringUtils.isBlank(this.groupId)) {
+            throw new IllegalArgumentException("Blank group id" + this.groupId);
+        }
+        //Adds RPC server to Server.
+        NodeManager.getInstance().addAddress(this.serverId.getEndpoint());
+
+        this.node = RaftServiceFactory.createAndInitRaftNode(this.groupId, this.serverId, this.nodeOptions);
+        if (startRpcServer) {
+            this.rpcServer.init(null);
+        } else {
+            LOG.warn("RPC server is not started in RaftGroupService.");
+        }
+        this.started = true;
+        LOG.info("Start the RaftGroupService successfully.");
+        return this.node;
+    }
+
+    /**
+     * Block thread to wait the server shutdown.
+     *
+     * @throws InterruptedException if the current thread is interrupted
+     *         while waiting
+     */
+    public synchronized void join() throws InterruptedException {
+        if (this.node != null) {
+            this.node.join();
+            this.node = null;
+        }
+    }
+
+    public synchronized void shutdown() {
+        if (!this.started) {
+            return;
+        }
+        if (this.rpcServer != null) {
+            try {
+                if (!this.sharedRpcServer) {
+                    this.rpcServer.shutdown();
+                }
+            } catch (final Exception ignored) {
+                // ignore
+            }
+            this.rpcServer = null;
+        }
+        this.node.shutdown();
+        NodeManager.getInstance().removeAddress(this.serverId.getEndpoint());
+        this.started = false;
+        LOG.info("Stop the RaftGroupService successfully.");
+    }
+
+    /**
+     * Returns true when service is started.
+     */
+    public boolean isStarted() {
+        return this.started;
+    }
+
+    /**
+     * Returns the raft group id.
+     */
+    public String getGroupId() {
+        return this.groupId;
+    }
+
+    /**
+     * Set the raft group id
+     */
+    public void setGroupId(final String groupId) {
+        if (this.started) {
+            throw new IllegalStateException("Raft group service already started");
+        }
+        this.groupId = groupId;
+    }
+
+    /**
+     * Returns the node serverId
+     */
+    public PeerId getServerId() {
+        return this.serverId;
+    }
+
+    /**
+     * Set the node serverId
+     */
+    public void setServerId(final PeerId serverId) {
+        if (this.started) {
+            throw new IllegalStateException("Raft group service already started");
+        }
+        this.serverId = serverId;
+    }
+
+    /**
+     * Returns the node options.
+     */
+    public RpcOptions getNodeOptions() {
+        return this.nodeOptions;
+    }
+
+    /**
+     * Set node options.
+     */
+    public void setNodeOptions(final NodeOptions nodeOptions) {
+        if (this.started) {
+            throw new IllegalStateException("Raft group service already started");
+        }
+        if (nodeOptions == null) {
+            throw new IllegalArgumentException("Invalid node options.");
+        }
+        nodeOptions.validate();
+        this.nodeOptions = nodeOptions;
+    }
+
+    /**
+     * Returns the rpc server instance.
+     */
+    public RpcServer getRpcServer() {
+        return this.rpcServer;
+    }
+
+    /**
+     * Set rpc server.
+     */
+    public void setRpcServer(final RpcServer rpcServer) {
+        if (this.started) {
+            throw new IllegalStateException("Raft group service already started");
+        }
+        if (this.serverId == null) {
+            throw new IllegalStateException("Please set serverId at first");
+        }
+        if (rpcServer.boundPort() != this.serverId.getPort()) {
+            throw new IllegalArgumentException("RPC server port mismatch");
+        }
+        this.rpcServer = rpcServer;
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftServiceFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftServiceFactory.java
new file mode 100644
index 0000000..8aaea5c
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftServiceFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alipay.sofa.jraft;
+
+import com.alipay.sofa.jraft.core.CliServiceImpl;
+import com.alipay.sofa.jraft.core.NodeImpl;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.option.CliOptions;
+import com.alipay.sofa.jraft.option.NodeOptions;
+
+/**
+ * Service factory to create raft services, such as Node/CliService etc.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-May-03 11:06:02 AM
+ */
+public final class RaftServiceFactory {
+
+    /**
+     * Create a raft node with group id and it's serverId.
+     */
+    public static Node createRaftNode(final String groupId, final PeerId serverId) {
+        return new NodeImpl(groupId, serverId);
+    }
+
+    /**
+     * Create and initialize a raft node with node options.
+     * Throw {@link IllegalStateException} when fail to initialize.
+     */
+    public static Node createAndInitRaftNode(final String groupId, final PeerId serverId, final NodeOptions opts) {
+        final Node ret = createRaftNode(groupId, serverId);
+        if (!ret.init(opts)) {
+            throw new IllegalStateException("Fail to init node, please see the logs to find the reason.");
+        }
+        return ret;
+    }
+
+    /**
+     * Create a {@link CliService} instance.
+     */
+    public static CliService createCliService() {
+        return new CliServiceImpl();
+    }
+
+    /**
+     * Create and initialize a CliService instance.
+     */
+    public static CliService createAndInitCliService(final CliOptions cliOptions) {
+        final CliService ret = createCliService();
+        if (!ret.init(cliOptions)) {
+            throw new IllegalStateException("Fail to init CliService");
+        }
+        return ret;
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/ReadOnlyService.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/ReadOnlyService.java
new file mode 100644
index 0000000..cd14364
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/ReadOnlyService.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import com.alipay.sofa.jraft.closure.ReadIndexClosure;
+import com.alipay.sofa.jraft.error.RaftException;
+import com.alipay.sofa.jraft.option.ReadOnlyServiceOptions;
+
+/**
+ * The read-only query service.
+ *
+ * @author dennis
+ *
+ */
+public interface ReadOnlyService extends Lifecycle<ReadOnlyServiceOptions> {
+
+    /**
+     * Adds a ReadIndex request.
+     *
+     * @param reqCtx    request context of readIndex
+     * @param closure   callback
+     */
+    void addRequest(final byte[] reqCtx, final ReadIndexClosure closure);
+
+    /**
+     * Waits for service shutdown.
+     *
+     * @throws InterruptedException if the current thread is interrupted
+     *         while waiting
+     */
+    void join() throws InterruptedException;
+
+    /**
+     * Called when the node is turned into error state.
+     * @param error error with raft info
+     */
+    void setError(final RaftException error);
+
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/ReplicatorGroup.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/ReplicatorGroup.java
new file mode 100644
index 0000000..933bfbf
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/ReplicatorGroup.java
@@ -0,0 +1,229 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import com.alipay.sofa.jraft.closure.CatchUpClosure;
+import com.alipay.sofa.jraft.conf.ConfigurationEntry;
+import com.alipay.sofa.jraft.core.ReplicatorType;
+import com.alipay.sofa.jraft.entity.NodeId;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.option.ReplicatorGroupOptions;
+import com.alipay.sofa.jraft.rpc.RpcRequests.AppendEntriesResponse;
+import com.alipay.sofa.jraft.rpc.RpcResponseClosure;
+import com.alipay.sofa.jraft.util.Describer;
+import com.alipay.sofa.jraft.util.ThreadId;
+
+/**
+ * Replicators in a raft group.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-08 5:35:26 PM
+ */
+public interface ReplicatorGroup extends Describer {
+    /**
+     * Init the replicator group.
+     *
+     * @param nodeId node id
+     * @param opts   options of replicator grop
+     * @return true if init success
+     */
+    boolean init(final NodeId nodeId, final ReplicatorGroupOptions opts);
+
+    /**
+     * Adds a replicator for follower({@link ReplicatorType#Follower}).
+     * @see #addReplicator(PeerId, ReplicatorType)
+     *
+     * @param peer target peer
+     * @return true on success
+     */
+    default boolean addReplicator(final PeerId peer) {
+        return addReplicator(peer, ReplicatorType.Follower);
+    }
+
+    /**
+     * Add a replicator attached with |peer|
+     * will be a notification when the replicator catches up according to the
+     * arguments.
+     * NOTE: when calling this function, the replicators starts to work
+     * immediately, and might call Node#stepDown which might have race with
+     * the caller, you should deal with this situation.
+     *
+     * @param peer           target peer
+     * @param replicatorType replicator type
+     * @return true on success
+     */
+    default boolean addReplicator(final PeerId peer, ReplicatorType replicatorType) {
+        return addReplicator(peer, replicatorType, true);
+    }
+
+    /**
+     * Try to add a replicator attached with |peer|
+     * will be a notification when the replicator catches up according to the
+     * arguments.
+     * NOTE: when calling this function, the replicators starts to work
+     * immediately, and might call Node#stepDown which might have race with
+     * the caller, you should deal with this situation.
+     *
+     * @param peer           target peer
+     * @param replicatorType replicator type
+     * @param sync           synchronous
+     * @return true on success
+     */
+    boolean addReplicator(final PeerId peer, ReplicatorType replicatorType, boolean sync);
+
+    /**
+     * Send heartbeat to a peer.
+     *
+     * @param peer    target peer
+     * @param closure callback
+     */
+    void sendHeartbeat(final PeerId peer, final RpcResponseClosure<AppendEntriesResponse> closure);
+
+    /**
+     * Get replicator id by peer, null if not found.
+     *
+     * @param peer peer of replicator
+     * @return the replicator id
+     */
+    ThreadId getReplicator(final PeerId peer);
+
+    /**
+     * Check replicator state, if it's not started, start it;
+     * if it is blocked, unblock it. It should be called by leader.
+     *
+     * @param peer     peer of replicator
+     * @param lockNode if lock with node
+     */
+    void checkReplicator(final PeerId peer, final boolean lockNode);
+
+    /**
+     * Clear failure to start replicators
+     */
+    void clearFailureReplicators();
+
+    /**
+     * Wait the peer catchup.
+     */
+    boolean waitCaughtUp(final PeerId peer, final long maxMargin, final long dueTime, final CatchUpClosure done);
+
+    /**
+     * Get peer's last rpc send timestamp (monotonic time in milliseconds).
+     *
+     * @param peer the peer of replicator
+     */
+    long getLastRpcSendTimestamp(final PeerId peer);
+
+    /**
+     * Stop all replicators.
+     */
+    boolean stopAll();
+
+    /**
+     * Stop replicator for the peer.
+     *
+     * @param peer the peer of replicator
+     * @return true on success
+     */
+    boolean stopReplicator(final PeerId peer);
+
+    /**
+     * Reset the term of all to-add replicators.
+     * This method is supposed to be called when the very candidate becomes the
+     * leader, so we suppose that there are no running replicators.
+     * Return true on success, false otherwise
+     *
+     * @param newTerm new term num
+     * @return true on success
+     */
+    boolean resetTerm(final long newTerm);
+
+    /**
+     * Reset the interval of heartbeat,
+     * This method is supposed to be called when the very candidate becomes the
+     *  leader, so we suppose that there are no running replicators.
+     *  return true when success, false otherwise.
+     *
+     * @param newIntervalMs new heartbeat interval millis
+     * @return true on success
+     */
+    boolean resetHeartbeatInterval(final int newIntervalMs);
+
+    /**
+     * Reset the interval of electionTimeout for replicator.
+     *
+     * @param newIntervalMs new election timeout millis
+     * @return true on success
+     */
+    boolean resetElectionTimeoutInterval(final int newIntervalMs);
+
+    /**
+     * Returns true if the there's a replicator attached to the given |peer|
+     *
+     * @param peer target peer
+     * @return true on contains
+     */
+    boolean contains(final PeerId peer);
+
+    /**
+     * Transfer leadership to the given |peer|
+     *
+     * @param peer     target peer
+     * @param logIndex log index
+     * @return true on success
+     */
+    boolean transferLeadershipTo(final PeerId peer, final long logIndex);
+
+    /**
+     * Stop transferring leadership to the given |peer|
+     *
+     * @param peer target peer
+     * @return true on success
+     */
+    boolean stopTransferLeadership(final PeerId peer);
+
+    /**
+     * Stop all the replicators except for the one that we think can be the
+     * candidate of the next leader, which has the largest `last_log_id' among
+     * peers in |current_conf|.
+     * |candidate| would be returned if we found one and
+     * the caller is responsible for stopping it, or an invalid value if we
+     * found none.
+     * Returns candidate replicator id on success and null otherwise.
+     *
+     * @param conf configuration of all replicators
+     * @return candidate replicator id on success
+     */
+    ThreadId stopAllAndFindTheNextCandidate(final ConfigurationEntry conf);
+
+    /**
+     * Find the follower with the most log entries in this group, which is
+     * likely becomes the leader according to the election algorithm of raft.
+     * Returns the follower peerId on success and null otherwise.
+     *
+     * @param conf configuration of all replicators
+     * @return the follower peerId on success
+     */
+    PeerId findTheNextCandidate(final ConfigurationEntry conf);
+
+    /**
+     * Returns all replicators.
+     */
+    List<ThreadId> listReplicators();
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/RouteTable.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/RouteTable.java
new file mode 100644
index 0000000..4d11be7
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/RouteTable.java
@@ -0,0 +1,384 @@
+/*
+ * 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;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.StampedLock;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.error.RaftError;
+import com.alipay.sofa.jraft.rpc.CliClientService;
+import com.alipay.sofa.jraft.rpc.CliRequests;
+import com.alipay.sofa.jraft.rpc.RpcRequests;
+import com.alipay.sofa.jraft.util.Describer;
+import com.alipay.sofa.jraft.util.Requires;
+import com.alipay.sofa.jraft.rpc.Message;
+
+/**
+ * Maintain routes to raft groups.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-09 10:41:21 AM
+ */
+public class RouteTable implements Describer {
+
+    private static final Logger                    LOG            = LoggerFactory.getLogger(RouteTable.class);
+
+    private static final RouteTable                INSTANCE       = new RouteTable();
+
+    // Map<groupId, groupConf>
+    private final ConcurrentMap<String, GroupConf> groupConfTable = new ConcurrentHashMap<>();
+
+    public static RouteTable getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Update configuration of group in route table.
+     *
+     * @param groupId raft group id
+     * @param conf    configuration to update
+     * @return true on success
+     */
+    public boolean updateConfiguration(final String groupId, final Configuration conf) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(conf, "Null configuration");
+
+        final GroupConf gc = getOrCreateGroupConf(groupId);
+        final StampedLock stampedLock = gc.stampedLock;
+        final long stamp = stampedLock.writeLock();
+        try {
+            gc.conf = conf;
+            if (gc.leader != null && !gc.conf.contains(gc.leader)) {
+                gc.leader = null;
+            }
+        } finally {
+            stampedLock.unlockWrite(stamp);
+        }
+        return true;
+    }
+
+    private GroupConf getOrCreateGroupConf(final String groupId) {
+        GroupConf gc = this.groupConfTable.get(groupId);
+        if (gc == null) {
+            gc = new GroupConf();
+            final GroupConf old = this.groupConfTable.putIfAbsent(groupId, gc);
+            if (old != null) {
+                gc = old;
+            }
+        }
+        return gc;
+    }
+
+    /**
+     * Update configuration of group in route table.
+     *
+     * @param groupId raft group id
+     * @param confStr configuration string
+     * @return true on success
+     */
+    public boolean updateConfiguration(final String groupId, final String confStr) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireTrue(!StringUtils.isBlank(confStr), "Blank configuration");
+
+        final Configuration conf = new Configuration();
+        if (conf.parse(confStr)) {
+            return updateConfiguration(groupId, conf);
+        } else {
+            LOG.error("Fail to parse confStr: {}", confStr);
+            return false;
+        }
+    }
+
+    /**
+     * Get the cached leader of the group, return it when found, null otherwise.
+     * Make sure calls {@link #refreshLeader(CliClientService, String, int)} already
+     * before invoke this method.
+     *
+     * @param groupId raft group id
+     * @return peer of leader
+     */
+    public PeerId selectLeader(final String groupId) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+
+        final GroupConf gc = this.groupConfTable.get(groupId);
+        if (gc == null) {
+            return null;
+        }
+        final StampedLock stampedLock = gc.stampedLock;
+        long stamp = stampedLock.tryOptimisticRead();
+        PeerId leader = gc.leader;
+        if (!stampedLock.validate(stamp)) {
+            stamp = stampedLock.readLock();
+            try {
+                leader = gc.leader;
+            } finally {
+                stampedLock.unlockRead(stamp);
+            }
+        }
+        return leader;
+    }
+
+    /**
+     * Update leader info.
+     *
+     * @param groupId raft group id
+     * @param leader  peer of leader
+     * @return true on success
+     */
+    public boolean updateLeader(final String groupId, final PeerId leader) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+
+        if (leader != null) {
+            // If leader presents, it should not be empty.
+            Requires.requireTrue(!leader.isEmpty(), "Empty leader");
+        }
+
+        final GroupConf gc = getOrCreateGroupConf(groupId);
+        final StampedLock stampedLock = gc.stampedLock;
+        final long stamp = stampedLock.writeLock();
+        try {
+            gc.leader = leader;
+        } finally {
+            stampedLock.unlockWrite(stamp);
+        }
+        return true;
+    }
+
+    /**
+     * Update leader info.
+     *
+     * @param groupId   raft group id
+     * @param leaderStr peer string of leader
+     * @return true on success
+     */
+    public boolean updateLeader(final String groupId, final String leaderStr) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireTrue(!StringUtils.isBlank(leaderStr), "Blank leader");
+
+        final PeerId leader = new PeerId();
+        if (leader.parse(leaderStr)) {
+            return updateLeader(groupId, leader);
+        } else {
+            LOG.error("Fail to parse leaderStr: {}", leaderStr);
+            return false;
+        }
+    }
+
+    /**
+     * Get the configuration by groupId, returns null when not found.
+     *
+     * @param groupId raft group id
+     * @return configuration of the group id
+     */
+    public Configuration getConfiguration(final String groupId) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+
+        final GroupConf gc = this.groupConfTable.get(groupId);
+        if (gc == null) {
+            return null;
+        }
+        final StampedLock stampedLock = gc.stampedLock;
+        long stamp = stampedLock.tryOptimisticRead();
+        Configuration conf = gc.conf;
+        if (!stampedLock.validate(stamp)) {
+            stamp = stampedLock.readLock();
+            try {
+                conf = gc.conf;
+            } finally {
+                stampedLock.unlockRead(stamp);
+            }
+        }
+        return conf;
+    }
+
+    /**
+     * Blocking the thread until query_leader finishes.
+     *
+     * @param groupId   raft group id
+     * @param timeoutMs timeout millis
+     * @return operation status
+     */
+    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);
+
+        final Configuration conf = getConfiguration(groupId);
+        if (conf == null) {
+            return new Status(RaftError.ENOENT,
+                "Group %s is not registered in RouteTable, forgot to call updateConfiguration?", groupId);
+        }
+        final Status st = Status.OK();
+        final CliRequests.GetLeaderRequest.Builder rb = CliRequests.GetLeaderRequest.newBuilder();
+        rb.setGroupId(groupId);
+        final CliRequests.GetLeaderRequest request = rb.build();
+        TimeoutException timeoutException = null;
+        for (final PeerId peer : conf) {
+            if (!cliClientService.connect(peer.getEndpoint())) {
+                if (st.isOk()) {
+                    st.setError(-1, "Fail to init channel to %s", peer);
+                } else {
+                    final String savedMsg = st.getErrorMsg();
+                    st.setError(-1, "%s, Fail to init channel to %s", savedMsg, peer);
+                }
+                continue;
+            }
+            final Future<Message> result = cliClientService.getLeader(peer.getEndpoint(), request, null);
+            try {
+                final Message msg = result.get(timeoutMs, TimeUnit.MILLISECONDS);
+                if (msg instanceof RpcRequests.ErrorResponse) {
+                    if (st.isOk()) {
+                        st.setError(-1, ((RpcRequests.ErrorResponse) msg).getErrorMsg());
+                    } else {
+                        final String savedMsg = st.getErrorMsg();
+                        st.setError(-1, "%s, %s", savedMsg, ((RpcRequests.ErrorResponse) msg).getErrorMsg());
+                    }
+                } else {
+                    final CliRequests.GetLeaderResponse response = (CliRequests.GetLeaderResponse) msg;
+                    updateLeader(groupId, response.getLeaderId());
+                    return Status.OK();
+                }
+            } catch (final TimeoutException e) {
+                timeoutException = e;
+            } catch (final ExecutionException e) {
+                if (st.isOk()) {
+                    st.setError(-1, e.getMessage());
+                } else {
+                    final String savedMsg = st.getErrorMsg();
+                    st.setError(-1, "%s, %s", savedMsg, e.getMessage());
+                }
+            }
+        }
+        if (timeoutException != null) {
+            throw timeoutException;
+        }
+
+        return st;
+    }
+
+    public Status refreshConfiguration(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);
+
+        final Configuration conf = getConfiguration(groupId);
+        if (conf == null) {
+            return new Status(RaftError.ENOENT,
+                "Group %s is not registered in RouteTable, forgot to call updateConfiguration?", groupId);
+        }
+        final Status st = Status.OK();
+        PeerId leaderId = selectLeader(groupId);
+        if (leaderId == null) {
+            refreshLeader(cliClientService, groupId, timeoutMs);
+            leaderId = selectLeader(groupId);
+        }
+        if (leaderId == null) {
+            st.setError(-1, "Fail to get leader of group %s", groupId);
+            return st;
+        }
+        if (!cliClientService.connect(leaderId.getEndpoint())) {
+            st.setError(-1, "Fail to init channel to %s", leaderId);
+            return st;
+        }
+        final CliRequests.GetPeersRequest.Builder rb = CliRequests.GetPeersRequest.newBuilder();
+        rb.setGroupId(groupId);
+        rb.setLeaderId(leaderId.toString());
+        try {
+            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();
+                for (final String peerIdStr : resp.getPeersList()) {
+                    final PeerId newPeer = new PeerId();
+                    newPeer.parse(peerIdStr);
+                    newConf.addPeer(newPeer);
+                }
+                if (!conf.equals(newConf)) {
+                    LOG.info("Configuration of replication group {} changed from {} to {}", groupId, conf, newConf);
+                }
+                updateConfiguration(groupId, newConf);
+            } else {
+                final RpcRequests.ErrorResponse resp = (RpcRequests.ErrorResponse) result;
+                st.setError(resp.getErrorCode(), resp.getErrorMsg());
+            }
+        } catch (final Exception e) {
+            st.setError(-1, e.getMessage());
+        }
+        return st;
+    }
+
+    /**
+     * Reset the states.
+     */
+    public void reset() {
+        this.groupConfTable.clear();
+    }
+
+    /**
+     * Remove the group from route table.
+     *
+     * @param groupId raft group id
+     * @return true on success
+     */
+    public boolean removeGroup(final String groupId) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+
+        return this.groupConfTable.remove(groupId) != null;
+    }
+
+    @Override
+    public String toString() {
+        return "RouteTable{" + "groupConfTable=" + groupConfTable + '}';
+    }
+
+    private RouteTable() {
+    }
+
+    @Override
+    public void describe(final Printer out) {
+        out.println("RouteTable:") //
+            .print("  ") //
+            .println(toString());
+    }
+
+    private static class GroupConf {
+
+        private final StampedLock stampedLock = new StampedLock();
+
+        private Configuration     conf;
+        private PeerId            leader;
+
+        @Override
+        public String toString() {
+            return "GroupConf{" + "conf=" + conf + ", leader=" + leader + '}';
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/StateMachine.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/StateMachine.java
new file mode 100644
index 0000000..ccf1aa2
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/StateMachine.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.entity.LeaderChangeContext;
+import com.alipay.sofa.jraft.error.RaftException;
+import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader;
+import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter;
+
+/**
+ * |StateMachine| is the sink of all the events of a very raft node.
+ * Implement a specific StateMachine for your own business logic.
+ * NOTE: All the interfaces are not guaranteed to be thread safe and they are
+ * called sequentially, saying that every single operation will block all the
+ * following ones.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-08 5:43:21 PM
+ */
+public interface StateMachine {
+
+    /**
+     * Update the StateMachine with a batch a tasks that can be accessed
+     * through |iterator|.
+     *
+     * Invoked when one or more tasks that were passed to Node#apply(Task) have been
+     * committed to the raft group (quorum of the group peers have received
+     * those tasks and stored them on the backing storage).
+     *
+     * Once this function returns to the caller, we will regard all the iterated
+     * tasks through |iter| have been successfully applied. And if you didn't
+     * apply all the the given tasks, we would regard this as a critical error
+     * and report a error whose type is ERROR_TYPE_STATE_MACHINE.
+     *
+     * @param iter iterator of states
+     */
+    void onApply(final Iterator iter);
+
+    /**
+     * Invoked once when the raft node was shut down.
+     * Default do nothing
+     */
+    void onShutdown();
+
+    /**
+     * User defined snapshot generate function, this method will block StateMachine#onApply(Iterator).
+     * user can make snapshot async when fsm can be cow(copy-on-write).
+     * call done.run(status) when snapshot finished.
+     * Default: Save nothing and returns error.
+     *
+     * @param writer snapshot writer
+     * @param done   callback
+     */
+    void onSnapshotSave(final SnapshotWriter writer, final Closure done);
+
+    /**
+     * User defined snapshot load function
+     * get and load snapshot
+     * Default: Load nothing and returns error.
+     *
+     * @param reader snapshot reader
+     * @return true on success
+     */
+    boolean onSnapshotLoad(final SnapshotReader reader);
+
+    /**
+     * Invoked when the belonging node becomes the leader of the group at |term|
+     * Default: Do nothing
+     *
+     * @param term new term num
+     */
+    void onLeaderStart(final long term);
+
+    /**
+     * Invoked when this node steps down from the leader of the replication
+     * group and |status| describes detailed information
+     *
+     * @param status status info
+     */
+    void onLeaderStop(final Status status);
+
+    /**
+     * This method is called when a critical error was encountered, after this
+     * point, no any further modification is allowed to applied to this node
+     * until the error is fixed and this node restarts.
+     *
+     * @param e raft error message
+     */
+    void onError(final RaftException e);
+
+    /**
+     * Invoked when a configuration has been committed to the group.
+     *
+     * @param conf committed configuration
+     */
+    void onConfigurationCommitted(final Configuration conf);
+
+    /**
+     * This method is called when a follower stops following a leader and its leaderId becomes null,
+     * situations including:
+     * 1. handle election timeout and start preVote
+     * 2. receive requests with higher term such as VoteRequest from a candidate
+     *    or appendEntries request from a new leader
+     * 3. receive timeoutNow request from current leader and start request vote.
+     * 
+     * the parameter ctx gives the information(leaderId, term and status) about the
+     * very leader whom the follower followed before.
+     * User can reset the node's information as it stops following some leader.
+     *
+     * @param ctx context of leader change
+     */
+    void onStopFollowing(final LeaderChangeContext ctx);
+
+    /**
+     * This method is called when a follower or candidate starts following a leader and its leaderId
+     * (should be NULL before the method is called) is set to the leader's id,
+     * situations including:
+     * 1. a candidate receives appendEntries request from a leader
+     * 2. a follower(without leader) receives appendEntries from a leader
+     * 
+     * the parameter ctx gives the information(leaderId, term and status) about
+     * the very leader whom the follower starts to follow.
+     * User can reset the node's information as it starts to follow some leader.
+     *
+     * @param ctx context of leader change
+     */
+    void onStartFollowing(final LeaderChangeContext ctx);
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/Status.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/Status.java
new file mode 100644
index 0000000..559a093
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/Status.java
@@ -0,0 +1,233 @@
+/*
+ * 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;
+
+import com.alipay.sofa.jraft.error.RaftError;
+import com.alipay.sofa.jraft.util.Copiable;
+
+//A Status encapsulates the result of an operation. It may indicate success,
+
+//or it may indicate an error with an associated error message. It's suitable
+//for passing status of functions with richer information than just error_code
+//in exception-forbidden code. This utility is inspired by leveldb::Status.
+//
+//Multiple threads can invoke const methods on a Status without
+//external synchronization, but if any of the threads may call a
+//non-const method, all threads accessing the same Status must use
+//external synchronization.
+//
+//Since failed status needs to allocate memory, you should be careful when
+//failed status is frequent.
+public class Status implements Copiable<Status> {
+
+    /**
+     * Status internal state.
+     *
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-03 11:17:51 AM
+     */
+    private static class State {
+        /** error code */
+        int    code;
+        /** error msg*/
+        String msg;
+
+        State(int code, String msg) {
+            super();
+            this.code = code;
+            this.msg = msg;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + this.code;
+            result = prime * result + (this.msg == null ? 0 : this.msg.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            State other = (State) obj;
+            if (this.code != other.code) {
+                return false;
+            }
+            if (this.msg == null) {
+                return other.msg == null;
+            } else {
+                return this.msg.equals(other.msg);
+            }
+        }
+    }
+
+    private State state;
+
+    public Status() {
+        this.state = null;
+    }
+
+    /**
+     * Creates a OK status instance.
+     */
+    public static Status OK() {
+        return new Status();
+    }
+
+    public Status(Status s) {
+        if (s.state != null) {
+            this.state = new State(s.state.code, s.state.msg);
+        } else {
+            this.state = null;
+        }
+    }
+
+    public Status(RaftError raftError, String fmt, Object... args) {
+        this.state = new State(raftError.getNumber(), String.format(fmt, args));
+    }
+
+    public Status(int code, String fmt, Object... args) {
+        this.state = new State(code, String.format(fmt, args));
+    }
+
+    public Status(int code, String errorMsg) {
+        this.state = new State(code, errorMsg);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (this.state == null ? 0 : this.state.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        Status other = (Status) obj;
+        if (this.state == null) {
+            return other.state == null;
+        } else {
+            return this.state.equals(other.state);
+        }
+    }
+
+    /**
+     * Reset status to be OK state.
+     */
+    public void reset() {
+        this.state = null;
+    }
+
+    /**
+     * Returns true when status is in OK state.
+     */
+    public boolean isOk() {
+        return this.state == null || this.state.code == 0;
+    }
+
+    /**
+     * Set error code.
+     */
+    public void setCode(int code) {
+        if (this.state == null) {
+            this.state = new State(code, null);
+        } else {
+            this.state.code = code;
+        }
+    }
+
+    /**
+     * Get error code.
+     */
+    public int getCode() {
+        return this.state == null ? 0 : this.state.code;
+    }
+
+    /**
+     * Get raft error from error code.
+     */
+    public RaftError getRaftError() {
+        return this.state == null ? RaftError.SUCCESS : RaftError.forNumber(this.state.code);
+    }
+
+    /**
+     * Set error msg
+     */
+    public void setErrorMsg(String errMsg) {
+        if (this.state == null) {
+            this.state = new State(0, errMsg);
+        } else {
+            this.state.msg = errMsg;
+        }
+    }
+
+    /**
+     * Set error code and error msg.
+     */
+    public void setError(int code, String fmt, Object... args) {
+        this.state = new State(code, String.format(String.valueOf(fmt), args));
+    }
+
+    /**
+     * Set raft error and error msg.
+     */
+    public void setError(RaftError error, String fmt, Object... args) {
+        this.state = new State(error.getNumber(), String.format(String.valueOf(fmt), args));
+    }
+
+    @Override
+    public String toString() {
+        if (isOk()) {
+            return "Status[OK]";
+        } else {
+            return "Status[" + RaftError.describeCode(this.state.code) + "<" + this.state.code + ">: " + this.state.msg
+                   + "]";
+        }
+    }
+
+    @Override
+    public Status copy() {
+        return new Status(this.getCode(), this.getErrorMsg());
+    }
+
+    /**
+     * Get the error msg.
+     */
+    public String getErrorMsg() {
+        return this.state == null ? null : this.state.msg;
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/ThreadPoolMetricsSignalHandler.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/ThreadPoolMetricsSignalHandler.java
new file mode 100644
index 0000000..6e89667
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/ThreadPoolMetricsSignalHandler.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.util.FileOutputSignalHandler;
+import com.alipay.sofa.jraft.util.MetricReporter;
+import com.alipay.sofa.jraft.util.SystemPropertyUtil;
+import com.alipay.sofa.jraft.util.ThreadPoolMetricRegistry;
+
+/**
+ *
+ * @author jiachun.fjc
+ */
+public class ThreadPoolMetricsSignalHandler extends FileOutputSignalHandler {
+
+    private static Logger       LOG       = LoggerFactory.getLogger(ThreadPoolMetricsSignalHandler.class);
+
+    private static final String DIR       = SystemPropertyUtil.get("jraft.signal.thread.pool.metrics.dir", "");
+    private static final String BASE_NAME = "thread_pool_metrics.log";
+
+    @Override
+    public void handle(final String signalName) {
+        try {
+            final File file = getOutputFile(DIR, BASE_NAME);
+
+            LOG.info("Printing thread pools metrics with signal: {} to file: {}.", signalName, file);
+
+            try (final PrintStream out = new PrintStream(new FileOutputStream(file, true))) {
+                MetricReporter.forRegistry(ThreadPoolMetricRegistry.metricRegistry()) //
+                    .outputTo(out) //
+                    .build() //
+                    .report();
+            }
+        } catch (final IOException e) {
+            LOG.error("Fail to print thread pools metrics.", e);
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/CatchUpClosure.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/CatchUpClosure.java
new file mode 100644
index 0000000..54ea24c
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/CatchUpClosure.java
@@ -0,0 +1,72 @@
+/*
+ * 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.closure;
+
+import java.util.concurrent.ScheduledFuture;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.Status;
+
+/**
+ * A catchup closure for peer to catch up.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ * <p>
+ * 2018-Apr-04 2:15:05 PM
+ */
+public abstract class CatchUpClosure implements Closure {
+
+    private long maxMargin;
+    private ScheduledFuture<?> timer;
+    private boolean hasTimer;
+    private boolean errorWasSet;
+
+    private final Status status = Status.OK();
+
+    public Status getStatus() {
+        return this.status;
+    }
+
+    public long getMaxMargin() {
+        return this.maxMargin;
+    }
+
+    public void setMaxMargin(long maxMargin) {
+        this.maxMargin = maxMargin;
+    }
+
+    public ScheduledFuture<?> getTimer() {
+        return this.timer;
+    }
+
+    public void setTimer(ScheduledFuture<?> timer) {
+        this.timer = timer;
+        this.hasTimer = true;
+    }
+
+    public boolean hasTimer() {
+        return this.hasTimer;
+    }
+
+    public boolean isErrorWasSet() {
+        return this.errorWasSet;
+    }
+
+    public void setErrorWasSet(boolean errorWasSet) {
+        this.errorWasSet = errorWasSet;
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ClosureQueue.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ClosureQueue.java
new file mode 100644
index 0000000..5aeded5
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ClosureQueue.java
@@ -0,0 +1,80 @@
+/*
+ * 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.closure;
+
+import java.util.List;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+import com.alipay.sofa.jraft.Closure;
+
+/**
+ * A thread-safe closure queue.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Mar-14 10:29:12 AM
+ */
+@ThreadSafe
+public interface ClosureQueue {
+
+    /**
+     * Clear all closure in queue.
+     */
+    void clear();
+
+    /**
+     * Reset the first index in queue.
+     *
+     * @param firstIndex the first index of queue
+     */
+    void resetFirstIndex(final long firstIndex);
+
+    /**
+     * Append a new closure into queue.
+     *
+     * @param closure the closure to append
+     */
+    void appendPendingClosure(final Closure closure);
+
+    /**
+     * Pop closure from queue until index(inclusion), returns the first
+     * popped out index, returns -1 when out of range, returns index+1
+     * when not found.
+     *
+     * @param endIndex     the index of queue
+     * @param closures     closure list
+     * @return returns the first popped out index, returns -1 when out
+     * of range, returns index+1
+     * when not found.
+     */
+    long popClosureUntil(final long endIndex, final List<Closure> closures);
+
+    /**
+     * Pop closure from queue until index(inclusion), returns the first
+     * popped out index, returns -1 when out of range, returns index+1
+     * when not found.
+     *
+     * @param endIndex     the index of queue
+     * @param closures     closure list
+     * @param taskClosures task closure list
+     * @return returns the first popped out index, returns -1 when out
+     * of range, returns index+1
+     * when not found.
+     */
+    long popClosureUntil(final long endIndex, final List<Closure> closures, final List<TaskClosure> taskClosures);
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ClosureQueueImpl.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ClosureQueueImpl.java
new file mode 100644
index 0000000..10e9ed0
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ClosureQueueImpl.java
@@ -0,0 +1,145 @@
+/*
+ * 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.closure;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.Status;
+import com.alipay.sofa.jraft.error.RaftError;
+import com.alipay.sofa.jraft.util.OnlyForTest;
+import com.alipay.sofa.jraft.util.Requires;
+import com.alipay.sofa.jraft.util.Utils;
+
+/**
+ * Closure queue implementation.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Mar-28 11:44:01 AM
+ */
+public class ClosureQueueImpl implements ClosureQueue {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ClosureQueueImpl.class);
+
+    private final Lock          lock;
+    private long                firstIndex;
+    private LinkedList<Closure> queue;
+
+    @OnlyForTest
+    public long getFirstIndex() {
+        return firstIndex;
+    }
+
+    @OnlyForTest
+    public LinkedList<Closure> getQueue() {
+        return queue;
+    }
+
+    public ClosureQueueImpl() {
+        super();
+        this.lock = new ReentrantLock();
+        this.firstIndex = 0;
+        this.queue = new LinkedList<>();
+    }
+
+    @Override
+    public void clear() {
+        List<Closure> savedQueue;
+        this.lock.lock();
+        try {
+            this.firstIndex = 0;
+            savedQueue = this.queue;
+            this.queue = new LinkedList<>();
+        } finally {
+            this.lock.unlock();
+        }
+
+        final Status status = new Status(RaftError.EPERM, "Leader stepped down");
+        Utils.runInThread(() -> {
+            for (final Closure done : savedQueue) {
+                if (done != null) {
+                    done.run(status);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void resetFirstIndex(final long firstIndex) {
+        this.lock.lock();
+        try {
+            Requires.requireTrue(this.queue.isEmpty(), "Queue is not empty.");
+            this.firstIndex = firstIndex;
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    @Override
+    public void appendPendingClosure(final Closure closure) {
+        this.lock.lock();
+        try {
+            this.queue.add(closure);
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    @Override
+    public long popClosureUntil(final long endIndex, final List<Closure> closures) {
+        return popClosureUntil(endIndex, closures, null);
+    }
+
+    @Override
+    public long popClosureUntil(final long endIndex, final List<Closure> closures, final List<TaskClosure> taskClosures) {
+        closures.clear();
+        if (taskClosures != null) {
+            taskClosures.clear();
+        }
+        this.lock.lock();
+        try {
+            final int queueSize = this.queue.size();
+            if (queueSize == 0 || endIndex < this.firstIndex) {
+                return endIndex + 1;
+            }
+            if (endIndex > this.firstIndex + queueSize - 1) {
+                LOG.error("Invalid endIndex={}, firstIndex={}, closureQueueSize={}", endIndex, this.firstIndex,
+                    queueSize);
+                return -1;
+            }
+            final long outFirstIndex = this.firstIndex;
+            for (long i = outFirstIndex; i <= endIndex; i++) {
+                final Closure closure = this.queue.pollFirst();
+                if (taskClosures != null && closure instanceof TaskClosure) {
+                    taskClosures.add((TaskClosure) closure);
+                }
+                closures.add(closure);
+            }
+            this.firstIndex = endIndex + 1;
+            return outFirstIndex;
+        } finally {
+            this.lock.unlock();
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/JoinableClosure.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/JoinableClosure.java
new file mode 100644
index 0000000..5099b47
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/JoinableClosure.java
@@ -0,0 +1,58 @@
+/*
+ * 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.closure;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.Status;
+import com.alipay.sofa.jraft.util.Requires;
+
+/**
+ * @author jiachun.fjc
+ */
+public class JoinableClosure implements Closure {
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final Closure        closure;
+
+    public JoinableClosure(Closure closure) {
+        this.closure = Requires.requireNonNull(closure, "closure");
+    }
+
+    @Override
+    public void run(final Status status) {
+        this.closure.run(status);
+        latch.countDown();
+    }
+
+    public void join() throws InterruptedException {
+        this.latch.await();
+    }
+
+    public void join(final long timeoutMillis) throws InterruptedException, TimeoutException {
+        if (!this.latch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
+            throw new TimeoutException("joined timeout");
+        }
+    }
+
+    public Closure getClosure() {
+        return closure;
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/LoadSnapshotClosure.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/LoadSnapshotClosure.java
new file mode 100644
index 0000000..496d1df
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/LoadSnapshotClosure.java
@@ -0,0 +1,37 @@
+/*
+ * 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.closure;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader;
+
+/**
+ * Load snapshot closure
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-04 2:20:09 PM
+ */
+public interface LoadSnapshotClosure extends Closure {
+
+    /**
+     * Start to load snapshot, returns a snapshot reader.
+     *
+     * @return a snapshot reader.
+     */
+    SnapshotReader start();
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ReadIndexClosure.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ReadIndexClosure.java
new file mode 100644
index 0000000..9bd23c6
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/ReadIndexClosure.java
@@ -0,0 +1,166 @@
+/*
+ * 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.closure;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.JRaftUtils;
+import com.alipay.sofa.jraft.Node;
+import com.alipay.sofa.jraft.Status;
+import com.alipay.sofa.jraft.error.RaftError;
+import com.alipay.sofa.jraft.util.SystemPropertyUtil;
+import com.alipay.sofa.jraft.util.timer.Timeout;
+import com.alipay.sofa.jraft.util.timer.Timer;
+import com.alipay.sofa.jraft.util.timer.TimerTask;
+
+/**
+ * Read index closure
+ *
+ * @author dennis
+ */
+public abstract class ReadIndexClosure implements Closure {
+
+    private static final Logger                                      LOG               = LoggerFactory
+                                                                                           .getLogger(ReadIndexClosure.class);
+
+    private static final AtomicIntegerFieldUpdater<ReadIndexClosure> STATE_UPDATER     = AtomicIntegerFieldUpdater
+                                                                                           .newUpdater(
+                                                                                               ReadIndexClosure.class,
+                                                                                               "state");
+
+    private static final long                                        DEFAULT_TIMEOUT   = SystemPropertyUtil.getInt(
+                                                                                           "jraft.read-index.timeout",
+                                                                                           2 * 1000);
+
+    private static final int                                         PENDING           = 0;
+    private static final int                                         COMPLETE          = 1;
+    private static final int                                         TIMEOUT           = 2;
+
+    /**
+     * Invalid log index -1.
+     */
+    public static final long                                         INVALID_LOG_INDEX = -1;
+
+    private long                                                     index             = INVALID_LOG_INDEX;
+    private byte[]                                                   requestContext;
+
+    private volatile int                                             state             = PENDING;
+
+    public ReadIndexClosure() {
+        this(DEFAULT_TIMEOUT);
+    }
+
+    /**
+     * Create a read-index closure with a timeout parameter.
+     *
+     * @param timeoutMs timeout millis
+     */
+    public ReadIndexClosure(long timeoutMs) {
+        if (timeoutMs >= 0) {
+            // Lazy to init the timer
+            TimeoutScanner.TIMER.newTimeout(new TimeoutTask(this), timeoutMs, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    /**
+     * Called when ReadIndex can be executed.
+     *
+     * @param status the readIndex status.
+     * @param index  the committed index when starts readIndex.
+     * @param reqCtx the request context passed by {@link Node#readIndex(byte[], ReadIndexClosure)}.
+     * @see Node#readIndex(byte[], ReadIndexClosure)
+     */
+    public abstract void run(final Status status, final long index, final byte[] reqCtx);
+
+    /**
+     * Set callback result, called by jraft.
+     *
+     * @param index  the committed index.
+     * @param reqCtx the request context passed by {@link Node#readIndex(byte[], ReadIndexClosure)}.
+     */
+    public void setResult(final long index, final byte[] reqCtx) {
+        this.index = index;
+        this.requestContext = reqCtx;
+    }
+
+    /**
+     * The committed log index when starts readIndex request. return -1 if fails.
+     *
+     * @return returns the committed index.  returns -1 if fails.
+     */
+    public long getIndex() {
+        return this.index;
+    }
+
+    /**
+     * Returns the request context.
+     *
+     * @return the request context.
+     */
+    public byte[] getRequestContext() {
+        return this.requestContext;
+    }
+
+    @Override
+    public void run(final Status status) {
+        if (!STATE_UPDATER.compareAndSet(this, PENDING, COMPLETE)) {
+            LOG.warn("A timeout read-index response finally returned: {}.", status);
+            return;
+        }
+
+        try {
+            run(status, this.index, this.requestContext);
+        } catch (final Throwable t) {
+            LOG.error("Fail to run ReadIndexClosure with status: {}.", status, t);
+        }
+    }
+
+    static class TimeoutTask implements TimerTask {
+
+        private final ReadIndexClosure closure;
+
+        TimeoutTask(ReadIndexClosure closure) {
+            this.closure = closure;
+        }
+
+        @Override
+        public void run(final Timeout timeout) throws Exception {
+            if (!STATE_UPDATER.compareAndSet(this.closure, PENDING, TIMEOUT)) {
+                return;
+            }
+
+            final Status status = new Status(RaftError.ETIMEDOUT, "read-index request timeout");
+            try {
+                this.closure.run(status, INVALID_LOG_INDEX, null);
+            } catch (final Throwable t) {
+                LOG.error("[Timeout] fail to run ReadIndexClosure with status: {}.", status, t);
+            }
+        }
+    }
+
+    /**
+     * Lazy to create a timer
+     */
+    static class TimeoutScanner {
+        private static final Timer TIMER = JRaftUtils.raftTimerFactory().createTimer("read-index.timeout.scanner");
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/SaveSnapshotClosure.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/SaveSnapshotClosure.java
new file mode 100644
index 0000000..4b1b9b6
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/SaveSnapshotClosure.java
@@ -0,0 +1,39 @@
+/*
+ * 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.closure;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.entity.RaftOutter.SnapshotMeta;
+import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter;
+
+/**
+ * Save snapshot closure
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-04 2:21:30 PM
+ */
+public interface SaveSnapshotClosure extends Closure {
+
+    /**
+     * Starts to save snapshot, returns the writer.
+     *
+     * @param meta metadata of snapshot.
+     * @return returns snapshot writer.
+     */
+    SnapshotWriter start(final SnapshotMeta meta);
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/SynchronizedClosure.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/SynchronizedClosure.java
new file mode 100644
index 0000000..c1d2c2a
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/SynchronizedClosure.java
@@ -0,0 +1,83 @@
+/*
+ * 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.closure;
+
+import java.util.concurrent.CountDownLatch;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.Status;
+
+/**
+ * A special Closure which provides synchronization primitives.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Mar-16 2:45:34 PM
+ */
+public class SynchronizedClosure implements Closure {
+
+    private CountDownLatch  latch;
+    private volatile Status status;
+    /**
+     * Latch count to reset
+     */
+    private int             count;
+
+    public SynchronizedClosure() {
+        this(1);
+    }
+
+    public SynchronizedClosure(final int n) {
+        this.count = n;
+        this.latch = new CountDownLatch(n);
+    }
+
+    /**
+     * Get last ran status
+     *
+     * @return returns the last ran status
+     */
+    public Status getStatus() {
+        return this.status;
+    }
+
+    @Override
+    public void run(final Status status) {
+        this.status = status;
+        this.latch.countDown();
+    }
+
+    /**
+     * Wait for closure run
+     *
+     * @return status
+     * @throws InterruptedException if the current thread is interrupted
+     *                              while waiting
+     */
+    public Status await() throws InterruptedException {
+        this.latch.await();
+        return this.status;
+    }
+
+    /**
+     * Reset the closure
+     */
+    public void reset() {
+        this.status = null;
+        this.latch = new CountDownLatch(this.count);
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/TaskClosure.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/TaskClosure.java
new file mode 100644
index 0000000..23c1415
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/closure/TaskClosure.java
@@ -0,0 +1,35 @@
+/*
+ * 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.closure;
+
+import com.alipay.sofa.jraft.Closure;
+
+/**
+ * Closure for task applying.
+ * @author dennis
+ */
+public interface TaskClosure extends Closure {
+
+    /**
+     * Called when task is committed to majority peers of the
+     * RAFT group but before it is applied to state machine.
+     * 
+     * <strong>Note: user implementation should not block
+     * this method and throw any exceptions.</strong>
+     */
+    void onCommitted();
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/Configuration.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/Configuration.java
new file mode 100644
index 0000000..7175980
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/Configuration.java
@@ -0,0 +1,324 @@
+/*
+ * 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.conf;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.util.Copiable;
+import com.alipay.sofa.jraft.util.Requires;
+
+/**
+ * A configuration with a set of peers.
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Mar-15 11:00:26 AM
+ */
+public class Configuration implements Iterable<PeerId>, Copiable<Configuration> {
+
+    private static final Logger   LOG             = LoggerFactory.getLogger(Configuration.class);
+
+    private static final String   LEARNER_POSTFIX = "/learner";
+
+    private List<PeerId>          peers           = new ArrayList<>();
+
+    // use LinkedHashSet to keep insertion order.
+    private LinkedHashSet<PeerId> learners        = new LinkedHashSet<>();
+
+    public Configuration() {
+        super();
+    }
+
+    /**
+     * Construct a configuration instance with peers.
+     *
+     * @param conf configuration
+     */
+    public Configuration(final Iterable<PeerId> conf) {
+        this(conf, null);
+    }
+
+    /**
+     * Construct a configuration from another conf.
+     *
+     * @param conf configuration
+     */
+    public Configuration(final Configuration conf) {
+        this(conf.getPeers(), conf.getLearners());
+    }
+
+    /**
+     * Construct a Configuration instance with peers and learners.
+     *
+     * @param conf     peers configuration
+     * @param learners learners
+     * @since 1.3.0
+     */
+    public Configuration(final Iterable<PeerId> conf, final Iterable<PeerId> learners) {
+        Requires.requireNonNull(conf, "conf");
+        for (final PeerId peer : conf) {
+            this.peers.add(peer.copy());
+        }
+        addLearners(learners);
+    }
+
+    public void setLearners(final LinkedHashSet<PeerId> learners) {
+        this.learners = learners;
+    }
+
+    /**
+     * Add a learner peer.
+     *
+     * @param learner learner to add
+     * @return true when add successfully.
+     */
+    public boolean addLearner(final PeerId learner) {
+        return this.learners.add(learner);
+    }
+
+    /**
+     * Add learners in batch, returns the added count.
+     *
+     * @param learners learners to add
+     * @return the total added count
+     */
+    public int addLearners(final Iterable<PeerId> learners) {
+        int ret = 0;
+        if (learners != null) {
+            for (final PeerId peer : learners) {
+                if (this.learners.add(peer.copy())) {
+                    ret++;
+                }
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Remove a learner peer.
+     *
+     * @param learner learner to remove
+     * @return true when remove successfully.
+     */
+    public boolean removeLearner(final PeerId learner) {
+        return this.learners.remove(learner);
+    }
+
+    /**
+     * Retrieve the learners set.
+     *
+     * @return learners
+     */
+    public LinkedHashSet<PeerId> getLearners() {
+        return this.learners;
+    }
+
+    /**
+     * Retrieve the learners set copy.
+     *
+     * @return learners
+     */
+    public List<PeerId> listLearners() {
+        return new ArrayList<>(this.learners);
+    }
+
+    @Override
+    public Configuration copy() {
+        return new Configuration(this.peers, this.learners);
+    }
+
+    /**
+     * Returns true when the configuration is valid.
+     *
+     * @return true if the configuration is valid.
+     */
+    public boolean isValid() {
+        final Set<PeerId> intersection = new HashSet<>(this.peers);
+        intersection.retainAll(this.learners);
+        return !this.peers.isEmpty() && intersection.isEmpty();
+    }
+
+    public void reset() {
+        this.peers.clear();
+        this.learners.clear();
+    }
+
+    public boolean isEmpty() {
+        return this.peers.isEmpty();
+    }
+
+    /**
+     * Returns the peers total number.
+     *
+     * @return total num of peers
+     */
+    public int size() {
+        return this.peers.size();
+    }
+
+    @Override
+    public Iterator<PeerId> iterator() {
+        return this.peers.iterator();
+    }
+
+    public Set<PeerId> getPeerSet() {
+        return new HashSet<>(this.peers);
+    }
+
+    public List<PeerId> listPeers() {
+        return new ArrayList<>(this.peers);
+    }
+
+    public List<PeerId> getPeers() {
+        return this.peers;
+    }
+
+    public void setPeers(final List<PeerId> peers) {
+        this.peers.clear();
+        for (final PeerId peer : peers) {
+            this.peers.add(peer.copy());
+        }
+    }
+
+    public void appendPeers(final Collection<PeerId> set) {
+        this.peers.addAll(set);
+    }
+
+    public boolean addPeer(final PeerId peer) {
+        return this.peers.add(peer);
+    }
+
+    public boolean removePeer(final PeerId peer) {
+        return this.peers.remove(peer);
+    }
+
+    public boolean contains(final PeerId peer) {
+        return this.peers.contains(peer);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((this.learners == null) ? 0 : this.learners.hashCode());
+        result = prime * result + ((this.peers == null) ? 0 : this.peers.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        Configuration other = (Configuration) obj;
+        if (this.learners == null) {
+            if (other.learners != null) {
+                return false;
+            }
+        } else if (!this.learners.equals(other.learners)) {
+            return false;
+        }
+        if (this.peers == null) {
+            return other.peers == null;
+        } else {
+            return this.peers.equals(other.peers);
+        }
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        final List<PeerId> peers = listPeers();
+        int i = 0;
+        int size = peers.size();
+        for (final PeerId peer : peers) {
+            sb.append(peer);
+            if (i < size - 1 || !this.learners.isEmpty()) {
+                sb.append(",");
+            }
+            i++;
+        }
+
+        size = this.learners.size();
+        i = 0;
+        for (final PeerId peer : this.learners) {
+            sb.append(peer).append(LEARNER_POSTFIX);
+            if (i < size - 1) {
+                sb.append(",");
+            }
+            i++;
+        }
+
+        return sb.toString();
+    }
+
+    public boolean parse(final String conf) {
+        if (StringUtils.isBlank(conf)) {
+            return false;
+        }
+        reset();
+        final String[] peerStrs = StringUtils.split(conf, ',');
+        for (String peerStr : peerStrs) {
+            final PeerId peer = new PeerId();
+            int index;
+            boolean isLearner = false;
+            if ((index = peerStr.indexOf(LEARNER_POSTFIX)) > 0) {
+                // It's a learner
+                peerStr = peerStr.substring(0, index);
+                isLearner = true;
+            }
+            if (peer.parse(peerStr)) {
+                if (isLearner) {
+                    addLearner(peer);
+                } else {
+                    addPeer(peer);
+                }
+            } else {
+                LOG.error("Fail to parse peer {} in {}, ignore it.", peerStr, conf);
+            }
+        }
+        return true;
+    }
+
+    /**
+     *  Get the difference between |*this| and |rhs|
+     *  |included| would be assigned to |*this| - |rhs|
+     *  |excluded| would be assigned to |rhs| - |*this|
+     */
+    public void diff(final Configuration rhs, final Configuration included, final Configuration excluded) {
+        included.peers = new ArrayList<>(this.peers);
+        included.peers.removeAll(rhs.peers);
+        excluded.peers = new ArrayList<>(rhs.peers);
+        excluded.peers.removeAll(this.peers);
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationEntry.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationEntry.java
new file mode 100644
index 0000000..d13fd17
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationEntry.java
@@ -0,0 +1,129 @@
+/*
+ * 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.conf;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.entity.LogId;
+import com.alipay.sofa.jraft.entity.PeerId;
+
+/**
+ * A configuration entry with current peers and old peers.
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-04 2:25:06 PM
+ */
+public class ConfigurationEntry {
+
+    private static final Logger LOG     = LoggerFactory.getLogger(ConfigurationEntry.class);
+
+    private LogId               id      = new LogId(0, 0);
+    private Configuration       conf    = new Configuration();
+    private Configuration       oldConf = new Configuration();
+
+    public LogId getId() {
+        return this.id;
+    }
+
+    public void setId(final LogId id) {
+        this.id = id;
+    }
+
+    public Configuration getConf() {
+        return this.conf;
+    }
+
+    public void setConf(final Configuration conf) {
+        this.conf = conf;
+    }
+
+    public Configuration getOldConf() {
+        return this.oldConf;
+    }
+
+    public void setOldConf(final Configuration oldConf) {
+        this.oldConf = oldConf;
+    }
+
+    public ConfigurationEntry() {
+        super();
+    }
+
+    public ConfigurationEntry(final LogId id, final Configuration conf, final Configuration oldConf) {
+        super();
+        this.id = id;
+        this.conf = conf;
+        this.oldConf = oldConf;
+    }
+
+    public boolean isStable() {
+        return this.oldConf.isEmpty();
+    }
+
+    public boolean isEmpty() {
+        return this.conf.isEmpty();
+    }
+
+    public Set<PeerId> listPeers() {
+        final Set<PeerId> ret = new HashSet<>(this.conf.listPeers());
+        ret.addAll(this.oldConf.listPeers());
+        return ret;
+    }
+
+    /**
+     * Returns true when the conf entry is valid.
+     *
+     * @return if the the entry is valid
+     */
+    public boolean isValid() {
+        if (!this.conf.isValid()) {
+            return false;
+        }
+
+        // The peer set and learner set should not have intersection set.
+        final Set<PeerId> intersection = listPeers();
+        intersection.retainAll(listLearners());
+        if (intersection.isEmpty()) {
+            return true;
+        }
+        LOG.error("Invalid conf entry {}, peers and learners have intersection: {}.", this, intersection);
+        return false;
+    }
+
+    public Set<PeerId> listLearners() {
+        final Set<PeerId> ret = new HashSet<>(this.conf.getLearners());
+        ret.addAll(this.oldConf.getLearners());
+        return ret;
+    }
+
+    public boolean containsLearner(final PeerId learner) {
+        return this.conf.getLearners().contains(learner) || this.oldConf.getLearners().contains(learner);
+    }
+
+    public boolean contains(final PeerId peer) {
+        return this.conf.contains(peer) || this.oldConf.contains(peer);
+    }
+
+    @Override
+    public String toString() {
+        return "ConfigurationEntry [id=" + this.id + ", conf=" + this.conf + ", oldConf=" + this.oldConf + "]";
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationManager.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationManager.java
new file mode 100644
index 0000000..3337699
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/conf/ConfigurationManager.java
@@ -0,0 +1,110 @@
+/*
+ * 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.conf;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.util.Requires;
+
+/**
+ * Configuration manager
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ * <p>
+ * 2018-Apr-04 2:24:54 PM
+ */
+public class ConfigurationManager {
+
+    private static final Logger                  LOG            = LoggerFactory.getLogger(ConfigurationManager.class);
+
+    private final LinkedList<ConfigurationEntry> configurations = new LinkedList<>();
+    private ConfigurationEntry                   snapshot       = new ConfigurationEntry();
+
+    /**
+     * Adds a new conf entry.
+     */
+    public boolean add(final ConfigurationEntry entry) {
+        if (!this.configurations.isEmpty()) {
+            if (this.configurations.peekLast().getId().getIndex() >= entry.getId().getIndex()) {
+                LOG.error("Did you forget to call truncateSuffix before the last log index goes back.");
+                return false;
+            }
+        }
+        return this.configurations.add(entry);
+    }
+
+    /**
+     * [1, first_index_kept) are being discarded
+     */
+    public void truncatePrefix(final long firstIndexKept) {
+        while (!this.configurations.isEmpty() && this.configurations.peekFirst().getId().getIndex() < firstIndexKept) {
+            this.configurations.pollFirst();
+        }
+    }
+
+    /**
+     * (last_index_kept, infinity) are being discarded
+     */
+    public void truncateSuffix(final long lastIndexKept) {
+        while (!this.configurations.isEmpty() && this.configurations.peekLast().getId().getIndex() > lastIndexKept) {
+            this.configurations.pollLast();
+        }
+    }
+
+    public ConfigurationEntry getSnapshot() {
+        return this.snapshot;
+    }
+
+    public void setSnapshot(final ConfigurationEntry snapshot) {
+        this.snapshot = snapshot;
+    }
+
+    public ConfigurationEntry getLastConfiguration() {
+        if (this.configurations.isEmpty()) {
+            return snapshot;
+        } else {
+            return this.configurations.peekLast();
+        }
+    }
+
+    public ConfigurationEntry get(final 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());
+            return this.snapshot;
+        }
+        ListIterator<ConfigurationEntry> it = this.configurations.listIterator();
+        while (it.hasNext()) {
+            if (it.next().getId().getIndex() > lastIncludedIndex) {
+                it.previous();
+                break;
+            }
+        }
+        if (it.hasPrevious()) {
+            // find the first position that is less than or equal to lastIncludedIndex.
+            return it.previous();
+        } else {
+            // position not found position, return snapshot.
+            return this.snapshot;
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java
new file mode 100644
index 0000000..64d2bf9
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/BallotBox.java
@@ -0,0 +1,283 @@
+/*
+ * 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.core;
+
+import java.util.concurrent.locks.StampedLock;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.FSMCaller;
+import com.alipay.sofa.jraft.Lifecycle;
+import com.alipay.sofa.jraft.closure.ClosureQueue;
+import com.alipay.sofa.jraft.conf.Configuration;
+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.Describer;
+import com.alipay.sofa.jraft.util.OnlyForTest;
+import com.alipay.sofa.jraft.util.Requires;
+import com.alipay.sofa.jraft.util.SegmentList;
+
+/**
+ * Ballot box for voting.
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-04 2:32:10 PM
+ */
+@ThreadSafe
+public class BallotBox implements Lifecycle<BallotBoxOptions>, Describer {
+
+    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 SegmentList<Ballot> pendingMetaQueue   = new SegmentList<>(false);
+
+    @OnlyForTest
+    long getPendingIndex() {
+        return this.pendingIndex;
+    }
+
+    @OnlyForTest
+    SegmentList<Ballot> getPendingMetaQueue() {
+        return this.pendingMetaQueue;
+    }
+
+    public long getLastCommittedIndex() {
+        long stamp = this.stampedLock.tryOptimisticRead();
+        final long optimisticVal = this.lastCommittedIndex;
+        if (this.stampedLock.validate(stamp)) {
+            return optimisticVal;
+        }
+        stamp = this.stampedLock.readLock();
+        try {
+            return this.lastCommittedIndex;
+        } finally {
+            this.stampedLock.unlockRead(stamp);
+        }
+    }
+
+    @Override
+    public boolean init(final BallotBoxOptions opts) {
+        if (opts.getWaiter() == null || opts.getClosureQueue() == null) {
+            LOG.error("waiter or closure queue is null.");
+            return false;
+        }
+        this.waiter = opts.getWaiter();
+        this.closureQueue = opts.getClosureQueue();
+        return true;
+    }
+
+    /**
+     * Called by leader, otherwise the behavior is undefined
+     * Set logs in [first_log_index, last_log_index] are stable at |peer|.
+     */
+    public boolean commitAt(final long firstLogIndex, final long lastLogIndex, final PeerId peer) {
+        // TODO  use lock-free algorithm here?
+        final long stamp = this.stampedLock.writeLock();
+        long lastCommittedIndex = 0;
+        try {
+            if (this.pendingIndex == 0) {
+                return false;
+            }
+            if (lastLogIndex < this.pendingIndex) {
+                return true;
+            }
+
+            if (lastLogIndex >= this.pendingIndex + this.pendingMetaQueue.size()) {
+                throw new ArrayIndexOutOfBoundsException();
+            }
+
+            final long startAt = Math.max(this.pendingIndex, firstLogIndex);
+            Ballot.PosHint hint = new Ballot.PosHint();
+            for (long logIndex = startAt; logIndex <= lastLogIndex; logIndex++) {
+                final Ballot bl = this.pendingMetaQueue.get((int) (logIndex - this.pendingIndex));
+                hint = bl.grant(peer, hint);
+                if (bl.isGranted()) {
+                    lastCommittedIndex = logIndex;
+                }
+            }
+            if (lastCommittedIndex == 0) {
+                return true;
+            }
+            // When removing a peer off the raft group which contains even number of
+            // peers, the quorum would decrease by 1, e.g. 3 of 4 changes to 2 of 3. In
+            // this case, the log after removal may be committed before some previous
+            // 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
+            this.pendingMetaQueue.removeFromFirst((int) (lastCommittedIndex - this.pendingIndex) + 1);
+            LOG.debug("Committed log fromIndex={}, toIndex={}.", this.pendingIndex, lastCommittedIndex);
+            this.pendingIndex = lastCommittedIndex + 1;
+            this.lastCommittedIndex = lastCommittedIndex;
+        } finally {
+            this.stampedLock.unlockWrite(stamp);
+        }
+        this.waiter.onCommitted(lastCommittedIndex);
+        return true;
+    }
+
+    /**
+     * Called when the leader steps down, otherwise the behavior is undefined
+     * When a leader steps down, the uncommitted user applications should
+     * fail immediately, which the new leader will deal whether to commit or
+     * truncate.
+     */
+    public void clearPendingTasks() {
+        final long stamp = this.stampedLock.writeLock();
+        try {
+            this.pendingMetaQueue.clear();
+            this.pendingIndex = 0;
+            this.closureQueue.clear();
+        } finally {
+            this.stampedLock.unlockWrite(stamp);
+        }
+    }
+
+    /**
+     * Called when a candidate becomes the new leader, otherwise the behavior is
+     * undefined.
+     * According the the raft algorithm, the logs from previous terms can't be
+     * committed until a log at the new term becomes committed, so
+     * |newPendingIndex| should be |last_log_index| + 1.
+     * @param newPendingIndex pending index of new leader
+     * @return returns true if reset success
+     */
+    public boolean resetPendingIndex(final long newPendingIndex) {
+        final long stamp = this.stampedLock.writeLock();
+        try {
+            if (!(this.pendingIndex == 0 && this.pendingMetaQueue.isEmpty())) {
+                LOG.error("resetPendingIndex fail, pendingIndex={}, pendingMetaQueueSize={}.", this.pendingIndex,
+                    this.pendingMetaQueue.size());
+                return false;
+            }
+            if (newPendingIndex <= this.lastCommittedIndex) {
+                LOG.error("resetPendingIndex fail, newPendingIndex={}, lastCommittedIndex={}.", newPendingIndex,
+                    this.lastCommittedIndex);
+                return false;
+            }
+            this.pendingIndex = newPendingIndex;
+            this.closureQueue.resetFirstIndex(newPendingIndex);
+            return true;
+        } finally {
+            this.stampedLock.unlockWrite(stamp);
+        }
+    }
+
+    /**
+     * Called by leader, otherwise the behavior is undefined
+     * Store application context before replication.
+     *
+     * @param conf      current configuration
+     * @param oldConf   old configuration
+     * @param done      callback
+     * @return          returns true on success
+     */
+    public boolean appendPendingTask(final Configuration conf, final Configuration oldConf, final Closure done) {
+        final Ballot bl = new Ballot();
+        if (!bl.init(conf, oldConf)) {
+            LOG.error("Fail to init ballot.");
+            return false;
+        }
+        final long stamp = this.stampedLock.writeLock();
+        try {
+            if (this.pendingIndex <= 0) {
+                LOG.error("Fail to appendingTask, pendingIndex={}.", this.pendingIndex);
+                return false;
+            }
+            this.pendingMetaQueue.add(bl);
+            this.closureQueue.appendPendingClosure(done);
+            return true;
+        } finally {
+            this.stampedLock.unlockWrite(stamp);
+        }
+    }
+
+    /**
+     * Called by follower, otherwise the behavior is undefined.
+     * Set committed index received from leader
+     *
+     * @param lastCommittedIndex last committed index
+     * @return returns true if set success
+     */
+    public boolean setLastCommittedIndex(final long lastCommittedIndex) {
+        boolean doUnlock = true;
+        final long stamp = this.stampedLock.writeLock();
+        try {
+            if (this.pendingIndex != 0 || !this.pendingMetaQueue.isEmpty()) {
+                Requires.requireTrue(lastCommittedIndex < this.pendingIndex,
+                    "Node changes to leader, pendingIndex=%d, param lastCommittedIndex=%d", this.pendingIndex,
+                    lastCommittedIndex);
+                return false;
+            }
+            if (lastCommittedIndex < this.lastCommittedIndex) {
+                return false;
+            }
+            if (lastCommittedIndex > this.lastCommittedIndex) {
+                this.lastCommittedIndex = lastCommittedIndex;
+                this.stampedLock.unlockWrite(stamp);
+                doUnlock = false;
+                this.waiter.onCommitted(lastCommittedIndex);
+            }
+        } finally {
+            if (doUnlock) {
+                this.stampedLock.unlockWrite(stamp);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void shutdown() {
+        clearPendingTasks();
+    }
+
+    @Override
+    public void describe(final Printer out) {
+        long _lastCommittedIndex;
+        long _pendingIndex;
+        long _pendingMetaQueueSize;
+        long stamp = this.stampedLock.tryOptimisticRead();
+        if (this.stampedLock.validate(stamp)) {
+            _lastCommittedIndex = this.lastCommittedIndex;
+            _pendingIndex = this.pendingIndex;
+            _pendingMetaQueueSize = this.pendingMetaQueue.size();
+        } else {
+            stamp = this.stampedLock.readLock();
+            try {
+                _lastCommittedIndex = this.lastCommittedIndex;
+                _pendingIndex = this.pendingIndex;
+                _pendingMetaQueueSize = this.pendingMetaQueue.size();
+            } finally {
+                this.stampedLock.unlockRead(stamp);
+            }
+        }
+        out.print("  lastCommittedIndex: ") //
+            .println(_lastCommittedIndex);
+        out.print("  pendingIndex: ") //
+            .println(_pendingIndex);
+        out.print("  pendingMetaQueueSize: ") //
+            .println(_pendingMetaQueueSize);
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/CliServiceImpl.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/CliServiceImpl.java
new file mode 100644
index 0000000..ff14538
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/CliServiceImpl.java
@@ -0,0 +1,687 @@
+/*
+ * 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.core;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.CliService;
+import com.alipay.sofa.jraft.Status;
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.error.JRaftException;
+import com.alipay.sofa.jraft.error.RaftError;
+import com.alipay.sofa.jraft.option.CliOptions;
+import com.alipay.sofa.jraft.rpc.CliClientService;
+import com.alipay.sofa.jraft.rpc.CliRequests.AddLearnersRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.AddPeerRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.AddPeerResponse;
+import com.alipay.sofa.jraft.rpc.CliRequests.ChangePeersRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.ChangePeersResponse;
+import com.alipay.sofa.jraft.rpc.CliRequests.GetLeaderRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.GetLeaderResponse;
+import com.alipay.sofa.jraft.rpc.CliRequests.GetPeersRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.GetPeersResponse;
+import com.alipay.sofa.jraft.rpc.CliRequests.LearnersOpResponse;
+import com.alipay.sofa.jraft.rpc.CliRequests.RemoveLearnersRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.RemovePeerRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.RemovePeerResponse;
+import com.alipay.sofa.jraft.rpc.CliRequests.ResetLearnersRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.ResetPeerRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.SnapshotRequest;
+import com.alipay.sofa.jraft.rpc.CliRequests.TransferLeaderRequest;
+import com.alipay.sofa.jraft.rpc.RpcRequests.ErrorResponse;
+import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl;
+import com.alipay.sofa.jraft.util.Requires;
+import com.alipay.sofa.jraft.util.Utils;
+import com.alipay.sofa.jraft.rpc.Message;
+
+/**
+ * Cli service implementation.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ * @author jiachun.fjc
+ */
+public class CliServiceImpl implements CliService {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CliServiceImpl.class);
+
+    private CliOptions          cliOptions;
+    private CliClientService    cliClientService;
+
+    @Override
+    public synchronized boolean init(final CliOptions opts) {
+        Requires.requireNonNull(opts, "Null cli options");
+
+        if (this.cliClientService != null) {
+            return true;
+        }
+        this.cliOptions = opts;
+        this.cliClientService = new CliClientServiceImpl();
+        return this.cliClientService.init(this.cliOptions);
+    }
+
+    @Override
+    public synchronized void shutdown() {
+        if (this.cliClientService == null) {
+            return;
+        }
+        this.cliClientService.shutdown();
+        this.cliClientService = null;
+    }
+
+    @Override
+    public Status addPeer(final String groupId, final Configuration conf, final PeerId peer) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(conf, "Null configuration");
+        Requires.requireNonNull(peer, "Null peer");
+
+        final PeerId leaderId = new PeerId();
+        final Status st = getLeader(groupId, conf, leaderId);
+        if (!st.isOk()) {
+            return st;
+        }
+
+        if (!this.cliClientService.connect(leaderId.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to leader %s", leaderId);
+        }
+        final AddPeerRequest.Builder rb = AddPeerRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setLeaderId(leaderId.toString()) //
+            .setPeerId(peer.toString());
+
+        try {
+            final Message result = this.cliClientService.addPeer(leaderId.getEndpoint(), rb.build(), null).get();
+            if (result instanceof AddPeerResponse) {
+                final AddPeerResponse resp = (AddPeerResponse) result;
+                final Configuration oldConf = new Configuration();
+                for (final String peerIdStr : resp.getOldPeersList()) {
+                    final PeerId oldPeer = new PeerId();
+                    oldPeer.parse(peerIdStr);
+                    oldConf.addPeer(oldPeer);
+                }
+                final Configuration newConf = new Configuration();
+                for (final String peerIdStr : resp.getNewPeersList()) {
+                    final PeerId newPeer = new PeerId();
+                    newPeer.parse(peerIdStr);
+                    newConf.addPeer(newPeer);
+                }
+
+                LOG.info("Configuration of replication group {} changed from {} to {}.", groupId, oldConf, newConf);
+                return Status.OK();
+            } else {
+                return statusFromResponse(result);
+            }
+
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    private Status statusFromResponse(final Message result) {
+        final ErrorResponse resp = (ErrorResponse) result;
+        return new Status(resp.getErrorCode(), resp.getErrorMsg());
+    }
+
+    @Override
+    public Status removePeer(final String groupId, final Configuration conf, final PeerId peer) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(conf, "Null configuration");
+        Requires.requireNonNull(peer, "Null peer");
+        Requires.requireTrue(!peer.isEmpty(), "Removing peer is blank");
+
+        final PeerId leaderId = new PeerId();
+        final Status st = getLeader(groupId, conf, leaderId);
+        if (!st.isOk()) {
+            return st;
+        }
+
+        if (!this.cliClientService.connect(leaderId.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to leader %s", leaderId);
+        }
+
+        final RemovePeerRequest.Builder rb = RemovePeerRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setLeaderId(leaderId.toString()) //
+            .setPeerId(peer.toString());
+
+        try {
+            final Message result = this.cliClientService.removePeer(leaderId.getEndpoint(), rb.build(), null).get();
+            if (result instanceof RemovePeerResponse) {
+                final RemovePeerResponse resp = (RemovePeerResponse) result;
+                final Configuration oldConf = new Configuration();
+                for (final String peerIdStr : resp.getOldPeersList()) {
+                    final PeerId oldPeer = new PeerId();
+                    oldPeer.parse(peerIdStr);
+                    oldConf.addPeer(oldPeer);
+                }
+                final Configuration newConf = new Configuration();
+                for (final String peerIdStr : resp.getNewPeersList()) {
+                    final PeerId newPeer = new PeerId();
+                    newPeer.parse(peerIdStr);
+                    newConf.addPeer(newPeer);
+                }
+
+                LOG.info("Configuration of replication group {} changed from {} to {}", groupId, oldConf, newConf);
+                return Status.OK();
+            } else {
+                return statusFromResponse(result);
+
+            }
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    // TODO refactor addPeer/removePeer/changePeers/transferLeader, remove duplicated code.
+    @Override
+    public Status changePeers(final String groupId, final Configuration conf, final Configuration newPeers) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(conf, "Null configuration");
+        Requires.requireNonNull(newPeers, "Null new peers");
+
+        final PeerId leaderId = new PeerId();
+        final Status st = getLeader(groupId, conf, leaderId);
+        if (!st.isOk()) {
+            return st;
+        }
+
+        if (!this.cliClientService.connect(leaderId.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to leader %s", leaderId);
+        }
+
+        final ChangePeersRequest.Builder rb = ChangePeersRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setLeaderId(leaderId.toString());
+        for (final PeerId peer : newPeers) {
+            rb.addNewPeers(peer.toString());
+        }
+
+        try {
+            final Message result = this.cliClientService.changePeers(leaderId.getEndpoint(), rb.build(), null).get();
+            if (result instanceof ChangePeersResponse) {
+                final ChangePeersResponse resp = (ChangePeersResponse) result;
+                final Configuration oldConf = new Configuration();
+                for (final String peerIdStr : resp.getOldPeersList()) {
+                    final PeerId oldPeer = new PeerId();
+                    oldPeer.parse(peerIdStr);
+                    oldConf.addPeer(oldPeer);
+                }
+                final Configuration newConf = new Configuration();
+                for (final String peerIdStr : resp.getNewPeersList()) {
+                    final PeerId newPeer = new PeerId();
+                    newPeer.parse(peerIdStr);
+                    newConf.addPeer(newPeer);
+                }
+
+                LOG.info("Configuration of replication group {} changed from {} to {}", groupId, oldConf, newConf);
+                return Status.OK();
+            } else {
+                return statusFromResponse(result);
+
+            }
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    @Override
+    public Status resetPeer(final String groupId, final PeerId peerId, final Configuration newPeers) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(peerId, "Null peerId");
+        Requires.requireNonNull(newPeers, "Null new peers");
+
+        if (!this.cliClientService.connect(peerId.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to %s", peerId);
+        }
+
+        final ResetPeerRequest.Builder rb = ResetPeerRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setPeerId(peerId.toString());
+        for (final PeerId peer : newPeers) {
+            rb.addNewPeers(peer.toString());
+        }
+
+        try {
+            final Message result = this.cliClientService.resetPeer(peerId.getEndpoint(), rb.build(), null).get();
+            return statusFromResponse(result);
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    private void checkPeers(final Collection<PeerId> peers) {
+        for (final PeerId peer : peers) {
+            Requires.requireNonNull(peer, "Null peer in collection");
+        }
+    }
+
+    @Override
+    public Status addLearners(final String groupId, final Configuration conf, final List<PeerId> learners) {
+        checkLearnersOpParams(groupId, conf, learners);
+
+        final PeerId leaderId = new PeerId();
+        final Status st = getLeader(groupId, conf, leaderId);
+        if (!st.isOk()) {
+            return st;
+        }
+
+        if (!this.cliClientService.connect(leaderId.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to leader %s", leaderId);
+        }
+        final AddLearnersRequest.Builder rb = AddLearnersRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setLeaderId(leaderId.toString());
+        for (final PeerId peer : learners) {
+            rb.addLearners(peer.toString());
+        }
+
+        try {
+            final Message result = this.cliClientService.addLearners(leaderId.getEndpoint(), rb.build(), null).get();
+            return processLearnersOpResponse(groupId, result, "adding learners: %s", learners);
+
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    private void checkLearnersOpParams(final String groupId, final Configuration conf, final List<PeerId> learners) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(conf, "Null configuration");
+        Requires.requireTrue(learners != null && !learners.isEmpty(), "Empty peers");
+        checkPeers(learners);
+    }
+
+    private Status processLearnersOpResponse(final String groupId, final Message result, final String fmt,
+                                             final Object... formatArgs) {
+        if (result instanceof LearnersOpResponse) {
+            final LearnersOpResponse resp = (LearnersOpResponse) result;
+            final Configuration oldConf = new Configuration();
+            for (final String peerIdStr : resp.getOldLearnersList()) {
+                final PeerId oldPeer = new PeerId();
+                oldPeer.parse(peerIdStr);
+                oldConf.addLearner(oldPeer);
+            }
+            final Configuration newConf = new Configuration();
+            for (final String peerIdStr : resp.getNewLearnersList()) {
+                final PeerId newPeer = new PeerId();
+                newPeer.parse(peerIdStr);
+                newConf.addLearner(newPeer);
+            }
+
+            LOG.info("Learners of replication group {} changed from {} to {} after {}.", groupId, oldConf, newConf,
+                String.format(fmt, formatArgs));
+            return Status.OK();
+        } else {
+            return statusFromResponse(result);
+        }
+    }
+
+    @Override
+    public Status removeLearners(final String groupId, final Configuration conf, final List<PeerId> learners) {
+        checkLearnersOpParams(groupId, conf, learners);
+
+        final PeerId leaderId = new PeerId();
+        final Status st = getLeader(groupId, conf, leaderId);
+        if (!st.isOk()) {
+            return st;
+        }
+
+        if (!this.cliClientService.connect(leaderId.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to leader %s", leaderId);
+        }
+        final RemoveLearnersRequest.Builder rb = RemoveLearnersRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setLeaderId(leaderId.toString());
+        for (final PeerId peer : learners) {
+            rb.addLearners(peer.toString());
+        }
+
+        try {
+            final Message result = this.cliClientService.removeLearners(leaderId.getEndpoint(), rb.build(), null).get();
+            return processLearnersOpResponse(groupId, result, "removing learners: %s", learners);
+
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    @Override
+    public Status resetLearners(final String groupId, final Configuration conf, final List<PeerId> learners) {
+        checkLearnersOpParams(groupId, conf, learners);
+
+        final PeerId leaderId = new PeerId();
+        final Status st = getLeader(groupId, conf, leaderId);
+        if (!st.isOk()) {
+            return st;
+        }
+
+        if (!this.cliClientService.connect(leaderId.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to leader %s", leaderId);
+        }
+        final ResetLearnersRequest.Builder rb = ResetLearnersRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setLeaderId(leaderId.toString());
+        for (final PeerId peer : learners) {
+            rb.addLearners(peer.toString());
+        }
+
+        try {
+            final Message result = this.cliClientService.resetLearners(leaderId.getEndpoint(), rb.build(), null).get();
+            return processLearnersOpResponse(groupId, result, "resetting learners: %s", learners);
+
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    @Override
+    public Status transferLeader(final String groupId, final Configuration conf, final PeerId peer) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(conf, "Null configuration");
+        Requires.requireNonNull(peer, "Null peer");
+
+        final PeerId leaderId = new PeerId();
+        final Status st = getLeader(groupId, conf, leaderId);
+        if (!st.isOk()) {
+            return st;
+        }
+
+        if (!this.cliClientService.connect(leaderId.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to leader %s", leaderId);
+        }
+
+        final TransferLeaderRequest.Builder rb = TransferLeaderRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setLeaderId(leaderId.toString());
+        if (!peer.isEmpty()) {
+            rb.setPeerId(peer.toString());
+        }
+
+        try {
+            final Message result = this.cliClientService.transferLeader(leaderId.getEndpoint(), rb.build(), null).get();
+            return statusFromResponse(result);
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    @Override
+    public Status snapshot(final String groupId, final PeerId peer) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(peer, "Null peer");
+
+        if (!this.cliClientService.connect(peer.getEndpoint())) {
+            return new Status(-1, "Fail to init channel to %s", peer);
+        }
+
+        final SnapshotRequest.Builder rb = SnapshotRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setPeerId(peer.toString());
+
+        try {
+            final Message result = this.cliClientService.snapshot(peer.getEndpoint(), rb.build(), null).get();
+            return statusFromResponse(result);
+        } catch (final Exception e) {
+            return new Status(-1, e.getMessage());
+        }
+    }
+
+    @Override
+    public Status getLeader(final String groupId, final Configuration conf, final PeerId leaderId) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(leaderId, "Null leader id");
+
+        if (conf == null || conf.isEmpty()) {
+            return new Status(RaftError.EINVAL, "Empty group configuration");
+        }
+
+        final Status st = new Status(-1, "Fail to get leader of group %s", groupId);
+        for (final PeerId peer : conf) {
+            if (!this.cliClientService.connect(peer.getEndpoint())) {
+                LOG.error("Fail to connect peer {} to get leader for group {}.", peer, groupId);
+                continue;
+            }
+
+            final GetLeaderRequest.Builder rb = GetLeaderRequest.newBuilder() //
+                .setGroupId(groupId) //
+                .setPeerId(peer.toString());
+
+            final Future<Message> result = this.cliClientService.getLeader(peer.getEndpoint(), rb.build(), null);
+            try {
+
+                final Message msg = result.get(
+                    this.cliOptions.getTimeoutMs() <= 0 ? this.cliOptions.getRpcDefaultTimeout() : this.cliOptions
+                        .getTimeoutMs(), TimeUnit.MILLISECONDS);
+                if (msg instanceof ErrorResponse) {
+                    if (st.isOk()) {
+                        st.setError(-1, ((ErrorResponse) msg).getErrorMsg());
+                    } else {
+                        final String savedMsg = st.getErrorMsg();
+                        st.setError(-1, "%s, %s", savedMsg, ((ErrorResponse) msg).getErrorMsg());
+                    }
+                } else {
+                    final GetLeaderResponse response = (GetLeaderResponse) msg;
+                    if (leaderId.parse(response.getLeaderId())) {
+                        break;
+                    }
+                }
+            } catch (final Exception e) {
+                if (st.isOk()) {
+                    st.setError(-1, e.getMessage());
+                } else {
+                    final String savedMsg = st.getErrorMsg();
+                    st.setError(-1, "%s, %s", savedMsg, e.getMessage());
+                }
+            }
+        }
+
+        if (leaderId.isEmpty()) {
+            return st;
+        }
+        return Status.OK();
+    }
+
+    @Override
+    public List<PeerId> getPeers(final String groupId, final Configuration conf) {
+        return getPeers(groupId, conf, false, false);
+    }
+
+    @Override
+    public List<PeerId> getAlivePeers(final String groupId, final Configuration conf) {
+        return getPeers(groupId, conf, false, true);
+    }
+
+    @Override
+    public List<PeerId> getLearners(final String groupId, final Configuration conf) {
+        return getPeers(groupId, conf, true, false);
+    }
+
+    @Override
+    public List<PeerId> getAliveLearners(final String groupId, final Configuration conf) {
+        return getPeers(groupId, conf, true, true);
+    }
+
+    @Override
+    public Status rebalance(final Set<String> balanceGroupIds, final Configuration conf,
+                            final Map<String, PeerId> rebalancedLeaderIds) {
+        Requires.requireNonNull(balanceGroupIds, "Null balance group ids");
+        Requires.requireTrue(!balanceGroupIds.isEmpty(), "Empty balance group ids");
+        Requires.requireNonNull(conf, "Null configuration");
+        Requires.requireTrue(!conf.isEmpty(), "No peers of configuration");
+
+        LOG.info("Rebalance start with raft groups={}.", balanceGroupIds);
+
+        final long start = Utils.monotonicMs();
+        int transfers = 0;
+        Status failedStatus = null;
+        final Queue<String> groupDeque = new ArrayDeque<>(balanceGroupIds);
+        final LeaderCounter leaderCounter = new LeaderCounter(balanceGroupIds.size(), conf.size());
+        for (;;) {
+            final String groupId = groupDeque.poll();
+            if (groupId == null) { // well done
+                break;
+            }
+
+            final PeerId leaderId = new PeerId();
+            final Status leaderStatus = getLeader(groupId, conf, leaderId);
+            if (!leaderStatus.isOk()) {
+                failedStatus = leaderStatus;
+                break;
+            }
+
+            if (rebalancedLeaderIds != null) {
+                rebalancedLeaderIds.put(groupId, leaderId);
+            }
+
+            if (leaderCounter.incrementAndGet(leaderId) <= leaderCounter.getExpectedAverage()) {
+                // The num of leaders is less than the expected average, we are going to deal with others
+                continue;
+            }
+
+            // Find the target peer and try to transfer the leader to this peer
+            final PeerId targetPeer = findTargetPeer(leaderId, groupId, conf, leaderCounter);
+            if (!targetPeer.isEmpty()) {
+                final Status transferStatus = transferLeader(groupId, conf, targetPeer);
+                transfers++;
+                if (!transferStatus.isOk()) {
+                    // The failure of `transfer leader` usually means the node is busy,
+                    // so we return failure status and should try `rebalance` again later.
+                    failedStatus = transferStatus;
+                    break;
+                }
+
+                LOG.info("Group {} transfer leader to {}.", groupId, targetPeer);
+                leaderCounter.decrementAndGet(leaderId);
+                groupDeque.add(groupId);
+                if (rebalancedLeaderIds != null) {
+                    rebalancedLeaderIds.put(groupId, targetPeer);
+                }
+            }
+        }
+
+        final Status status = failedStatus != null ? failedStatus : Status.OK();
+        if (LOG.isInfoEnabled()) {
+            LOG.info(
+                "Rebalanced raft groups={}, status={}, number of transfers={}, elapsed time={} ms, rebalanced result={}.",
+                balanceGroupIds, status, transfers, Utils.monotonicMs() - start, rebalancedLeaderIds);
+        }
+        return status;
+    }
+
+    private PeerId findTargetPeer(final PeerId self, final String groupId, final Configuration conf,
+                                  final LeaderCounter leaderCounter) {
+        for (final PeerId peerId : getAlivePeers(groupId, conf)) {
+            if (peerId.equals(self)) {
+                continue;
+            }
+            if (leaderCounter.get(peerId) >= leaderCounter.getExpectedAverage()) {
+                continue;
+            }
+            return peerId;
+        }
+        return PeerId.emptyPeer();
+    }
+
+    private List<PeerId> getPeers(final String groupId, final Configuration conf, final boolean returnLearners,
+                                  final boolean onlyGetAlive) {
+        Requires.requireTrue(!StringUtils.isBlank(groupId), "Blank group id");
+        Requires.requireNonNull(conf, "Null conf");
+
+        final PeerId leaderId = new PeerId();
+        final Status st = getLeader(groupId, conf, leaderId);
+        if (!st.isOk()) {
+            throw new IllegalStateException(st.getErrorMsg());
+        }
+
+        if (!this.cliClientService.connect(leaderId.getEndpoint())) {
+            throw new IllegalStateException("Fail to init channel to leader " + leaderId);
+        }
+
+        final GetPeersRequest.Builder rb = GetPeersRequest.newBuilder() //
+            .setGroupId(groupId) //
+            .setLeaderId(leaderId.toString()) //
+            .setOnlyAlive(onlyGetAlive);
+
+        try {
+            final Message result = this.cliClientService.getPeers(leaderId.getEndpoint(), rb.build(), null).get(
+                this.cliOptions.getTimeoutMs() <= 0 ? this.cliOptions.getRpcDefaultTimeout()
+                    : this.cliOptions.getTimeoutMs(), TimeUnit.MILLISECONDS);
+            if (result instanceof GetPeersResponse) {
+                final GetPeersResponse resp = (GetPeersResponse) result;
+                final List<PeerId> peerIdList = new ArrayList<>();
+                final List<java.lang.String> responsePeers = returnLearners ? resp.getLearnersList() : resp.getPeersList();
+                for (final String peerIdStr : responsePeers) {
+                    final PeerId newPeer = new PeerId();
+                    newPeer.parse(peerIdStr);
+                    peerIdList.add(newPeer);
+                }
+                return peerIdList;
+            } else {
+                final ErrorResponse resp = (ErrorResponse) result;
+                throw new JRaftException(resp.getErrorMsg());
+            }
+        } catch (final JRaftException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new JRaftException(e);
+        }
+    }
+
+    public CliClientService getCliClientService() {
+        return this.cliClientService;
+    }
+
+    private static class LeaderCounter {
+
+        private final Map<PeerId, Integer> counter = new HashMap<>();
+        // The expected average leader number on every peerId
+        private final int                  expectedAverage;
+
+        public LeaderCounter(final int groupCount, final int peerCount) {
+            this.expectedAverage = (int) Math.ceil((double) groupCount / peerCount);
+        }
+
+        public int getExpectedAverage() {
+            return this.expectedAverage;
+        }
+
+        public int incrementAndGet(final PeerId peerId) {
+            return this.counter.compute(peerId, (ignored, num) -> num == null ? 1 : num + 1);
+        }
+
+        public int decrementAndGet(final PeerId peerId) {
+            return this.counter.compute(peerId, (ignored, num) -> num == null ? 0 : num - 1);
+        }
+
+        public int get(final PeerId peerId) {
+            return this.counter.getOrDefault(peerId, 0);
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/DefaultJRaftServiceFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/DefaultJRaftServiceFactory.java
new file mode 100644
index 0000000..5707ba5
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/DefaultJRaftServiceFactory.java
@@ -0,0 +1,69 @@
+/*
+ * 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.core;
+
+import com.alipay.sofa.jraft.entity.codec.v1.LogEntryV1CodecFactory;
+import org.apache.commons.lang.StringUtils;
+
+import com.alipay.sofa.jraft.JRaftServiceFactory;
+import com.alipay.sofa.jraft.entity.codec.LogEntryCodecFactory;
+import com.alipay.sofa.jraft.option.RaftOptions;
+import com.alipay.sofa.jraft.storage.LogStorage;
+import com.alipay.sofa.jraft.storage.RaftMetaStorage;
+import com.alipay.sofa.jraft.storage.SnapshotStorage;
+import com.alipay.sofa.jraft.storage.impl.LocalRaftMetaStorage;
+import com.alipay.sofa.jraft.storage.impl.RocksDBLogStorage;
+import com.alipay.sofa.jraft.storage.snapshot.local.LocalSnapshotStorage;
+import com.alipay.sofa.jraft.util.Requires;
+import com.alipay.sofa.jraft.util.SPI;
+
+/**
+ * The default factory for JRaft services.
+ * @author boyan(boyan@antfin.com)
+ * @since 1.2.6
+ *
+ */
+@SPI
+public class DefaultJRaftServiceFactory implements JRaftServiceFactory {
+
+    public static DefaultJRaftServiceFactory newInstance() {
+        return new DefaultJRaftServiceFactory();
+    }
+
+    @Override
+    public LogStorage createLogStorage(final String uri, final RaftOptions raftOptions) {
+        Requires.requireTrue(StringUtils.isNotBlank(uri), "Blank log storage uri.");
+        return new RocksDBLogStorage(uri, raftOptions);
+    }
+
+    @Override
+    public SnapshotStorage createSnapshotStorage(final String uri, final RaftOptions raftOptions) {
+        Requires.requireTrue(!StringUtils.isBlank(uri), "Blank snapshot storage uri.");
+        return new LocalSnapshotStorage(uri, raftOptions);
+    }
+
+    @Override
+    public RaftMetaStorage createRaftMetaStorage(final String uri, final RaftOptions raftOptions) {
+        Requires.requireTrue(!StringUtils.isBlank(uri), "Blank raft meta storage uri.");
+        return new LocalRaftMetaStorage(uri, raftOptions);
+    }
+
+    @Override
+    public LogEntryCodecFactory createLogEntryCodecFactory() {
+        return LogEntryV1CodecFactory.getInstance();
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/ElectionPriority.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/ElectionPriority.java
new file mode 100644
index 0000000..16cae25
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/ElectionPriority.java
@@ -0,0 +1,40 @@
+/*
+ * 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.core;
+
+/**
+ * ElectionPriority Type
+ *
+ * @author zongtanghu
+ */
+public class ElectionPriority {
+
+    /**
+     * Priority -1 represents this node disabled the priority election function.
+     */
+    public static final int Disabled   = -1;
+
+    /**
+     * Priority 0 is a special value so that a node will never participate in election.
+     */
+    public static final int NotElected = 0;
+
+    /**
+     * Priority 1 is a minimum value for priority election.
+     */
+    public static final int MinValue   = 1;
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/FSMCallerImpl.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/FSMCallerImpl.java
new file mode 100644
index 0000000..7aa2b28
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/FSMCallerImpl.java
@@ -0,0 +1,729 @@
+/*
+ * 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.core;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.FSMCaller;
+import com.alipay.sofa.jraft.StateMachine;
+import com.alipay.sofa.jraft.Status;
+import com.alipay.sofa.jraft.closure.ClosureQueue;
+import com.alipay.sofa.jraft.closure.LoadSnapshotClosure;
+import com.alipay.sofa.jraft.closure.SaveSnapshotClosure;
+import com.alipay.sofa.jraft.closure.TaskClosure;
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.conf.ConfigurationEntry;
+import com.alipay.sofa.jraft.entity.EnumOutter;
+import com.alipay.sofa.jraft.entity.EnumOutter.ErrorType;
+import com.alipay.sofa.jraft.entity.LeaderChangeContext;
+import com.alipay.sofa.jraft.entity.LogEntry;
+import com.alipay.sofa.jraft.entity.LogId;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.entity.RaftOutter;
+import com.alipay.sofa.jraft.error.RaftError;
+import com.alipay.sofa.jraft.error.RaftException;
+import com.alipay.sofa.jraft.option.FSMCallerOptions;
+import com.alipay.sofa.jraft.storage.LogManager;
+import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader;
+import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter;
+import com.alipay.sofa.jraft.util.DisruptorBuilder;
+import com.alipay.sofa.jraft.util.DisruptorMetricSet;
+import com.alipay.sofa.jraft.util.LogExceptionHandler;
+import com.alipay.sofa.jraft.util.NamedThreadFactory;
+import com.alipay.sofa.jraft.util.OnlyForTest;
+import com.alipay.sofa.jraft.util.Requires;
+import com.alipay.sofa.jraft.util.Utils;
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.EventFactory;
+import com.lmax.disruptor.EventHandler;
+import com.lmax.disruptor.EventTranslator;
+import com.lmax.disruptor.RingBuffer;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+
+/**
+ * The finite state machine caller implementation.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-03 11:12:14 AM
+ */
+public class FSMCallerImpl implements FSMCaller {
+
+    private static final Logger LOG = LoggerFactory.getLogger(FSMCallerImpl.class);
+
+    /**
+     * Task type
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-03 11:12:25 AM
+     */
+    private enum TaskType {
+        IDLE, //
+        COMMITTED, //
+        SNAPSHOT_SAVE, //
+        SNAPSHOT_LOAD, //
+        LEADER_STOP, //
+        LEADER_START, //
+        START_FOLLOWING, //
+        STOP_FOLLOWING, //
+        SHUTDOWN, //
+        FLUSH, //
+        ERROR;
+
+        private String metricName;
+
+        public String metricName() {
+            if (this.metricName == null) {
+                this.metricName = "fsm-" + name().toLowerCase().replaceAll("_", "-");
+            }
+            return this.metricName;
+        }
+    }
+
+    /**
+     * Apply task for disruptor.
+     *
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-03 11:12:35 AM
+     */
+    private static class ApplyTask {
+        TaskType            type;
+        // union fields
+        long                committedIndex;
+        long                term;
+        Status              status;
+        LeaderChangeContext leaderChangeCtx;
+        Closure             done;
+        CountDownLatch      shutdownLatch;
+
+        public void reset() {
+            this.type = null;
+            this.committedIndex = 0;
+            this.term = 0;
+            this.status = null;
+            this.leaderChangeCtx = null;
+            this.done = null;
+            this.shutdownLatch = null;
+        }
+    }
+
+    private static class ApplyTaskFactory implements EventFactory<ApplyTask> {
+
+        @Override
+        public ApplyTask newInstance() {
+            return new ApplyTask();
+        }
+    }
+
+    private class ApplyTaskHandler implements EventHandler<ApplyTask> {
+        // max committed index in current batch, reset to -1 every batch
+        private long maxCommittedIndex = -1;
+
+        @Override
+        public void onEvent(final ApplyTask event, final long sequence, final boolean endOfBatch) throws Exception {
+            this.maxCommittedIndex = runApplyTask(event, this.maxCommittedIndex, endOfBatch);
+        }
+    }
+
+    private LogManager                                              logManager;
+    private StateMachine                                            fsm;
+    private ClosureQueue                                            closureQueue;
+    private final AtomicLong                                        lastAppliedIndex;
+    private long                                                    lastAppliedTerm;
+    private Closure                                                 afterShutdown;
+    private NodeImpl                                                node;
+    private volatile TaskType                                       currTask;
+    private final AtomicLong                                        applyingIndex;
+    private volatile RaftException                                  error;
+    private Disruptor<ApplyTask>                                    disruptor;
+    private RingBuffer<ApplyTask>                                   taskQueue;
+    private volatile CountDownLatch                                 shutdownLatch;
+    private NodeMetrics                                             nodeMetrics;
+    private final CopyOnWriteArrayList<LastAppliedLogIndexListener> lastAppliedLogIndexListeners = new CopyOnWriteArrayList<>();
+
+    public FSMCallerImpl() {
+        super();
+        this.currTask = TaskType.IDLE;
+        this.lastAppliedIndex = new AtomicLong(0);
+        this.applyingIndex = new AtomicLong(0);
+    }
+
+    @Override
+    public boolean init(final FSMCallerOptions opts) {
+        this.logManager = opts.getLogManager();
+        this.fsm = opts.getFsm();
+        this.closureQueue = opts.getClosureQueue();
+        this.afterShutdown = opts.getAfterShutdown();
+        this.node = opts.getNode();
+        this.nodeMetrics = this.node.getNodeMetrics();
+        this.lastAppliedIndex.set(opts.getBootstrapId().getIndex());
+        notifyLastAppliedIndexUpdated(this.lastAppliedIndex.get());
+        this.lastAppliedTerm = opts.getBootstrapId().getTerm();
+        this.disruptor = DisruptorBuilder.<ApplyTask> newInstance() //
+            .setEventFactory(new ApplyTaskFactory()) //
+            .setRingBufferSize(opts.getDisruptorBufferSize()) //
+            .setThreadFactory(new NamedThreadFactory("JRaft-FSMCaller-Disruptor-", true)) //
+            .setProducerType(ProducerType.MULTI) //
+            .setWaitStrategy(new BlockingWaitStrategy()) //
+            .build();
+        this.disruptor.handleEventsWith(new ApplyTaskHandler());
+        this.disruptor.setDefaultExceptionHandler(new LogExceptionHandler<Object>(getClass().getSimpleName()));
+        this.taskQueue = this.disruptor.start();
+        if (this.nodeMetrics.getMetricRegistry() != null) {
+            this.nodeMetrics.getMetricRegistry().register("jraft-fsm-caller-disruptor",
+                new DisruptorMetricSet(this.taskQueue));
+        }
+        this.error = new RaftException(EnumOutter.ErrorType.ERROR_TYPE_NONE);
+        LOG.info("Starts FSMCaller successfully.");
+        return true;
+    }
+
+    @Override
+    public synchronized void shutdown() {
+        if (this.shutdownLatch != null) {
+            return;
+        }
+        LOG.info("Shutting down FSMCaller...");
+
+        if (this.taskQueue != null) {
+            final CountDownLatch latch = new CountDownLatch(1);
+            this.shutdownLatch = latch;
+            Utils.runInThread(() -> this.taskQueue.publishEvent((task, sequence) -> {
+                task.reset();
+                task.type = TaskType.SHUTDOWN;
+                task.shutdownLatch = latch;
+            }));
+        }
+        doShutdown();
+    }
+
+    @Override
+    public void addLastAppliedLogIndexListener(final LastAppliedLogIndexListener listener) {
+        this.lastAppliedLogIndexListeners.add(listener);
+    }
+
+    private boolean enqueueTask(final EventTranslator<ApplyTask> tpl) {
+        if (this.shutdownLatch != null) {
+            // Shutting down
+            LOG.warn("FSMCaller is stopped, can not apply new task.");
+            return false;
+        }
+        if (!this.taskQueue.tryPublishEvent(tpl)) {
+            setError(new RaftException(ErrorType.ERROR_TYPE_STATE_MACHINE, new Status(RaftError.EBUSY,
+                "FSMCaller is overload.")));
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onCommitted(final long committedIndex) {
+        return enqueueTask((task, sequence) -> {
+            task.type = TaskType.COMMITTED;
+            task.committedIndex = committedIndex;
+        });
+    }
+
+    /**
+     * Flush all events in disruptor.
+     */
+    @OnlyForTest
+    void flush() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        enqueueTask((task, sequence) -> {
+            task.type = TaskType.FLUSH;
+            task.shutdownLatch = latch;
+        });
+        latch.await();
+    }
+
+    @Override
+    public boolean onSnapshotLoad(final LoadSnapshotClosure done) {
+        return enqueueTask((task, sequence) -> {
+            task.type = TaskType.SNAPSHOT_LOAD;
+            task.done = done;
+        });
+    }
+
+    @Override
+    public boolean onSnapshotSave(final SaveSnapshotClosure done) {
+        return enqueueTask((task, sequence) -> {
+            task.type = TaskType.SNAPSHOT_SAVE;
+            task.done = done;
+        });
+    }
+
+    @Override
+    public boolean onLeaderStop(final Status status) {
+        return enqueueTask((task, sequence) -> {
+            task.type = TaskType.LEADER_STOP;
+            task.status = new Status(status);
+        });
+    }
+
+    @Override
+    public boolean onLeaderStart(final long term) {
+        return enqueueTask((task, sequence) -> {
+            task.type = TaskType.LEADER_START;
+            task.term = term;
+        });
+    }
+
+    @Override
+    public boolean onStartFollowing(final LeaderChangeContext ctx) {
+        return enqueueTask((task, sequence) -> {
+            task.type = TaskType.START_FOLLOWING;
+            task.leaderChangeCtx = new LeaderChangeContext(ctx.getLeaderId(), ctx.getTerm(), ctx.getStatus());
+        });
+    }
+
+    @Override
+    public boolean onStopFollowing(final LeaderChangeContext ctx) {
+        return enqueueTask((task, sequence) -> {
+            task.type = TaskType.STOP_FOLLOWING;
+            task.leaderChangeCtx = new LeaderChangeContext(ctx.getLeaderId(), ctx.getTerm(), ctx.getStatus());
+        });
+    }
+
+    /**
+     * Closure runs with an error.
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-04 2:20:31 PM
+     */
+    public class OnErrorClosure implements Closure {
+        private RaftException error;
+
+        public OnErrorClosure(final RaftException error) {
+            super();
+            this.error = error;
+        }
+
+        public RaftException getError() {
+            return this.error;
+        }
+
+        public void setError(final RaftException error) {
+            this.error = error;
+        }
+
+        @Override
+        public void run(final Status st) {
+        }
+    }
+
+    @Override
+    public boolean onError(final RaftException error) {
+        if (!this.error.getStatus().isOk()) {
+            LOG.warn("FSMCaller already in error status, ignore new error: {}", error);
+            return false;
+        }
+        final OnErrorClosure c = new OnErrorClosure(error);
+        return enqueueTask((task, sequence) -> {
+            task.type = TaskType.ERROR;
+            task.done = c;
+        });
+    }
+
+    @Override
+    public long getLastAppliedIndex() {
+        return this.lastAppliedIndex.get();
+    }
+
+    @Override
+    public synchronized void join() throws InterruptedException {
+        if (this.shutdownLatch != null) {
+            this.shutdownLatch.await();
+            this.disruptor.shutdown();
+            if (this.afterShutdown != null) {
+                this.afterShutdown.run(Status.OK());
+                this.afterShutdown = null;
+            }
+            this.shutdownLatch = null;
+        }
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    private long runApplyTask(final ApplyTask task, long maxCommittedIndex, final boolean endOfBatch) {
+        CountDownLatch shutdown = null;
+        if (task.type == TaskType.COMMITTED) {
+            if (task.committedIndex > maxCommittedIndex) {
+                maxCommittedIndex = task.committedIndex;
+            }
+        } else {
+            if (maxCommittedIndex >= 0) {
+                this.currTask = TaskType.COMMITTED;
+                doCommitted(maxCommittedIndex);
+                maxCommittedIndex = -1L; // reset maxCommittedIndex
+            }
+            final long startMs = Utils.monotonicMs();
+            try {
+                switch (task.type) {
+                    case COMMITTED:
+                        Requires.requireTrue(false, "Impossible");
+                        break;
+                    case SNAPSHOT_SAVE:
+                        this.currTask = TaskType.SNAPSHOT_SAVE;
+                        if (passByStatus(task.done)) {
+                            doSnapshotSave((SaveSnapshotClosure) task.done);
+                        }
+                        break;
+                    case SNAPSHOT_LOAD:
+                        this.currTask = TaskType.SNAPSHOT_LOAD;
+                        if (passByStatus(task.done)) {
+                            doSnapshotLoad((LoadSnapshotClosure) task.done);
+                        }
+                        break;
+                    case LEADER_STOP:
+                        this.currTask = TaskType.LEADER_STOP;
+                        doLeaderStop(task.status);
+                        break;
+                    case LEADER_START:
+                        this.currTask = TaskType.LEADER_START;
+                        doLeaderStart(task.term);
+                        break;
+                    case START_FOLLOWING:
+                        this.currTask = TaskType.START_FOLLOWING;
+                        doStartFollowing(task.leaderChangeCtx);
+                        break;
+                    case STOP_FOLLOWING:
+                        this.currTask = TaskType.STOP_FOLLOWING;
+                        doStopFollowing(task.leaderChangeCtx);
+                        break;
+                    case ERROR:
+                        this.currTask = TaskType.ERROR;
+                        doOnError((OnErrorClosure) task.done);
+                        break;
+                    case IDLE:
+                        Requires.requireTrue(false, "Can't reach here");
+                        break;
+                    case SHUTDOWN:
+                        this.currTask = TaskType.SHUTDOWN;
+                        shutdown = task.shutdownLatch;
+                        break;
+                    case FLUSH:
+                        this.currTask = TaskType.FLUSH;
+                        shutdown = task.shutdownLatch;
+                        break;
+                }
+            } finally {
+                this.nodeMetrics.recordLatency(task.type.metricName(), Utils.monotonicMs() - startMs);
+            }
+        }
+        try {
+            if (endOfBatch && maxCommittedIndex >= 0) {
+                this.currTask = TaskType.COMMITTED;
+                doCommitted(maxCommittedIndex);
+                maxCommittedIndex = -1L; // reset maxCommittedIndex
+            }
+            this.currTask = TaskType.IDLE;
+            return maxCommittedIndex;
+        } finally {
+            if (shutdown != null) {
+                shutdown.countDown();
+            }
+        }
+    }
+
+    private void doShutdown() {
+        if (this.node != null) {
+            this.node = null;
+        }
+        if (this.fsm != null) {
+            this.fsm.onShutdown();
+        }
+    }
+
+    private void notifyLastAppliedIndexUpdated(final long lastAppliedIndex) {
+        for (final LastAppliedLogIndexListener listener : this.lastAppliedLogIndexListeners) {
+            listener.onApplied(lastAppliedIndex);
+        }
+    }
+
+    private void doCommitted(final long committedIndex) {
+        if (!this.error.getStatus().isOk()) {
+            return;
+        }
+        final long lastAppliedIndex = this.lastAppliedIndex.get();
+        // We can tolerate the disorder of committed_index
+        if (lastAppliedIndex >= committedIndex) {
+            return;
+        }
+        final long startMs = Utils.monotonicMs();
+        try {
+            final List<Closure> closures = new ArrayList<>();
+            final List<TaskClosure> taskClosures = new ArrayList<>();
+            final long firstClosureIndex = this.closureQueue.popClosureUntil(committedIndex, closures, taskClosures);
+
+            // Calls TaskClosure#onCommitted if necessary
+            onTaskCommitted(taskClosures);
+
+            Requires.requireTrue(firstClosureIndex >= 0, "Invalid firstClosureIndex");
+            final IteratorImpl iterImpl = new IteratorImpl(this.fsm, this.logManager, closures, firstClosureIndex,
+                lastAppliedIndex, committedIndex, this.applyingIndex);
+            while (iterImpl.isGood()) {
+                final LogEntry logEntry = iterImpl.entry();
+                if (logEntry.getType() != EnumOutter.EntryType.ENTRY_TYPE_DATA) {
+                    if (logEntry.getType() == EnumOutter.EntryType.ENTRY_TYPE_CONFIGURATION) {
+                        if (logEntry.getOldPeers() != null && !logEntry.getOldPeers().isEmpty()) {
+                            // Joint stage is not supposed to be noticeable by end users.
+                            this.fsm.onConfigurationCommitted(new Configuration(iterImpl.entry().getPeers()));
+                        }
+                    }
+                    if (iterImpl.done() != null) {
+                        // For other entries, we have nothing to do besides flush the
+                        // pending tasks and run this closure to notify the caller that the
+                        // entries before this one were successfully committed and applied.
+                        iterImpl.done().run(Status.OK());
+                    }
+                    iterImpl.next();
+                    continue;
+                }
+
+                // Apply data task to user state machine
+                doApplyTasks(iterImpl);
+            }
+
+            if (iterImpl.hasError()) {
+                setError(iterImpl.getError());
+                iterImpl.runTheRestClosureWithError();
+            }
+            final long lastIndex = iterImpl.getIndex() - 1;
+            final long lastTerm = this.logManager.getTerm(lastIndex);
+            final LogId lastAppliedId = new LogId(lastIndex, lastTerm);
+            this.lastAppliedIndex.set(lastIndex);
+            this.lastAppliedTerm = lastTerm;
+            this.logManager.setAppliedId(lastAppliedId);
+            notifyLastAppliedIndexUpdated(lastIndex);
+        } finally {
+            this.nodeMetrics.recordLatency("fsm-commit", Utils.monotonicMs() - startMs);
+        }
+    }
+
+    private void onTaskCommitted(final List<TaskClosure> closures) {
+        for (int i = 0, size = closures.size(); i < size; i++) {
+            final TaskClosure done = closures.get(i);
+            done.onCommitted();
+        }
+    }
+
+    private void doApplyTasks(final IteratorImpl iterImpl) {
+        final IteratorWrapper iter = new IteratorWrapper(iterImpl);
+        final long startApplyMs = Utils.monotonicMs();
+        final long startIndex = iter.getIndex();
+        try {
+            this.fsm.onApply(iter);
+        } finally {
+            this.nodeMetrics.recordLatency("fsm-apply-tasks", Utils.monotonicMs() - startApplyMs);
+            this.nodeMetrics.recordSize("fsm-apply-tasks-count", iter.getIndex() - startIndex);
+        }
+        if (iter.hasNext()) {
+            LOG.error("Iterator is still valid, did you return before iterator reached the end?");
+        }
+        // Try move to next in case that we pass the same log twice.
+        iter.next();
+    }
+
+    private void doSnapshotSave(final 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 ConfigurationEntry confEntry = this.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));
+            return;
+        }
+        for (final PeerId peer : confEntry.getConf()) {
+            metaBuilder.addPeers(peer.toString());
+        }
+        for (final PeerId peer : confEntry.getConf().getLearners()) {
+            metaBuilder.addLearners(peer.toString());
+        }
+        if (confEntry.getOldConf() != null) {
+            for (final PeerId peer : confEntry.getOldConf()) {
+                metaBuilder.addOldPeers(peer.toString());
+            }
+            for (final PeerId peer : confEntry.getOldConf().getLearners()) {
+                metaBuilder.addOldLearners(peer.toString());
+            }
+        }
+        final SnapshotWriter writer = done.start(metaBuilder.build());
+        if (writer == null) {
+            done.run(new Status(RaftError.EINVAL, "snapshot_storage create SnapshotWriter failed"));
+            return;
+        }
+        this.fsm.onSnapshotSave(writer, done);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("StateMachine [");
+        switch (this.currTask) {
+            case IDLE:
+                sb.append("Idle");
+                break;
+            case COMMITTED:
+                sb.append("Applying logIndex=").append(this.applyingIndex);
+                break;
+            case SNAPSHOT_SAVE:
+                sb.append("Saving snapshot");
+                break;
+            case SNAPSHOT_LOAD:
+                sb.append("Loading snapshot");
+                break;
+            case ERROR:
+                sb.append("Notifying error");
+                break;
+            case LEADER_STOP:
+                sb.append("Notifying leader stop");
+                break;
+            case LEADER_START:
+                sb.append("Notifying leader start");
+                break;
+            case START_FOLLOWING:
+                sb.append("Notifying start following");
+                break;
+            case STOP_FOLLOWING:
+                sb.append("Notifying stop following");
+                break;
+            case SHUTDOWN:
+                sb.append("Shutting down");
+                break;
+            default:
+                break;
+        }
+        return sb.append(']').toString();
+    }
+
+    private void doSnapshotLoad(final LoadSnapshotClosure done) {
+        Requires.requireNonNull(done, "LoadSnapshotClosure is null");
+        final SnapshotReader reader = done.start();
+        if (reader == null) {
+            done.run(new Status(RaftError.EINVAL, "open SnapshotReader failed"));
+            return;
+        }
+        final RaftOutter.SnapshotMeta meta = reader.load();
+        if (meta == null) {
+            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");
+                setError(err);
+            }
+            return;
+        }
+        final LogId lastAppliedId = new LogId(this.lastAppliedIndex.get(), this.lastAppliedTerm);
+        final LogId snapshotId = new LogId(meta.getLastIncludedIndex(), meta.getLastIncludedTerm());
+        if (lastAppliedId.compareTo(snapshotId) > 0) {
+            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");
+            setError(e);
+            return;
+        }
+        if (meta.getOldPeersCount() == 0) {
+            // Joint stage is not supposed to be noticeable by end users.
+            final Configuration conf = new Configuration();
+            for (int i = 0, size = meta.getPeersCount(); i < size; i++) {
+                final PeerId peer = new PeerId();
+                Requires.requireTrue(peer.parse(meta.getPeers(i)), "Parse peer failed");
+                conf.addPeer(peer);
+            }
+            this.fsm.onConfigurationCommitted(conf);
+        }
+        this.lastAppliedIndex.set(meta.getLastIncludedIndex());
+        this.lastAppliedTerm = meta.getLastIncludedTerm();
+        done.run(Status.OK());
+    }
+
+    private void doOnError(final OnErrorClosure done) {
+        setError(done.getError());
+    }
+
+    private void doLeaderStop(final Status status) {
+        this.fsm.onLeaderStop(status);
+    }
+
+    private void doLeaderStart(final long term) {
+        this.fsm.onLeaderStart(term);
+    }
+
+    private void doStartFollowing(final LeaderChangeContext ctx) {
+        this.fsm.onStartFollowing(ctx);
+    }
+
+    private void doStopFollowing(final LeaderChangeContext ctx) {
+        this.fsm.onStopFollowing(ctx);
+    }
+
+    private void setError(final RaftException e) {
+        if (this.error.getType() != EnumOutter.ErrorType.ERROR_TYPE_NONE) {
+            // already report
+            return;
+        }
+        this.error = e;
+        if (this.fsm != null) {
+            this.fsm.onError(e);
+        }
+        if (this.node != null) {
+            this.node.onError(e);
+        }
+    }
+
+    @OnlyForTest
+    RaftException getError() {
+        return this.error;
+    }
+
+    private boolean passByStatus(final Closure done) {
+        final Status status = this.error.getStatus();
+        if (!status.isOk()) {
+            if (done != null) {
+                done.run(new Status(RaftError.EINVAL, "FSMCaller is in bad status=`%s`", status));
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void describe(final Printer out) {
+        out.print("  ") //
+            .println(toString());
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/IteratorImpl.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/IteratorImpl.java
new file mode 100644
index 0000000..a272b2f
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/IteratorImpl.java
@@ -0,0 +1,161 @@
+/*
+ * 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.core;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.StateMachine;
+import com.alipay.sofa.jraft.Status;
+import com.alipay.sofa.jraft.entity.EnumOutter;
+import com.alipay.sofa.jraft.entity.LogEntry;
+import com.alipay.sofa.jraft.error.LogEntryCorruptedException;
+import com.alipay.sofa.jraft.error.RaftError;
+import com.alipay.sofa.jraft.error.RaftException;
+import com.alipay.sofa.jraft.storage.LogManager;
+import com.alipay.sofa.jraft.util.Requires;
+import com.alipay.sofa.jraft.util.Utils;
+
+/**
+ * The iterator implementation.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-03 3:28:37 PM
+ */
+public class IteratorImpl {
+
+    private final StateMachine  fsm;
+    private final LogManager    logManager;
+    private final List<Closure> closures;
+    private final long          firstClosureIndex;
+    private long                currentIndex;
+    private final long          committedIndex;
+    private LogEntry            currEntry = new LogEntry(); // blank entry
+    private final AtomicLong    applyingIndex;
+    private RaftException       error;
+
+    public IteratorImpl(final StateMachine fsm, final LogManager logManager, final List<Closure> closures,
+                        final long firstClosureIndex, final long lastAppliedIndex, final long committedIndex,
+                        final AtomicLong applyingIndex) {
+        super();
+        this.fsm = fsm;
+        this.logManager = logManager;
+        this.closures = closures;
+        this.firstClosureIndex = firstClosureIndex;
+        this.currentIndex = lastAppliedIndex;
+        this.committedIndex = committedIndex;
+        this.applyingIndex = applyingIndex;
+        next();
+    }
+
+    @Override
+    public String toString() {
+        return "IteratorImpl [fsm=" + this.fsm + ", logManager=" + this.logManager + ", closures=" + this.closures
+               + ", firstClosureIndex=" + this.firstClosureIndex + ", currentIndex=" + this.currentIndex
+               + ", committedIndex=" + this.committedIndex + ", currEntry=" + this.currEntry + ", applyingIndex="
+               + this.applyingIndex + ", error=" + this.error + "]";
+    }
+
+    public LogEntry entry() {
+        return this.currEntry;
+    }
+
+    public RaftException getError() {
+        return this.error;
+    }
+
+    public boolean isGood() {
+        return this.currentIndex <= this.committedIndex && !hasError();
+    }
+
+    public boolean hasError() {
+        return this.error != null;
+    }
+
+    /**
+     * Move to next
+     */
+    public void next() {
+        this.currEntry = null; //release current entry
+        //get next entry
+        if (this.currentIndex <= this.committedIndex) {
+            ++this.currentIndex;
+            if (this.currentIndex <= this.committedIndex) {
+                try {
+                    this.currEntry = this.logManager.getEntry(this.currentIndex);
+                    if (this.currEntry == null) {
+                        getOrCreateError().setType(EnumOutter.ErrorType.ERROR_TYPE_LOG);
+                        getOrCreateError().getStatus().setError(-1,
+                            "Fail to get entry at index=%d while committed_index=%d", this.currentIndex,
+                            this.committedIndex);
+                    }
+                } catch (final LogEntryCorruptedException e) {
+                    getOrCreateError().setType(EnumOutter.ErrorType.ERROR_TYPE_LOG);
+                    getOrCreateError().getStatus().setError(RaftError.EINVAL, e.getMessage());
+                }
+                this.applyingIndex.set(this.currentIndex);
+            }
+        }
+    }
+
+    public long getIndex() {
+        return this.currentIndex;
+    }
+
+    public Closure done() {
+        if (this.currentIndex < this.firstClosureIndex) {
+            return null;
+        }
+        return this.closures.get((int) (this.currentIndex - this.firstClosureIndex));
+    }
+
+    protected void runTheRestClosureWithError() {
+        for (long i = Math.max(this.currentIndex, this.firstClosureIndex); i <= this.committedIndex; i++) {
+            final Closure done = this.closures.get((int) (i - this.firstClosureIndex));
+            if (done != null) {
+                Requires.requireNonNull(this.error, "error");
+                Requires.requireNonNull(this.error.getStatus(), "error.status");
+                final Status status = this.error.getStatus();
+                Utils.runClosureInThread(done, status);
+            }
+        }
+    }
+
+    public void setErrorAndRollback(final long ntail, final Status st) {
+        Requires.requireTrue(ntail > 0, "Invalid ntail=" + ntail);
+        if (this.currEntry == null || this.currEntry.getType() != EnumOutter.EntryType.ENTRY_TYPE_DATA) {
+            this.currentIndex -= ntail;
+        } else {
+            this.currentIndex -= ntail - 1;
+        }
+        this.currEntry = null;
+        getOrCreateError().setType(EnumOutter.ErrorType.ERROR_TYPE_STATE_MACHINE);
+        getOrCreateError().getStatus().setError(RaftError.ESTATEMACHINE,
+            "StateMachine meet critical error when applying one or more tasks since index=%d, %s", this.currentIndex,
+            st != null ? st.toString() : "none");
+
+    }
+
+    private RaftException getOrCreateError() {
+        if (this.error == null) {
+            this.error = new RaftException();
+        }
+        return this.error;
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/IteratorWrapper.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/IteratorWrapper.java
new file mode 100644
index 0000000..ce504d0
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/IteratorWrapper.java
@@ -0,0 +1,75 @@
+/*
+ * 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.core;
+
+import java.nio.ByteBuffer;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.Iterator;
+import com.alipay.sofa.jraft.Status;
+import com.alipay.sofa.jraft.entity.EnumOutter;
+import com.alipay.sofa.jraft.entity.LogEntry;
+
+public class IteratorWrapper implements Iterator {
+
+    private final IteratorImpl impl;
+
+    public IteratorWrapper(IteratorImpl iterImpl) {
+        super();
+        this.impl = iterImpl;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return this.impl.isGood() && this.impl.entry().getType() == EnumOutter.EntryType.ENTRY_TYPE_DATA;
+    }
+
+    @Override
+    public ByteBuffer next() {
+        final ByteBuffer data = getData();
+        if (hasNext()) {
+            this.impl.next();
+        }
+        return data;
+    }
+
+    @Override
+    public ByteBuffer getData() {
+        final LogEntry entry = this.impl.entry();
+        return entry != null ? entry.getData() : null;
+    }
+
+    @Override
+    public long getIndex() {
+        return this.impl.getIndex();
+    }
+
+    @Override
+    public long getTerm() {
+        return this.impl.entry().getId().getTerm();
+    }
+
+    @Override
+    public Closure done() {
+        return this.impl.done();
+    }
+
+    @Override
+    public void setErrorAndRollback(final long ntail, final Status st) {
+        this.impl.setErrorAndRollback(ntail, st);
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java
new file mode 100644
index 0000000..09139d5
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java
@@ -0,0 +1,3464 @@
+/*
+ * 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.core;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alipay.sofa.jraft.Closure;
+import com.alipay.sofa.jraft.FSMCaller;
+import com.alipay.sofa.jraft.JRaftServiceFactory;
+import com.alipay.sofa.jraft.JRaftUtils;
+import com.alipay.sofa.jraft.Node;
+import com.alipay.sofa.jraft.NodeManager;
+import com.alipay.sofa.jraft.ReadOnlyService;
+import com.alipay.sofa.jraft.ReplicatorGroup;
+import com.alipay.sofa.jraft.Status;
+import com.alipay.sofa.jraft.closure.CatchUpClosure;
+import com.alipay.sofa.jraft.closure.ClosureQueue;
+import com.alipay.sofa.jraft.closure.ClosureQueueImpl;
+import com.alipay.sofa.jraft.closure.ReadIndexClosure;
+import com.alipay.sofa.jraft.closure.SynchronizedClosure;
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.conf.ConfigurationEntry;
+import com.alipay.sofa.jraft.conf.ConfigurationManager;
+import com.alipay.sofa.jraft.entity.Ballot;
+import com.alipay.sofa.jraft.entity.EnumOutter;
+import com.alipay.sofa.jraft.entity.LeaderChangeContext;
+import com.alipay.sofa.jraft.entity.LogEntry;
+import com.alipay.sofa.jraft.entity.LogId;
+import com.alipay.sofa.jraft.entity.NodeId;
+import com.alipay.sofa.jraft.entity.PeerId;
+import com.alipay.sofa.jraft.entity.RaftOutter;
+import com.alipay.sofa.jraft.entity.Task;
+import com.alipay.sofa.jraft.entity.UserLog;
+import com.alipay.sofa.jraft.error.LogIndexOutOfBoundsException;
+import com.alipay.sofa.jraft.error.LogNotFoundException;
+import com.alipay.sofa.jraft.error.RaftError;
+import com.alipay.sofa.jraft.error.RaftException;
+import com.alipay.sofa.jraft.option.BallotBoxOptions;
+import com.alipay.sofa.jraft.option.BootstrapOptions;
+import com.alipay.sofa.jraft.option.FSMCallerOptions;
+import com.alipay.sofa.jraft.option.LogManagerOptions;
+import com.alipay.sofa.jraft.option.NodeOptions;
+import com.alipay.sofa.jraft.option.RaftMetaStorageOptions;
+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;
+import com.alipay.sofa.jraft.rpc.RaftClientService;
+import com.alipay.sofa.jraft.rpc.RaftServerService;
+import com.alipay.sofa.jraft.rpc.RpcRequestClosure;
+import com.alipay.sofa.jraft.rpc.RpcRequests.AppendEntriesRequest;
+import com.alipay.sofa.jraft.rpc.RpcRequests.AppendEntriesResponse;
+import com.alipay.sofa.jraft.rpc.RpcRequests.InstallSnapshotRequest;
+import com.alipay.sofa.jraft.rpc.RpcRequests.InstallSnapshotResponse;
+import com.alipay.sofa.jraft.rpc.RpcRequests.ReadIndexRequest;
+import com.alipay.sofa.jraft.rpc.RpcRequests.ReadIndexResponse;
+import com.alipay.sofa.jraft.rpc.RpcRequests.RequestVoteRequest;
+import com.alipay.sofa.jraft.rpc.RpcRequests.RequestVoteResponse;
+import com.alipay.sofa.jraft.rpc.RpcRequests.TimeoutNowRequest;
+import com.alipay.sofa.jraft.rpc.RpcRequests.TimeoutNowResponse;
+import com.alipay.sofa.jraft.rpc.RpcResponseClosure;
+import com.alipay.sofa.jraft.rpc.RpcResponseClosureAdapter;
+import com.alipay.sofa.jraft.rpc.impl.core.DefaultRaftClientService;
+import com.alipay.sofa.jraft.storage.LogManager;
+import com.alipay.sofa.jraft.storage.LogStorage;
+import com.alipay.sofa.jraft.storage.RaftMetaStorage;
+import com.alipay.sofa.jraft.storage.SnapshotExecutor;
+import com.alipay.sofa.jraft.storage.impl.LogManagerImpl;
+import com.alipay.sofa.jraft.storage.snapshot.SnapshotExecutorImpl;
+import com.alipay.sofa.jraft.util.Describer;
+import com.alipay.sofa.jraft.util.DisruptorBuilder;
+import com.alipay.sofa.jraft.util.DisruptorMetricSet;
+import com.alipay.sofa.jraft.util.JRaftServiceLoader;
+import com.alipay.sofa.jraft.util.JRaftSignalHandler;
+import com.alipay.sofa.jraft.util.LogExceptionHandler;
+import com.alipay.sofa.jraft.util.NamedThreadFactory;
+import com.alipay.sofa.jraft.util.OnlyForTest;
+import com.alipay.sofa.jraft.util.Platform;
+import com.alipay.sofa.jraft.util.RepeatedTimer;
+import com.alipay.sofa.jraft.util.Requires;
+import com.alipay.sofa.jraft.util.RpcFactoryHelper;
+import com.alipay.sofa.jraft.util.SignalHelper;
+import com.alipay.sofa.jraft.util.SystemPropertyUtil;
+import com.alipay.sofa.jraft.util.ThreadHelper;
+import com.alipay.sofa.jraft.util.ThreadId;
+import com.alipay.sofa.jraft.util.Utils;
+import com.alipay.sofa.jraft.util.concurrent.LongHeldDetectingReadWriteLock;
+import com.alipay.sofa.jraft.util.timer.RaftTimerFactory;
+import com.alipay.sofa.jraft.rpc.Message;
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.EventFactory;
+import com.lmax.disruptor.EventHandler;
+import com.lmax.disruptor.EventTranslator;
+import com.lmax.disruptor.RingBuffer;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+
+/**
+ * The raft replica node implementation.
+ *
+ * @author boyan (boyan@alibaba-inc.com)
+ *
+ * 2018-Apr-03 4:26:51 PM
+ */
+public class NodeImpl implements Node, RaftServerService {
+
+    private static final Logger                                            LOG                      = LoggerFactory
+                                                                                                        .getLogger(NodeImpl.class);
+
+    static {
+        try {
+            if (SignalHelper.supportSignal()) {
+                // TODO support windows signal
+                if (!Platform.isWindows()) {
+                    final List<JRaftSignalHandler> handlers = JRaftServiceLoader.load(JRaftSignalHandler.class) //
+                        .sort();
+                    SignalHelper.addSignal(SignalHelper.SIG_USR2, handlers);
+                }
+            }
+        } catch (final Throwable t) {
+            LOG.error("Fail to add signal.", t);
+        }
+    }
+
+    public final static RaftTimerFactory                                   TIMER_FACTORY            = JRaftUtils
+                                                                                                        .raftTimerFactory();
+
+    // Max retry times when applying tasks.
+    private static final int                                               MAX_APPLY_RETRY_TIMES    = 3;
+
+    public static final AtomicInteger                                      GLOBAL_NUM_NODES         = new AtomicInteger(
+                                                                                                        0);
+
+    /** Internal states */
+    private final ReadWriteLock                                            readWriteLock            = new NodeReadWriteLock(
+                                                                                                        this);
+    protected final Lock                                                   writeLock                = this.readWriteLock
+                                                                                                        .writeLock();
+    protected final Lock                                                   readLock                 = this.readWriteLock
+                                                                                                        .readLock();
+    private volatile State                                                 state;
+    private volatile CountDownLatch                                        shutdownLatch;
+    private long                                                           currTerm;
+    private volatile long                                                  lastLeaderTimestamp;
+    private PeerId                                                         leaderId                 = new PeerId();
+    private PeerId                                                         votedId;
+    private final Ballot                                                   voteCtx                  = new Ballot();
+    private final Ballot                                                   prevVoteCtx              = new Ballot();
+    private ConfigurationEntry                                             conf;
+    private StopTransferArg                                                stopTransferArg;
+    /** Raft group and node options and identifier */
+    private final String                                                   groupId;
+    private NodeOptions                                                    options;
+    private RaftOptions                                                    raftOptions;
+    private final PeerId                                                   serverId;
+    /** Other services */
+    private final ConfigurationCtx                                         confCtx;
+    private LogStorage                                                     logStorage;
+    private RaftMetaStorage                                                metaStorage;
+    private ClosureQueue                                                   closureQueue;
+    private ConfigurationManager                                           configManager;
+    private LogManager                                                     logManager;
+    private FSMCaller                                                      fsmCaller;
+    private BallotBox                                                      ballotBox;
+    private SnapshotExecutor                                               snapshotExecutor;
+    private ReplicatorGroup                                                replicatorGroup;
+    private final List<Closure>                                            shutdownContinuations    = new ArrayList<>();
+    private RaftClientService                                              rpcService;
+    private ReadOnlyService                                                readOnlyService;
+    /** Timers */
+    private Scheduler                                                      timerManager;
+    private RepeatedTimer                                                  electionTimer;
+    private RepeatedTimer                                                  voteTimer;
+    private RepeatedTimer                                                  stepDownTimer;
+    private RepeatedTimer                                                  snapshotTimer;
+    private ScheduledFuture<?>                                             transferTimer;
+    private ThreadId                                                       wakingCandidate;
+    /** Disruptor to run node service */
+    private Disruptor<LogEntryAndClosure>                                  applyDisruptor;
+    private RingBuffer<LogEntryAndClosure>                                 applyQueue;
+
+    /** Metrics */
+    private NodeMetrics                                                    metrics;
+
+    private NodeId                                                         nodeId;
+    private JRaftServiceFactory                                            serviceFactory;
+
+    /** ReplicatorStateListeners */
+    private final CopyOnWriteArrayList<Replicator.ReplicatorStateListener> replicatorStateListeners = new CopyOnWriteArrayList<>();
+    /** Node's target leader election priority value */
+    private volatile int                                                   targetPriority;
+    /** The number of elections time out for current node */
+    private volatile int                                                   electionTimeoutCounter;
+
+    private static class NodeReadWriteLock extends LongHeldDetectingReadWriteLock {
+
+        static final long  MAX_BLOCKING_MS_TO_REPORT = SystemPropertyUtil.getLong(
+                                                         "jraft.node.detecting.lock.max_blocking_ms_to_report", -1);
+
+        private final Node node;
+
+        public NodeReadWriteLock(final Node node) {
+            super(MAX_BLOCKING_MS_TO_REPORT, TimeUnit.MILLISECONDS);
+            this.node = node;
+        }
+
+        @Override
+        public void report(final AcquireMode acquireMode, final Thread heldThread,
+                           final Collection<Thread> queuedThreads, final long blockedNanos) {
+            final long blockedMs = TimeUnit.NANOSECONDS.toMillis(blockedNanos);
+            LOG.warn(
+                "Raft-Node-Lock report: currentThread={}, acquireMode={}, heldThread={}, queuedThreads={}, blockedMs={}.",
+                Thread.currentThread(), acquireMode, heldThread, queuedThreads, blockedMs);
+
+            final NodeMetrics metrics = this.node.getNodeMetrics();
+            if (metrics != null) {
+                metrics.recordLatency("node-lock-blocked", blockedMs);
+            }
+        }
+    }
+
+    /**
+     * Node service event.
+     *
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-03 4:29:55 PM
+     */
+    private static class LogEntryAndClosure {
+        LogEntry       entry;
+        Closure        done;
+        long           expectedTerm;
+        CountDownLatch shutdownLatch;
+
+        public void reset() {
+            this.entry = null;
+            this.done = null;
+            this.expectedTerm = 0;
+            this.shutdownLatch = null;
+        }
+    }
+
+    private static class LogEntryAndClosureFactory implements EventFactory<LogEntryAndClosure> {
+
+        @Override
+        public LogEntryAndClosure newInstance() {
+            return new LogEntryAndClosure();
+        }
+    }
+
+    /**
+     * Event handler.
+     *
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-03 4:30:07 PM
+     */
+    private class LogEntryAndClosureHandler implements EventHandler<LogEntryAndClosure> {
+        // task list for batch
+        private final List<LogEntryAndClosure> tasks = new ArrayList<>(NodeImpl.this.raftOptions.getApplyBatch());
+
+        @Override
+        public void onEvent(final LogEntryAndClosure event, final long sequence, final boolean endOfBatch)
+                                                                                                          throws Exception {
+            if (event.shutdownLatch != null) {
+                if (!this.tasks.isEmpty()) {
+                    executeApplyingTasks(this.tasks);
+                    this.tasks.clear();
+                }
+                final int num = GLOBAL_NUM_NODES.decrementAndGet();
+                LOG.info("The number of active nodes decrement to {}.", num);
+                event.shutdownLatch.countDown();
+                return;
+            }
+
+            this.tasks.add(event);
+            if (this.tasks.size() >= NodeImpl.this.raftOptions.getApplyBatch() || endOfBatch) {
+                executeApplyingTasks(this.tasks);
+                this.tasks.clear();
+            }
+        }
+    }
+
+    /**
+     * Configuration commit context.
+     *
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-03 4:29:38 PM
+     */
+    private static class ConfigurationCtx {
+        enum Stage {
+            STAGE_NONE, // none stage
+            STAGE_CATCHING_UP, // the node is catching-up
+            STAGE_JOINT, // joint stage
+            STAGE_STABLE // stable stage
+        }
+
+        final NodeImpl node;
+        Stage          stage;
+        // Peers change times
+        int            nchanges;
+        long           version;
+        // peers
+        List<PeerId>   newPeers    = new ArrayList<>();
+        List<PeerId>   oldPeers    = new ArrayList<>();
+        List<PeerId>   addingPeers = new ArrayList<>();
+        // learners
+        List<PeerId>   newLearners = new ArrayList<>();
+        List<PeerId>   oldLearners = new ArrayList<>();
+        Closure        done;
+
+        public ConfigurationCtx(final NodeImpl node) {
+            super();
+            this.node = node;
+            this.stage = Stage.STAGE_NONE;
+            this.version = 0;
+            this.done = null;
+        }
+
+        /**
+         * Start change configuration.
+         */
+        void start(final Configuration oldConf, final Configuration newConf, final Closure done) {
+            if (isBusy()) {
+                if (done != null) {
+                    Utils.runClosureInThread(done, new Status(RaftError.EBUSY, "Already in busy stage."));
+                }
+                throw new IllegalStateException("Busy stage");
+            }
+            if (this.done != null) {
+                if (done != null) {
+                    Utils.runClosureInThread(done, new Status(RaftError.EINVAL, "Already have done closure."));
+                }
+                throw new IllegalArgumentException("Already have done closure");
+            }
+            this.done = done;
+            this.stage = Stage.STAGE_CATCHING_UP;
+            this.oldPeers = oldConf.listPeers();
+            this.newPeers = newConf.listPeers();
+            this.oldLearners = oldConf.listLearners();
+            this.newLearners = newConf.listLearners();
+            final Configuration adding = new Configuration();
+            final Configuration removing = new Configuration();
+            newConf.diff(oldConf, adding, removing);
+            this.nchanges = adding.size() + removing.size();
+
+            addNewLearners();
+            if (adding.isEmpty()) {
+                nextStage();
+                return;
+            }
+            addNewPeers(adding);
+        }
+
+        private void addNewPeers(final Configuration adding) {
+            this.addingPeers = adding.listPeers();
+            LOG.info("Adding peers: {}.", this.addingPeers);
+            for (final PeerId newPeer : this.addingPeers) {
+                if (!this.node.replicatorGroup.addReplicator(newPeer)) {
+                    LOG.error("Node {} start the replicator failed, peer={}.", this.node.getNodeId(), newPeer);
+                    onCaughtUp(this.version, newPeer, false);
+                    return;
+                }
+                final OnCaughtUp caughtUp = new OnCaughtUp(this.node, this.node.currTerm, newPeer, this.version);
+                final long dueTime = Utils.nowMs() + this.node.options.getElectionTimeoutMs();
+                if (!this.node.replicatorGroup.waitCaughtUp(newPeer, this.node.options.getCatchupMargin(), dueTime,
+                    caughtUp)) {
+                    LOG.error("Node {} waitCaughtUp, peer={}.", this.node.getNodeId(), newPeer);
+                    onCaughtUp(this.version, newPeer, false);
+                    return;
+                }
+            }
+        }
+
+        private void addNewLearners() {
+            final Set<PeerId> addingLearners = new HashSet<>(this.newLearners);
+            addingLearners.removeAll(this.oldLearners);
+            LOG.info("Adding learners: {}.", this.addingPeers);
+            for (final PeerId newLearner : addingLearners) {
+                if (!this.node.replicatorGroup.addReplicator(newLearner, ReplicatorType.Learner)) {
+                    LOG.error("Node {} start the learner replicator failed, peer={}.", this.node.getNodeId(),
+                        newLearner);
+                }
+            }
+        }
+
+        void onCaughtUp(final long version, final PeerId peer, final boolean success) {
+            if (version != this.version) {
+                LOG.warn("Ignore onCaughtUp message, mismatch configuration context version, expect {}, but is {}.",
+                    this.version, version);
+                return;
+            }
+            Requires.requireTrue(this.stage == Stage.STAGE_CATCHING_UP, "Stage is not in STAGE_CATCHING_UP");
+            if (success) {
+                this.addingPeers.remove(peer);
+                if (this.addingPeers.isEmpty()) {
+                    nextStage();
+                    return;
+                }
+                return;
+            }
+            LOG.warn("Node {} fail to catch up peer {} when trying to change peers from {} to {}.",
+                this.node.getNodeId(), peer, this.oldPeers, this.newPeers);
+            reset(new Status(RaftError.ECATCHUP, "Peer %s failed to catch up.", peer));
+        }
+
+        void reset() {
+            reset(null);
+        }
+
+        void reset(final Status st) {
+            if (st != null && st.isOk()) {
+                this.node.stopReplicator(this.newPeers, this.oldPeers);
+                this.node.stopReplicator(this.newLearners, this.oldLearners);
+            } else {
+                this.node.stopReplicator(this.oldPeers, this.newPeers);
+                this.node.stopReplicator(this.oldLearners, this.newLearners);
+            }
+            clearPeers();
+            clearLearners();
+
+            this.version++;
+            this.stage = Stage.STAGE_NONE;
+            this.nchanges = 0;
+            if (this.done != null) {
+                Utils.runClosureInThread(this.done, st != null ? st : new Status(RaftError.EPERM,
+                    "Leader stepped down."));
+                this.done = null;
+            }
+        }
+
+        private void clearLearners() {
+            this.newLearners.clear();
+            this.oldLearners.clear();
+        }
+
+        private void clearPeers() {
+            this.newPeers.clear();
+            this.oldPeers.clear();
+            this.addingPeers.clear();
+        }
+
+        /**
+         * Invoked when this node becomes the leader, write a configuration change log as the first log.
+         */
+        void flush(final Configuration conf, final Configuration oldConf) {
+            Requires.requireTrue(!isBusy(), "Flush when busy");
+            this.newPeers = conf.listPeers();
+            this.newLearners = conf.listLearners();
+            if (oldConf == null || oldConf.isEmpty()) {
+                this.stage = Stage.STAGE_STABLE;
+                this.oldPeers = this.newPeers;
+                this.oldLearners = this.newLearners;
+            } else {
+                this.stage = Stage.STAGE_JOINT;
+                this.oldPeers = oldConf.listPeers();
+                this.oldLearners = oldConf.listLearners();
+            }
+            this.node.unsafeApplyConfiguration(conf, oldConf == null || oldConf.isEmpty() ? null : oldConf, true);
+        }
+
+        void nextStage() {
+            Requires.requireTrue(isBusy(), "Not in busy stage");
+            switch (this.stage) {
+                case STAGE_CATCHING_UP:
+                    if (this.nchanges > 0) {
+                        this.stage = Stage.STAGE_JOINT;
+                        this.node.unsafeApplyConfiguration(new Configuration(this.newPeers, this.newLearners),
+                            new Configuration(this.oldPeers), false);
+                        return;
+                    }
+                case STAGE_JOINT:
+                    this.stage = Stage.STAGE_STABLE;
+                    this.node.unsafeApplyConfiguration(new Configuration(this.newPeers, this.newLearners), null, false);
+                    break;
+                case STAGE_STABLE:
+                    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."));
+                    }
+                    break;
+                case STAGE_NONE:
+                    // noinspection ConstantConditions
+                    Requires.requireTrue(false, "Can't reach here");
+                    break;
+            }
+        }
+
+        boolean isBusy() {
+            return this.stage != Stage.STAGE_NONE;
+        }
+    }
+
+    public NodeImpl() {
+        this(null, null);
+    }
+
+    public NodeImpl(final String groupId, final PeerId serverId) {
+        super();
+        if (groupId != null) {
+            Utils.verifyGroupId(groupId);
+        }
+        this.groupId = groupId;
+        this.serverId = serverId != null ? serverId.copy() : null;
+        this.state = State.STATE_UNINITIALIZED;
+        this.currTerm = 0;
+        updateLastLeaderTimestamp(Utils.monotonicMs());
+        this.confCtx = new ConfigurationCtx(this);
+        this.wakingCandidate = null;
+        final int num = GLOBAL_NUM_NODES.incrementAndGet();
+        LOG.info("The number of active nodes increment to {}.", num);
+    }
+
+    private boolean initSnapshotStorage() {
+        if (StringUtils.isEmpty(this.options.getSnapshotUri())) {
+            LOG.warn("Do not set snapshot uri, ignore initSnapshotStorage.");
+            return true;
+        }
+        this.snapshotExecutor = new SnapshotExecutorImpl();
+        final SnapshotExecutorOptions opts = new SnapshotExecutorOptions();
+        opts.setUri(this.options.getSnapshotUri());
+        opts.setFsmCaller(this.fsmCaller);
+        opts.setNode(this);
+        opts.setLogManager(this.logManager);
+        opts.setAddr(this.serverId != null ? this.serverId.getEndpoint() : null);
+        opts.setInitTerm(this.currTerm);
+        opts.setFilterBeforeCopyRemote(this.options.isFilterBeforeCopyRemote());
+        // get snapshot throttle
+        opts.setSnapshotThrottle(this.options.getSnapshotThrottle());
+        return this.snapshotExecutor.init(opts);
+    }
+
+    private boolean initLogStorage() {
+        Requires.requireNonNull(this.fsmCaller, "Null fsm caller");
+        this.logStorage = this.serviceFactory.createLogStorage(this.options.getLogUri(), this.raftOptions);
+        this.logManager = new LogManagerImpl();
+        final LogManagerOptions opts = new LogManagerOptions();
+        opts.setLogEntryCodecFactory(this.serviceFactory.createLogEntryCodecFactory());
+        opts.setLogStorage(this.logStorage);
+        opts.setConfigurationManager(this.configManager);
+        opts.setFsmCaller(this.fsmCaller);
+        opts.setNodeMetrics(this.metrics);
+        opts.setDisruptorBufferSize(this.raftOptions.getDisruptorBufferSize());
+        opts.setRaftOptions(this.raftOptions);
+        return this.logManager.init(opts);
+    }
+
+    private boolean initMetaStorage() {
+        this.metaStorage = this.serviceFactory.createRaftMetaStorage(this.options.getRaftMetaUri(), this.raftOptions);
+        RaftMetaStorageOptions opts = new RaftMetaStorageOptions();
+        opts.setNode(this);
+        if (!this.metaStorage.init(opts)) {
+            LOG.error("Node {} init meta storage failed, uri={}.", this.serverId, this.options.getRaftMetaUri());
+            return false;
+        }
+        this.currTerm = this.metaStorage.getTerm();
+        this.votedId = this.metaStorage.getVotedFor().copy();
+        return true;
+    }
+
+    private void handleSnapshotTimeout() {
+        this.writeLock.lock();
+        try {
+            if (!this.state.isActive()) {
+                return;
+            }
+        } finally {
+            this.writeLock.unlock();
+        }
+        // do_snapshot in another thread to avoid blocking the timer thread.
+        Utils.runInThread(() -> doSnapshot(null));
+    }
+
+    private void handleElectionTimeout() {
+        boolean doUnlock = true;
+        this.writeLock.lock();
+        try {
+            if (this.state != State.STATE_FOLLOWER) {
+                return;
+            }
+            if (isCurrentLeaderValid()) {
+                return;
+            }
+            resetLeaderId(PeerId.emptyPeer(), new Status(RaftError.ERAFTTIMEDOUT, "Lost connection from leader %s.",
+                this.leaderId));
+
+            // Judge whether to launch a election.
+            if (!allowLaunchElection()) {
+                return;
+            }
+
+            doUnlock = false;
+            preVote();
+
+        } finally {
+            if (doUnlock) {
+                this.writeLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Whether to allow for launching election or not by comparing node's priority with target
+     * priority. And at the same time, if next leader is not elected until next election
+     * timeout, it decays its local target priority exponentially.
+     *
+     * @return Whether current node will launch election or not.
+     */
+    @SuppressWarnings("NonAtomicOperationOnVolatileField")
+    private boolean allowLaunchElection() {
+
+        // Priority 0 is a special value so that a node will never participate in election.
+        if (this.serverId.isPriorityNotElected()) {
+            LOG.warn("Node {} will never participate in election, because it's priority={}.", getNodeId(),
+                this.serverId.getPriority());
+            return false;
+        }
+
+        // If this nodes disable priority election, then it can make a election.
+        if (this.serverId.isPriorityDisabled()) {
+            return true;
+        }
+
+        // If current node's priority < target_priority, it does not initiate leader,
+        // election and waits for the next election timeout.
+        if (this.serverId.getPriority() < this.targetPriority) {
+            this.electionTimeoutCounter++;
+
+            // If next leader is not elected until next election timeout, it
+            // decays its local target priority exponentially.
+            if (this.electionTimeoutCounter > 1) {
+                decayTargetPriority();
+                this.electionTimeoutCounter = 0;
+            }
+
+            if (this.electionTimeoutCounter == 1) {
+                LOG.debug("Node {} does not initiate leader election and waits for the next election timeout.",
+                    getNodeId());
+                return false;
+            }
+        }
+
+        return this.serverId.getPriority() >= this.targetPriority;
+    }
+
+    /**
+     * Decay targetPriority value based on gap value.
+     */
+    @SuppressWarnings("NonAtomicOperationOnVolatileField")
+    private void decayTargetPriority() {
+        // Default Gap value should be bigger than 10.
+        final int decayPriorityGap = Math.max(this.options.getDecayPriorityGap(), 10);
+        final int gap = Math.max(decayPriorityGap, (this.targetPriority / 5));
+
+        final int prevTargetPriority = this.targetPriority;
+        this.targetPriority = Math.max(ElectionPriority.MinValue, (this.targetPriority - gap));
+        LOG.info("Node {} priority decay, from: {}, to: {}.", getNodeId(), prevTargetPriority, this.targetPriority);
+    }
+
+    /**
+     * Check and set configuration for node.At the same time, if configuration is changed,
+     * then compute and update the target priority value.
+     *
+     * @param inLock whether the writeLock has already been locked in other place.
+     *
+     */
+    private void checkAndSetConfiguration(final boolean inLock) {
+        if (!inLock) {
+            this.writeLock.lock();
+        }
+        try {
+            final ConfigurationEntry prevConf = this.conf;
+            this.conf = this.logManager.checkAndSetConfiguration(prevConf);
+
+            if (this.conf != prevConf) {
+                // Update target priority value
+                final int prevTargetPriority = this.targetPriority;
+                this.targetPriority = getMaxPriorityOfNodes(this.conf.getConf().getPeers());
+                if (prevTargetPriority != this.targetPriority) {
+                    LOG.info("Node {} target priority value has changed from: {}, to: {}.", getNodeId(),
+                        prevTargetPriority, this.targetPriority);
+                }
+                this.electionTimeoutCounter = 0;
+            }
+        } finally {
+            if (!inLock) {
+                this.writeLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Get max priority value for all nodes in the same Raft group, and update current node's target priority value.
+     *
+     * @param peerIds peer nodes in the same Raft group
+     *
+     */
+    private int getMaxPriorityOfNodes(final List<PeerId> peerIds) {
+        Requires.requireNonNull(peerIds, "Null peer list");
+
+        int maxPriority = Integer.MIN_VALUE;
+        for (final PeerId peerId : peerIds) {
+            final int priorityVal = peerId.getPriority();
+            maxPriority = Math.max(priorityVal, maxPriority);
+        }
+
+        return maxPriority;
+    }
+
+    private boolean initFSMCaller(final LogId bootstrapId) {
+        if (this.fsmCaller == null) {
+            LOG.error("Fail to init fsm caller, null instance, bootstrapId={}.", bootstrapId);
+            return false;
+        }
+        this.closureQueue = new ClosureQueueImpl();
+        final FSMCallerOptions opts = new FSMCallerOptions();
+        opts.setAfterShutdown(status -> afterShutdown());
+        opts.setLogManager(this.logManager);
+        opts.setFsm(this.options.getFsm());
+        opts.setClosureQueue(this.closureQueue);
+        opts.setNode(this);
+        opts.setBootstrapId(bootstrapId);
+        opts.setDisruptorBufferSize(this.raftOptions.getDisruptorBufferSize());
+        return this.fsmCaller.init(opts);
+    }
+
+    private static class BootstrapStableClosure extends LogManager.StableClosure {
+
+        private final SynchronizedClosure done = new SynchronizedClosure();
+
+        public BootstrapStableClosure() {
+            super(null);
+        }
+
+        public Status await() throws InterruptedException {
+            return this.done.await();
+        }
+
+        @Override
+        public void run(final Status status) {
+            this.done.run(status);
+        }
+    }
+
+    public boolean bootstrap(final BootstrapOptions opts) throws InterruptedException {
+        if (opts.getLastLogIndex() > 0 && (opts.getGroupConf().isEmpty() || opts.getFsm() == null)) {
+            LOG.error("Invalid arguments for bootstrap, groupConf={}, fsm={}, lastLogIndex={}.", opts.getGroupConf(),
+                opts.getFsm(), opts.getLastLogIndex());
+            return false;
+        }
+        if (opts.getGroupConf().isEmpty()) {
+            LOG.error("Bootstrapping an empty node makes no sense.");
+            return false;
+        }
+        Requires.requireNonNull(opts.getServiceFactory(), "Null jraft service factory");
+        this.serviceFactory = opts.getServiceFactory();
+        // Term is not an option since changing it is very dangerous
+        final long bootstrapLogTerm = opts.getLastLogIndex() > 0 ? 1 : 0;
+        final LogId bootstrapId = new LogId(opts.getLastLogIndex(), bootstrapLogTerm);
+        this.options = new NodeOptions();
+        this.raftOptions = this.options.getRaftOptions();
+        this.metrics = new NodeMetrics(opts.isEnableMetrics());
+        this.options.setFsm(opts.getFsm());
+        this.options.setLogUri(opts.getLogUri());
+        this.options.setRaftMetaUri(opts.getRaftMetaUri());
+        this.options.setSnapshotUri(opts.getSnapshotUri());
+
+        this.configManager = new ConfigurationManager();
+        // Create fsmCaller at first as logManager needs it to report error
+        this.fsmCaller = new FSMCallerImpl();
+
+        if (!initLogStorage()) {
+            LOG.error("Fail to init log storage.");
+            return false;
+        }
+        if (!initMetaStorage()) {
+            LOG.error("Fail to init meta storage.");
+            return false;
+        }
+        if (this.currTerm == 0) {
+            this.currTerm = 1;
+            if (!this.metaStorage.setTermAndVotedFor(1, new PeerId())) {
+                LOG.error("Fail to set term.");
+                return false;
+            }
+        }
+
+        if (opts.getFsm() != null && !initFSMCaller(bootstrapId)) {
+            LOG.error("Fail to init fsm caller.");
+            return false;
+        }
+
+        final LogEntry entry = new LogEntry(EnumOutter.EntryType.ENTRY_TYPE_CONFIGURATION);
+        entry.getId().setTerm(this.currTerm);
+        entry.setPeers(opts.getGroupConf().listPeers());
+        entry.setLearners(opts.getGroupConf().listLearners());
+
+        final List<LogEntry> entries = new ArrayList<>();
+        entries.add(entry);
+
+        final BootstrapStableClosure bootstrapDone = new BootstrapStableClosure();
+        this.logManager.appendEntries(entries, bootstrapDone);
+        if (!bootstrapDone.await().isOk()) {
+            LOG.error("Fail to append configuration.");
+            return false;
+        }
+
+        if (opts.getLastLogIndex() > 0) {
+            if (!initSnapshotStorage()) {
+                LOG.error("Fail to init snapshot storage.");
+                return false;
+            }
+            final SynchronizedClosure snapshotDone = new SynchronizedClosure();
+            this.snapshotExecutor.doSnapshot(snapshotDone);
+            if (!snapshotDone.await().isOk()) {
+                LOG.error("Fail to save snapshot, status={}.", snapshotDone.getStatus());
+                return false;
+            }
+        }
+
+        if (this.logManager.getFirstLogIndex() != opts.getLastLogIndex() + 1) {
+            throw new IllegalStateException("First and last log index mismatch");
+        }
+        if (opts.getLastLogIndex() > 0) {
+            if (this.logManager.getLastLogIndex() != opts.getLastLogIndex()) {
+                throw new IllegalStateException("Last log index mismatch");
+            }
+        } else {
+            if (this.logManager.getLastLogIndex() != opts.getLastLogIndex() + 1) {
+                throw new IllegalStateException("Last log index mismatch");
+            }
+        }
+
+        return true;
+    }
+
+    private int heartbeatTimeout(final int electionTimeout) {
+        return Math.max(electionTimeout / this.raftOptions.getElectionHeartbeatFactor(), 10);
+    }
+
+    private int randomTimeout(final int timeoutMs) {
+        return ThreadLocalRandom.current().nextInt(timeoutMs, timeoutMs + this.raftOptions.getMaxElectionDelayMs());
+    }
+
+    @Override
+    public boolean init(final NodeOptions opts) {
+        Requires.requireNonNull(opts, "Null node options");
+        Requires.requireNonNull(opts.getRaftOptions(), "Null raft options");
+        Requires.requireNonNull(opts.getServiceFactory(), "Null jraft service factory");
+        this.serviceFactory = opts.getServiceFactory();
+        this.options = opts;
+        this.raftOptions = opts.getRaftOptions();
+        this.metrics = new NodeMetrics(opts.isEnableMetrics());
+        this.serverId.setPriority(opts.getElectionPriority());
+        this.electionTimeoutCounter = 0;
+
+        if (this.serverId.getIp().equals(Utils.IP_ANY)) {
+            LOG.error("Node can't started from IP_ANY.");
+            return false;
+        }
+
+        if (!NodeManager.getInstance().serverExists(this.serverId.getEndpoint())) {
+            LOG.error("No RPC server attached to, did you forget to call addService?");
+            return false;
+        }
+
+        this.timerManager = TIMER_FACTORY.getRaftScheduler(this.options.isSharedTimerPool(),
+            this.options.getTimerPoolSize(), "JRaft-Node-ScheduleThreadPool");
+
+        // Init timers
+        final String suffix = getNodeId().toString();
+        String name = "JRaft-VoteTimer-" + suffix;
+        this.voteTimer = new RepeatedTimer(name, this.options.getElectionTimeoutMs(), TIMER_FACTORY.getVoteTimer(
+            this.options.isSharedVoteTimer(), name)) {
+
+            @Override
+            protected void onTrigger() {
+                handleVoteTimeout();
+            }
+
+            @Override
+            protected int adjustTimeout(final int timeoutMs) {
+                return randomTimeout(timeoutMs);
+            }
+        };
+        name = "JRaft-ElectionTimer-" + suffix;
+        this.electionTimer = new RepeatedTimer(name, this.options.getElectionTimeoutMs(),
+            TIMER_FACTORY.getElectionTimer(this.options.isSharedElectionTimer(), name)) {
+
+            @Override
+            protected void onTrigger() {
+                handleElectionTimeout();
+            }
+
+            @Override
+            protected int adjustTimeout(final int timeoutMs) {
+                return randomTimeout(timeoutMs);
+            }
+        };
+        name = "JRaft-StepDownTimer-" + suffix;
+        this.stepDownTimer = new RepeatedTimer(name, this.options.getElectionTimeoutMs() >> 1,
+            TIMER_FACTORY.getStepDownTimer(this.options.isSharedStepDownTimer(), name)) {
+
+            @Override
+            protected void onTrigger() {
+                handleStepDownTimeout();
+            }
+        };
+        name = "JRaft-SnapshotTimer-" + suffix;
+        this.snapshotTimer = new RepeatedTimer(name, this.options.getSnapshotIntervalSecs() * 1000,
+            TIMER_FACTORY.getSnapshotTimer(this.options.isSharedSnapshotTimer(), name)) {
+
+            private volatile boolean firstSchedule = true;
+
+            @Override
+            protected void onTrigger() {
+                handleSnapshotTimeout();
+            }
+
+            @Override
+            protected int adjustTimeout(final int timeoutMs) {
+                if (!this.firstSchedule) {
+                    return timeoutMs;
+                }
+
+                // Randomize the first snapshot trigger timeout
+                this.firstSchedule = false;
+                if (timeoutMs > 0) {
+                    int half = timeoutMs / 2;
+                    return half + ThreadLocalRandom.current().nextInt(half);
+                } else {
+                    return timeoutMs;
+                }
+            }
+        };
+
+        this.configManager = new ConfigurationManager();
+
+        this.applyDisruptor = DisruptorBuilder.<LogEntryAndClosure> newInstance() //
+            .setRingBufferSize(this.raftOptions.getDisruptorBufferSize()) //
+            .setEventFactory(new LogEntryAndClosureFactory()) //
+            .setThreadFactory(new NamedThreadFactory("JRaft-NodeImpl-Disruptor-", true)) //
+            .setProducerType(ProducerType.MULTI) //
+            .setWaitStrategy(new BlockingWaitStrategy()) //
+            .build();
+        this.applyDisruptor.handleEventsWith(new LogEntryAndClosureHandler());
+        this.applyDisruptor.setDefaultExceptionHandler(new LogExceptionHandler<Object>(getClass().getSimpleName()));
+        this.applyQueue = this.applyDisruptor.start();
+        if (this.metrics.getMetricRegistry() != null) {
+            this.metrics.getMetricRegistry().register("jraft-node-impl-disruptor",
+                new DisruptorMetricSet(this.applyQueue));
+        }
+
+        this.fsmCaller = new FSMCallerImpl();
+        if (!initLogStorage()) {
+            LOG.error("Node {} initLogStorage failed.", getNodeId());
+            return false;
+        }
+        if (!initMetaStorage()) {
+            LOG.error("Node {} initMetaStorage failed.", getNodeId());
+            return false;
+        }
+        if (!initFSMCaller(new LogId(0, 0))) {
+            LOG.error("Node {} initFSMCaller failed.", getNodeId());
+            return false;
+        }
+        this.ballotBox = new BallotBox();
+        final BallotBoxOptions ballotBoxOpts = new BallotBoxOptions();
+        ballotBoxOpts.setWaiter(this.fsmCaller);
+        ballotBoxOpts.setClosureQueue(this.closureQueue);
+        if (!this.ballotBox.init(ballotBoxOpts)) {
+            LOG.error("Node {} init ballotBox failed.", getNodeId());
+            return false;
+        }
+
+        if (!initSnapshotStorage()) {
+            LOG.error("Node {} initSnapshotStorage failed.", getNodeId());
+            return false;
+        }
+
+        final Status st = this.logManager.checkConsistency();
+        if (!st.isOk()) {
+            LOG.error("Node {} is initialized with inconsistent log, status={}.", getNodeId(), st);
+            return false;
+        }
+        this.conf = new ConfigurationEntry();
+        this.conf.setId(new LogId());
+        // if have log using conf in log, else using conf in options
+        if (this.logManager.getLastLogIndex() > 0) {
+            checkAndSetConfiguration(false);
+        } else {
+            this.conf.setConf(this.options.getInitialConf());
+            // initially set to max(priority of all nodes)
+            this.targetPriority = getMaxPriorityOfNodes(this.conf.getConf().getPeers());
+        }
+
+        if (!this.conf.isEmpty()) {
+            Requires.requireTrue(this.conf.isValid(), "Invalid conf: %s", this.conf);
+        } else {
+            LOG.info("Init node {} with empty conf.", this.serverId);
+        }
+
+        // TODO RPC service and ReplicatorGroup is in cycle dependent, refactor it
+        this.replicatorGroup = new ReplicatorGroupImpl();
+        this.rpcService = new DefaultRaftClientService(this.replicatorGroup);
+        final ReplicatorGroupOptions rgOpts = new ReplicatorGroupOptions();
+        rgOpts.setHeartbeatTimeoutMs(heartbeatTimeout(this.options.getElectionTimeoutMs()));
+        rgOpts.setElectionTimeoutMs(this.options.getElectionTimeoutMs());
+        rgOpts.setLogManager(this.logManager);
+        rgOpts.setBallotBox(this.ballotBox);
+        rgOpts.setNode(this);
+        rgOpts.setRaftRpcClientService(this.rpcService);
+        rgOpts.setSnapshotStorage(this.snapshotExecutor != null ? this.snapshotExecutor.getSnapshotStorage() : null);
+        rgOpts.setRaftOptions(this.raftOptions);
+        rgOpts.setTimerManager(this.timerManager);
+
+        // Adds metric registry to RPC service.
+        this.options.setMetricRegistry(this.metrics.getMetricRegistry());
+
+        if (!this.rpcService.init(this.options)) {
+            LOG.error("Fail to init rpc service.");
+            return false;
+        }
+        this.replicatorGroup.init(new NodeId(this.groupId, this.serverId), rgOpts);
+
+        this.readOnlyService = new ReadOnlyServiceImpl();
+        final ReadOnlyServiceOptions rosOpts = new ReadOnlyServiceOptions();
+        rosOpts.setFsmCaller(this.fsmCaller);
+        rosOpts.setNode(this);
+        rosOpts.setRaftOptions(this.raftOptions);
+
+        if (!this.readOnlyService.init(rosOpts)) {
+            LOG.error("Fail to init readOnlyService.");
+            return false;
+        }
+
+        // set state to follower
+        this.state = State.STATE_FOLLOWER;
+
+        if (LOG.isInfoEnabled()) {
+            LOG.info("Node {} init, term={}, lastLogId={}, conf={}, oldConf={}.", getNodeId(), this.currTerm,
+                this.logManager.getLastLogId(false), this.conf.getConf(), this.conf.getOldConf());
+        }
+
+        if (this.snapshotExecutor != null && this.options.getSnapshotIntervalSecs() > 0) {
+            LOG.debug("Node {} start snapshot timer, term={}.", getNodeId(), this.currTerm);
+            this.snapshotTimer.start();
+        }
+
+        if (!this.conf.isEmpty()) {
+            stepDown(this.currTerm, false, new Status());
+        }
+
+        if (!NodeManager.getInstance().add(this)) {
+            LOG.error("NodeManager add {} failed.", getNodeId());
+            return false;
+        }
+
+        // Now the raft node is started , have to acquire the writeLock to avoid race
+        // conditions
+        this.writeLock.lock();
+        if (this.conf.isStable() && this.conf.getConf().size() == 1 && this.conf.getConf().contains(this.serverId)) {
+            // The group contains only this server which must be the LEADER, trigger
+            // the timer immediately.
+            electSelf();
+        } else {
+            this.writeLock.unlock();
+        }
+
+        return true;
+    }
+
+    @OnlyForTest
+    void tryElectSelf() {
+        this.writeLock.lock();
+        // unlock in electSelf
+        electSelf();
+    }
+
+    // should be in writeLock
+    private void electSelf() {
+        long oldTerm;
+        try {
+            LOG.info("Node {} start vote and grant vote self, term={}.", getNodeId(), this.currTerm);
+            if (!this.conf.contains(this.serverId)) {
+                LOG.warn("Node {} can't do electSelf as it is not in {}.", getNodeId(), this.conf);
+                return;
+            }
+            if (this.state == State.STATE_FOLLOWER) {
+                LOG.debug("Node {} stop election timer, term={}.", getNodeId(), this.currTerm);
+                this.electionTimer.stop();
+            }
+            resetLeaderId(PeerId.emptyPeer(), new Status(RaftError.ERAFTTIMEDOUT,
+                "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();
+            LOG.debug("Node {} start vote timer, term={} .", getNodeId(), this.currTerm);
+            this.voteTimer.start();
+            this.voteCtx.init(this.conf.getConf(), this.conf.isStable() ? null : this.conf.getOldConf());
+            oldTerm = this.currTerm;
+        } finally {
+            this.writeLock.unlock();
+        }
+
+        final LogId lastLogId = this.logManager.getLastLogId(true);
+
+        this.writeLock.lock();
+        try {
+            // vote need defense ABA after unlock&writeLock
+            if (oldTerm != this.currTerm) {
+                LOG.warn("Node {} raise term {} when getLastLogId.", getNodeId(), this.currTerm);
+                return;
+            }
+            for (final PeerId peer : this.conf.listPeers()) {
+                if (peer.equals(this.serverId)) {
+                    continue;
+                }
+                if (!this.rpcService.connect(peer.getEndpoint())) {
+                    LOG.warn("Node {} channel init failed, address={}.", getNodeId(), peer.getEndpoint());
+                    continue;
+                }
+                final OnRequestVoteRpcDone done = new OnRequestVoteRpcDone(peer, this.currTerm, this);
+                done.request = RequestVoteRequest.newBuilder() //
+                    .setPreVote(false) // It's not a pre-vote request.
+                    .setGroupId(this.groupId) //
+                    .setServerId(this.serverId.toString()) //
+                    .setPeerId(peer.toString()) //
+                    .setTerm(this.currTerm) //
+                    .setLastLogIndex(lastLogId.getIndex()) //
+                    .setLastLogTerm(lastLogId.getTerm()) //
+                    .build();
+                this.rpcService.requestVote(peer.getEndpoint(), done.request, done);
+            }
+
+            this.metaStorage.setTermAndVotedFor(this.currTerm, this.serverId);
+            this.voteCtx.grant(this.serverId);
+            if (this.voteCtx.isGranted()) {
+                becomeLeader();
+            }
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    private void resetLeaderId(final PeerId newLeaderId, final Status status) {
+        if (newLeaderId.isEmpty()) {
+            if (!this.leaderId.isEmpty() && this.state.compareTo(State.STATE_TRANSFERRING) > 0) {
+                this.fsmCaller.onStopFollowing(new LeaderChangeContext(this.leaderId.copy(), this.currTerm, status));
+            }
+            this.leaderId = PeerId.emptyPeer();
+        } else {
+            if (this.leaderId == null || this.leaderId.isEmpty()) {
+                this.fsmCaller.onStartFollowing(new LeaderChangeContext(newLeaderId, this.currTerm, status));
+            }
+            this.leaderId = newLeaderId.copy();
+        }
+    }
+
+    // in writeLock
+    private void checkStepDown(final long requestTerm, final PeerId serverId) {
+        final Status status = new Status();
+        if (requestTerm > this.currTerm) {
+            status.setError(RaftError.ENEWLEADER, "Raft node receives message from new leader with higher term.");
+            stepDown(requestTerm, false, status);
+        } else if (this.state != State.STATE_FOLLOWER) {
+            status.setError(RaftError.ENEWLEADER, "Candidate receives message from new leader with the same term.");
+            stepDown(requestTerm, false, status);
+        } else if (this.leaderId.isEmpty()) {
+            status.setError(RaftError.ENEWLEADER, "Follower receives message from new leader with the same term.");
+            stepDown(requestTerm, false, status);
+        }
+        // save current leader
+        if (this.leaderId == null || this.leaderId.isEmpty()) {
+            resetLeaderId(serverId, status);
+        }
+    }
+
+    private void becomeLeader() {
+        Requires.requireTrue(this.state == State.STATE_CANDIDATE, "Illegal state: " + this.state);
+        LOG.info("Node {} become leader of group, term={}, conf={}, oldConf={}.", getNodeId(), this.currTerm,
+            this.conf.getConf(), this.conf.getOldConf());
+        // cancel candidate vote timer
+        stopVoteTimer();
+        this.state = State.STATE_LEADER;
+        this.leaderId = this.serverId.copy();
+        this.replicatorGroup.resetTerm(this.currTerm);
+        // Start follower's replicators
+        for (final PeerId peer : this.conf.listPeers()) {
+            if (peer.equals(this.serverId)) {
+                continue;
+            }
+            LOG.debug("Node {} add a replicator, term={}, peer={}.", getNodeId(), this.currTerm, peer);
+            if (!this.replicatorGroup.addReplicator(peer)) {
+                LOG.error("Fail to add a replicator, peer={}.", peer);
+            }
+        }
+
+        // Start learner's replicators
+        for (final PeerId peer : this.conf.listLearners()) {
+            LOG.debug("Node {} add a learner replicator, term={}, peer={}.", getNodeId(), this.currTerm, peer);
+            if (!this.replicatorGroup.addReplicator(peer, ReplicatorType.Learner)) {
+                LOG.error("Fail to add a learner replicator, peer={}.", peer);
+            }
+        }
+
+        // init commit manager
+        this.ballotBox.resetPendingIndex(this.logManager.getLastLogIndex() + 1);
+        // Register _conf_ctx to reject configuration changing before the first log
+        // is committed.
+        if (this.confCtx.isBusy()) {
+            throw new IllegalStateException();
+        }
+        this.confCtx.flush(this.conf.getConf(), this.conf.getOldConf());
+        this.stepDownTimer.start();
+    }
+
+    // should be in writeLock
+    private void stepDown(final long term, final boolean wakeupCandidate, final Status status) {
+        LOG.debug("Node {} stepDown, term={}, newTerm={}, wakeupCandidate={}.", getNodeId(), this.currTerm, term,
+            wakeupCandidate);
+        if (!this.state.isActive()) {
+            return;
+        }
+        if (this.state == State.STATE_CANDIDATE) {
+            stopVoteTimer();
+        } else if (this.state.compareTo(State.STATE_TRANSFERRING) <= 0) {
+            stopStepDownTimer();
+            this.ballotBox.clearPendingTasks();
+            // signal fsm leader stop immediately
+            if (this.state == State.STATE_LEADER) {
+                onLeaderStop(status);
+            }
+        }
+        // reset leader_id
+        resetLeaderId(PeerId.emptyPeer(), status);
+
+        // soft state in memory
+        this.state = State.STATE_FOLLOWER;
+        this.confCtx.reset();
+        updateLastLeaderTimestamp(Utils.monotonicMs());
+        if (this.snapshotExecutor != null) {
+            this.snapshotExecutor.interruptDownloadingSnapshots(term);
+        }
+
+        // meta state
+        if (term > this.currTerm) {
+            this.currTerm = term;
+            this.votedId = PeerId.emptyPeer();
+            this.metaStorage.setTermAndVotedFor(term, this.votedId);
+        }
+
+        if (wakeupCandidate) {
+            this.wakingCandidate = this.replicatorGroup.stopAllAndFindTheNextCandidate(this.conf);
+            if (this.wakingCandidate != null) {
+                Replicator.sendTimeoutNowAndStop(this.wakingCandidate, this.options.getElectionTimeoutMs());
+            }
+        } else {
+            this.replicatorGroup.stopAll();
+        }
+        if (this.stopTransferArg != null) {
+            if (this.transferTimer != null) {
+                this.transferTimer.cancel(true);
+            }
+            // There is at most one StopTransferTimer at the same term, it's safe to
+            // mark stopTransferArg to NULL
+            this.stopTransferArg = null;
+        }
+        // Learner node will not trigger the election timer.
+        if (!isLearner()) {
+            this.electionTimer.restart();
+        } else {
+            LOG.info("Node {} is a learner, election timer is not started.", this.nodeId);
+        }
+    }
+
+    // Should be in readLock
+    private boolean isLearner() {
+        return this.conf.listLearners().contains(this.serverId);
+    }
+
+    private void stopStepDownTimer() {
+        if (this.stepDownTimer != null) {
+            this.stepDownTimer.stop();
+        }
+    }
+
+    private void stopVoteTimer() {
+        if (this.voteTimer != null) {
+            this.voteTimer.stop();
+        }
+    }
+
+    class LeaderStableClosure extends LogManager.StableClosure {
+
+        public LeaderStableClosure(final List<LogEntry> entries) {
+            super(entries);
+        }
+
+        @Override
+        public void run(final Status status) {
+            if (status.isOk()) {
+                NodeImpl.this.ballotBox.commitAt(this.firstLogIndex, this.firstLogIndex + this.nEntries - 1,
+                    NodeImpl.this.serverId);
+            } else {
+                LOG.error("Node {} append [{}, {}] failed, status={}.", getNodeId(), this.firstLogIndex,
+                    this.firstLogIndex + this.nEntries - 1, status);
+            }
+        }
+    }
+
+    private void executeApplyingTasks(final List<LogEntryAndClosure> tasks) {
+        this.writeLock.lock();
+        try {
+            final int size = tasks.size();
+            if (this.state != State.STATE_LEADER) {
+                final Status st = new Status();
+                if (this.state != State.STATE_TRANSFERRING) {
+                    st.setError(RaftError.EPERM, "Is not leader.");
+                } else {
+                    st.setError(RaftError.EBUSY, "Is transferring leadership.");
+                }
+                LOG.debug("Node {} can't apply, status={}.", getNodeId(), st);
+                final List<LogEntryAndClosure> savedTasks = new ArrayList<>(tasks);
+                Utils.runInThread(() -> {
+                    for (int i = 0; i < size; i++) {
+                        savedTasks.get(i).done.run(st);
+                    }
+                });
+                return;
+            }
+            final List<LogEntry> entries = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                final LogEntryAndClosure task = tasks.get(i);
+                if (task.expectedTerm != -1 && task.expectedTerm != this.currTerm) {
+                    LOG.debug("Node {} can't apply task whose expectedTerm={} doesn't match currTerm={}.", getNodeId(),
+                        task.expectedTerm, this.currTerm);
+                    if (task.done != null) {
+                        final Status st = new Status(RaftError.EPERM, "expected_term=%d doesn't match current_term=%d",
+                            task.expectedTerm, this.currTerm);
+                        Utils.runClosureInThread(task.done, st);
+                    }
+                    continue;
+                }
+                if (!this.ballotBox.appendPendingTask(this.conf.getConf(),
+                    this.conf.isStable() ? null : this.conf.getOldConf(), task.done)) {
+                    Utils.runClosureInThread(task.done, new Status(RaftError.EINTERNAL, "Fail to append task."));
+                    continue;
+                }
+                // set task entry info before adding to list.
+                task.entry.getId().setTerm(this.currTerm);
+                task.entry.setType(EnumOutter.EntryType.ENTRY_TYPE_DATA);
+                entries.add(task.entry);
+            }
+            this.logManager.appendEntries(entries, new LeaderStableClosure(entries));
+            // update conf.first
+            checkAndSetConfiguration(true);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    /**
+     * Returns the node metrics.
+     *
+     * @return returns metrics of current node.
+     */
+    @Override
+    public NodeMetrics getNodeMetrics() {
+        return this.metrics;
+    }
+
+    /**
+     * Returns the JRaft service factory for current node.
+     * @since 1.2.6
+     * @return the service factory
+     */
+    public JRaftServiceFactory getServiceFactory() {
+        return this.serviceFactory;
+    }
+
+    @Override
+    public void readIndex(final byte[] requestContext, final ReadIndexClosure done) {
+        if (this.shutdownLatch != null) {
+            Utils.runClosureInThread(done, new Status(RaftError.ENODESHUTDOWN, "Node is shutting down."));
+            throw new IllegalStateException("Node is shutting down");
+        }
+        Requires.requireNonNull(done, "Null closure");
+        this.readOnlyService.addRequest(requestContext, done);
+    }
+
+    /**
+     * ReadIndex response closure
+     * @author dennis
+     */
+    private class ReadIndexHeartbeatResponseClosure extends RpcResponseClosureAdapter<AppendEntriesResponse> {
+        final ReadIndexResponse.Builder             respBuilder;
+        final RpcResponseClosure<ReadIndexResponse> closure;
+        final int                                   quorum;
+        final int                                   failPeersThreshold;
+        int                                         ackSuccess;
+        int                                         ackFailures;
+        boolean                                     isDone;
+
+        public ReadIndexHeartbeatResponseClosure(final RpcResponseClosure<ReadIndexResponse> closure,
+                                                 final ReadIndexResponse.Builder rb, final int quorum,
+                                                 final int peersCount) {
+            super();
+            this.closure = closure;
+            this.respBuilder = rb;
+            this.quorum = quorum;
+            this.failPeersThreshold = peersCount % 2 == 0 ? (quorum - 1) : quorum;
+            this.ackSuccess = 0;
+            this.ackFailures = 0;
+            this.isDone = false;
+        }
+
+        @Override
+        public synchronized void run(final Status status) {
+            if (this.isDone) {
+                return;
+            }
+            if (status.isOk() && getResponse().getSuccess()) {
+                this.ackSuccess++;
+            } else {
+                this.ackFailures++;
+            }
+            // Include leader self vote yes.
+            if (this.ackSuccess + 1 >= this.quorum) {
+                this.respBuilder.setSuccess(true);
+                this.closure.setResponse(this.respBuilder.build());
+                this.closure.run(Status.OK());
+                this.isDone = true;
+            } else if (this.ackFailures >= this.failPeersThreshold) {
+                this.respBuilder.setSuccess(false);
+                this.closure.setResponse(this.respBuilder.build());
+                this.closure.run(Status.OK());
+                this.isDone = true;
+            }
+        }
+    }
+
+    /**
+     * Handle read index request.
+     */
+    @Override
+    public void handleReadIndexRequest(final ReadIndexRequest request, final RpcResponseClosure<ReadIndexResponse> done) {
+        final long startMs = Utils.monotonicMs();
+        this.readLock.lock();
+        try {
+            switch (this.state) {
+                case STATE_LEADER:
+                    readLeader(request, ReadIndexResponse.newBuilder(), done);
+                    break;
+                case STATE_FOLLOWER:
+                    readFollower(request, done);
+                    break;
+                case STATE_TRANSFERRING:
+                    done.run(new Status(RaftError.EBUSY, "Is transferring leadership."));
+                    break;
+                default:
+                    done.run(new Status(RaftError.EPERM, "Invalid state for readIndex: %s.", this.state));
+                    break;
+            }
+        } finally {
+            this.readLock.unlock();
+            this.metrics.recordLatency("handle-read-index", Utils.monotonicMs() - startMs);
+            this.metrics.recordSize("handle-read-index-entries", request.getEntriesCount());
+        }
+    }
+
+    private int getQuorum() {
+        final Configuration c = this.conf.getConf();
+        if (c.isEmpty()) {
+            return 0;
+        }
+        return c.getPeers().size() / 2 + 1;
+    }
+
+    private void readFollower(final ReadIndexRequest request, final RpcResponseClosure<ReadIndexResponse> closure) {
+        if (this.leaderId == null || this.leaderId.isEmpty()) {
+            closure.run(new Status(RaftError.EPERM, "No leader at term %d.", this.currTerm));
+            return;
+        }
+        // send request to leader.
+        final ReadIndexRequest newRequest = ReadIndexRequest.newBuilder() //
+            .mergeFrom(request) //
+            .setPeerId(this.leaderId.toString()) //
+            .build();
+        this.rpcService.readIndex(this.leaderId.getEndpoint(), newRequest, -1, closure);
+    }
+
+    private void readLeader(final ReadIndexRequest request, final ReadIndexResponse.Builder respBuilder,
+                            final RpcResponseClosure<ReadIndexResponse> closure) {
+        final int quorum = getQuorum();
+        if (quorum <= 1) {
+            // Only one peer, fast path.
+            respBuilder.setSuccess(true) //
+                .setIndex(this.ballotBox.getLastCommittedIndex());
+            closure.setResponse(respBuilder.build());
+            closure.run(Status.OK());
+            return;
+        }
+
+        final long lastCommittedIndex = this.ballotBox.getLastCommittedIndex();
+        if (this.logManager.getTerm(lastCommittedIndex) != this.currTerm) {
+            // Reject read only request when this leader has not committed any log entry at its term
+            closure
+                .run(new Status(
+                    RaftError.EAGAIN,
+                    "ReadIndex request rejected because leader has not committed any log entry at its term, logIndex=%d, currTerm=%d.",
+                    lastCommittedIndex, this.currTerm));
+            return;
+        }
+        respBuilder.setIndex(lastCommittedIndex);
+
+        if (request.getPeerId() != null) {
+            // request from follower or learner, check if the follower/learner is in current conf.
+            final PeerId peer = new PeerId();
+            peer.parse(request.getServerId());
+            if (!this.conf.contains(peer) && !this.conf.containsLearner(peer)) {
+                closure
+                    .run(new Status(RaftError.EPERM, "Peer %s is not in current configuration: %s.", peer, this.conf));
+                return;
+            }
+        }
+
+        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 List<PeerId> peers = this.conf.getConf().getPeers();
+                Requires.requireTrue(peers != null && !peers.isEmpty(), "Empty peers");
+                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)) {
+                        continue;
+                    }
+                    this.replicatorGroup.sendHeartbeat(peer, heartbeatDone);
+                }
+                break;
+            case ReadOnlyLeaseBased:
+                // Responses to followers and local node.
+                respBuilder.setSuccess(true);
+                closure.setResponse(respBuilder.build());
+                closure.run(Status.OK());
+                break;
+        }
+    }
+
+    @Override
+    public void apply(final Task task) {
+        if (this.shutdownLatch != null) {
+            Utils.runClosureInThread(task.getDone(), new Status(RaftError.ENODESHUTDOWN, "Node is shutting down."));
+            throw new IllegalStateException("Node is shutting down");
+        }
+        Requires.requireNonNull(task, "Null task");
+
+        final LogEntry entry = new LogEntry();
+        entry.setData(task.getData());
+        int retryTimes = 0;
+        try {
+            final EventTranslator<LogEntryAndClosure> translator = (event, sequence) -> {
+                event.reset();
+                event.done = task.getDone();
+                event.entry = entry;
+                event.expectedTerm = task.getExpectedTerm();
+            };
+            while (true) {
+                if (this.applyQueue.tryPublishEvent(translator)) {
+                    break;
+                } else {
+                    retryTimes++;
+                    if (retryTimes > MAX_APPLY_RETRY_TIMES) {
+                        Utils.runClosureInThread(task.getDone(),
+                            new Status(RaftError.EBUSY, "Node is busy, has too many tasks."));
+                        LOG.warn("Node {} applyQueue is overload.", getNodeId());
+                        this.metrics.recordTimes("apply-task-overload-times", 1);
+                        return;
+                    }
+                    ThreadHelper.onSpinWait();
+                }
+            }
+
+        } catch (final Exception e) {
+            LOG.error("Fail to apply task.", e);
+            Utils.runClosureInThread(task.getDone(), new Status(RaftError.EPERM, "Node is down."));
+        }
+    }
+
+    @Override
+    public Message handlePreVoteRequest(final RequestVoteRequest request) {
+        boolean doUnlock = true;
+        this.writeLock.lock();
+        try {
+            if (!this.state.isActive()) {
+                LOG.warn("Node {} is not in active state, currTerm={}.", getNodeId(), this.currTerm);
+                return RpcFactoryHelper //
+                    .responseFactory() //
+                    .newResponse(RequestVoteResponse.getDefaultInstance(), RaftError.EINVAL,
+                        "Node %s is not in active state, state %s.", getNodeId(), this.state.name());
+            }
+            final PeerId candidateId = new PeerId();
+            if (!candidateId.parse(request.getServerId())) {
+                LOG.warn("Node {} received PreVoteRequest from {} serverId bad format.", getNodeId(),
+                    request.getServerId());
+                return RpcFactoryHelper //
+                    .responseFactory() //
+                    .newResponse(RequestVoteResponse.getDefaultInstance(), RaftError.EINVAL,
+                        "Parse candidateId failed: %s.", request.getServerId());
+            }
+            boolean granted = false;
+            // noinspection ConstantConditions
+            do {
+                if (!this.conf.contains(candidateId)) {
+                    LOG.warn("Node {} ignore PreVoteRequest from {} as it is not in conf <{}>.", getNodeId(),
+                        request.getServerId(), this.conf);
+                    break;
+                }
+                if (this.leaderId != null && !this.leaderId.isEmpty() && isCurrentLeaderValid()) {
+                    LOG.info(
+                        "Node {} ignore PreVoteRequest from {}, term={}, currTerm={}, because the leader {}'s lease is still valid.",
+                        getNodeId(), request.getServerId(), request.getTerm(), this.currTerm, this.leaderId);
+                    break;
+                }
+                if (request.getTerm() < this.currTerm) {
+                    LOG.info("Node {} ignore PreVoteRequest from {}, term={}, currTerm={}.", getNodeId(),
+                        request.getServerId(), request.getTerm(), this.currTerm);
+                    // A follower replicator may not be started when this node become leader, so we must check it.
+                    checkReplicator(candidateId);
+                    break;
+                }
+                // A follower replicator may not be started when this node become leader, so we must check it.
+                // check replicator state
+                checkReplicator(candidateId);
+
+                doUnlock = false;
+                this.writeLock.unlock();
+
+                final LogId lastLogId = this.logManager.getLastLogId(true);
+
+                doUnlock = true;
+                this.writeLock.lock();
+                final LogId requestLastLogId = new LogId(request.getLastLogIndex(), request.getLastLogTerm());
+                granted = requestLastLogId.compareTo(lastLogId) >= 0;
+
+                LOG.info(
+                    "Node {} received PreVoteRequest from {}, term={}, currTerm={}, granted={}, requestLastLogId={}, lastLogId={}.",
+                    getNodeId(), request.getServerId(), request.getTerm(), this.currTerm, granted, requestLastLogId,
+                    lastLogId);
+            } while (false);
+
+            return RequestVoteResponse.newBuilder() //
+                .setTerm(this.currTerm) //
+                .setGranted(granted) //
+                .build();
+        } finally {
+            if (doUnlock) {
+                this.writeLock.unlock();
+            }
+        }
+    }
+
+    // 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(final long monotonicNowMs) {
+        return monotonicNowMs - this.lastLeaderTimestamp < this.options.getLeaderLeaseTimeoutMs();
+    }
+
+    private boolean isCurrentLeaderValid() {
+        return Utils.monotonicMs() - this.lastLeaderTimestamp < this.options.getElectionTimeoutMs();
+    }
+
+    private void updateLastLeaderTimestamp(final long lastLeaderTimestamp) {
+        this.lastLeaderTimestamp = lastLeaderTimestamp;
+    }
+
+    private void checkReplicator(final PeerId candidateId) {
+        if (this.state == State.STATE_LEADER) {
+            this.replicatorGroup.checkReplicator(candidateId, false);
+        }
+    }
+
+    @Override
+    public Message handleRequestVoteRequest(final RequestVoteRequest request) {
+        boolean doUnlock = true;
+        this.writeLock.lock();
+        try {
+            if (!this.state.isActive()) {
+                LOG.warn("Node {} is not in active state, currTerm={}.", getNodeId(), this.currTerm);
+                return RpcFactoryHelper //
+                    .responseFactory() //
+                    .newResponse(RequestVoteResponse.getDefaultInstance(), RaftError.EINVAL,
+                        "Node %s is not in active state, state %s.", getNodeId(), this.state.name());
+            }
+            final PeerId candidateId = new PeerId();
+            if (!candidateId.parse(request.getServerId())) {
+                LOG.warn("Node {} received RequestVoteRequest from {} serverId bad format.", getNodeId(),
+                    request.getServerId());
+                return RpcFactoryHelper //
+                    .responseFactory() //
+                    .newResponse(RequestVoteResponse.getDefaultInstance(), RaftError.EINVAL,
+                        "Parse candidateId failed: %s.", request.getServerId());
+            }
+
+            // noinspection ConstantConditions
+            do {
+                // check term
+                if (request.getTerm() >= this.currTerm) {
+                    LOG.info("Node {} received RequestVoteRequest from {}, term={}, currTerm={}.", getNodeId(),
+                        request.getServerId(), request.getTerm(), this.currTerm);
+                    // increase current term, change state to follower
+                    if (request.getTerm() > this.currTerm) {
+                        stepDown(request.getTerm(), false, new Status(RaftError.EHIGHERTERMRESPONSE,
+                            "Raft node receives higher term RequestVoteRequest."));
+                    }
+                } else {
+                    // ignore older term
+                    LOG.info("Node {} ignore RequestVoteRequest from {}, term={}, currTerm={}.", getNodeId(),
+                        request.getServerId(), request.getTerm(), this.currTerm);
+                    break;
+                }
+                doUnlock = false;
+                this.writeLock.unlock();
+
+                final LogId lastLogId = this.logManager.getLastLogId(true);
+
+                doUnlock = true;
+                this.writeLock.lock();
+                // vote need ABA check after unlock&writeLock
+                if (request.getTerm() != this.currTerm) {
+                    LOG.warn("Node {} raise term {} when get lastLogId.", getNodeId(), this.currTerm);
+                    break;
+                }
+
+                final boolean logIsOk = new LogId(request.getLastLogIndex(), request.getLastLogTerm())
+                    .compareTo(lastLogId) >= 0;
+
+                if (logIsOk && (this.votedId == null || this.votedId.isEmpty())) {
+                    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);
+
+            return RequestVoteResponse.newBuilder() //
+                .setTerm(this.currTerm) //
+                .setGranted(request.getTerm() == this.currTerm && candidateId.equals(this.votedId)) //
+                .build();
+        } finally {
+            if (doUnlock) {
+                this.writeLock.unlock();
+            }
+        }
+    }
+
+    private static class FollowerStableClosure extends LogManager.StableClosure {
+
+        final long                          committedIndex;
+        final AppendEntriesResponse.Builder responseBuilder;
+        final NodeImpl                      node;
+        final RpcRequestClosure             done;
+        final long                          term;
+
+        public FollowerStableClosure(final AppendEntriesRequest request,
+                                     final AppendEntriesResponse.Builder responseBuilder, final NodeImpl node,
+                                     final RpcRequestClosure done, final long term) {
+            super(null);
+            this.committedIndex = Math.min(
+            // 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());
+            this.responseBuilder = responseBuilder;
+            this.node = node;
+            this.done = done;
+            this.term = term;
+        }
+
+        @Override
+        public void run(final Status status) {
+
+            if (!status.isOk()) {
+                this.done.run(status);
+                return;
+            }
+
+            this.node.readLock.lock();
+            try {
+                if (this.term != this.node.currTerm) {
+                    // The change of term indicates that leader has been changed during
+                    // appending entries, so we can't respond ok to the old leader
+                    // because we are not sure if the appended logs would be truncated
+                    // by the new leader:
+                    //  - If they won't be truncated and we respond failure to the old
+                    //    leader, the new leader would know that they are stored in this
+                    //    peer and they will be eventually committed when the new leader
+                    //    found that quorum of the cluster have stored.
+                    //  - If they will be truncated and we responded success to the old
+                    //    leader, the old leader would possibly regard those entries as
+                    //    committed (very likely in a 3-nodes cluster) and respond
+                    //    success to the clients, which would break the rule that
+                    //    committed entries would never be truncated.
+                    // So we have to respond failure to the old leader and set the new
+                    // term to make it stepped down if it didn't.
+                    this.responseBuilder.setSuccess(false).setTerm(this.node.currTerm);
+                    this.done.sendResponse(this.responseBuilder.build());
+                    return;
+                }
+            } finally {
+                // It's safe to release lock as we know everything is ok at this point.
+                this.node.readLock.unlock();
+            }
+
+            // Don't touch node any more.
+            this.responseBuilder.setSuccess(true).setTerm(this.term);
+
+            // Ballot box is thread safe and tolerates disorder.
+            this.node.ballotBox.setLastCommittedIndex(this.committedIndex);
+
+            this.done.sendResponse(this.responseBuilder.build());
+        }
+    }
+
+    @Override
+    public Message handleAppendEntriesRequest(final AppendEntriesRequest request, final RpcRequestClosure done) {
+        boolean doUnlock = true;
+        final long startMs = Utils.monotonicMs();
+        this.writeLock.lock();
+        final int entriesCount = request.getEntriesCount();
+        try {
+            if (!this.state.isActive()) {
+                LOG.warn("Node {} is not in active state, currTerm={}.", getNodeId(), this.currTerm);
+                return RpcFactoryHelper //
+                    .responseFactory() //
+                    .newResponse(AppendEntriesResponse.getDefaultInstance(), RaftError.EINVAL,
+                        "Node %s is not in active state, state %s.", getNodeId(), this.state.name());
+            }
+
+            final PeerId serverId = new PeerId();
+            if (!serverId.parse(request.getServerId())) {
+                LOG.warn("Node {} received AppendEntriesRequest from {} serverId bad format.", getNodeId(),
+                    request.getServerId());
+                return RpcFactoryHelper //
+                    .responseFactory() //
+                    .newResponse(AppendEntriesResponse.getDefaultInstance(), RaftError.EINVAL,
+                        "Parse serverId failed: %s.", request.getServerId());
+            }
+
+            // Check stale term
+            if (request.getTerm() < this.currTerm) {
+                LOG.warn("Node {} ignore stale AppendEntriesRequest from {}, term={}, currTerm={}.", getNodeId(),
+                    request.getServerId(), request.getTerm(), this.currTerm);
+                return AppendEntriesResponse.newBuilder() //
+                    .setSuccess(false) //
+                    .setTerm(this.currTerm) //
+                    .build();
+            }
+
+            // Check term and state to step down
+            checkStepDown(request.getTerm(), serverId);
+            if (!serverId.equals(this.leaderId)) {
+                LOG.error("Another peer {} declares that it is the leader at term {} which was occupied by leader {}.",
+                    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."));
+                return AppendEntriesResponse.newBuilder() //
+                    .setSuccess(false) //
+                    .setTerm(request.getTerm() + 1) //
+                    .build();
+            }
+
+            updateLastLeaderTimestamp(Utils.monotonicMs());
+
+            if (entriesCount > 0 && this.snapshotExecutor != null && this.snapshotExecutor.isInstallingSnapshot()) {
+                LOG.warn("Node {} received AppendEntriesRequest while installing snapshot.", getNodeId());
+                return RpcFactoryHelper //
+                    .responseFactory() //
+                    .newResponse(AppendEntriesResponse.getDefaultInstance(), RaftError.EBUSY,
+                        "Node %s:%s is installing snapshot.", this.groupId, this.serverId);
+            }
+
+            final long prevLogIndex = request.getPrevLogIndex();
+            final long prevLogTerm = request.getPrevLogTerm();
+            final long localPrevLogTerm = this.logManager.getTerm(prevLogIndex);
+            if (localPrevLogTerm != prevLogTerm) {
+                final long lastLogIndex = this.logManager.getLastLogIndex();
+
+                LOG.warn(
+                    "Node {} reject term_unmatched AppendEntriesRequest from {}, term={}, prevLogIndex={}, prevLogTerm={}, localPrevLogTerm={}, lastLogIndex={}, entriesSize={}.",
+                    getNodeId(), request.getServerId(), request.getTerm(), prevLogIndex, prevLogTerm, localPrevLogTerm,
+                    lastLogIndex, entriesCount);
+
+                return AppendEntriesResponse.newBuilder() //
+                    .setSuccess(false) //
+                    .setTerm(this.currTerm) //
+                    .setLastLogIndex(lastLogIndex) //
+                    .build();
+            }
+
+            if (entriesCount == 0) {
+                // heartbeat or probe request
+                final AppendEntriesResponse.Builder respBuilder = AppendEntriesResponse.newBuilder() //
+                    .setSuccess(true) //
+                    .setTerm(this.currTerm) //
+                    .setLastLogIndex(this.logManager.getLastLogIndex());
+                doUnlock = false;
+                this.writeLock.unlock();
+                // see the comments at FollowerStableClosure#run()
+                this.ballotBox.setLastCommittedIndex(Math.min(request.getCommittedIndex(), prevLogIndex));
+                return respBuilder.build();
+            }
+
+            // Parse request
+            long index = prevLogIndex;
+            final List<LogEntry> entries = new ArrayList<>(entriesCount);
+            ByteBuffer allData = request.getData().asReadOnlyByteBuffer();
+
+            final List<RaftOutter.EntryMeta> entriesList = request.getEntriesList();
+            for (int i = 0; i < entriesCount; i++) {
+                index++;
+                final RaftOutter.EntryMeta entry = entriesList.get(i);
+
+                final LogEntry logEntry = logEntryFromMeta(index, allData, entry);
+
+                if (logEntry != null) {
+                    // Validate checksum
+                    if (this.raftOptions.isEnableLogEntryChecksum() && logEntry.isCorrupted()) {
+                        long realChecksum = logEntry.checksum();
+                        LOG.error(
+                            "Corrupted log entry received from leader, index={}, term={}, expectedChecksum={}, realChecksum={}",
+                            logEntry.getId().getIndex(), logEntry.getId().getTerm(), logEntry.getChecksum(),
+                            realChecksum);
+                        return RpcFactoryHelper //
+                            .responseFactory() //
+                            .newResponse(AppendEntriesResponse.getDefaultInstance(), RaftError.EINVAL,
+                                "The log entry is corrupted, index=%d, term=%d, expectedChecksum=%d, realChecksum=%d",
+                                logEntry.getId().getIndex(), logEntry.getId().getTerm(), logEntry.getChecksum(),
+                                realChecksum);
+                    }
+                    entries.add(logEntry);
+                }
+            }
+
+            final FollowerStableClosure closure = new FollowerStableClosure(request, AppendEntriesResponse.newBuilder()
+                .setTerm(this.currTerm), this, done, this.currTerm);
+            this.logManager.appendEntries(entries, closure);
+            // update configuration after _log_manager updated its memory status
+            checkAndSetConfiguration(true);
+            return null;
+        } finally {
+            if (doUnlock) {
+                this.writeLock.unlock();
+            }
+            this.metrics.recordLatency("handle-append-entries", Utils.monotonicMs() - startMs);
+            this.metrics.recordSize("handle-append-entries-count", entriesCount);
+        }
+    }
+
+    private LogEntry logEntryFromMeta(final long index, final ByteBuffer allData, final RaftOutter.EntryMeta entry) {
+        if (entry.getType() != EnumOutter.EntryType.ENTRY_TYPE_UNKNOWN) {
+            final LogEntry logEntry = new LogEntry();
+            logEntry.setId(new LogId(index, entry.getTerm()));
+            logEntry.setType(entry.getType());
+            logEntry.setChecksum(entry.getChecksum()); // since 1.2.6
+
+            final long dataLen = entry.getDataLen();
+            if (dataLen > 0) {
+                final byte[] bs = new byte[(int) dataLen];
+                assert allData != null;
+                allData.get(bs, 0, bs.length);
+                logEntry.setData(ByteBuffer.wrap(bs));
+            }
+
+            if (entry.getPeersCount() > 0) {
+                if (entry.getType() != EnumOutter.EntryType.ENTRY_TYPE_CONFIGURATION) {
+                    throw new IllegalStateException(
+                        "Invalid log entry that contains peers but is not ENTRY_TYPE_CONFIGURATION type: "
+                                + entry.getType());
+                }
+
+                fillLogEntryPeers(entry, logEntry);
+            } 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");
+            }
+            return logEntry;
+        }
+        return null;
+    }
+
+    private void fillLogEntryPeers(final RaftOutter.EntryMeta entry, final LogEntry logEntry) {
+        // TODO refactor
+        if (entry.getPeersCount() > 0) {
+            final List<PeerId> peers = new ArrayList<>(entry.getPeersCount());
+            for (final String peerStr : entry.getPeersList()) {
+                final PeerId peer = new PeerId();
+                peer.parse(peerStr);
+                peers.add(peer);
+            }
+            logEntry.setPeers(peers);
+        }
+
+        if (entry.getOldPeersCount() > 0) {
+            final List<PeerId> oldPeers = new ArrayList<>(entry.getOldPeersCount());
+            for (final String peerStr : entry.getOldPeersList()) {
+                final PeerId peer = new PeerId();
+                peer.parse(peerStr);
+                oldPeers.add(peer);
+            }
+            logEntry.setOldPeers(oldPeers);
+        }
+
+        if (entry.getLearnersCount() > 0) {
+            final List<PeerId> peers = new ArrayList<>(entry.getLearnersCount());
+            for (final String peerStr : entry.getLearnersList()) {
+                final PeerId peer = new PeerId();
+                peer.parse(peerStr);
+                peers.add(peer);
+            }
+            logEntry.setLearners(peers);
+        }
+
+        if (entry.getOldLearnersCount() > 0) {
+            final List<PeerId> peers = new ArrayList<>(entry.getOldLearnersCount());
+            for (final String peerStr : entry.getOldLearnersList()) {
+                final PeerId peer = new PeerId();
+                peer.parse(peerStr);
+                peers.add(peer);
+            }
+            logEntry.setOldLearners(peers);
+        }
+    }
+
+    // called when leader receive greater term in AppendEntriesResponse
+    void increaseTermTo(final long newTerm, final Status status) {
+        this.writeLock.lock();
+        try {
+            if (newTerm < this.currTerm) {
+                return;
+            }
+            stepDown(newTerm, false, status);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    /**
+     * Peer catch up callback
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-11 2:10:02 PM
+     */
+    private static class OnCaughtUp extends CatchUpClosure {
+        private final NodeImpl node;
+        private final long     term;
+        private final PeerId   peer;
+        private final long     version;
+
+        public OnCaughtUp(final NodeImpl node, final long term, final PeerId peer, final long version) {
+            super();
+            this.node = node;
+            this.term = term;
+            this.peer = peer;
+            this.version = version;
+        }
+
+        @Override
+        public void run(final Status status) {
+            this.node.onCaughtUp(this.peer, this.term, this.version, status);
+        }
+    }
+
+    private void onCaughtUp(final PeerId peer, final long term, final long version, final Status st) {
+        this.writeLock.lock();
+        try {
+            // check current_term and state to avoid ABA problem
+            if (term != this.currTerm && this.state != State.STATE_LEADER) {
+                // term has changed and nothing should be done, otherwise there will be
+                // an ABA problem.
+                return;
+            }
+            if (st.isOk()) {
+                // Caught up successfully
+                this.confCtx.onCaughtUp(version, peer, true);
+                return;
+            }
+            // Retry if this peer is still alive
+            if (st.getCode() == RaftError.ETIMEDOUT.getNumber()
+                && Utils.monotonicMs() - this.replicatorGroup.getLastRpcSendTimestamp(peer) <= this.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() + this.options.getElectionTimeoutMs();
+                if (this.replicatorGroup.waitCaughtUp(peer, this.options.getCatchupMargin(), dueTime, caughtUp)) {
+                    return;
+                }
+                LOG.warn("Node {} waitCaughtUp failed, peer={}.", getNodeId(), peer);
+            }
+            LOG.warn("Node {} caughtUp failed, status={}, peer={}.", getNodeId(), st, peer);
+            this.confCtx.onCaughtUp(version, peer, false);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    private boolean checkDeadNodes(final Configuration conf, final long monotonicNowMs,
+                                   final boolean stepDownOnCheckFail) {
+        // Check learner replicators at first.
+        for (final PeerId peer : conf.getLearners()) {
+            checkReplicator(peer);
+        }
+        // Ensure quorum nodes alive.
+        final List<PeerId> peers = conf.listPeers();
+        final Configuration deadNodes = new Configuration();
+        if (checkDeadNodes0(peers, monotonicNowMs, true, deadNodes)) {
+            return true;
+        }
+        if (stepDownOnCheckFail) {
+            LOG.warn("Node {} steps down when alive nodes don't satisfy quorum, term={}, deadNodes={}, 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 boolean checkDeadNodes0(final List<PeerId> peers, final long monotonicNowMs, final boolean checkReplicator,
+                                    final 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;
+            }
+            if (checkReplicator) {
+                checkReplicator(peer);
+            }
+            final long lastRpcSendTimestamp = this.replicatorGroup.getLastRpcSendTimestamp(peer);
+            if (monotonicNowMs - lastRpcSendTimestamp <= leaderLeaseTimeoutMs) {
+                aliveCount++;
+                if (startLease > lastRpcSendTimestamp) {
+                    startLease = lastRpcSendTimestamp;
+                }
+                continue;
+            }
+            if (deadNodes != null) {
+                deadNodes.addPeer(peer);
+            }
+        }
+        if (aliveCount >= peers.size() / 2 + 1) {
+            updateLastLeaderTimestamp(startLease);
+            return true;
+        }
+        return false;
+    }
+
+    // in read_lock
+    private List<PeerId> getAliveNodes(final Collection<PeerId> peers, final long monotonicNowMs) {
+        final int leaderLeaseTimeoutMs = this.options.getLeaderLeaseTimeoutMs();
+        final List<PeerId> alivePeers = new ArrayList<>();
+        for (final PeerId peer : peers) {
+            if (peer.equals(this.serverId)) {
+                alivePeers.add(peer.copy());
+                continue;
+            }
+            if (monotonicNowMs - this.replicatorGroup.getLastRpcSendTimestamp(peer) <= leaderLeaseTimeoutMs) {
+                alivePeers.add(peer.copy());
+            }
+        }
+        return alivePeers;
+    }
+
+    @SuppressWarnings({ "LoopStatementThatDoesntLoop", "ConstantConditions" })
+    private void handleStepDownTimeout() {
+        do {
+            this.readLock.lock();
+            try {
+                if (this.state.compareTo(State.STATE_TRANSFERRING) > 0) {
+                    LOG.debug("Node {} stop step-down timer, term={}, state={}.", getNodeId(), this.currTerm,
+                        this.state);
+                    return;
+                }
+                final long monotonicNowMs = Utils.monotonicMs();
+                if (!checkDeadNodes(this.conf.getConf(), monotonicNowMs, false)) {
+                    break;
+                }
+                if (!this.conf.getOldConf().isEmpty()) {
+                    if (!checkDeadNodes(this.conf.getOldConf(), monotonicNowMs, false)) {
+                        break;
+                    }
+                }
+                return;
+            } finally {
+                this.readLock.unlock();
+            }
+        } while (false);
+
+        this.writeLock.lock();
+        try {
+            if (this.state.compareTo(State.STATE_TRANSFERRING) > 0) {
+                LOG.debug("Node {} stop step-down timer, term={}, state={}.", getNodeId(), this.currTerm, this.state);
+                return;
+            }
+            final long monotonicNowMs = Utils.monotonicMs();
+            checkDeadNodes(this.conf.getConf(), monotonicNowMs, true);
+            if (!this.conf.getOldConf().isEmpty()) {
+                checkDeadNodes(this.conf.getOldConf(), monotonicNowMs, true);
+            }
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    /**
+     * Configuration changed callback.
+     *
+     * @author boyan (boyan@alibaba-inc.com)
+     *
+     * 2018-Apr-11 2:53:43 PM
+     */
+    private class ConfigurationChangeDone implements Closure {
+        private final long    term;
+        private final boolean leaderStart;
+
+        public ConfigurationChangeDone(final long term, final boolean leaderStart) {
+            super();
+            this.term = term;
+            this.leaderStart = leaderStart;
+        }
+
+        @Override
+        public void run(final Status status) {
+            if (status.isOk()) {
+                onConfigurationChangeDone(this.term);
+                if (this.leaderStart) {
+                    getOptions().getFsm().onLeaderStart(this.term);
+                }
+            } else {
+                LOG.error("Fail to run ConfigurationChangeDone, status: {}.", status);
+            }
+        }
+    }
+
+    private void unsafeApplyConfiguration(final Configuration newConf, final Configuration oldConf,
+                                          final boolean leaderStart) {
+        Requires.requireTrue(this.confCtx.isBusy(), "ConfigurationContext is not busy");
+        final LogEntry entry = new LogEntry(EnumOutter.EntryType.ENTRY_TYPE_CONFIGURATION);
+        entry.setId(new LogId(0, this.currTerm));
+        entry.setPeers(newConf.listPeers());
+        entry.setLearners(newConf.listLearners());
+        if (oldConf != null) {
+            entry.setOldPeers(oldConf.listPeers());
+            entry.setOldLearners(oldConf.listLearners());
+        }
+        final ConfigurationChangeDone configurationChangeDone = new ConfigurationChangeDone(this.currTerm, leaderStart);
+        // Use the new_conf to deal the quorum of this very log
+        if (!this.ballotBox.appendPendingTask(newConf, oldConf, configurationChangeDone)) {
+            Utils.runClosureInThread(configurationChangeDone, new Status(RaftError.EINTERNAL, "Fail to append task."));
+            return;
+        }
+        final List<LogEntry> entries = new ArrayList<>();
+        entries.add(entry);
+        this.logManager.appendEntries(entries, new LeaderStableClosure(entries));
+        checkAndSetConfiguration(false);
+    }
+
+    private void unsafeRegisterConfChange(final Configuration oldConf, final Configuration newConf, final Closure done) {
+
+        Requires.requireTrue(newConf.isValid(), "Invalid new conf: %s", newConf);
+        // The new conf entry(will be stored in log manager) should be valid
+        Requires.requireTrue(new ConfigurationEntry(null, newConf, oldConf).isValid(), "Invalid conf entry: %s",
+            newConf);
+
+        if (this.state != State.STATE_LEADER) {
+            LOG.warn("Node {} refused configuration changing as the state={}.", getNodeId(), this.state);
+            if (done != null) {
+                final Status status = new Status();
+                if (this.state == State.STATE_TRANSFERRING) {
+                    status.setError(RaftError.EBUSY, "Is transferring leadership.");
+                } else {
+                    status.setError(RaftError.EPERM, "Not leader");
+                }
+                Utils.runClosureInThread(done, status);
+            }
+            return;
+        }
+        // check concurrent conf change
+        if (this.confCtx.isBusy()) {
+            LOG.warn("Node {} refused configuration concurrent changing.", getNodeId());
+            if (done != null) {
+                Utils.runClosureInThread(done, new Status(RaftError.EBUSY, "Doing another configuration change."));
+            }
+            return;
+        }
+        // Return immediately when the new peers equals to current configuration
+        if (this.conf.getConf().equals(newConf)) {
+            Utils.runClosureInThread(done);
+            return;
+        }
+        this.confCtx.start(oldConf, newConf, done);
+    }
+
+    private void afterShutdown() {
+        List<Closure> savedDoneList = null;
+        this.writeLock.lock();
+        try {
+            if (!this.shutdownContinuations.isEmpty()) {
+                savedDoneList = new ArrayList<>(this.shutdownContinuations);
+            }
+            if (this.logStorage != null) {
+                this.logStorage.shutdown();
+            }
+            this.state = State.STATE_SHUTDOWN;
+        } finally {
+            this.writeLock.unlock();
+        }
+        if (savedDoneList != null) {
+            for (final Closure closure : savedDoneList) {
+                Utils.runClosureInThread(closure);
+            }
+        }
+    }
+
+    @Override
+    public NodeOptions getOptions() {
+        return this.options;
+    }
+
+    public Scheduler getTimerManager() {
+        return this.timerManager;
+    }
+
+    @Override
+    public RaftOptions getRaftOptions() {
+        return this.raftOptions;
+    }
+
+    @OnlyForTest
+    long getCurrentTerm() {
+        this.readLock.lock();
+        try {
+            return this.currTerm;
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @OnlyForTest
+    ConfigurationEntry getConf() {
+        this.readLock.lock();
+        try {
+            return this.conf;
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        shutdown(null);
+    }
+
+    public void onConfigurationChangeDone(final long term) {
+        this.writeLock.lock();
+        try {
+            if (term != this.currTerm || this.state.compareTo(State.STATE_TRANSFERRING) > 0) {
+                LOG.warn("Node {} process onConfigurationChangeDone at term {} while state={}, currTerm={}.",
+                    getNodeId(), term, this.state, this.currTerm);
+                return;
+            }
+            this.confCtx.nextStage();
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public PeerId getLeaderId() {
+        this.readLock.lock();
+        try {
+            return this.leaderId.isEmpty() ? null : this.leaderId;
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public String getGroupId() {
+        return this.groupId;
+    }
+
+    public PeerId getServerId() {
+        return this.serverId;
+    }
+
+    @Override
+    public NodeId getNodeId() {
+        if (this.nodeId == null) {
+            this.nodeId = new NodeId(this.groupId, this.serverId);
+        }
+        return this.nodeId;
+    }
+
+    public RaftClientService getRpcService() {
+        return this.rpcService;
+    }
+
+    public void onError(final RaftException error) {
+        LOG.warn("Node {} got error: {}.", getNodeId(), error);
+        if (this.fsmCaller != null) {
+            // onError of fsmCaller is guaranteed to be executed once.
+            this.fsmCaller.onError(error);
+        }
+        if (this.readOnlyService != null) {
+            this.readOnlyService.setError(error);
+        }
+        this.writeLock.lock();
+        try {
+            // If it is leader, need to wake up a new one;
+            // If it is follower, also step down to call on_stop_following.
+            if (this.state.compareTo(State.STATE_FOLLOWER) <= 0) {
+                stepDown(this.currTerm, this.state == State.STATE_LEADER, new Status(RaftError.EBADNODE,
+                    "Raft node(leader or candidate) is in error."));
+            }
+            if (this.state.compareTo(State.STATE_ERROR) < 0) {
+                this.state = State.STATE_ERROR;
+            }
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    public void handleRequestVoteResponse(final PeerId peerId, final long term, final RequestVoteResponse response) {
+        this.writeLock.lock();
+        try {
+            if (this.state != State.STATE_CANDIDATE) {
+                LOG.warn("Node {} received invalid RequestVoteResponse from {}, state not in STATE_CANDIDATE but {}.",
+                    getNodeId(), peerId, this.state);
+                return;
+            }
+            // check stale term
+            if (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."));
+                return;
+            }
+            // check granted quorum?
+            if (response.getGranted()) {
+                this.voteCtx.grant(peerId);
+                if (this.voteCtx.isGranted()) {
+                    becomeLeader();
+                }
+            }
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    private class OnRequestVoteRpcDone extends RpcResponseClosureAdapter<RequestVoteResponse> {
+
+        final long         startMs;
+        final PeerId       peer;
+        final long         term;
+        final NodeImpl     node;
+        RequestVoteRequest request;
+
+        public OnRequestVoteRpcDone(final PeerId peer, final long term, final NodeImpl node) {
+            super();
+            this.startMs = Utils.monotonicMs();
+            this.peer = peer;
+            this.term = term;
+            this.node = node;
+        }
+
+        @Override
+        public void run(final Status status) {
+            NodeImpl.this.metrics.recordLatency("request-vote", Utils.monotonicMs() - this.startMs);
+            if (!status.isOk()) {
+                LOG.warn("Node {} RequestVote to {} error: {}.", this.node.getNodeId(), this.peer, status);
+            } else {
+                this.node.handleRequestVoteResponse(this.peer, this.term, getResponse());
+            }
+        }
+    }
+
+    public void handlePreVoteResponse(final PeerId peerId, final long term, final RequestVoteResponse response) {
+        boolean doUnlock = true;
+        this.writeLock.lock();
+        try {
+            if (this.state != State.STATE_FOLLOWER) {
+                LOG.warn("Node {} received invalid PreVoteResponse from {}, state not in STATE_FOLLOWER but {}.",
+                    getNodeId(), peerId, this.state);
+                return;
+            }
+            if (term != this.currTerm) {
+                LOG.warn("Node {} received invalid PreVoteResponse from {}, term={}, currTerm={}.", getNodeId(),
+                    peerId, term, this.currTerm);
+                return;
+            }
+            if (response.getTerm() > this.currTerm) {
+                LOG.warn("Node {} received invalid PreVoteResponse from {}, term {}, expect={}.", 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={}.", getNodeId(), peerId,
+                response.getTerm(), response.getGranted());
+            // check granted quorum?
+            if (response.getGranted()) {
+                this.prevVoteCtx.grant(peerId);
+                if (this.prevVoteCtx.isGranted()) {
+                    doUnlock = false;
+                    electSelf();
+                }
+            }
+        } finally {
+            if (doUnlock) {
+                this.writeLock.unlock();
+            }
+        }
+    }
+
+    private class OnPreVoteRpcDone extends RpcResponseClosureAdapter<RequestVoteResponse> {
+
+        final long         startMs;
+        final PeerId       peer;
+        final long         term;
+        RequestVoteRequest request;
+
+        public OnPreVoteRpcDone(final PeerId peer, final long term) {
+            super();
+            this.startMs = Utils.monotonicMs();
+            this.peer = peer;
+            this.term = term;
+        }
+
+        @Override
+        public void run(final Status status) {
+            NodeImpl.this.metrics.recordLatency("pre-vote", Utils.monotonicMs() - this.startMs);
+            if (!status.isOk()) {
+                LOG.warn("Node {} PreVote to {} error: {}.", getNodeId(), this.peer, status);
+            } else {
+                handlePreVoteResponse(this.peer, this.term, getResponse());
+            }
+        }
+    }
+
+    // in writeLock
+    private void preVote() {
+        long oldTerm;
+        try {
+            LOG.info("Node {} term {} start preVote.", getNodeId(), this.currTerm);
+            if (this.snapshotExecutor != null && this.snapshotExecutor.isInstallingSnapshot()) {
+                LOG.warn(
+                    "Node {} term {} doesn't do preVote when installing snapshot as the configuration may be out of date.",
+                    getNodeId(), this.currTerm);
+                return;
+            }
+            if (!this.conf.contains(this.serverId)) {
+                LOG.warn("Node {} can't do preVote as it is not in conf <{}>.", getNodeId(), this.conf);
+                return;
+            }
+            oldTerm = this.currTerm;
+        } finally {
+            this.writeLock.unlock();
+        }
+
+        final LogId lastLogId = this.logManager.getLastLogId(true);
+
+        boolean doUnlock = true;
+        this.writeLock.lock();
+        try {
+            // pre_vote need defense ABA after unlock&writeLock
+            if (oldTerm != this.currTerm) {
+                LOG.warn("Node {} raise term {} when get lastLogId.", getNodeId(), this.currTerm);
+                return;
+            }
+            this.prevVoteCtx.init(this.conf.getConf(), this.conf.isStable() ? null : this.conf.getOldConf());
+            for (final PeerId peer : this.conf.listPeers()) {
+                if (peer.equals(this.serverId)) {
+                    continue;
+                }
+                if (!this.rpcService.connect(peer.getEndpoint())) {
+                    LOG.warn("Node {} channel init failed, address={}.", getNodeId(), peer.getEndpoint());
+                    continue;
+                }
+                final OnPreVoteRpcDone done = new OnPreVoteRpcDone(peer, this.currTerm);
+                done.request = RequestVoteRequest.newBuilder() //
+                    .setPreVote(true) // it's a pre-vote request.
+                    .setGroupId(this.groupId) //
+                    .setServerId(this.serverId.toString()) //
+                    .setPeerId(peer.toString()) //
+                    .setTerm(this.currTerm + 1) // next term
+                    .setLastLogIndex(lastLogId.getIndex()) //
+                    .setLastLogTerm(lastLogId.getTerm()) //
+                    .build();
+                this.rpcService.preVote(peer.getEndpoint(), done.request, done);
+            }
+            this.prevVoteCtx.grant(this.serverId);
+            if (this.prevVoteCtx.isGranted()) {
+                doUnlock = false;
+                electSelf();
+            }
+        } finally {
+            if (doUnlock) {
+                this.writeLock.unlock();
+            }
+        }
+    }
+
+    private void handleVoteTimeout() {
+        this.writeLock.lock();
+        if (this.state != State.STATE_CANDIDATE) {
+            this.writeLock.unlock();
+            return;
+        }
+
+        if (this.raftOptions.isStepDownWhenVoteTimedout()) {
+            LOG.warn(
+                "Candidate node {} term {} steps down when election reaching vote timeout: fail to get quorum vote-granted.",
+                this.nodeId, this.currTerm);
+            stepDown(this.currTerm, false, new Status(RaftError.ETIMEDOUT,
+                "Vote timeout: fail to get quorum vote-granted."));
+            // unlock in preVote
+            preVote();
+        } else {
+            LOG.debug("Node {} term {} retry to vote self.", getNodeId(), this.currTerm);
+            // unlock in electSelf
+            electSelf();
+        }
+    }
+
+    @Override
+    public boolean isLeader() {
+        return isLeader(true);
+    }
+
+    @Override
+    public boolean isLeader(final boolean blocking) {
+        if (!blocking) {
+            return this.state == State.STATE_LEADER;
+        }
+        this.readLock.lock();
+        try {
+            return this.state == State.STATE_LEADER;
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public void shutdown(final Closure done) {
+        List<RepeatedTimer> timers = null;
+        this.writeLock.lock();
+        try {
+            LOG.info("Node {} shutdown, currTerm={} state={}.", getNodeId(), this.currTerm, this.state);
+            if (this.state.compareTo(State.STATE_SHUTTING) < 0) {
+                NodeManager.getInstance().remove(this);
+                // If it is leader, set the wakeup_a_candidate with true;
+                // If it is follower, call on_stop_following in step_down
+                if (this.state.compareTo(State.STATE_FOLLOWER) <= 0) {
+                    stepDown(this.currTerm, this.state == State.STATE_LEADER,
+                            new Status(RaftError.ESHUTDOWN, "Raft node is going to quit."));
+                }
+                this.state = State.STATE_SHUTTING;
+                // Stop all timers
+                timers = stopAllTimers();
+                if (this.readOnlyService != null) {
+                    this.readOnlyService.shutdown();
+                }
+                if (this.logManager != null) {
+                    this.logManager.shutdown();
+                }
+                if (this.metaStorage != null) {
+                    this.metaStorage.shutdown();
+                }
+                if (this.snapshotExecutor != null) {
+                    this.snapshotExecutor.shutdown();
+                }
+                if (this.wakingCandidate != null) {
+                    Replicator.stop(this.wakingCandidate);
+                }
+                if (this.fsmCaller != null) {
+                    this.fsmCaller.shutdown();
+                }
+                if (this.rpcService != null) {
+                    this.rpcService.shutdown();
+                }
+                if (this.applyQueue != null) {
+                    final CountDownLatch latch = new CountDownLatch(1);
+                    this.shutdownLatch = latch;
+                    Utils.runInThread(
+                        () -> this.applyQueue.publishEvent((event, sequence) -> event.shutdownLatch = latch));
+                } else {
+                    final int num = GLOBAL_NUM_NODES.decrementAndGet();
+                    LOG.info("The number of active nodes decrement to {}.", num);
+                }
+                if (this.timerManager != null) {
+                    this.timerManager.shutdown();
+                }
+            }
+
+            if (this.state != State.STATE_SHUTDOWN) {
+                if (done != null) {
+                    this.shutdownContinuations.add(done);
+                }
+                return;
+            }
+
+            // This node is down, it's ok to invoke done right now. Don't invoke this
+            // in place to avoid the dead writeLock issue when done.Run() is going to acquire
+            // a writeLock which is already held by the caller
+            if (done != null) {
+                Utils.runClosureInThread(done);
+            }
+        } finally {
+            this.writeLock.unlock();
+
+            // Destroy all timers out of lock
+            if (timers != null) {
+                destroyAllTimers(timers);
+            }
+        }
+    }
+
+    // Should in lock
+    private List<RepeatedTimer> stopAllTimers() {
+        final List<RepeatedTimer> timers = new ArrayList<>();
+        if (this.electionTimer != null) {
+            this.electionTimer.stop();
+            timers.add(this.electionTimer);
+        }
+        if (this.voteTimer != null) {
+            this.voteTimer.stop();
+            timers.add(this.voteTimer);
+        }
+        if (this.stepDownTimer != null) {
+            this.stepDownTimer.stop();
+            timers.add(this.stepDownTimer);
+        }
+        if (this.snapshotTimer != null) {
+            this.snapshotTimer.stop();
+            timers.add(this.snapshotTimer);
+        }
+        return timers;
+    }
+
+    private void destroyAllTimers(final List<RepeatedTimer> timers) {
+        for (final RepeatedTimer timer : timers) {
+            timer.destroy();
+        }
+    }
+
+    @Override
+    public synchronized void join() throws InterruptedException {
+        if (this.shutdownLatch != null) {
+            if (this.readOnlyService != null) {
+                this.readOnlyService.join();
+            }
+            if (this.logManager != null) {
+                this.logManager.join();
+            }
+            if (this.snapshotExecutor != null) {
+                this.snapshotExecutor.join();
+            }
+            if (this.wakingCandidate != null) {
+                Replicator.join(this.wakingCandidate);
+            }
+            this.shutdownLatch.await();
+            this.applyDisruptor.shutdown();
+            this.shutdownLatch = null;
+        }
+        if (this.fsmCaller != null) {
+            this.fsmCaller.join();
+        }
+    }
+
+    private static class StopTransferArg {
+        final NodeImpl node;
+        final long     term;
+        final PeerId   peer;
+
+        public StopTransferArg(final NodeImpl node, final long term, final PeerId peer) {
+            super();
+            this.node = node;
+            this.term = term;
+            this.peer = peer;
+        }
+    }
+
+    private void handleTransferTimeout(final long term, final PeerId peer) {
+        LOG.info("Node {} failed to transfer leadership to peer {}, reached timeout.", getNodeId(), peer);
+        this.writeLock.lock();
+        try {
+            if (term == this.currTerm) {
+                this.replicatorGroup.stopTransferLeadership(peer);
+                if (this.state == State.STATE_TRANSFERRING) {
+                    this.fsmCaller.onLeaderStart(term);
+                    this.state = State.STATE_LEADER;
+                    this.stopTransferArg = null;
+                }
+            }
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    private void onTransferTimeout(final StopTransferArg arg) {
+        arg.node.handleTransferTimeout(arg.term, arg.peer);
+    }
+
+    /**
+     * Retrieve current configuration this node seen so far. It's not a reliable way to
+     * retrieve cluster peers info, you should use {@link #listPeers()} instead.
+     *
+     * @return current configuration.
+     *
+     * @since 1.0.3
+     */
+    public Configuration getCurrentConf() {
+        this.readLock.lock();
+        try {
+            if (this.conf != null && this.conf.getConf() != null) {
+                return this.conf.getConf().copy();
+            }
+            return null;
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public List<PeerId> listPeers() {
+        this.readLock.lock();
+        try {
+            if (this.state != State.STATE_LEADER) {
+                throw new IllegalStateException("Not leader");
+            }
+            return this.conf.getConf().listPeers();
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public List<PeerId> listAlivePeers() {
+        this.readLock.lock();
+        try {
+            if (this.state != State.STATE_LEADER) {
+                throw new IllegalStateException("Not leader");
+            }
+            return getAliveNodes(this.conf.getConf().getPeers(), Utils.monotonicMs());
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public List<PeerId> listLearners() {
+        this.readLock.lock();
+        try {
+            if (this.state != State.STATE_LEADER) {
+                throw new IllegalStateException("Not leader");
+            }
+            return this.conf.getConf().listLearners();
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public List<PeerId> listAliveLearners() {
+        this.readLock.lock();
+        try {
+            if (this.state != State.STATE_LEADER) {
+                throw new IllegalStateException("Not leader");
+            }
+            return getAliveNodes(this.conf.getConf().getLearners(), Utils.monotonicMs());
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public void addPeer(final PeerId peer, final Closure done) {
+        Requires.requireNonNull(peer, "Null peer");
+        this.writeLock.lock();
+        try {
+            Requires.requireTrue(!this.conf.getConf().contains(peer), "Peer already exists in current configuration");
+
+            final Configuration newConf = new Configuration(this.conf.getConf());
+            newConf.addPeer(peer);
+            unsafeRegisterConfChange(this.conf.getConf(), newConf, done);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public void removePeer(final PeerId peer, final Closure done) {
+        Requires.requireNonNull(peer, "Null peer");
+        this.writeLock.lock();
+        try {
+            Requires.requireTrue(this.conf.getConf().contains(peer), "Peer not found in current configuration");
+
+            final Configuration newConf = new Configuration(this.conf.getConf());
+            newConf.removePeer(peer);
+            unsafeRegisterConfChange(this.conf.getConf(), newConf, done);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public void changePeers(final Configuration newPeers, final Closure done) {
+        Requires.requireNonNull(newPeers, "Null new peers");
+        Requires.requireTrue(!newPeers.isEmpty(), "Empty new peers");
+        this.writeLock.lock();
+        try {
+            LOG.info("Node {} change peers from {} to {}.", getNodeId(), this.conf.getConf(), newPeers);
+            unsafeRegisterConfChange(this.conf.getConf(), newPeers, done);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public Status resetPeers(final Configuration newPeers) {
+        Requires.requireNonNull(newPeers, "Null new peers");
+        Requires.requireTrue(!newPeers.isEmpty(), "Empty new peers");
+        Requires.requireTrue(newPeers.isValid(), "Invalid new peers: %s", newPeers);
+        this.writeLock.lock();
+        try {
+            if (newPeers.isEmpty()) {
+                LOG.warn("Node {} set empty peers.", getNodeId());
+                return new Status(RaftError.EINVAL, "newPeers is empty");
+            }
+            if (!this.state.isActive()) {
+                LOG.warn("Node {} is in state {}, can't set peers.", getNodeId(), this.state);
+                return new Status(RaftError.EPERM, "Bad state: %s", this.state);
+            }
+            // bootstrap?
+            if (this.conf.getConf().isEmpty()) {
+                LOG.info("Node {} set peers to {} from empty.", getNodeId(), newPeers);
+                this.conf.setConf(newPeers);
+                stepDown(this.currTerm + 1, false, new Status(RaftError.ESETPEER, "Set peer from empty configuration"));
+                return Status.OK();
+            }
+            if (this.state == State.STATE_LEADER && this.confCtx.isBusy()) {
+                LOG.warn("Node {} set peers need wait current conf changing.", getNodeId());
+                return new Status(RaftError.EBUSY, "Changing to another configuration");
+            }
+            // check equal, maybe retry direct return
+            if (this.conf.getConf().equals(newPeers)) {
+                return Status.OK();
+            }
+            final Configuration newConf = new Configuration(newPeers);
+            LOG.info("Node {} set peers from {} to {}.", getNodeId(), this.conf.getConf(), newPeers);
+            this.conf.setConf(newConf);
+            this.conf.getOldConf().reset();
+            stepDown(this.currTerm + 1, false, new Status(RaftError.ESETPEER, "Raft node set peer normally"));
+            return Status.OK();
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public void addLearners(final List<PeerId> learners, final Closure done) {
+        checkPeers(learners);
+        this.writeLock.lock();
+        try {
+            final Configuration newConf = new Configuration(this.conf.getConf());
+            for (final PeerId peer : learners) {
+                newConf.addLearner(peer);
+            }
+            unsafeRegisterConfChange(this.conf.getConf(), newConf, done);
+        } finally {
+            this.writeLock.unlock();
+        }
+
+    }
+
+    private void checkPeers(final List<PeerId> peers) {
+        Requires.requireNonNull(peers, "Null peers");
+        Requires.requireTrue(!peers.isEmpty(), "Empty peers");
+        for (final PeerId peer : peers) {
+            Requires.requireNonNull(peer, "Null peer");
+        }
+    }
+
+    @Override
+    public void removeLearners(final List<PeerId> learners, final Closure done) {
+        checkPeers(learners);
+        this.writeLock.lock();
+        try {
+            final Configuration newConf = new Configuration(this.conf.getConf());
+            for (final PeerId peer : learners) {
+                newConf.removeLearner(peer);
+            }
+            unsafeRegisterConfChange(this.conf.getConf(), newConf, done);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public void resetLearners(final List<PeerId> learners, final Closure done) {
+        checkPeers(learners);
+        this.writeLock.lock();
+        try {
+            final Configuration newConf = new Configuration(this.conf.getConf());
+            newConf.setLearners(new LinkedHashSet<>(learners));
+            unsafeRegisterConfChange(this.conf.getConf(), newConf, done);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public void snapshot(final Closure done) {
+        doSnapshot(done);
+    }
+
+    private void doSnapshot(final Closure done) {
+        if (this.snapshotExecutor != null) {
+            this.snapshotExecutor.doSnapshot(done);
+        } else {
+            if (done != null) {
+                final Status status = new Status(RaftError.EINVAL, "Snapshot is not supported");
+                Utils.runClosureInThread(done, status);
+            }
+        }
+    }
+
+    @Override
+    public void resetElectionTimeoutMs(final int electionTimeoutMs) {
+        Requires.requireTrue(electionTimeoutMs > 0, "Invalid electionTimeoutMs");
+        this.writeLock.lock();
+        try {
+            this.options.setElectionTimeoutMs(electionTimeoutMs);
+            this.replicatorGroup.resetHeartbeatInterval(heartbeatTimeout(this.options.getElectionTimeoutMs()));
+            this.replicatorGroup.resetElectionTimeoutInterval(electionTimeoutMs);
+            LOG.info("Node {} reset electionTimeout, currTimer {} state {} new electionTimeout {}.", getNodeId(),
+                this.currTerm, this.state, electionTimeoutMs);
+            this.electionTimer.reset(electionTimeoutMs);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public Status transferLeadershipTo(final PeerId peer) {
+        Requires.requireNonNull(peer, "Null peer");
+        this.writeLock.lock();
+        try {
+            if (this.state != State.STATE_LEADER) {
+                LOG.warn("Node {} can't transfer leadership to peer {} as it is in state {}.", getNodeId(), peer,
+                    this.state);
+                return new Status(this.state == State.STATE_TRANSFERRING ? RaftError.EBUSY : RaftError.EPERM,
+                        "Not a leader");
+            }
+            if (this.confCtx.isBusy()) {
+                // It's very messy to deal with the case when the |peer| received
+                // TimeoutNowRequest and increase the term while somehow another leader
+                // which was not replicated with the newest configuration has been
+                // 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 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
+                // completed so that the peer's configuration is up-to-date when it
+                // receives the TimeOutNowRequest.
+                LOG.warn(
+                    "Node {} refused to transfer leadership to peer {} when the leader is changing the configuration.",
+                    getNodeId(), peer);
+                return new Status(RaftError.EBUSY, "Changing the configuration");
+            }
+
+            PeerId peerId = peer.copy();
+            // if peer_id is ANY_PEER(0.0.0.0:0:0), the peer with the largest
+            // last_log_id will be selected.
+            if (peerId.equals(PeerId.ANY_PEER)) {
+                LOG.info("Node {} starts to transfer leadership to any peer.", getNodeId());
+                if ((peerId = this.replicatorGroup.findTheNextCandidate(this.conf)) == null) {
+                    return new Status(-1, "Candidate not found for any peer");
+                }
+            }
+            if (peerId.equals(this.serverId)) {
+                LOG.info("Node {} transferred leadership to self.", this.serverId);
+                return Status.OK();
+            }
+            if (!this.conf.contains(peerId)) {
+                LOG.info("Node {} refused to transfer leadership to peer {} as it is not in {}.", getNodeId(), peer,
+                    this.conf);
+                return new Status(RaftError.EINVAL, "Not in current configuration");
+            }
+
+            final long lastLogIndex = this.logManager.getLastLogIndex();
+            if (!this.replicatorGroup.transferLeadershipTo(peerId, lastLogIndex)) {
+                LOG.warn("No such peer {}.", peer);
+                return new Status(RaftError.EINVAL, "No such peer %s", peer);
+            }
+            this.state = State.STATE_TRANSFERRING;
+            final Status status = new Status(RaftError.ETRANSFERLEADERSHIP,
+                "Raft leader is transferring leadership to %s", peerId);
+            onLeaderStop(status);
+            LOG.info("Node {} starts to transfer leadership to peer {}.", getNodeId(), peer);
+            final StopTransferArg stopArg = new StopTransferArg(this, this.currTerm, peerId);
+            this.stopTransferArg = stopArg;
+            this.transferTimer = this.timerManager.schedule(() -> onTransferTimeout(stopArg),
+                this.options.getElectionTimeoutMs(), TimeUnit.MILLISECONDS);
+
+        } finally {
+            this.writeLock.unlock();
+        }
+        return Status.OK();
+    }
+
+    private void onLeaderStop(final Status status) {
+        this.replicatorGroup.clearFailureReplicators();
+        this.fsmCaller.onLeaderStop(status);
+    }
+
+    @Override
+    public Message handleTimeoutNowRequest(final TimeoutNowRequest request, final RpcRequestClosure done) {
+        boolean doUnlock = true;
+        this.writeLock.lock();
+        try {
+            if (request.getTerm() != this.currTerm) {
+                final long savedCurrTerm = this.currTerm;
+                if (request.getTerm() > this.currTerm) {
+                    stepDown(request.getTerm(), false, new Status(RaftError.EHIGHERTERMREQUEST,
+                        "Raft node receives higher term request"));
+                }
+                LOG.info("Node {} received TimeoutNowRequest from {} while currTerm={} didn't match requestTerm={}.",
+                    getNodeId(), request.getPeerId(), savedCurrTerm, request.getTerm());
+                return TimeoutNowResponse.newBuilder() //
+                    .setTerm(this.currTerm) //
+                    .setSuccess(false) //
+                    .build();
+            }
+            if (this.state != State.STATE_FOLLOWER) {
+                LOG.info("Node {} received TimeoutNowRequest from {}, while state={}, term={}.", getNodeId(),
+                    request.getServerId(), this.state, this.currTerm);
+                return TimeoutNowResponse.newBuilder() //
+                    .setTerm(this.currTerm) //
+                    .setSuccess(false) //
+                    .build();
+            }
+
+            final long savedTerm = this.currTerm;
+            final TimeoutNowResponse resp = TimeoutNowResponse.newBuilder() //
+                .setTerm(this.currTerm + 1) //
+                .setSuccess(true) //
+                .build();
+            // Parallelize response and election
+            done.sendResponse(resp);
+            doUnlock = false;
+            electSelf();
+            LOG.info("Node {} received TimeoutNowRequest from {}, term={}.", getNodeId(), request.getServerId(),
+                savedTerm);
+        } finally {
+            if (doUnlock) {
+                this.writeLock.unlock();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Message handleInstallSnapshot(final InstallSnapshotRequest request, final RpcRequestClosure done) {
+        if (this.snapshotExecutor == null) {
+            return RpcFactoryHelper //
+                .responseFactory() //
+                .newResponse(InstallSnapshotResponse.getDefaultInstance(), RaftError.EINVAL, "Not supported snapshot");
+        }
+        final PeerId serverId = new PeerId();
+        if (!serverId.parse(request.getServerId())) {
+            LOG.warn("Node {} ignore InstallSnapshotRequest from {} bad server id.", getNodeId(), request.getServerId());
+            return RpcFactoryHelper //
+                .responseFactory() //
+                .newResponse(InstallSnapshotResponse.getDefaultInstance(), RaftError.EINVAL,
+                    "Parse serverId failed: %s", request.getServerId());
+        }
+
+        this.writeLock.lock();
+        try {
+            if (!this.state.isActive()) {
+                LOG.warn("Node {} ignore InstallSnapshotRequest as it is not in active state {}.", getNodeId(),
+                    this.state);
+                return RpcFactoryHelper //
+                    .responseFactory() //
+                    .newResponse(InstallSnapshotResponse.getDefaultInstance(), RaftError.EINVAL,
+                        "Node %s:%s is not in active state, state %s.", this.groupId, this.serverId, this.state.name());
+            }
+
+            if (request.getTerm() < this.currTerm) {
+                LOG.warn("Node {} ignore stale InstallSnapshotRequest from {}, term={}, currTerm={}.", getNodeId(),
+                    request.getPeerId(), request.getTerm(), this.currTerm);
+                return InstallSnapshotResponse.newBuilder() //
+                    .setTerm(this.currTerm) //
+                    .setSuccess(false) //
+                    .build();
+            }
+
+            checkStepDown(request.getTerm(), serverId);
+
+            if (!serverId.equals(this.leaderId)) {
+                LOG.error("Another peer {} declares that it is the leader at term {} which was occupied by leader {}.",
+                    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."));
+                return InstallSnapshotResponse.newBuilder() //
+                    .setTerm(request.getTerm() + 1) //
+                    .setSuccess(false) //
+                    .build();
+            }
+
+        } finally {
+            this.writeLock.unlock();
+        }
+        final long startMs = Utils.monotonicMs();
+        try {
+            if (LOG.isInfoEnabled()) {
+                LOG.info(
+                    "Node {} received InstallSnapshotRequest from {}, lastIncludedLogIndex={}, lastIncludedLogTerm={}, lastLogId={}.",
+                    getNodeId(), request.getServerId(), request.getMeta().getLastIncludedIndex(), request.getMeta()
+                        .getLastIncludedTerm(), this.logManager.getLastLogId(false));
+            }
+            this.snapshotExecutor.installSnapshot(request, InstallSnapshotResponse.newBuilder(), done);
+            return null;
+        } finally {
+            this.metrics.recordLatency("install-snapshot", Utils.monotonicMs() - startMs);
+        }
+    }
+
+    public void updateConfigurationAfterInstallingSnapshot() {
+        checkAndSetConfiguration(false);
... 35455 lines suppressed ...


[ignite-3] 02/02: IGNITE-13885 compiling

Posted by as...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ascherbakov pushed a commit to branch ignite-13885
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 9df47739b0f1fcbc94211442047b760ff38acb80
Author: Alexey Scherbakov <al...@gmail.com>
AuthorDate: Tue Dec 29 13:26:54 2020 +0300

    IGNITE-13885 compiling
---
 .../com/alipay/sofa/jraft/RaftGroupService.java    |    7 +-
 .../jraft/core/DefaultJRaftServiceFactory.java     |    4 +-
 .../java/com/alipay/sofa/jraft/core/NodeImpl.java  |   32 +-
 .../sofa/jraft/core/ReadOnlyServiceImpl.java       |    1 -
 .../sofa/jraft/entity/LocalFileMetaOutter.java     |   13 +-
 .../sofa/jraft/entity/LocalStorageOutter.java      |   54 +-
 .../com/alipay/sofa/jraft/entity/RaftOutter.java   |   16 +
 .../sofa/jraft/entity/codec/v1/V1Decoder.java      |    1 +
 .../com/alipay/sofa/jraft/rpc/CliRequests.java     |    4 +-
 .../sofa/jraft/rpc/MessageBuilderFactory.java      |    9 +
 .../sofa/jraft/rpc/RaftRpcServerFactory.java       |    4 -
 .../com/alipay/sofa/jraft/rpc/RpcRequests.java     |   74 +-
 .../sofa/jraft/rpc/impl/AbstractClientService.java |    9 +-
 .../sofa/jraft/rpc/impl/BoltRaftRpcFactory.java    |   98 --
 .../alipay/sofa/jraft/rpc/impl/BoltRpcClient.java  |  193 ----
 .../alipay/sofa/jraft/rpc/impl/BoltRpcServer.java  |  179 ---
 .../impl/LocalRaftRpcFactory.java}                 |   35 +-
 .../alipay/sofa/jraft/rpc/impl/LocalRpcClient.java |   64 ++
 .../impl/LocalRpcServer.java}                      |   29 +-
 .../ClientServiceConnectionEventProcessor.java     |   56 -
 .../rpc/message/DefaultMessageBuilderFactory.java  |   50 +
 .../com/alipay/sofa/jraft/storage/FileService.java |    8 +-
 .../sofa/jraft/storage/impl/LocalLogStorage.java   |  311 +++++
 .../sofa/jraft/storage/impl/RocksDBLogStorage.java |  743 ------------
 .../alipay/sofa/jraft/storage/io/ProtoBufFile.java |   59 +-
 .../alipay/sofa/jraft/storage/log/AbortFile.java   |   74 --
 .../sofa/jraft/storage/log/CheckpointFile.java     |  119 --
 .../com/alipay/sofa/jraft/storage/log/LibC.java    |   60 -
 .../storage/log/RocksDBSegmentLogStorage.java      | 1216 --------------------
 .../alipay/sofa/jraft/storage/log/SegmentFile.java |  905 ---------------
 .../snapshot/local/LocalSnapshotMetaTable.java     |    9 +-
 .../alipay/sofa/jraft/util/AsciiStringUtil.java    |   11 +-
 .../com/alipay/sofa/jraft/util/ByteString.java     |   14 +
 .../java/com/alipay/sofa/jraft/util/BytesUtil.java |   68 +-
 .../com/alipay/sofa/jraft/util/JDKMarshaller.java  |   32 +
 .../com/alipay/sofa/jraft/util/Marshaller.java     |   11 +
 .../main/java/com/alipay/sofa/jraft/util/Mpsc.java |   24 +-
 .../com/alipay/sofa/jraft/util/SegmentList.java    |   14 +-
 .../com/alipay/sofa/jraft/util/SignalHelper.java   |  114 --
 .../sofa/jraft/util/StorageOptionsFactory.java     |  350 ------
 .../alipay/sofa/jraft/util/internal/ThrowUtil.java |   12 +-
 .../util/internal/UnsafeIntegerFieldUpdater.java   |   49 -
 .../util/internal/UnsafeReferenceFieldUpdater.java |   50 -
 .../sofa/jraft/util/internal/UnsafeUtf8Util.java   |  481 --------
 .../sofa/jraft/util/internal/UnsafeUtil.java       | 1194 +++++++++----------
 .../alipay/sofa/jraft/util/internal/Updaters.java  |   36 +-
 46 files changed, 1418 insertions(+), 5478 deletions(-)

diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftGroupService.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftGroupService.java
index df9f9d1..09ed56e 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftGroupService.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/RaftGroupService.java
@@ -23,7 +23,6 @@ import org.slf4j.LoggerFactory;
 import com.alipay.sofa.jraft.entity.PeerId;
 import com.alipay.sofa.jraft.option.NodeOptions;
 import com.alipay.sofa.jraft.option.RpcOptions;
-import com.alipay.sofa.jraft.rpc.ProtobufMsgFactory;
 import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory;
 import com.alipay.sofa.jraft.rpc.RpcServer;
 import com.alipay.sofa.jraft.util.Endpoint;
@@ -40,9 +39,9 @@ public class RaftGroupService {
 
     private static final Logger LOG     = LoggerFactory.getLogger(RaftGroupService.class);
 
-    static {
-        ProtobufMsgFactory.load();
-    }
+//    static {
+//        ProtobufMsgFactory.load();
+//    }
 
     private volatile boolean    started = false;
 
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/DefaultJRaftServiceFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/DefaultJRaftServiceFactory.java
index 5707ba5..68258f6 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/DefaultJRaftServiceFactory.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/DefaultJRaftServiceFactory.java
@@ -17,6 +17,7 @@
 package com.alipay.sofa.jraft.core;
 
 import com.alipay.sofa.jraft.entity.codec.v1.LogEntryV1CodecFactory;
+import com.alipay.sofa.jraft.storage.impl.LocalLogStorage;
 import org.apache.commons.lang.StringUtils;
 
 import com.alipay.sofa.jraft.JRaftServiceFactory;
@@ -26,7 +27,6 @@ import com.alipay.sofa.jraft.storage.LogStorage;
 import com.alipay.sofa.jraft.storage.RaftMetaStorage;
 import com.alipay.sofa.jraft.storage.SnapshotStorage;
 import com.alipay.sofa.jraft.storage.impl.LocalRaftMetaStorage;
-import com.alipay.sofa.jraft.storage.impl.RocksDBLogStorage;
 import com.alipay.sofa.jraft.storage.snapshot.local.LocalSnapshotStorage;
 import com.alipay.sofa.jraft.util.Requires;
 import com.alipay.sofa.jraft.util.SPI;
@@ -47,7 +47,7 @@ public class DefaultJRaftServiceFactory implements JRaftServiceFactory {
     @Override
     public LogStorage createLogStorage(final String uri, final RaftOptions raftOptions) {
         Requires.requireTrue(StringUtils.isNotBlank(uri), "Blank log storage uri.");
-        return new RocksDBLogStorage(uri, raftOptions);
+        return new LocalLogStorage(uri, raftOptions);
     }
 
     @Override
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java
index 09139d5..1621b30 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/NodeImpl.java
@@ -103,16 +103,12 @@ import com.alipay.sofa.jraft.storage.snapshot.SnapshotExecutorImpl;
 import com.alipay.sofa.jraft.util.Describer;
 import com.alipay.sofa.jraft.util.DisruptorBuilder;
 import com.alipay.sofa.jraft.util.DisruptorMetricSet;
-import com.alipay.sofa.jraft.util.JRaftServiceLoader;
-import com.alipay.sofa.jraft.util.JRaftSignalHandler;
 import com.alipay.sofa.jraft.util.LogExceptionHandler;
 import com.alipay.sofa.jraft.util.NamedThreadFactory;
 import com.alipay.sofa.jraft.util.OnlyForTest;
-import com.alipay.sofa.jraft.util.Platform;
 import com.alipay.sofa.jraft.util.RepeatedTimer;
 import com.alipay.sofa.jraft.util.Requires;
 import com.alipay.sofa.jraft.util.RpcFactoryHelper;
-import com.alipay.sofa.jraft.util.SignalHelper;
 import com.alipay.sofa.jraft.util.SystemPropertyUtil;
 import com.alipay.sofa.jraft.util.ThreadHelper;
 import com.alipay.sofa.jraft.util.ThreadId;
@@ -140,20 +136,20 @@ public class NodeImpl implements Node, RaftServerService {
     private static final Logger                                            LOG                      = LoggerFactory
                                                                                                         .getLogger(NodeImpl.class);
 
-    static {
-        try {
-            if (SignalHelper.supportSignal()) {
-                // TODO support windows signal
-                if (!Platform.isWindows()) {
-                    final List<JRaftSignalHandler> handlers = JRaftServiceLoader.load(JRaftSignalHandler.class) //
-                        .sort();
-                    SignalHelper.addSignal(SignalHelper.SIG_USR2, handlers);
-                }
-            }
-        } catch (final Throwable t) {
-            LOG.error("Fail to add signal.", t);
-        }
-    }
+//    static {
+//        try {
+//            if (SignalHelper.supportSignal()) {
+//                // TODO support windows signal
+//                if (!Platform.isWindows()) {
+//                    final List<JRaftSignalHandler> handlers = JRaftServiceLoader.load(JRaftSignalHandler.class) //
+//                        .sort();
+//                    SignalHelper.addSignal(SignalHelper.SIG_USR2, handlers);
+//                }
+//            }
+//        } catch (final Throwable t) {
+//            LOG.error("Fail to add signal.", t);
+//        }
+//    }
 
     public final static RaftTimerFactory                                   TIMER_FACTORY            = JRaftUtils
                                                                                                         .raftTimerFactory();
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java
index 671671e..b55f208 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/core/ReadOnlyServiceImpl.java
@@ -54,7 +54,6 @@ import com.alipay.sofa.jraft.util.NamedThreadFactory;
 import com.alipay.sofa.jraft.util.OnlyForTest;
 import com.alipay.sofa.jraft.util.ThreadHelper;
 import com.alipay.sofa.jraft.util.Utils;
-import com.google.protobuf.ZeroByteStringHelper;
 import com.lmax.disruptor.BlockingWaitStrategy;
 import com.lmax.disruptor.EventFactory;
 import com.lmax.disruptor.EventHandler;
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalFileMetaOutter.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalFileMetaOutter.java
index 2220da9..f808749 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalFileMetaOutter.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalFileMetaOutter.java
@@ -19,6 +19,7 @@
 
 package com.alipay.sofa.jraft.entity;
 
+import com.alipay.sofa.jraft.rpc.Message;
 import com.alipay.sofa.jraft.util.ByteString;
 
 public final class LocalFileMetaOutter {
@@ -67,15 +68,25 @@ public final class LocalFileMetaOutter {
         }
     }
 
-    public interface LocalFileMeta {
+    public interface LocalFileMeta extends Message {
+        static Builder newBuilder() {
+            return null;
+        }
+
         ByteString getUserMeta();
 
         FileSource getSource();
 
         java.lang.String getChecksum();
 
+        boolean hasChecksum();
+
         interface Builder {
             LocalFileMeta build();
+
+            Builder setUserMeta(ByteString data);
+
+            void mergeFrom(Message fileMeta);
         }
     }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalStorageOutter.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalStorageOutter.java
index e747173..81e54b4 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalStorageOutter.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/LocalStorageOutter.java
@@ -19,6 +19,11 @@
 
 package com.alipay.sofa.jraft.entity;
 
+import com.alipay.sofa.jraft.rpc.Message;
+import com.alipay.sofa.jraft.storage.RaftMetaStorage;
+import com.alipay.sofa.jraft.util.DisruptorBuilder;
+import java.nio.ByteBuffer;
+
 public final class LocalStorageOutter {
     public interface ConfigurationPB {
         java.util.List<java.lang.String> getPeersList();
@@ -38,13 +43,34 @@ public final class LocalStorageOutter {
         long getFirstLogIndex();
     }
 
-    public interface StablePBMeta {
+    public interface StablePBMeta extends Message {
+        static Builder newBuilder() {
+            return null;
+        }
+
         long getTerm();
 
         java.lang.String getVotedfor();
+
+        interface Builder {
+
+            Builder setTerm(long term);
+
+            Builder setVotedfor(String votedFor);
+
+            StablePBMeta build();
+        }
     }
 
-    public interface LocalSnapshotPbMeta {
+    public interface LocalSnapshotPbMeta extends Message {
+        static Builder newBuilder() {
+            return null;
+        }
+
+        static LocalSnapshotPbMeta parseFrom(ByteBuffer buf) {
+            throw new UnsupportedOperationException();
+        }
+
         com.alipay.sofa.jraft.entity.RaftOutter.SnapshotMeta getMeta();
 
         java.util.List<com.alipay.sofa.jraft.entity.LocalStorageOutter.LocalSnapshotPbMeta.File> getFilesList();
@@ -53,10 +79,34 @@ public final class LocalStorageOutter {
 
         com.alipay.sofa.jraft.entity.LocalStorageOutter.LocalSnapshotPbMeta.File getFiles(int index);
 
+        byte[] toByteArray();
+
+        boolean hasMeta();
+
         interface File {
+            static Builder newBuilder() {
+                return null;
+            }
+
             java.lang.String getName();
 
             LocalFileMetaOutter.LocalFileMeta getMeta();
+
+            public interface Builder {
+                Builder setName(String key);
+
+                Builder setMeta(LocalFileMetaOutter.LocalFileMeta meta);
+
+                File build();
+            }
+        }
+
+        public interface Builder {
+            Builder setMeta(RaftOutter.SnapshotMeta meta);
+
+            Builder addFiles(File file);
+
+            LocalSnapshotPbMeta build();
         }
     }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/RaftOutter.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/RaftOutter.java
index bf8cf92..83d40ea 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/RaftOutter.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/RaftOutter.java
@@ -81,6 +81,10 @@ public final class RaftOutter {
     }
 
     public interface SnapshotMeta {
+        static Builder newBuilder() {
+            return null;
+        }
+
         long getLastIncludedIndex();
 
         long getLastIncludedTerm();
@@ -111,6 +115,18 @@ public final class RaftOutter {
 
         interface Builder {
             SnapshotMeta build();
+
+            Builder setLastIncludedIndex(long lastAppliedIndex);
+
+            Builder setLastIncludedTerm(long lastAppliedTerm);
+
+            Builder addPeers(String peerId);
+
+            Builder addLearners(String learnerId);
+
+            Builder addOldPeers(String oldPeerId);
+
+            Builder addOldLearners(String oldLearnerId);
         }
     }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/v1/V1Decoder.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/v1/V1Decoder.java
index ff811b2..7b92669 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/v1/V1Decoder.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/entity/codec/v1/V1Decoder.java
@@ -32,6 +32,7 @@ import com.alipay.sofa.jraft.util.Bits;
  * V1 log entry decoder
  * @author boyan(boyan@antfin.com)
  *
+ * TODO checksum
  */
 @Deprecated
 public final class V1Decoder implements LogEntryDecoder {
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/CliRequests.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/CliRequests.java
index 47aecff..4f1f46d 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/CliRequests.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/CliRequests.java
@@ -19,8 +19,6 @@
 
 package com.alipay.sofa.jraft.rpc;
 
-import com.alipay.sofa.jraft.entity.LeaderChangeContext;
-
 public final class CliRequests {
     public interface AddPeerRequest extends Message {
         java.lang.String getGroupId();
@@ -40,7 +38,7 @@ public final class CliRequests {
         }
 
         public static Builder newBuilder() {
-            return null;
+            return MessageBuilderFactory.DEFAULT.create();
         }
     }
 
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/MessageBuilderFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/MessageBuilderFactory.java
new file mode 100644
index 0000000..9d1a965
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/MessageBuilderFactory.java
@@ -0,0 +1,9 @@
+package com.alipay.sofa.jraft.rpc;
+
+import com.alipay.sofa.jraft.rpc.message.DefaultMessageBuilderFactory;
+
+public interface MessageBuilderFactory {
+    public static MessageBuilderFactory DEFAULT = new DefaultMessageBuilderFactory();
+
+    CliRequests.AddPeerRequest.Builder create();
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RaftRpcServerFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RaftRpcServerFactory.java
index 38dc6b6..2e8eb85 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RaftRpcServerFactory.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RaftRpcServerFactory.java
@@ -47,10 +47,6 @@ import com.alipay.sofa.jraft.util.RpcFactoryHelper;
  */
 public class RaftRpcServerFactory {
 
-    static {
-        ProtobufMsgFactory.load();
-    }
-
     /**
      * Creates a raft RPC server with default request executors.
      *
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcRequests.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcRequests.java
index 8a34b6d..22f2ff9 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcRequests.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/RpcRequests.java
@@ -26,12 +26,13 @@ import com.alipay.sofa.jraft.option.BootstrapOptions;
 import com.alipay.sofa.jraft.option.ReplicatorOptions;
 import com.alipay.sofa.jraft.util.ByteString;
 import com.alipay.sofa.jraft.util.DisruptorBuilder;
+import java.io.ByteArrayOutputStream;
 
 public final class RpcRequests {
     private RpcRequests() {
     }
 
-    public interface PingRequest {
+    public interface PingRequest extends Message {
         /**
          * <code>required int64 send_timestamp = 1;</code>
          */
@@ -137,7 +138,7 @@ public final class RpcRequests {
         }
     }
 
-    public interface TimeoutNowRequest {
+    public interface TimeoutNowRequest extends Message {
         static Builder newBuilder() {
             return null;
         }
@@ -168,6 +169,10 @@ public final class RpcRequests {
             return null;
         }
 
+        static Message getDefaultInstance() {
+            return null;
+        }
+
         /**
          * <code>required int64 term = 1;</code>
          */
@@ -192,7 +197,7 @@ public final class RpcRequests {
         }
     }
 
-    public interface RequestVoteRequest {
+    public interface RequestVoteRequest extends Message {
         java.lang.String getGroupId();
 
         java.lang.String getServerId();
@@ -263,7 +268,8 @@ public final class RpcRequests {
         }
     }
 
-    public interface AppendEntriesRequestHeader {
+    // TODO asch not needed
+    public interface AppendEntriesRequestHeader extends Message {
         /**
          * <code>required string group_id = 1;</code>
          */
@@ -304,28 +310,18 @@ public final class RpcRequests {
 
         long getPrevLogIndex();
 
-        /**
-         * <code>repeated .jraft.EntryMeta entries = 7;</code>
-         */
         java.util.List<com.alipay.sofa.jraft.entity.RaftOutter.EntryMeta> getEntriesList();
 
-        /**
-         * <code>repeated .jraft.EntryMeta entries = 7;</code>
-         */
         com.alipay.sofa.jraft.entity.RaftOutter.EntryMeta getEntries(int index);
 
-        /**
-         * <code>repeated .jraft.EntryMeta entries = 7;</code>
-         */
         int getEntriesCount();
 
         long getCommittedIndex();
 
-        /**
-         * <code>optional bytes data = 9;</code>
-         */
         ByteString getData();
 
+        boolean hasData();
+
         interface Builder {
             AppendEntriesRequest build();
 
@@ -379,7 +375,11 @@ public final class RpcRequests {
         }
     }
 
-    public interface GetFileRequest {
+    public interface GetFileRequest extends Message {
+        static Builder newBuilder() {
+            return null;
+        }
+
         long getReaderId();
 
         java.lang.String getFilename();
@@ -392,22 +392,56 @@ public final class RpcRequests {
 
         interface Builder {
             GetFileRequest build();
+
+            long getReaderId();
+
+            String getFilename();
+
+            long getOffset();
+
+            Builder setCount(long cnt);
+
+            long getCount();
+
+            Builder setOffset(long offset);
+
+            Builder setReadPartly(boolean readPartly);
+
+            Builder setFilename(String fileName);
+
+            Builder setReaderId(long readerId);
         }
     }
 
     public interface GetFileResponse extends HasErrorResponse {
+        static Message getDefaultInstance() {
+            return null;
+        }
+
+        static Builder newBuilder() {
+            return null;
+        }
+
         boolean getEof();
 
         long getReadSize();
 
         ErrorResponse getErrorResponse();
 
+        ByteString getData();
+
         interface Builder {
             GetFileResponse build();
+
+            Builder setReadSize(int read);
+
+            Builder setEof(boolean eof);
+
+            Builder setData(ByteString data);
         }
     }
 
-    public interface ReadIndexRequest {
+    public interface ReadIndexRequest extends Message {
         static Builder newBuilder() {
             return null;
         }
@@ -444,6 +478,10 @@ public final class RpcRequests {
             return null;
         }
 
+        static Message getDefaultInstance() {
+            return null;
+        }
+
         long getIndex();
 
         boolean getSuccess();
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractClientService.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractClientService.java
index dc4f745..80e7d1e 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractClientService.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/AbstractClientService.java
@@ -33,7 +33,6 @@ import com.alipay.sofa.jraft.option.RpcOptions;
 import com.alipay.sofa.jraft.rpc.ClientService;
 import com.alipay.sofa.jraft.rpc.InvokeCallback;
 import com.alipay.sofa.jraft.rpc.InvokeContext;
-import com.alipay.sofa.jraft.rpc.ProtobufMsgFactory;
 import com.alipay.sofa.jraft.rpc.RaftRpcFactory;
 import com.alipay.sofa.jraft.rpc.RpcClient;
 import com.alipay.sofa.jraft.rpc.RpcRequests.ErrorResponse;
@@ -58,10 +57,10 @@ import com.alipay.sofa.jraft.rpc.Message;
 public abstract class AbstractClientService implements ClientService {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AbstractClientService.class);
-
-    static {
-        ProtobufMsgFactory.load();
-    }
+//
+//    static {
+//        ProtobufMsgFactory.load();
+//    }
 
     protected volatile RpcClient  rpcClient;
     protected ThreadPoolExecutor  rpcExecutor;
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRaftRpcFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRaftRpcFactory.java
deleted file mode 100644
index 9fda8b0..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRaftRpcFactory.java
+++ /dev/null
@@ -1,98 +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.rpc.impl;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.alipay.remoting.CustomSerializerManager;
-import com.alipay.remoting.InvokeContext;
-import com.alipay.remoting.rpc.RpcConfigManager;
-import com.alipay.remoting.rpc.RpcConfigs;
-import com.alipay.sofa.jraft.option.RpcOptions;
-import com.alipay.sofa.jraft.rpc.ProtobufSerializer;
-import com.alipay.sofa.jraft.rpc.RaftRpcFactory;
-import com.alipay.sofa.jraft.rpc.RpcClient;
-import com.alipay.sofa.jraft.rpc.RpcServer;
-import com.alipay.sofa.jraft.util.Endpoint;
-import com.alipay.sofa.jraft.util.Requires;
-import com.alipay.sofa.jraft.util.SPI;
-import com.alipay.sofa.jraft.util.SystemPropertyUtil;
-
-/**
- *
- * @author jiachun.fjc
- */
-@SPI
-public class BoltRaftRpcFactory implements RaftRpcFactory {
-
-    private static final Logger LOG                               = LoggerFactory.getLogger(BoltRaftRpcFactory.class);
-
-    static final int            CHANNEL_WRITE_BUF_LOW_WATER_MARK  = SystemPropertyUtil.getInt(
-                                                                      "bolt.channel_write_buf_low_water_mark",
-                                                                      256 * 1024);
-    static final int            CHANNEL_WRITE_BUF_HIGH_WATER_MARK = SystemPropertyUtil.getInt(
-                                                                      "bolt.channel_write_buf_high_water_mark",
-                                                                      512 * 1024);
-
-    @Override
-    public void registerProtobufSerializer(final String className, final Object... args) {
-        CustomSerializerManager.registerCustomSerializer(className, ProtobufSerializer.INSTANCE);
-    }
-
-    @Override
-    public RpcClient createRpcClient(final ConfigHelper<RpcClient> helper) {
-        final com.alipay.remoting.rpc.RpcClient boltImpl = new com.alipay.remoting.rpc.RpcClient();
-        final RpcClient rpcClient = new BoltRpcClient(boltImpl);
-        if (helper != null) {
-            helper.config(rpcClient);
-        }
-        return rpcClient;
-    }
-
-    @Override
-    public RpcServer createRpcServer(final Endpoint endpoint, final ConfigHelper<RpcServer> helper) {
-        final int port = Requires.requireNonNull(endpoint, "endpoint").getPort();
-        Requires.requireTrue(port > 0 && port < 0xFFFF, "port out of range:" + port);
-        final com.alipay.remoting.rpc.RpcServer boltImpl = new com.alipay.remoting.rpc.RpcServer(port, true, false);
-        final RpcServer rpcServer = new BoltRpcServer(boltImpl);
-        if (helper != null) {
-            helper.config(rpcServer);
-        }
-        return rpcServer;
-    }
-
-    @Override
-    public ConfigHelper<RpcClient> defaultJRaftClientConfigHelper(final RpcOptions opts) {
-        return ins -> {
-            final BoltRpcClient client = (BoltRpcClient) ins;
-            final InvokeContext ctx = new InvokeContext();
-            ctx.put(InvokeContext.BOLT_CRC_SWITCH, opts.isEnableRpcChecksum());
-            client.setDefaultInvokeCtx(ctx);
-        };
-    }
-
-    @Override
-    public void ensurePipeline() {
-        // enable `bolt.rpc.dispatch-msg-list-in-default-executor` system property
-        if (RpcConfigManager.dispatch_msg_list_in_default_executor()) {
-            System.setProperty(RpcConfigs.DISPATCH_MSG_LIST_IN_DEFAULT_EXECUTOR, "false");
-            LOG.warn("JRaft SET {} to be false for replicator pipeline optimistic.",
-                RpcConfigs.DISPATCH_MSG_LIST_IN_DEFAULT_EXECUTOR);
-        }
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRpcClient.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRpcClient.java
deleted file mode 100644
index 1728caa..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRpcClient.java
+++ /dev/null
@@ -1,193 +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.rpc.impl;
-
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-import com.alipay.remoting.ConnectionEventType;
-import com.alipay.remoting.RejectedExecutionPolicy;
-import com.alipay.remoting.config.switches.GlobalSwitch;
-import com.alipay.sofa.jraft.ReplicatorGroup;
-import com.alipay.sofa.jraft.error.InvokeTimeoutException;
-import com.alipay.sofa.jraft.error.RemotingException;
-import com.alipay.sofa.jraft.option.RpcOptions;
-import com.alipay.sofa.jraft.rpc.InvokeCallback;
-import com.alipay.sofa.jraft.rpc.InvokeContext;
-import com.alipay.sofa.jraft.rpc.RpcClient;
-import com.alipay.sofa.jraft.rpc.impl.core.ClientServiceConnectionEventProcessor;
-import com.alipay.sofa.jraft.util.Endpoint;
-import com.alipay.sofa.jraft.util.Requires;
-
-/**
- * Bolt rpc client impl.
- *
- * @author jiachun.fjc
- */
-public class BoltRpcClient implements RpcClient {
-
-    public static final String                      BOLT_CTX                       = "BOLT_CTX";
-    public static final String                      BOLT_REJECTED_EXECUTION_POLICY = "BOLT_REJECTED_EXECUTION_POLICY";
-
-    private final com.alipay.remoting.rpc.RpcClient rpcClient;
-    private com.alipay.remoting.InvokeContext       defaultInvokeCtx;
-
-    public BoltRpcClient(com.alipay.remoting.rpc.RpcClient rpcClient) {
-        this.rpcClient = Requires.requireNonNull(rpcClient, "rpcClient");
-    }
-
-    @Override
-    public boolean init(final RpcOptions opts) {
-        this.rpcClient.switches().turnOn(GlobalSwitch.CODEC_FLUSH_CONSOLIDATION);
-        this.rpcClient.initWriteBufferWaterMark(BoltRaftRpcFactory.CHANNEL_WRITE_BUF_LOW_WATER_MARK,
-            BoltRaftRpcFactory.CHANNEL_WRITE_BUF_HIGH_WATER_MARK);
-        this.rpcClient.enableReconnectSwitch();
-        this.rpcClient.startup();
-        return true;
-    }
-
-    @Override
-    public void shutdown() {
-        this.rpcClient.shutdown();
-    }
-
-    @Override
-    public boolean checkConnection(final Endpoint endpoint) {
-        Requires.requireNonNull(endpoint, "endpoint");
-        return this.rpcClient.checkConnection(endpoint.toString());
-    }
-
-    @Override
-    public boolean checkConnection(final Endpoint endpoint, final boolean createIfAbsent) {
-        Requires.requireNonNull(endpoint, "endpoint");
-        return this.rpcClient.checkConnection(endpoint.toString(), true, true);
-    }
-
-    @Override
-    public void closeConnection(final Endpoint endpoint) {
-        Requires.requireNonNull(endpoint, "endpoint");
-        this.rpcClient.closeConnection(endpoint.toString());
-    }
-
-    @Override
-    public void registerConnectEventListener(final ReplicatorGroup replicatorGroup) {
-        this.rpcClient.addConnectionEventProcessor(ConnectionEventType.CONNECT,
-            new ClientServiceConnectionEventProcessor(replicatorGroup));
-    }
-
-    @Override
-    public Object invokeSync(final Endpoint endpoint, final Object request, final InvokeContext ctx,
-                             final long timeoutMs) throws InterruptedException, RemotingException {
-        Requires.requireNonNull(endpoint, "endpoint");
-        try {
-            return this.rpcClient.invokeSync(endpoint.toString(), request, getBoltInvokeCtx(ctx), (int) timeoutMs);
-        } catch (final com.alipay.remoting.rpc.exception.InvokeTimeoutException e) {
-            throw new InvokeTimeoutException(e);
-        } catch (final com.alipay.remoting.exception.RemotingException e) {
-            throw new RemotingException(e);
-        }
-    }
-
-    @Override
-    public void invokeAsync(final Endpoint endpoint, final Object request, final InvokeContext ctx,
-                            final InvokeCallback callback, final long timeoutMs) throws InterruptedException,
-                                                                                RemotingException {
-        Requires.requireNonNull(endpoint, "endpoint");
-        try {
-            this.rpcClient.invokeWithCallback(endpoint.toString(), request, getBoltInvokeCtx(ctx),
-                getBoltCallback(callback, ctx), (int) timeoutMs);
-        } catch (final com.alipay.remoting.rpc.exception.InvokeTimeoutException e) {
-            throw new InvokeTimeoutException(e);
-        } catch (final com.alipay.remoting.exception.RemotingException e) {
-            throw new RemotingException(e);
-        }
-    }
-
-    public com.alipay.remoting.rpc.RpcClient getRpcClient() {
-        return rpcClient;
-    }
-
-    public com.alipay.remoting.InvokeContext getDefaultInvokeCtx() {
-        return defaultInvokeCtx;
-    }
-
-    public void setDefaultInvokeCtx(com.alipay.remoting.InvokeContext defaultInvokeCtx) {
-        this.defaultInvokeCtx = defaultInvokeCtx;
-    }
-
-    private RejectedExecutionPolicy getRejectedPolicy(final InvokeContext ctx) {
-        return ctx == null ? RejectedExecutionPolicy.CALLER_HANDLE_EXCEPTION : ctx.getOrDefault(
-            BOLT_REJECTED_EXECUTION_POLICY, RejectedExecutionPolicy.CALLER_HANDLE_EXCEPTION);
-    }
-
-    private com.alipay.remoting.InvokeContext getBoltInvokeCtx(final InvokeContext ctx) {
-        if (ctx == null) {
-            return this.defaultInvokeCtx;
-        }
-
-        com.alipay.remoting.InvokeContext boltCtx = ctx.get(BOLT_CTX);
-        if (boltCtx != null) {
-            return boltCtx;
-        }
-
-        boltCtx = new com.alipay.remoting.InvokeContext();
-        for (Map.Entry<String, Object> entry : ctx.entrySet()) {
-            boltCtx.put(entry.getKey(), entry.getValue());
-        }
-        final Boolean crcSwitch = ctx.get(InvokeContext.CRC_SWITCH);
-        if (crcSwitch != null) {
-            boltCtx.put(com.alipay.remoting.InvokeContext.BOLT_CRC_SWITCH, crcSwitch);
-        }
-        return boltCtx;
-    }
-
-    private BoltCallback getBoltCallback(final InvokeCallback callback, final InvokeContext ctx) {
-        Requires.requireNonNull(callback, "callback");
-        return new BoltCallback(callback, getRejectedPolicy(ctx));
-    }
-
-    private static class BoltCallback implements com.alipay.remoting.RejectionProcessableInvokeCallback {
-
-        private final InvokeCallback          callback;
-        private final RejectedExecutionPolicy rejectedPolicy;
-
-        private BoltCallback(final InvokeCallback callback, final RejectedExecutionPolicy rejectedPolicy) {
-            this.callback = callback;
-            this.rejectedPolicy = rejectedPolicy;
-        }
-
-        @Override
-        public void onResponse(final Object result) {
-            this.callback.complete(result, null);
-        }
-
-        @Override
-        public void onException(final Throwable err) {
-            this.callback.complete(null, err);
-        }
-
-        @Override
-        public Executor getExecutor() {
-            return this.callback.executor();
-        }
-
-        @Override
-        public RejectedExecutionPolicy rejectedExecutionPolicy() {
-            return this.rejectedPolicy;
-        }
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRpcServer.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRpcServer.java
deleted file mode 100644
index bf5690b..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/BoltRpcServer.java
+++ /dev/null
@@ -1,179 +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.rpc.impl;
-
-import java.util.concurrent.Executor;
-
-import com.alipay.remoting.AsyncContext;
-import com.alipay.remoting.BizContext;
-import com.alipay.remoting.ConnectionEventType;
-import com.alipay.remoting.config.switches.GlobalSwitch;
-import com.alipay.remoting.rpc.protocol.AsyncUserProcessor;
-import com.alipay.sofa.jraft.rpc.Connection;
-import com.alipay.sofa.jraft.rpc.RpcContext;
-import com.alipay.sofa.jraft.rpc.RpcProcessor;
-import com.alipay.sofa.jraft.rpc.RpcServer;
-import com.alipay.sofa.jraft.util.Requires;
-
-/**
- * Bolt RPC server impl.
- *
- * @author jiachun.fjc
- */
-public class BoltRpcServer implements RpcServer {
-
-    private final com.alipay.remoting.rpc.RpcServer rpcServer;
-
-    public BoltRpcServer(final com.alipay.remoting.rpc.RpcServer rpcServer) {
-        this.rpcServer = Requires.requireNonNull(rpcServer, "rpcServer");
-    }
-
-    @Override
-    public boolean init(final Void opts) {
-        this.rpcServer.switches().turnOn(GlobalSwitch.CODEC_FLUSH_CONSOLIDATION);
-        this.rpcServer.initWriteBufferWaterMark(BoltRaftRpcFactory.CHANNEL_WRITE_BUF_LOW_WATER_MARK,
-            BoltRaftRpcFactory.CHANNEL_WRITE_BUF_HIGH_WATER_MARK);
-        this.rpcServer.startup();
-        return this.rpcServer.isStarted();
-    }
-
-    @Override
-    public void shutdown() {
-        this.rpcServer.shutdown();
-    }
-
-    @Override
-    public void registerConnectionClosedEventListener(final ConnectionClosedEventListener listener) {
-        this.rpcServer.addConnectionEventProcessor(ConnectionEventType.CLOSE, (remoteAddress, conn) -> {
-            final Connection proxyConn = conn == null ? null : new Connection() {
-
-                @Override
-                public Object getAttribute(final String key) {
-                    return conn.getAttribute(key);
-                }
-
-                @Override
-                public Object setAttributeIfAbsent(final String key, final Object value) {
-                    return conn.setAttributeIfAbsent(key, value);
-                }
-
-                @Override
-                public void setAttribute(final String key, final Object value) {
-                    conn.setAttribute(key, value);
-                }
-
-                @Override
-                public void close() {
-                    conn.close();
-                }
-            };
-
-            listener.onClosed(remoteAddress, proxyConn);
-        });
-    }
-
-    @Override
-    public int boundPort() {
-        return this.rpcServer.port();
-    }
-
-    @Override
-    public void registerProcessor(final RpcProcessor processor) {
-        this.rpcServer.registerUserProcessor(new AsyncUserProcessor<Object>() {
-
-            @SuppressWarnings("unchecked")
-            @Override
-            public void handleRequest(final BizContext bizCtx, final AsyncContext asyncCtx, final Object request) {
-                final RpcContext rpcCtx = new RpcContext() {
-
-                    @Override
-                    public void sendResponse(final Object responseObj) {
-                        asyncCtx.sendResponse(responseObj);
-                    }
-
-                    @Override
-                    public Connection getConnection() {
-                        com.alipay.remoting.Connection conn = bizCtx.getConnection();
-                        if (conn == null) {
-                            return null;
-                        }
-                        return new BoltConnection(conn);
-                    }
-
-                    @Override
-                    public String getRemoteAddress() {
-                        return bizCtx.getRemoteAddress();
-                    }
-                };
-
-                processor.handleRequest(rpcCtx, request);
-            }
-
-            @Override
-            public String interest() {
-                return processor.interest();
-            }
-
-            @Override
-            public ExecutorSelector getExecutorSelector() {
-                final RpcProcessor.ExecutorSelector realSelector = processor.executorSelector();
-                if (realSelector == null) {
-                    return null;
-                }
-                return realSelector::select;
-            }
-
-            @Override
-            public Executor getExecutor() {
-                return processor.executor();
-            }
-        });
-    }
-
-    public com.alipay.remoting.rpc.RpcServer getServer() {
-        return this.rpcServer;
-    }
-
-    private static class BoltConnection implements Connection {
-
-        private final com.alipay.remoting.Connection conn;
-
-        private BoltConnection(final com.alipay.remoting.Connection conn) {
-            this.conn = Requires.requireNonNull(conn, "conn");
-        }
-
-        @Override
-        public Object getAttribute(final String key) {
-            return this.conn.getAttribute(key);
-        }
-
-        @Override
-        public Object setAttributeIfAbsent(final String key, final Object value) {
-            return this.conn.setAttributeIfAbsent(key, value);
-        }
-
-        @Override
-        public void setAttribute(final String key, final Object value) {
-            this.conn.setAttribute(key, value);
-        }
-
-        @Override
-        public void close() {
-            this.conn.close();
-        }
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeLongFieldUpdater.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRaftRpcFactory.java
similarity index 51%
rename from modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeLongFieldUpdater.java
rename to modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRaftRpcFactory.java
index d3f5705..acaa136 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeLongFieldUpdater.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRaftRpcFactory.java
@@ -14,36 +14,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.alipay.sofa.jraft.util.internal;
+package com.alipay.sofa.jraft.rpc.impl;
 
-import java.lang.reflect.Field;
-import sun.misc.Unsafe;
+import com.alipay.sofa.jraft.rpc.RaftRpcFactory;
+import com.alipay.sofa.jraft.rpc.RpcClient;
+import com.alipay.sofa.jraft.rpc.RpcServer;
+import com.alipay.sofa.jraft.util.Endpoint;
+import com.alipay.sofa.jraft.util.SPI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  *
  * @author jiachun.fjc
  */
-final class UnsafeLongFieldUpdater<U> implements LongFieldUpdater<U> {
+@SPI
+public class LocalRaftRpcFactory implements RaftRpcFactory {
+    private static final Logger LOG                               = LoggerFactory.getLogger(LocalRaftRpcFactory.class);
 
-    private final long   offset;
-    private final Unsafe unsafe;
+    @Override public void registerProtobufSerializer(String className, Object... args) {
 
-    UnsafeLongFieldUpdater(Unsafe unsafe, Class<? super U> tClass, String fieldName) throws NoSuchFieldException {
-        final Field field = tClass.getDeclaredField(fieldName);
-        if (unsafe == null) {
-            throw new NullPointerException("unsafe");
-        }
-        this.unsafe = unsafe;
-        this.offset = unsafe.objectFieldOffset(field);
     }
 
-    @Override
-    public void set(final U obj, final long newValue) {
-        this.unsafe.putLong(obj, this.offset, newValue);
+    @Override public RpcClient createRpcClient(ConfigHelper<RpcClient> helper) {
+        return null;
     }
 
-    @Override
-    public long get(final U obj) {
-        return this.unsafe.getLong(obj, this.offset);
+    @Override public RpcServer createRpcServer(Endpoint endpoint, ConfigHelper<RpcServer> helper) {
+        return null;
     }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRpcClient.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRpcClient.java
new file mode 100644
index 0000000..a6c063b
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRpcClient.java
@@ -0,0 +1,64 @@
+/*
+ * 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.rpc.impl;
+
+import com.alipay.sofa.jraft.ReplicatorGroup;
+import com.alipay.sofa.jraft.error.RemotingException;
+import com.alipay.sofa.jraft.option.RpcOptions;
+import com.alipay.sofa.jraft.rpc.InvokeCallback;
+import com.alipay.sofa.jraft.rpc.InvokeContext;
+import com.alipay.sofa.jraft.rpc.RpcClient;
+import com.alipay.sofa.jraft.util.Endpoint;
+
+/**
+ * Bolt rpc client impl.
+ *
+ * @author jiachun.fjc
+ */
+public class LocalRpcClient implements RpcClient {
+    @Override public boolean checkConnection(Endpoint endpoint) {
+        return false;
+    }
+
+    @Override public boolean checkConnection(Endpoint endpoint, boolean createIfAbsent) {
+        return false;
+    }
+
+    @Override public void closeConnection(Endpoint endpoint) {
+
+    }
+
+    @Override public void registerConnectEventListener(ReplicatorGroup replicatorGroup) {
+
+    }
+
+    @Override public Object invokeSync(Endpoint endpoint, Object request, InvokeContext ctx, long timeoutMs) throws InterruptedException, RemotingException {
+        return null;
+    }
+
+    @Override public void invokeAsync(Endpoint endpoint, Object request, InvokeContext ctx, InvokeCallback callback, long timeoutMs) throws InterruptedException, RemotingException {
+
+    }
+
+    @Override public boolean init(RpcOptions opts) {
+        return false;
+    }
+
+    @Override public void shutdown() {
+
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/DebugStatistics.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRpcServer.java
similarity index 59%
rename from modules/raft/src/main/java/com/alipay/sofa/jraft/util/DebugStatistics.java
rename to modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRpcServer.java
index b2901a7..4423a42 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/DebugStatistics.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/LocalRpcServer.java
@@ -14,23 +14,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.alipay.sofa.jraft.util;
+package com.alipay.sofa.jraft.rpc.impl;
 
-import org.rocksdb.Statistics;
+import com.alipay.sofa.jraft.rpc.RpcProcessor;
+import com.alipay.sofa.jraft.rpc.RpcServer;
 
 /**
+ * Bolt RPC server impl.
  *
  * @author jiachun.fjc
  */
-public class DebugStatistics extends Statistics {
+public class LocalRpcServer implements RpcServer {
+    @Override public void registerConnectionClosedEventListener(ConnectionClosedEventListener listener) {
 
-    public String getString() {
-        return super.toString();
     }
 
-    @Override
-    public String toString() {
-        // no crash when debug
-        return getClass().getName() + "@" + Integer.toHexString(hashCode());
+    @Override public void registerProcessor(RpcProcessor<?> processor) {
+
+    }
+
+    @Override public int boundPort() {
+        return 0;
+    }
+
+    @Override public boolean init(Void opts) {
+        return false;
+    }
+
+    @Override public void shutdown() {
+
     }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/ClientServiceConnectionEventProcessor.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/ClientServiceConnectionEventProcessor.java
deleted file mode 100644
index d83a60c..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/impl/core/ClientServiceConnectionEventProcessor.java
+++ /dev/null
@@ -1,56 +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.rpc.impl.core;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.alipay.remoting.Connection;
-import com.alipay.remoting.ConnectionEventProcessor;
-import com.alipay.remoting.ConnectionEventType;
-import com.alipay.sofa.jraft.ReplicatorGroup;
-import com.alipay.sofa.jraft.entity.PeerId;
-
-/**
- * Client RPC service connection event processor for {@link ConnectionEventType#CONNECT}
- *
- * @author boyan (boyan@alibaba-inc.com)
- *
- * 2018-Apr-12 10:21:22 AM
- */
-public class ClientServiceConnectionEventProcessor implements ConnectionEventProcessor {
-
-    private static final Logger   LOG = LoggerFactory.getLogger(ClientServiceConnectionEventProcessor.class);
-
-    private final ReplicatorGroup rgGroup;
-
-    public ClientServiceConnectionEventProcessor(ReplicatorGroup rgGroup) {
-        super();
-        this.rgGroup = rgGroup;
-    }
-
-    @Override
-    public void onEvent(final String remoteAddr, final Connection conn) {
-        final PeerId peer = new PeerId();
-        if (peer.parse(remoteAddr)) {
-            LOG.info("Peer {} is connected", peer);
-            this.rgGroup.checkReplicator(peer, true);
-        } else {
-            LOG.error("Fail to parse peer: {}", remoteAddr);
-        }
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/message/DefaultMessageBuilderFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/message/DefaultMessageBuilderFactory.java
new file mode 100644
index 0000000..ce11a1b
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/rpc/message/DefaultMessageBuilderFactory.java
@@ -0,0 +1,50 @@
+package com.alipay.sofa.jraft.rpc.message;
+
+import com.alipay.sofa.jraft.rpc.CliRequests;
+import com.alipay.sofa.jraft.rpc.MessageBuilderFactory;
+
+public class DefaultMessageBuilderFactory implements MessageBuilderFactory {
+    @Override public CliRequests.AddPeerRequest.Builder create() {
+        return new AddPeerRequestImpl();
+    }
+
+    private static class AddPeerRequestImpl implements CliRequests.AddPeerRequest, CliRequests.AddPeerRequest.Builder {
+        private String groupId;
+        private String leaderId;
+        private String peerId;
+
+        @Override public String getGroupId() {
+            return groupId;
+        }
+
+        @Override public String getLeaderId() {
+            return leaderId;
+        }
+
+        @Override public String getPeerId() {
+            return peerId;
+        }
+
+        @Override public Builder setGroupId(String groupId) {
+            this.groupId = groupId;
+
+            return this;
+        }
+
+        @Override public Builder setLeaderId(String leaderId) {
+            this.leaderId = leaderId;
+
+            return this;
+        }
+
+        @Override public Builder setPeerId(String peerId) {
+            this.peerId = peerId;
+
+            return this;
+        }
+
+        @Override public CliRequests.AddPeerRequest build() {
+            return this;
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/FileService.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/FileService.java
index 1aa2b25..d206760 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/FileService.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/FileService.java
@@ -16,14 +16,14 @@
  */
 package com.alipay.sofa.jraft.storage;
 
+import com.alipay.sofa.jraft.util.ByteString;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicLong;
 
-import io.netty.util.internal.ThreadLocalRandom;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -37,9 +37,7 @@ import com.alipay.sofa.jraft.util.ByteBufferCollector;
 import com.alipay.sofa.jraft.util.OnlyForTest;
 import com.alipay.sofa.jraft.util.RpcFactoryHelper;
 import com.alipay.sofa.jraft.util.Utils;
-import com.google.protobuf.ByteString;
 import com.alipay.sofa.jraft.rpc.Message;
-import com.google.protobuf.ZeroByteStringHelper;
 
 /**
  * File reader service.
@@ -114,7 +112,7 @@ public final class FileService {
                 responseBuilder.setData(ByteString.EMPTY);
             } else {
                 // TODO check hole
-                responseBuilder.setData(ZeroByteStringHelper.wrap(buf));
+                responseBuilder.setData(new ByteString(buf));
             }
             return responseBuilder.build();
         } catch (final RetryAgainException e) {
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/LocalLogStorage.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/LocalLogStorage.java
new file mode 100644
index 0000000..4176210
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/LocalLogStorage.java
@@ -0,0 +1,311 @@
+package com.alipay.sofa.jraft.storage.impl;
+
+import com.alipay.sofa.jraft.conf.Configuration;
+import com.alipay.sofa.jraft.conf.ConfigurationEntry;
+import com.alipay.sofa.jraft.conf.ConfigurationManager;
+import com.alipay.sofa.jraft.entity.EnumOutter;
+import com.alipay.sofa.jraft.entity.LogEntry;
+import com.alipay.sofa.jraft.entity.LogId;
+import com.alipay.sofa.jraft.entity.codec.LogEntryDecoder;
+import com.alipay.sofa.jraft.entity.codec.LogEntryEncoder;
+import com.alipay.sofa.jraft.option.LogStorageOptions;
+import com.alipay.sofa.jraft.option.RaftOptions;
+import com.alipay.sofa.jraft.storage.LogStorage;
+import com.alipay.sofa.jraft.util.Describer;
+import com.alipay.sofa.jraft.util.Requires;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Stores log in heap.
+ *
+ * TODO can use SegmentList.
+ */
+public class LocalLogStorage implements LogStorage, Describer {
+    private static final Logger LOG = LoggerFactory.getLogger(LocalLogStorage.class);
+
+    private final String                    path;
+    private final boolean                   sync;
+    private final boolean                   openStatistics;
+    private final ReadWriteLock             readWriteLock = new ReentrantReadWriteLock();
+    private final Lock                      readLock      = this.readWriteLock.readLock();
+    private final Lock                      writeLock     = this.readWriteLock.writeLock();
+
+    private volatile long                   firstLogIndex = 1;
+
+    private final LinkedList<LogEntry> log = new LinkedList<>();
+
+    private LogEntryEncoder logEntryEncoder;
+    private LogEntryDecoder logEntryDecoder;
+
+    private volatile boolean initialized = false;
+
+    public LocalLogStorage(final String path, final RaftOptions raftOptions) {
+        super();
+        this.path = path;
+        this.sync = raftOptions.isSync();
+        this.openStatistics = raftOptions.isOpenStatistics();
+    }
+
+    @Override
+    public boolean init(final LogStorageOptions opts) {
+        Requires.requireNonNull(opts.getConfigurationManager(), "Null conf manager");
+        Requires.requireNonNull(opts.getLogEntryCodecFactory(), "Null log entry codec factory");
+        this.writeLock.lock();
+        try {
+            if (initialized) {
+                LOG.warn("RocksDBLogStorage init() already.");
+                return true;
+            }
+            this.initialized = true;
+            this.logEntryDecoder = opts.getLogEntryCodecFactory().decoder();
+            this.logEntryEncoder = opts.getLogEntryCodecFactory().encoder();
+            Requires.requireNonNull(this.logEntryDecoder, "Null log entry decoder");
+            Requires.requireNonNull(this.logEntryEncoder, "Null log entry encoder");
+
+            return true;
+        } catch (final Exception e) {
+            LOG.error("Fail to init RocksDBLogStorage, path={}.", this.path, e);
+            return false;
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    /**
+     * Save the first log index into conf column family.
+     */
+    private boolean saveFirstLogIndex(final long firstLogIndex) {
+        this.readLock.lock();
+        try {
+//            final byte[] vs = new byte[8];
+//            Bits.putLong(vs, 0, firstLogIndex);
+//            checkState();
+//            this.db.put(this.confHandle, this.writeOptions, FIRST_LOG_IDX_KEY, vs);
+
+            this.firstLogIndex = firstLogIndex;
+
+            return true;
+        } catch (final Exception e) {
+            LOG.error("Fail to save first log index {}.", firstLogIndex, e);
+            return false;
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        this.writeLock.lock();
+        try {
+
+            this.initialized = false;
+            this.log.clear();
+            LOG.info("DB destroyed, the db path is: {}.", this.path);
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+//    private void closeDB() {
+//        this.confHandle.close();
+//        this.defaultHandle.close();
+//        this.db.close();
+//    }
+
+    @Override
+    public long getFirstLogIndex() {
+        this.readLock.lock();
+        try {
+//            if (this.hasLoadFirstLogIndex) {
+//                return this.firstLogIndex;
+//            }
+//            checkState();
+//            it = this.db.newIterator(this.defaultHandle, this.totalOrderReadOptions);
+//            it.seekToFirst();
+//            if (it.isValid()) {
+//                final long ret = Bits.getLong(it.key(), 0);
+//                saveFirstLogIndex(ret);
+//                setFirstLogIndex(ret);
+//                return ret;
+//            }
+            return this.firstLogIndex;
+        } finally {
+//            if (it != null) {
+//                it.close();
+//            }
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public long getLastLogIndex() {
+        this.readLock.lock();
+        //checkState();
+        try  {
+//            it.seekToLast();
+//            if (it.isValid()) {
+//                return Bits.getLong(it.key(), 0);
+//            }
+
+
+
+            return this.firstLogIndex - 1 + this.log.size();
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public LogEntry getEntry(final long index) {
+        this.readLock.lock();
+        try {
+            if (index < this.firstLogIndex) {
+                return null;
+            }
+
+            return log.get((int) (this.firstLogIndex - 1 + this.log.size()));
+        } catch (Exception e) {
+            LOG.error("Fail to get log entry at index {}.", index, e);
+        } finally {
+            this.readLock.unlock();
+        }
+        return null;
+    }
+
+    @Override
+    public long getTerm(final long index) {
+        final LogEntry entry = getEntry(index);
+        if (entry != null) {
+            return entry.getId().getTerm();
+        }
+        return 0;
+    }
+
+    @Override
+    public boolean appendEntry(final LogEntry entry) {
+        this.readLock.lock();
+        try {
+            if (!initialized) {
+                LOG.warn("DB not initialized or destroyed.");
+                return false;
+            }
+
+            this.log.add(entry);
+
+            return true;
+        } catch (Exception e) {
+            LOG.error("Fail to append entry.", e);
+            return false;
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public int appendEntries(final List<LogEntry> entries) {
+        if (entries == null || entries.isEmpty()) {
+            return 0;
+        }
+        final int entriesCount = entries.size();
+        try {
+            if (!initialized) {
+                LOG.warn("DB not initialized or destroyed.");
+                return 0;
+            }
+
+            this.log.addAll(entries);
+
+            return entriesCount;
+        } catch (Exception e) {
+            LOG.error("Fail to append entry.", e);
+            return 0;
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    @Override
+    public boolean truncatePrefix(final long firstIndexKept) {
+        this.readLock.lock();
+        try {
+            final long startIndex = getFirstLogIndex();
+
+            this.firstLogIndex = firstIndexKept;
+
+            for (long i = startIndex; i < firstIndexKept; i++)
+                log.pollFirst();
+
+            return true;
+        } finally {
+            this.readLock.unlock();
+        }
+
+    }
+
+    @Override
+    public boolean truncateSuffix(final long lastIndexKept) {
+        this.readLock.lock();
+        try {
+            long lastLogIndex = getLastLogIndex();
+
+            while(lastLogIndex-- > lastIndexKept)
+                log.pollLast();
+
+            return true;
+        } catch (Exception e) {
+            LOG.error("Fail to truncateSuffix {}.", lastIndexKept, e);
+        } finally {
+            this.readLock.unlock();
+        }
+        return false;
+    }
+
+    @Override
+    // TOOD it doesn't work.
+    public boolean reset(final long nextLogIndex) {
+        if (nextLogIndex <= 0) {
+            throw new IllegalArgumentException("Invalid next log index.");
+        }
+        this.writeLock.lock();
+        try {
+            LogEntry entry = getEntry(nextLogIndex);
+
+            try {
+                if (false) { // TODO should read snapshot.
+                    if (entry == null) {
+                        entry = new LogEntry();
+                        entry.setType(EnumOutter.EntryType.ENTRY_TYPE_NO_OP);
+                        entry.setId(new LogId(nextLogIndex, 0));
+                        LOG.warn("Entry not found for nextLogIndex {} when reset.", nextLogIndex);
+                    }
+                    return appendEntry(entry);
+                } else {
+                    return false;
+                }
+            } catch (final Exception e) {
+                LOG.error("Fail to reset next log index.", e);
+                return false;
+            }
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+
+    @Override
+    public void describe(final Printer out) {
+        this.readLock.lock();
+        try {
+            // TODO
+        } catch (final Exception e) {
+            out.println(e);
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java
deleted file mode 100644
index 50ed51c..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/impl/RocksDBLogStorage.java
+++ /dev/null
@@ -1,743 +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.storage.impl;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.rocksdb.BlockBasedTableConfig;
-import org.rocksdb.ColumnFamilyDescriptor;
-import org.rocksdb.ColumnFamilyHandle;
-import org.rocksdb.ColumnFamilyOptions;
-import org.rocksdb.DBOptions;
-import org.rocksdb.Options;
-import org.rocksdb.ReadOptions;
-import org.rocksdb.RocksDB;
-import org.rocksdb.RocksDBException;
-import org.rocksdb.RocksIterator;
-import org.rocksdb.StringAppendOperator;
-import org.rocksdb.WriteBatch;
-import org.rocksdb.WriteOptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.alipay.sofa.jraft.conf.Configuration;
-import com.alipay.sofa.jraft.conf.ConfigurationEntry;
-import com.alipay.sofa.jraft.conf.ConfigurationManager;
-import com.alipay.sofa.jraft.entity.EnumOutter.EntryType;
-import com.alipay.sofa.jraft.entity.LogEntry;
-import com.alipay.sofa.jraft.entity.LogId;
-import com.alipay.sofa.jraft.entity.codec.LogEntryDecoder;
-import com.alipay.sofa.jraft.entity.codec.LogEntryEncoder;
-import com.alipay.sofa.jraft.option.LogStorageOptions;
-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.BytesUtil;
-import com.alipay.sofa.jraft.util.DebugStatistics;
-import com.alipay.sofa.jraft.util.Describer;
-import com.alipay.sofa.jraft.util.Requires;
-import com.alipay.sofa.jraft.util.StorageOptionsFactory;
-import com.alipay.sofa.jraft.util.Utils;
-
-/**
- * Log storage based on rocksdb.
- *
- * @author boyan (boyan@alibaba-inc.com)
- *
- * 2018-Apr-06 7:27:47 AM
- */
-public class RocksDBLogStorage implements LogStorage, Describer {
-
-    private static final Logger LOG = LoggerFactory.getLogger(RocksDBLogStorage.class);
-
-    static {
-        RocksDB.loadLibrary();
-    }
-
-    /**
-     * Write batch template.
-     *
-     * @author boyan (boyan@alibaba-inc.com)
-     *
-     * 2017-Nov-08 11:19:22 AM
-     */
-    private interface WriteBatchTemplate {
-
-        void execute(WriteBatch batch) throws RocksDBException, IOException, InterruptedException;
-    }
-
-    /**
-     * A write context
-     * @author boyan(boyan@antfin.com)
-     *
-     */
-    public interface WriteContext {
-        /**
-         * Start a sub job.
-         */
-        default void startJob() {
-        }
-
-        /**
-         * Finish a sub job
-         */
-        default void finishJob() {
-        }
-
-        /**
-         * Adds a callback that will be invoked after all sub jobs finish.
-         */
-        default void addFinishHook(final Runnable r) {
-
-        }
-
-        /**
-         * Set an exception to context.
-         * @param e exception
-         */
-        default void setError(final Exception e) {
-        }
-
-        /**
-         * Wait for all sub jobs finish.
-         */
-        default void joinAll() throws InterruptedException, IOException {
-        }
-    }
-
-    /**
-     * An empty write context
-     * @author boyan(boyan@antfin.com)
-     *
-     */
-    protected static class EmptyWriteContext implements WriteContext {
-        static EmptyWriteContext INSTANCE = new EmptyWriteContext();
-    }
-
-    private final String                    path;
-    private final boolean                   sync;
-    private final boolean                   openStatistics;
-    private RocksDB                         db;
-    private DBOptions                       dbOptions;
-    private WriteOptions                    writeOptions;
-    private final List<ColumnFamilyOptions> cfOptions     = new ArrayList<>();
-    private ColumnFamilyHandle              defaultHandle;
-    private ColumnFamilyHandle              confHandle;
-    private ReadOptions                     totalOrderReadOptions;
-    private DebugStatistics                 statistics;
-    private final ReadWriteLock             readWriteLock = new ReentrantReadWriteLock();
-    private final Lock                      readLock      = this.readWriteLock.readLock();
-    private final Lock                      writeLock     = this.readWriteLock.writeLock();
-
-    private volatile long                   firstLogIndex = 1;
-
-    private volatile boolean                hasLoadFirstLogIndex;
-
-    private LogEntryEncoder                 logEntryEncoder;
-    private LogEntryDecoder                 logEntryDecoder;
-
-    public RocksDBLogStorage(final String path, final RaftOptions raftOptions) {
-        super();
-        this.path = path;
-        this.sync = raftOptions.isSync();
-        this.openStatistics = raftOptions.isOpenStatistics();
-    }
-
-    public static DBOptions createDBOptions() {
-        return StorageOptionsFactory.getRocksDBOptions(RocksDBLogStorage.class);
-    }
-
-    public static ColumnFamilyOptions createColumnFamilyOptions() {
-        final BlockBasedTableConfig tConfig = StorageOptionsFactory
-                .getRocksDBTableFormatConfig(RocksDBLogStorage.class);
-        return StorageOptionsFactory.getRocksDBColumnFamilyOptions(RocksDBLogStorage.class) //
-                .useFixedLengthPrefixExtractor(8) //
-                .setTableFormatConfig(tConfig) //
-                .setMergeOperator(new StringAppendOperator());
-    }
-
-    @Override
-    public boolean init(final LogStorageOptions opts) {
-        Requires.requireNonNull(opts.getConfigurationManager(), "Null conf manager");
-        Requires.requireNonNull(opts.getLogEntryCodecFactory(), "Null log entry codec factory");
-        this.writeLock.lock();
-        try {
-            if (this.db != null) {
-                LOG.warn("RocksDBLogStorage init() already.");
-                return true;
-            }
-            this.logEntryDecoder = opts.getLogEntryCodecFactory().decoder();
-            this.logEntryEncoder = opts.getLogEntryCodecFactory().encoder();
-            Requires.requireNonNull(this.logEntryDecoder, "Null log entry decoder");
-            Requires.requireNonNull(this.logEntryEncoder, "Null log entry encoder");
-            this.dbOptions = createDBOptions();
-            if (this.openStatistics) {
-                this.statistics = new DebugStatistics();
-                this.dbOptions.setStatistics(this.statistics);
-            }
-
-            this.writeOptions = new WriteOptions();
-            this.writeOptions.setSync(this.sync);
-            this.totalOrderReadOptions = new ReadOptions();
-            this.totalOrderReadOptions.setTotalOrderSeek(true);
-
-            return initAndLoad(opts.getConfigurationManager());
-        } catch (final RocksDBException e) {
-            LOG.error("Fail to init RocksDBLogStorage, path={}.", this.path, e);
-            return false;
-        } finally {
-            this.writeLock.unlock();
-        }
-
-    }
-
-    private boolean initAndLoad(final ConfigurationManager confManager) throws RocksDBException {
-        this.hasLoadFirstLogIndex = false;
-        this.firstLogIndex = 1;
-        final List<ColumnFamilyDescriptor> columnFamilyDescriptors = new ArrayList<>();
-        final ColumnFamilyOptions cfOption = createColumnFamilyOptions();
-        this.cfOptions.add(cfOption);
-        // Column family to store configuration log entry.
-        columnFamilyDescriptors.add(new ColumnFamilyDescriptor("Configuration".getBytes(), cfOption));
-        // Default column family to store user data log entry.
-        columnFamilyDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOption));
-
-        openDB(columnFamilyDescriptors);
-        load(confManager);
-        return onInitLoaded();
-    }
-
-    /**
-     * First log index and last log index key in configuration column family.
-     */
-    public static final byte[] FIRST_LOG_IDX_KEY = Utils.getBytes("meta/firstLogIndex");
-
-    private void load(final ConfigurationManager confManager) {
-        checkState();
-        try (final RocksIterator it = this.db.newIterator(this.confHandle, this.totalOrderReadOptions)) {
-            it.seekToFirst();
-            while (it.isValid()) {
-                final byte[] ks = it.key();
-                final byte[] bs = it.value();
-
-                // LogEntry index
-                if (ks.length == 8) {
-                    final LogEntry entry = this.logEntryDecoder.decode(bs);
-                    if (entry != null) {
-                        if (entry.getType() == EntryType.ENTRY_TYPE_CONFIGURATION) {
-                            final ConfigurationEntry confEntry = new ConfigurationEntry();
-                            confEntry.setId(new LogId(entry.getId().getIndex(), entry.getId().getTerm()));
-                            confEntry.setConf(new Configuration(entry.getPeers(), entry.getLearners()));
-                            if (entry.getOldPeers() != null) {
-                                confEntry.setOldConf(new Configuration(entry.getOldPeers(), entry.getOldLearners()));
-                            }
-                            if (confManager != null) {
-                                confManager.add(confEntry);
-                            }
-                        }
-                    } else {
-                        LOG.warn("Fail to decode conf entry at index {}, the log data is: {}.", Bits.getLong(ks, 0),
-                            BytesUtil.toHex(bs));
-                    }
-                } else {
-                    if (Arrays.equals(FIRST_LOG_IDX_KEY, ks)) {
-                        setFirstLogIndex(Bits.getLong(bs, 0));
-                        truncatePrefixInBackground(0L, this.firstLogIndex);
-                    } else {
-                        LOG.warn("Unknown entry in configuration storage key={}, value={}.", BytesUtil.toHex(ks),
-                            BytesUtil.toHex(bs));
-                    }
-                }
-                it.next();
-            }
-        }
-    }
-
-    private void setFirstLogIndex(final long index) {
-        this.firstLogIndex = index;
-        this.hasLoadFirstLogIndex = true;
-    }
-
-    /**
-     * Save the first log index into conf column family.
-     */
-    private boolean saveFirstLogIndex(final long firstLogIndex) {
-        this.readLock.lock();
-        try {
-            final byte[] vs = new byte[8];
-            Bits.putLong(vs, 0, firstLogIndex);
-            checkState();
-            this.db.put(this.confHandle, this.writeOptions, FIRST_LOG_IDX_KEY, vs);
-            return true;
-        } catch (final RocksDBException e) {
-            LOG.error("Fail to save first log index {}.", firstLogIndex, e);
-            return false;
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-
-    private void openDB(final List<ColumnFamilyDescriptor> columnFamilyDescriptors) throws RocksDBException {
-        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
-
-        final File dir = new File(this.path);
-        if (dir.exists() && !dir.isDirectory()) {
-            throw new IllegalStateException("Invalid log path, it's a regular file: " + this.path);
-        }
-        this.db = RocksDB.open(this.dbOptions, this.path, columnFamilyDescriptors, columnFamilyHandles);
-
-        assert (columnFamilyHandles.size() == 2);
-        this.confHandle = columnFamilyHandles.get(0);
-        this.defaultHandle = columnFamilyHandles.get(1);
-    }
-
-    private void checkState() {
-        Requires.requireNonNull(this.db, "DB not initialized or destroyed");
-    }
-
-    /**
-     * Execute write batch template.
-     *
-     * @param template write batch template
-     */
-    private boolean executeBatch(final WriteBatchTemplate template) {
-        this.readLock.lock();
-        if (this.db == null) {
-            LOG.warn("DB not initialized or destroyed.");
-            this.readLock.unlock();
-            return false;
-        }
-        try (final WriteBatch batch = new WriteBatch()) {
-            template.execute(batch);
-            this.db.write(this.writeOptions, batch);
-        } catch (final RocksDBException e) {
-            LOG.error("Execute batch failed with rocksdb exception.", e);
-            return false;
-        } catch (final IOException e) {
-            LOG.error("Execute batch failed with io exception.", e);
-            return false;
-        } catch (final InterruptedException e) {
-            LOG.error("Execute batch failed with interrupt.", e);
-            Thread.currentThread().interrupt();
-            return false;
-        } finally {
-            this.readLock.unlock();
-        }
-        return true;
-    }
-
-    @Override
-    public void shutdown() {
-        this.writeLock.lock();
-        try {
-            // The shutdown order is matter.
-            // 1. close column family handles
-            closeDB();
-            onShutdown();
-            // 2. close column family options.
-            for (final ColumnFamilyOptions opt : this.cfOptions) {
-                opt.close();
-            }
-            // 3. close options
-            this.dbOptions.close();
-            if (this.statistics != null) {
-                this.statistics.close();
-            }
-            this.writeOptions.close();
-            this.totalOrderReadOptions.close();
-            // 4. help gc.
-            this.cfOptions.clear();
-            this.dbOptions = null;
-            this.statistics = null;
-            this.writeOptions = null;
-            this.totalOrderReadOptions = null;
-            this.defaultHandle = null;
-            this.confHandle = null;
-            this.db = null;
-            LOG.info("DB destroyed, the db path is: {}.", this.path);
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    private void closeDB() {
-        this.confHandle.close();
-        this.defaultHandle.close();
-        this.db.close();
-    }
-
-    @Override
-    public long getFirstLogIndex() {
-        this.readLock.lock();
-        RocksIterator it = null;
-        try {
-            if (this.hasLoadFirstLogIndex) {
-                return this.firstLogIndex;
-            }
-            checkState();
-            it = this.db.newIterator(this.defaultHandle, this.totalOrderReadOptions);
-            it.seekToFirst();
-            if (it.isValid()) {
-                final long ret = Bits.getLong(it.key(), 0);
-                saveFirstLogIndex(ret);
-                setFirstLogIndex(ret);
-                return ret;
-            }
-            return 1L;
-        } finally {
-            if (it != null) {
-                it.close();
-            }
-            this.readLock.unlock();
-        }
-    }
-
-    @Override
-    public long getLastLogIndex() {
-        this.readLock.lock();
-        checkState();
-        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 {
-            this.readLock.unlock();
-        }
-    }
-
-    @Override
-    public LogEntry getEntry(final long index) {
-        this.readLock.lock();
-        try {
-            if (this.hasLoadFirstLogIndex && index < this.firstLogIndex) {
-                return null;
-            }
-            final byte[] keyBytes = getKeyBytes(index);
-            final byte[] bs = onDataGet(index, getValueFromRocksDB(keyBytes));
-            if (bs != null) {
-                final LogEntry entry = this.logEntryDecoder.decode(bs);
-                if (entry != null) {
-                    return entry;
-                } else {
-                    LOG.error("Bad log entry format for index={}, the log data is: {}.", index, BytesUtil.toHex(bs));
-                    // invalid data remove? TODO
-                    return null;
-                }
-            }
-        } catch (final RocksDBException | IOException e) {
-            LOG.error("Fail to get log entry at index {}.", index, e);
-        } finally {
-            this.readLock.unlock();
-        }
-        return null;
-    }
-
-    protected byte[] getValueFromRocksDB(final byte[] keyBytes) throws RocksDBException {
-        checkState();
-        return this.db.get(this.defaultHandle, keyBytes);
-    }
-
-    protected byte[] getKeyBytes(final long index) {
-        final byte[] ks = new byte[8];
-        Bits.putLong(ks, 0, index);
-        return ks;
-    }
-
-    @Override
-    public long getTerm(final long index) {
-        final LogEntry entry = getEntry(index);
-        if (entry != null) {
-            return entry.getId().getTerm();
-        }
-        return 0;
-    }
-
-    private void addConfBatch(final LogEntry entry, final WriteBatch batch) throws RocksDBException {
-        final byte[] ks = getKeyBytes(entry.getId().getIndex());
-        final byte[] content = this.logEntryEncoder.encode(entry);
-        batch.put(this.defaultHandle, ks, content);
-        batch.put(this.confHandle, ks, content);
-    }
-
-    private void addDataBatch(final LogEntry entry, final WriteBatch batch,
-                              final WriteContext ctx) throws RocksDBException, IOException, InterruptedException {
-        final long logIndex = entry.getId().getIndex();
-        final byte[] content = this.logEntryEncoder.encode(entry);
-        batch.put(this.defaultHandle, getKeyBytes(logIndex), onDataAppend(logIndex, content, ctx));
-    }
-
-    @Override
-    public boolean appendEntry(final LogEntry entry) {
-        if (entry.getType() == EntryType.ENTRY_TYPE_CONFIGURATION) {
-            return executeBatch(batch -> addConfBatch(entry, batch));
-        } else {
-            this.readLock.lock();
-            try {
-                if (this.db == null) {
-                    LOG.warn("DB not initialized or destroyed.");
-                    return false;
-                }
-                final WriteContext writeCtx = newWriteContext();
-                final long logIndex = entry.getId().getIndex();
-                final byte[] valueBytes = this.logEntryEncoder.encode(entry);
-                final byte[] newValueBytes = onDataAppend(logIndex, valueBytes, writeCtx);
-                writeCtx.startJob();
-                this.db.put(this.defaultHandle, this.writeOptions, getKeyBytes(logIndex), newValueBytes);
-                writeCtx.joinAll();
-                if (newValueBytes != valueBytes) {
-                    doSync();
-                }
-                return true;
-            } catch (final RocksDBException | IOException e) {
-                LOG.error("Fail to append entry.", e);
-                return false;
-            } catch (final InterruptedException e) {
-                Thread.currentThread().interrupt();
-                return false;
-            } finally {
-                this.readLock.unlock();
-            }
-        }
-    }
-
-    private void doSync() throws IOException, InterruptedException {
-        onSync();
-    }
-
-    @Override
-    public int appendEntries(final List<LogEntry> entries) {
-        if (entries == null || entries.isEmpty()) {
-            return 0;
-        }
-        final int entriesCount = entries.size();
-        final boolean ret = executeBatch(batch -> {
-            final WriteContext writeCtx = newWriteContext();
-            for (int i = 0; i < entriesCount; i++) {
-                final LogEntry entry = entries.get(i);
-                if (entry.getType() == EntryType.ENTRY_TYPE_CONFIGURATION) {
-                    addConfBatch(entry, batch);
-                } else {
-                    writeCtx.startJob();
-                    addDataBatch(entry, batch, writeCtx);
-                }
-            }
-            writeCtx.joinAll();
-            doSync();
-        });
-
-        if (ret) {
-            return entriesCount;
-        } else {
-            return 0;
-        }
-    }
-
-    @Override
-    public boolean truncatePrefix(final long firstIndexKept) {
-        this.readLock.lock();
-        try {
-            final long startIndex = getFirstLogIndex();
-            final boolean ret = saveFirstLogIndex(firstIndexKept);
-            if (ret) {
-                setFirstLogIndex(firstIndexKept);
-            }
-            truncatePrefixInBackground(startIndex, firstIndexKept);
-            return ret;
-        } finally {
-            this.readLock.unlock();
-        }
-
-    }
-
-    private void truncatePrefixInBackground(final long startIndex, final long firstIndexKept) {
-        // delete logs in background.
-        Utils.runInThread(() -> {
-            this.readLock.lock();
-            try {
-                if (this.db == null) {
-                    return;
-                }
-                onTruncatePrefix(startIndex, firstIndexKept);
-                this.db.deleteRange(this.defaultHandle, getKeyBytes(startIndex), getKeyBytes(firstIndexKept));
-                this.db.deleteRange(this.confHandle, getKeyBytes(startIndex), getKeyBytes(firstIndexKept));
-            } catch (final RocksDBException | IOException e) {
-                LOG.error("Fail to truncatePrefix {}.", firstIndexKept, e);
-            } finally {
-                this.readLock.unlock();
-            }
-        });
-    }
-
-    @Override
-    public boolean truncateSuffix(final long lastIndexKept) {
-        this.readLock.lock();
-        try {
-            try {
-                onTruncateSuffix(lastIndexKept);
-            } finally {
-                this.db.deleteRange(this.defaultHandle, this.writeOptions, getKeyBytes(lastIndexKept + 1),
-                    getKeyBytes(getLastLogIndex() + 1));
-                this.db.deleteRange(this.confHandle, this.writeOptions, getKeyBytes(lastIndexKept + 1),
-                    getKeyBytes(getLastLogIndex() + 1));
-            }
-            return true;
-        } catch (final RocksDBException | IOException e) {
-            LOG.error("Fail to truncateSuffix {}.", lastIndexKept, e);
-        } finally {
-            this.readLock.unlock();
-        }
-        return false;
-    }
-
-    @Override
-    public boolean reset(final long nextLogIndex) {
-        if (nextLogIndex <= 0) {
-            throw new IllegalArgumentException("Invalid next log index.");
-        }
-        this.writeLock.lock();
-        try (final Options opt = new Options()) {
-            LogEntry entry = getEntry(nextLogIndex);
-            closeDB();
-            try {
-                RocksDB.destroyDB(this.path, opt);
-                onReset(nextLogIndex);
-                if (initAndLoad(null)) {
-                    if (entry == null) {
-                        entry = new LogEntry();
-                        entry.setType(EntryType.ENTRY_TYPE_NO_OP);
-                        entry.setId(new LogId(nextLogIndex, 0));
-                        LOG.warn("Entry not found for nextLogIndex {} when reset.", nextLogIndex);
-                    }
-                    return appendEntry(entry);
-                } else {
-                    return false;
-                }
-            } catch (final RocksDBException e) {
-                LOG.error("Fail to reset next log index.", e);
-                return false;
-            }
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    // Hooks for {@link RocksDBSegmentLogStorage}
-
-    /**
-     * Called after opening RocksDB and loading configuration into conf manager.
-     */
-    protected boolean onInitLoaded() {
-        return true;
-    }
-
-    /**
-     * Called after closing db.
-     */
-    protected void onShutdown() {
-    }
-
-    /**
-     * Called after resetting db.
-     *
-     * @param nextLogIndex next log index
-     */
-    protected void onReset(final long nextLogIndex) {
-    }
-
-    /**
-     * Called after truncating prefix logs in rocksdb.
-     *
-     * @param startIndex     the start index
-     * @param firstIndexKept the first index to kept
-     */
-    protected void onTruncatePrefix(final long startIndex, final long firstIndexKept) throws RocksDBException,
-    IOException {
-    }
-
-    /**
-     * Called when sync data into file system.
-     */
-    protected void onSync() throws IOException, InterruptedException {
-    }
-
-    protected boolean isSync() {
-        return this.sync;
-    }
-
-    /**
-     * Called after truncating suffix logs in rocksdb.
-     *
-     * @param lastIndexKept the last index to kept
-     */
-    protected void onTruncateSuffix(final long lastIndexKept) throws RocksDBException, IOException {
-    }
-
-    protected WriteContext newWriteContext() {
-        return EmptyWriteContext.INSTANCE;
-    }
-
-    /**
-     * Called before appending data entry.
-     *
-     * @param logIndex the log index
-     * @param value    the data value in log entry.
-     * @return the new value
-     */
-    protected byte[] onDataAppend(final long logIndex, final byte[] value,
-                                  final WriteContext ctx) throws IOException, InterruptedException {
-        ctx.finishJob();
-        return value;
-    }
-
-    /**
-     * Called after getting data from rocksdb.
-     *
-     * @param logIndex the log index
-     * @param value    the value in rocksdb
-     * @return the new value
-     */
-    protected byte[] onDataGet(final long logIndex, final byte[] value) throws IOException {
-        return value;
-    }
-
-    @Override
-    public void describe(final Printer out) {
-        this.readLock.lock();
-        try {
-            if (this.db != null) {
-                out.println(this.db.getProperty("rocksdb.stats"));
-            }
-            out.println("");
-            if (this.statistics != null) {
-                out.println(this.statistics.getString());
-            }
-        } catch (final RocksDBException e) {
-            out.println(e);
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java
index 14e5135..4ce83f0 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/io/ProtoBufFile.java
@@ -16,6 +16,8 @@
  */
 package com.alipay.sofa.jraft.storage.io;
 
+import com.alipay.sofa.jraft.rpc.CliRequests;
+import com.alipay.sofa.jraft.util.Marshaller;
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -24,7 +26,6 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
-import com.alipay.sofa.jraft.rpc.ProtobufMsgFactory;
 import com.alipay.sofa.jraft.util.Bits;
 import com.alipay.sofa.jraft.util.Utils;
 import com.alipay.sofa.jraft.rpc.Message;
@@ -43,9 +44,9 @@ import com.alipay.sofa.jraft.rpc.Message;
  */
 public class ProtoBufFile {
 
-    static {
-        ProtobufMsgFactory.load();
-    }
+//    static {
+//        ProtobufMsgFactory.load();
+//    }
 
     /** file path */
     private final String path;
@@ -68,18 +69,19 @@ public class ProtoBufFile {
         try (final FileInputStream fin = new FileInputStream(file);
                 final BufferedInputStream input = new BufferedInputStream(fin)) {
             readBytes(lenBytes, input);
-            final int len = Bits.getInt(lenBytes, 0);
+            final int len = Bits.getInt(lenBytes, 0); // TODO asch endianess ?
             if (len <= 0) {
                 throw new IOException("Invalid message fullName.");
             }
             final byte[] nameBytes = new byte[len];
             readBytes(nameBytes, input);
-            final String name = new String(nameBytes);
-            readBytes(lenBytes, input);
-            final int msgLen = Bits.getInt(lenBytes, 0);
-            final byte[] msgBytes = new byte[msgLen];
-            readBytes(msgBytes, input);
-            return ProtobufMsgFactory.newMessageByProtoClassName(name, msgBytes);
+//            final String name = new String(nameBytes);
+//            readBytes(lenBytes, input);
+//            final int msgLen = Bits.getInt(lenBytes, 0);
+//            final byte[] msgBytes = new byte[msgLen];
+//            readBytes(msgBytes, input);
+//            return ProtobufMsgFactory.newMessageByProtoClassName(name, msgBytes);
+            return Marshaller.DEFAULT.unmarshall(nameBytes);
         }
     }
 
@@ -102,19 +104,12 @@ public class ProtoBufFile {
         final File file = new File(this.path + ".tmp");
         try (final FileOutputStream fOut = new FileOutputStream(file);
                 final BufferedOutputStream output = new BufferedOutputStream(fOut)) {
-            final byte[] lenBytes = new byte[4];
+            byte[] bytes = Marshaller.DEFAULT.marshall(msg);
 
-            // name len + name
-            final String fullName = msg.getDescriptorForType().getFullName();
-            final int nameLen = fullName.length();
-            Bits.putInt(lenBytes, 0, nameLen);
-            output.write(lenBytes);
-            output.write(fullName.getBytes());
-            // msg len + msg
-            final int msgLen = msg.getSerializedSize();
-            Bits.putInt(lenBytes, 0, msgLen);
+            final byte[] lenBytes = new byte[4];
+            Bits.putInt(lenBytes, 0, bytes.length);
             output.write(lenBytes);
-            msg.writeTo(output);
+            output.write(bytes);
             output.flush();
         }
         if (sync) {
@@ -123,4 +118,24 @@ public class ProtoBufFile {
 
         return Utils.atomicMoveFile(file, new File(this.path), sync);
     }
+
+    public static void main(String[] args) throws IOException {
+        File file = File.createTempFile("store", "tmp");
+
+        ProtoBufFile tmp = new ProtoBufFile(file.getAbsolutePath());
+
+        CliRequests.AddPeerRequest.Builder b = CliRequests.AddPeerRequest.newBuilder();
+        b.setGroupId("grp1");
+        b.setLeaderId("zzz");
+        b.setPeerId("tmp");
+
+        CliRequests.AddPeerRequest req0 = b.build();
+        boolean saved = tmp.save(req0, false);
+
+        CliRequests.AddPeerRequest req1 = tmp.load();
+
+        System.out.println();
+
+        file.delete();
+    }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/AbortFile.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/AbortFile.java
deleted file mode 100644
index 84f5f6c..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/AbortFile.java
+++ /dev/null
@@ -1,74 +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.storage.log;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Date;
-
-/**
- * Abort file
- *
- * @author boyan(boyan@antfin.com)
- */
-public class AbortFile {
-
-    private final String path;
-
-    public String getPath() {
-        return this.path;
-    }
-
-    public AbortFile(final String path) {
-        super();
-        this.path = path;
-    }
-
-    public boolean create() throws IOException {
-        final File file = new File(this.path);
-        if (file.createNewFile()) {
-            writeDate();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    private void writeDate() throws IOException {
-        final File file = new File(this.path);
-        try (final FileWriter writer = new FileWriter(file, false)) {
-            writer.write(new Date().toGMTString());
-            writer.write(System.lineSeparator());
-        }
-    }
-
-    public void touch() throws IOException {
-        writeDate();
-    }
-
-    public boolean exists() {
-        final File file = new File(this.path);
-        return file.isFile() && file.exists();
-    }
-
-    public boolean destroy() {
-        return new File(this.path) //
-            .delete();
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/CheckpointFile.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/CheckpointFile.java
deleted file mode 100644
index 88a03a9..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/CheckpointFile.java
+++ /dev/null
@@ -1,119 +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.storage.log;
-
-import java.io.File;
-import java.io.IOException;
-
-import org.apache.commons.io.FileUtils;
-
-import com.alipay.sofa.jraft.entity.LocalFileMetaOutter.LocalFileMeta;
-import com.alipay.sofa.jraft.storage.io.ProtoBufFile;
-import com.alipay.sofa.jraft.util.AsciiStringUtil;
-import com.alipay.sofa.jraft.util.Bits;
-import com.google.protobuf.ZeroByteStringHelper;
-
-/**
- * Segments checkpoint file.
- *
- * @author boyan(boyan@antfin.com)
- */
-public class CheckpointFile {
-    /**
-     * Checkpoint metadata info.
-     *
-     * @author boyan(boyan@antfin.com)
-     */
-    public static final class Checkpoint {
-        // Segment file name
-        public String segFilename;
-        // Segment file current commit position.
-        public int    committedPos;
-
-        public Checkpoint(final String segFilename, final int committedPos) {
-            super();
-            this.segFilename = segFilename;
-            this.committedPos = committedPos;
-        }
-
-        /**
-         * commitPos (4 bytes) + path(4 byte len + string bytes)
-         */
-        byte[] encode() {
-            byte[] ps = AsciiStringUtil.unsafeEncode(this.segFilename);
-            byte[] bs = new byte[8 + ps.length];
-            Bits.putInt(bs, 0, this.committedPos);
-            Bits.putInt(bs, 4, ps.length);
-            System.arraycopy(ps, 0, bs, 8, ps.length);
-            return bs;
-        }
-
-        boolean decode(final byte[] bs) {
-            if (bs.length < 8) {
-                return false;
-            }
-            this.committedPos = Bits.getInt(bs, 0);
-            int len = Bits.getInt(bs, 4);
-            this.segFilename = AsciiStringUtil.unsafeDecode(bs, 8, len);
-            return this.committedPos >= 0 && !this.segFilename.isEmpty();
-        }
-
-        @Override
-        public String toString() {
-            return "Checkpoint [segFilename=" + this.segFilename + ", committedPos=" + this.committedPos + "]";
-        }
-    }
-
-    public void destroy() {
-        FileUtils.deleteQuietly(new File(this.path));
-    }
-
-    public String getPath() {
-        return this.path;
-    }
-
-    private final String path;
-
-    public CheckpointFile(final String path) {
-        super();
-        this.path = path;
-    }
-
-    public synchronized boolean save(final Checkpoint checkpoint) throws IOException {
-        final ProtoBufFile file = new ProtoBufFile(this.path);
-        final byte[] data = checkpoint.encode();
-
-        final LocalFileMeta meta = LocalFileMeta.newBuilder() //
-            .setUserMeta(ZeroByteStringHelper.wrap(data)) //
-            .build();
-
-        return file.save(meta, true);
-    }
-
-    public Checkpoint load() throws IOException {
-        final ProtoBufFile file = new ProtoBufFile(this.path);
-        final LocalFileMeta meta = file.load();
-        if (meta != null) {
-            final byte[] data = meta.getUserMeta().toByteArray();
-            Checkpoint checkpoint = new Checkpoint(null, -1);
-            if (checkpoint.decode(data)) {
-                return checkpoint;
-            }
-        }
-        return null;
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/LibC.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/LibC.java
deleted file mode 100644
index e68ba32..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/LibC.java
+++ /dev/null
@@ -1,60 +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.storage.log;
-
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-import com.sun.jna.NativeLong;
-import com.sun.jna.Platform;
-import com.sun.jna.Pointer;
-
-/**
- * Moved from rocketmq.
- *
- *  https://raw.githubusercontent.com/apache/rocketmq/master/store/src/main/java/org/apache/rocketmq/store/util/LibC.java
- * @author boyan(boyan@antfin.com)
- *
- */
-public interface LibC extends Library {
-    LibC INSTANCE      = Native.load(Platform.isWindows() ? "msvcrt" : "c", LibC.class);
-
-    int  MADV_WILLNEED = 3;
-    int  MADV_DONTNEED = 4;
-
-    int  MCL_CURRENT   = 1;
-    int  MCL_FUTURE    = 2;
-    int  MCL_ONFAULT   = 4;
-
-    /* sync memory asynchronously */
-    int  MS_ASYNC      = 0x0001;
-    /* invalidate mappings & caches */
-    int  MS_INVALIDATE = 0x0002;
-    /* synchronous memory sync */
-    int  MS_SYNC       = 0x0004;
-
-    int mlock(Pointer var1, NativeLong var2);
-
-    int munlock(Pointer var1, NativeLong var2);
-
-    int madvise(Pointer var1, NativeLong var2, int var3);
-
-    Pointer memset(Pointer p, int v, long len);
-
-    int mlockall(int flags);
-
-    int msync(Pointer p, NativeLong length, int flags);
-}
\ No newline at end of file
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/RocksDBSegmentLogStorage.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/RocksDBSegmentLogStorage.java
deleted file mode 100644
index 0e1077f..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/RocksDBSegmentLogStorage.java
+++ /dev/null
@@ -1,1216 +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.storage.log;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.regex.Pattern;
-
-import org.apache.commons.io.FileUtils;
-import org.rocksdb.RocksDBException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.alipay.sofa.common.profile.StringUtil;
-import com.alipay.sofa.jraft.option.RaftOptions;
-import com.alipay.sofa.jraft.storage.impl.RocksDBLogStorage;
-import com.alipay.sofa.jraft.storage.log.CheckpointFile.Checkpoint;
-import com.alipay.sofa.jraft.storage.log.SegmentFile.SegmentFileOptions;
-import com.alipay.sofa.jraft.util.ArrayDeque;
-import com.alipay.sofa.jraft.util.Bits;
-import com.alipay.sofa.jraft.util.CountDownEvent;
-import com.alipay.sofa.jraft.util.NamedThreadFactory;
-import com.alipay.sofa.jraft.util.Platform;
-import com.alipay.sofa.jraft.util.Requires;
-import com.alipay.sofa.jraft.util.SystemPropertyUtil;
-import com.alipay.sofa.jraft.util.ThreadPoolUtil;
-import com.alipay.sofa.jraft.util.Utils;
-
-/**
- * Log Storage implementation based on rocksdb and segment files.
- *
- * @author boyan(boyan@antfin.com)
- */
-public class RocksDBSegmentLogStorage extends RocksDBLogStorage {
-
-    private static final int PRE_ALLOCATE_SEGMENT_COUNT = 2;
-    private static final int MEM_SEGMENT_COUNT          = 3;
-
-    private static class AllocatedResult {
-        SegmentFile segmentFile;
-        IOException ie;
-
-        public AllocatedResult(final SegmentFile segmentFile) {
-            super();
-            this.segmentFile = segmentFile;
-        }
-
-        public AllocatedResult(final IOException ie) {
-            super();
-            this.ie = ie;
-        }
-
-    }
-
-    public static class BarrierWriteContext implements WriteContext {
-        private final CountDownEvent    events = new CountDownEvent();
-        private volatile Exception      e;
-        private volatile List<Runnable> hooks;
-
-        @Override
-        public void startJob() {
-            this.events.incrementAndGet();
-        }
-
-        @Override
-        public synchronized void addFinishHook(final Runnable r) {
-            if (this.hooks == null) {
-                this.hooks = new CopyOnWriteArrayList<>();
-            }
-            this.hooks.add(r);
-        }
-
-        @Override
-        public void finishJob() {
-            this.events.countDown();
-        }
-
-        @Override
-        public void setError(final Exception e) {
-            this.e = e;
-        }
-
-        @Override
-        public void joinAll() throws InterruptedException, IOException {
-            this.events.await();
-            if (this.hooks != null) {
-                for (Runnable r : this.hooks) {
-                    r.run();
-                }
-            }
-            if (this.e != null) {
-                throw new IOException("Fail to apppend entries", this.e);
-            }
-        }
-
-    }
-
-    private static final String SEGMENT_FILE_POSFIX            = ".s";
-
-    private static final Logger LOG                            = LoggerFactory
-                                                                   .getLogger(RocksDBSegmentLogStorage.class);
-
-    /**
-     * Default checkpoint interval in milliseconds.
-     */
-    private static final int    DEFAULT_CHECKPOINT_INTERVAL_MS = SystemPropertyUtil.getInt(
-                                                                   "jraft.log_storage.segment.checkpoint.interval.ms",
-                                                                   5000);
-
-    /**
-     * Location metadata format:
-     * 1. magic bytes
-     * 2. reserved(2 B)
-     * 3. segmentFileName(8 B)
-     * 4. wrotePosition(4 B)
-     */
-    private static final int    LOCATION_METADATA_SIZE         = SegmentFile.RECORD_MAGIC_BYTES_SIZE + 2 + 8 + 4;
-
-    /**
-     * Max segment file size, 1G
-     */
-    private static final int    MAX_SEGMENT_FILE_SIZE          = SystemPropertyUtil.getInt(
-                                                                   "jraft.log_storage.segment.max.size.bytes",
-                                                                   1024 * 1024 * 1024);
-
-    // Default value size threshold to decide whether it will be stored in segments or rocksdb, default is 4K.
-    // When the value size is less than 4K, it will be stored in rocksdb directly.
-    private static int          DEFAULT_VALUE_SIZE_THRESHOLD   = SystemPropertyUtil.getInt(
-                                                                   "jraft.log_storage.segment.value.threshold.bytes",
-                                                                   4 * 1024);
-
-    /**
-     * RocksDBSegmentLogStorage builder
-     * @author boyan(boyan@antfin.com)
-     *
-     */
-    public static class Builder {
-        private String             path;
-        private RaftOptions        raftOptions;
-        private int                valueSizeThreshold       = DEFAULT_VALUE_SIZE_THRESHOLD;
-        private int                maxSegmentFileSize       = MAX_SEGMENT_FILE_SIZE;
-        private ThreadPoolExecutor writeExecutor;
-        private int                preAllocateSegmentCount  = PRE_ALLOCATE_SEGMENT_COUNT;
-        private int                keepInMemorySegmentCount = MEM_SEGMENT_COUNT;
-        private int                checkpointIntervalMs     = DEFAULT_CHECKPOINT_INTERVAL_MS;
-
-        public String getPath() {
-            return this.path;
-        }
-
-        public Builder setPath(final String path) {
-            this.path = path;
-            return this;
-        }
-
-        public RaftOptions getRaftOptions() {
-            return this.raftOptions;
-        }
-
-        public Builder setRaftOptions(final RaftOptions raftOptions) {
-            this.raftOptions = raftOptions;
-            return this;
-        }
-
-        public int getValueSizeThreshold() {
-            return this.valueSizeThreshold;
-        }
-
-        public Builder setValueSizeThreshold(final int valueSizeThreshold) {
-            this.valueSizeThreshold = valueSizeThreshold;
-            return this;
-        }
-
-        public int getMaxSegmentFileSize() {
-            return this.maxSegmentFileSize;
-        }
-
-        public Builder setMaxSegmentFileSize(final int maxSegmentFileSize) {
-            this.maxSegmentFileSize = maxSegmentFileSize;
-            return this;
-        }
-
-        public ThreadPoolExecutor getWriteExecutor() {
-            return this.writeExecutor;
-        }
-
-        public Builder setWriteExecutor(final ThreadPoolExecutor writeExecutor) {
-            this.writeExecutor = writeExecutor;
-            return this;
-        }
-
-        public int getPreAllocateSegmentCount() {
-            return this.preAllocateSegmentCount;
-        }
-
-        public Builder setPreAllocateSegmentCount(final int preAllocateSegmentCount) {
-            this.preAllocateSegmentCount = preAllocateSegmentCount;
-            return this;
-        }
-
-        public int getKeepInMemorySegmentCount() {
-            return this.keepInMemorySegmentCount;
-        }
-
-        public Builder setKeepInMemorySegmentCount(final int keepInMemorySegmentCount) {
-            this.keepInMemorySegmentCount = keepInMemorySegmentCount;
-            return this;
-        }
-
-        public int getCheckpointIntervalMs() {
-            return this.checkpointIntervalMs;
-        }
-
-        public Builder setCheckpointIntervalMs(final int checkpointIntervalMs) {
-            this.checkpointIntervalMs = checkpointIntervalMs;
-            return this;
-        }
-
-        public RocksDBSegmentLogStorage build() {
-            return new RocksDBSegmentLogStorage(this.path, this.raftOptions, this.valueSizeThreshold,
-                this.maxSegmentFileSize, this.preAllocateSegmentCount, this.keepInMemorySegmentCount,
-                this.checkpointIntervalMs, this.writeExecutor);
-        }
-
-    }
-
-    private final int                   valueSizeThreshold;
-    private final String                segmentsPath;
-    private final CheckpointFile        checkpointFile;
-    // used  or using segments.
-    private List<SegmentFile>           segments;
-    // pre-allocated and blank segments.
-    private ArrayDeque<AllocatedResult> blankSegments;
-    private final Lock                  allocateLock             = new ReentrantLock();
-    private final Condition             fullCond                 = this.allocateLock.newCondition();
-    private final Condition             emptyCond                = this.allocateLock.newCondition();
-    // segment file sequence.
-    private final AtomicLong            nextFileSequence         = new AtomicLong(0);
-    private final ReadWriteLock         readWriteLock            = new ReentrantReadWriteLock();
-    private final Lock                  writeLock                = this.readWriteLock.writeLock();
-    private final Lock                  readLock                 = this.readWriteLock.readLock();
-    private ScheduledExecutorService    checkpointExecutor;
-    private final AbortFile             abortFile;
-    private final ThreadPoolExecutor    writeExecutor;
-    private Thread                      segmentAllocator;
-    private final int                   maxSegmentFileSize;
-    private int                         preAllocateSegmentCount  = PRE_ALLOCATE_SEGMENT_COUNT;
-    private int                         keepInMemorySegmentCount = MEM_SEGMENT_COUNT;
-    private int                         checkpointIntervalMs     = DEFAULT_CHECKPOINT_INTERVAL_MS;
-
-    /**
-     * Creates a RocksDBSegmentLogStorage builder.
-     * @return a builder instance.
-     */
-    public static final Builder builder(final String uri, final RaftOptions raftOptions) {
-        return new Builder().setPath(uri).setRaftOptions(raftOptions);
-    }
-
-    public RocksDBSegmentLogStorage(final String path, final RaftOptions raftOptions) {
-        this(path, raftOptions, DEFAULT_VALUE_SIZE_THRESHOLD, MAX_SEGMENT_FILE_SIZE);
-    }
-
-    public RocksDBSegmentLogStorage(final String path, final RaftOptions raftOptions, final int valueSizeThreshold,
-                                    final int maxSegmentFileSize) {
-        this(path, raftOptions, valueSizeThreshold, maxSegmentFileSize, PRE_ALLOCATE_SEGMENT_COUNT, MEM_SEGMENT_COUNT,
-            DEFAULT_CHECKPOINT_INTERVAL_MS, createDefaultWriteExecutor());
-    }
-
-    private static ThreadPoolExecutor createDefaultWriteExecutor() {
-        return ThreadPoolUtil.newThreadPool("RocksDBSegmentLogStorage-write-pool", true, Utils.cpus(),
-            Utils.cpus() * 3, 60, new ArrayBlockingQueue<>(10000), new NamedThreadFactory(
-                "RocksDBSegmentLogStorageWriter"), new ThreadPoolExecutor.CallerRunsPolicy());
-    }
-
-    public RocksDBSegmentLogStorage(final String path, final RaftOptions raftOptions, final int valueSizeThreshold,
-                                    final int maxSegmentFileSize, final int preAllocateSegmentCount,
-                                    final int keepInMemorySegmentCount, final int checkpointIntervalMs,
-                                    final ThreadPoolExecutor writeExecutor) {
-        super(path, raftOptions);
-        if (Platform.isMac()) {
-            LOG.warn("RocksDBSegmentLogStorage is not recommended on mac os x, it's performance is poorer than RocksDBLogStorage.");
-        }
-        Requires.requireTrue(maxSegmentFileSize > 0, "maxSegmentFileSize is not greater than zero");
-        Requires.requireTrue(preAllocateSegmentCount > 0, "preAllocateSegmentCount is not greater than zero");
-        Requires.requireTrue(checkpointIntervalMs > 0, "checkpointIntervalMs is not greater than zero");
-        Requires.requireTrue(keepInMemorySegmentCount > 0, "keepInMemorySegmentCount is not greater than zero");
-        this.segmentsPath = path + File.separator + "segments";
-        this.abortFile = new AbortFile(this.segmentsPath + File.separator + "abort");
-        this.checkpointFile = new CheckpointFile(this.segmentsPath + File.separator + "checkpoint");
-        this.valueSizeThreshold = valueSizeThreshold;
-        this.maxSegmentFileSize = maxSegmentFileSize;
-        this.writeExecutor = writeExecutor == null ? createDefaultWriteExecutor() : writeExecutor;
-        this.preAllocateSegmentCount = preAllocateSegmentCount;
-        this.checkpointIntervalMs = checkpointIntervalMs;
-        this.keepInMemorySegmentCount = keepInMemorySegmentCount;
-
-    }
-
-    private SegmentFile getLastSegmentFile(final long logIndex, final int waitToWroteSize,
-                                           final boolean createIfNecessary, final WriteContext ctx) throws IOException,
-                                                                                                   InterruptedException {
-        SegmentFile lastFile = null;
-        while (true) {
-            int segmentCount = 0;
-            this.readLock.lock();
-            try {
-
-                if (!this.segments.isEmpty()) {
-                    segmentCount = this.segments.size();
-                    final SegmentFile currLastFile = getLastSegmentWithoutLock();
-                    if (waitToWroteSize <= 0 || !currLastFile.reachesFileEndBy(waitToWroteSize)) {
-                        lastFile = currLastFile;
-                    }
-                }
-            } finally {
-                this.readLock.unlock();
-            }
-            if (lastFile == null && createIfNecessary) {
-                lastFile = createNewSegmentFile(logIndex, segmentCount, ctx);
-                if (lastFile != null) {
-                    return lastFile;
-                } else {
-                    // Try again
-                    continue;
-                }
-            }
-            return lastFile;
-        }
-    }
-
-    private SegmentFile createNewSegmentFile(final long logIndex, final int oldSegmentCount,
-                                             final WriteContext ctx) throws InterruptedException, IOException {
-        SegmentFile segmentFile = null;
-        this.writeLock.lock();
-        try {
-            // CAS by segments count.
-            if (this.segments.size() != oldSegmentCount) {
-                return segmentFile;
-            }
-            if (!this.segments.isEmpty()) {
-                // Sync current last file and correct it's lastLogIndex.
-                final SegmentFile currLastFile = getLastSegmentWithoutLock();
-                currLastFile.setLastLogIndex(logIndex - 1);
-                ctx.startJob();
-                // Attach a finish hook to set last segment file to be read-only.
-                ctx.addFinishHook(() -> currLastFile.setReadOnly(true));
-                // Run sync in parallel
-                this.writeExecutor.execute(() -> {
-                    try {
-                        currLastFile.sync(isSync());
-                    } catch (final IOException e) {
-                        ctx.setError(e);
-                    } finally {
-                        ctx.finishJob();
-                    }
-
-                });
-
-            }
-            segmentFile = allocateSegmentFile(logIndex);
-            return segmentFile;
-        } finally {
-            this.writeLock.unlock();
-        }
-
-    }
-
-    private SegmentFile allocateSegmentFile(final long index) throws InterruptedException, IOException {
-        this.allocateLock.lock();
-        try {
-            while (this.blankSegments.isEmpty()) {
-                this.emptyCond.await();
-            }
-            final AllocatedResult result = this.blankSegments.pollFirst();
-            if (result.ie != null) {
-                throw result.ie;
-            }
-            this.fullCond.signal();
-            result.segmentFile.setFirstLogIndex(index);
-            this.segments.add(result.segmentFile);
-            return result.segmentFile;
-        } finally {
-            this.allocateLock.unlock();
-        }
-    }
-
-    private SegmentFile allocateNewSegmentFile() throws IOException {
-        final String newSegPath = getNewSegmentFilePath();
-        SegmentFile segmentFile = new SegmentFile(this.maxSegmentFileSize, newSegPath, this.writeExecutor);
-        final SegmentFileOptions opts = SegmentFileOptions.builder() //
-            .setSync(false) //
-            .setRecover(false) //
-            .setLastFile(true) //
-            .setNewFile(true) //
-            .setPos(0).build();
-
-        try {
-            if (!segmentFile.init(opts)) {
-                throw new IOException("Fail to create new segment file");
-            }
-            segmentFile.hintLoad();
-            LOG.info("Create a new segment file {}.", segmentFile.getPath());
-            return segmentFile;
-        } catch (IOException e) {
-            // Delete the file if fails
-            FileUtils.deleteQuietly(new File(newSegPath));
-            throw e;
-        }
-    }
-
-    private String getNewSegmentFilePath() {
-        return this.segmentsPath + File.separator + String.format("%019d", this.nextFileSequence.getAndIncrement())
-               + SEGMENT_FILE_POSFIX;
-    }
-
-    @Override
-    protected void onSync() throws IOException, InterruptedException {
-        final SegmentFile lastSegmentFile = getLastSegmentFileForRead();
-        if (lastSegmentFile != null) {
-            lastSegmentFile.sync(isSync());
-        }
-    }
-
-    private static final Pattern SEGMENT_FILE_NAME_PATTERN = Pattern.compile("[0-9]+\\.s");
-
-    @Override
-    protected boolean onInitLoaded() {
-        final long startMs = Utils.monotonicMs();
-        this.writeLock.lock();
-        try {
-            final File segmentsDir = new File(this.segmentsPath);
-            if (!ensureDir(segmentsDir)) {
-                return false;
-            }
-            final Checkpoint checkpoint = loadCheckpoint();
-
-            final File[] segmentFiles = segmentsDir
-                    .listFiles((final File dir, final String name) -> SEGMENT_FILE_NAME_PATTERN.matcher(name).matches());
-
-            final boolean normalExit = !this.abortFile.exists();
-            if (!normalExit) {
-                LOG.info("{} {} did not exit normally, will try to recover last file.", getServiceName(),
-                    this.segmentsPath);
-            }
-            this.segments = new ArrayList<>(segmentFiles == null ? 10 : segmentFiles.length);
-            this.blankSegments = new ArrayDeque<>();
-            List<File> corruptedHeaderSegments = new ArrayList<>();
-
-            if (segmentFiles != null && segmentFiles.length > 0) {
-                // Sort by sequences.
-                Arrays.sort(segmentFiles, Comparator.comparing(RocksDBSegmentLogStorage::getFileSequenceFromFileName));
-
-                final String checkpointSegFile = getCheckpointSegFilePath(checkpoint);
-
-                // mmap files
-                for (int i = 0; i < segmentFiles.length; i++) {
-                    final File segFile = segmentFiles[i];
-                    this.nextFileSequence.set(getFileSequenceFromFileName(segFile) + 1);
-                    final SegmentFile segmentFile = new SegmentFile(this.maxSegmentFileSize, segFile.getAbsolutePath(),
-                        this.writeExecutor);
-
-                    if (!segmentFile.mmapFile(false)) {
-                      assert (segmentFile.isHeaderCorrupted());
-                      corruptedHeaderSegments.add(segFile);
-                      continue;
-                    }
-
-                    if (segmentFile.isBlank()) {
-                      this.blankSegments.add(new AllocatedResult(segmentFile));
-                    } else if (segmentFile.isHeaderCorrupted()) {
-                      corruptedHeaderSegments.add(segFile);
-                    } else {
-                      this.segments.add(segmentFile);
-                    }
-                }
-
-                // Processing corrupted header files
-                //TODO(boyan) maybe we can find a better solution for such case that new allocated segment file is corrupted when power failure etc.
-                if(!processCorruptedHeaderFiles(corruptedHeaderSegments)) {
-                  return false;
-                }
-
-                // init blank segments
-                if(!initBlankFiles()) {
-                  return false;
-                }
-
-                // try to recover segments
-                if(!recoverFiles(checkpoint, normalExit, checkpointSegFile)) {
-                  return false;
-                }
-            } else {
-                if (checkpoint != null) {
-                    LOG.warn("Missing segment files, checkpoint is: {}", checkpoint);
-                    return false;
-                }
-            }
-
-            LOG.info("{} Loaded {} segment files and  {} blank segment files from path {}.", getServiceName(),
-                this.segments.size(), this.blankSegments.size(), this.segmentsPath);
-
-            LOG.info("{} segments: \n{}", getServiceName(), descSegments());
-
-            startCheckpointTask();
-
-            if (normalExit) {
-                if (!this.abortFile.create()) {
-                    LOG.error("Fail to create abort file {}.", this.abortFile.getPath());
-                    return false;
-                }
-            } else {
-                this.abortFile.touch();
-            }
-            startSegmentAllocator();
-
-            return true;
-        } catch (final Exception e) {
-            LOG.error("Fail to load segment files from directory {}.", this.segmentsPath, e);
-            return false;
-        } finally {
-            this.writeLock.unlock();
-            LOG.info("{} init and load cost {} ms.", getServiceName(), Utils.monotonicMs() - startMs);
-        }
-    }
-
-    private boolean recoverFiles(final Checkpoint checkpoint, final boolean normalExit, final String checkpointSegFile) {
-        boolean needRecover = false;
-        SegmentFile prevFile = null;
-        for (int i = 0; i < this.segments.size(); i++) {
-            final boolean isLastFile = i == this.segments.size() - 1;
-            SegmentFile segmentFile = this.segments.get(i);
-            int pos = segmentFile.getSize();
-            if (StringUtil.equalsIgnoreCase(checkpointSegFile, segmentFile.getFilename())) {
-                needRecover = true;
-                assert (checkpoint != null);
-                pos = checkpoint.committedPos;
-            } else {
-                if (needRecover) {
-                    pos = 0;
-                }
-            }
-
-            final SegmentFileOptions opts = SegmentFileOptions.builder() //
-                .setSync(isSync()) //
-                .setRecover(needRecover && !normalExit) //
-                .setLastFile(isLastFile) //
-                .setNewFile(false) //
-                .setPos(pos).build();
-
-            if (!segmentFile.init(opts)) {
-                LOG.error("Fail to load segment file {}.", segmentFile.getPath());
-                segmentFile.shutdown();
-                return false;
-            }
-            /**
-             * It's wrote position is from start(HEADER_SIZE) but it's not the last file, SHOULD not happen.
-             */
-            if (segmentFile.getWrotePos() == SegmentFile.HEADER_SIZE && !isLastFile) {
-                LOG.error("Detected corrupted segment file {}.", segmentFile.getPath());
-                return false;
-            }
-
-            if (prevFile != null) {
-                prevFile.setLastLogIndex(segmentFile.getFirstLogIndex() - 1);
-            }
-            prevFile = segmentFile;
-        }
-        if (getLastLogIndex() > 0 && prevFile != null) {
-            prevFile.setLastLogIndex(getLastLogIndex());
-        }
-        return true;
-    }
-
-    private boolean initBlankFiles() {
-        for (AllocatedResult ret : this.blankSegments) {
-            final SegmentFile segmentFile = ret.segmentFile;
-            final SegmentFileOptions opts = SegmentFileOptions.builder() //
-                .setSync(false) //
-                .setRecover(false) //
-                .setLastFile(true) //
-                .build();
-
-            if (!segmentFile.init(opts)) {
-                LOG.error("Fail to load blank segment file {}.", segmentFile.getPath());
-                segmentFile.shutdown();
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private boolean processCorruptedHeaderFiles(final List<File> corruptedHeaderSegments) throws IOException {
-        if (corruptedHeaderSegments.size() == 1) {
-            final File corruptedFile = corruptedHeaderSegments.get(0);
-            if (getFileSequenceFromFileName(corruptedFile) != this.nextFileSequence.get() - 1) {
-                LOG.error("Detected corrupted header segment file {}.", corruptedFile);
-                return false;
-            } else {
-                // The file is the last file,it's the new blank segment but fail to save header, we can
-                // remove it safely.
-                LOG.warn("Truncate the last segment file {} which it's header is corrupted.",
-                    corruptedFile.getAbsolutePath());
-                // We don't want to delete it, but rename it for safety.
-                FileUtils.moveFile(corruptedFile, new File(corruptedFile.getAbsolutePath() + ".corrupted"));
-            }
-        } else if (corruptedHeaderSegments.size() > 1) {
-            // FATAL: it should not happen.
-            LOG.error("Detected corrupted header segment files: {}.", corruptedHeaderSegments);
-            return false;
-        }
-
-        return true;
-    }
-
-    private void startSegmentAllocator() throws IOException {
-        // Warmup
-        if (this.blankSegments.isEmpty()) {
-            doAllocateSegment0();
-        }
-        // Start the thread.
-        this.segmentAllocator = new Thread() {
-            @Override
-            public void run() {
-                doAllocateSegment();
-            }
-
-        };
-        this.segmentAllocator.setDaemon(true);
-        this.segmentAllocator.setName("SegmentAllocator");
-        this.segmentAllocator.start();
-    }
-
-    private void doAllocateSegment() {
-        LOG.info("SegmentAllocator is started.");
-        while (!Thread.currentThread().isInterrupted()) {
-            doAllocateSegmentInLock();
-            doSwappOutSegments();
-        }
-        LOG.info("SegmentAllocator exit.");
-    }
-
-    private void doAllocateSegmentInLock() {
-        this.allocateLock.lock();
-        try {
-            //TODO configure cap
-            while (this.blankSegments.size() >= this.preAllocateSegmentCount) {
-                this.fullCond.await();
-            }
-            doAllocateSegment0();
-            this.emptyCond.signal();
-        } catch (final InterruptedException e) {
-            Thread.currentThread().interrupt();
-        } catch (final IOException e) {
-            this.blankSegments.add(new AllocatedResult(e));
-            this.emptyCond.signal();
-        } finally {
-            this.allocateLock.unlock();
-        }
-    }
-
-    private void doSwappOutSegments() {
-        this.readLock.lock();
-        try {
-            if (this.segments.size() <= this.keepInMemorySegmentCount) {
-                return;
-            }
-            int segmentsInMemeCount = 0;
-            int swappedOutCount = 0;
-            final long beginTime = Utils.monotonicMs();
-            final int lastIndex = this.segments.size() - 1;
-            for (int i = lastIndex; i >= 0; i--) {
-                SegmentFile segFile = this.segments.get(i);
-                if (!segFile.isSwappedOut()) {
-                    segmentsInMemeCount++;
-                    if (segmentsInMemeCount >= this.keepInMemorySegmentCount && i != lastIndex) {
-                        segFile.hintUnload();
-                        segFile.swapOut();
-                        swappedOutCount++;
-                    }
-                }
-            }
-            LOG.info("Swapped out {} segment files, cost {} ms.", swappedOutCount, Utils.monotonicMs() - beginTime);
-        } catch (final Exception e) {
-            LOG.error("Fail to swap out segments.", e);
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-
-    private void doAllocateSegment0() throws IOException {
-        SegmentFile segFile = allocateNewSegmentFile();
-        this.blankSegments.add(new AllocatedResult(segFile));
-    }
-
-    private static long getFileSequenceFromFileName(final File file) {
-        final String name = file.getName();
-        assert (name.endsWith(SEGMENT_FILE_POSFIX));
-        int idx = name.indexOf(SEGMENT_FILE_POSFIX);
-        return Long.valueOf(name.substring(0, idx));
-    }
-
-    private Checkpoint loadCheckpoint() {
-        final Checkpoint checkpoint;
-        try {
-            checkpoint = this.checkpointFile.load();
-            if (checkpoint != null) {
-                LOG.info("Loaded checkpoint: {} from {}.", checkpoint, this.checkpointFile.getPath());
-            }
-        } catch (final IOException e) {
-            LOG.error("Fail to load checkpoint file: {}", this.checkpointFile.getPath(), e);
-            return null;
-        }
-        return checkpoint;
-    }
-
-    private boolean ensureDir(final File segmentsDir) {
-        try {
-            FileUtils.forceMkdir(segmentsDir);
-            return true;
-        } catch (final IOException e) {
-            LOG.error("Fail to create segments directory: {}", this.segmentsPath, e);
-            return false;
-        }
-    }
-
-    private String getCheckpointSegFilePath(final Checkpoint checkpoint) {
-        return checkpoint != null ? checkpoint.segFilename : null;
-    }
-
-    private void startCheckpointTask() {
-        this.checkpointExecutor = Executors
-                .newSingleThreadScheduledExecutor(new NamedThreadFactory(getServiceName() + "-Checkpoint-Thread-", true));
-        this.checkpointExecutor.scheduleAtFixedRate(this::doCheckpoint, this.checkpointIntervalMs,
-            this.checkpointIntervalMs, TimeUnit.MILLISECONDS);
-        LOG.info("{} started checkpoint task.", getServiceName());
-    }
-
-    private StringBuilder descSegments() {
-        final StringBuilder segmentsDesc = new StringBuilder("[\n");
-        for (final SegmentFile segFile : this.segments) {
-            segmentsDesc.append("  ").append(segFile.toString()).append("\n");
-        }
-        segmentsDesc.append("]");
-        return segmentsDesc;
-    }
-
-    private String getServiceName() {
-        return this.getClass().getSimpleName();
-    }
-
-    private void stopSegmentAllocator() {
-        this.segmentAllocator.interrupt();
-        try {
-            this.segmentAllocator.join(500);
-        } catch (final InterruptedException e) {
-            Thread.currentThread().interrupt();
-        }
-    }
-
-    @Override
-    protected void onShutdown() {
-        stopCheckpointTask();
-        stopSegmentAllocator();
-
-        List<SegmentFile> shutdownFiles = Collections.emptyList();
-        this.writeLock.lock();
-        try {
-            doCheckpoint();
-            shutdownFiles = new ArrayList<>(this.segments);
-            this.segments.clear();
-            if (!this.abortFile.destroy()) {
-                LOG.error("Fail to delete abort file {}.", this.abortFile.getPath());
-            }
-        } finally {
-            this.writeLock.unlock();
-            for (final SegmentFile segmentFile : shutdownFiles) {
-                segmentFile.shutdown();
-            }
-            shutdownBlankSegments();
-        }
-        this.writeExecutor.shutdown();
-    }
-
-    private void shutdownBlankSegments() {
-        this.allocateLock.lock();
-        try {
-            for (final AllocatedResult ret : this.blankSegments) {
-                if (ret.segmentFile != null) {
-                    ret.segmentFile.shutdown();
-                }
-            }
-        } finally {
-            this.allocateLock.unlock();
-        }
-    }
-
-    private void stopCheckpointTask() {
-        if (this.checkpointExecutor != null) {
-            this.checkpointExecutor.shutdownNow();
-            try {
-                this.checkpointExecutor.awaitTermination(10, TimeUnit.SECONDS);
-            } catch (final InterruptedException e) {
-                Thread.currentThread().interrupt();
-            }
-            LOG.info("{} stopped checkpoint task.", getServiceName());
-        }
-    }
-
-    private void doCheckpoint() {
-        SegmentFile lastSegmentFile = null;
-        try {
-            lastSegmentFile = getLastSegmentFileForRead();
-            if (lastSegmentFile != null) {
-                this.checkpointFile.save(new Checkpoint(lastSegmentFile.getFilename(), lastSegmentFile
-                    .getCommittedPos()));
-            }
-        } catch (final InterruptedException e) {
-            Thread.currentThread().interrupt();
-        } catch (final IOException e) {
-            LOG.error("Fatal error, fail to do checkpoint, last segment file is {}.",
-                lastSegmentFile != null ? lastSegmentFile.getPath() : "null", e);
-        }
-    }
-
-    public SegmentFile getLastSegmentFileForRead() throws IOException, InterruptedException {
-        return getLastSegmentFile(-1, 0, false, null);
-    }
-
-    @Override
-    protected void onReset(final long nextLogIndex) {
-        List<SegmentFile> destroyedFiles = new ArrayList<>();
-        this.writeLock.lock();
-        try {
-            this.checkpointFile.destroy();
-            destroyedFiles.addAll(this.segments);
-            this.segments.clear();
-            LOG.info("Destroyed segments and checkpoint in path {} by resetting.", this.segmentsPath);
-        } finally {
-            this.writeLock.unlock();
-            for (SegmentFile segFile : destroyedFiles) {
-                segFile.destroy();
-            }
-        }
-    }
-
-    private SegmentFile getLastSegmentWithoutLock() {
-        return this.segments.get(this.segments.size() - 1);
-    }
-
-    @Override
-    protected void onTruncatePrefix(final long startIndex, final long firstIndexKept) throws RocksDBException,
-                                                                                     IOException {
-        List<SegmentFile> destroyedFiles = null;
-        this.writeLock.lock();
-        try {
-            int fromIndex = binarySearchFileIndexByLogIndex(startIndex);
-            int toIndex = binarySearchFileIndexByLogIndex(firstIndexKept);
-
-            if (fromIndex < 0) {
-                fromIndex = 0;
-            }
-            if (toIndex < 0) {
-                // When all the segments contain logs that index is smaller than firstIndexKept,
-                // truncate all segments.
-                do {
-                    if (!this.segments.isEmpty()) {
-                        if (getLastSegmentWithoutLock().getLastLogIndex() < firstIndexKept) {
-                            toIndex = this.segments.size();
-                            break;
-                        }
-                    }
-                    LOG.warn("Segment file not found by logIndex={} to be truncate_prefix, current segments:\n{}.",
-                        firstIndexKept, descSegments());
-                    return;
-                } while (false);
-            }
-
-            final List<SegmentFile> removedFiles = this.segments.subList(fromIndex, toIndex);
-            destroyedFiles = new ArrayList<>(removedFiles);
-            removedFiles.clear();
-            doCheckpoint();
-        } finally {
-            this.writeLock.unlock();
-            if (destroyedFiles != null) {
-                for (final SegmentFile segmentFile : destroyedFiles) {
-                    segmentFile.destroy();
-                }
-            }
-        }
-    }
-
-    private boolean isMetadata(final byte[] data) {
-        for (int offset = 0; offset < SegmentFile.RECORD_MAGIC_BYTES_SIZE; offset++) {
-            if (data[offset] != SegmentFile.RECORD_MAGIC_BYTES[offset]) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private SegmentFile getFirstSegmentWithoutLock() {
-        return this.segments.get(0);
-    }
-
-    @Override
-    protected void onTruncateSuffix(final long lastIndexKept) throws RocksDBException, IOException {
-        List<SegmentFile> destroyedFiles = null;
-        this.writeLock.lock();
-        try {
-            final int keptFileIndex = binarySearchFileIndexByLogIndex(lastIndexKept);
-            int toIndex = binarySearchFileIndexByLogIndex(getLastLogIndex());
-
-            if (keptFileIndex < 0) {
-                // When all the segments contain logs that index is greater than lastIndexKept,
-                // truncate all segments.
-                if (!this.segments.isEmpty()) {
-                    final long firstLogIndex = getFirstSegmentWithoutLock().getFirstLogIndex();
-                    if (firstLogIndex > lastIndexKept) {
-                        final List<SegmentFile> removedFiles = this.segments.subList(0, this.segments.size());
-                        destroyedFiles = new ArrayList<>(removedFiles);
-                        removedFiles.clear();
-                    }
-                    LOG.info(
-                        "Truncating all segments in {} because the first log index {} is greater than lastIndexKept={}",
-                        this.segmentsPath, firstLogIndex, lastIndexKept);
-                }
-
-                LOG.warn("Segment file not found by logIndex={} to be truncate_suffix, current segments:\n{}.",
-                    lastIndexKept, descSegments());
-                return;
-            }
-
-            if (toIndex < 0) {
-                toIndex = this.segments.size() - 1;
-            }
-
-            // Destroyed files after keptFile
-            final List<SegmentFile> removedFiles = this.segments.subList(keptFileIndex + 1, toIndex + 1);
-            destroyedFiles = new ArrayList<>(removedFiles);
-            removedFiles.clear();
-
-            // Process logs in keptFile(firstLogIndex=lastIndexKept)
-            final SegmentFile keptFile = this.segments.get(keptFileIndex);
-            if (keptFile.isBlank()) {
-                return;
-            }
-            int logWrotePos = -1; // The truncate position in keptFile.
-
-            // Try to find the right position to be truncated.
-            {
-                // First, find in right [lastIndexKept + 1, getLastLogIndex()]
-                long nextIndex = lastIndexKept + 1;
-                final long endIndex = Math.min(getLastLogIndex(), keptFile.getLastLogIndex());
-                while (nextIndex <= endIndex) {
-                    final byte[] data = getValueFromRocksDB(getKeyBytes(nextIndex));
-                    if (data != null) {
-                        if (data.length == LOCATION_METADATA_SIZE) {
-                            if (!isMetadata(data)) {
-                                // Stored in rocksdb directly.
-                                nextIndex++;
-                                continue;
-                            }
-                            logWrotePos = getWrotePosition(data);
-                            break;
-                        } else {
-                            // Stored in rocksdb directly.
-                            nextIndex++;
-                        }
-                    } else {
-                        // No more data.
-                        break;
-                    }
-                }
-            }
-
-            // Not found in [lastIndexKept + 1, getLastLogIndex()]
-            if (logWrotePos < 0) {
-                // Second, try to find in left  [firstLogIndex, lastIndexKept) when lastIndexKept is not stored in segments.
-                final byte[] keptData = getValueFromRocksDB(getKeyBytes(lastIndexKept));
-                // The kept log's data is not stored in segments.
-                if (!isMetadata(keptData)) {
-                    //lastIndexKept's log is stored in rocksdb directly, try to find the first previous log that stored in segment.
-                    long prevIndex = lastIndexKept - 1;
-                    final long startIndex = keptFile.getFirstLogIndex();
-                    while (prevIndex >= startIndex) {
-                        final byte[] data = getValueFromRocksDB(getKeyBytes(prevIndex));
-                        if (data != null) {
-                            if (data.length == LOCATION_METADATA_SIZE) {
-                                if (!isMetadata(data)) {
-                                    // Stored in rocksdb directly.
-                                    prevIndex--;
-                                    continue;
-                                }
-                                // Found the position.
-                                logWrotePos = getWrotePosition(data);
-                                final byte[] logData = onDataGet(prevIndex, data);
-                                // Skip this log, it should be kept(logs that are less than lastIndexKept should be kept).
-                                // Fine the next log position.
-                                logWrotePos += SegmentFile.getWriteBytes(logData);
-                                break;
-                            } else {
-                                // Stored in rocksdb directly.
-                                prevIndex--;
-                            }
-                        } else {
-                            LOG.warn(
-                                "Log entry not found at index={} when truncating logs suffix from lastIndexKept={}.",
-                                prevIndex, lastIndexKept);
-                            prevIndex--;
-                        }
-                    }
-                }
-            }
-
-            if (logWrotePos >= 0 && logWrotePos < keptFile.getSize()) {
-                // Truncate the file from wrotePos and set it's lastLogIndex=lastIndexKept.
-                keptFile.truncateSuffix(logWrotePos, lastIndexKept, isSync());
-            }
-            // Finally, do checkpoint.
-            doCheckpoint();
-
-        } finally {
-            this.writeLock.unlock();
-            if (destroyedFiles != null) {
-                for (final SegmentFile segmentFile : destroyedFiles) {
-                    segmentFile.destroy();
-                }
-            }
-        }
-    }
-
-    /**
-     * Retrieve the log wrote position from metadata.
-     *
-     * @param data the metadata
-     * @return the log wrote position
-     */
-    private int getWrotePosition(final byte[] data) {
-        return Bits.getInt(data, SegmentFile.RECORD_MAGIC_BYTES_SIZE + 2 + 8);
-    }
-
-    @Override
-    protected WriteContext newWriteContext() {
-        return new BarrierWriteContext();
-    }
-
-    @Override
-    protected byte[] onDataAppend(final long logIndex, final byte[] value, final WriteContext ctx) throws IOException,
-                                                                                                  InterruptedException {
-        final int waitToWroteBytes = SegmentFile.getWriteBytes(value);
-        SegmentFile lastSegmentFile = getLastSegmentFile(logIndex, waitToWroteBytes, true, ctx);
-        if (lastSegmentFile.reachesFileEndBy(waitToWroteBytes)) {
-            throw new IOException("Too large value size: " + value.length + ", maxSegmentFileSize="
-                                  + this.maxSegmentFileSize);
-        }
-        if (value.length < this.valueSizeThreshold) {
-            // Small value will be stored in rocksdb directly.
-            lastSegmentFile.setLastLogIndex(logIndex);
-            ctx.finishJob();
-            return value;
-        }
-        // Large value is stored in segment file and returns an encoded location info that will be stored in rocksdb.
-        final int pos = lastSegmentFile.write(logIndex, value, ctx);
-        final long firstLogIndex = lastSegmentFile.getFirstLogIndex();
-        return encodeLocationMetadata(firstLogIndex, pos);
-    }
-
-    /**
-     * Encode segment file firstLogIndex(fileName) and position to a byte array in the format of:
-     * <ul>
-     *  <li> magic bytes(2 B)</li>
-     *  <li> reserved (2 B)</li>
-     *  <li> firstLogIndex(8 B)</li>
-     *  <li> wrote position(4 B)</li>
-     * </ul>
-     * @param firstLogIndex the first log index
-     * @param pos           the wrote position
-     * @return segment info
-     */
-    private byte[] encodeLocationMetadata(final long firstLogIndex, final int pos) {
-        final byte[] newData = new byte[LOCATION_METADATA_SIZE];
-        System.arraycopy(SegmentFile.RECORD_MAGIC_BYTES, 0, newData, 0, SegmentFile.RECORD_MAGIC_BYTES_SIZE);
-        // 2 bytes reserved
-        Bits.putLong(newData, SegmentFile.RECORD_MAGIC_BYTES_SIZE + 2, firstLogIndex);
-        Bits.putInt(newData, SegmentFile.RECORD_MAGIC_BYTES_SIZE + 2 + 8, pos);
-        return newData;
-    }
-
-    private int binarySearchFileIndexByLogIndex(final long logIndex) {
-        this.readLock.lock();
-        try {
-            if (this.segments.isEmpty()) {
-                return -1;
-            }
-            if (this.segments.size() == 1) {
-                final SegmentFile firstFile = this.segments.get(0);
-                if (firstFile.contains(logIndex)) {
-                    return 0;
-                } else {
-                    return -1;
-                }
-            }
-
-            int low = 0;
-            int high = this.segments.size() - 1;
-
-            while (low <= high) {
-                final int mid = (low + high) >>> 1;
-
-                final SegmentFile file = this.segments.get(mid);
-                if (file.getLastLogIndex() < logIndex) {
-                    low = mid + 1;
-                } else if (file.getFirstLogIndex() > logIndex) {
-                    high = mid - 1;
-                } else {
-                    return mid;
-                }
-            }
-            return -(low + 1);
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-
-    private SegmentFile binarySearchFileByFirstLogIndex(final long logIndex) {
-        this.readLock.lock();
-        try {
-            if (this.segments.isEmpty()) {
-                return null;
-            }
-            if (this.segments.size() == 1) {
-                final SegmentFile firstFile = this.segments.get(0);
-                if (firstFile.getFirstLogIndex() == logIndex) {
-                    return firstFile;
-                } else {
-                    return null;
-                }
-            }
-
-            int low = 0;
-            int high = this.segments.size() - 1;
-
-            while (low <= high) {
-                final int mid = (low + high) >>> 1;
-
-                final SegmentFile file = this.segments.get(mid);
-                if (file.getFirstLogIndex() < logIndex) {
-                    low = mid + 1;
-                } else if (file.getFirstLogIndex() > logIndex) {
-                    high = mid - 1;
-                } else {
-                    return file;
-                }
-            }
-            return null;
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-
-    @Override
-    protected byte[] onDataGet(final long logIndex, final byte[] value) throws IOException {
-        if (value == null || value.length != LOCATION_METADATA_SIZE) {
-            return value;
-        }
-
-        int offset = 0;
-        for (; offset < SegmentFile.RECORD_MAGIC_BYTES_SIZE; offset++) {
-            if (value[offset] != SegmentFile.RECORD_MAGIC_BYTES[offset]) {
-                return value;
-            }
-        }
-
-        // skip reserved
-        offset += 2;
-
-        final long firstLogIndex = Bits.getLong(value, offset);
-        final int pos = Bits.getInt(value, offset + 8);
-        final SegmentFile file = binarySearchFileByFirstLogIndex(firstLogIndex);
-        if (file == null) {
-            return null;
-        }
-        return file.read(logIndex, pos);
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/SegmentFile.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/SegmentFile.java
deleted file mode 100644
index 2086930..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/log/SegmentFile.java
+++ /dev/null
@@ -1,905 +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.storage.log;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileChannel.MapMode;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.util.Arrays;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.alipay.sofa.jraft.Lifecycle;
-import com.alipay.sofa.jraft.storage.impl.RocksDBLogStorage.WriteContext;
-import com.alipay.sofa.jraft.storage.log.SegmentFile.SegmentFileOptions;
-import com.alipay.sofa.jraft.util.Bits;
-import com.alipay.sofa.jraft.util.BytesUtil;
-import com.alipay.sofa.jraft.util.OnlyForTest;
-import com.alipay.sofa.jraft.util.Utils;
-import com.sun.jna.NativeLong;
-import com.sun.jna.Pointer;
-
-import sun.nio.ch.DirectBuffer;
-
-/**
- * A fixed size file. The content format is:
- * <pre>
- *   magic bytes       first log index    reserved
- *   [0x20 0x20]      [... 8 bytes...]   [8 bytes]
- *
- *   [record, record, ...]
- * <pre>
- *
- * Every record format is:
- * <pre>
- *   Magic bytes     data length   data
- *   [0x57, 0x8A]    [4 bytes]     [bytes]
- *</pre>
- *
- * @author boyan(boyan@antfin.com)
- * @since 1.2.6
- */
-public class SegmentFile implements Lifecycle<SegmentFileOptions> {
-
-    private static final int  FSYNC_COST_MS_THRESHOLD = 1000;
-    private static final int  ONE_MINUTE              = 60 * 1000;
-    public static final int   HEADER_SIZE             = 18;
-    private static final long BLANK_LOG_INDEX         = -99;
-
-    /**
-     * Segment file header.
-     * @author boyan(boyan@antfin.com)
-     *
-     */
-    public static class SegmentHeader {
-
-        private static final long RESERVED_FLAG = 0L;
-        // The file first log index(inclusive)
-        volatile long             firstLogIndex = BLANK_LOG_INDEX;
-        @SuppressWarnings("unused")
-        long                      reserved;
-        private static final byte MAGIC         = 0x20;
-
-        public SegmentHeader() {
-            super();
-        }
-
-        ByteBuffer encode() {
-            ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE);
-            buffer.put(MAGIC);
-            buffer.put(MAGIC);
-            buffer.putLong(this.firstLogIndex);
-            buffer.putLong(RESERVED_FLAG);
-            buffer.flip();
-            return buffer;
-        }
-
-        boolean decode(final ByteBuffer buffer) {
-            if (buffer == null || buffer.remaining() < HEADER_SIZE) {
-                LOG.error("Fail to decode segment header, invalid buffer length: {}",
-                    buffer == null ? 0 : buffer.remaining());
-                return false;
-            }
-            if (buffer.get() != MAGIC) {
-                LOG.error("Fail to decode segment header, invalid magic.");
-                return false;
-            }
-            if (buffer.get() != MAGIC) {
-                LOG.error("Fail to decode segment header, invalid magic.");
-                return false;
-            }
-            this.firstLogIndex = buffer.getLong();
-            return true;
-        }
-    }
-
-    /**
-     * Segment file options.
-     *
-     * @author boyan(boyan@antfin.com)
-     */
-    public static class SegmentFileOptions {
-        // Whether to recover
-        final boolean recover;
-        // Recover start position
-        final int     pos;
-        // True when is the last file.
-        final boolean isLastFile;
-        // True when is a new created file.
-        final boolean isNewFile;
-        final boolean sync;
-
-        private SegmentFileOptions(final boolean recover, final boolean isLastFile, final boolean isNewFile,
-                                   final boolean sync, final int pos) {
-            super();
-            this.isNewFile = isNewFile;
-            this.isLastFile = isLastFile;
-            this.recover = recover;
-            this.sync = sync;
-            this.pos = pos;
-        }
-
-        public static Builder builder() {
-            return new Builder();
-        }
-
-        public static class Builder {
-            boolean recover    = false;
-            int     pos        = 0;
-            boolean isLastFile = false;
-            boolean isNewFile  = false;
-            boolean sync       = true;
-
-            public Builder setRecover(final boolean recover) {
-                this.recover = recover;
-                return this;
-            }
-
-            public Builder setPos(final int pos) {
-                this.pos = pos;
-                return this;
-            }
-
-            public Builder setLastFile(final boolean isLastFile) {
-                this.isLastFile = isLastFile;
-                return this;
-            }
-
-            public Builder setNewFile(final boolean isNewFile) {
-                this.isNewFile = isNewFile;
-                return this;
-            }
-
-            public Builder setSync(final boolean sync) {
-                this.sync = sync;
-                return this;
-            }
-
-            public SegmentFileOptions build() {
-                return new SegmentFileOptions(this.recover, this.isLastFile, this.isNewFile, this.sync, this.pos);
-            }
-        }
-
-    }
-
-    private static final int         BLANK_HOLE_SIZE         = 64;
-
-    private static final Logger      LOG                     = LoggerFactory.getLogger(SegmentFile.class);
-
-    // 4 Bytes for written data length
-    private static final int         RECORD_DATA_LENGTH_SIZE = 4;
-
-    /**
-     * Magic bytes for data buffer.
-     */
-    public static final byte[]       RECORD_MAGIC_BYTES      = new byte[] { (byte) 0x57, (byte) 0x8A };
-
-    public static final int          RECORD_MAGIC_BYTES_SIZE = RECORD_MAGIC_BYTES.length;
-
-    private final SegmentHeader      header;
-
-    // The file last log index(inclusive)
-    private volatile long            lastLogIndex            = Long.MAX_VALUE;
-    // File size
-    private int                      size;
-    // File path
-    private final String             path;
-    // mmap byte buffer.
-    private MappedByteBuffer         buffer;
-    // Wrote position.
-    private volatile int             wrotePos;
-    // Committed position
-    private volatile int             committedPos;
-
-    private final ReadWriteLock      readWriteLock           = new ReentrantReadWriteLock(false);
-
-    private final Lock               writeLock               = this.readWriteLock.writeLock();
-    private final Lock               readLock                = this.readWriteLock.readLock();
-    private final ThreadPoolExecutor writeExecutor;
-    private volatile boolean         swappedOut;
-    private volatile boolean         readOnly;
-    private long                     swappedOutTimestamp     = -1L;
-    private final String             filename;
-
-    public SegmentFile(final int size, final String path, final ThreadPoolExecutor writeExecutor) {
-        super();
-        this.header = new SegmentHeader();
-        this.size = size;
-        this.writeExecutor = writeExecutor;
-        this.path = path;
-        this.filename = FilenameUtils.getName(this.path);
-        this.swappedOut = this.readOnly = false;
-    }
-
-    void setReadOnly(final boolean readOnly) {
-        this.readOnly = readOnly;
-    }
-
-    void setFirstLogIndex(final long index) {
-        this.header.firstLogIndex = index;
-    }
-
-    long getLastLogIndex() {
-        return this.lastLogIndex;
-    }
-
-    @OnlyForTest
-    public int getWrotePos() {
-        return this.wrotePos;
-    }
-
-    int getCommittedPos() {
-        return this.committedPos;
-    }
-
-    String getFilename() {
-        return this.filename;
-    }
-
-    long getFirstLogIndex() {
-        return this.header.firstLogIndex;
-    }
-
-    public boolean isSwappedOut() {
-        return this.swappedOut;
-    }
-
-    int getSize() {
-        return this.size;
-    }
-
-    /**
-     * return true when this segment file is blank that we don't write any data into it.
-     * @return
-     */
-    boolean isBlank() {
-        return this.header.firstLogIndex == BLANK_LOG_INDEX;
-    }
-
-    boolean isHeaderCorrupted() {
-        return this.header == null;
-    }
-
-    String getPath() {
-        return this.path;
-    }
-
-    public void setLastLogIndex(final long lastLogIndex) {
-        this.writeLock.lock();
-        try {
-            this.lastLogIndex = lastLogIndex;
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    private void swapIn() {
-        if (this.swappedOut) {
-            this.writeLock.lock();
-            try {
-                if (!this.swappedOut) {
-                    return;
-                }
-                long startMs = Utils.monotonicMs();
-                mmapFile(false);
-                this.swappedOut = false;
-                LOG.info("Swapped in segment file {} cost {} ms.", this.path, Utils.monotonicMs() - startMs);
-            } finally {
-                this.writeLock.unlock();
-            }
-        }
-    }
-
-    public void hintLoad() {
-        final long address = ((DirectBuffer) (this.buffer)).address();
-        Pointer pointer = new Pointer(address);
-
-        long beginTime = Utils.monotonicMs();
-        int ret = LibC.INSTANCE.madvise(pointer, new NativeLong(this.size), LibC.MADV_WILLNEED);
-        LOG.info("madvise(MADV_WILLNEED) {} {} {} ret = {} time consuming = {}", address, this.path, this.size, ret,
-            Utils.monotonicMs() - beginTime);
-    }
-
-    public void hintUnload() {
-        final long address = ((DirectBuffer) (this.buffer)).address();
-        Pointer pointer = new Pointer(address);
-
-        long beginTime = Utils.monotonicMs();
-        int ret = LibC.INSTANCE.madvise(pointer, new NativeLong(this.size), LibC.MADV_DONTNEED);
-        LOG.info("madvise(MADV_DONTNEED) {} {} {} ret = {} time consuming = {}", address, this.path, this.size, ret,
-            Utils.monotonicMs() - beginTime);
-    }
-
-    public void swapOut() {
-        if (!this.swappedOut) {
-            this.writeLock.lock();
-            try {
-                if (this.swappedOut) {
-                    return;
-                }
-                if (!this.readOnly) {
-                    LOG.warn("The segment file {} is not readonly, can't be swapped out.", this.path);
-                    return;
-                }
-                final long now = Utils.monotonicMs();
-                if (this.swappedOutTimestamp > 0 && now - this.swappedOutTimestamp < ONE_MINUTE) {
-                    return;
-                }
-                this.swappedOut = true;
-                unmap(this.buffer);
-                this.buffer = null;
-                this.swappedOutTimestamp = now;
-                LOG.info("Swapped out segment file {} cost {} ms.", this.path, Utils.monotonicMs() - now);
-            } finally {
-                this.writeLock.unlock();
-            }
-        }
-    }
-
-    /**
-     * Truncate data from wrotePos(inclusive) to the file end and set lastLogIndex=logIndex.
-     * @param wrotePos the wrote position(inclusive)
-     * @param logIndex the log index
-     * @param sync whether to call fsync
-     */
-    public void truncateSuffix(final int wrotePos, final long logIndex, final boolean sync) {
-        this.writeLock.lock();
-        try {
-            if (wrotePos >= this.wrotePos) {
-                return;
-            }
-            swapInIfNeed();
-            final int oldPos = this.wrotePos;
-            clear(wrotePos, sync);
-            this.wrotePos = wrotePos;
-            this.lastLogIndex = logIndex;
-            this.buffer.position(wrotePos);
-            LOG.info(
-                "Segment file {} truncate suffix from pos={}, then set lastLogIndex={}, oldWrotePos={}, newWrotePos={}",
-                this.path, wrotePos, logIndex, oldPos, this.wrotePos);
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    /**
-     * Returns true when the segment file contains the log index.
-     *
-     * @param logIndex the log index
-     * @return true if the segment file contains the log index, otherwise return false
-     */
-    public boolean contains(final long logIndex) {
-        this.readLock.lock();
-        try {
-            return logIndex >= this.header.firstLogIndex && logIndex <= this.lastLogIndex;
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-
-    /**
-     * Clear data in [startPos, startPos+64).
-     *
-     * @param startPos the start position(inclusive)
-     */
-    public void clear(final int startPos, final boolean sync) {
-        this.writeLock.lock();
-        try {
-            if (startPos < 0 || startPos > this.size) {
-                return;
-            }
-            final int endPos = Math.min(this.size, startPos + BLANK_HOLE_SIZE);
-            for (int i = startPos; i < endPos; i++) {
-                this.buffer.put(i, (byte) 0);
-            }
-            if (sync) {
-                fsync(this.buffer);
-            }
-            LOG.info("Segment file {} cleared data in [{}, {}).", this.path, startPos, endPos);
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    @Override
-    public boolean init(final SegmentFileOptions opts) {
-        if (opts.isNewFile) {
-            return loadNewFile(opts);
-        } else {
-            return loadExistsFile(opts);
-        }
-
-    }
-
-    private boolean loadNewFile(final SegmentFileOptions opts) {
-        assert (opts.pos == 0);
-        assert (!opts.recover);
-
-        final File file = new File(this.path);
-
-        if (file.exists()) {
-            LOG.error("File {} already exists.", this.path);
-            return false;
-        }
-        long startMs = Utils.monotonicMs();
-        this.writeLock.lock();
-        try (FileChannel fc = openFileChannel(true)) {
-            this.buffer = fc.map(MapMode.READ_WRITE, 0, this.size);
-            // Warmup mmap file
-            this.buffer.position(0);
-            this.buffer.limit(this.size);
-            saveHeader(true);
-
-            this.committedPos = this.wrotePos = HEADER_SIZE;
-            this.buffer.position(this.wrotePos);
-
-            assert (this.wrotePos == this.buffer.position());
-
-            LOG.info("Created a new segment file {} cost {} ms, wrotePosition={}, bufferPosition={}, mappedSize={}.",
-                this.path, Utils.monotonicMs() - startMs, this.wrotePos, this.buffer.position(), this.size);
-            return true;
-        } catch (final IOException e) {
-            LOG.error("Fail to init segment file {}.", this.path, e);
-            return false;
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    private boolean loadExistsFile(final SegmentFileOptions opts) {
-        this.writeLock.lock();
-        try {
-            if (!mmapFile(false)) {
-                return false;
-            }
-            if (!tryRecoverExistsFile(opts)) {
-                return false;
-            }
-            this.readOnly = !opts.isLastFile;
-            return true;
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    private boolean tryRecoverExistsFile(final SegmentFileOptions opts) {
-        try {
-            if (isBlank()) {
-                // A blank segment, we don't need to recover.
-                assert (!opts.recover);
-                this.committedPos = this.wrotePos = HEADER_SIZE;
-                this.buffer.position(this.wrotePos);
-                LOG.info("Segment file {} is blank, truncate it from {}.", this.path, HEADER_SIZE);
-                clear(this.wrotePos, opts.sync);
-            } else {
-                if (opts.recover) {
-                    if (!recover(opts)) {
-                        return false;
-                    }
-                } else {
-                    this.wrotePos = opts.pos;
-                    this.buffer.position(this.wrotePos);
-                }
-                assert (this.wrotePos == this.buffer.position());
-                this.committedPos = this.wrotePos;
-            }
-            LOG.info("Loaded segment file {}, wrotePosition={}, bufferPosition={}, mappedSize={}.", this.path,
-                this.wrotePos, this.buffer.position(), this.size);
-        } catch (final Exception e) {
-            LOG.error("Fail to load segment file {}.", this.path, e);
-            return false;
-        }
-        return true;
-    }
-
-    boolean mmapFile(final boolean create) {
-        if (this.buffer != null) {
-            return true;
-        }
-        final File file = new File(this.path);
-
-        if (file.exists()) {
-            this.size = (int) file.length();
-        } else {
-            if (!create) {
-                LOG.error("File {} is not exists.", this.path);
-                return false;
-            }
-        }
-        try (FileChannel fc = openFileChannel(create)) {
-            this.buffer = fc.map(MapMode.READ_WRITE, 0, this.size);
-            this.buffer.limit(this.size);
-            if (!loadHeader()) {
-                LOG.error("Fail to load segment header from file {}.", this.path);
-                return false;
-            }
-            return true;
-        } catch (final IOException e) {
-            LOG.error("Fail to mmap segment file {}.", this.path, e);
-            return false;
-        }
-    }
-
-    private FileChannel openFileChannel(final boolean create) throws IOException {
-        if (create) {
-            return FileChannel.open(Paths.get(this.path), StandardOpenOption.CREATE, StandardOpenOption.READ,
-                StandardOpenOption.WRITE);
-        } else {
-            return FileChannel.open(Paths.get(this.path), StandardOpenOption.READ, StandardOpenOption.WRITE);
-        }
-    }
-
-    boolean loadHeader() {
-        int oldPos = this.buffer.position();
-        try {
-            this.buffer.position(0);
-            return this.header.decode(this.buffer.asReadOnlyBuffer());
-        } finally {
-            this.buffer.position(oldPos);
-        }
-    }
-
-    void saveHeader(final boolean sync) {
-        int oldPos = this.buffer.position();
-        try {
-            this.buffer.position(0);
-            final ByteBuffer headerBuf = this.header.encode();
-            assert (headerBuf.remaining() == HEADER_SIZE);
-            this.buffer.put(headerBuf);
-            if (sync) {
-                fsync(this.buffer);
-            }
-        } finally {
-            this.buffer.position(oldPos);
-        }
-    }
-
-    @SuppressWarnings("NonAtomicOperationOnVolatileField")
-    private boolean recover(final SegmentFileOptions opts) throws IOException {
-        LOG.info("Start to recover segment file {} from position {}.", this.path, opts.pos);
-        this.wrotePos = opts.pos;
-        if (this.wrotePos < HEADER_SIZE) {
-            this.wrotePos = HEADER_SIZE;
-        }
-        this.buffer.position(this.wrotePos);
-        final long start = Utils.monotonicMs();
-        while (this.wrotePos < this.size) {
-            if (this.buffer.remaining() < RECORD_MAGIC_BYTES_SIZE) {
-                LOG.error("Fail to recover segment file {}, missing magic bytes.", this.path);
-                return false;
-            }
-            final byte[] magicBytes = new byte[RECORD_MAGIC_BYTES_SIZE];
-            this.buffer.get(magicBytes);
-
-            if (!Arrays.equals(RECORD_MAGIC_BYTES, magicBytes)) {
-
-                boolean truncateDirty = false;
-
-                int i = 0;
-                for (final byte b : magicBytes) {
-                    i++;
-                    if (b != 0) {
-                        if (opts.isLastFile) {
-                            // It's the last file
-                            // Truncate the dirty data from wrotePos
-                            LOG.error("Corrupted magic bytes in segment file {} at pos={}, will truncate it.",
-                                this.path, this.wrotePos + i);
-                            truncateDirty = true;
-                            break;
-                        } else {
-                            // It's not the last file, but has invalid magic bytes, the data is corrupted.
-                            LOG.error("Fail to recover segment file {}, invalid magic bytes: {} at pos={}.", this.path,
-                                BytesUtil.toHex(magicBytes), this.wrotePos);
-                            return false;
-                        }
-                    }
-                }
-
-                if (truncateDirty) {
-                    truncateFile(opts.sync);
-                } else {
-                    // Reach blank hole, rewind position.
-                    this.buffer.position(this.buffer.position() - RECORD_MAGIC_BYTES_SIZE);
-                }
-                // Reach end or dirty magic bytes, we should break out.
-                break;
-            }
-
-            if (this.buffer.remaining() < RECORD_DATA_LENGTH_SIZE) {
-                if (opts.isLastFile) {
-                    LOG.error("Corrupted data length in segment file {} at pos={}, will truncate it.", this.path,
-                        this.buffer.position());
-                    truncateFile(opts.sync);
-                    break;
-                } else {
-                    LOG.error(
-                        "Fail to recover segment file {}, invalid data length remaining: {}, expected {} at pos={}.",
-                        this.path, this.buffer.remaining(), RECORD_DATA_LENGTH_SIZE, this.wrotePos);
-                    return false;
-                }
-            }
-
-            final int dataLen = this.buffer.getInt();
-            if (this.buffer.remaining() < dataLen) {
-                if (opts.isLastFile) {
-                    LOG.error(
-                        "Corrupted data in segment file {} at pos={},  expectDataLength={}, but remaining is {}, will truncate it.",
-                        this.path, this.buffer.position(), dataLen, this.buffer.remaining());
-                    truncateFile(opts.sync);
-                    break;
-                } else {
-                    LOG.error(
-                        "Fail to recover segment file {}, invalid data: expected {} bytes in buf but actual {} at pos={}.",
-                        this.path, dataLen, this.buffer.remaining(), this.wrotePos);
-                    return false;
-                }
-
-            }
-            // Skip data
-            this.buffer.position(this.buffer.position() + dataLen);
-            this.wrotePos += RECORD_MAGIC_BYTES_SIZE + RECORD_DATA_LENGTH_SIZE + dataLen;
-        }
-        LOG.info("Recover segment file {} cost {} millis.", this.path, Utils.monotonicMs() - start);
-        return true;
-    }
-
-    private void truncateFile(final boolean sync) throws IOException {
-        // Truncate dirty data.
-        clear(this.wrotePos, sync);
-        this.buffer.position(this.wrotePos);
-        LOG.warn("Truncated segment file {} from pos={}.", this.path, this.wrotePos);
-    }
-
-    public boolean reachesFileEndBy(final long waitToWroteBytes) {
-        this.readLock.lock();
-        try {
-            return this.wrotePos + waitToWroteBytes > this.size;
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-
-    public boolean isFull() {
-        return reachesFileEndBy(1);
-    }
-
-    static int getWriteBytes(final byte[] data) {
-        return RECORD_MAGIC_BYTES_SIZE + RECORD_DATA_LENGTH_SIZE + data.length;
-    }
-
-    /**
-     * Write the data and return it's wrote position.
-     *
-     * @param logIndex the log index
-     * @param data     data to write
-     * @return the wrote position
-     */
-    @SuppressWarnings("NonAtomicOperationOnVolatileField")
-    public int write(final long logIndex, final byte[] data, final WriteContext ctx) {
-        int pos = -1;
-        MappedByteBuffer buf = null;
-        this.writeLock.lock();
-        try {
-            assert (this.wrotePos == this.buffer.position());
-            buf = this.buffer;
-            pos = this.wrotePos;
-            this.wrotePos += RECORD_MAGIC_BYTES_SIZE + RECORD_DATA_LENGTH_SIZE + data.length;
-            this.buffer.position(this.wrotePos);
-            // Update log index.
-            if (isBlank() || pos == HEADER_SIZE) {
-                this.header.firstLogIndex = logIndex;
-                // we don't need to call fsync header here, the new header will be flushed with this wrote.
-                saveHeader(false);
-            }
-            this.lastLogIndex = logIndex;
-            return pos;
-        } finally {
-            this.writeLock.unlock();
-            final int wroteIndex = pos;
-            final MappedByteBuffer buffer = buf;
-            this.writeExecutor.execute(() -> {
-                try {
-                    put(buffer, wroteIndex, RECORD_MAGIC_BYTES);
-                    putInt(buffer, wroteIndex + RECORD_MAGIC_BYTES_SIZE, data.length);
-                    put(buffer, wroteIndex + RECORD_MAGIC_BYTES_SIZE + RECORD_DATA_LENGTH_SIZE, data);
-                } catch (final Exception e) {
-                    ctx.setError(e);
-                } finally {
-                    ctx.finishJob();
-                }
-            });
-        }
-    }
-
-    private static void putInt(final MappedByteBuffer buffer, final int index, final int n) {
-        byte[] bs = new byte[RECORD_DATA_LENGTH_SIZE];
-        Bits.putInt(bs, 0, n);
-        for (int i = 0; i < bs.length; i++) {
-            buffer.put(index + i, bs[i]);
-        }
-    }
-
-    private static void put(final MappedByteBuffer buffer, final int index, final byte[] data) {
-        for (int i = 0; i < data.length; i++) {
-            buffer.put(index + i, data[i]);
-        }
-    }
-
-    /**
-     * Read data from the position.
-     *
-     * @param logIndex the log index
-     * @param pos      the position to read
-     * @return read data
-     */
-    public byte[] read(final long logIndex, final int pos) throws IOException {
-        assert (pos >= HEADER_SIZE);
-        swapInIfNeed();
-        this.readLock.lock();
-        try {
-            if (logIndex < this.header.firstLogIndex || logIndex > this.lastLogIndex) {
-                LOG.warn(
-                    "Try to read data from segment file {} out of range, logIndex={}, readPos={}, firstLogIndex={}, lastLogIndex={}.",
-                    this.path, logIndex, pos, this.header.firstLogIndex, this.lastLogIndex);
-                return null;
-            }
-            if (pos >= this.committedPos) {
-                LOG.warn(
-                    "Try to read data from segment file {} out of comitted position, logIndex={}, readPos={}, wrotePos={}, this.committedPos={}.",
-                    this.path, logIndex, pos, this.wrotePos, this.committedPos);
-                return null;
-            }
-            final ByteBuffer readBuffer = this.buffer.asReadOnlyBuffer();
-            readBuffer.position(pos);
-            if (readBuffer.remaining() < RECORD_MAGIC_BYTES_SIZE) {
-                throw new IOException("Missing magic buffer.");
-            }
-            readBuffer.position(pos + RECORD_MAGIC_BYTES_SIZE);
-            final int dataLen = readBuffer.getInt();
-            //TODO(boyan) reuse data array?
-            final byte[] data = new byte[dataLen];
-            readBuffer.get(data);
-            return data;
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-
-    private void swapInIfNeed() {
-        if (this.swappedOut) {
-            swapIn();
-        }
-    }
-
-    /**
-     * Forces any changes made to this segment file's content to be written to the
-     * storage device containing the mapped file.
-     */
-    public void sync(final boolean sync) throws IOException {
-        MappedByteBuffer buf = null;
-        this.writeLock.lock();
-        try {
-            if (this.committedPos >= this.wrotePos) {
-                return;
-            }
-            this.committedPos = this.wrotePos;
-            buf = this.buffer;
-            LOG.debug("Commit segment file {} at pos {}.", this.path, this.committedPos);
-        } finally {
-            this.writeLock.unlock();
-        }
-        if (sync) {
-            fsync(buf);
-        }
-    }
-
-    private void fsync(final MappedByteBuffer buffer) {
-        if (buffer != null) {
-            long startMs = Utils.monotonicMs();
-            buffer.force();
-            final long cost = Utils.monotonicMs() - startMs;
-            if (cost >= FSYNC_COST_MS_THRESHOLD) {
-                LOG.warn("Call fsync on file {}  cost {} ms.", this.path, cost);
-            }
-        }
-    }
-
-    /**
-     * Destroy the file.
-     */
-    public void destroy() {
-        this.writeLock.lock();
-        try {
-            shutdown();
-            FileUtils.deleteQuietly(new File(this.path));
-            LOG.info("Deleted segment file {}.", this.path);
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    // See https://stackoverflow.com/questions/2972986/how-to-unmap-a-file-from-memory-mapped-using-filechannel-in-java
-    // TODO move into utils
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    private static void unmap(final MappedByteBuffer cb) {
-        // JavaSpecVer: 1.6, 1.7, 1.8, 9, 10
-        final boolean isOldJDK = System.getProperty("java.specification.version", "99").startsWith("1.");
-        try {
-            if (isOldJDK) {
-                final Method cleaner = cb.getClass().getMethod("cleaner");
-                cleaner.setAccessible(true);
-                final Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean");
-                clean.setAccessible(true);
-                clean.invoke(cleaner.invoke(cb));
-            } else {
-                Class unsafeClass;
-                try {
-                    unsafeClass = Class.forName("sun.misc.Unsafe");
-                } catch (final Exception ex) {
-                    // jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method,
-                    // but that method should be added if sun.misc.Unsafe is removed.
-                    unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
-                }
-                final Method clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
-                clean.setAccessible(true);
-                final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
-                theUnsafeField.setAccessible(true);
-                final Object theUnsafe = theUnsafeField.get(null);
-                clean.invoke(theUnsafe, cb);
-            }
-        } catch (final Exception ex) {
-            LOG.error("Fail to un-mapped segment file.", ex);
-        }
-    }
-
-    @Override
-    public void shutdown() {
-        this.writeLock.lock();
-        try {
-            if (this.buffer == null) {
-                return;
-            }
-            hintUnload();
-            unmap(this.buffer);
-            this.buffer = null;
-            LOG.info("Unloaded segment file {}, current status: {}.", this.path, toString());
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "SegmentFile [firstLogIndex=" + this.header.firstLogIndex + ", lastLogIndex=" + this.lastLogIndex
-               + ", size=" + this.size + ", path=" + this.path + ", wrotePos=" + this.wrotePos + ", committedPos="
-               + this.committedPos + "]";
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotMetaTable.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotMetaTable.java
index ee99ac9..f26b337 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotMetaTable.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/storage/snapshot/local/LocalSnapshotMetaTable.java
@@ -31,8 +31,6 @@ import com.alipay.sofa.jraft.entity.LocalStorageOutter.LocalSnapshotPbMeta.File;
 import com.alipay.sofa.jraft.entity.RaftOutter.SnapshotMeta;
 import com.alipay.sofa.jraft.option.RaftOptions;
 import com.alipay.sofa.jraft.storage.io.ProtoBufFile;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.ZeroByteStringHelper;
 
 /**
  * Table to keep local snapshot metadata infos.
@@ -40,6 +38,7 @@ import com.google.protobuf.ZeroByteStringHelper;
  * @author boyan (boyan@alibaba-inc.com)
  *
  * 2018-Mar-12 7:22:27 PM
+ * TODO asch broken
  */
 public class LocalSnapshotMetaTable {
 
@@ -81,13 +80,13 @@ public class LocalSnapshotMetaTable {
             return false;
         }
         try {
-            final LocalSnapshotPbMeta pbMeta = LocalSnapshotPbMeta.parseFrom(ZeroByteStringHelper.wrap(buf));
+            final LocalSnapshotPbMeta pbMeta = LocalSnapshotPbMeta.parseFrom(buf);
             if (pbMeta == null) {
                 LOG.error("Fail to load meta from buffer.");
                 return false;
             }
             return loadFromPbMeta(pbMeta);
-        } catch (final InvalidProtocolBufferException e) {
+        } catch (final Exception e) {
             LOG.error("Fail to parse LocalSnapshotPbMeta from byte buffer", e);
             return false;
         }
@@ -127,7 +126,7 @@ public class LocalSnapshotMetaTable {
      * Returns true when has the snapshot metadata.
      */
     public boolean hasMeta() {
-        return this.meta != null && this.meta.isInitialized();
+        return this.meta != null;
     }
 
     /**
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/AsciiStringUtil.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/AsciiStringUtil.java
index 42e7ade..791b6aa 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/AsciiStringUtil.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/AsciiStringUtil.java
@@ -16,9 +16,6 @@
  */
 package com.alipay.sofa.jraft.util;
 
-import com.alipay.sofa.jraft.util.internal.UnsafeUtil;
-import com.google.protobuf.ByteString;
-
 /**
  * @author jiachun.fjc
  */
@@ -38,7 +35,7 @@ public final class AsciiStringUtil {
         for (int i = 0; i < len; i++) {
             out[i] = (char) (in[i + offset] & 0xFF);
         }
-        return UnsafeUtil.moveToString(out);
+        return moveToString(out);
     }
 
     public static String unsafeDecode(final byte[] in) {
@@ -51,9 +48,13 @@ public final class AsciiStringUtil {
         for (int i = 0; i < len; i++) {
             out[i] = (char) (in.byteAt(i) & 0xFF);
         }
-        return UnsafeUtil.moveToString(out);
+        return moveToString(out);
     }
 
     private AsciiStringUtil() {
     }
+
+    public static String moveToString(final char[] chars) {
+        return new String(chars);
+    }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/ByteString.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/ByteString.java
index dc76b81..b4d92a5 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/ByteString.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/ByteString.java
@@ -1,6 +1,10 @@
 package com.alipay.sofa.jraft.util;
 
+import java.io.IOException;
+import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.WritableByteChannel;
 
 public class ByteString {
     public static final ByteString EMPTY = new ByteString(ByteBuffer.wrap(new byte[0]));
@@ -22,4 +26,14 @@ public class ByteString {
     public ByteBuffer asReadOnlyByteBuffer() {
         return buf.asReadOnlyBuffer();
     }
+
+    public byte byteAt(int pos) {
+        return buf.get(pos);
+    }
+
+    public void writeTo(OutputStream outputStream) throws IOException {
+        WritableByteChannel channel = Channels.newChannel(outputStream);
+
+        channel.write(buf);
+    }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/BytesUtil.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/BytesUtil.java
index f3364ee..01ccac2 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/BytesUtil.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/BytesUtil.java
@@ -20,8 +20,6 @@ import java.io.Serializable;
 import java.nio.charset.StandardCharsets;
 import java.util.Comparator;
 
-import com.alipay.sofa.jraft.util.internal.UnsafeUtf8Util;
-import com.alipay.sofa.jraft.util.internal.UnsafeUtil;
 
 /**
  * @author jiachun.fjc
@@ -41,39 +39,39 @@ public final class BytesUtil {
         return bytes == null || bytes.length == 0;
     }
 
-    /**
-     * This method has better performance than String#getBytes(Charset),
-     * See the benchmark class: Utf8Benchmark for details.
-     */
-    public static byte[] writeUtf8(final String in) {
-        if (in == null) {
-            return null;
-        }
-        if (UnsafeUtil.hasUnsafe()) {
-            // Calculate the encoded length.
-            final int len = UnsafeUtf8Util.encodedLength(in);
-            final byte[] outBytes = new byte[len];
-            UnsafeUtf8Util.encodeUtf8(in, outBytes, 0, len);
-            return outBytes;
-        } else {
-            return in.getBytes(StandardCharsets.UTF_8);
-        }
-    }
-
-    /**
-     * This method has better performance than String#String(byte[], Charset),
-     * See the benchmark class: Utf8Benchmark for details.
-     */
-    public static String readUtf8(final byte[] in) {
-        if (in == null) {
-            return null;
-        }
-        if (UnsafeUtil.hasUnsafe()) {
-            return UnsafeUtf8Util.decodeUtf8(in, 0, in.length);
-        } else {
-            return new String(in, StandardCharsets.UTF_8);
-        }
-    }
+//    /**
+//     * This method has better performance than String#getBytes(Charset),
+//     * See the benchmark class: Utf8Benchmark for details.
+//     */
+//    public static byte[] writeUtf8(final String in) {
+//        if (in == null) {
+//            return null;
+//        }
+//        if (UnsafeUtil.hasUnsafe()) {
+//            // Calculate the encoded length.
+//            final int len = UnsafeUtf8Util.encodedLength(in);
+//            final byte[] outBytes = new byte[len];
+//            UnsafeUtf8Util.encodeUtf8(in, outBytes, 0, len);
+//            return outBytes;
+//        } else {
+//            return in.getBytes(StandardCharsets.UTF_8);
+//        }
+//    }
+//
+//    /**
+//     * This method has better performance than String#String(byte[], Charset),
+//     * See the benchmark class: Utf8Benchmark for details.
+//     */
+//    public static String readUtf8(final byte[] in) {
+//        if (in == null) {
+//            return null;
+//        }
+//        if (UnsafeUtil.hasUnsafe()) {
+//            return UnsafeUtf8Util.decodeUtf8(in, 0, in.length);
+//        } else {
+//            return new String(in, StandardCharsets.UTF_8);
+//        }
+//    }
 
     public static byte[] nextBytes(final byte[] bytes) {
         Requires.requireNonNull(bytes, "bytes");
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/JDKMarshaller.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/JDKMarshaller.java
new file mode 100644
index 0000000..cf9bdce
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/JDKMarshaller.java
@@ -0,0 +1,32 @@
+package com.alipay.sofa.jraft.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/** */
+public class JDKMarshaller implements Marshaller {
+    /** {@inheritDoc} */
+    @Override public byte[] marshall(Object o) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(o);
+        oos.close();
+
+        return baos.toByteArray();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object unmarshall(byte[] raw) throws IOException{
+        ByteArrayInputStream bais = new ByteArrayInputStream(raw);
+        ObjectInputStream oos = new ObjectInputStream(bais);
+
+        try {
+            return oos.readObject();
+        } catch (ClassNotFoundException e) {
+            throw new Error(e);
+        }
+    }
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/Marshaller.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/Marshaller.java
new file mode 100644
index 0000000..6f28493
--- /dev/null
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/Marshaller.java
@@ -0,0 +1,11 @@
+package com.alipay.sofa.jraft.util;
+
+import java.io.IOException;
+
+public interface Marshaller {
+    public static Marshaller DEFAULT = new JDKMarshaller();
+
+    byte[] marshall(Object o) throws IOException;
+
+    <T> T unmarshall(byte[] raw) throws IOException;
+}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/Mpsc.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/Mpsc.java
index 76a5987..df8e5c1 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/Mpsc.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/Mpsc.java
@@ -18,12 +18,13 @@ package com.alipay.sofa.jraft.util;
 
 import java.util.Queue;
 
-import org.jctools.queues.MpscChunkedArrayQueue;
-import org.jctools.queues.MpscUnboundedArrayQueue;
-import org.jctools.queues.atomic.MpscGrowableAtomicArrayQueue;
-import org.jctools.queues.atomic.MpscUnboundedAtomicArrayQueue;
+//import org.jctools.queues.MpscChunkedArrayQueue;
+//import org.jctools.queues.MpscUnboundedArrayQueue;
+//import org.jctools.queues.atomic.MpscGrowableAtomicArrayQueue;
+//import org.jctools.queues.atomic.MpscUnboundedAtomicArrayQueue;
 
-import com.alipay.sofa.jraft.util.internal.UnsafeUtil;
+//import com.alipay.sofa.jraft.util.internal.UnsafeUtil;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 /**
  * @author jiachun.fjc
@@ -34,13 +35,16 @@ public final class Mpsc {
     private static final int MIN_MAX_MPSC_CAPACITY = MPSC_CHUNK_SIZE << 1;
 
     public static Queue<Runnable> newMpscQueue() {
-        return UnsafeUtil.hasUnsafe() ? new MpscUnboundedArrayQueue<>(MPSC_CHUNK_SIZE)
-            : new MpscUnboundedAtomicArrayQueue<>(MPSC_CHUNK_SIZE);
+//        return UnsafeUtil.hasUnsafe() ? new MpscUnboundedArrayQueue<>(MPSC_CHUNK_SIZE)
+//            : new MpscUnboundedAtomicArrayQueue<>(MPSC_CHUNK_SIZE);
+        return new ConcurrentLinkedQueue<>();
     }
 
     public static Queue<Runnable> newMpscQueue(final int maxCapacity) {
-        final int capacity = Math.max(MIN_MAX_MPSC_CAPACITY, maxCapacity);
-        return UnsafeUtil.hasUnsafe() ? new MpscChunkedArrayQueue<>(MPSC_CHUNK_SIZE, capacity)
-            : new MpscGrowableAtomicArrayQueue<>(MPSC_CHUNK_SIZE, capacity);
+//        final int capacity = Math.max(MIN_MAX_MPSC_CAPACITY, maxCapacity);
+//        return UnsafeUtil.hasUnsafe() ? new MpscChunkedArrayQueue<>(MPSC_CHUNK_SIZE, capacity)
+//            : new MpscGrowableAtomicArrayQueue<>(MPSC_CHUNK_SIZE, capacity);
+
+        return new ConcurrentLinkedQueue<>();
     }
 }
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/SegmentList.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/SegmentList.java
index 1828b20..7f20772 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/SegmentList.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/SegmentList.java
@@ -22,7 +22,6 @@ import java.util.Collection;
 import java.util.function.Predicate;
 
 import com.alipay.sofa.jraft.util.internal.ReferenceFieldUpdater;
-import com.alipay.sofa.jraft.util.internal.UnsafeUtil;
 import com.alipay.sofa.jraft.util.internal.Updaters;
 
 /**
@@ -375,11 +374,14 @@ public class SegmentList<T> {
 
     private Object[] coll2Array(final Collection<T> coll) {
         Object[] src;
-        if (coll instanceof ArrayList && UnsafeUtil.hasUnsafe()) {
-            src = LIST_ARRAY_GETTER.get((ArrayList<T>) coll);
-        } else {
-            src = coll.toArray();
-        }
+//        if (coll instanceof ArrayList && UnsafeUtil.hasUnsafe()) {
+//            src = LIST_ARRAY_GETTER.get((ArrayList<T>) coll);
+//        } else {
+//            src = coll.toArray();
+//        }
+
+        src = coll.toArray();
+
         return src;
     }
 
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/SignalHelper.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/SignalHelper.java
deleted file mode 100644
index e698485..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/SignalHelper.java
+++ /dev/null
@@ -1,114 +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.util;
-
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- *
- * @author jiachun.fjc
- */
-public final class SignalHelper {
-
-    private static final Logger         LOG             = LoggerFactory.getLogger(SignalHelper.class);
-
-    private static final SignalAccessor SIGNAL_ACCESSOR = getSignalAccessor0();
-
-    public static final String          SIG_USR2        = "USR2";
-
-    public static boolean supportSignal() {
-        return SIGNAL_ACCESSOR != null;
-    }
-
-    /**
-     * Registers user signal handlers.
-     *
-     * @param signalName a signal name
-     * @param handlers   user signal handlers
-     * @return true if support on current platform
-     */
-    public static boolean addSignal(final String signalName, final List<JRaftSignalHandler> handlers) {
-        if (SIGNAL_ACCESSOR != null) {
-            SIGNAL_ACCESSOR.addSignal(signalName, handlers);
-            return true;
-        }
-        return false;
-    }
-
-    private static SignalAccessor getSignalAccessor0() {
-        return hasSignal0() ? new SignalAccessor() : null;
-    }
-
-    private static boolean hasSignal0() {
-        try {
-            Class.forName("sun.misc.Signal");
-            return true;
-        } catch (final Throwable t) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("sun.misc.Signal: unavailable, {}.", t);
-            }
-        }
-        return false;
-    }
-
-    private SignalHelper() {
-    }
-
-    static class SignalAccessor {
-
-        public void addSignal(final String signalName, final List<JRaftSignalHandler> handlers) {
-            final sun.misc.Signal signal = new sun.misc.Signal(signalName);
-            final SignalHandlerAdapter adapter = new SignalHandlerAdapter(signal, handlers);
-            sun.misc.Signal.handle(signal, adapter);
-        }
-    }
-
-    static class SignalHandlerAdapter implements sun.misc.SignalHandler {
-
-        private final sun.misc.Signal          target;
-        private final List<JRaftSignalHandler> handlers;
-
-        public static void addSignal(final SignalHandlerAdapter adapter) {
-            sun.misc.Signal.handle(adapter.target, adapter);
-        }
-
-        public SignalHandlerAdapter(sun.misc.Signal target, List<JRaftSignalHandler> handlers) {
-            this.target = target;
-            this.handlers = handlers;
-        }
-
-        @Override
-        public void handle(final sun.misc.Signal signal) {
-            try {
-                if (!this.target.equals(signal)) {
-                    return;
-                }
-
-                LOG.info("Handling signal {}.", signal);
-
-                for (final JRaftSignalHandler h : this.handlers) {
-                    h.handle(signal.getName());
-                }
-            } catch (final Throwable t) {
-                LOG.error("Fail to handle signal: {}.", signal, t);
-            }
-        }
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/StorageOptionsFactory.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/StorageOptionsFactory.java
deleted file mode 100644
index f137a30..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/StorageOptionsFactory.java
+++ /dev/null
@@ -1,350 +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.util;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.rocksdb.BlockBasedTableConfig;
-import org.rocksdb.BloomFilter;
-import org.rocksdb.ColumnFamilyOptions;
-import org.rocksdb.CompactionStyle;
-import org.rocksdb.CompressionType;
-import org.rocksdb.DBOptions;
-import org.rocksdb.IndexType;
-import org.rocksdb.RocksDB;
-import org.rocksdb.RocksObject;
-import org.rocksdb.util.SizeUnit;
-
-/**
- *
- * @author jiachun.fjc
- */
-public final class StorageOptionsFactory {
-
-    static {
-        RocksDB.loadLibrary();
-    }
-
-    private static final Map<String, DBOptions>             rocksDBOptionsTable      = new ConcurrentHashMap<>();
-    private static final Map<String, ColumnFamilyOptions>   columnFamilyOptionsTable = new ConcurrentHashMap<>();
-    private static final Map<String, BlockBasedTableConfig> tableFormatConfigTable   = 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 ColumnFamilyOptions, 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();
-        }
-
-        // https://github.com/facebook/rocksdb/pull/5744
-        opts.setForceConsistencyChecks(true);
-
-        return opts;
-    }
-
-    /**
-     * Users can register a custom rocksdb BlockBasedTableConfig, then the related
-     * classes will get their options by the key of their own class name.  If
-     * the user does not register a config, a default config will be provided.
-     *
-     * @param cls the key of BlockBasedTableConfig
-     * @param cfg the BlockBasedTableConfig
-     */
-    public static void registerRocksDBTableFormatConfig(final Class<?> cls, final BlockBasedTableConfig cfg) {
-        Requires.requireNonNull(cls, "cls");
-        Requires.requireNonNull(cfg, "cfg");
-        if (tableFormatConfigTable.putIfAbsent(cls.getName(), cfg) != null) {
-            throw new IllegalStateException("TableFormatConfig with class key [" + cls.getName()
-                                            + "] has already been registered");
-        }
-    }
-
-    /**
-     * Get a new default TableFormatConfig or a copy of the exist ableFormatConfig.
-     *
-     * @param cls the key of TableFormatConfig
-     * @return new default TableFormatConfig or a copy of the exist TableFormatConfig
-     */
-    public static BlockBasedTableConfig getRocksDBTableFormatConfig(final Class<?> cls) {
-        Requires.requireNonNull(cls, "cls");
-        BlockBasedTableConfig cfg = tableFormatConfigTable.get(cls.getName());
-        if (cfg == null) {
-            final BlockBasedTableConfig newCfg = getDefaultRocksDBTableConfig();
-            cfg = tableFormatConfigTable.putIfAbsent(cls.getName(), newCfg);
-            if (cfg == null) {
-                cfg = newCfg;
-            }
-        }
-        return copyTableFormatConfig(cfg);
-    }
-
-    public static BlockBasedTableConfig getDefaultRocksDBTableConfig() {
-        // See https://github.com/sofastack/sofa-jraft/pull/156
-        return new BlockBasedTableConfig() //
-            // Begin to use partitioned index filters
-            // https://github.com/facebook/rocksdb/wiki/Partitioned-Index-Filters#how-to-use-it
-            .setIndexType(IndexType.kTwoLevelIndexSearch) //
-            .setFilter(new BloomFilter(16, false)) //
-            .setPartitionFilters(true) //
-            .setMetadataBlockSize(8 * SizeUnit.KB) //
-            .setCacheIndexAndFilterBlocks(false) //
-            .setCacheIndexAndFilterBlocksWithHighPriority(true) //
-            .setPinL0FilterAndIndexBlocksInCache(true) //
-            // End of partitioned index filters settings.
-            .setBlockSize(4 * SizeUnit.KB)//
-            .setBlockCacheSize(512 * SizeUnit.MB) //
-            .setCacheNumShardBits(8);
-    }
-
-    private static BlockBasedTableConfig copyTableFormatConfig(final BlockBasedTableConfig cfg) {
-        return new BlockBasedTableConfig() //
-            .setNoBlockCache(cfg.noBlockCache()) //
-            .setBlockCacheSize(cfg.blockCacheSize()) //
-            .setCacheNumShardBits(cfg.cacheNumShardBits()) //
-            .setBlockSize(cfg.blockSize()) //
-            .setBlockSizeDeviation(cfg.blockSizeDeviation()) //
-            .setBlockRestartInterval(cfg.blockRestartInterval()) //
-            .setWholeKeyFiltering(cfg.wholeKeyFiltering()) //
-            .setCacheIndexAndFilterBlocks(cfg.cacheIndexAndFilterBlocks()) //
-            .setCacheIndexAndFilterBlocksWithHighPriority(cfg.cacheIndexAndFilterBlocksWithHighPriority()) //
-            .setPinL0FilterAndIndexBlocksInCache(cfg.pinL0FilterAndIndexBlocksInCache()) //
-            .setPartitionFilters(cfg.partitionFilters()) //
-            .setMetadataBlockSize(cfg.metadataBlockSize()) //
-            .setPinTopLevelIndexAndFilter(cfg.pinTopLevelIndexAndFilter()) //
-            .setHashIndexAllowCollision(cfg.hashIndexAllowCollision()) //
-            .setBlockCacheCompressedSize(cfg.blockCacheCompressedSize()) //
-            .setBlockCacheCompressedNumShardBits(cfg.blockCacheCompressedNumShardBits()) //
-            .setChecksumType(cfg.checksumType()) //
-            .setIndexType(cfg.indexType()) //
-            .setFormatVersion(cfg.formatVersion());
-    }
-
-    private static <T extends RocksObject> 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/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/ThrowUtil.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/ThrowUtil.java
index c179a89..71b1829 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/ThrowUtil.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/ThrowUtil.java
@@ -30,11 +30,13 @@ public final class ThrowUtil {
      * Raises an exception bypassing compiler checks for checked exceptions.
      */
     public static void throwException(final Throwable t) {
-        if (UnsafeUtil.hasUnsafe()) {
-            UnsafeUtil.throwException(t);
-        } else {
-            ThrowUtil.throwException0(t);
-        }
+//        if (UnsafeUtil.hasUnsafe()) {
+//            UnsafeUtil.throwException(t);
+//        } else {
+//            ThrowUtil.throwException0(t);
+//        }
+
+        ThrowUtil.throwException0(t);
     }
 
     /**
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeIntegerFieldUpdater.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeIntegerFieldUpdater.java
deleted file mode 100644
index 4bc081c..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeIntegerFieldUpdater.java
+++ /dev/null
@@ -1,49 +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.util.internal;
-
-import java.lang.reflect.Field;
-import sun.misc.Unsafe;
-
-/**
- *
- * @author jiachun.fjc
- */
-final class UnsafeIntegerFieldUpdater<U> implements IntegerFieldUpdater<U> {
-
-    private final long   offset;
-    private final Unsafe unsafe;
-
-    UnsafeIntegerFieldUpdater(Unsafe unsafe, Class<? super U> tClass, String fieldName) throws NoSuchFieldException {
-        final Field field = tClass.getDeclaredField(fieldName);
-        if (unsafe == null) {
-            throw new NullPointerException("unsafe");
-        }
-        this.unsafe = unsafe;
-        this.offset = unsafe.objectFieldOffset(field);
-    }
-
-    @Override
-    public void set(final U obj, final int newValue) {
-        this.unsafe.putInt(obj, this.offset, newValue);
-    }
-
-    @Override
-    public int get(final U obj) {
-        return this.unsafe.getInt(obj, this.offset);
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeReferenceFieldUpdater.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeReferenceFieldUpdater.java
deleted file mode 100644
index 9c32c56..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeReferenceFieldUpdater.java
+++ /dev/null
@@ -1,50 +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.util.internal;
-
-import java.lang.reflect.Field;
-import sun.misc.Unsafe;
-
-/**
- *
- * @author jiachun.fjc
- */
-@SuppressWarnings("unchecked")
-final class UnsafeReferenceFieldUpdater<U, W> implements ReferenceFieldUpdater<U, W> {
-
-    private final long   offset;
-    private final Unsafe unsafe;
-
-    UnsafeReferenceFieldUpdater(Unsafe unsafe, Class<? super U> tClass, String fieldName) throws NoSuchFieldException {
-        final Field field = tClass.getDeclaredField(fieldName);
-        if (unsafe == null) {
-            throw new NullPointerException("unsafe");
-        }
-        this.unsafe = unsafe;
-        this.offset = unsafe.objectFieldOffset(field);
-    }
-
-    @Override
-    public void set(final U obj, final W newValue) {
-        this.unsafe.putObject(obj, this.offset, newValue);
-    }
-
-    @Override
-    public W get(final U obj) {
-        return (W) this.unsafe.getObject(obj, this.offset);
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeUtf8Util.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeUtf8Util.java
deleted file mode 100644
index d3706ea..0000000
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeUtf8Util.java
+++ /dev/null
@@ -1,481 +0,0 @@
-// Protocol Buffers - Google's data interchange format
-// Copyright 2008 Google Inc.  All rights reserved.
-// https://developers.google.com/protocol-buffers/
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//     * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package com.alipay.sofa.jraft.util.internal;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-import static java.lang.Character.MAX_SURROGATE;
-import static java.lang.Character.MIN_HIGH_SURROGATE;
-import static java.lang.Character.MIN_LOW_SURROGATE;
-import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
-import static java.lang.Character.MIN_SURROGATE;
-import static java.lang.Character.isSurrogatePair;
-import static java.lang.Character.toCodePoint;
-
-/**
- *
- * Refer to the implementation of protobuf: <A>https://github.com/protocolbuffers/protobuf/blob/master/java/core/src/main/java/com/google/protobuf/Utf8.java<A/>.
- */
-public final class UnsafeUtf8Util {
-
-    /**
-     * Maximum number of bytes per Java UTF-16 char in UTF-8.
-     *
-     * @see java.nio.charset.CharsetEncoder#maxBytesPerChar()
-     */
-    public static final int MAX_BYTES_PER_CHAR = 3;
-
-    public static String decodeUtf8(byte[] bytes, int index, int size) {
-        if ((index | size | bytes.length - index - size) < 0) {
-            throw new ArrayIndexOutOfBoundsException("buffer length=" + bytes.length + ", index=" + index + ", size="
-                                                     + size);
-        }
-
-        int offset = index;
-        final int limit = offset + size;
-
-        // The longest possible resulting String is the same as the number of input bytes, when it is
-        // all ASCII. For other cases, this over-allocates and we will truncate in the end.
-        char[] resultArr = new char[size];
-        int resultPos = 0;
-
-        // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this).
-        // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII).
-        while (offset < limit) {
-            byte b = UnsafeUtil.getByte(bytes, offset);
-            if (!DecodeUtil.isOneByte(b)) {
-                break;
-            }
-            offset++;
-            DecodeUtil.handleOneByte(b, resultArr, resultPos++);
-        }
-
-        while (offset < limit) {
-            byte byte1 = UnsafeUtil.getByte(bytes, offset++);
-            if (DecodeUtil.isOneByte(byte1)) {
-                DecodeUtil.handleOneByte(byte1, resultArr, resultPos++);
-                // It's common for there to be multiple ASCII characters in a run mixed in, so add an
-                // extra optimized loop to take care of these runs.
-                while (offset < limit) {
-                    byte b = UnsafeUtil.getByte(bytes, offset);
-                    if (!DecodeUtil.isOneByte(b)) {
-                        break;
-                    }
-                    offset++;
-                    DecodeUtil.handleOneByte(b, resultArr, resultPos++);
-                }
-            } else if (DecodeUtil.isTwoBytes(byte1)) {
-                if (offset >= limit) {
-                    throw invalidUtf8();
-                }
-                DecodeUtil.handleTwoBytes(byte1, /* byte2 */UnsafeUtil.getByte(bytes, offset++), resultArr,
-                    resultPos++);
-            } else if (DecodeUtil.isThreeBytes(byte1)) {
-                if (offset >= limit - 1) {
-                    throw invalidUtf8();
-                }
-                DecodeUtil.handleThreeBytes(byte1,
-                /* byte2 */UnsafeUtil.getByte(bytes, offset++),
-                /* byte3 */UnsafeUtil.getByte(bytes, offset++), resultArr, resultPos++);
-            } else {
-                if (offset >= limit - 2) {
-                    throw invalidUtf8();
-                }
-                DecodeUtil.handleFourBytes(byte1,
-                /* byte2 */UnsafeUtil.getByte(bytes, offset++),
-                /* byte3 */UnsafeUtil.getByte(bytes, offset++),
-                /* byte4 */UnsafeUtil.getByte(bytes, offset++), resultArr, resultPos++);
-                // 4-byte case requires two chars.
-                resultPos++;
-            }
-        }
-
-        if (resultPos < resultArr.length) {
-            resultArr = Arrays.copyOf(resultArr, resultPos);
-        }
-        return UnsafeUtil.moveToString(resultArr);
-    }
-
-    public static String decodeUtf8Direct(ByteBuffer buffer, int index, int size) {
-        // Bitwise OR combines the sign bits so any negative value fails the check.
-        if ((index | size | buffer.limit() - index - size) < 0) {
-            throw new ArrayIndexOutOfBoundsException("buffer limit=" + buffer.limit() + ", index=" + index + ", limit="
-                                                     + size);
-        }
-        long address = UnsafeUtil.addressOffset(buffer) + index;
-        final long addressLimit = address + size;
-
-        // The longest possible resulting String is the same as the number of input bytes, when it is
-        // all ASCII. For other cases, this over-allocates and we will truncate in the end.
-        char[] resultArr = new char[size];
-        int resultPos = 0;
-
-        // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this).
-        // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII).
-        while (address < addressLimit) {
-            byte b = UnsafeUtil.getByte(address);
-            if (!DecodeUtil.isOneByte(b)) {
-                break;
-            }
-            address++;
-            DecodeUtil.handleOneByte(b, resultArr, resultPos++);
-        }
-
-        while (address < addressLimit) {
-            byte byte1 = UnsafeUtil.getByte(address++);
-            if (DecodeUtil.isOneByte(byte1)) {
-                DecodeUtil.handleOneByte(byte1, resultArr, resultPos++);
-                // It's common for there to be multiple ASCII characters in a run mixed in, so add an
-                // extra optimized loop to take care of these runs.
-                while (address < addressLimit) {
-                    byte b = UnsafeUtil.getByte(address);
-                    if (!DecodeUtil.isOneByte(b)) {
-                        break;
-                    }
-                    address++;
-                    DecodeUtil.handleOneByte(b, resultArr, resultPos++);
-                }
-            } else if (DecodeUtil.isTwoBytes(byte1)) {
-                if (address >= addressLimit) {
-                    throw invalidUtf8();
-                }
-                DecodeUtil.handleTwoBytes(byte1, /* byte2 */UnsafeUtil.getByte(address++), resultArr, resultPos++);
-            } else if (DecodeUtil.isThreeBytes(byte1)) {
-                if (address >= addressLimit - 1) {
-                    throw invalidUtf8();
-                }
-                DecodeUtil.handleThreeBytes(byte1,
-                /* byte2 */UnsafeUtil.getByte(address++),
-                /* byte3 */UnsafeUtil.getByte(address++), resultArr, resultPos++);
-            } else {
-                if (address >= addressLimit - 2) {
-                    throw invalidUtf8();
-                }
-                DecodeUtil.handleFourBytes(byte1,
-                /* byte2 */UnsafeUtil.getByte(address++),
-                /* byte3 */UnsafeUtil.getByte(address++),
-                /* byte4 */UnsafeUtil.getByte(address++), resultArr, resultPos++);
-                // 4-byte case requires two chars.
-                resultPos++;
-            }
-        }
-
-        if (resultPos < resultArr.length) {
-            resultArr = Arrays.copyOf(resultArr, resultPos);
-        }
-        return UnsafeUtil.moveToString(resultArr);
-    }
-
-    public static int encodeUtf8(CharSequence in, byte[] out, int offset, int length) {
-        long outIx = offset;
-        final long outLimit = outIx + length;
-        final int inLimit = in.length();
-        if (inLimit > length || out.length - length < offset) {
-            // Not even enough room for an ASCII-encoded string.
-            throw new ArrayIndexOutOfBoundsException("Failed writing " + in.charAt(inLimit - 1) + " at index "
-                                                     + (offset + length));
-        }
-
-        // Designed to take advantage of
-        // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
-        int inIx = 0;
-        for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) {
-            UnsafeUtil.putByte(out, outIx++, (byte) c);
-        }
-        if (inIx == inLimit) {
-            // We're done, it was ASCII encoded.
-            return (int) outIx;
-        }
-
-        for (char c; inIx < inLimit; ++inIx) {
-            c = in.charAt(inIx);
-            if (c < 0x80 && outIx < outLimit) {
-                UnsafeUtil.putByte(out, outIx++, (byte) c);
-            } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes
-                UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 6) | (c >>> 6)));
-                UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c)));
-            } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) {
-                // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
-                UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 5) | (c >>> 12)));
-                UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (c >>> 6))));
-                UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c)));
-            } else if (outIx <= outLimit - 4L) {
-                // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8
-                // bytes
-                final char low;
-                if (inIx + 1 == inLimit || !isSurrogatePair(c, (low = in.charAt(++inIx)))) {
-                    throw new IllegalArgumentException("Unpaired surrogate at index " + (inIx - 1) + " of " + inLimit);
-                }
-                int codePoint = toCodePoint(c, low);
-                UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 4) | (codePoint >>> 18)));
-                UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
-                UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
-                UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & codePoint)));
-            } else {
-                if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE)
-                    && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) {
-                    // We are surrogates and we're not a surrogate pair.
-                    throw new IllegalArgumentException("Unpaired surrogate at index " + inIx + " of " + inLimit);
-                }
-                // Not enough space in the output buffer.
-                throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + outIx);
-            }
-        }
-
-        // All bytes have been encoded.
-        return (int) outIx;
-    }
-
-    public static void encodeUtf8Direct(CharSequence in, ByteBuffer out) {
-        final long address = UnsafeUtil.addressOffset(out);
-        long outIx = address + out.position();
-        final long outLimit = address + out.limit();
-        final int inLimit = in.length();
-        if (inLimit > outLimit - outIx) {
-            // Not even enough room for an ASCII-encoded string.
-            throw new ArrayIndexOutOfBoundsException("Failed writing " + in.charAt(inLimit - 1) + " at index "
-                                                     + out.limit());
-        }
-
-        // Designed to take advantage of
-        // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
-        int inIx = 0;
-        for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) {
-            UnsafeUtil.putByte(outIx++, (byte) c);
-        }
-        if (inIx == inLimit) {
-            // We're done, it was ASCII encoded.
-            out.position((int) (outIx - address));
-            return;
-        }
-
-        for (char c; inIx < inLimit; ++inIx) {
-            c = in.charAt(inIx);
-            if (c < 0x80 && outIx < outLimit) {
-                UnsafeUtil.putByte(outIx++, (byte) c);
-            } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes
-                UnsafeUtil.putByte(outIx++, (byte) ((0xF << 6) | (c >>> 6)));
-                UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & c)));
-            } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) {
-                // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
-                UnsafeUtil.putByte(outIx++, (byte) ((0xF << 5) | (c >>> 12)));
-                UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (c >>> 6))));
-                UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & c)));
-            } else if (outIx <= outLimit - 4L) {
-                // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8
-                // bytes
-                final char low;
-                if (inIx + 1 == inLimit || !isSurrogatePair(c, (low = in.charAt(++inIx)))) {
-                    throw new IllegalArgumentException("Unpaired surrogate at index " + (inIx - 1) + " of " + inLimit);
-                }
-                int codePoint = toCodePoint(c, low);
-                UnsafeUtil.putByte(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18)));
-                UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
-                UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
-                UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & codePoint)));
-            } else {
-                if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE)
-                    && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) {
-                    // We are surrogates and we're not a surrogate pair.
-                    throw new IllegalArgumentException("Unpaired surrogate at index " + inIx + " of " + inLimit);
-                }
-                // Not enough space in the output buffer.
-                throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + outIx);
-            }
-        }
-
-        // All bytes have been encoded.
-        out.position((int) (outIx - address));
-    }
-
-    /**
-     * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string,
-     * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in
-     * both time and space.
-     *
-     * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired
-     *                                  surrogates)
-     */
-    public static int encodedLength(CharSequence sequence) {
-        // Warning to maintainers: this implementation is highly optimized.
-        int utf16Length = sequence.length();
-        int utf8Length = utf16Length;
-        int i = 0;
-
-        // This loop optimizes for pure ASCII.
-        while (i < utf16Length && sequence.charAt(i) < 0x80) {
-            i++;
-        }
-
-        // This loop optimizes for chars less than 0x800.
-        for (; i < utf16Length; i++) {
-            char c = sequence.charAt(i);
-            if (c < 0x800) {
-                utf8Length += ((0x7f - c) >>> 31); // branch free!
-            } else {
-                utf8Length += encodedLengthGeneral(sequence, i);
-                break;
-            }
-        }
-
-        if (utf8Length < utf16Length) {
-            // Necessary and sufficient condition for overflow because of maximum 3x expansion
-            throw new IllegalArgumentException("UTF-8 length does not fit in int: " + (utf8Length + (1L << 32)));
-        }
-        return utf8Length;
-    }
-
-    private static int encodedLengthGeneral(CharSequence sequence, int start) {
-        int utf16Length = sequence.length();
-        int utf8Length = 0;
-        for (int i = start; i < utf16Length; i++) {
-            char c = sequence.charAt(i);
-            if (c < 0x800) {
-                utf8Length += (0x7f - c) >>> 31; // branch free!
-            } else {
-                utf8Length += 2;
-                // jdk7+: if (Character.isSurrogate(c)) {
-                if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) {
-                    // Check that we have a well-formed surrogate pair.
-                    int cp = Character.codePointAt(sequence, i);
-                    if (cp < MIN_SUPPLEMENTARY_CODE_POINT) {
-                        throw new IllegalArgumentException("Unpaired surrogate at index " + i + " of " + utf16Length);
-                    }
-                    i++;
-                }
-            }
-        }
-        return utf8Length;
-    }
-
-    /**
-     * Utility methods for decoding bytes into {@link String}. Callers are responsible for extracting
-     * bytes (possibly using Unsafe methods), and checking remaining bytes. All other UTF-8 validity
-     * checks and codepoint conversion happen in this class.
-     */
-    private static class DecodeUtil {
-
-        /**
-         * Returns whether this is a single-byte codepoint (i.e., ASCII) with the form '0XXXXXXX'.
-         */
-        private static boolean isOneByte(byte b) {
-            return b >= 0;
-        }
-
-        /**
-         * Returns whether this is a two-byte codepoint with the form '10XXXXXX'.
-         */
-        private static boolean isTwoBytes(byte b) {
-            return b < (byte) 0xE0;
-        }
-
-        /**
-         * Returns whether this is a three-byte codepoint with the form '110XXXXX'.
-         */
-        private static boolean isThreeBytes(byte b) {
-            return b < (byte) 0xF0;
-        }
-
-        private static void handleOneByte(byte byte1, char[] resultArr, int resultPos) {
-            resultArr[resultPos] = (char) byte1;
-        }
-
-        private static void handleTwoBytes(byte byte1, byte byte2, char[] resultArr, int resultPos) {
-            // Simultaneously checks for illegal trailing-byte in leading position (<= '11000000') and
-            // overlong 2-byte, '11000001'.
-            if (byte1 < (byte) 0xC2 || isNotTrailingByte(byte2)) {
-                throw invalidUtf8();
-            }
-            resultArr[resultPos] = (char) (((byte1 & 0x1F) << 6) | trailingByteValue(byte2));
-        }
-
-        private static void handleThreeBytes(byte byte1, byte byte2, byte byte3, char[] resultArr, int resultPos) {
-            if (isNotTrailingByte(byte2)
-            // overlong? 5 most significant bits must not all be zero
-                || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0)
-                // check for illegal surrogate codepoints
-                || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) || isNotTrailingByte(byte3)) {
-                throw invalidUtf8();
-            }
-            resultArr[resultPos] = (char) (((byte1 & 0x0F) << 12) | (trailingByteValue(byte2) << 6) | trailingByteValue(byte3));
-        }
-
-        private static void handleFourBytes(byte byte1, byte byte2, byte byte3, byte byte4, char[] resultArr,
-                                            int resultPos) {
-            if (isNotTrailingByte(byte2)
-                // Check that 1 <= plane <= 16.  Tricky optimized form of:
-                //   valid 4-byte leading byte?
-                // if (byte1 > (byte) 0xF4 ||
-                //   overlong? 4 most significant bits must not all be zero
-                //     byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 ||
-                //   codepoint larger than the highest code point (U+10FFFF)?
-                //     byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F)
-                || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 || isNotTrailingByte(byte3)
-                || isNotTrailingByte(byte4)) {
-                throw invalidUtf8();
-            }
-            int codePoint = ((byte1 & 0x07) << 18) | (trailingByteValue(byte2) << 12) | (trailingByteValue(byte3) << 6)
-                            | trailingByteValue(byte4);
-            resultArr[resultPos] = DecodeUtil.highSurrogate(codePoint);
-            resultArr[resultPos + 1] = DecodeUtil.lowSurrogate(codePoint);
-        }
-
-        /**
-         * Returns whether the byte is not a valid continuation of the form '10XXXXXX'.
-         */
-        private static boolean isNotTrailingByte(byte b) {
-            return b > (byte) 0xBF;
-        }
-
-        /**
-         * Returns the actual value of the trailing byte (removes the prefix '10') for composition.
-         */
-        private static int trailingByteValue(byte b) {
-            return b & 0x3F;
-        }
-
-        private static char highSurrogate(int codePoint) {
-            return (char) ((MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >>> 10)) + (codePoint >>> 10));
-        }
-
-        private static char lowSurrogate(int codePoint) {
-            return (char) (MIN_LOW_SURROGATE + (codePoint & 0x3ff));
-        }
-    }
-
-    static IllegalStateException invalidUtf8() {
-        return new IllegalStateException("Message had invalid UTF-8.");
-    }
-
-    private UnsafeUtf8Util() {
-    }
-}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeUtil.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeUtil.java
index 02440f7..836d5f3 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeUtil.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/UnsafeUtil.java
@@ -30,600 +30,600 @@ import org.slf4j.LoggerFactory;
  *
  * @author jiachun.fjc
  */
-@SuppressWarnings("ConstantConditions")
-public final class UnsafeUtil {
-
-    private static final Logger         LOG                       = LoggerFactory.getLogger(UnsafeUtil.class);
-
-    private static final Object         UNSAFE                    = getUnsafe0();
-
-    private static final UnsafeAccessor UNSAFE_ACCESSOR           = getUnsafeAccessor0();
-
-    private static final long           BYTE_ARRAY_BASE_OFFSET    = arrayBaseOffset(byte[].class);
-    // Micro-optimization: we can assume a scale of 1 and skip the multiply
-    // private static final long BYTE_ARRAY_INDEX_SCALE = 1;
-
-    private static final long           BOOLEAN_ARRAY_BASE_OFFSET = arrayBaseOffset(boolean[].class);
-    private static final long           BOOLEAN_ARRAY_INDEX_SCALE = arrayIndexScale(boolean[].class);
-
-    private static final long           INT_ARRAY_BASE_OFFSET     = arrayBaseOffset(int[].class);
-    private static final long           INT_ARRAY_INDEX_SCALE     = arrayIndexScale(int[].class);
-
-    private static final long           LONG_ARRAY_BASE_OFFSET    = arrayBaseOffset(long[].class);
-    private static final long           LONG_ARRAY_INDEX_SCALE    = arrayIndexScale(long[].class);
-
-    private static final long           FLOAT_ARRAY_BASE_OFFSET   = arrayBaseOffset(float[].class);
-    private static final long           FLOAT_ARRAY_INDEX_SCALE   = arrayIndexScale(float[].class);
-
-    private static final long           DOUBLE_ARRAY_BASE_OFFSET  = arrayBaseOffset(double[].class);
-    private static final long           DOUBLE_ARRAY_INDEX_SCALE  = arrayIndexScale(double[].class);
-
-    private static final long           OBJECT_ARRAY_BASE_OFFSET  = arrayBaseOffset(Object[].class);
-    private static final long           OBJECT_ARRAY_INDEX_SCALE  = arrayIndexScale(Object[].class);
-
-    private static final long           BUFFER_ADDRESS_OFFSET     = objectFieldOffset(bufferAddressField());
-
-    private static final long           STRING_VALUE_OFFSET       = objectFieldOffset(stringValueField());
-
-    /**
-     * Whether or not can use the unsafe api.
-     */
-    public static boolean hasUnsafe() {
-        return UNSAFE != null;
-    }
-
-    /**
-     * Get a {@link UnsafeAccessor} appropriate for the platform.
-     */
-    public static UnsafeAccessor getUnsafeAccessor() {
-        return UNSAFE_ACCESSOR;
-    }
-
-    public static byte getByte(final Object target, final long offset) {
-        return UNSAFE_ACCESSOR.getByte(target, offset);
-    }
-
-    public static void putByte(final Object target, final long offset, final byte value) {
-        UNSAFE_ACCESSOR.putByte(target, offset, value);
-    }
-
-    public static int getInt(final Object target, final long offset) {
-        return UNSAFE_ACCESSOR.getInt(target, offset);
-    }
-
-    public static void putInt(final Object target, final long offset, final int value) {
-        UNSAFE_ACCESSOR.putInt(target, offset, value);
-    }
-
-    public static long getLong(final Object target, final long offset) {
-        return UNSAFE_ACCESSOR.getLong(target, offset);
-    }
-
-    public static void putLong(final Object target, final long offset, final long value) {
-        UNSAFE_ACCESSOR.putLong(target, offset, value);
-    }
-
-    public static boolean getBoolean(final Object target, final long offset) {
-        return UNSAFE_ACCESSOR.getBoolean(target, offset);
-    }
-
-    public static void putBoolean(final Object target, final long offset, final boolean value) {
-        UNSAFE_ACCESSOR.putBoolean(target, offset, value);
-    }
-
-    public static float getFloat(final Object target, final long offset) {
-        return UNSAFE_ACCESSOR.getFloat(target, offset);
-    }
-
-    public static void putFloat(final Object target, final long offset, final float value) {
-        UNSAFE_ACCESSOR.putFloat(target, offset, value);
-    }
-
-    public static double getDouble(final Object target, final long offset) {
-        return UNSAFE_ACCESSOR.getDouble(target, offset);
-    }
-
-    public static void putDouble(final Object target, final long offset, final double value) {
-        UNSAFE_ACCESSOR.putDouble(target, offset, value);
-    }
-
-    public static Object getObject(final Object target, final long offset) {
-        return UNSAFE_ACCESSOR.getObject(target, offset);
-    }
-
-    public static void putObject(final Object target, final long offset, final Object value) {
-        UNSAFE_ACCESSOR.putObject(target, offset, value);
-    }
-
-    public static byte getByte(final byte[] target, final long index) {
-        return UNSAFE_ACCESSOR.getByte(target, BYTE_ARRAY_BASE_OFFSET + index);
-    }
-
-    public static void putByte(final byte[] target, final long index, final byte value) {
-        UNSAFE_ACCESSOR.putByte(target, BYTE_ARRAY_BASE_OFFSET + index, value);
-    }
-
-    public static int getInt(final int[] target, final long index) {
-        return UNSAFE_ACCESSOR.getInt(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putInt(final int[] target, final long index, final int value) {
-        UNSAFE_ACCESSOR.putInt(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static long getLong(final long[] target, final long index) {
-        return UNSAFE_ACCESSOR.getLong(target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putLong(final long[] target, final long index, final long value) {
-        UNSAFE_ACCESSOR.putLong(target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static boolean getBoolean(final boolean[] target, final long index) {
-        return UNSAFE_ACCESSOR.getBoolean(target, BOOLEAN_ARRAY_BASE_OFFSET + (index * BOOLEAN_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putBoolean(final boolean[] target, final long index, final boolean value) {
-        UNSAFE_ACCESSOR.putBoolean(target, BOOLEAN_ARRAY_BASE_OFFSET + (index * BOOLEAN_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static float getFloat(final float[] target, final long index) {
-        return UNSAFE_ACCESSOR.getFloat(target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putFloat(final float[] target, final long index, final float value) {
-        UNSAFE_ACCESSOR.putFloat(target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static double getDouble(final double[] target, final long index) {
-        return UNSAFE_ACCESSOR.getDouble(target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putDouble(final double[] target, final long index, final double value) {
-        UNSAFE_ACCESSOR.putDouble(target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static Object getObject(final Object[] target, final long index) {
-        return UNSAFE_ACCESSOR.getObject(target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putObject(final Object[] target, final long index, final Object value) {
-        UNSAFE_ACCESSOR.putObject(target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static byte getByte(final long address) {
-        return UNSAFE_ACCESSOR.getByte(address);
-    }
-
-    public static void putByte(final long address, final byte value) {
-        UNSAFE_ACCESSOR.putByte(address, value);
-    }
-
-    public static int getInt(final long address) {
-        return UNSAFE_ACCESSOR.getInt(address);
-    }
-
-    public static void putInt(final long address, final int value) {
-        UNSAFE_ACCESSOR.putInt(address, value);
-    }
-
-    public static long getLong(final long address) {
-        return UNSAFE_ACCESSOR.getLong(address);
-    }
-
-    public static void putLong(final long address, final long value) {
-        UNSAFE_ACCESSOR.putLong(address, value);
-    }
-
-    public static byte getByteVolatile(final byte[] target, final long index) {
-        return UNSAFE_ACCESSOR.getByteVolatile(target, BYTE_ARRAY_BASE_OFFSET + index);
-    }
-
-    public static void putByteVolatile(final byte[] target, final long index, final byte value) {
-        UNSAFE_ACCESSOR.putByteVolatile(target, BYTE_ARRAY_BASE_OFFSET + index, value);
-    }
-
-    public static int getIntVolatile(final int[] target, final long index) {
-        return UNSAFE_ACCESSOR.getIntVolatile(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putIntVolatile(final int[] target, final long index, final int value) {
-        UNSAFE_ACCESSOR.putIntVolatile(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static long getLongVolatile(final long[] target, final long index) {
-        return UNSAFE_ACCESSOR.getLongVolatile(target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putLongVolatile(final long[] target, final long index, final long value) {
-        UNSAFE_ACCESSOR.putLongVolatile(target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static boolean getBooleanVolatile(final boolean[] target, final long index) {
-        return UNSAFE_ACCESSOR.getBooleanVolatile(target, BOOLEAN_ARRAY_BASE_OFFSET
-                                                          + (index * BOOLEAN_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putBooleanVolatile(final boolean[] target, final long index, final boolean value) {
-        UNSAFE_ACCESSOR.putBooleanVolatile(target, BOOLEAN_ARRAY_BASE_OFFSET + (index * BOOLEAN_ARRAY_INDEX_SCALE),
-            value);
-    }
-
-    public static float getFloatVolatile(final float[] target, final long index) {
-        return UNSAFE_ACCESSOR.getFloatVolatile(target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putFloatVolatile(final float[] target, final long index, final float value) {
-        UNSAFE_ACCESSOR.putFloatVolatile(target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static double getDoubleVolatile(final double[] target, final long index) {
-        return UNSAFE_ACCESSOR.getDoubleVolatile(target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putDoubleVolatile(final double[] target, final long index, final double value) {
-        UNSAFE_ACCESSOR.putDoubleVolatile(target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE), value);
-    }
-
-    public static Object getObjectVolatile(final Object[] target, final long index) {
-        return UNSAFE_ACCESSOR.getObjectVolatile(target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE));
-    }
-
-    public static void putObjectVolatile(final Object[] target, final long index, final Object value) {
-        UNSAFE_ACCESSOR.putObjectVolatile(target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE), value);
-    }
-
-    /**
-     * Reports the offset of the first element in the storage allocation of a
-     * given array class.
-     */
-    public static int arrayBaseOffset(final Class<?> clazz) {
-        return hasUnsafe() ? UNSAFE_ACCESSOR.arrayBaseOffset(clazz) : -1;
-    }
-
-    /**
-     * Reports the scale factor for addressing elements in the storage
-     * allocation of a given array class.
-     */
-    public static int arrayIndexScale(final Class<?> clazz) {
-        return hasUnsafe() ? UNSAFE_ACCESSOR.arrayIndexScale(clazz) : -1;
-    }
-
-    /**
-     * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not
-     * available.
-     */
-    public static long objectFieldOffset(final Field field) {
-        return field == null || hasUnsafe() ? UNSAFE_ACCESSOR.objectFieldOffset(field) : -1;
-    }
-
-    /**
-     * Returns the offset of the provided class and fieldName, or {@code -1} if {@code sun.misc.Unsafe} is not
-     * available.
-     */
-    public static long objectFieldOffset(final Class<?> clazz, final String fieldName) {
-        try {
-            return objectFieldOffset(clazz.getDeclaredField(fieldName));
-        } catch (final NoSuchFieldException e) {
-            UNSAFE_ACCESSOR.throwException(e);
-        }
-        return -1; // never get here
-    }
-
-    /**
-     * Gets the offset of the {@code address} field of the given
-     * direct {@link ByteBuffer}.
-     */
-    public static long addressOffset(final ByteBuffer buffer) {
-        return UNSAFE_ACCESSOR.getLong(buffer, BUFFER_ADDRESS_OFFSET);
-    }
-
-    public static void throwException(final Throwable t) {
-        UNSAFE_ACCESSOR.throwException(t);
-    }
-
-    /**
-     * Returns a new {@link String} backed by the given {@code chars}.
-     * The char array should not be mutated any more after calling
-     * this function.
-     */
-    public static String moveToString(final char[] chars) {
-        if (STRING_VALUE_OFFSET == -1) {
-            // In the off-chance that this JDK does not implement String as we'd expect, just do a copy.
-            return new String(chars);
-        }
-        final String str;
-        try {
-            str = (String) UNSAFE_ACCESSOR.allocateInstance(String.class);
-        } catch (final InstantiationException e) {
-            // This should never happen, but return a copy as a fallback just in case.
-            return new String(chars);
-        }
-        UNSAFE_ACCESSOR.putObject(str, STRING_VALUE_OFFSET, chars);
-        return str;
-    }
-
-    /**
-     * Returns the system {@link ClassLoader}.
-     */
-    public static ClassLoader getSystemClassLoader() {
-        if (System.getSecurityManager() == null) {
-            return ClassLoader.getSystemClassLoader();
-        } else {
-            return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) ClassLoader::getSystemClassLoader);
-        }
-    }
-
-    /**
-     * Finds the address field within a direct {@link Buffer}.
-     */
-    private static Field bufferAddressField() {
-        return field(Buffer.class, "address", long.class);
-    }
-
-    /**
-     * Finds the value field within a {@link String}.
-     */
-    private static Field stringValueField() {
-        return field(String.class, "value", char[].class);
-    }
-
-    /**
-     * Gets the field with the given name within the class, or
-     * {@code null} if not found. If found, the field is made accessible.
-     */
-    private static Field field(final Class<?> clazz, final String fieldName, final Class<?> expectedType) {
-        Field field;
-        try {
-            field = clazz.getDeclaredField(fieldName);
-            field.setAccessible(true);
-            if (!field.getType().equals(expectedType)) {
-                return null;
-            }
-        } catch (final Throwable t) {
-            // Failed to access the fields.
-            field = null;
-        }
-        return field;
-    }
-
-    private static UnsafeAccessor getUnsafeAccessor0() {
-        return hasUnsafe() ? new UnsafeAccessor(UNSAFE) : null;
-    }
-
-    private static Object getUnsafe0() {
-        Object unsafe;
-        try {
-            final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
-            final Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
-            unsafeField.setAccessible(true);
-            unsafe = unsafeField.get(null);
-        } catch (final Throwable t) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("sun.misc.Unsafe.theUnsafe: unavailable, {}.", t);
-            }
-            unsafe = null;
-        }
-        return unsafe;
-    }
-
-    public static class UnsafeAccessor {
-
-        private final sun.misc.Unsafe unsafe;
-
-        UnsafeAccessor(Object unsafe) {
-            this.unsafe = (sun.misc.Unsafe) unsafe;
-        }
-
-        /**
-         * Returns the {@link sun.misc.Unsafe}'s instance.
-         */
-        public sun.misc.Unsafe getUnsafe() {
-            return unsafe;
-        }
-
-        public byte getByte(final Object target, final long offset) {
-            return this.unsafe.getByte(target, offset);
-        }
-
-        public void putByte(final Object target, final long offset, final byte value) {
-            this.unsafe.putByte(target, offset, value);
-        }
-
-        public short getShort(final Object target, final long offset) {
-            return this.unsafe.getShort(target, offset);
-        }
-
-        public void putShort(final Object target, final long offset, final short value) {
-            this.unsafe.putShort(target, offset, value);
-        }
-
-        public int getInt(final Object target, final long offset) {
-            return this.unsafe.getInt(target, offset);
-        }
-
-        public void putInt(final Object target, final long offset, final int value) {
-            this.unsafe.putInt(target, offset, value);
-        }
-
-        public long getLong(final Object target, final long offset) {
-            return this.unsafe.getLong(target, offset);
-        }
-
-        public void putLong(final Object target, final long offset, final long value) {
-            this.unsafe.putLong(target, offset, value);
-        }
-
-        public boolean getBoolean(final Object target, final long offset) {
-            return this.unsafe.getBoolean(target, offset);
-        }
-
-        public void putBoolean(final Object target, final long offset, final boolean value) {
-            this.unsafe.putBoolean(target, offset, value);
-        }
-
-        public float getFloat(final Object target, final long offset) {
-            return this.unsafe.getFloat(target, offset);
-        }
-
-        public void putFloat(final Object target, final long offset, final float value) {
-            this.unsafe.putFloat(target, offset, value);
-        }
-
-        public double getDouble(final Object target, final long offset) {
-            return this.unsafe.getDouble(target, offset);
-        }
-
-        public void putDouble(final Object target, final long offset, final double value) {
-            this.unsafe.putDouble(target, offset, value);
-        }
-
-        public Object getObject(final Object target, final long offset) {
-            return this.unsafe.getObject(target, offset);
-        }
-
-        public void putObject(final Object target, final long offset, final Object value) {
-            this.unsafe.putObject(target, offset, value);
-        }
-
-        public byte getByte(final long address) {
-            return this.unsafe.getByte(address);
-        }
-
-        public void putByte(final long address, final byte value) {
-            this.unsafe.putByte(address, value);
-        }
-
-        public short getShort(final long address) {
-            return this.unsafe.getShort(address);
-        }
-
-        public void putShort(final long address, final short value) {
-            this.unsafe.putShort(address, value);
-        }
-
-        public int getInt(final long address) {
-            return this.unsafe.getInt(address);
-        }
-
-        public void putInt(final long address, final int value) {
-            this.unsafe.putInt(address, value);
-        }
-
-        public long getLong(final long address) {
-            return this.unsafe.getLong(address);
-        }
-
-        public void putLong(final long address, final long value) {
-            this.unsafe.putLong(address, value);
-        }
-
-        public void copyMemory(final Object srcBase, final long srcOffset, final Object dstBase, final long dstOffset,
-                               final long bytes) {
-            this.unsafe.copyMemory(srcBase, srcOffset, dstBase, dstOffset, bytes);
-        }
-
-        public void copyMemory(final long srcAddress, final long dstAddress, final long bytes) {
-            this.unsafe.copyMemory(srcAddress, dstAddress, bytes);
-        }
-
-        public byte getByteVolatile(final Object target, final long offset) {
-            return this.unsafe.getByteVolatile(target, offset);
-        }
-
-        public void putByteVolatile(final Object target, final long offset, final byte value) {
-            this.unsafe.putByteVolatile(target, offset, value);
-        }
-
-        public short getShortVolatile(final Object target, final long offset) {
-            return this.unsafe.getShortVolatile(target, offset);
-        }
-
-        public void putShortVolatile(final Object target, final long offset, final short value) {
-            this.unsafe.putShortVolatile(target, offset, value);
-        }
-
-        public int getIntVolatile(final Object target, final long offset) {
-            return this.unsafe.getIntVolatile(target, offset);
-        }
-
-        public void putIntVolatile(final Object target, final long offset, final int value) {
-            this.unsafe.putIntVolatile(target, offset, value);
-        }
-
-        public long getLongVolatile(final Object target, final long offset) {
-            return this.unsafe.getLongVolatile(target, offset);
-        }
-
-        public void putLongVolatile(final Object target, final long offset, final long value) {
-            this.unsafe.putLongVolatile(target, offset, value);
-        }
-
-        public boolean getBooleanVolatile(final Object target, final long offset) {
-            return this.unsafe.getBooleanVolatile(target, offset);
-        }
-
-        public void putBooleanVolatile(final Object target, final long offset, final boolean value) {
-            this.unsafe.putBooleanVolatile(target, offset, value);
-        }
-
-        public float getFloatVolatile(final Object target, final long offset) {
-            return this.unsafe.getFloatVolatile(target, offset);
-        }
-
-        public void putFloatVolatile(final Object target, final long offset, final float value) {
-            this.unsafe.putFloatVolatile(target, offset, value);
-        }
-
-        public double getDoubleVolatile(final Object target, final long offset) {
-            return this.unsafe.getDoubleVolatile(target, offset);
-        }
-
-        public void putDoubleVolatile(final Object target, final long offset, final double value) {
-            this.unsafe.putDoubleVolatile(target, offset, value);
-        }
-
-        public Object getObjectVolatile(final Object target, final long offset) {
-            return this.unsafe.getObjectVolatile(target, offset);
-        }
-
-        public void putObjectVolatile(final Object target, final long offset, final Object value) {
-            this.unsafe.putObjectVolatile(target, offset, value);
-        }
-
-        /**
-         * Reports the offset of the first element in the storage allocation of a
-         * given array class.
-         */
-        public int arrayBaseOffset(final Class<?> clazz) {
-            return this.unsafe != null ? this.unsafe.arrayBaseOffset(clazz) : -1;
-        }
-
-        /**
-         * Reports the scale factor for addressing elements in the storage
-         * allocation of a given array class.
-         */
-        public int arrayIndexScale(final Class<?> clazz) {
-            return this.unsafe != null ? this.unsafe.arrayIndexScale(clazz) : -1;
-        }
-
-        /**
-         * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not
-         * available.
-         */
-        public long objectFieldOffset(final Field field) {
-            return field == null || this.unsafe == null ? -1 : this.unsafe.objectFieldOffset(field);
-        }
-
-        public Object allocateInstance(final Class<?> clazz) throws InstantiationException {
-            return this.unsafe.allocateInstance(clazz);
-        }
-
-        public void throwException(final Throwable t) {
-            this.unsafe.throwException(t);
-        }
-    }
-
-    private UnsafeUtil() {
-    }
-}
+//@SuppressWarnings("ConstantConditions")
+//public final class UnsafeUtil {
+//
+//    private static final Logger         LOG                       = LoggerFactory.getLogger(UnsafeUtil.class);
+//
+//    private static final Object         UNSAFE                    = getUnsafe0();
+//
+//    private static final UnsafeAccessor UNSAFE_ACCESSOR           = getUnsafeAccessor0();
+//
+//    private static final long           BYTE_ARRAY_BASE_OFFSET    = arrayBaseOffset(byte[].class);
+//    // Micro-optimization: we can assume a scale of 1 and skip the multiply
+//    // private static final long BYTE_ARRAY_INDEX_SCALE = 1;
+//
+//    private static final long           BOOLEAN_ARRAY_BASE_OFFSET = arrayBaseOffset(boolean[].class);
+//    private static final long           BOOLEAN_ARRAY_INDEX_SCALE = arrayIndexScale(boolean[].class);
+//
+//    private static final long           INT_ARRAY_BASE_OFFSET     = arrayBaseOffset(int[].class);
+//    private static final long           INT_ARRAY_INDEX_SCALE     = arrayIndexScale(int[].class);
+//
+//    private static final long           LONG_ARRAY_BASE_OFFSET    = arrayBaseOffset(long[].class);
+//    private static final long           LONG_ARRAY_INDEX_SCALE    = arrayIndexScale(long[].class);
+//
+//    private static final long           FLOAT_ARRAY_BASE_OFFSET   = arrayBaseOffset(float[].class);
+//    private static final long           FLOAT_ARRAY_INDEX_SCALE   = arrayIndexScale(float[].class);
+//
+//    private static final long           DOUBLE_ARRAY_BASE_OFFSET  = arrayBaseOffset(double[].class);
+//    private static final long           DOUBLE_ARRAY_INDEX_SCALE  = arrayIndexScale(double[].class);
+//
+//    private static final long           OBJECT_ARRAY_BASE_OFFSET  = arrayBaseOffset(Object[].class);
+//    private static final long           OBJECT_ARRAY_INDEX_SCALE  = arrayIndexScale(Object[].class);
+//
+//    private static final long           BUFFER_ADDRESS_OFFSET     = objectFieldOffset(bufferAddressField());
+//
+//    private static final long           STRING_VALUE_OFFSET       = objectFieldOffset(stringValueField());
+//
+//    /**
+//     * Whether or not can use the unsafe api.
+//     */
+//    public static boolean hasUnsafe() {
+//        return UNSAFE != null;
+//    }
+//
+//    /**
+//     * Get a {@link UnsafeAccessor} appropriate for the platform.
+//     */
+//    public static UnsafeAccessor getUnsafeAccessor() {
+//        return UNSAFE_ACCESSOR;
+//    }
+//
+//    public static byte getByte(final Object target, final long offset) {
+//        return UNSAFE_ACCESSOR.getByte(target, offset);
+//    }
+//
+//    public static void putByte(final Object target, final long offset, final byte value) {
+//        UNSAFE_ACCESSOR.putByte(target, offset, value);
+//    }
+//
+//    public static int getInt(final Object target, final long offset) {
+//        return UNSAFE_ACCESSOR.getInt(target, offset);
+//    }
+//
+//    public static void putInt(final Object target, final long offset, final int value) {
+//        UNSAFE_ACCESSOR.putInt(target, offset, value);
+//    }
+//
+//    public static long getLong(final Object target, final long offset) {
+//        return UNSAFE_ACCESSOR.getLong(target, offset);
+//    }
+//
+//    public static void putLong(final Object target, final long offset, final long value) {
+//        UNSAFE_ACCESSOR.putLong(target, offset, value);
+//    }
+//
+//    public static boolean getBoolean(final Object target, final long offset) {
+//        return UNSAFE_ACCESSOR.getBoolean(target, offset);
+//    }
+//
+//    public static void putBoolean(final Object target, final long offset, final boolean value) {
+//        UNSAFE_ACCESSOR.putBoolean(target, offset, value);
+//    }
+//
+//    public static float getFloat(final Object target, final long offset) {
+//        return UNSAFE_ACCESSOR.getFloat(target, offset);
+//    }
+//
+//    public static void putFloat(final Object target, final long offset, final float value) {
+//        UNSAFE_ACCESSOR.putFloat(target, offset, value);
+//    }
+//
+//    public static double getDouble(final Object target, final long offset) {
+//        return UNSAFE_ACCESSOR.getDouble(target, offset);
+//    }
+//
+//    public static void putDouble(final Object target, final long offset, final double value) {
+//        UNSAFE_ACCESSOR.putDouble(target, offset, value);
+//    }
+//
+//    public static Object getObject(final Object target, final long offset) {
+//        return UNSAFE_ACCESSOR.getObject(target, offset);
+//    }
+//
+//    public static void putObject(final Object target, final long offset, final Object value) {
+//        UNSAFE_ACCESSOR.putObject(target, offset, value);
+//    }
+//
+//    public static byte getByte(final byte[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getByte(target, BYTE_ARRAY_BASE_OFFSET + index);
+//    }
+//
+//    public static void putByte(final byte[] target, final long index, final byte value) {
+//        UNSAFE_ACCESSOR.putByte(target, BYTE_ARRAY_BASE_OFFSET + index, value);
+//    }
+//
+//    public static int getInt(final int[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getInt(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putInt(final int[] target, final long index, final int value) {
+//        UNSAFE_ACCESSOR.putInt(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static long getLong(final long[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getLong(target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putLong(final long[] target, final long index, final long value) {
+//        UNSAFE_ACCESSOR.putLong(target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static boolean getBoolean(final boolean[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getBoolean(target, BOOLEAN_ARRAY_BASE_OFFSET + (index * BOOLEAN_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putBoolean(final boolean[] target, final long index, final boolean value) {
+//        UNSAFE_ACCESSOR.putBoolean(target, BOOLEAN_ARRAY_BASE_OFFSET + (index * BOOLEAN_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static float getFloat(final float[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getFloat(target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putFloat(final float[] target, final long index, final float value) {
+//        UNSAFE_ACCESSOR.putFloat(target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static double getDouble(final double[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getDouble(target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putDouble(final double[] target, final long index, final double value) {
+//        UNSAFE_ACCESSOR.putDouble(target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static Object getObject(final Object[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getObject(target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putObject(final Object[] target, final long index, final Object value) {
+//        UNSAFE_ACCESSOR.putObject(target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static byte getByte(final long address) {
+//        return UNSAFE_ACCESSOR.getByte(address);
+//    }
+//
+//    public static void putByte(final long address, final byte value) {
+//        UNSAFE_ACCESSOR.putByte(address, value);
+//    }
+//
+//    public static int getInt(final long address) {
+//        return UNSAFE_ACCESSOR.getInt(address);
+//    }
+//
+//    public static void putInt(final long address, final int value) {
+//        UNSAFE_ACCESSOR.putInt(address, value);
+//    }
+//
+//    public static long getLong(final long address) {
+//        return UNSAFE_ACCESSOR.getLong(address);
+//    }
+//
+//    public static void putLong(final long address, final long value) {
+//        UNSAFE_ACCESSOR.putLong(address, value);
+//    }
+//
+//    public static byte getByteVolatile(final byte[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getByteVolatile(target, BYTE_ARRAY_BASE_OFFSET + index);
+//    }
+//
+//    public static void putByteVolatile(final byte[] target, final long index, final byte value) {
+//        UNSAFE_ACCESSOR.putByteVolatile(target, BYTE_ARRAY_BASE_OFFSET + index, value);
+//    }
+//
+//    public static int getIntVolatile(final int[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getIntVolatile(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putIntVolatile(final int[] target, final long index, final int value) {
+//        UNSAFE_ACCESSOR.putIntVolatile(target, INT_ARRAY_BASE_OFFSET + (index * INT_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static long getLongVolatile(final long[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getLongVolatile(target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putLongVolatile(final long[] target, final long index, final long value) {
+//        UNSAFE_ACCESSOR.putLongVolatile(target, LONG_ARRAY_BASE_OFFSET + (index * LONG_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static boolean getBooleanVolatile(final boolean[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getBooleanVolatile(target, BOOLEAN_ARRAY_BASE_OFFSET
+//                                                          + (index * BOOLEAN_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putBooleanVolatile(final boolean[] target, final long index, final boolean value) {
+//        UNSAFE_ACCESSOR.putBooleanVolatile(target, BOOLEAN_ARRAY_BASE_OFFSET + (index * BOOLEAN_ARRAY_INDEX_SCALE),
+//            value);
+//    }
+//
+//    public static float getFloatVolatile(final float[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getFloatVolatile(target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putFloatVolatile(final float[] target, final long index, final float value) {
+//        UNSAFE_ACCESSOR.putFloatVolatile(target, FLOAT_ARRAY_BASE_OFFSET + (index * FLOAT_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static double getDoubleVolatile(final double[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getDoubleVolatile(target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putDoubleVolatile(final double[] target, final long index, final double value) {
+//        UNSAFE_ACCESSOR.putDoubleVolatile(target, DOUBLE_ARRAY_BASE_OFFSET + (index * DOUBLE_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    public static Object getObjectVolatile(final Object[] target, final long index) {
+//        return UNSAFE_ACCESSOR.getObjectVolatile(target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE));
+//    }
+//
+//    public static void putObjectVolatile(final Object[] target, final long index, final Object value) {
+//        UNSAFE_ACCESSOR.putObjectVolatile(target, OBJECT_ARRAY_BASE_OFFSET + (index * OBJECT_ARRAY_INDEX_SCALE), value);
+//    }
+//
+//    /**
+//     * Reports the offset of the first element in the storage allocation of a
+//     * given array class.
+//     */
+//    public static int arrayBaseOffset(final Class<?> clazz) {
+//        return hasUnsafe() ? UNSAFE_ACCESSOR.arrayBaseOffset(clazz) : -1;
+//    }
+//
+//    /**
+//     * Reports the scale factor for addressing elements in the storage
+//     * allocation of a given array class.
+//     */
+//    public static int arrayIndexScale(final Class<?> clazz) {
+//        return hasUnsafe() ? UNSAFE_ACCESSOR.arrayIndexScale(clazz) : -1;
+//    }
+//
+//    /**
+//     * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not
+//     * available.
+//     */
+//    public static long objectFieldOffset(final Field field) {
+//        return field == null || hasUnsafe() ? UNSAFE_ACCESSOR.objectFieldOffset(field) : -1;
+//    }
+//
+//    /**
+//     * Returns the offset of the provided class and fieldName, or {@code -1} if {@code sun.misc.Unsafe} is not
+//     * available.
+//     */
+//    public static long objectFieldOffset(final Class<?> clazz, final String fieldName) {
+//        try {
+//            return objectFieldOffset(clazz.getDeclaredField(fieldName));
+//        } catch (final NoSuchFieldException e) {
+//            UNSAFE_ACCESSOR.throwException(e);
+//        }
+//        return -1; // never get here
+//    }
+//
+//    /**
+//     * Gets the offset of the {@code address} field of the given
+//     * direct {@link ByteBuffer}.
+//     */
+//    public static long addressOffset(final ByteBuffer buffer) {
+//        return UNSAFE_ACCESSOR.getLong(buffer, BUFFER_ADDRESS_OFFSET);
+//    }
+//
+//    public static void throwException(final Throwable t) {
+//        UNSAFE_ACCESSOR.throwException(t);
+//    }
+//
+//    /**
+//     * Returns a new {@link String} backed by the given {@code chars}.
+//     * The char array should not be mutated any more after calling
+//     * this function.
+//     */
+//    public static String moveToString(final char[] chars) {
+//        if (STRING_VALUE_OFFSET == -1) {
+//            // In the off-chance that this JDK does not implement String as we'd expect, just do a copy.
+//            return new String(chars);
+//        }
+//        final String str;
+//        try {
+//            str = (String) UNSAFE_ACCESSOR.allocateInstance(String.class);
+//        } catch (final InstantiationException e) {
+//            // This should never happen, but return a copy as a fallback just in case.
+//            return new String(chars);
+//        }
+//        UNSAFE_ACCESSOR.putObject(str, STRING_VALUE_OFFSET, chars);
+//        return str;
+//    }
+//
+//    /**
+//     * Returns the system {@link ClassLoader}.
+//     */
+//    public static ClassLoader getSystemClassLoader() {
+//        if (System.getSecurityManager() == null) {
+//            return ClassLoader.getSystemClassLoader();
+//        } else {
+//            return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) ClassLoader::getSystemClassLoader);
+//        }
+//    }
+//
+//    /**
+//     * Finds the address field within a direct {@link Buffer}.
+//     */
+//    private static Field bufferAddressField() {
+//        return field(Buffer.class, "address", long.class);
+//    }
+//
+//    /**
+//     * Finds the value field within a {@link String}.
+//     */
+//    private static Field stringValueField() {
+//        return field(String.class, "value", char[].class);
+//    }
+//
+//    /**
+//     * Gets the field with the given name within the class, or
+//     * {@code null} if not found. If found, the field is made accessible.
+//     */
+//    private static Field field(final Class<?> clazz, final String fieldName, final Class<?> expectedType) {
+//        Field field;
+//        try {
+//            field = clazz.getDeclaredField(fieldName);
+//            field.setAccessible(true);
+//            if (!field.getType().equals(expectedType)) {
+//                return null;
+//            }
+//        } catch (final Throwable t) {
+//            // Failed to access the fields.
+//            field = null;
+//        }
+//        return field;
+//    }
+//
+//    private static UnsafeAccessor getUnsafeAccessor0() {
+//        return hasUnsafe() ? new UnsafeAccessor(UNSAFE) : null;
+//    }
+//
+//    private static Object getUnsafe0() {
+//        Object unsafe;
+//        try {
+//            final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+//            final Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
+//            unsafeField.setAccessible(true);
+//            unsafe = unsafeField.get(null);
+//        } catch (final Throwable t) {
+//            if (LOG.isWarnEnabled()) {
+//                LOG.warn("sun.misc.Unsafe.theUnsafe: unavailable, {}.", t);
+//            }
+//            unsafe = null;
+//        }
+//        return unsafe;
+//    }
+//
+//    public static class UnsafeAccessor {
+//
+//        private final sun.misc.Unsafe unsafe;
+//
+//        UnsafeAccessor(Object unsafe) {
+//            this.unsafe = (sun.misc.Unsafe) unsafe;
+//        }
+//
+//        /**
+//         * Returns the {@link sun.misc.Unsafe}'s instance.
+//         */
+//        public sun.misc.Unsafe getUnsafe() {
+//            return unsafe;
+//        }
+//
+//        public byte getByte(final Object target, final long offset) {
+//            return this.unsafe.getByte(target, offset);
+//        }
+//
+//        public void putByte(final Object target, final long offset, final byte value) {
+//            this.unsafe.putByte(target, offset, value);
+//        }
+//
+//        public short getShort(final Object target, final long offset) {
+//            return this.unsafe.getShort(target, offset);
+//        }
+//
+//        public void putShort(final Object target, final long offset, final short value) {
+//            this.unsafe.putShort(target, offset, value);
+//        }
+//
+//        public int getInt(final Object target, final long offset) {
+//            return this.unsafe.getInt(target, offset);
+//        }
+//
+//        public void putInt(final Object target, final long offset, final int value) {
+//            this.unsafe.putInt(target, offset, value);
+//        }
+//
+//        public long getLong(final Object target, final long offset) {
+//            return this.unsafe.getLong(target, offset);
+//        }
+//
+//        public void putLong(final Object target, final long offset, final long value) {
+//            this.unsafe.putLong(target, offset, value);
+//        }
+//
+//        public boolean getBoolean(final Object target, final long offset) {
+//            return this.unsafe.getBoolean(target, offset);
+//        }
+//
+//        public void putBoolean(final Object target, final long offset, final boolean value) {
+//            this.unsafe.putBoolean(target, offset, value);
+//        }
+//
+//        public float getFloat(final Object target, final long offset) {
+//            return this.unsafe.getFloat(target, offset);
+//        }
+//
+//        public void putFloat(final Object target, final long offset, final float value) {
+//            this.unsafe.putFloat(target, offset, value);
+//        }
+//
+//        public double getDouble(final Object target, final long offset) {
+//            return this.unsafe.getDouble(target, offset);
+//        }
+//
+//        public void putDouble(final Object target, final long offset, final double value) {
+//            this.unsafe.putDouble(target, offset, value);
+//        }
+//
+//        public Object getObject(final Object target, final long offset) {
+//            return this.unsafe.getObject(target, offset);
+//        }
+//
+//        public void putObject(final Object target, final long offset, final Object value) {
+//            this.unsafe.putObject(target, offset, value);
+//        }
+//
+//        public byte getByte(final long address) {
+//            return this.unsafe.getByte(address);
+//        }
+//
+//        public void putByte(final long address, final byte value) {
+//            this.unsafe.putByte(address, value);
+//        }
+//
+//        public short getShort(final long address) {
+//            return this.unsafe.getShort(address);
+//        }
+//
+//        public void putShort(final long address, final short value) {
+//            this.unsafe.putShort(address, value);
+//        }
+//
+//        public int getInt(final long address) {
+//            return this.unsafe.getInt(address);
+//        }
+//
+//        public void putInt(final long address, final int value) {
+//            this.unsafe.putInt(address, value);
+//        }
+//
+//        public long getLong(final long address) {
+//            return this.unsafe.getLong(address);
+//        }
+//
+//        public void putLong(final long address, final long value) {
+//            this.unsafe.putLong(address, value);
+//        }
+//
+//        public void copyMemory(final Object srcBase, final long srcOffset, final Object dstBase, final long dstOffset,
+//                               final long bytes) {
+//            this.unsafe.copyMemory(srcBase, srcOffset, dstBase, dstOffset, bytes);
+//        }
+//
+//        public void copyMemory(final long srcAddress, final long dstAddress, final long bytes) {
+//            this.unsafe.copyMemory(srcAddress, dstAddress, bytes);
+//        }
+//
+//        public byte getByteVolatile(final Object target, final long offset) {
+//            return this.unsafe.getByteVolatile(target, offset);
+//        }
+//
+//        public void putByteVolatile(final Object target, final long offset, final byte value) {
+//            this.unsafe.putByteVolatile(target, offset, value);
+//        }
+//
+//        public short getShortVolatile(final Object target, final long offset) {
+//            return this.unsafe.getShortVolatile(target, offset);
+//        }
+//
+//        public void putShortVolatile(final Object target, final long offset, final short value) {
+//            this.unsafe.putShortVolatile(target, offset, value);
+//        }
+//
+//        public int getIntVolatile(final Object target, final long offset) {
+//            return this.unsafe.getIntVolatile(target, offset);
+//        }
+//
+//        public void putIntVolatile(final Object target, final long offset, final int value) {
+//            this.unsafe.putIntVolatile(target, offset, value);
+//        }
+//
+//        public long getLongVolatile(final Object target, final long offset) {
+//            return this.unsafe.getLongVolatile(target, offset);
+//        }
+//
+//        public void putLongVolatile(final Object target, final long offset, final long value) {
+//            this.unsafe.putLongVolatile(target, offset, value);
+//        }
+//
+//        public boolean getBooleanVolatile(final Object target, final long offset) {
+//            return this.unsafe.getBooleanVolatile(target, offset);
+//        }
+//
+//        public void putBooleanVolatile(final Object target, final long offset, final boolean value) {
+//            this.unsafe.putBooleanVolatile(target, offset, value);
+//        }
+//
+//        public float getFloatVolatile(final Object target, final long offset) {
+//            return this.unsafe.getFloatVolatile(target, offset);
+//        }
+//
+//        public void putFloatVolatile(final Object target, final long offset, final float value) {
+//            this.unsafe.putFloatVolatile(target, offset, value);
+//        }
+//
+//        public double getDoubleVolatile(final Object target, final long offset) {
+//            return this.unsafe.getDoubleVolatile(target, offset);
+//        }
+//
+//        public void putDoubleVolatile(final Object target, final long offset, final double value) {
+//            this.unsafe.putDoubleVolatile(target, offset, value);
+//        }
+//
+//        public Object getObjectVolatile(final Object target, final long offset) {
+//            return this.unsafe.getObjectVolatile(target, offset);
+//        }
+//
+//        public void putObjectVolatile(final Object target, final long offset, final Object value) {
+//            this.unsafe.putObjectVolatile(target, offset, value);
+//        }
+//
+//        /**
+//         * Reports the offset of the first element in the storage allocation of a
+//         * given array class.
+//         */
+//        public int arrayBaseOffset(final Class<?> clazz) {
+//            return this.unsafe != null ? this.unsafe.arrayBaseOffset(clazz) : -1;
+//        }
+//
+//        /**
+//         * Reports the scale factor for addressing elements in the storage
+//         * allocation of a given array class.
+//         */
+//        public int arrayIndexScale(final Class<?> clazz) {
+//            return this.unsafe != null ? this.unsafe.arrayIndexScale(clazz) : -1;
+//        }
+//
+//        /**
+//         * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not
+//         * available.
+//         */
+//        public long objectFieldOffset(final Field field) {
+//            return field == null || this.unsafe == null ? -1 : this.unsafe.objectFieldOffset(field);
+//        }
+//
+//        public Object allocateInstance(final Class<?> clazz) throws InstantiationException {
+//            return this.unsafe.allocateInstance(clazz);
+//        }
+//
+//        public void throwException(final Throwable t) {
+//            this.unsafe.throwException(t);
+//        }
+//    }
+//
+//    private UnsafeUtil() {
+//    }
+//}
diff --git a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/Updaters.java b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/Updaters.java
index 3732d41..a17e62f 100644
--- a/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/Updaters.java
+++ b/modules/raft/src/main/java/com/alipay/sofa/jraft/util/internal/Updaters.java
@@ -32,11 +32,13 @@ public class Updaters {
     public static <U> IntegerFieldUpdater<U> newIntegerFieldUpdater(final Class<? super U> tClass,
                                                                     final String fieldName) {
         try {
-            if (UnsafeUtil.hasUnsafe()) {
-                return new UnsafeIntegerFieldUpdater<>(UnsafeUtil.getUnsafeAccessor().getUnsafe(), tClass, fieldName);
-            } else {
-                return new ReflectionIntegerFieldUpdater<>(tClass, fieldName);
-            }
+//            if (UnsafeUtil.hasUnsafe()) {
+//                return new UnsafeIntegerFieldUpdater<>(UnsafeUtil.getUnsafeAccessor().getUnsafe(), tClass, fieldName);
+//            } else {
+//                return new ReflectionIntegerFieldUpdater<>(tClass, fieldName);
+//            }
+
+            return new ReflectionIntegerFieldUpdater<>(tClass, fieldName);
         } catch (final Throwable t) {
             throw new RuntimeException(t);
         }
@@ -50,11 +52,13 @@ public class Updaters {
      */
     public static <U> LongFieldUpdater<U> newLongFieldUpdater(final Class<? super U> tClass, final String fieldName) {
         try {
-            if (UnsafeUtil.hasUnsafe()) {
-                return new UnsafeLongFieldUpdater<>(UnsafeUtil.getUnsafeAccessor().getUnsafe(), tClass, fieldName);
-            } else {
-                return new ReflectionLongFieldUpdater<>(tClass, fieldName);
-            }
+//            if (UnsafeUtil.hasUnsafe()) {
+//                return new UnsafeLongFieldUpdater<>(UnsafeUtil.getUnsafeAccessor().getUnsafe(), tClass, fieldName);
+//            } else {
+//                return new ReflectionLongFieldUpdater<>(tClass, fieldName);
+//            }
+
+            return new ReflectionLongFieldUpdater<>(tClass, fieldName);
         } catch (final Throwable t) {
             throw new RuntimeException(t);
         }
@@ -69,11 +73,13 @@ public class Updaters {
     public static <U, W> ReferenceFieldUpdater<U, W> newReferenceFieldUpdater(final Class<? super U> tClass,
                                                                               final String fieldName) {
         try {
-            if (UnsafeUtil.hasUnsafe()) {
-                return new UnsafeReferenceFieldUpdater<>(UnsafeUtil.getUnsafeAccessor().getUnsafe(), tClass, fieldName);
-            } else {
-                return new ReflectionReferenceFieldUpdater<>(tClass, fieldName);
-            }
+//            if (UnsafeUtil.hasUnsafe()) {
+//                return new UnsafeReferenceFieldUpdater<>(UnsafeUtil.getUnsafeAccessor().getUnsafe(), tClass, fieldName);
+//            } else {
+//                return new ReflectionReferenceFieldUpdater<>(tClass, fieldName);
+//            }
+
+            return new ReflectionReferenceFieldUpdater<>(tClass, fieldName);
         } catch (final Throwable t) {
             throw new RuntimeException(t);
         }