Showing preview only (726K chars total). Download the full file or copy to clipboard to get everything.
Repository: hashicorp/raft
Branch: main
Commit: b9f94dd8172c
Files: 91
Total size: 695.5 KB
Directory structure:
gitextract_c91ofz_i/
├── .github/
│ ├── CODEOWNERS
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ ├── stale.yml
│ └── workflows/
│ ├── ci.yml
│ └── two-step-pr-approval.yml
├── .gitignore
├── .gitmodules
├── .golangci-lint.yml
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── api.go
├── bench/
│ └── bench.go
├── bench_test.go
├── commands.go
├── commitment.go
├── commitment_test.go
├── config.go
├── configuration.go
├── configuration_test.go
├── discard_snapshot.go
├── discard_snapshot_test.go
├── docs/
│ ├── README.md
│ ├── apply.md
│ └── divergence.md
├── file_snapshot.go
├── file_snapshot_test.go
├── fsm.go
├── future.go
├── future_test.go
├── fuzzy/
│ ├── apply_src.go
│ ├── cluster.go
│ ├── fsm.go
│ ├── fsm_batch.go
│ ├── go.mod
│ ├── go.sum
│ ├── leadershiptransfer_test.go
│ ├── membership_test.go
│ ├── node.go
│ ├── partition_test.go
│ ├── readme.md
│ ├── resolve.go
│ ├── simple_test.go
│ ├── slowvoter_test.go
│ ├── transport.go
│ └── verifier.go
├── go.mod
├── go.sum
├── inmem_snapshot.go
├── inmem_snapshot_test.go
├── inmem_store.go
├── inmem_transport.go
├── inmem_transport_test.go
├── integ_test.go
├── log.go
├── log_cache.go
├── log_cache_test.go
├── log_test.go
├── membership.md
├── net_transport.go
├── net_transport_test.go
├── observer.go
├── peersjson.go
├── peersjson_test.go
├── progress.go
├── raft-compat/
│ ├── go.mod
│ ├── go.sum
│ ├── prevote_test.go
│ ├── rolling_upgrade_test.go
│ ├── testcluster/
│ │ └── cluster.go
│ └── utils/
│ └── test_utils.go
├── raft.go
├── raft_test.go
├── replication.go
├── saturation.go
├── saturation_test.go
├── snapshot.go
├── stable.go
├── state.go
├── tag.sh
├── tcp_transport.go
├── tcp_transport_test.go
├── testing.go
├── testing_batch.go
├── transport.go
├── transport_test.go
├── util.go
└── util_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODEOWNERS
================================================
# Each line is a file pattern followed by one or more owners.
# More on CODEOWNERS files: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
# Default owner
* @hashicorp/team-ip-compliance @hashicorp/consul-core-reviewers @hashicorp/nomad-eng @hashicorp/raft-force
# Add override rules below. Each line is a file/folder pattern followed by one or more owners.
# Being an owner means those groups or individuals will be added as reviewers to PRs affecting
# those areas of the code.
# Examples:
# /docs/ @docs-team
# *.js @js-team
# *.go @go-team
================================================
FILE: .github/dependabot.yml
================================================
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "sunday"
commit-message:
prefix: "[chore] : "
groups:
actions:
patterns:
- "*"
- package-ecosystem: "gomod"
directories:
- "/"
- "/fuzzy"
- "/raft-compat"
schedule:
interval: "weekly"
day: "sunday"
commit-message:
prefix: "[chore] : "
groups:
go:
patterns:
- "*"
applies-to: "version-updates"
go-security:
patterns:
- "*"
applies-to: "security-updates"
================================================
FILE: .github/pull_request_template.md
================================================
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
## Description
<!-- Provide a summary of what the PR does and why it is being submitted. -->
## Related Issue
<!-- If this PR is linked to any issue, provide the issue number or description here. Any related JIRA tickets can also be added here. -->
## How Has This Been Tested?
<!-- Describe how the changes have been tested. Provide test instructions or details. -->
================================================
FILE: .github/stale.yml
================================================
# Copyright IBM Corp. 2013, 2025
# SPDX-License-Identifier: MPL-2.0
# Number of days of inactivity before an Issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 30
# Issues with these labels will never be considered stale. Set to `[]` to disable
# We don't close any issue that is an enhancement or confirmed bug, but issues
# waiting for reproduction cases and questions tend to get outdated.
exemptLabels:
- "enhancement"
- "bug"
- "thinking"
- "docs"
# Label to use when marking as stale
staleLabel: "waiting-reply"
# Comment to post when marking as stale. Set to `false` to disable
markComment: |
Hey there,
We wanted to check in on this request since it has been inactive for at least 90 days.
Have you reviewed the latest [godocs](https://godoc.org/github.com/hashicorp/raft)?
If you think this is still an important issue in the latest version of [the Raft library](https://github.com/hashicorp/raft/compare/) or
[its documentation](https://github.com/hashicorp/raft/compare/) please feel let us know and we'll keep it open for investigation.
If there is still no activity on this request in 30 days, we will go ahead and close it.
Thank you!
# Comment to post when removing the stale label. Set to `false` to disable
unmarkComment: false
# Comment to post when closing a stale Issue. Set to `false` to disable
closeComment: >
Hey there,
This issue has been automatically closed because there hasn't been any activity for a while.
If you are still experiencing problems, or still have questions, feel free to [open a new one](https://github.com/hashicorp/raft/issues/new) :+1
================================================
FILE: .github/workflows/ci.yml
================================================
name: ci
on:
pull_request:
branches: ["main"]
push:
branches: ["main"]
tags: ["*"]
permissions:
contents: read
jobs:
go-fmt-and-vet:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 'stable'
cache: true
- name: go fmt
run: |
files=$(go fmt ./...)
if [ -n "$files" ]; then
echo "The following file(s) do not conform to go fmt:"
echo "$files"
exit 1
fi
- name: go vet
run: |
PACKAGE_NAMES=$(go list ./... | grep -v github.com/hashicorp/raft/fuzzy)
go vet $PACKAGE_NAMES
- name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
go-test:
needs: go-fmt-and-vet
strategy:
matrix:
go: ['stable', 'oldstable']
arch: ['x32', 'x64']
runs-on: ubuntu-22.04
env:
INTEG_TESTS: yes
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{ matrix.go }}
architecture: ${{ matrix.arch }}
cache: true
# x86 specific build.
- if: matrix.arch == 'x32'
run: |
sudo apt-get update
sudo apt-get install gcc-multilib
go test --tags batchtest ./...
# x86-64 specific build.
- if: matrix.arch == 'x64'
run: go test -race --tags batchtest ./...
go-test-compat:
needs: go-test
strategy:
matrix:
go: ['stable', 'oldstable']
arch: ['x32', 'x64']
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{ matrix.go }}
architecture: ${{ matrix.arch }}
cache: true
submodules: true
# x86 specific build.
- if: matrix.arch == 'x32'
run: |
sudo apt-get update
sudo apt-get install gcc-multilib
git submodule update --init --recursive
cd raft-compat
go mod tidy
go test -v -coverpkg=./... ./... -coverprofile="${{ github.workspace }}/coverage.out"
# x86-64 specific build.
- if: matrix.arch == 'x64'
run: |
git submodule update --init --recursive
cd raft-compat
go mod tidy
go test -race -v -coverpkg=./... ./... -coverprofile="${{ github.workspace }}/coverage.out"
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
path: "${{ github.workspace }}/coverage.out"
name: coverage-report-${{matrix.go}}-${{matrix.arch}}
================================================
FILE: .github/workflows/two-step-pr-approval.yml
================================================
name: Two-Stage PR Review Process
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled, ready_for_review, converted_to_draft]
pull_request_review:
types: [submitted]
jobs:
manage-pr-status:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
- name: Two stage PR review
uses: hashicorp/two-stage-pr-approval@v0.1.0
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
# Goland IDE
.idea
================================================
FILE: .gitmodules
================================================
[submodule "raft-compat/raft-latest"]
path = raft-compat/raft-previous-version
url = https://github.com/hashicorp/raft.git
================================================
FILE: .golangci-lint.yml
================================================
# Copyright IBM Corp. 2013, 2025
# SPDX-License-Identifier: MPL-2.0
run:
deadline: 5m
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
depguard:
rules:
main:
list-mode: lax
allow:
- "github.com/hashicorp/go-metrics/compat"
deny:
- pkg: "github.com/hashicorp/go-metrics"
desc: not allowed, use github.com/hashicorp/go-metrics/compat instead
- pkg: "github.com/armon/go-metrics"
desc: not allowed, use github.com/hashicorp/go-metrics/compat instead
linters:
disable-all: true
enable:
- gofmt
#- golint
- govet
- depguard
#- varcheck
#- typecheck
#- gosimple
issues:
exclude-use-default: false
exclude:
# ignore the false positive erros resulting from not including a comment above every `package` keyword
- should have a package comment, unless it's in another file for this package (golint)
# golint: Annoying issue about not having a comment. The rare codebase has such comments
# - (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)
# errcheck: Almost all programs ignore errors on these functions and in most cases it's ok
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked
# golint: False positive when tests are defined in package 'test'
- func name will be used as test\.Test.* by other packages, and that stutters; consider calling this
# staticcheck: Developers tend to write in C-style with an
# explicit 'break' in a 'switch', so it's ok to ignore
- ineffective break statement. Did you mean to break out of the outer loop
# gosec: Too many false-positives on 'unsafe' usage
- Use of unsafe calls should be audited
# gosec: Too many false-positives for parametrized shell calls
- Subprocess launch(ed with variable|ing should be audited)
# gosec: Duplicated errcheck checks
- G104
# gosec: Too many issues in popular repos
- (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)
# gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)'
- Potential file inclusion via variable
================================================
FILE: .travis.yml
================================================
# Copyright IBM Corp. 2013, 2025
# SPDX-License-Identifier: MPL-2.0
language: go
go:
# Disabled until https://github.com/armon/go-metrics/issues/59 is fixed
# - 1.6
- 1.8
- 1.9
- 1.12
- tip
install:
- make deps
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
script:
- make integ
notifications:
flowdock:
secure: fZrcf9rlh2IrQrlch1sHkn3YI7SKvjGnAl/zyV5D6NROe1Bbr6d3QRMuCXWWdhJHzjKmXk5rIzbqJhUc0PNF7YjxGNKSzqWMQ56KcvN1k8DzlqxpqkcA3Jbs6fXCWo2fssRtZ7hj/wOP1f5n6cc7kzHDt9dgaYJ6nO2fqNPJiTc=
================================================
FILE: CHANGELOG.md
================================================
# UNRELEASED
IMPROVEMENETS
* Added a flag to skip legacy duplicate telemetry. [GH-630](https://github.com/hashicorp/raft/pull/630)
# 1.7.0 (June 5th, 2024)
CHANGES
* Raft multi version testing [GH-559](https://github.com/hashicorp/raft/pull/559)
IMPROVEMENTS
* Raft pre-vote extension implementation, activated by default. [GH-530](https://github.com/hashicorp/raft/pull/530)
BUG FIXES
* Fix serialize NetworkTransport data race on ServerAddr(). [GH-591](https://github.com/hashicorp/raft/pull/591)
# 1.6.1 (January 8th, 2024)
CHANGES
* Add reference use of Hashicorp Raft. [GH-584](https://github.com/hashicorp/raft/pull/584)
* [COMPLIANCE] Add Copyright and License Headers. [GH-580](https://github.com/hashicorp/raft/pull/580)
IMPROVEMENTS
* Bump github.com/hashicorp/go-hclog from 1.5.0 to 1.6.2. [GH-583](https://github.com/hashicorp/raft/pull/583)
BUG FIXES
* Fix rare leadership transfer failures when writes happen during transfer. [GH-581](https://github.com/hashicorp/raft/pull/581)
# 1.6.0 (November 15th, 2023)
CHANGES
* Upgrade hashicorp/go-msgpack to v2, with go.mod upgraded from v0.5.5 to v2.1.1. [GH-577](https://github.com/hashicorp/raft/pull/577)
go-msgpack v2.1.1 is by default binary compatible with v0.5.5 ("non-builtin" encoding of `time.Time`), but can decode messages produced by v1.1.5 as well ("builtin" encoding of `time.Time`).
However, if users of this library overrode the version of go-msgpack (especially to v1), this **could break** compatibility if raft nodes are running a mix of versions.
This compatibility can be configured at runtime in Raft using `NetworkTransportConfig.MsgpackUseNewTimeFormat` -- the default is `false`, which maintains compatibility with `go-msgpack` v0.5.5, but if set to `true`, will be compatible with `go-msgpack` v1.1.5.
IMPROVEMENTS
* Push to notify channel when shutting down. [GH-567](https://github.com/hashicorp/raft/pull/567)
* Add CommitIndex API [GH-560](https://github.com/hashicorp/raft/pull/560)
* Document some Apply error cases better [GH-561](https://github.com/hashicorp/raft/pull/561)
BUG FIXES
* Race with `candidateFromLeadershipTransfer` [GH-570](https://github.com/hashicorp/raft/pull/570)
# 1.5.0 (April 21st, 2023)
IMPROVEMENTS
* Fixed a performance anomaly related to pipelining RPCs that caused large increases in commit latency under high write throughput. Default behavior has changed. For more information see #541.
# 1.4.0 (March 17th, 2023)
FEATURES
* Support log stores with a monotonically increasing index. Implementing a log store with the `MonotonicLogStore` interface where `IsMonotonic()` returns true will allow Raft to clear all previous logs on user restores of Raft snapshots.
BUG FIXES
* Restoring a snapshot with the raft-wal log store caused a panic due to index gap that is created during snapshot restores.
# 1.3.0 (April 22nd, 2021)
IMPROVEMENTS
* Added metrics for `oldestLogAge` and `lastRestoreDuration` to monitor capacity issues that can cause unrecoverable cluster failure [[GH-452](https://github.com/hashicorp/raft/pull/452)][[GH-454](https://github.com/hashicorp/raft/pull/454/files)]
* Made `TrailingLogs`, `SnapshotInterval` and `SnapshotThreshold` reloadable at runtime using a new `ReloadConfig` method. This allows recovery from cases where there are not enough logs retained for followers to catchup after a restart. [[GH-444](https://github.com/hashicorp/raft/pull/444)]
* Inclusify the repository by switching to main [[GH-446](https://github.com/hashicorp/raft/pull/446)]
* Add option for a buffered `ApplyCh` if `MaxAppendEntries` is enabled [[GH-445](https://github.com/hashicorp/raft/pull/445)]
* Add string to `LogType` for more human readable debugging [[GH-442](https://github.com/hashicorp/raft/pull/442)]
* Extract fuzzy testing into its own module [[GH-459](https://github.com/hashicorp/raft/pull/459)]
BUG FIXES
* Update LogCache `StoreLogs()` to capture an error that would previously cause a panic [[GH-460](https://github.com/hashicorp/raft/pull/460)]
# 1.2.0 (October 5th, 2020)
IMPROVEMENTS
* Remove `StartAsLeader` configuration option [[GH-364](https://github.com/hashicorp/raft/pull/386)]
* Allow futures to react to `Shutdown()` to prevent a deadlock with `takeSnapshot()` [[GH-390](https://github.com/hashicorp/raft/pull/390)]
* Prevent non-voters from becoming eligible for leadership elections [[GH-398](https://github.com/hashicorp/raft/pull/398)]
* Remove an unneeded `io.Copy` from snapshot writes [[GH-399](https://github.com/hashicorp/raft/pull/399)]
* Log decoded candidate address in `duplicate requestVote` warning [[GH-400](https://github.com/hashicorp/raft/pull/400)]
* Prevent starting a TCP transport when IP address is `nil` [[GH-403](https://github.com/hashicorp/raft/pull/403)]
* Reject leadership transfer requests when in candidate state to prevent indefinite blocking while unable to elect a leader [[GH-413](https://github.com/hashicorp/raft/pull/413)]
* Add labels for metric metadata to reduce cardinality of metric names [[GH-409](https://github.com/hashicorp/raft/pull/409)]
* Add peers metric [[GH-413](https://github.com/hashicorp/raft/pull/431)]
BUG FIXES
* Make `LeaderCh` always deliver the latest leadership transition [[GH-384](https://github.com/hashicorp/raft/pull/384)]
* Handle updating an existing peer in `startStopReplication` [[GH-419](https://github.com/hashicorp/raft/pull/419)]
# 1.1.2 (January 17th, 2020)
FEATURES
* Improve FSM apply performance through batching. Implementing the `BatchingFSM` interface enables this new feature [[GH-364](https://github.com/hashicorp/raft/pull/364)]
* Add ability to obtain Raft configuration before Raft starts with GetConfiguration [[GH-369](https://github.com/hashicorp/raft/pull/369)]
IMPROVEMENTS
* Remove lint violations and add a `make` rule for running the linter.
* Replace logger with hclog [[GH-360](https://github.com/hashicorp/raft/pull/360)]
* Read latest configuration independently from main loop [[GH-379](https://github.com/hashicorp/raft/pull/379)]
BUG FIXES
* Export the leader field in LeaderObservation [[GH-357](https://github.com/hashicorp/raft/pull/357)]
* Fix snapshot to not attempt to truncate a negative range [[GH-358](https://github.com/hashicorp/raft/pull/358)]
* Check for shutdown in inmemPipeline before sending RPCs [[GH-276](https://github.com/hashicorp/raft/pull/276)]
# 1.1.1 (July 23rd, 2019)
FEATURES
* Add support for extensions to be sent on log entries [[GH-353](https://github.com/hashicorp/raft/pull/353)]
* Add config option to skip snapshot restore on startup [[GH-340](https://github.com/hashicorp/raft/pull/340)]
* Add optional configuration store interface [[GH-339](https://github.com/hashicorp/raft/pull/339)]
IMPROVEMENTS
* Break out of group commit early when no logs are present [[GH-341](https://github.com/hashicorp/raft/pull/341)]
BUGFIXES
* Fix 64-bit counters on 32-bit platforms [[GH-344](https://github.com/hashicorp/raft/pull/344)]
* Don't defer closing source in recover/restore operations since it's in a loop [[GH-337](https://github.com/hashicorp/raft/pull/337)]
# 1.1.0 (May 23rd, 2019)
FEATURES
* Add transfer leadership extension [[GH-306](https://github.com/hashicorp/raft/pull/306)]
IMPROVEMENTS
* Move to `go mod` [[GH-323](https://github.com/hashicorp/consul/pull/323)]
* Leveled log [[GH-321](https://github.com/hashicorp/consul/pull/321)]
* Add peer changes to observations [[GH-326](https://github.com/hashicorp/consul/pull/326)]
BUGFIXES
* Copy the contents of an InmemSnapshotStore when opening a snapshot [[GH-270](https://github.com/hashicorp/consul/pull/270)]
* Fix logging panic when converting parameters to strings [[GH-332](https://github.com/hashicorp/consul/pull/332)]
# 1.0.1 (April 12th, 2019)
IMPROVEMENTS
* InMemTransport: Add timeout for sending a message [[GH-313](https://github.com/hashicorp/raft/pull/313)]
* ensure 'make deps' downloads test dependencies like testify [[GH-310](https://github.com/hashicorp/raft/pull/310)]
* Clarifies function of CommitTimeout [[GH-309](https://github.com/hashicorp/raft/pull/309)]
* Add additional metrics regarding log dispatching and committal [[GH-316](https://github.com/hashicorp/raft/pull/316)]
# 1.0.0 (October 3rd, 2017)
v1.0.0 takes the changes that were staged in the library-v2-stage-one branch. This version manages server identities using a UUID, so introduces some breaking API changes. It also versions the Raft protocol, and requires some special steps when interoperating with Raft servers running older versions of the library (see the detailed comment in config.go about version compatibility). You can reference https://github.com/hashicorp/consul/pull/2222 for an idea of what was required to port Consul to these new interfaces.
# 0.1.0 (September 29th, 2017)
v0.1.0 is the original stable version of the library that was in main and has been maintained with no breaking API changes. This was in use by Consul prior to version 0.7.0.
================================================
FILE: LICENSE
================================================
Copyright IBM Corp. 2013, 2025
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor’s Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party’s
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients’ rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients’
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
party’s negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a party’s ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.
================================================
FILE: Makefile
================================================
DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
ENV = $(shell go env GOPATH)
GO_VERSION = $(shell go version)
GOLANG_CI_VERSION = v1.19.0
# Look for versions prior to 1.10 which have a different fmt output
# and don't lint with gofmt against them.
ifneq (,$(findstring go version go1.8, $(GO_VERSION)))
FMT=
else ifneq (,$(findstring go version go1.9, $(GO_VERSION)))
FMT=
else
FMT=--enable gofmt
endif
TEST_RESULTS_DIR?=/tmp/test-results
test:
GOTRACEBACK=all go test $(TESTARGS) -timeout=180s -race .
GOTRACEBACK=all go test $(TESTARGS) -timeout=180s -tags batchtest -race .
integ: test
INTEG_TESTS=yes go test $(TESTARGS) -timeout=60s -run=Integ .
INTEG_TESTS=yes go test $(TESTARGS) -timeout=60s -tags batchtest -run=Integ .
fuzz:
cd ./fuzzy && go test $(TESTARGS) -timeout=20m .
cd ./fuzzy && go test $(TESTARGS) -timeout=20m -tags batchtest .
deps:
go get -t -d -v ./...
echo $(DEPS) | xargs -n1 go get -d
lint:
gofmt -s -w .
golangci-lint run -c .golangci-lint.yml $(FMT) .
dep-linter:
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(ENV)/bin $(GOLANG_CI_VERSION)
cov:
INTEG_TESTS=yes gocov test github.com/hashicorp/raft | gocov-html > /tmp/coverage.html
open /tmp/coverage.html
.PHONY: test cov integ deps dep-linter lint
================================================
FILE: README.md
================================================
raft [](https://github.com/hashicorp/raft/actions)
[](https://pkg.go.dev/github.com/hashicorp/raft)
[](https://goreportcard.com/report/github.com/hashicorp/raft)
[](https://opensource.org/licenses/MPL-2.0)
[](https://github.com/hashicorp/raft/actions)
[](https://github.com/hashicorp/raft/releases)
[](https://github.com/hashicorp/raft/issues)
[](https://github.com/hashicorp/raft/pulls)
====
raft is a [Go](http://www.golang.org) library that manages a replicated
log and can be used with an FSM to manage replicated state machines. It
is a library for providing [consensus](http://en.wikipedia.org/wiki/Consensus_(computer_science)).
The use cases for such a library are far-reaching, such as replicated state
machines which are a key component of many distributed systems. They enable
building Consistent, Partition Tolerant (CP) systems, with limited
fault tolerance as well.
## Building
If you wish to build raft you'll need Go version 1.16+ installed.
Please check your installation with:
```
go version
```
## Documentation
For complete documentation, see the associated [Godoc](http://godoc.org/github.com/hashicorp/raft).
To prevent complications with cgo, the primary backend `MDBStore` is in a separate repository,
called [raft-mdb](http://github.com/hashicorp/raft-mdb). That is the recommended implementation
for the `LogStore` and `StableStore`.
A pure Go backend using [Bbolt](https://github.com/etcd-io/bbolt) is also available called
[raft-boltdb](https://github.com/hashicorp/raft-boltdb). It can also be used as a `LogStore`
and `StableStore`.
## Community Contributed Examples
- [Raft gRPC Example](https://github.com/Jille/raft-grpc-example) - Utilizing the Raft repository with gRPC
- [Raft-based KV-store Example](https://github.com/otoolep/hraftd) - Uses Hashicorp Raft to build a distributed key-value store
## Tagged Releases
As of September 2017, HashiCorp will start using tags for this library to clearly indicate
major version updates. We recommend you vendor your application's dependency on this library.
* v0.1.0 is the original stable version of the library that was in main and has been maintained
with no breaking API changes. This was in use by Consul prior to version 0.7.0.
* v1.0.0 takes the changes that were staged in the library-v2-stage-one branch. This version
manages server identities using a UUID, so introduces some breaking API changes. It also versions
the Raft protocol, and requires some special steps when interoperating with Raft servers running
older versions of the library (see the detailed comment in config.go about version compatibility).
You can reference https://github.com/hashicorp/consul/pull/2222 for an idea of what was required
to port Consul to these new interfaces.
This version includes some new features as well, including non voting servers, a new address
provider abstraction in the transport layer, and more resilient snapshots.
## Protocol
raft is based on ["Raft: In Search of an Understandable Consensus Algorithm"](https://raft.github.io/raft.pdf)
A high level overview of the Raft protocol is described below, but for details please read the full
[Raft paper](https://raft.github.io/raft.pdf)
followed by the raft source. Any questions about the raft protocol should be sent to the
[raft-dev mailing list](https://groups.google.com/forum/#!forum/raft-dev).
### Protocol Description
Raft nodes are always in one of three states: follower, candidate or leader. All
nodes initially start out as a follower. In this state, nodes can accept log entries
from a leader and cast votes. If no entries are received for some time, nodes
self-promote to the candidate state. In the candidate state nodes request votes from
their peers. If a candidate receives a quorum of votes, then it is promoted to a leader.
The leader must accept new log entries and replicate to all the other followers.
In addition, if stale reads are not acceptable, all queries must also be performed on
the leader.
Once a cluster has a leader, it is able to accept new log entries. A client can
request that a leader append a new log entry, which is an opaque binary blob to
Raft. The leader then writes the entry to durable storage and attempts to replicate
to a quorum of followers. Once the log entry is considered *committed*, it can be
*applied* to a finite state machine. The finite state machine is application specific,
and is implemented using an interface.
An obvious question relates to the unbounded nature of a replicated log. Raft provides
a mechanism by which the current state is snapshotted, and the log is compacted. Because
of the FSM abstraction, restoring the state of the FSM must result in the same state
as a replay of old logs. This allows Raft to capture the FSM state at a point in time,
and then remove all the logs that were used to reach that state. This is performed automatically
without user intervention, and prevents unbounded disk usage as well as minimizing
time spent replaying logs.
Lastly, there is the issue of updating the peer set when new servers are joining
or existing servers are leaving. As long as a quorum of nodes is available, this
is not an issue as Raft provides mechanisms to dynamically update the peer set.
If a quorum of nodes is unavailable, then this becomes a very challenging issue.
For example, suppose there are only 2 peers, A and B. The quorum size is also
2, meaning both nodes must agree to commit a log entry. If either A or B fails,
it is now impossible to reach quorum. This means the cluster is unable to add,
or remove a node, or commit any additional log entries. This results in *unavailability*.
At this point, manual intervention would be required to remove either A or B,
and to restart the remaining node in bootstrap mode.
A Raft cluster of 3 nodes can tolerate a single node failure, while a cluster
of 5 can tolerate 2 node failures. The recommended configuration is to either
run 3 or 5 raft servers. This maximizes availability without
greatly sacrificing performance.
In terms of performance, Raft is comparable to Paxos. Assuming stable leadership,
committing a log entry requires a single round trip to half of the cluster.
Thus performance is bound by disk I/O and network latency.
## Metrics Emission and Compatibility
This library can emit metrics using either `github.com/armon/go-metrics` or `github.com/hashicorp/go-metrics`. Choosing between the libraries is controlled via build tags.
**Build Tags**
* `armonmetrics` - Using this tag will cause metrics to be routed to `armon/go-metrics`
* `hashicorpmetrics` - Using this tag will cause all metrics to be routed to `hashicorp/go-metrics`
If no build tag is specified, the default behavior is to use `armon/go-metrics`.
**Deprecating `armon/go-metrics`**
Emitting metrics to `armon/go-metrics` is officially deprecated. Usage of `armon/go-metrics` will remain the default until mid-2025 with opt-in support continuing to the end of 2025.
**Migration**
To migrate an application currently using the older `armon/go-metrics` to instead use `hashicorp/go-metrics` the following should be done.
1. Upgrade libraries using `armon/go-metrics` to consume `hashicorp/go-metrics/compat` instead. This should involve only changing import statements. All repositories in the `hashicorp` namespace
2. Update an applications library dependencies to those that have the compatibility layer configured.
3. Update the application to use `hashicorp/go-metrics` for configuring metrics export instead of `armon/go-metrics`
* Replace all application imports of `github.com/armon/go-metrics` with `github.com/hashicorp/go-metrics`
* Instrument your build system to build with the `hashicorpmetrics` tag.
Eventually once the default behavior changes to use `hashicorp/go-metrics` by default (mid-2025), you can drop the `hashicorpmetrics` build tag.
================================================
FILE: api.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"errors"
"fmt"
"io"
"strconv"
"sync"
"sync/atomic"
"time"
hclog "github.com/hashicorp/go-hclog"
metrics "github.com/hashicorp/go-metrics/compat"
)
const (
// SuggestedMaxDataSize of the data in a raft log entry, in bytes.
//
// The value is based on current architecture, default timing, etc. Clients can
// ignore this value if they want as there is no actual hard checking
// within the library. As the library is enhanced this value may change
// over time to reflect current suggested maximums.
//
// Applying log entries with data greater than this size risks RPC IO taking
// too long and preventing timely heartbeat signals. These signals are sent in serial
// in current transports, potentially causing leadership instability.
SuggestedMaxDataSize = 512 * 1024
)
var (
// ErrLeader is returned when an operation can't be completed on a
// leader node.
ErrLeader = errors.New("node is the leader")
// ErrNotLeader is returned when an operation can't be completed on a
// follower or candidate node.
ErrNotLeader = errors.New("node is not the leader")
// ErrNotVoter is returned when an operation can't be completed on a
// non-voter node.
ErrNotVoter = errors.New("node is not a voter")
// ErrLeadershipLost is returned when a leader fails to commit a log entry
// because it's been deposed in the process.
ErrLeadershipLost = errors.New("leadership lost while committing log")
// ErrAbortedByRestore is returned when a leader fails to commit a log
// entry because it's been superseded by a user snapshot restore.
ErrAbortedByRestore = errors.New("snapshot restored while committing log")
// ErrRaftShutdown is returned when operations are requested against an
// inactive Raft.
ErrRaftShutdown = errors.New("raft is already shutdown")
// ErrEnqueueTimeout is returned when a command fails due to a timeout.
ErrEnqueueTimeout = errors.New("timed out enqueuing operation")
// ErrNothingNewToSnapshot is returned when trying to create a snapshot
// but there's nothing new committed to the FSM since we started.
ErrNothingNewToSnapshot = errors.New("nothing new to snapshot")
// ErrUnsupportedProtocol is returned when an operation is attempted
// that's not supported by the current protocol version.
ErrUnsupportedProtocol = errors.New("operation not supported with current protocol version")
// ErrCantBootstrap is returned when attempt is made to bootstrap a
// cluster that already has state present.
ErrCantBootstrap = errors.New("bootstrap only works on new clusters")
// ErrLeadershipTransferInProgress is returned when the leader is rejecting
// client requests because it is attempting to transfer leadership.
ErrLeadershipTransferInProgress = errors.New("leadership transfer in progress")
)
// Raft implements a Raft node.
type Raft struct {
raftState
// protocolVersion is used to inter-operate with Raft servers running
// different versions of the library. See comments in config.go for more
// details.
protocolVersion ProtocolVersion
// applyCh is used to async send logs to the main thread to
// be committed and applied to the FSM.
applyCh chan *logFuture
// conf stores the current configuration to use. This is the most recent one
// provided. All reads of config values should use the config() helper method
// to read this safely.
conf atomic.Value
// confReloadMu ensures that only one thread can reload config at once since
// we need to read-modify-write the atomic. It is NOT necessary to hold this
// for any other operation e.g. reading config using config().
confReloadMu sync.Mutex
// FSM is the client state machine to apply commands to
fsm FSM
// fsmMutateCh is used to send state-changing updates to the FSM. This
// receives pointers to commitTuple structures when applying logs or
// pointers to restoreFuture structures when restoring a snapshot. We
// need control over the order of these operations when doing user
// restores so that we finish applying any old log applies before we
// take a user snapshot on the leader, otherwise we might restore the
// snapshot and apply old logs to it that were in the pipe.
fsmMutateCh chan interface{}
// fsmSnapshotCh is used to trigger a new snapshot being taken
fsmSnapshotCh chan *reqSnapshotFuture
// lastContact is the last time we had contact from the
// leader node. This can be used to gauge staleness.
lastContact time.Time
lastContactLock sync.RWMutex
// leaderAddr is the current cluster leader Address
leaderAddr ServerAddress
// LeaderID is the current cluster leader ID
leaderID ServerID
leaderLock sync.RWMutex
// leaderCh is used to notify of leadership changes
leaderCh chan bool
// leaderState used only while state is leader
leaderState leaderState
// candidateFromLeadershipTransfer is used to indicate that this server became
// candidate because the leader tries to transfer leadership. This flag is
// used in RequestVoteRequest to express that a leadership transfer is going
// on.
candidateFromLeadershipTransfer atomic.Bool
// Stores our local server ID, used to avoid sending RPCs to ourself
localID ServerID
// Stores our local addr
localAddr ServerAddress
// Used for our logging
logger hclog.Logger
// LogStore provides durable storage for logs
logs LogStore
// Used to request the leader to make configuration changes.
configurationChangeCh chan *configurationChangeFuture
// Tracks the latest configuration and latest committed configuration from
// the log/snapshot.
configurations configurations
// Holds a copy of the latest configuration which can be read independently
// of the main loop.
latestConfiguration atomic.Value
// RPC chan comes from the transport layer
rpcCh <-chan RPC
// Shutdown channel to exit, protected to prevent concurrent exits
shutdown bool
shutdownCh chan struct{}
shutdownLock sync.Mutex
// snapshots is used to store and retrieve snapshots
snapshots SnapshotStore
// userSnapshotCh is used for user-triggered snapshots
userSnapshotCh chan *userSnapshotFuture
// userRestoreCh is used for user-triggered restores of external
// snapshots
userRestoreCh chan *userRestoreFuture
// stable is a StableStore implementation for durable state
// It provides stable storage for many fields in raftState
stable StableStore
// The transport layer we use
trans Transport
// verifyCh is used to async send verify futures to the main thread
// to verify we are still the leader
verifyCh chan *verifyFuture
// configurationsCh is used to get the configuration data safely from
// outside of the main thread.
configurationsCh chan *configurationsFuture
// bootstrapCh is used to attempt an initial bootstrap from outside of
// the main thread.
bootstrapCh chan *bootstrapFuture
// List of observers and the mutex that protects them. The observers list
// is indexed by an artificial ID which is used for deregistration.
observersLock sync.RWMutex
observers map[uint64]*Observer
// leadershipTransferCh is used to start a leadership transfer from outside of
// the main thread.
leadershipTransferCh chan *leadershipTransferFuture
// leaderNotifyCh is used to tell leader that config has changed
leaderNotifyCh chan struct{}
// followerNotifyCh is used to tell followers that config has changed
followerNotifyCh chan struct{}
// mainThreadSaturation measures the saturation of the main raft goroutine.
mainThreadSaturation *saturationMetric
// preVoteDisabled control if the pre-vote feature is activated,
// prevote feature is disabled if set to true.
preVoteDisabled bool
// noLegacyTelemetry allows to skip the legacy metrics to avoid duplicates.
// legacy metrics are those that have `_peer_name` as metric suffix instead as labels.
// e.g: raft_replication_heartbeat_peer0
noLegacyTelemetry bool
}
// BootstrapCluster initializes a server's storage with the given cluster
// configuration. This should only be called at the beginning of time for the
// cluster with an identical configuration listing all Voter servers. There is
// no need to bootstrap Nonvoter and Staging servers.
//
// A cluster can only be bootstrapped once from a single participating Voter
// server. Any further attempts to bootstrap will return an error that can be
// safely ignored.
//
// One approach is to bootstrap a single server with a configuration
// listing just itself as a Voter, then invoke AddVoter() on it to add other
// servers to the cluster.
func BootstrapCluster(conf *Config, logs LogStore, stable StableStore,
snaps SnapshotStore, trans Transport, configuration Configuration,
) error {
// Validate the Raft server config.
if err := ValidateConfig(conf); err != nil {
return err
}
// Sanity check the Raft peer configuration.
if err := checkConfiguration(configuration); err != nil {
return err
}
// Make sure the cluster is in a clean state.
hasState, err := HasExistingState(logs, stable, snaps)
if err != nil {
return fmt.Errorf("failed to check for existing state: %v", err)
}
if hasState {
return ErrCantBootstrap
}
// Set current term to 1.
if err := stable.SetUint64(keyCurrentTerm, 1); err != nil {
return fmt.Errorf("failed to save current term: %v", err)
}
// Append configuration entry to log.
entry := &Log{
Index: 1,
Term: 1,
}
if conf.ProtocolVersion < 3 {
entry.Type = LogRemovePeerDeprecated
entry.Data = encodePeers(configuration, trans)
} else {
entry.Type = LogConfiguration
entry.Data = EncodeConfiguration(configuration)
}
if err := logs.StoreLog(entry); err != nil {
return fmt.Errorf("failed to append configuration entry to log: %v", err)
}
return nil
}
// RecoverCluster is used to manually force a new configuration in order to
// recover from a loss of quorum where the current configuration cannot be
// restored, such as when several servers die at the same time. This works by
// reading all the current state for this server, creating a snapshot with the
// supplied configuration, and then truncating the Raft log. This is the only
// safe way to force a given configuration without actually altering the log to
// insert any new entries, which could cause conflicts with other servers with
// different state.
//
// WARNING! This operation implicitly commits all entries in the Raft log, so
// in general this is an extremely unsafe operation. If you've lost your other
// servers and are performing a manual recovery, then you've also lost the
// commit information, so this is likely the best you can do, but you should be
// aware that calling this can cause Raft log entries that were in the process
// of being replicated but not yet be committed to be committed.
//
// Note the FSM passed here is used for the snapshot operations and will be
// left in a state that should not be used by the application. Be sure to
// discard this FSM and any associated state and provide a fresh one when
// calling NewRaft later.
//
// A typical way to recover the cluster is to shut down all servers and then
// run RecoverCluster on every server using an identical configuration. When
// the cluster is then restarted, and election should occur and then Raft will
// resume normal operation. If it's desired to make a particular server the
// leader, this can be used to inject a new configuration with that server as
// the sole voter, and then join up other new clean-state peer servers using
// the usual APIs in order to bring the cluster back into a known state.
func RecoverCluster(conf *Config, fsm FSM, logs LogStore, stable StableStore,
snaps SnapshotStore, trans Transport, configuration Configuration,
) error {
// Validate the Raft server config.
if err := ValidateConfig(conf); err != nil {
return err
}
// Sanity check the Raft peer configuration.
if err := checkConfiguration(configuration); err != nil {
return err
}
// Refuse to recover if there's no existing state. This would be safe to
// do, but it is likely an indication of an operator error where they
// expect data to be there and it's not. By refusing, we force them
// to show intent to start a cluster fresh by explicitly doing a
// bootstrap, rather than quietly fire up a fresh cluster here.
if hasState, err := HasExistingState(logs, stable, snaps); err != nil {
return fmt.Errorf("failed to check for existing state: %v", err)
} else if !hasState {
return fmt.Errorf("refused to recover cluster with no initial state, this is probably an operator error")
}
// Attempt to restore any snapshots we find, newest to oldest.
var (
snapshotIndex uint64
snapshotTerm uint64
snapshots, err = snaps.List()
)
if err != nil {
return fmt.Errorf("failed to list snapshots: %v", err)
}
logger := conf.getOrCreateLogger()
for _, snapshot := range snapshots {
var source io.ReadCloser
_, source, err = snaps.Open(snapshot.ID)
if err != nil {
// Skip this one and try the next. We will detect if we
// couldn't open any snapshots.
continue
}
// Note this is the one place we call fsm.Restore without the
// fsmRestoreAndMeasure wrapper since this function should only be called to
// reset state on disk and the FSM passed will not be used for a running
// server instance. If the same process will eventually become a Raft peer
// then it will call NewRaft and restore again from disk then which will
// report metrics.
snapLogger := logger.With(
"id", snapshot.ID,
"last-index", snapshot.Index,
"last-term", snapshot.Term,
"size-in-bytes", snapshot.Size,
)
crc := newCountingReadCloser(source)
monitor := startSnapshotRestoreMonitor(snapLogger, crc, snapshot.Size, false)
err = fsm.Restore(crc)
// Close the source after the restore has completed
_ = source.Close()
monitor.StopAndWait()
if err != nil {
// Same here, skip and try the next one.
continue
}
snapshotIndex = snapshot.Index
snapshotTerm = snapshot.Term
break
}
if len(snapshots) > 0 && (snapshotIndex == 0 || snapshotTerm == 0) {
return fmt.Errorf("failed to restore any of the available snapshots")
}
// The snapshot information is the best known end point for the data
// until we play back the Raft log entries.
lastIndex := snapshotIndex
lastTerm := snapshotTerm
// Apply any Raft log entries past the snapshot.
lastLogIndex, err := logs.LastIndex()
if err != nil {
return fmt.Errorf("failed to find last log: %v", err)
}
for index := snapshotIndex + 1; index <= lastLogIndex; index++ {
var entry Log
if err = logs.GetLog(index, &entry); err != nil {
return fmt.Errorf("failed to get log at index %d: %v", index, err)
}
if entry.Type == LogCommand {
_ = fsm.Apply(&entry)
}
lastIndex = entry.Index
lastTerm = entry.Term
}
// Create a new snapshot, placing the configuration in as if it was
// committed at index 1.
snapshot, err := fsm.Snapshot()
if err != nil {
return fmt.Errorf("failed to snapshot FSM: %v", err)
}
version := getSnapshotVersion(conf.ProtocolVersion)
sink, err := snaps.Create(version, lastIndex, lastTerm, configuration, 1, trans)
if err != nil {
return fmt.Errorf("failed to create snapshot: %v", err)
}
if err = snapshot.Persist(sink); err != nil {
return fmt.Errorf("failed to persist snapshot: %v", err)
}
if err = sink.Close(); err != nil {
return fmt.Errorf("failed to finalize snapshot: %v", err)
}
// Compact the log so that we don't get bad interference from any
// configuration change log entries that might be there.
firstLogIndex, err := logs.FirstIndex()
if err != nil {
return fmt.Errorf("failed to get first log index: %v", err)
}
if err := logs.DeleteRange(firstLogIndex, lastLogIndex); err != nil {
return fmt.Errorf("log compaction failed: %v", err)
}
return nil
}
// GetConfiguration returns the persisted configuration of the Raft cluster
// without starting a Raft instance or connecting to the cluster. This function
// has identical behavior to Raft.GetConfiguration.
func GetConfiguration(conf *Config, fsm FSM, logs LogStore, stable StableStore,
snaps SnapshotStore, trans Transport,
) (Configuration, error) {
conf.skipStartup = true
r, err := NewRaft(conf, fsm, logs, stable, snaps, trans)
if err != nil {
return Configuration{}, err
}
future := r.GetConfiguration()
if err = future.Error(); err != nil {
return Configuration{}, err
}
return future.Configuration(), nil
}
// HasExistingState returns true if the server has any existing state (logs,
// knowledge of a current term, or any snapshots).
func HasExistingState(logs LogStore, stable StableStore, snaps SnapshotStore) (bool, error) {
// Make sure we don't have a current term.
currentTerm, err := stable.GetUint64(keyCurrentTerm)
if err == nil {
if currentTerm > 0 {
return true, nil
}
} else {
if err.Error() != "not found" {
return false, fmt.Errorf("failed to read current term: %v", err)
}
}
// Make sure we have an empty log.
lastIndex, err := logs.LastIndex()
if err != nil {
return false, fmt.Errorf("failed to get last log index: %v", err)
}
if lastIndex > 0 {
return true, nil
}
// Make sure we have no snapshots
snapshots, err := snaps.List()
if err != nil {
return false, fmt.Errorf("failed to list snapshots: %v", err)
}
if len(snapshots) > 0 {
return true, nil
}
return false, nil
}
// NewRaft is used to construct a new Raft node. It takes a configuration, as well
// as implementations of various interfaces that are required. If we have any
// old state, such as snapshots, logs, peers, etc, all those will be restored
// when creating the Raft node.
func NewRaft(conf *Config, fsm FSM, logs LogStore, stable StableStore, snaps SnapshotStore, trans Transport) (*Raft, error) {
// Validate the configuration.
if err := ValidateConfig(conf); err != nil {
return nil, err
}
// Ensure we have a LogOutput.
logger := conf.getOrCreateLogger()
// Try to restore the current term.
currentTerm, err := stable.GetUint64(keyCurrentTerm)
if err != nil && err.Error() != "not found" {
return nil, fmt.Errorf("failed to load current term: %v", err)
}
// Read the index of the last log entry.
lastIndex, err := logs.LastIndex()
if err != nil {
return nil, fmt.Errorf("failed to find last log: %v", err)
}
// Get the last log entry.
var lastLog Log
if lastIndex > 0 {
if err = logs.GetLog(lastIndex, &lastLog); err != nil {
return nil, fmt.Errorf("failed to get last log at index %d: %v", lastIndex, err)
}
}
// Make sure we have a valid server address and ID.
protocolVersion := conf.ProtocolVersion
localAddr := trans.LocalAddr()
localID := conf.LocalID
// TODO (slackpad) - When we deprecate protocol version 2, remove this
// along with the AddPeer() and RemovePeer() APIs.
if protocolVersion < 3 && string(localID) != string(localAddr) {
return nil, fmt.Errorf("when running with ProtocolVersion < 3, LocalID must be set to the network address")
}
// Buffer applyCh to MaxAppendEntries if the option is enabled
applyCh := make(chan *logFuture)
if conf.BatchApplyCh {
applyCh = make(chan *logFuture, conf.MaxAppendEntries)
}
_, transportSupportPreVote := trans.(WithPreVote)
// Create Raft struct.
r := &Raft{
protocolVersion: protocolVersion,
applyCh: applyCh,
fsm: fsm,
fsmMutateCh: make(chan interface{}, 128),
fsmSnapshotCh: make(chan *reqSnapshotFuture),
leaderCh: make(chan bool, 1),
localID: localID,
localAddr: localAddr,
logger: logger,
logs: logs,
configurationChangeCh: make(chan *configurationChangeFuture),
configurations: configurations{},
rpcCh: trans.Consumer(),
snapshots: snaps,
userSnapshotCh: make(chan *userSnapshotFuture),
userRestoreCh: make(chan *userRestoreFuture),
shutdownCh: make(chan struct{}),
stable: stable,
trans: trans,
verifyCh: make(chan *verifyFuture, 64),
configurationsCh: make(chan *configurationsFuture, 8),
bootstrapCh: make(chan *bootstrapFuture),
observers: make(map[uint64]*Observer),
leadershipTransferCh: make(chan *leadershipTransferFuture, 1),
leaderNotifyCh: make(chan struct{}, 1),
followerNotifyCh: make(chan struct{}, 1),
mainThreadSaturation: newSaturationMetric([]string{"raft", "thread", "main", "saturation"}, 1*time.Second),
preVoteDisabled: conf.PreVoteDisabled || !transportSupportPreVote,
noLegacyTelemetry: conf.NoLegacyTelemetry,
}
if !transportSupportPreVote && !conf.PreVoteDisabled {
r.logger.Warn("pre-vote is disabled because it is not supported by the Transport")
}
r.conf.Store(*conf)
// Initialize as a follower.
r.setState(Follower)
// Restore the current term and the last log.
r.setCurrentTerm(currentTerm)
r.setLastLog(lastLog.Index, lastLog.Term)
// Attempt to restore a snapshot if there are any.
if err := r.restoreSnapshot(); err != nil {
return nil, err
}
// Scan through the log for any configuration change entries.
snapshotIndex, _ := r.getLastSnapshot()
for index := snapshotIndex + 1; index <= lastLog.Index; index++ {
var entry Log
if err := r.logs.GetLog(index, &entry); err != nil {
r.logger.Error("failed to get log", "index", index, "error", err)
panic(err)
}
if err := r.processConfigurationLogEntry(&entry); err != nil {
return nil, err
}
}
r.logger.Info("initial configuration",
"index", r.configurations.latestIndex,
"servers", hclog.Fmt("%+v", r.configurations.latest.Servers))
// Setup a heartbeat fast-path to avoid head-of-line
// blocking where possible. It MUST be safe for this
// to be called concurrently with a blocking RPC.
trans.SetHeartbeatHandler(r.processHeartbeat)
if conf.skipStartup {
return r, nil
}
// Start the background work.
r.goFunc(r.run)
r.goFunc(r.runFSM)
r.goFunc(r.runSnapshots)
return r, nil
}
// restoreSnapshot attempts to restore the latest snapshots, and fails if none
// of them can be restored. This is called at initialization time, and is
// completely unsafe to call at any other time.
func (r *Raft) restoreSnapshot() error {
snapshots, err := r.snapshots.List()
if err != nil {
r.logger.Error("failed to list snapshots", "error", err)
return err
}
// Try to load in order of newest to oldest
for _, snapshot := range snapshots {
if success := r.tryRestoreSingleSnapshot(snapshot); !success {
continue
}
// Update the lastApplied so we don't replay old logs
r.setLastApplied(snapshot.Index)
// Update the last stable snapshot info
r.setLastSnapshot(snapshot.Index, snapshot.Term)
// Update the configuration
var conf Configuration
var index uint64
if snapshot.Version > 0 {
conf = snapshot.Configuration
index = snapshot.ConfigurationIndex
} else {
var err error
if conf, err = decodePeers(snapshot.Peers, r.trans); err != nil {
return err
}
index = snapshot.Index
}
r.setCommittedConfiguration(conf, index)
r.setLatestConfiguration(conf, index)
// Success!
return nil
}
// If we had snapshots and failed to load them, its an error
if len(snapshots) > 0 {
return fmt.Errorf("failed to load any existing snapshots")
}
return nil
}
func (r *Raft) tryRestoreSingleSnapshot(snapshot *SnapshotMeta) bool {
if r.config().NoSnapshotRestoreOnStart {
return true
}
snapLogger := r.logger.With(
"id", snapshot.ID,
"last-index", snapshot.Index,
"last-term", snapshot.Term,
"size-in-bytes", snapshot.Size,
)
snapLogger.Info("starting restore from snapshot")
_, source, err := r.snapshots.Open(snapshot.ID)
if err != nil {
snapLogger.Error("failed to open snapshot", "error", err)
return false
}
if err := fsmRestoreAndMeasure(snapLogger, r.fsm, source, snapshot.Size); err != nil {
_ = source.Close()
snapLogger.Error("failed to restore snapshot", "error", err)
return false
}
_ = source.Close()
snapLogger.Info("restored from snapshot")
return true
}
func (r *Raft) config() Config {
return r.conf.Load().(Config)
}
// ReloadConfig updates the configuration of a running raft node. If the new
// configuration is invalid an error is returned and no changes made to the
// instance. All fields will be copied from rc into the new configuration, even
// if they are zero valued.
func (r *Raft) ReloadConfig(rc ReloadableConfig) error {
r.confReloadMu.Lock()
defer r.confReloadMu.Unlock()
// Load the current config (note we are under a lock so it can't be changed
// between this read and a later Store).
oldCfg := r.config()
// Set the reloadable fields
newCfg := rc.apply(oldCfg)
if err := ValidateConfig(&newCfg); err != nil {
return err
}
r.conf.Store(newCfg)
if rc.HeartbeatTimeout < oldCfg.HeartbeatTimeout {
// On leader, ensure replication loops running with a longer
// timeout than what we want now discover the change.
asyncNotifyCh(r.leaderNotifyCh)
// On follower, update current timer to use the shorter new value.
asyncNotifyCh(r.followerNotifyCh)
}
return nil
}
// ReloadableConfig returns the current state of the reloadable fields in Raft's
// configuration. This is useful for programs to discover the current state for
// reporting to users or tests. It is safe to call from any goroutine. It is
// intended for reporting and testing purposes primarily; external
// synchronization would be required to safely use this in a read-modify-write
// pattern for reloadable configuration options.
func (r *Raft) ReloadableConfig() ReloadableConfig {
cfg := r.config()
var rc ReloadableConfig
rc.fromConfig(cfg)
return rc
}
// BootstrapCluster is equivalent to non-member BootstrapCluster but can be
// called on an un-bootstrapped Raft instance after it has been created. This
// should only be called at the beginning of time for the cluster with an
// identical configuration listing all Voter servers. There is no need to
// bootstrap Nonvoter and Staging servers.
//
// A cluster can only be bootstrapped once from a single participating Voter
// server. Any further attempts to bootstrap will return an error that can be
// safely ignored.
//
// One sane approach is to bootstrap a single server with a configuration
// listing just itself as a Voter, then invoke AddVoter() on it to add other
// servers to the cluster.
func (r *Raft) BootstrapCluster(configuration Configuration) Future {
bootstrapReq := &bootstrapFuture{}
bootstrapReq.init()
bootstrapReq.configuration = configuration
select {
case <-r.shutdownCh:
return errorFuture{ErrRaftShutdown}
case r.bootstrapCh <- bootstrapReq:
return bootstrapReq
}
}
// Leader is used to return the current leader of the cluster.
// Deprecated: use LeaderWithID instead
// It may return empty string if there is no current leader
// or the leader is unknown.
// Deprecated: use LeaderWithID instead.
func (r *Raft) Leader() ServerAddress {
r.leaderLock.RLock()
leaderAddr := r.leaderAddr
r.leaderLock.RUnlock()
return leaderAddr
}
// LeaderWithID is used to return the current leader address and ID of the cluster.
// It may return empty strings if there is no current leader
// or the leader is unknown.
func (r *Raft) LeaderWithID() (ServerAddress, ServerID) {
r.leaderLock.RLock()
leaderAddr := r.leaderAddr
leaderID := r.leaderID
r.leaderLock.RUnlock()
return leaderAddr, leaderID
}
// Apply is used to apply a command to the FSM in a highly consistent
// manner. This returns a future that can be used to wait on the application.
// An optional timeout can be provided to limit the amount of time we wait
// for the command to be started. This must be run on the leader or it
// will fail.
//
// If the node discovers it is no longer the leader while applying the command,
// it will return ErrLeadershipLost. There is no way to guarantee whether the
// write succeeded or failed in this case. For example, if the leader is
// partitioned it can't know if a quorum of followers wrote the log to disk. If
// at least one did, it may survive into the next leader's term.
//
// If a user snapshot is restored while the command is in-flight, an
// ErrAbortedByRestore is returned. In this case the write effectively failed
// since its effects will not be present in the FSM after the restore.
func (r *Raft) Apply(cmd []byte, timeout time.Duration) ApplyFuture {
return r.ApplyLog(Log{Data: cmd}, timeout)
}
// ApplyLog performs Apply but takes in a Log directly. The only values
// currently taken from the submitted Log are Data and Extensions. See
// Apply for details on error cases.
func (r *Raft) ApplyLog(log Log, timeout time.Duration) ApplyFuture {
metrics.IncrCounter([]string{"raft", "apply"}, 1)
var timer <-chan time.Time
if timeout > 0 {
timer = time.After(timeout)
}
// Create a log future, no index or term yet
logFuture := &logFuture{
log: Log{
Type: LogCommand,
Data: log.Data,
Extensions: log.Extensions,
},
}
logFuture.init()
select {
case <-timer:
return errorFuture{ErrEnqueueTimeout}
case <-r.shutdownCh:
return errorFuture{ErrRaftShutdown}
case r.applyCh <- logFuture:
return logFuture
}
}
// Barrier is used to issue a command that blocks until all preceding
// operations have been applied to the FSM. It can be used to ensure the
// FSM reflects all queued writes. An optional timeout can be provided to
// limit the amount of time we wait for the command to be started. This
// must be run on the leader, or it will fail.
func (r *Raft) Barrier(timeout time.Duration) Future {
metrics.IncrCounter([]string{"raft", "barrier"}, 1)
var timer <-chan time.Time
if timeout > 0 {
timer = time.After(timeout)
}
// Create a log future, no index or term yet
logFuture := &logFuture{log: Log{Type: LogBarrier}}
logFuture.init()
select {
case <-timer:
return errorFuture{ErrEnqueueTimeout}
case <-r.shutdownCh:
return errorFuture{ErrRaftShutdown}
case r.applyCh <- logFuture:
return logFuture
}
}
// VerifyLeader is used to ensure this peer is still the leader. It may be used
// to prevent returning stale data from the FSM after the peer has lost
// leadership.
func (r *Raft) VerifyLeader() Future {
metrics.IncrCounter([]string{"raft", "verify_leader"}, 1)
verifyFuture := &verifyFuture{}
verifyFuture.init()
select {
case <-r.shutdownCh:
return errorFuture{ErrRaftShutdown}
case r.verifyCh <- verifyFuture:
return verifyFuture
}
}
// GetConfiguration returns the latest configuration. This may not yet be
// committed. The main loop can access this directly.
func (r *Raft) GetConfiguration() ConfigurationFuture {
configReq := &configurationsFuture{}
configReq.init()
configReq.configurations = configurations{latest: r.getLatestConfiguration()}
configReq.respond(nil)
return configReq
}
// AddPeer to the cluster configuration. Must be run on the leader, or it will fail.
//
// Deprecated: Use AddVoter/AddNonvoter instead.
func (r *Raft) AddPeer(peer ServerAddress) Future {
if r.protocolVersion > 2 {
return errorFuture{ErrUnsupportedProtocol}
}
return r.requestConfigChange(configurationChangeRequest{
command: AddVoter,
serverID: ServerID(peer),
serverAddress: peer,
prevIndex: 0,
}, 0)
}
// RemovePeer from the cluster configuration. If the current leader is being
// removed, it will cause a new election to occur. Must be run on the leader,
// or it will fail.
// Deprecated: Use RemoveServer instead.
func (r *Raft) RemovePeer(peer ServerAddress) Future {
if r.protocolVersion > 2 {
return errorFuture{ErrUnsupportedProtocol}
}
return r.requestConfigChange(configurationChangeRequest{
command: RemoveServer,
serverID: ServerID(peer),
prevIndex: 0,
}, 0)
}
// AddVoter will add the given server to the cluster as a staging server. If the
// server is already in the cluster as a voter, this updates the server's address.
// This must be run on the leader or it will fail. The leader will promote the
// staging server to a voter once that server is ready. If nonzero, prevIndex is
// the index of the only configuration upon which this change may be applied; if
// another configuration entry has been added in the meantime, this request will
// fail. If nonzero, timeout is how long this server should wait before the
// configuration change log entry is appended.
func (r *Raft) AddVoter(id ServerID, address ServerAddress, prevIndex uint64, timeout time.Duration) IndexFuture {
if r.protocolVersion < 2 {
return errorFuture{ErrUnsupportedProtocol}
}
return r.requestConfigChange(configurationChangeRequest{
command: AddVoter,
serverID: id,
serverAddress: address,
prevIndex: prevIndex,
}, timeout)
}
// AddNonvoter will add the given server to the cluster but won't assign it a
// vote. The server will receive log entries, but it won't participate in
// elections or log entry commitment. If the server is already in the cluster,
// this updates the server's address. This must be run on the leader or it will
// fail. For prevIndex and timeout, see AddVoter.
func (r *Raft) AddNonvoter(id ServerID, address ServerAddress, prevIndex uint64, timeout time.Duration) IndexFuture {
if r.protocolVersion < 3 {
return errorFuture{ErrUnsupportedProtocol}
}
return r.requestConfigChange(configurationChangeRequest{
command: AddNonvoter,
serverID: id,
serverAddress: address,
prevIndex: prevIndex,
}, timeout)
}
// RemoveServer will remove the given server from the cluster. If the current
// leader is being removed, it will cause a new election to occur. This must be
// run on the leader or it will fail. For prevIndex and timeout, see AddVoter.
func (r *Raft) RemoveServer(id ServerID, prevIndex uint64, timeout time.Duration) IndexFuture {
if r.protocolVersion < 2 {
return errorFuture{ErrUnsupportedProtocol}
}
return r.requestConfigChange(configurationChangeRequest{
command: RemoveServer,
serverID: id,
prevIndex: prevIndex,
}, timeout)
}
// DemoteVoter will take away a server's vote, if it has one. If present, the
// server will continue to receive log entries, but it won't participate in
// elections or log entry commitment. If the server is not in the cluster, this
// does nothing. This must be run on the leader or it will fail. For prevIndex
// and timeout, see AddVoter.
func (r *Raft) DemoteVoter(id ServerID, prevIndex uint64, timeout time.Duration) IndexFuture {
if r.protocolVersion < 3 {
return errorFuture{ErrUnsupportedProtocol}
}
return r.requestConfigChange(configurationChangeRequest{
command: DemoteVoter,
serverID: id,
prevIndex: prevIndex,
}, timeout)
}
// Shutdown is used to stop the Raft background routines.
// This is not a graceful operation. Provides a future that
// can be used to block until all background routines have exited.
func (r *Raft) Shutdown() Future {
r.shutdownLock.Lock()
defer r.shutdownLock.Unlock()
if !r.shutdown {
close(r.shutdownCh)
r.shutdown = true
r.setState(Shutdown)
return &shutdownFuture{r}
}
// avoid closing transport twice
return &shutdownFuture{nil}
}
// Snapshot is used to manually force Raft to take a snapshot. Returns a future
// that can be used to block until complete, and that contains a function that
// can be used to open the snapshot.
func (r *Raft) Snapshot() SnapshotFuture {
future := &userSnapshotFuture{}
future.init()
select {
case r.userSnapshotCh <- future:
return future
case <-r.shutdownCh:
future.respond(ErrRaftShutdown)
return future
}
}
// Restore is used to manually force Raft to consume an external snapshot, such
// as if restoring from a backup. We will use the current Raft configuration,
// not the one from the snapshot, so that we can restore into a new cluster. We
// will also use the max of the index of the snapshot, or the current index,
// and then add 1 to that, so we force a new state with a hole in the Raft log,
// so that the snapshot will be sent to followers and used for any new joiners.
// This can only be run on the leader, and blocks until the restore is complete
// or an error occurs.
//
// WARNING! This operation has the leader take on the state of the snapshot and
// then sets itself up so that it replicates that to its followers though the
// install snapshot process. This involves a potentially dangerous period where
// the leader commits ahead of its followers, so should only be used for disaster
// recovery into a fresh cluster, and should not be used in normal operations.
func (r *Raft) Restore(meta *SnapshotMeta, reader io.Reader, timeout time.Duration) error {
metrics.IncrCounter([]string{"raft", "restore"}, 1)
var timer <-chan time.Time
if timeout > 0 {
timer = time.After(timeout)
}
// Perform the restore.
restore := &userRestoreFuture{
meta: meta,
reader: reader,
}
restore.init()
select {
case <-timer:
return ErrEnqueueTimeout
case <-r.shutdownCh:
return ErrRaftShutdown
case r.userRestoreCh <- restore:
// If the restore is ingested then wait for it to complete.
if err := restore.Error(); err != nil {
return err
}
}
// Apply a no-op log entry. Waiting for this allows us to wait until the
// followers have gotten the restore and replicated at least this new
// entry, which shows that we've also faulted and installed the
// snapshot with the contents of the restore.
noop := &logFuture{
log: Log{
Type: LogNoop,
},
}
noop.init()
select {
case <-timer:
return ErrEnqueueTimeout
case <-r.shutdownCh:
return ErrRaftShutdown
case r.applyCh <- noop:
return noop.Error()
}
}
// State returns the state of this raft peer.
func (r *Raft) State() RaftState {
return r.getState()
}
// LeaderCh is used to get a channel which delivers signals on acquiring or
// losing leadership. It sends true if we become the leader, and false if we
// lose it.
//
// Receivers can expect to receive a notification only if leadership
// transition has occurred.
//
// If receivers aren't ready for the signal, signals may drop and only the
// latest leadership transition. For example, if a receiver receives subsequent
// `true` values, they may deduce that leadership was lost and regained while
// the receiver was processing first leadership transition.
func (r *Raft) LeaderCh() <-chan bool {
return r.leaderCh
}
// String returns a string representation of this Raft node.
func (r *Raft) String() string {
return fmt.Sprintf("Node at %s [%v]", r.localAddr, r.getState())
}
// LastContact returns the time of last contact by a leader.
// This only makes sense if we are currently a follower.
func (r *Raft) LastContact() time.Time {
r.lastContactLock.RLock()
last := r.lastContact
r.lastContactLock.RUnlock()
return last
}
// Stats is used to return a map of various internal stats. This
// should only be used for informative purposes or debugging.
//
// Keys are: "state", "term", "last_log_index", "last_log_term",
// "commit_index", "applied_index", "fsm_pending",
// "last_snapshot_index", "last_snapshot_term",
// "latest_configuration", "last_contact", and "num_peers".
//
// The value of "state" is a numeric constant representing one of
// the possible leadership states the node is in at any given time.
// the possible states are: "Follower", "Candidate", "Leader", "Shutdown".
//
// The value of "latest_configuration" is a string which contains
// the id of each server, its suffrage status, and its address.
//
// The value of "last_contact" is either "never" if there
// has been no contact with a leader, "0" if the node is in the
// leader state, or the time since last contact with a leader
// formatted as a string.
//
// The value of "num_peers" is the number of other voting servers in the
// cluster, not including this node. If this node isn't part of the
// configuration then this will be "0".
//
// All other values are uint64s, formatted as strings.
func (r *Raft) Stats() map[string]string {
toString := func(v uint64) string {
return strconv.FormatUint(v, 10)
}
lastLogIndex, lastLogTerm := r.getLastLog()
lastSnapIndex, lastSnapTerm := r.getLastSnapshot()
s := map[string]string{
"state": r.getState().String(),
"term": toString(r.getCurrentTerm()),
"last_log_index": toString(lastLogIndex),
"last_log_term": toString(lastLogTerm),
"commit_index": toString(r.getCommitIndex()),
"applied_index": toString(r.getLastApplied()),
"fsm_pending": toString(uint64(len(r.fsmMutateCh))),
"last_snapshot_index": toString(lastSnapIndex),
"last_snapshot_term": toString(lastSnapTerm),
"protocol_version": toString(uint64(r.protocolVersion)),
"protocol_version_min": toString(uint64(ProtocolVersionMin)),
"protocol_version_max": toString(uint64(ProtocolVersionMax)),
"snapshot_version_min": toString(uint64(SnapshotVersionMin)),
"snapshot_version_max": toString(uint64(SnapshotVersionMax)),
}
future := r.GetConfiguration()
if err := future.Error(); err != nil {
r.logger.Warn("could not get configuration for stats", "error", err)
} else {
configuration := future.Configuration()
s["latest_configuration_index"] = toString(future.Index())
s["latest_configuration"] = fmt.Sprintf("%+v", configuration.Servers)
// This is a legacy metric that we've seen people use in the wild.
hasUs := false
numPeers := 0
for _, server := range configuration.Servers {
if server.Suffrage == Voter {
if server.ID == r.localID {
hasUs = true
} else {
numPeers++
}
}
}
if !hasUs {
numPeers = 0
}
s["num_peers"] = toString(uint64(numPeers))
}
last := r.LastContact()
if r.getState() == Leader {
s["last_contact"] = "0"
} else if last.IsZero() {
s["last_contact"] = "never"
} else {
s["last_contact"] = fmt.Sprintf("%v", time.Since(last))
}
return s
}
// CurrentTerm returns the current term.
func (r *Raft) CurrentTerm() uint64 {
return r.getCurrentTerm()
}
// LastIndex returns the last index in stable storage,
// either from the last log or from the last snapshot.
func (r *Raft) LastIndex() uint64 {
return r.getLastIndex()
}
// CommitIndex returns the committed index.
// This API maybe helpful for server to implement the read index optimization
// as described in the Raft paper.
func (r *Raft) CommitIndex() uint64 {
return r.getCommitIndex()
}
// AppliedIndex returns the last index applied to the FSM. This is generally
// lagging behind the last index, especially for indexes that are persisted but
// have not yet been considered committed by the leader. NOTE - this reflects
// the last index that was sent to the application's FSM over the apply channel
// but DOES NOT mean that the application's FSM has yet consumed it and applied
// it to its internal state. Thus, the application's state may lag behind this
// index.
func (r *Raft) AppliedIndex() uint64 {
return r.getLastApplied()
}
// LeadershipTransfer will transfer leadership to a server in the cluster.
// This can only be called from the leader, or it will fail. The leader will
// stop accepting client requests, make sure the target server is up to date
// and starts the transfer with a TimeoutNow message. This message has the same
// effect as if the election timeout on the target server fires. Since
// it is unlikely that another server is starting an election, it is very
// likely that the target server is able to win the election. Note that raft
// protocol version 3 is not sufficient to use LeadershipTransfer. A recent
// version of that library has to be used that includes this feature. Using
// transfer leadership is safe however in a cluster where not every node has
// the latest version. If a follower cannot be promoted, it will fail
// gracefully.
func (r *Raft) LeadershipTransfer() Future {
if r.protocolVersion < 3 {
return errorFuture{ErrUnsupportedProtocol}
}
return r.initiateLeadershipTransfer(nil, nil)
}
// LeadershipTransferToServer does the same as LeadershipTransfer but takes a
// server in the arguments in case a leadership should be transitioned to a
// specific server in the cluster. Note that raft protocol version 3 is not
// sufficient to use LeadershipTransfer. A recent version of that library has
// to be used that includes this feature. Using transfer leadership is safe
// however in a cluster where not every node has the latest version. If a
// follower cannot be promoted, it will fail gracefully.
func (r *Raft) LeadershipTransferToServer(id ServerID, address ServerAddress) Future {
if r.protocolVersion < 3 {
return errorFuture{ErrUnsupportedProtocol}
}
return r.initiateLeadershipTransfer(&id, &address)
}
================================================
FILE: bench/bench.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raftbench
// raftbench provides common benchmarking functions which can be used by
// anything which implements the raft.LogStore and raft.StableStore interfaces.
// All functions accept these interfaces and perform benchmarking. This
// makes comparing backend performance easier by sharing the tests.
import (
"testing"
"github.com/hashicorp/raft"
)
func FirstIndex(b *testing.B, store raft.LogStore) {
// Create some fake data
var logs []*raft.Log
for i := 1; i < 10; i++ {
logs = append(logs, &raft.Log{Index: uint64(i), Data: []byte("data")})
}
if err := store.StoreLogs(logs); err != nil {
b.Fatalf("err: %s", err)
}
b.ResetTimer()
// Run FirstIndex a number of times
for n := 0; n < b.N; n++ {
_, _ = store.FirstIndex()
}
}
func LastIndex(b *testing.B, store raft.LogStore) {
// Create some fake data
var logs []*raft.Log
for i := 1; i < 10; i++ {
logs = append(logs, &raft.Log{Index: uint64(i), Data: []byte("data")})
}
if err := store.StoreLogs(logs); err != nil {
b.Fatalf("err: %s", err)
}
b.ResetTimer()
// Run LastIndex a number of times
for n := 0; n < b.N; n++ {
_, _ = store.LastIndex()
}
}
func GetLog(b *testing.B, store raft.LogStore) {
// Create some fake data
var logs []*raft.Log
for i := 1; i < 10; i++ {
logs = append(logs, &raft.Log{Index: uint64(i), Data: []byte("data")})
}
if err := store.StoreLogs(logs); err != nil {
b.Fatalf("err: %s", err)
}
b.ResetTimer()
// Run GetLog a number of times
for n := 0; n < b.N; n++ {
if err := store.GetLog(5, new(raft.Log)); err != nil {
b.Fatalf("err: %s", err)
}
}
}
func StoreLog(b *testing.B, store raft.LogStore) {
// Run StoreLog a number of times
for n := 0; n < b.N; n++ {
log := &raft.Log{Index: uint64(n), Data: []byte("data")}
if err := store.StoreLog(log); err != nil {
b.Fatalf("err: %s", err)
}
}
}
func StoreLogs(b *testing.B, store raft.LogStore) {
// Run StoreLogs a number of times. We want to set multiple logs each
// run, so we create 3 logs with incrementing indexes for each iteration.
for n := 0; n < b.N; n++ {
b.StopTimer()
offset := 3 * (n + 1)
logs := []*raft.Log{
{Index: uint64(offset - 2), Data: []byte("data")},
{Index: uint64(offset - 1), Data: []byte("data")},
{Index: uint64(offset), Data: []byte("data")},
}
b.StartTimer()
if err := store.StoreLogs(logs); err != nil {
b.Fatalf("err: %s", err)
}
}
}
func DeleteRange(b *testing.B, store raft.LogStore) {
// Create some fake data. In this case, we create 3 new log entries for each
// test case, and separate them by index in multiples of 10. This allows
// some room so that we can test deleting ranges with "extra" logs
// to ensure we stop going to the database once our max index is hit.
var logs []*raft.Log
for n := 0; n < b.N; n++ {
offset := 10 * n
for i := offset; i < offset+3; i++ {
logs = append(logs, &raft.Log{Index: uint64(i), Data: []byte("data")})
}
}
if err := store.StoreLogs(logs); err != nil {
b.Fatalf("err: %s", err)
}
b.ResetTimer()
// Delete a range of the data
for n := 0; n < b.N; n++ {
offset := 10 * n
if err := store.DeleteRange(uint64(offset), uint64(offset+9)); err != nil {
b.Fatalf("err: %s", err)
}
}
}
func Set(b *testing.B, store raft.StableStore) {
// Run Set a number of times
for n := 0; n < b.N; n++ {
if err := store.Set([]byte{byte(n)}, []byte("val")); err != nil {
b.Fatalf("err: %s", err)
}
}
}
func Get(b *testing.B, store raft.StableStore) {
// Create some fake data
for i := 1; i < 10; i++ {
if err := store.Set([]byte{byte(i)}, []byte("val")); err != nil {
b.Fatalf("err: %s", err)
}
}
b.ResetTimer()
// Run Get a number of times
for n := 0; n < b.N; n++ {
if _, err := store.Get([]byte{0x05}); err != nil {
b.Fatalf("err: %s", err)
}
}
}
func SetUint64(b *testing.B, store raft.StableStore) {
// Run SetUint64 a number of times
for n := 0; n < b.N; n++ {
if err := store.SetUint64([]byte{byte(n)}, uint64(n)); err != nil {
b.Fatalf("err: %s", err)
}
}
}
func GetUint64(b *testing.B, store raft.StableStore) {
// Create some fake data
for i := 0; i < 10; i++ {
if err := store.SetUint64([]byte{byte(i)}, uint64(i)); err != nil {
b.Fatalf("err: %s", err)
}
}
b.ResetTimer()
// Run GetUint64 a number of times
for n := 0; n < b.N; n++ {
if _, err := store.GetUint64([]byte{0x05}); err != nil {
b.Fatalf("err: %s", err)
}
}
}
================================================
FILE: bench_test.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"testing"
"time"
"github.com/hashicorp/go-hclog"
)
func BenchmarkStoreLogInMem(b *testing.B) {
conf := DefaultConfig()
conf.LocalID = "first"
conf.HeartbeatTimeout = 50 * time.Millisecond
conf.ElectionTimeout = 50 * time.Millisecond
conf.LeaderLeaseTimeout = 50 * time.Millisecond
conf.CommitTimeout = 5 * time.Millisecond
conf.SnapshotThreshold = 100
conf.TrailingLogs = 10
conf.LogLevel = "OFF"
raft := MakeRaft(b, conf, true)
raft.logger.SetLevel(hclog.Off)
NoErr(WaitFor(raft, Leader), b)
applyAndWait := func(leader *RaftEnv, n, sz int) {
// Do some commits
var futures []ApplyFuture
for i := 0; i < n; i++ {
futures = append(futures, leader.raft.Apply(logBytes(i, sz), 0))
}
for _, f := range futures {
NoErr(WaitFuture(f), b)
leader.logger.Debug("applied", "index", f.Index(), "size", sz)
}
}
for i := 0; i < b.N; i++ {
// Do some commits
applyAndWait(raft, 100, 10)
// Do a snapshot
NoErr(WaitFuture(raft.raft.Snapshot()), b)
}
}
================================================
FILE: commands.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
// RPCHeader is a common sub-structure used to pass along protocol version and
// other information about the cluster. For older Raft implementations before
// versioning was added this will default to a zero-valued structure when read
// by newer Raft versions.
type RPCHeader struct {
// ProtocolVersion is the version of the protocol the sender is
// speaking.
ProtocolVersion ProtocolVersion
// ID is the ServerID of the node sending the RPC Request or Response
ID []byte
// Addr is the ServerAddr of the node sending the RPC Request or Response
Addr []byte
}
// WithRPCHeader is an interface that exposes the RPC header.
type WithRPCHeader interface {
GetRPCHeader() RPCHeader
}
// AppendEntriesRequest is the command used to append entries to the
// replicated log.
type AppendEntriesRequest struct {
RPCHeader
// Provide the current term and leader
Term uint64
// Deprecated: use RPCHeader.Addr instead
Leader []byte
// Provide the previous entries for integrity checking
PrevLogEntry uint64
PrevLogTerm uint64
// New entries to commit
Entries []*Log
// Commit index on the leader
LeaderCommitIndex uint64
}
// GetRPCHeader - See WithRPCHeader.
func (r *AppendEntriesRequest) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// AppendEntriesResponse is the response returned from an
// AppendEntriesRequest.
type AppendEntriesResponse struct {
RPCHeader
// Newer term if leader is out of date
Term uint64
// Last Log is a hint to help accelerate rebuilding slow nodes
LastLog uint64
// We may not succeed if we have a conflicting entry
Success bool
// There are scenarios where this request didn't succeed
// but there's no need to wait/back-off the next attempt.
NoRetryBackoff bool
}
// GetRPCHeader - See WithRPCHeader.
func (r *AppendEntriesResponse) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// RequestVoteRequest is the command used by a candidate to ask a Raft peer
// for a vote in an election.
type RequestVoteRequest struct {
RPCHeader
// Provide the term and our id
Term uint64
// Deprecated: use RPCHeader.Addr instead
Candidate []byte
// Used to ensure safety
LastLogIndex uint64
LastLogTerm uint64
// Used to indicate to peers if this vote was triggered by a leadership
// transfer. It is required for leadership transfer to work, because servers
// wouldn't vote otherwise if they are aware of an existing leader.
LeadershipTransfer bool
}
// GetRPCHeader - See WithRPCHeader.
func (r *RequestVoteRequest) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// RequestVoteResponse is the response returned from a RequestVoteRequest.
type RequestVoteResponse struct {
RPCHeader
// Newer term if leader is out of date.
Term uint64
// Peers is deprecated, but required by servers that only understand
// protocol version 0. This is not populated in protocol version 2
// and later.
Peers []byte
// Is the vote granted.
Granted bool
}
// GetRPCHeader - See WithRPCHeader.
func (r *RequestVoteResponse) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// RequestPreVoteRequest is the command used by a candidate to ask a Raft peer
// for a vote in an election.
type RequestPreVoteRequest struct {
RPCHeader
// Provide the term and our id
Term uint64
// Used to ensure safety
LastLogIndex uint64
LastLogTerm uint64
}
// GetRPCHeader - See WithRPCHeader.
func (r *RequestPreVoteRequest) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// RequestPreVoteResponse is the response returned from a RequestPreVoteRequest.
type RequestPreVoteResponse struct {
RPCHeader
// Newer term if leader is out of date.
Term uint64
// Is the vote granted.
Granted bool
}
// GetRPCHeader - See WithRPCHeader.
func (r *RequestPreVoteResponse) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// InstallSnapshotRequest is the command sent to a Raft peer to bootstrap its
// log (and state machine) from a snapshot on another peer.
type InstallSnapshotRequest struct {
RPCHeader
SnapshotVersion SnapshotVersion
Term uint64
Leader []byte
// These are the last index/term included in the snapshot
LastLogIndex uint64
LastLogTerm uint64
// Peer Set in the snapshot.
// but remains here in case we receive an InstallSnapshot from a leader
// that's running old code.
// Deprecated: This is deprecated in favor of Configuration
Peers []byte
// Cluster membership.
Configuration []byte
// Log index where 'Configuration' entry was originally written.
ConfigurationIndex uint64
// Size of the snapshot
Size int64
}
// GetRPCHeader - See WithRPCHeader.
func (r *InstallSnapshotRequest) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// InstallSnapshotResponse is the response returned from an
// InstallSnapshotRequest.
type InstallSnapshotResponse struct {
RPCHeader
Term uint64
Success bool
}
// GetRPCHeader - See WithRPCHeader.
func (r *InstallSnapshotResponse) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// TimeoutNowRequest is the command used by a leader to signal another server to
// start an election.
type TimeoutNowRequest struct {
RPCHeader
}
// GetRPCHeader - See WithRPCHeader.
func (r *TimeoutNowRequest) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
// TimeoutNowResponse is the response to TimeoutNowRequest.
type TimeoutNowResponse struct {
RPCHeader
}
// GetRPCHeader - See WithRPCHeader.
func (r *TimeoutNowResponse) GetRPCHeader() RPCHeader {
return r.RPCHeader
}
================================================
FILE: commitment.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"sort"
"sync"
)
// Commitment is used to advance the leader's commit index. The leader and
// replication goroutines report in newly written entries with match(), and
// this notifies on commitCh when the commit index has advanced.
type commitment struct {
// protects matchIndexes and commitIndex
sync.Mutex
// notified when commitIndex increases
commitCh chan struct{}
// voter ID to log index: the server stores up through this log entry
matchIndexes map[ServerID]uint64
// a quorum stores up through this log entry. monotonically increases.
commitIndex uint64
// the first index of this leader's term: this needs to be replicated to a
// majority of the cluster before this leader may mark anything committed
// (per Raft's commitment rule)
startIndex uint64
}
// newCommitment returns a commitment struct that notifies the provided
// channel when log entries have been committed. A new commitment struct is
// created each time this server becomes leader for a particular term.
// 'configuration' is the servers in the cluster.
// 'startIndex' is the first index created in this term (see
// its description above).
func newCommitment(commitCh chan struct{}, configuration Configuration, startIndex uint64) *commitment {
matchIndexes := make(map[ServerID]uint64)
for _, server := range configuration.Servers {
if server.Suffrage == Voter {
matchIndexes[server.ID] = 0
}
}
return &commitment{
commitCh: commitCh,
matchIndexes: matchIndexes,
commitIndex: 0,
startIndex: startIndex,
}
}
// Called when a new cluster membership configuration is created: it will be
// used to determine commitment from now on. 'configuration' is the servers in
// the cluster.
func (c *commitment) setConfiguration(configuration Configuration) {
c.Lock()
defer c.Unlock()
oldMatchIndexes := c.matchIndexes
c.matchIndexes = make(map[ServerID]uint64)
for _, server := range configuration.Servers {
if server.Suffrage == Voter {
c.matchIndexes[server.ID] = oldMatchIndexes[server.ID] // defaults to 0
}
}
c.recalculate()
}
// Called by leader after commitCh is notified
func (c *commitment) getCommitIndex() uint64 {
c.Lock()
defer c.Unlock()
return c.commitIndex
}
// Match is called once a server completes writing entries to disk: either the
// leader has written the new entry or a follower has replied to an
// AppendEntries RPC. The given server's disk agrees with this server's log up
// through the given index.
func (c *commitment) match(server ServerID, matchIndex uint64) {
c.Lock()
defer c.Unlock()
if prev, hasVote := c.matchIndexes[server]; hasVote && matchIndex > prev {
c.matchIndexes[server] = matchIndex
c.recalculate()
}
}
// Internal helper to calculate new commitIndex from matchIndexes.
// Must be called with lock held.
func (c *commitment) recalculate() {
if len(c.matchIndexes) == 0 {
return
}
matched := make([]uint64, 0, len(c.matchIndexes))
for _, idx := range c.matchIndexes {
matched = append(matched, idx)
}
sort.Sort(uint64Slice(matched))
quorumMatchIndex := matched[(len(matched)-1)/2]
if quorumMatchIndex > c.commitIndex && quorumMatchIndex >= c.startIndex {
c.commitIndex = quorumMatchIndex
asyncNotifyCh(c.commitCh)
}
}
================================================
FILE: commitment_test.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"testing"
)
func makeConfiguration(voters []string) Configuration {
var configuration Configuration
for _, voter := range voters {
configuration.Servers = append(configuration.Servers, Server{
Suffrage: Voter,
Address: ServerAddress(voter + "addr"),
ID: ServerID(voter),
})
}
return configuration
}
// Returns a slice of server names of size n.
func voters(n int) Configuration {
if n > 7 {
panic("only up to 7 servers implemented")
}
return makeConfiguration([]string{"s1", "s2", "s3", "s4", "s5", "s6", "s7"}[:n])
}
// Tests setVoters() keeps matchIndexes where possible.
func TestCommitment_setVoters(t *testing.T) {
commitCh := make(chan struct{}, 1)
c := newCommitment(commitCh, makeConfiguration([]string{"a", "b", "c"}), 0)
c.match("a", 10)
c.match("b", 20)
c.match("c", 30)
// commitIndex: 20
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
c.setConfiguration(makeConfiguration([]string{"c", "d", "e"}))
// c: 30, d: 0, e: 0
c.match("e", 40)
if c.getCommitIndex() != 30 {
t.Fatalf("expected 30 entries committed, found %d",
c.getCommitIndex())
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
}
// Tests match() being called with smaller index than before.
func TestCommitment_match_max(t *testing.T) {
commitCh := make(chan struct{}, 1)
c := newCommitment(commitCh, voters(5), 4)
c.match("s1", 8)
c.match("s2", 8)
c.match("s2", 1)
c.match("s3", 8)
if c.getCommitIndex() != 8 {
t.Fatalf("calling match with an earlier index should be ignored")
}
}
// Tests match() being called with non-voters.
func TestCommitment_match_nonVoting(t *testing.T) {
commitCh := make(chan struct{}, 1)
c := newCommitment(commitCh, voters(5), 4)
c.match("s1", 8)
c.match("s2", 8)
c.match("s3", 8)
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
c.match("s90", 10)
c.match("s91", 10)
c.match("s92", 10)
if c.getCommitIndex() != 8 {
t.Fatalf("non-voting servers shouldn't be able to commit")
}
if drainNotifyCh(commitCh) {
t.Fatalf("unexpected commit notify")
}
}
// Tests recalculate() algorithm.
func TestCommitment_recalculate(t *testing.T) {
commitCh := make(chan struct{}, 1)
c := newCommitment(commitCh, voters(5), 0)
c.match("s1", 30)
c.match("s2", 20)
if c.getCommitIndex() != 0 {
t.Fatalf("shouldn't commit after two of five servers")
}
if drainNotifyCh(commitCh) {
t.Fatalf("unexpected commit notify")
}
c.match("s3", 10)
if c.getCommitIndex() != 10 {
t.Fatalf("expected 10 entries committed, found %d",
c.getCommitIndex())
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
c.match("s4", 15)
if c.getCommitIndex() != 15 {
t.Fatalf("expected 15 entries committed, found %d",
c.getCommitIndex())
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
c.setConfiguration(voters(3))
// s1: 30, s2: 20, s3: 10
if c.getCommitIndex() != 20 {
t.Fatalf("expected 20 entries committed, found %d",
c.getCommitIndex())
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
c.setConfiguration(voters(4))
// s1: 30, s2: 20, s3: 10, s4: 0
c.match("s2", 25)
if c.getCommitIndex() != 20 {
t.Fatalf("expected 20 entries committed, found %d",
c.getCommitIndex())
}
if drainNotifyCh(commitCh) {
t.Fatalf("unexpected commit notify")
}
c.match("s4", 23)
if c.getCommitIndex() != 23 {
t.Fatalf("expected 23 entries committed, found %d",
c.getCommitIndex())
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
}
// Tests recalculate() respecting startIndex.
func TestCommitment_recalculate_startIndex(t *testing.T) {
commitCh := make(chan struct{}, 1)
c := newCommitment(commitCh, voters(5), 4)
c.match("s1", 3)
c.match("s2", 3)
c.match("s3", 3)
if c.getCommitIndex() != 0 {
t.Fatalf("can't commit until startIndex is replicated to a quorum")
}
if drainNotifyCh(commitCh) {
t.Fatalf("unexpected commit notify")
}
c.match("s1", 4)
c.match("s2", 4)
c.match("s3", 4)
if c.getCommitIndex() != 4 {
t.Fatalf("should be able to commit startIndex once replicated to a quorum")
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
}
// With no voting members in the cluster, the most sane behavior is probably
// to not mark anything committed.
func TestCommitment_noVoterSanity(t *testing.T) {
commitCh := make(chan struct{}, 1)
c := newCommitment(commitCh, makeConfiguration([]string{}), 4)
c.match("s1", 10)
c.setConfiguration(makeConfiguration([]string{}))
c.match("s1", 10)
if c.getCommitIndex() != 0 {
t.Fatalf("no voting servers: shouldn't be able to commit")
}
if drainNotifyCh(commitCh) {
t.Fatalf("unexpected commit notify")
}
// add a voter so we can commit something and then remove it
c.setConfiguration(voters(1))
c.match("s1", 10)
if c.getCommitIndex() != 10 {
t.Fatalf("expected 10 entries committed, found %d",
c.getCommitIndex())
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
c.setConfiguration(makeConfiguration([]string{}))
c.match("s1", 20)
if c.getCommitIndex() != 10 {
t.Fatalf("expected 10 entries committed, found %d",
c.getCommitIndex())
}
if drainNotifyCh(commitCh) {
t.Fatalf("unexpected commit notify")
}
}
// Single voter commits immediately.
func TestCommitment_singleVoter(t *testing.T) {
commitCh := make(chan struct{}, 1)
c := newCommitment(commitCh, voters(1), 4)
c.match("s1", 10)
if c.getCommitIndex() != 10 {
t.Fatalf("expected 10 entries committed, found %d",
c.getCommitIndex())
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
c.setConfiguration(voters(1))
if drainNotifyCh(commitCh) {
t.Fatalf("unexpected commit notify")
}
c.match("s1", 12)
if c.getCommitIndex() != 12 {
t.Fatalf("expected 12 entries committed, found %d",
c.getCommitIndex())
}
if !drainNotifyCh(commitCh) {
t.Fatalf("expected commit notify")
}
}
================================================
FILE: config.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"fmt"
"io"
"os"
"time"
"github.com/hashicorp/go-hclog"
)
// ProtocolVersion is the version of the protocol (which includes RPC messages
// as well as Raft-specific log entries) that this server can _understand_. Use
// the ProtocolVersion member of the Config object to control the version of
// the protocol to use when _speaking_ to other servers. Note that depending on
// the protocol version being spoken, some otherwise understood RPC messages
// may be refused. See dispositionRPC for details of this logic.
//
// There are notes about the upgrade path in the description of the versions
// below. If you are starting a fresh cluster then there's no reason not to
// jump right to the latest protocol version. If you need to interoperate with
// older, version 0 Raft servers you'll need to drive the cluster through the
// different versions in order.
//
// The version details are complicated, but here's a summary of what's required
// to get from a version 0 cluster to version 3:
//
// 1. In version N of your app that starts using the new Raft library with
// versioning, set ProtocolVersion to 1.
// 2. Make version N+1 of your app require version N as a prerequisite (all
// servers must be upgraded). For version N+1 of your app set ProtocolVersion
// to 2.
// 3. Similarly, make version N+2 of your app require version N+1 as a
// prerequisite. For version N+2 of your app, set ProtocolVersion to 3.
//
// During this upgrade, older cluster members will still have Server IDs equal
// to their network addresses. To upgrade an older member and give it an ID, it
// needs to leave the cluster and re-enter:
//
// 1. Remove the server from the cluster with RemoveServer, using its network
// address as its ServerID.
// 2. Update the server's config to use a UUID or something else that is
// not tied to the machine as the ServerID (restarting the server).
// 3. Add the server back to the cluster with AddVoter, using its new ID.
//
// You can do this during the rolling upgrade from N+1 to N+2 of your app, or
// as a rolling change at any time after the upgrade.
//
// # Version History
//
// 0: Original Raft library before versioning was added. Servers running this
//
// version of the Raft library use AddPeerDeprecated/RemovePeerDeprecated
// for all configuration changes, and have no support for LogConfiguration.
//
// 1: First versioned protocol, used to interoperate with old servers, and begin
//
// the migration path to newer versions of the protocol. Under this version
// all configuration changes are propagated using the now-deprecated
// RemovePeerDeprecated Raft log entry. This means that server IDs are always
// set to be the same as the server addresses (since the old log entry type
// cannot transmit an ID), and only AddPeer/RemovePeer APIs are supported.
// Servers running this version of the protocol can understand the new
// LogConfiguration Raft log entry but will never generate one so they can
// remain compatible with version 0 Raft servers in the cluster.
//
// 2: Transitional protocol used when migrating an existing cluster to the new
//
// server ID system. Server IDs are still set to be the same as server
// addresses, but all configuration changes are propagated using the new
// LogConfiguration Raft log entry type, which can carry full ID information.
// This version supports the old AddPeer/RemovePeer APIs as well as the new
// ID-based AddVoter/RemoveServer APIs which should be used when adding
// version 3 servers to the cluster later. This version sheds all
// interoperability with version 0 servers, but can interoperate with newer
// Raft servers running with protocol version 1 since they can understand the
// new LogConfiguration Raft log entry, and this version can still understand
// their RemovePeerDeprecated Raft log entries. We need this protocol version
// as an intermediate step between 1 and 3 so that servers will propagate the
// ID information that will come from newly-added (or -rolled) servers using
// protocol version 3, but since they are still using their address-based IDs
// from the previous step they will still be able to track commitments and
// their own voting status properly. If we skipped this step, servers would
// be started with their new IDs, but they wouldn't see themselves in the old
// address-based configuration, so none of the servers would think they had a
// vote.
//
// 3: Protocol adding full support for server IDs and new ID-based server APIs
//
// (AddVoter, AddNonvoter, etc.), old AddPeer/RemovePeer APIs are no longer
// supported. Version 2 servers should be swapped out by removing them from
// the cluster one-by-one and re-adding them with updated configuration for
// this protocol version, along with their server ID. The remove/add cycle
// is required to populate their server ID. Note that removing must be done
// by ID, which will be the old server's address.
type ProtocolVersion int
const (
// ProtocolVersionMin is the minimum protocol version
ProtocolVersionMin ProtocolVersion = 0
// ProtocolVersionMax is the maximum protocol version
ProtocolVersionMax = 3
)
// SnapshotVersion is the version of snapshots that this server can understand.
// Currently, it is always assumed that the server generates the latest version,
// though this may be changed in the future to include a configurable version.
//
// # Version History
//
// 0: Original Raft library before versioning was added. The peers portion of
//
// these snapshots is encoded in the legacy format which requires decodePeers
// to parse. This version of snapshots should only be produced by the
// unversioned Raft library.
//
// 1: New format which adds support for a full configuration structure and its
//
// associated log index, with support for server IDs and non-voting server
// modes. To ease upgrades, this also includes the legacy peers structure but
// that will never be used by servers that understand version 1 snapshots.
// Since the original Raft library didn't enforce any versioning, we must
// include the legacy peers structure for this version, but we can deprecate
// it in the next snapshot version.
type SnapshotVersion int
const (
// SnapshotVersionMin is the minimum snapshot version
SnapshotVersionMin SnapshotVersion = 0
// SnapshotVersionMax is the maximum snapshot version
SnapshotVersionMax = 1
)
// Config provides any necessary configuration for the Raft server.
type Config struct {
// ProtocolVersion allows a Raft server to inter-operate with older
// Raft servers running an older version of the code. This is used to
// version the wire protocol as well as Raft-specific log entries that
// the server uses when _speaking_ to other servers. There is currently
// no auto-negotiation of versions so all servers must be manually
// configured with compatible versions. See ProtocolVersionMin and
// ProtocolVersionMax for the versions of the protocol that this server
// can _understand_.
ProtocolVersion ProtocolVersion
// HeartbeatTimeout specifies the time in follower state without contact
// from a leader before we attempt an election.
HeartbeatTimeout time.Duration
// ElectionTimeout specifies the time in candidate state without contact
// from a leader before we attempt an election.
ElectionTimeout time.Duration
// CommitTimeout specifies the time without an Apply operation before the
// leader sends an AppendEntry RPC to followers, to ensure a timely commit of
// log entries.
// Due to random staggering, may be delayed as much as 2x this value.
CommitTimeout time.Duration
// MaxAppendEntries controls the maximum number of append entries
// to send at once. We want to strike a balance between efficiency
// and avoiding waste if the follower is going to reject because of
// an inconsistent log.
MaxAppendEntries int
// BatchApplyCh indicates whether we should buffer applyCh
// to size MaxAppendEntries. This enables batch log commitment,
// but breaks the timeout guarantee on Apply. Specifically,
// a log can be added to the applyCh buffer but not actually be
// processed until after the specified timeout.
BatchApplyCh bool
// If we are a member of a cluster, and RemovePeer is invoked for the
// local node, then we forget all peers and transition into the follower state.
// If ShutdownOnRemove is set, we additional shutdown Raft. Otherwise,
// we can become a leader of a cluster containing only this node.
ShutdownOnRemove bool
// TrailingLogs controls how many logs we leave after a snapshot. This is used
// so that we can quickly replay logs on a follower instead of being forced to
// send an entire snapshot. The value passed here is the initial setting used.
// This can be tuned during operation using ReloadConfig.
TrailingLogs uint64
// SnapshotInterval controls how often we check if we should perform a
// snapshot. We randomly stagger between this value and 2x this value to avoid
// the entire cluster from performing a snapshot at once. The value passed
// here is the initial setting used. This can be tuned during operation using
// ReloadConfig.
SnapshotInterval time.Duration
// SnapshotThreshold controls how many outstanding logs there must be before
// we perform a snapshot. This is to prevent excessive snapshotting by
// replaying a small set of logs instead. The value passed here is the initial
// setting used. This can be tuned during operation using ReloadConfig.
SnapshotThreshold uint64
// LeaderLeaseTimeout is used to control how long the "lease" lasts
// for being the leader without being able to contact a quorum
// of nodes. If we reach this interval without contact, we will
// step down as leader.
LeaderLeaseTimeout time.Duration
// LocalID is a unique ID for this server across all time. When running with
// ProtocolVersion < 3, you must set this to be the same as the network
// address of your transport.
LocalID ServerID
// NotifyCh is used to provide a channel that will be notified of leadership
// changes. Raft will block writing to this channel, so it should either be
// buffered or aggressively consumed.
NotifyCh chan<- bool
// LogOutput is used as a sink for logs, unless Logger is specified.
// Defaults to os.Stderr.
LogOutput io.Writer
// LogLevel represents a log level. If the value does not match a known
// logging level hclog.NoLevel is used.
LogLevel string
// Logger is a user-provided logger. If nil, a logger writing to
// LogOutput with LogLevel is used.
Logger hclog.Logger
// NoSnapshotRestoreOnStart controls if raft will restore a snapshot to the
// FSM on start. This is useful if your FSM recovers from other mechanisms
// than raft snapshotting. Snapshot metadata will still be used to initialize
// raft's configuration and index values.
NoSnapshotRestoreOnStart bool
// PreVoteDisabled deactivate the pre-vote feature when set to true
PreVoteDisabled bool
// NoLegacyTelemetry allows to skip the legacy metrics to avoid duplicates.
// legacy metrics are those that have `_peer_name` as metric suffix instead as labels.
// e.g: raft_replication_heartbeat_peer0
NoLegacyTelemetry bool
// skipStartup allows NewRaft() to bypass all background work goroutines
skipStartup bool
}
func (conf *Config) getOrCreateLogger() hclog.Logger {
if conf.Logger != nil {
return conf.Logger
}
if conf.LogOutput == nil {
conf.LogOutput = os.Stderr
}
return hclog.New(&hclog.LoggerOptions{
Name: "raft",
Level: hclog.LevelFromString(conf.LogLevel),
Output: conf.LogOutput,
})
}
// ReloadableConfig is the subset of Config that may be reconfigured during
// runtime using raft.ReloadConfig. We choose to duplicate fields over embedding
// or accepting a Config but only using specific fields to keep the API clear.
// Reconfiguring some fields is potentially dangerous so we should only
// selectively enable it for fields where that is allowed.
type ReloadableConfig struct {
// TrailingLogs controls how many logs we leave after a snapshot. This is used
// so that we can quickly replay logs on a follower instead of being forced to
// send an entire snapshot. The value passed here updates the setting at runtime
// which will take effect as soon as the next snapshot completes and truncation
// occurs.
TrailingLogs uint64
// SnapshotInterval controls how often we check if we should perform a snapshot.
// We randomly stagger between this value and 2x this value to avoid the entire
// cluster from performing a snapshot at once.
SnapshotInterval time.Duration
// SnapshotThreshold controls how many outstanding logs there must be before
// we perform a snapshot. This is to prevent excessive snapshots when we can
// just replay a small set of logs.
SnapshotThreshold uint64
// HeartbeatTimeout specifies the time in follower state without
// a leader before we attempt an election.
HeartbeatTimeout time.Duration
// ElectionTimeout specifies the time in candidate state without
// a leader before we attempt an election.
ElectionTimeout time.Duration
}
// apply sets the reloadable fields on the passed Config to the values in
// `ReloadableConfig`. It returns a copy of Config with the fields from this
// ReloadableConfig set.
func (rc *ReloadableConfig) apply(to Config) Config {
to.TrailingLogs = rc.TrailingLogs
to.SnapshotInterval = rc.SnapshotInterval
to.SnapshotThreshold = rc.SnapshotThreshold
to.HeartbeatTimeout = rc.HeartbeatTimeout
to.ElectionTimeout = rc.ElectionTimeout
return to
}
// fromConfig copies the reloadable fields from the passed Config.
func (rc *ReloadableConfig) fromConfig(from Config) {
rc.TrailingLogs = from.TrailingLogs
rc.SnapshotInterval = from.SnapshotInterval
rc.SnapshotThreshold = from.SnapshotThreshold
rc.HeartbeatTimeout = from.HeartbeatTimeout
rc.ElectionTimeout = from.ElectionTimeout
}
// DefaultConfig returns a Config with usable defaults.
func DefaultConfig() *Config {
return &Config{
ProtocolVersion: ProtocolVersionMax,
HeartbeatTimeout: 1000 * time.Millisecond,
ElectionTimeout: 1000 * time.Millisecond,
CommitTimeout: 50 * time.Millisecond,
MaxAppendEntries: 64,
ShutdownOnRemove: true,
TrailingLogs: 10240,
SnapshotInterval: 120 * time.Second,
SnapshotThreshold: 8192,
LeaderLeaseTimeout: 500 * time.Millisecond,
LogLevel: "DEBUG",
}
}
// ValidateConfig is used to validate a sane configuration
func ValidateConfig(config *Config) error {
// We don't actually support running as 0 in the library any more, but
// we do understand it.
protocolMin := ProtocolVersionMin
if protocolMin == 0 {
protocolMin = 1
}
if config.ProtocolVersion < protocolMin ||
config.ProtocolVersion > ProtocolVersionMax {
return fmt.Errorf("ProtocolVersion %d must be >= %d and <= %d",
config.ProtocolVersion, protocolMin, ProtocolVersionMax)
}
if len(config.LocalID) == 0 {
return fmt.Errorf("LocalID cannot be empty")
}
if config.HeartbeatTimeout < 5*time.Millisecond {
return fmt.Errorf("HeartbeatTimeout is too low")
}
if config.ElectionTimeout < 5*time.Millisecond {
return fmt.Errorf("ElectionTimeout is too low")
}
if config.CommitTimeout < time.Millisecond {
return fmt.Errorf("CommitTimeout is too low")
}
if config.MaxAppendEntries <= 0 {
return fmt.Errorf("MaxAppendEntries must be positive")
}
if config.MaxAppendEntries > 1024 {
return fmt.Errorf("MaxAppendEntries is too large")
}
if config.SnapshotInterval < 5*time.Millisecond {
return fmt.Errorf("SnapshotInterval is too low")
}
if config.LeaderLeaseTimeout < 5*time.Millisecond {
return fmt.Errorf("LeaderLeaseTimeout is too low")
}
if config.LeaderLeaseTimeout > config.HeartbeatTimeout {
return fmt.Errorf("LeaderLeaseTimeout (%s) cannot be larger than heartbeat timeout (%s)", config.LeaderLeaseTimeout, config.HeartbeatTimeout)
}
if config.ElectionTimeout < config.HeartbeatTimeout {
return fmt.Errorf("ElectionTimeout (%s) must be equal or greater than Heartbeat Timeout (%s)", config.ElectionTimeout, config.HeartbeatTimeout)
}
return nil
}
================================================
FILE: configuration.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import "fmt"
// ServerSuffrage determines whether a Server in a Configuration gets a vote.
type ServerSuffrage int
// Note: Don't renumber these, since the numbers are written into the log.
const (
// Voter is a server whose vote is counted in elections and whose match index
// is used in advancing the leader's commit index.
Voter ServerSuffrage = iota
// Nonvoter is a server that receives log entries but is not considered for
// elections or commitment purposes.
Nonvoter
// Staging is a server that acts like a Nonvoter. A configuration change
// with a ConfigurationChangeCommand of Promote can change a Staging server
// into a Voter.
// Deprecated: use Nonvoter instead.
Staging
)
func (s ServerSuffrage) String() string {
switch s {
case Voter:
return "Voter"
case Nonvoter:
return "Nonvoter"
case Staging:
return "Staging"
}
return "ServerSuffrage"
}
// ConfigurationStore provides an interface that can optionally be implemented by FSMs
// to store configuration updates made in the replicated log. In general this is only
// necessary for FSMs that mutate durable state directly instead of applying changes
// in memory and snapshotting periodically. By storing configuration changes, the
// persistent FSM state can behave as a complete snapshot, and be able to recover
// without an external snapshot just for persisting the raft configuration.
type ConfigurationStore interface {
// ConfigurationStore is a superset of the FSM functionality
FSM
// StoreConfiguration is invoked once a log entry containing a configuration
// change is committed. It takes the index at which the configuration was
// written and the configuration value.
StoreConfiguration(index uint64, configuration Configuration)
}
// ServerID is a unique string identifying a server for all time.
type ServerID string
// ServerAddress is a network address for a server that a transport can contact.
type ServerAddress string
// Server tracks the information about a single server in a configuration.
type Server struct {
// Suffrage determines whether the server gets a vote.
Suffrage ServerSuffrage
// ID is a unique string identifying this server for all time.
ID ServerID
// Address is its network address that a transport can contact.
Address ServerAddress
}
// Configuration tracks which servers are in the cluster, and whether they have
// votes. This should include the local server, if it's a member of the cluster.
// The servers are listed no particular order, but each should only appear once.
// These entries are appended to the log during membership changes.
type Configuration struct {
Servers []Server
}
// Clone makes a deep copy of a Configuration.
func (c *Configuration) Clone() (copy Configuration) {
copy.Servers = append(copy.Servers, c.Servers...)
return
}
// ConfigurationChangeCommand is the different ways to change the cluster
// configuration.
type ConfigurationChangeCommand uint8
const (
// AddVoter adds a server with Suffrage of Voter.
AddVoter ConfigurationChangeCommand = iota
// AddNonvoter makes a server Nonvoter unless its Staging or Voter.
AddNonvoter
// DemoteVoter makes a server Nonvoter unless its absent.
DemoteVoter
// RemoveServer removes a server entirely from the cluster membership.
RemoveServer
// Promote changes a server from Staging to Voter. The command will be a
// no-op if the server is not Staging.
// Deprecated: use AddVoter instead.
Promote
// AddStaging makes a server a Voter.
// Deprecated: AddStaging was actually AddVoter. Use AddVoter instead.
AddStaging = 0 // explicit 0 to preserve the old value.
)
func (c ConfigurationChangeCommand) String() string {
switch c {
case AddVoter:
return "AddVoter"
case AddNonvoter:
return "AddNonvoter"
case DemoteVoter:
return "DemoteVoter"
case RemoveServer:
return "RemoveServer"
case Promote:
return "Promote"
}
return "ConfigurationChangeCommand"
}
// configurationChangeRequest describes a change that a leader would like to
// make to its current configuration. It's used only within a single server
// (never serialized into the log), as part of `configurationChangeFuture`.
type configurationChangeRequest struct {
command ConfigurationChangeCommand
serverID ServerID
serverAddress ServerAddress // only present for AddVoter, AddNonvoter
// prevIndex, if nonzero, is the index of the only configuration upon which
// this change may be applied; if another configuration entry has been
// added in the meantime, this request will fail.
prevIndex uint64
}
// configurations is state tracked on every server about its Configurations.
// Note that, per Diego's dissertation, there can be at most one uncommitted
// configuration at a time (the next configuration may not be created until the
// prior one has been committed).
//
// One downside to storing just two configurations is that if you try to take a
// snapshot when your state machine hasn't yet applied the committedIndex, we
// have no record of the configuration that would logically fit into that
// snapshot. We disallow snapshots in that case now. An alternative approach,
// which LogCabin uses, is to track every configuration change in the
// log.
type configurations struct {
// committed is the latest configuration in the log/snapshot that has been
// committed (the one with the largest index).
committed Configuration
// committedIndex is the log index where 'committed' was written.
committedIndex uint64
// latest is the latest configuration in the log/snapshot (may be committed
// or uncommitted)
latest Configuration
// latestIndex is the log index where 'latest' was written.
latestIndex uint64
}
// Clone makes a deep copy of a configurations object.
func (c *configurations) Clone() (copy configurations) {
copy.committed = c.committed.Clone()
copy.committedIndex = c.committedIndex
copy.latest = c.latest.Clone()
copy.latestIndex = c.latestIndex
return
}
// hasVote returns true if the server identified by 'id' is a Voter in the
// provided Configuration.
func hasVote(configuration Configuration, id ServerID) bool {
for _, server := range configuration.Servers {
if server.ID == id {
return server.Suffrage == Voter
}
}
return false
}
// inConfiguration returns true if the server identified by 'id' is in the
// provided Configuration.
func inConfiguration(configuration Configuration, id ServerID) bool {
for _, server := range configuration.Servers {
if server.ID == id {
return true
}
}
return false
}
// checkConfiguration tests a cluster membership configuration for common
// errors.
func checkConfiguration(configuration Configuration) error {
idSet := make(map[ServerID]bool)
addressSet := make(map[ServerAddress]bool)
var voters int
for _, server := range configuration.Servers {
if server.ID == "" {
return fmt.Errorf("empty ID in configuration: %v", configuration)
}
if server.Address == "" {
return fmt.Errorf("empty address in configuration: %v", server)
}
if idSet[server.ID] {
return fmt.Errorf("found duplicate ID in configuration: %v", server.ID)
}
idSet[server.ID] = true
if addressSet[server.Address] {
return fmt.Errorf("found duplicate address in configuration: %v", server.Address)
}
addressSet[server.Address] = true
if server.Suffrage == Voter {
voters++
}
}
if voters == 0 {
return fmt.Errorf("need at least one voter in configuration: %v", configuration)
}
return nil
}
// nextConfiguration generates a new Configuration from the current one and a
// configuration change request. It's split from appendConfigurationEntry so
// that it can be unit tested easily.
func nextConfiguration(current Configuration, currentIndex uint64, change configurationChangeRequest) (Configuration, error) {
if change.prevIndex > 0 && change.prevIndex != currentIndex {
return Configuration{}, fmt.Errorf("configuration changed since %v (latest is %v)", change.prevIndex, currentIndex)
}
configuration := current.Clone()
switch change.command {
case AddVoter:
newServer := Server{
Suffrage: Voter,
ID: change.serverID,
Address: change.serverAddress,
}
found := false
for i, server := range configuration.Servers {
if server.ID == change.serverID {
if server.Suffrage == Voter {
configuration.Servers[i].Address = change.serverAddress
} else {
configuration.Servers[i] = newServer
}
found = true
break
}
}
if !found {
configuration.Servers = append(configuration.Servers, newServer)
}
case AddNonvoter:
newServer := Server{
Suffrage: Nonvoter,
ID: change.serverID,
Address: change.serverAddress,
}
found := false
for i, server := range configuration.Servers {
if server.ID == change.serverID {
if server.Suffrage != Nonvoter {
configuration.Servers[i].Address = change.serverAddress
} else {
configuration.Servers[i] = newServer
}
found = true
break
}
}
if !found {
configuration.Servers = append(configuration.Servers, newServer)
}
case DemoteVoter:
for i, server := range configuration.Servers {
if server.ID == change.serverID {
configuration.Servers[i].Suffrage = Nonvoter
break
}
}
case RemoveServer:
for i, server := range configuration.Servers {
if server.ID == change.serverID {
configuration.Servers = append(configuration.Servers[:i], configuration.Servers[i+1:]...)
break
}
}
case Promote:
for i, server := range configuration.Servers {
if server.ID == change.serverID && server.Suffrage == Staging {
configuration.Servers[i].Suffrage = Voter
break
}
}
}
// Make sure we didn't do something bad like remove the last voter
if err := checkConfiguration(configuration); err != nil {
return Configuration{}, err
}
return configuration, nil
}
// encodePeers is used to serialize a Configuration into the old peers format.
// This is here for backwards compatibility when operating with a mix of old
// servers and should be removed once we deprecate support for protocol version 1.
func encodePeers(configuration Configuration, trans Transport) []byte {
// Gather up all the voters, other suffrage types are not supported by
// this data format.
var encPeers [][]byte
for _, server := range configuration.Servers {
if server.Suffrage == Voter {
encPeers = append(encPeers, trans.EncodePeer(server.ID, server.Address))
}
}
// Encode the entire array.
buf, err := encodeMsgPack(encPeers)
if err != nil {
panic(fmt.Errorf("failed to encode peers: %v", err))
}
return buf.Bytes()
}
// decodePeers is used to deserialize an old list of peers into a Configuration.
// This is here for backwards compatibility with old log entries and snapshots;
// it should be removed eventually.
func decodePeers(buf []byte, trans Transport) (Configuration, error) {
// Decode the buffer first.
var encPeers [][]byte
if err := decodeMsgPack(buf, &encPeers); err != nil {
return Configuration{}, fmt.Errorf("failed to decode peers: %v", err)
}
// Deserialize each peer.
var servers []Server
for _, enc := range encPeers {
p := trans.DecodePeer(enc)
servers = append(servers, Server{
Suffrage: Voter,
ID: ServerID(p),
Address: p,
})
}
return Configuration{Servers: servers}, nil
}
// EncodeConfiguration serializes a Configuration using MsgPack, or panics on
// errors.
func EncodeConfiguration(configuration Configuration) []byte {
buf, err := encodeMsgPack(configuration)
if err != nil {
panic(fmt.Errorf("failed to encode configuration: %v", err))
}
return buf.Bytes()
}
// DecodeConfiguration deserializes a Configuration using MsgPack, or panics on
// errors.
func DecodeConfiguration(buf []byte) Configuration {
var configuration Configuration
if err := decodeMsgPack(buf, &configuration); err != nil {
panic(fmt.Errorf("failed to decode configuration: %v", err))
}
return configuration
}
================================================
FILE: configuration_test.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
var sampleConfiguration = Configuration{
Servers: []Server{
{
Suffrage: Nonvoter,
ID: ServerID("id0"),
Address: ServerAddress("addr0"),
},
{
Suffrage: Voter,
ID: ServerID("id1"),
Address: ServerAddress("addr1"),
},
{
Suffrage: Staging,
ID: ServerID("id2"),
Address: ServerAddress("addr2"),
},
},
}
func TestConfiguration_Configuration_Clone(t *testing.T) {
cloned := sampleConfiguration.Clone()
if !reflect.DeepEqual(sampleConfiguration, cloned) {
t.Fatalf("mismatch %v %v", sampleConfiguration, cloned)
}
cloned.Servers[1].ID = "scribble"
if sampleConfiguration.Servers[1].ID == "scribble" {
t.Fatalf("cloned configuration shouldn't alias Servers")
}
}
func TestConfiguration_configurations_Clone(t *testing.T) {
configuration := configurations{
committed: sampleConfiguration,
committedIndex: 1,
latest: sampleConfiguration,
latestIndex: 2,
}
cloned := configuration.Clone()
if !reflect.DeepEqual(configuration, cloned) {
t.Fatalf("mismatch %v %v", configuration, cloned)
}
cloned.committed.Servers[1].ID = "scribble"
cloned.latest.Servers[1].ID = "scribble"
if configuration.committed.Servers[1].ID == "scribble" ||
configuration.latest.Servers[1].ID == "scribble" {
t.Fatalf("cloned configuration shouldn't alias Servers")
}
}
func TestConfiguration_hasVote(t *testing.T) {
if hasVote(sampleConfiguration, "id0") {
t.Fatalf("id0 should not have vote")
}
if !hasVote(sampleConfiguration, "id1") {
t.Fatalf("id1 should have vote")
}
if hasVote(sampleConfiguration, "id2") {
t.Fatalf("id2 should not have vote")
}
if hasVote(sampleConfiguration, "someotherid") {
t.Fatalf("someotherid should not have vote")
}
}
func TestConfiguration_checkConfiguration(t *testing.T) {
var configuration Configuration
if checkConfiguration(configuration) == nil {
t.Fatalf("empty configuration should be error")
}
configuration.Servers = append(configuration.Servers, Server{
Suffrage: Nonvoter,
ID: ServerID("id0"),
Address: ServerAddress("addr0"),
})
if checkConfiguration(configuration) == nil {
t.Fatalf("lack of voter should be error")
}
configuration.Servers = append(configuration.Servers, Server{
Suffrage: Voter,
ID: ServerID("id1"),
Address: ServerAddress("addr1"),
})
if err := checkConfiguration(configuration); err != nil {
t.Fatalf("should be OK: %v", err)
}
configuration.Servers[1].ID = "id0"
err := checkConfiguration(configuration)
if err == nil {
t.Fatalf("duplicate ID should be error")
}
if !strings.Contains(err.Error(), "duplicate ID") {
t.Fatalf("unexpected error: %v", err)
}
configuration.Servers[1].ID = "id1"
configuration.Servers[1].Address = "addr0"
err = checkConfiguration(configuration)
if err == nil {
t.Fatalf("duplicate address should be error")
}
if !strings.Contains(err.Error(), "duplicate address") {
t.Fatalf("unexpected error: %v", err)
}
}
var singleServer = Configuration{
Servers: []Server{
{
Suffrage: Voter,
ID: ServerID("id1"),
Address: ServerAddress("addr1x"),
},
},
}
var oneOfEach = Configuration{
Servers: []Server{
{
Suffrage: Voter,
ID: ServerID("id1"),
Address: ServerAddress("addr1x"),
},
{
Suffrage: Staging,
ID: ServerID("id2"),
Address: ServerAddress("addr2x"),
},
{
Suffrage: Nonvoter,
ID: ServerID("id3"),
Address: ServerAddress("addr3x"),
},
},
}
var voterPair = Configuration{
Servers: []Server{
{
Suffrage: Voter,
ID: ServerID("id1"),
Address: ServerAddress("addr1x"),
},
{
Suffrage: Voter,
ID: ServerID("id2"),
Address: ServerAddress("addr2x"),
},
},
}
var nextConfigurationTests = []struct {
current Configuration
command ConfigurationChangeCommand
serverID int
next string
}{
// AddStaging: was missing.
{Configuration{}, AddStaging, 1, "{[{Voter id1 addr1}]}"},
{singleServer, AddStaging, 2, "{[{Voter id1 addr1x} {Voter id2 addr2}]}"},
// AddStaging: was Voter.
{singleServer, AddStaging, 1, "{[{Voter id1 addr1}]}"},
// AddStaging: was Staging.
{oneOfEach, AddStaging, 2, "{[{Voter id1 addr1x} {Voter id2 addr2} {Nonvoter id3 addr3x}]}"},
// AddStaging: was Nonvoter.
{oneOfEach, AddStaging, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Voter id3 addr3}]}"},
// AddVoter: was missing.
{Configuration{}, AddVoter, 1, "{[{Voter id1 addr1}]}"},
{singleServer, AddVoter, 2, "{[{Voter id1 addr1x} {Voter id2 addr2}]}"},
// AddVoter: was Voter.
{singleServer, AddVoter, 1, "{[{Voter id1 addr1}]}"},
// AddVoter: was Staging.
{oneOfEach, AddVoter, 2, "{[{Voter id1 addr1x} {Voter id2 addr2} {Nonvoter id3 addr3x}]}"},
// AddVoter: was Nonvoter.
{oneOfEach, AddVoter, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Voter id3 addr3}]}"},
// AddNonvoter: was missing.
{singleServer, AddNonvoter, 2, "{[{Voter id1 addr1x} {Nonvoter id2 addr2}]}"},
// AddNonvoter: was Voter.
{singleServer, AddNonvoter, 1, "{[{Voter id1 addr1}]}"},
// AddNonvoter: was Staging.
{oneOfEach, AddNonvoter, 2, "{[{Voter id1 addr1x} {Staging id2 addr2} {Nonvoter id3 addr3x}]}"},
// AddNonvoter: was Nonvoter.
{oneOfEach, AddNonvoter, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Nonvoter id3 addr3}]}"},
// DemoteVoter: was missing.
{singleServer, DemoteVoter, 2, "{[{Voter id1 addr1x}]}"},
// DemoteVoter: was Voter.
{voterPair, DemoteVoter, 2, "{[{Voter id1 addr1x} {Nonvoter id2 addr2x}]}"},
// DemoteVoter: was Staging.
{oneOfEach, DemoteVoter, 2, "{[{Voter id1 addr1x} {Nonvoter id2 addr2x} {Nonvoter id3 addr3x}]}"},
// DemoteVoter: was Nonvoter.
{oneOfEach, DemoteVoter, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Nonvoter id3 addr3x}]}"},
// RemoveServer: was missing.
{singleServer, RemoveServer, 2, "{[{Voter id1 addr1x}]}"},
// RemoveServer: was Voter.
{voterPair, RemoveServer, 2, "{[{Voter id1 addr1x}]}"},
// RemoveServer: was Staging.
{oneOfEach, RemoveServer, 2, "{[{Voter id1 addr1x} {Nonvoter id3 addr3x}]}"},
// RemoveServer: was Nonvoter.
{oneOfEach, RemoveServer, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x}]}"},
// Promote: was missing.
{singleServer, Promote, 2, "{[{Voter id1 addr1x}]}"},
// Promote: was Voter.
{singleServer, Promote, 1, "{[{Voter id1 addr1x}]}"},
// Promote: was Staging.
{oneOfEach, Promote, 2, "{[{Voter id1 addr1x} {Voter id2 addr2x} {Nonvoter id3 addr3x}]}"},
// Promote: was Nonvoter.
{oneOfEach, Promote, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Nonvoter id3 addr3x}]}"},
}
func TestConfiguration_nextConfiguration_table(t *testing.T) {
for i, tt := range nextConfigurationTests {
req := configurationChangeRequest{
command: tt.command,
serverID: ServerID(fmt.Sprintf("id%d", tt.serverID)),
serverAddress: ServerAddress(fmt.Sprintf("addr%d", tt.serverID)),
}
next, err := nextConfiguration(tt.current, 1, req)
if err != nil {
t.Errorf("nextConfiguration %d should have succeeded, got %v", i, err)
continue
}
if fmt.Sprintf("%v", next) != tt.next {
t.Errorf("nextConfiguration %d returned %v, expected %s", i, next, tt.next)
continue
}
}
}
func TestConfiguration_nextConfiguration_prevIndex(t *testing.T) {
// Stale prevIndex.
req := configurationChangeRequest{
command: AddVoter,
serverID: ServerID("id1"),
serverAddress: ServerAddress("addr1"),
prevIndex: 1,
}
_, err := nextConfiguration(singleServer, 2, req)
if err == nil || !strings.Contains(err.Error(), "changed") {
t.Fatalf("nextConfiguration should have failed due to intervening configuration change")
}
// Current prevIndex.
req = configurationChangeRequest{
command: AddVoter,
serverID: ServerID("id2"),
serverAddress: ServerAddress("addr2"),
prevIndex: 2,
}
_, err = nextConfiguration(singleServer, 2, req)
if err != nil {
t.Fatalf("nextConfiguration should have succeeded, got %v", err)
}
// Zero prevIndex.
req = configurationChangeRequest{
command: AddVoter,
serverID: ServerID("id3"),
serverAddress: ServerAddress("addr3"),
prevIndex: 0,
}
_, err = nextConfiguration(singleServer, 2, req)
if err != nil {
t.Fatalf("nextConfiguration should have succeeded, got %v", err)
}
}
func TestConfiguration_nextConfiguration_checkConfiguration(t *testing.T) {
req := configurationChangeRequest{
command: AddNonvoter,
serverID: ServerID("id1"),
serverAddress: ServerAddress("addr1"),
}
_, err := nextConfiguration(Configuration{}, 1, req)
if err == nil || !strings.Contains(err.Error(), "at least one voter") {
t.Fatalf("nextConfiguration should have failed for not having a voter")
}
}
func TestConfiguration_encodeDecodePeers(t *testing.T) {
// Set up configuration.
var configuration Configuration
for i := 0; i < 3; i++ {
address := NewInmemAddr()
configuration.Servers = append(configuration.Servers, Server{
Suffrage: Voter,
ID: ServerID(address),
Address: ServerAddress(address),
})
}
// Encode into the old format.
_, trans := NewInmemTransport("")
buf := encodePeers(configuration, trans)
// Decode from old format, as if reading an old log entry.
decoded, err := decodePeers(buf, trans)
require.NoError(t, err)
if !reflect.DeepEqual(configuration, decoded) {
t.Fatalf("mismatch %v %v", configuration, decoded)
}
}
func TestConfiguration_encodeDecodeConfiguration(t *testing.T) {
decoded := DecodeConfiguration(EncodeConfiguration(sampleConfiguration))
if !reflect.DeepEqual(sampleConfiguration, decoded) {
t.Fatalf("mismatch %v %v", sampleConfiguration, decoded)
}
}
================================================
FILE: discard_snapshot.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"fmt"
"io"
)
// DiscardSnapshotStore is used to successfully snapshot while
// always discarding the snapshot. This is useful for when the
// log should be truncated but no snapshot should be retained.
// This should never be used for production use, and is only
// suitable for testing.
type DiscardSnapshotStore struct{}
// DiscardSnapshotSink is used to fulfill the SnapshotSink interface
// while always discarding the . This is useful for when the log
// should be truncated but no snapshot should be retained. This
// should never be used for production use, and is only suitable
// for testing.
type DiscardSnapshotSink struct{}
// NewDiscardSnapshotStore is used to create a new DiscardSnapshotStore.
func NewDiscardSnapshotStore() *DiscardSnapshotStore {
return &DiscardSnapshotStore{}
}
// Create returns a valid type implementing the SnapshotSink which
// always discards the snapshot.
func (d *DiscardSnapshotStore) Create(version SnapshotVersion, index, term uint64,
configuration Configuration, configurationIndex uint64, trans Transport) (SnapshotSink, error) {
return &DiscardSnapshotSink{}, nil
}
// List returns successfully with a nil for []*SnapshotMeta.
func (d *DiscardSnapshotStore) List() ([]*SnapshotMeta, error) {
return nil, nil
}
// Open returns an error since the DiscardSnapshotStore does not
// support opening snapshots.
func (d *DiscardSnapshotStore) Open(id string) (*SnapshotMeta, io.ReadCloser, error) {
return nil, nil, fmt.Errorf("open is not supported")
}
// Write returns successfully with the length of the input byte slice
// to satisfy the WriteCloser interface
func (d *DiscardSnapshotSink) Write(b []byte) (int, error) {
return len(b), nil
}
// Close returns a nil error
func (d *DiscardSnapshotSink) Close() error {
return nil
}
// ID returns "discard" for DiscardSnapshotSink
func (d *DiscardSnapshotSink) ID() string {
return "discard"
}
// Cancel returns successfully with a nil error
func (d *DiscardSnapshotSink) Cancel() error {
return nil
}
================================================
FILE: discard_snapshot_test.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import "testing"
func TestDiscardSnapshotStoreImpl(t *testing.T) {
var impl interface{} = &DiscardSnapshotStore{}
if _, ok := impl.(SnapshotStore); !ok {
t.Fatalf("DiscardSnapshotStore not a SnapshotStore")
}
}
func TestDiscardSnapshotSinkImpl(t *testing.T) {
var impl interface{} = &DiscardSnapshotSink{}
if _, ok := impl.(SnapshotSink); !ok {
t.Fatalf("DiscardSnapshotSink not a SnapshotSink")
}
}
================================================
FILE: docs/README.md
================================================
# Raft Developer Documentation
This documentation provides a high level introduction to the `hashicorp/raft`
implementation. The intended audience is anyone interested in understanding
or contributing to the code.
## Contents
1. [Terminology](#terminology)
2. [Operations](#operations)
1. [Apply](./apply.md)
3. [Threads](#threads)
4. [Divergence](./divergence.md)
## Terminology
This documentation uses the following terms as defined.
* **Cluster** - the set of peers in the raft configuration
* **Peer** - a node that participates in the consensus protocol using `hashicorp/raft`. A
peer may be in one of the following states: **follower**, **candidate**, or **leader**.
* **Log** - the full set of log entries.
* **Log Entry** - an entry in the log. Each entry has an index that is used to order it
relative to other log entries.
* **Committed** - A log entry is considered committed if it is safe for that entry to be
applied to state machines. A log entry is committed once the leader that created the
entry has replicated it on a majority of the peers. A peer has successfully
replicated the entry once it is persisted.
* **Applied** - log entry applied to the state machine (FSM)
* **Term** - raft divides time into terms of arbitrary length. Terms are numbered with
consecutive integers. Each term begins with an election, in which one or more candidates
attempt to become leader. If a candidate wins the election, then it serves as leader for
the rest of the term. If the election ends with a split vote, the term will end with no
leader.
* **FSM** - finite state machine, stores the cluster state
* **Client** - the application that uses the `hashicorp/raft` library
## Operations
### Leader Write
Most write operations must be performed on the leader.
* RequestConfigChange - update the raft peer list configuration
* Apply - apply a log entry to the log on a majority of peers, and the FSM. See [raft apply](apply.md) for more details.
* Barrier - a special Apply that does not modify the FSM, used to wait for previous logs to be applied
* LeadershipTransfer - stop accepting client requests, and tell a different peer to start a leadership election
* Restore (Snapshot) - overwrite the cluster state with the contents of the snapshot (excluding cluster configuration)
* VerifyLeader - send a heartbeat to all voters to confirm the peer is still the leader
### Follower Write
* BootstrapCluster - store the cluster configuration in the local log store
### Read
Read operations can be performed on a peer in any state.
* AppliedIndex - get the index of the last log entry applied to the FSM
* GetConfiguration - return the latest cluster configuration
* LastContact - get the last time this peer made contact with the leader
* LastIndex - get the index of the latest stored log entry
* Leader - get the address of the peer that is currently the leader
* Snapshot - snapshot the current state of the FSM into a file
* State - return the state of the peer
* Stats - return some stats about the peer and the cluster
## Threads
Raft uses the following threads to handle operations. The name of the thread is in bold,
and a short description of the operation handled by the thread follows. The main thread is
responsible for handling many operations.
* **run** (main thread) - different behaviour based on peer state
* follower
* processRPC (from rpcCh)
* AppendEntries
* RequestVote
* InstallSnapshot
* TimeoutNow
* liveBootstrap (from bootstrapCh)
* periodic heartbeatTimer (HeartbeatTimeout)
* candidate - starts an election for itself when called
* processRPC (from rpcCh) - same as follower
* acceptVote (from askPeerForVote)
* leader - first starts replication to all peers, and applies a Noop log to ensure the new leader has committed up to the commit index
* processRPC (from rpcCh) - same as follower, however we don’t actually expect to receive any RPCs other than a RequestVote
* leadershipTransfer (from leadershipTransferCh) -
* commit (from commitCh) -
* verifyLeader (from verifyCh) -
* user restore snapshot (from userRestoreCh) -
* changeConfig (from configurationChangeCh) -
* dispatchLogs (from applyCh) - handle client Raft.Apply requests by persisting logs to disk, and notifying replication goroutines to replicate the new logs
* checkLease (periodically LeaseTimeout) -
* **runFSM** - has exclusive access to the FSM, all reads and writes must send a message to this thread. Commands:
* apply logs to the FSM, from the fsmMutateCh, from processLogs, from leaderLoop (leader) or appendEntries RPC (follower/candidate)
* restore a snapshot to the FSM, from the fsmMutateCh, from restoreUserSnapshot (leader) or installSnapshot RPC (follower/candidate)
* capture snapshot, from fsmSnapshotCh, from takeSnapshot (runSnapshot thread)
* **runSnapshot** - handles the slower part of taking a snapshot. From a pointer captured by the FSM.Snapshot operation, this thread persists the snapshot by calling FSMSnapshot.Persist. Also calls compactLogs to delete old logs.
* periodically (SnapshotInterval) takeSnapshot for log compaction
* user snapshot, from userSnapshotCh, takeSnapshot to return to the user
* **askPeerForVote (candidate only)** - short lived goroutine that synchronously sends a RequestVote RPC to all voting peers, and waits for the response. One goroutine per voting peer.
* **replicate (leader only)** - long running goroutine that synchronously sends log entry AppendEntry RPCs to all peers. Also starts the heartbeat thread, and possibly the pipelineDecode thread. Runs sendLatestSnapshot when AppendEntry fails.
* **heartbeat (leader only)** - long running goroutine that synchronously sends heartbeat AppendEntry RPCs to all peers.
* **pipelineDecode (leader only)**
================================================
FILE: docs/apply.md
================================================
# Raft Apply
Apply is the primary operation provided by raft. A client calls `raft.Apply` to apply
a command to the FSM. A command will first be committed, i.e., durably stored on a
quorum of raft nodes. Then, the committed command is applied to fsm.
This sequence diagram shows the steps involved in a `raft.Apply` operation. Each box
across the top is a separate thread. The name in the box identifies the state of the peer
(leader or follower) and the thread (`<peer state>:<thread name>`). When there are
multiple copies of the thread, it is indicated with `(each peer)`.
```mermaid
sequenceDiagram
autonumber
participant client
participant leadermain as leader:main
participant leaderfsm as leader:fsm
participant leaderreplicate as leader:replicate (each peer)
participant followermain as follower:main (each peer)
participant followerfsm as follower:fsm (each peer)
client-)leadermain: applyCh to dispatchLogs
leadermain->>leadermain: store logs to disk
leadermain-)leaderreplicate: triggerCh
leaderreplicate-->>followermain: Transport.AppendEntries RPC
followermain->>followermain: store logs to disk
opt leader commit index is ahead of peer commit index
followermain-)followerfsm: fsmMutateCh <br>apply committed logs
followerfsm->>followerfsm: fsm.Apply
end
followermain-->>leaderreplicate: respond success=true
leaderreplicate->>leaderreplicate: update commitment
opt quorum commit index has increased
leaderreplicate-)leadermain: commitCh
leadermain-)leaderfsm: fsmMutateCh
leaderfsm->>leaderfsm: fsm.Apply
leaderfsm-)client: future.respond
end
```
Following is the description of each step as shown in the above diagram
1. The raft node handles the `raft.Apply` call by creating a new log entry and send the entry
to the `applyCh` channel.
2. If the node is not a leader, the method will return an error of `ErrNotLeader`. Otherwise,
the main loop of the leader node calls `raft.dispatchLogs` to write the log entry locally.
3. `raft.dispatchLogs` also sends a notification to the `f.triggerCh` of each follower (`map[ServerID]*followerReplication`) to start replicating log entries to the followers.
4. For each follower, the leader has started a long running routine (`replicate`) to
replicates log entries. On receiving a log entry to the `triggerCh`, the `replicate`
routine makes the `Transport.AppendEntries` RPC call to do the replication. The log entries
to be replicated are from the follower's nextIndex to min(nextIndex + maxAppendEntries,
leader's lastIndex). Another parameter to AppendEntries is the LeaderCommitIndex. Following
is some examples:
```
AppendEntries(Log: 1..5, LeaderCommitIndex: 0) // Replicating log entries 1..5,
// the leader hasn't committed any log entry;
AppendEntries(Log: 6..8, LeaderCommitIndex: 4) // Replicating log entries 6..8,
// log 0..4 are committed after the leader receives
// a quorum of responses
AppendEntries(Log: 9, LeaderCommitIndex: 8) // Replicating log entry 9,
// log 5..8 are committed.
AppendEntries(Log: , LeaderCommitIndex: 9) // no new log, bumping the commit index
// to let the follower stay up to date of the
// latest committed entries
```
5. The follower which receives the `appendEntries` RPC calls invokes `raft.appendEntries` to handle
the request. It appends any new entries to the local log store.
6. In the same method on the follower as step 5, if the LeaderCommitIndex > this follower's
commitIndex, the follower updates it's commitIndex to min(LeaderCommitIndex, index of its last
log entries). In the first `AppendEntries` call of the above example, the follower won't
update it's commitIndex, because LeaderCommitIndex is 0. The last RPC call doesn't contain
any new log, whereas the follower will update its commitIndex to 9.
Further, the follower start `processLogs` to send all the committed entries that haven't been
applied to fsm (`fsmMutateCh <- batch`). Otherwise (i.e., `commitIndex <= lastApplied`),
the appendEntries RPC call returns success.
Therefore, it's possible that a very small window of time exists when all followers have
committed the log to disk, the write has been realized in the FSM of the leader but the
followers have not yet applied the log to their FSM.
7. The peer applies the committed entries to the FSM.
8. If all went well, the follower responds success (`resp.Success = true`) to the
`appendEntries` RPC call.
9. On receiving the successful response from `Transport.AppendEntries`, the leader needs to
update the fsm based on the replicated log entries. Specifically, the leader finds the
highest log entry index that has been replicated to a quorum of the servers (
`if quorumMatchIndex > c.commitIndex`), update `commitIndex` to that index, and
notify through the `commitCh` channel.
10. The leader receives the notification on the `r.leaderState.commitCh` channel and starts
grouping the entries that can be applied to the fsm.
11. `processLogs` applies all the committed entries that haven't been applied by batching the log entries and forwarding them through the `fsmMutateCh` channel to fsm.
12. The actual place applying the committed log entries is in the main loop of `runFSM()`.
13. After the log entries that contains the client req are applied to the fsm, the fsm
module will set the responses to the client request (`req.future.respond(nil)`). From the
client's point of view, the future returned by `raft.Apply` should now be unblocked and
calls to `Error()` or `Response()` should return the data at this point.
================================================
FILE: docs/divergence.md
================================================
# HashiCorp Raft Divergences
In 2013 HashiCorp created its own Raft implementation based on the just
released [Raft paper by Diego Ongaro and John Ousterhout][paper]. This was
before [Diego's subsequent Raft dissertation][diss] in 2014, and long before
third party analyses such as Heidi Howard and Ittai Abraham's [Raft does not
Guarantee Liveness in the face of Network Faults ][live]
in 2020.[^1]
HashiCorp's Raft library usage grew rapidly through its use in [Consul][consul]
and [Nomad][nomad], and [later Vault][vault], in parallel with rapidly
expanding use in [etcd][etcd] and other implementations.
The explosion in activity between live systems and research led to a wide
divergence between not only implementations, but implementations and the
original paper and dissertation.
This document attempts to explain where HashiCorp Raft either meaningfully diverges
from the original Raft paper, or makes an implementation choice not explicitly
outlined in the paper.
This is **not** expected to be a comprehensive list. Additions and edits are
welcome!
## Asynchronous Heartbeats
The Raft paper defines heartbeats as empty AppendEntries RPCs which are sent by
the leader to each server after elections and during idle periods to prevent
election timeouts.
HashiCorp Raft performs [heartbeating concurrently][async-heart] with other
AppendEntries RPCs to avoid having to set the election timeout high enough to
account for the max acceptable disk operation. This allows the heartbeat
timeout to detect network partitions much more quickly without risking causing
an election during periodic but ephemeral spikes in disk io latency.
## Rejecting votes when there's already a leader
The [Raft does not Guarantee liveness][live] paper describes how certain
partitions can prevent Raft clusters from making progress by causing continual
elections.
HashiCorp Raft implements the second of the suggested fixes from Howard's
paper: rejecting vote request RPCs when there is already an established leader.
The paper defines this more precisely as:
> ...ignore RequestVote RPCs if they have received an AppendEntries RPC from
> the leader within the election timeout.
This approach is actually mentioned in the Cluster membership changes section
of the original Raft paper, but explicitly excludes its use during "normal"
elections:
> To prevent this problem, servers disregard RequestVote RPCs when they believe
> a current leader exists. Specifically, if a server receives a RequestVote RPC
> within the minimum election timeout of hearing from a current leader, it does
> not update its term or grant its vote. This does not affect normal
> elections...
So HashiCorp Raft follows the later paper's suggestion and ignores the original
paper's exclusion of this logic during normal operation.
## Pre-Vote
[HashiCorp Raft implements the Pre-Vote extension][prevote-pr] defined in the
[Raft dissertation][diss] (§9.6). Pre-Vote is an optimization where a candidate
discovers whether its index is up to date and therefore able to win an election
before incrementing its term and causing an election.
The Pre-Vote extension is enabled by default but may be disabled in using the
[Config.PreVoteDisabled][prevote-config] flag.
## Leadership Transfer
[HashiCorp Raft implements the Leadership Transfer extension][transleader-pr]
as defined in the [Raft dissertation][diss] (§3.10). Leadership transfer is an
optimization that allows the current leader to hand off leadership to a
follower to avoid waiting for the election timeout during regular operations
such as restarts and upgrades.
While leadership transfer in defined in the Raft dissertation, HashiCorp Raft
extends the specification slightly because of _another_ divergence in HashiCorp
Raft: [rejecting votes when there's already a
leader](#rejecting-votes-when-theres-already-a-leader). Since other followers
would reject the intended new-leader's request for a vote, HashiCorp Raft adds
an extra [`LeadershipTransfer` flag][transleader-flag] to override that
behavior in the case of leadership transfers.
All Raft members should support leadership transfers before a transfer is
attempted. The feature is **not** enabled by default and requires explicitly
triggering at the application level. Consul was the first to implement this via
mechanisms in their [API/CLI][transleader-cli] and [graceful agent
shutdown][transleader-shutdown].
[^1]: See https://raft.github.io/ for a comprehensive list of papers and
resources.
[paper]: https://raft.github.io/raft.pdf
[diss]: https://github.com/ongardie/dissertation#readme
[live]: https://decentralizedthoughts.github.io/2020-12-12-raft-liveness-full-omission/
[consul]: https://github.com/hashicorp/consul
[nomad]: https://github.com/hashicorp/nomad
[vault]: https://github.com/hashicorp/vault
[etcd]: https://etcd.io/
[async-heart]: https://github.com/hashicorp/raft/blob/v1.7.3/replication.go#L385-L387
[prevote-pr]: https://github.com/hashicorp/raft/pull/530
[prevote-config]: https://pkg.go.dev/github.com/hashicorp/raft#Config.PreVoteDisabled
[transleader-pr]: https://github.com/hashicorp/raft/pull/306
[transleader-flag]: https://pkg.go.dev/github.com/hashicorp/raft#RequestVoteRequest.LeadershipTransfer
[transleader-cli]: https://github.com/hashicorp/consul/issues/5405
[transleader-shutdown]: https://github.com/hashicorp/consul/issues/5406
================================================
FILE: file_snapshot.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"hash"
"hash/crc64"
"io"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
hclog "github.com/hashicorp/go-hclog"
)
const (
testPath = "permTest"
snapPath = "snapshots"
metaFilePath = "meta.json"
stateFilePath = "state.bin"
tmpSuffix = ".tmp"
)
// FileSnapshotStore implements the SnapshotStore interface and allows
// snapshots to be made on the local disk.
type FileSnapshotStore struct {
path string
retain int
logger hclog.Logger
// noSync, if true, skips crash-safe file fsync api calls.
// It's a private field, only used in testing
noSync bool
}
type snapMetaSlice []*fileSnapshotMeta
// FileSnapshotSink implements SnapshotSink with a file.
type FileSnapshotSink struct {
store *FileSnapshotStore
logger hclog.Logger
dir string
parentDir string
meta fileSnapshotMeta
noSync bool
stateFile *os.File
stateHash hash.Hash64
buffered *bufio.Writer
closed bool
}
// fileSnapshotMeta is stored on disk. We also put a CRC
// on disk so that we can verify the snapshot.
type fileSnapshotMeta struct {
SnapshotMeta
CRC []byte
}
// bufferedFile is returned when we open a snapshot. This way
// reads are buffered and the file still gets closed.
type bufferedFile struct {
bh *bufio.Reader
fh *os.File
}
func (b *bufferedFile) Read(p []byte) (n int, err error) {
return b.bh.Read(p)
}
func (b *bufferedFile) Close() error {
return b.fh.Close()
}
// NewFileSnapshotStoreWithLogger creates a new FileSnapshotStore based
// on a base directory. The `retain` parameter controls how many
// snapshots are retained. Must be at least 1.
func NewFileSnapshotStoreWithLogger(base string, retain int, logger hclog.Logger) (*FileSnapshotStore, error) {
if retain < 1 {
return nil, fmt.Errorf("must retain at least one snapshot")
}
if logger == nil {
logger = hclog.New(&hclog.LoggerOptions{
Name: "snapshot",
Output: hclog.DefaultOutput,
Level: hclog.DefaultLevel,
})
}
// Ensure our path exists
path := filepath.Join(base, snapPath)
if err := os.MkdirAll(path, 0o755); err != nil && !os.IsExist(err) {
return nil, fmt.Errorf("snapshot path not accessible: %v", err)
}
// Setup the store
store := &FileSnapshotStore{
path: path,
retain: retain,
logger: logger,
}
// Do a permissions test
if err := store.testPermissions(); err != nil {
return nil, fmt.Errorf("permissions test failed: %v", err)
}
return store, nil
}
// NewFileSnapshotStore creates a new FileSnapshotStore based
// on a base directory. The `retain` parameter controls how many
// snapshots are retained. Must be at least 1.
func NewFileSnapshotStore(base string, retain int, logOutput io.Writer) (*FileSnapshotStore, error) {
if logOutput == nil {
logOutput = os.Stderr
}
return NewFileSnapshotStoreWithLogger(base, retain, hclog.New(&hclog.LoggerOptions{
Name: "snapshot",
Output: logOutput,
Level: hclog.DefaultLevel,
}))
}
// testPermissions tries to touch a file in our path to see if it works.
func (f *FileSnapshotStore) testPermissions() error {
path := filepath.Join(f.path, testPath)
fh, err := os.Create(path)
if err != nil {
return err
}
if err = fh.Close(); err != nil {
return err
}
if err = os.Remove(path); err != nil {
return err
}
return nil
}
// snapshotName generates a name for the snapshot.
func snapshotName(term, index uint64) string {
now := time.Now()
msec := now.UnixNano() / int64(time.Millisecond)
return fmt.Sprintf("%d-%d-%d", term, index, msec)
}
// Create is used to start a new snapshot
func (f *FileSnapshotStore) Create(version SnapshotVersion, index, term uint64,
configuration Configuration, configurationIndex uint64, trans Transport) (SnapshotSink, error) {
// We only support version 1 snapshots at this time.
if version != 1 {
return nil, fmt.Errorf("unsupported snapshot version %d", version)
}
// Create a new path
name := snapshotName(term, index)
path := filepath.Join(f.path, name+tmpSuffix)
f.logger.Info("creating new snapshot", "path", path)
// Make the directory
if err := os.MkdirAll(path, 0o755); err != nil {
f.logger.Error("failed to make snapshot directly", "error", err)
return nil, err
}
// Create the sink
sink := &FileSnapshotSink{
store: f,
logger: f.logger,
dir: path,
parentDir: f.path,
noSync: f.noSync,
meta: fileSnapshotMeta{
SnapshotMeta: SnapshotMeta{
Version: version,
ID: name,
Index: index,
Term: term,
Peers: encodePeers(configuration, trans),
Configuration: configuration,
ConfigurationIndex: configurationIndex,
},
CRC: nil,
},
}
// Write out the meta data
if err := sink.writeMeta(); err != nil {
f.logger.Error("failed to write metadata", "error", err)
return nil, err
}
// Open the state file
statePath := filepath.Join(path, stateFilePath)
fh, err := os.Create(statePath)
if err != nil {
f.logger.Error("failed to create state file", "error", err)
return nil, err
}
sink.stateFile = fh
// Create a CRC64 hash
sink.stateHash = crc64.New(crc64.MakeTable(crc64.ECMA))
// Wrap both the hash and file in a MultiWriter with buffering
multi := io.MultiWriter(sink.stateFile, sink.stateHash)
sink.buffered = bufio.NewWriter(multi)
// Done
return sink, nil
}
// List returns available snapshots in the store.
func (f *FileSnapshotStore) List() ([]*SnapshotMeta, error) {
// Get the eligible snapshots
snapshots, err := f.getSnapshots()
if err != nil {
f.logger.Error("failed to get snapshots", "error", err)
return nil, err
}
var snapMeta []*SnapshotMeta
for _, meta := range snapshots {
snapMeta = append(snapMeta, &meta.SnapshotMeta)
if len(snapMeta) == f.retain {
break
}
}
return snapMeta, nil
}
// getSnapshots returns all the known snapshots.
func (f *FileSnapshotStore) getSnapshots() ([]*fileSnapshotMeta, error) {
// Get the eligible snapshots
snapshots, err := os.ReadDir(f.path)
if err != nil {
f.logger.Error("failed to scan snapshot directory", "error", err)
return nil, err
}
// Populate the metadata
var snapMeta []*fileSnapshotMeta
for _, snap := range snapshots {
// Ignore any files
if !snap.IsDir() {
continue
}
// Ignore any temporary snapshots
dirName := snap.Name()
if strings.HasSuffix(dirName, tmpSuffix) {
f.logger.Warn("found temporary snapshot", "name", dirName)
continue
}
// Try to read the meta data
meta, err := f.readMeta(dirName)
if err != nil {
f.logger.Warn("failed to read metadata", "name", dirName, "error", err)
continue
}
// Make sure we can understand this version.
if meta.Version < SnapshotVersionMin || meta.Version > SnapshotVersionMax {
f.logger.Warn("snapshot version not supported", "name", dirName, "version", meta.Version)
continue
}
// Append, but only return up to the retain count
snapMeta = append(snapMeta, meta)
}
// Sort the snapshot, reverse so we get new -> old
sort.Sort(sort.Reverse(snapMetaSlice(snapMeta)))
return snapMeta, nil
}
// readMeta is used to read the meta data for a given named backup
func (f *FileSnapshotStore) readMeta(name string) (*fileSnapshotMeta, error) {
// Open the meta file
metaPath := filepath.Join(f.path, name, metaFilePath)
fh, err := os.Open(metaPath)
if err != nil {
return nil, err
}
defer func() { _ = fh.Close() }()
// Buffer the file IO
buffered := bufio.NewReader(fh)
// Read in the JSON
meta := &fileSnapshotMeta{}
dec := json.NewDecoder(buffered)
if err := dec.Decode(meta); err != nil {
return nil, err
}
return meta, nil
}
// Open takes a snapshot ID and returns a ReadCloser for that snapshot.
func (f *FileSnapshotStore) Open(id string) (*SnapshotMeta, io.ReadCloser, error) {
// Get the metadata
meta, err := f.readMeta(id)
if err != nil {
f.logger.Error("failed to get meta data to open snapshot", "error", err)
return nil, nil, err
}
// Open the state file
statePath := filepath.Join(f.path, id, stateFilePath)
fh, err := os.Open(statePath)
if err != nil {
f.logger.Error("failed to open state file", "error", err)
return nil, nil, err
}
// Create a CRC64 hash
stateHash := crc64.New(crc64.MakeTable(crc64.ECMA))
// Compute the hash
_, err = io.Copy(stateHash, fh)
if err != nil {
f.logger.Error("failed to read state file", "error", err)
_ = fh.Close()
return nil, nil, err
}
// Verify the hash
computed := stateHash.Sum(nil)
if !bytes.Equal(meta.CRC, computed) {
f.logger.Error("CRC checksum failed", "stored", meta.CRC, "computed", computed)
_ = fh.Close()
return nil, nil, fmt.Errorf("CRC mismatch")
}
// Seek to the start
if _, err := fh.Seek(0, 0); err != nil {
f.logger.Error("state file seek failed", "error", err)
_ = fh.Close()
return nil, nil, err
}
// Return a buffered file
buffered := &bufferedFile{
bh: bufio.NewReader(fh),
fh: fh,
}
return &meta.SnapshotMeta, buffered, nil
}
// ReapSnapshots reaps any snapshots beyond the retain count.
func (f *FileSnapshotStore) ReapSnapshots() error {
snapshots, err := f.getSnapshots()
if err != nil {
f.logger.Error("failed to get snapshots", "error", err)
return err
}
for i := f.retain; i < len(snapshots); i++ {
path := filepath.Join(f.path, snapshots[i].ID)
f.logger.Info("reaping snapshot", "path", path)
if err := os.RemoveAll(path); err != nil {
f.logger.Error("failed to reap snapshot", "path", path, "error", err)
return err
}
}
return nil
}
// ID returns the ID of the snapshot, can be used with Open()
// after the snapshot is finalized.
func (s *FileSnapshotSink) ID() string {
return s.meta.ID
}
// Write is used to append to the state file. We write to the
// buffered IO object to reduce the amount of context switches.
func (s *FileSnapshotSink) Write(b []byte) (int, error) {
return s.buffered.Write(b)
}
// Close is used to indicate a successful end.
func (s *FileSnapshotSink) Close() error {
// Make sure close is idempotent
if s.closed {
return nil
}
s.closed = true
// Close the open handles
if err := s.finalize(); err != nil {
s.logger.Error("failed to finalize snapshot", "error", err)
if delErr := os.RemoveAll(s.dir); delErr != nil {
s.logger.Error("failed to delete temporary snapshot directory", "path", s.dir, "error", delErr)
return delErr
}
return err
}
// Write out the meta data
if err := s.writeMeta(); err != nil {
s.logger.Error("failed to write metadata", "error", err)
return err
}
// Move the directory into place
newPath := strings.TrimSuffix(s.dir, tmpSuffix)
if err := os.Rename(s.dir, newPath); err != nil {
s.logger.Error("failed to move snapshot into place", "error", err)
return err
}
if !s.noSync && runtime.GOOS != "windows" { // skipping fsync for directory entry edits on Windows, only needed for *nix style file systems
parentFH, err := os.Open(s.parentDir)
if err != nil {
s.logger.Error("failed to open snapshot parent directory", "path", s.parentDir, "error", err)
return err
}
defer func() { _ = parentFH.Close() }()
if err = parentFH.Sync(); err != nil {
s.logger.Error("failed syncing parent directory", "path", s.parentDir, "error", err)
return err
}
}
// Reap any old snapshots
if err := s.store.ReapSnapshots(); err != nil {
return err
}
return nil
}
// Cancel is used to indicate an unsuccessful end.
func (s *FileSnapshotSink) Cancel() error {
// Make sure close is idempotent
if s.closed {
return nil
}
s.closed = true
// Close the open handles
if err := s.finalize(); err != nil {
s.logger.Error("failed to finalize snapshot", "error", err)
return err
}
// Attempt to remove all artifacts
return os.RemoveAll(s.dir)
}
// finalize is used to close all of our resources.
func (s *FileSnapshotSink) finalize() error {
// Flush any remaining data
if err := s.buffered.Flush(); err != nil {
return err
}
// Sync to force fsync to disk
if !s.noSync {
if err := s.stateFile.Sync(); err != nil {
return err
}
}
// Get the file size
stat, statErr := s.stateFile.Stat()
// Close the file
if err := s.stateFile.Close(); err != nil {
return err
}
// Set the file size, check after we close
if statErr != nil {
return statErr
}
s.meta.Size = stat.Size()
// Set the CRC
s.meta.CRC = s.stateHash.Sum(nil)
return nil
}
// writeMeta is used to write out the metadata we have.
func (s *FileSnapshotSink) writeMeta() error {
var err error
// Open the meta file
metaPath := filepath.Join(s.dir, metaFilePath)
var fh *os.File
fh, err = os.Create(metaPath)
if err != nil {
return err
}
defer func() { _ = fh.Close() }()
// Buffer the file IO
buffered := bufio.NewWriter(fh)
// Write out as JSON
enc := json.NewEncoder(buffered)
if err = enc.Encode(&s.meta); err != nil {
return err
}
if err = buffered.Flush(); err != nil {
return err
}
if !s.noSync {
if err = fh.Sync(); err != nil {
return err
}
}
return nil
}
// Implement the sort interface for []*fileSnapshotMeta.
func (s snapMetaSlice) Len() int {
return len(s)
}
func (s snapMetaSlice) Less(i, j int) bool {
if s[i].Term != s[j].Term {
return s[i].Term < s[j].Term
}
if s[i].Index != s[j].Index {
return s[i].Index < s[j].Index
}
return s[i].ID < s[j].ID
}
func (s snapMetaSlice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
================================================
FILE: file_snapshot_test.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"bytes"
"io"
"os"
"reflect"
"runtime"
"testing"
)
func TestFileSnapshotStoreImpl(t *testing.T) {
var impl interface{} = &FileSnapshotStore{}
if _, ok := impl.(SnapshotStore); !ok {
t.Fatalf("FileSnapshotStore not a SnapshotStore")
}
}
func TestFileSnapshotSinkImpl(t *testing.T) {
var impl interface{} = &FileSnapshotSink{}
if _, ok := impl.(SnapshotSink); !ok {
t.Fatalf("FileSnapshotSink not a SnapshotSink")
}
}
func TestFileSS_CreateSnapshotMissingParentDir(t *testing.T) {
parent, err := os.MkdirTemp("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer func() { _ = os.RemoveAll(parent) }()
dir, err := os.MkdirTemp(parent, "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
snap, err := NewFileSnapshotStoreWithLogger(dir, 3, newTestLogger(t))
if err != nil {
t.Fatalf("err: %v", err)
}
_ = os.RemoveAll(parent)
_, trans := NewInmemTransport(NewInmemAddr())
_, err = snap.Create(SnapshotVersionMax, 10, 3, Configuration{}, 0, trans)
if err != nil {
t.Fatalf("should not fail when using non existing parent")
}
}
func TestFileSS_CreateSnapshot(t *testing.T) {
// Create a test dir
dir, err := os.MkdirTemp("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer func() { _ = os.RemoveAll(dir) }()
snap, err := NewFileSnapshotStoreWithLogger(dir, 3, newTestLogger(t))
if err != nil {
t.Fatalf("err: %v", err)
}
// Check no snapshots
snaps, err := snap.List()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(snaps) != 0 {
t.Fatalf("did not expect any snapshots: %v", snaps)
}
// Create a new sink
var configuration Configuration
configuration.Servers = append(configuration.Servers, Server{
Suffrage: Voter,
ID: ServerID("my id"),
Address: ServerAddress("over here"),
})
_, trans := NewInmemTransport(NewInmemAddr())
sink, err := snap.Create(SnapshotVersionMax, 10, 3, configuration, 2, trans)
if err != nil {
t.Fatalf("err: %v", err)
}
// The sink is not done, should not be in a list!
snaps, err = snap.List()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(snaps) != 0 {
t.Fatalf("did not expect any snapshots: %v", snaps)
}
// Write to the sink
_, err = sink.Write([]byte("first\n"))
if err != nil {
t.Fatalf("err: %v", err)
}
_, err = sink.Write([]byte("second\n"))
if err != nil {
t.Fatalf("err: %v", err)
}
// Done!
err = sink.Close()
if err != nil {
t.Fatalf("err: %v", err)
}
// Should have a snapshot!
snaps, err = snap.List()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(snaps) != 1 {
t.Fatalf("expect a snapshots: %v", snaps)
}
// Check the latest
latest := snaps[0]
if latest.Index != 10 {
t.Fatalf("bad snapshot: %v", *latest)
}
if latest.Term != 3 {
t.Fatalf("bad snapshot: %v", *latest)
}
if !reflect.DeepEqual(latest.Configuration, configuration) {
t.Fatalf("bad snapshot: %v", *latest)
}
if latest.ConfigurationIndex != 2 {
t.Fatalf("bad snapshot: %v", *latest)
}
if latest.Size != 13 {
t.Fatalf("bad snapshot: %v", *latest)
}
// Read the snapshot
_, r, err := snap.Open(latest.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
// Read out everything
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
t.Fatalf("err: %v", err)
}
if err := r.Close(); err != nil {
t.Fatalf("err: %v", err)
}
// Ensure a match
if !bytes.Equal(buf.Bytes(), []byte("first\nsecond\n")) {
t.Fatalf("content mismatch")
}
}
func TestFileSS_CancelSnapshot(t *testing.T) {
// Create a test dir
dir, err := os.MkdirTemp("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer func() { _ = os.RemoveAll(dir) }()
snap, err := NewFileSnapshotStoreWithLogger(dir, 3, newTestLogger(t))
if err != nil {
t.Fatalf("err: %v", err)
}
// Create a new sink
_, trans := NewInmemTransport(NewInmemAddr())
sink, err := snap.Create(SnapshotVersionMax, 10, 3, Configuration{}, 0, trans)
if err != nil {
t.Fatalf("err: %v", err)
}
// Cancel the snapshot! Should delete
err = sink.Cancel()
if err != nil {
t.Fatalf("err: %v", err)
}
// The sink is canceled, should not be in a list!
snaps, err := snap.List()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(snaps) != 0 {
t.Fatalf("did not expect any snapshots: %v", snaps)
}
}
func TestFileSS_Retention(t *testing.T) {
var err error
// Create a test dir
var dir string
dir, err = os.MkdirTemp("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer func() { _ = os.RemoveAll(dir) }()
var snap *FileSnapshotStore
snap, err = NewFileSnapshotStoreWithLogger(dir, 2, newTestLogger(t))
if err != nil {
t.Fatalf("err: %v", err)
}
// Create a few snapshots
_, trans := NewInmemTransport(NewInmemAddr())
for i := 10; i < 15; i++ {
var sink SnapshotSink
sink, err = snap.Create(SnapshotVersionMax, uint64(i), 3, Configuration{}, 0, trans)
if err != nil {
t.Fatalf("err: %v", err)
}
err = sink.Close()
if err != nil {
t.Fatalf("err: %v", err)
}
}
// Should only have 2 listed!
var snaps []*SnapshotMeta
snaps, err = snap.List()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(snaps) != 2 {
t.Fatalf("expect 2 snapshots: %v", snaps)
}
// Check they are the latest
if snaps[0].Index != 14 {
t.Fatalf("bad snap: %#v", *snaps[0])
}
if snaps[1].Index != 13 {
t.Fatalf("bad snap: %#v", *snaps[1])
}
}
func TestFileSS_BadPerm(t *testing.T) {
var err error
if runtime.GOOS == "windows" {
t.Skip("skipping file permission test on windows")
}
// Create a temp dir
var dir1 string
dir1, err = os.MkdirTemp("", "raft")
if err != nil {
t.Fatalf("err: %s", err)
}
defer func() { _ = os.RemoveAll(dir1) }()
// Create a sub dir and remove all permissions
var dir2 string
dir2, err = os.MkdirTemp(dir1, "badperm")
if err != nil {
t.Fatalf("err: %s", err)
}
if err = os.Chmod(dir2, 0o00); err != nil {
t.Fatalf("err: %s", err)
}
defer func() { _ = os.Chmod(dir2, 0777) }() // Set perms back for delete
// Should fail
if _, err = NewFileSnapshotStore(dir2, 3, nil); err == nil {
t.Fatalf("should fail to use dir with bad perms")
}
}
func TestFileSS_MissingParentDir(t *testing.T) {
parent, err := os.MkdirTemp("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer func() { _ = os.RemoveAll(parent) }()
dir, err := os.MkdirTemp(parent, "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
_ = os.RemoveAll(parent)
_, err = NewFileSnapshotStore(dir, 3, nil)
if err != nil {
t.Fatalf("should not fail when using non existing parent")
}
}
func TestFileSS_Ordering(t *testing.T) {
// Create a test dir
dir, err := os.MkdirTemp("", "raft")
if err != nil {
t.Fatalf("err: %v ", err)
}
defer func() { _ = os.RemoveAll(dir) }()
snap, err := NewFileSnapshotStoreWithLogger(dir, 3, newTestLogger(t))
if err != nil {
t.Fatalf("err: %v", err)
}
// Create a new sink
_, trans := NewInmemTransport(NewInmemAddr())
sink, err := snap.Create(SnapshotVersionMax, 130350, 5, Configuration{}, 0, trans)
if err != nil {
t.Fatalf("err: %v", err)
}
err = sink.Close()
if err != nil {
t.Fatalf("err: %v", err)
}
sink, err = snap.Create(SnapshotVersionMax, 204917, 36, Configuration{}, 0, trans)
if err != nil {
t.Fatalf("err: %v", err)
}
err = sink.Close()
if err != nil {
t.Fatalf("err: %v", err)
}
// Should only have 2 listed!
snaps, err := snap.List()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(snaps) != 2 {
t.Fatalf("expect 2 snapshots: %v", snaps)
}
// Check they are ordered
if snaps[0].Term != 36 {
t.Fatalf("bad snap: %#v", *snaps[0])
}
if snaps[1].Term != 5 {
t.Fatalf("bad snap: %#v", *snaps[1])
}
}
================================================
FILE: fsm.go
================================================
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: MPL-2.0
package raft
import (
"fmt"
"io"
"time"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-metrics/compat"
)
// FSM is implemented by clients to make use of the replicated log.
type FSM interface {
// Apply is called once a log entry is committed by a majority of the cluster.
//
// Apply should apply the log to the FSM. Apply must be deterministic and
// produce the same result on all peers in the cluster.
//
// The returned value is returned to the client as the ApplyFuture.Response.
Apply(*Log) interface{}
// Snapshot returns an FSMSnapshot used to: support log compaction, to
// restore the FSM to a previous state, or to bring out-of-date followers up
// to a recent log index.
//
// The Snapshot implementation should return quickly, because Apply can not
// be called while Snapshot is running. Generally this means Snapshot should
// only capture a pointer to the state, and any expensive IO should happen
// as part of FSMSnapshot.Persist.
//
// Apply and Snapshot are always called from the same thread, but Apply will
// be called concurrently with FSMSnapshot.Persist. This means the FSM should
// be implemented to allow for concurrent updates while a snapshot is happening.
//
// Clients of this library should make no assumptions about whether a returned
// Snapshot() will actually be stored by Raft. In fact it's quite possible that
// any Snapshot returned by this call will be discarded, and that
// FSMSnapshot.Persist will never be called. Raft will always call
// FSMSnapshot.Release however.
Snapshot() (FSMSnapshot, error)
// Restore is used to restore an FSM from a snapshot. It is not called
// concurrently with any other command. The FSM must discard all previous
// state before restoring the snapshot.
Restore(snapshot io.ReadCloser) error
}
// BatchingFSM extends the FSM interface to add an ApplyBatch function. This can
// optionally be implemented by clients to enable multiple logs to be applied to
// the FSM in batches. Up to MaxAppendEntries could be sent in a batch.
type BatchingFSM interface {
// ApplyBatch is invoked once a batch of log entries has been committed and
// are ready to be applied to the FSM. ApplyBatch will take in an array of
// log entries. These log entries will be in the order they were committed,
// will not have gaps, and could be of a few log types. Clients should check
// the log type prior to attempting to decode the data attached. Presently
// the LogCommand and LogConfiguration types will be sent.
//
// The returned slice must be the same length as the input and each response
// should correlate to the log at the same index of the input. The returned
// values will be made available in the ApplyFuture returned by Raft.Apply
// method if that method was called on the same Raft node as the FSM.
ApplyBatch([]*Log) []interface{}
FSM
}
// FSMSnapshot is returned by an FSM in response to a Snapshot
// It must be safe to invoke FSMSnapshot methods with concurrent
// calls to Apply.
type FSMSnapshot interface {
// Persist should dump all necessary state to the WriteCloser 'sink',
// and call sink.Close() when finished or call sink.Cancel() on error.
Persist(sink SnapshotSink) error
// Release is invoked when we are finished with the snapshot.
Release()
}
// runFSM is a long running goroutine responsible for applying logs
// to the FSM. This is done async of other logs since we don't want
// the FSM to block our internal operations.
func (r *Raft) runFSM() {
var lastIndex, lastTerm uint64
batchingFSM, batchingEnabled := r.fsm.(BatchingFSM)
configStore, configStoreEnabled := r.fsm.(ConfigurationStore)
applySingle := func(req *commitTuple) {
// Apply the log if a command or config change
var resp interface{}
// Make sure we send a response
defer func() {
// Invoke the future if given
if req.future != nil {
req.future.response = resp
req.future.respond(nil)
}
}()
switch req.log.Type {
case LogCommand:
start := time.Now()
resp = r.fsm.Apply(req.log)
metrics.MeasureSince([]string{"raft", "fsm", "apply"}, start)
case LogConfiguration:
if !configStoreEnabled {
// Return early to avoid incrementing the index and term for
// an unimplemented operation.
return
}
start := time.Now()
configStore.StoreConfiguration(req.log.Index, DecodeConfiguration(req.log.Data))
metrics.MeasureSince([]string{"raft", "fsm", "store_config"}, start)
}
// Update the indexes
lastIndex = req.log.Index
lastTerm = req.log.Term
}
applyBatch := func(reqs []*commitTuple) {
if !batchingEnabled {
for _, ct := range reqs {
applySingle(ct)
}
return
}
// Only send LogCommand and LogConfiguration log types. LogBarrier types
// will not be sent to the FSM.
shouldSend := func(l *Log) bool {
switch l.Type {
case LogCommand, LogConfiguration:
return true
}
return false
}
var lastBatchIndex, lastBatchTerm uint64
sendLogs := make([]*Log, 0, len(reqs))
for _, req := range reqs {
if shouldSend(req.log) {
sendLogs = append(sendLogs, req.log)
}
lastBatchIndex = req.log.Index
lastBatchTerm = req.log.Term
}
var responses []interface{}
if len(sendLogs) > 0 {
start := time.Now()
responses = batchingFSM.ApplyBatch(sendLogs)
metrics.MeasureSince([]string{"raft", "fsm", "applyBatch"}, start)
metrics.AddSample([]string{"raft", "fsm", "applyBatchNum"}, float32(len(reqs)))
// Ensure we get the expected responses
if len(sendLogs) != len(responses) {
panic("invalid number of responses")
}
}
// Update the indexes
lastIndex = lastBatchIndex
lastTerm = lastBatchTerm
var i int
for _, req := range reqs {
var resp interface{}
// If the log was sent to the FSM, retrieve the response.
if shouldSend(req.log) {
resp = responses[i]
i++
}
if req.future != nil {
req.future.response = resp
req.future.respond(nil)
}
}
}
restore := func(req *restoreFuture) {
// Open the snapshot
meta, source, err := r.snapshots.Open(req.ID)
if err != nil {
req.respond(fmt.Errorf("failed to open snapshot %v: %v", req.ID, err))
return
}
defer func() { _ = source.Close() }()
snapLogger := r.logger.With(
"id", req.ID,
"last-index", meta.Index,
"last-term", meta.Term,
"size-in-bytes", meta.Size,
)
// Attempt to restore
if err := fsmRestoreAndMeasure(snapLogger, r.fsm, source, meta.Size); err != nil {
gitextract_c91ofz_i/ ├── .github/ │ ├── CODEOWNERS │ ├── dependabot.yml │ ├── pull_request_template.md │ ├── stale.yml │ └── workflows/ │ ├── ci.yml │ └── two-step-pr-approval.yml ├── .gitignore ├── .gitmodules ├── .golangci-lint.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── api.go ├── bench/ │ └── bench.go ├── bench_test.go ├── commands.go ├── commitment.go ├── commitment_test.go ├── config.go ├── configuration.go ├── configuration_test.go ├── discard_snapshot.go ├── discard_snapshot_test.go ├── docs/ │ ├── README.md │ ├── apply.md │ └── divergence.md ├── file_snapshot.go ├── file_snapshot_test.go ├── fsm.go ├── future.go ├── future_test.go ├── fuzzy/ │ ├── apply_src.go │ ├── cluster.go │ ├── fsm.go │ ├── fsm_batch.go │ ├── go.mod │ ├── go.sum │ ├── leadershiptransfer_test.go │ ├── membership_test.go │ ├── node.go │ ├── partition_test.go │ ├── readme.md │ ├── resolve.go │ ├── simple_test.go │ ├── slowvoter_test.go │ ├── transport.go │ └── verifier.go ├── go.mod ├── go.sum ├── inmem_snapshot.go ├── inmem_snapshot_test.go ├── inmem_store.go ├── inmem_transport.go ├── inmem_transport_test.go ├── integ_test.go ├── log.go ├── log_cache.go ├── log_cache_test.go ├── log_test.go ├── membership.md ├── net_transport.go ├── net_transport_test.go ├── observer.go ├── peersjson.go ├── peersjson_test.go ├── progress.go ├── raft-compat/ │ ├── go.mod │ ├── go.sum │ ├── prevote_test.go │ ├── rolling_upgrade_test.go │ ├── testcluster/ │ │ └── cluster.go │ └── utils/ │ └── test_utils.go ├── raft.go ├── raft_test.go ├── replication.go ├── saturation.go ├── saturation_test.go ├── snapshot.go ├── stable.go ├── state.go ├── tag.sh ├── tcp_transport.go ├── tcp_transport_test.go ├── testing.go ├── testing_batch.go ├── transport.go ├── transport_test.go ├── util.go └── util_test.go
SYMBOL INDEX (929 symbols across 65 files)
FILE: api.go
constant SuggestedMaxDataSize (line 30) | SuggestedMaxDataSize = 512 * 1024
type Raft (line 79) | type Raft struct
method restoreSnapshot (line 631) | func (r *Raft) restoreSnapshot() error {
method tryRestoreSingleSnapshot (line 677) | func (r *Raft) tryRestoreSingleSnapshot(snapshot *SnapshotMeta) bool {
method config (line 709) | func (r *Raft) config() Config {
method ReloadConfig (line 717) | func (r *Raft) ReloadConfig(rc ReloadableConfig) error {
method ReloadableConfig (line 749) | func (r *Raft) ReloadableConfig() ReloadableConfig {
method BootstrapCluster (line 769) | func (r *Raft) BootstrapCluster(configuration Configuration) Future {
method Leader (line 786) | func (r *Raft) Leader() ServerAddress {
method LeaderWithID (line 796) | func (r *Raft) LeaderWithID() (ServerAddress, ServerID) {
method Apply (line 819) | func (r *Raft) Apply(cmd []byte, timeout time.Duration) ApplyFuture {
method ApplyLog (line 826) | func (r *Raft) ApplyLog(log Log, timeout time.Duration) ApplyFuture {
method Barrier (line 859) | func (r *Raft) Barrier(timeout time.Duration) Future {
method VerifyLeader (line 883) | func (r *Raft) VerifyLeader() Future {
method GetConfiguration (line 897) | func (r *Raft) GetConfiguration() ConfigurationFuture {
method AddPeer (line 908) | func (r *Raft) AddPeer(peer ServerAddress) Future {
method RemovePeer (line 926) | func (r *Raft) RemovePeer(peer ServerAddress) Future {
method AddVoter (line 946) | func (r *Raft) AddVoter(id ServerID, address ServerAddress, prevIndex ...
method AddNonvoter (line 964) | func (r *Raft) AddNonvoter(id ServerID, address ServerAddress, prevInd...
method RemoveServer (line 980) | func (r *Raft) RemoveServer(id ServerID, prevIndex uint64, timeout tim...
method DemoteVoter (line 997) | func (r *Raft) DemoteVoter(id ServerID, prevIndex uint64, timeout time...
method Shutdown (line 1012) | func (r *Raft) Shutdown() Future {
method Snapshot (line 1030) | func (r *Raft) Snapshot() SnapshotFuture {
method Restore (line 1056) | func (r *Raft) Restore(meta *SnapshotMeta, reader io.Reader, timeout t...
method State (line 1102) | func (r *Raft) State() RaftState {
method LeaderCh (line 1117) | func (r *Raft) LeaderCh() <-chan bool {
method String (line 1122) | func (r *Raft) String() string {
method LastContact (line 1128) | func (r *Raft) LastContact() time.Time {
method Stats (line 1160) | func (r *Raft) Stats() map[string]string {
method CurrentTerm (line 1221) | func (r *Raft) CurrentTerm() uint64 {
method LastIndex (line 1227) | func (r *Raft) LastIndex() uint64 {
method CommitIndex (line 1234) | func (r *Raft) CommitIndex() uint64 {
method AppliedIndex (line 1245) | func (r *Raft) AppliedIndex() uint64 {
method LeadershipTransfer (line 1261) | func (r *Raft) LeadershipTransfer() Future {
method LeadershipTransferToServer (line 1276) | func (r *Raft) LeadershipTransferToServer(id ServerID, address ServerA...
function BootstrapCluster (line 239) | func BootstrapCluster(conf *Config, logs LogStore, stable StableStore,
function RecoverCluster (line 313) | func RecoverCluster(conf *Config, fsm FSM, logs LogStore, stable StableS...
function GetConfiguration (line 445) | func GetConfiguration(conf *Config, fsm FSM, logs LogStore, stable Stabl...
function HasExistingState (line 462) | func HasExistingState(logs LogStore, stable StableStore, snaps SnapshotS...
function NewRaft (line 500) | func NewRaft(conf *Config, fsm FSM, logs LogStore, stable StableStore, s...
FILE: bench/bench.go
function FirstIndex (line 17) | func FirstIndex(b *testing.B, store raft.LogStore) {
function LastIndex (line 34) | func LastIndex(b *testing.B, store raft.LogStore) {
function GetLog (line 51) | func GetLog(b *testing.B, store raft.LogStore) {
function StoreLog (line 70) | func StoreLog(b *testing.B, store raft.LogStore) {
function StoreLogs (line 80) | func StoreLogs(b *testing.B, store raft.LogStore) {
function DeleteRange (line 99) | func DeleteRange(b *testing.B, store raft.LogStore) {
function Set (line 125) | func Set(b *testing.B, store raft.StableStore) {
function Get (line 134) | func Get(b *testing.B, store raft.StableStore) {
function SetUint64 (line 151) | func SetUint64(b *testing.B, store raft.StableStore) {
function GetUint64 (line 160) | func GetUint64(b *testing.B, store raft.StableStore) {
FILE: bench_test.go
function BenchmarkStoreLogInMem (line 13) | func BenchmarkStoreLogInMem(b *testing.B) {
FILE: commands.go
type RPCHeader (line 10) | type RPCHeader struct
type WithRPCHeader (line 21) | type WithRPCHeader interface
type AppendEntriesRequest (line 27) | type AppendEntriesRequest struct
method GetRPCHeader (line 48) | func (r *AppendEntriesRequest) GetRPCHeader() RPCHeader {
type AppendEntriesResponse (line 54) | type AppendEntriesResponse struct
method GetRPCHeader (line 72) | func (r *AppendEntriesResponse) GetRPCHeader() RPCHeader {
type RequestVoteRequest (line 78) | type RequestVoteRequest struct
method GetRPCHeader (line 98) | func (r *RequestVoteRequest) GetRPCHeader() RPCHeader {
type RequestVoteResponse (line 103) | type RequestVoteResponse struct
method GetRPCHeader (line 119) | func (r *RequestVoteResponse) GetRPCHeader() RPCHeader {
type RequestPreVoteRequest (line 125) | type RequestPreVoteRequest struct
method GetRPCHeader (line 137) | func (r *RequestPreVoteRequest) GetRPCHeader() RPCHeader {
type RequestPreVoteResponse (line 142) | type RequestPreVoteResponse struct
method GetRPCHeader (line 153) | func (r *RequestPreVoteResponse) GetRPCHeader() RPCHeader {
type InstallSnapshotRequest (line 159) | type InstallSnapshotRequest struct
method GetRPCHeader (line 186) | func (r *InstallSnapshotRequest) GetRPCHeader() RPCHeader {
type InstallSnapshotResponse (line 192) | type InstallSnapshotResponse struct
method GetRPCHeader (line 200) | func (r *InstallSnapshotResponse) GetRPCHeader() RPCHeader {
type TimeoutNowRequest (line 206) | type TimeoutNowRequest struct
method GetRPCHeader (line 211) | func (r *TimeoutNowRequest) GetRPCHeader() RPCHeader {
type TimeoutNowResponse (line 216) | type TimeoutNowResponse struct
method GetRPCHeader (line 221) | func (r *TimeoutNowResponse) GetRPCHeader() RPCHeader {
FILE: commitment.go
type commitment (line 14) | type commitment struct
method setConfiguration (line 53) | func (c *commitment) setConfiguration(configuration Configuration) {
method getCommitIndex (line 67) | func (c *commitment) getCommitIndex() uint64 {
method match (line 77) | func (c *commitment) match(server ServerID, matchIndex uint64) {
method recalculate (line 88) | func (c *commitment) recalculate() {
function newCommitment (line 35) | func newCommitment(commitCh chan struct{}, configuration Configuration, ...
FILE: commitment_test.go
function makeConfiguration (line 10) | func makeConfiguration(voters []string) Configuration {
function voters (line 23) | func voters(n int) Configuration {
function TestCommitment_setVoters (line 31) | func TestCommitment_setVoters(t *testing.T) {
function TestCommitment_match_max (line 54) | func TestCommitment_match_max(t *testing.T) {
function TestCommitment_match_nonVoting (line 69) | func TestCommitment_match_nonVoting(t *testing.T) {
function TestCommitment_recalculate (line 94) | func TestCommitment_recalculate(t *testing.T) {
function TestCommitment_recalculate_startIndex (line 156) | func TestCommitment_recalculate_startIndex(t *testing.T) {
function TestCommitment_noVoterSanity (line 185) | func TestCommitment_noVoterSanity(t *testing.T) {
function TestCommitment_singleVoter (line 221) | func TestCommitment_singleVoter(t *testing.T) {
FILE: config.go
type ProtocolVersion (line 99) | type ProtocolVersion
constant ProtocolVersionMin (line 103) | ProtocolVersionMin ProtocolVersion = 0
constant ProtocolVersionMax (line 105) | ProtocolVersionMax = 3
type SnapshotVersion (line 128) | type SnapshotVersion
constant SnapshotVersionMin (line 132) | SnapshotVersionMin SnapshotVersion = 0
constant SnapshotVersionMax (line 134) | SnapshotVersionMax = 1
type Config (line 138) | type Config struct
method getOrCreateLogger (line 247) | func (conf *Config) getOrCreateLogger() hclog.Logger {
type ReloadableConfig (line 267) | type ReloadableConfig struct
method apply (line 297) | func (rc *ReloadableConfig) apply(to Config) Config {
method fromConfig (line 307) | func (rc *ReloadableConfig) fromConfig(from Config) {
function DefaultConfig (line 316) | func DefaultConfig() *Config {
function ValidateConfig (line 333) | func ValidateConfig(config *Config) error {
FILE: configuration.go
type ServerSuffrage (line 9) | type ServerSuffrage
method String (line 26) | func (s ServerSuffrage) String() string {
constant Voter (line 15) | Voter ServerSuffrage = iota
constant Nonvoter (line 18) | Nonvoter
constant Staging (line 23) | Staging
type ConfigurationStore (line 44) | type ConfigurationStore interface
type ServerID (line 55) | type ServerID
type ServerAddress (line 58) | type ServerAddress
type Server (line 61) | type Server struct
type Configuration (line 74) | type Configuration struct
method Clone (line 79) | func (c *Configuration) Clone() (copy Configuration) {
type ConfigurationChangeCommand (line 86) | type ConfigurationChangeCommand
method String (line 106) | func (c ConfigurationChangeCommand) String() string {
constant AddVoter (line 90) | AddVoter ConfigurationChangeCommand = iota
constant AddNonvoter (line 92) | AddNonvoter
constant DemoteVoter (line 94) | DemoteVoter
constant RemoveServer (line 96) | RemoveServer
constant Promote (line 100) | Promote
constant AddStaging (line 103) | AddStaging = 0
type configurationChangeRequest (line 125) | type configurationChangeRequest struct
type configurations (line 146) | type configurations struct
method Clone (line 160) | func (c *configurations) Clone() (copy configurations) {
function hasVote (line 170) | func hasVote(configuration Configuration, id ServerID) bool {
function inConfiguration (line 181) | func inConfiguration(configuration Configuration, id ServerID) bool {
function checkConfiguration (line 192) | func checkConfiguration(configuration Configuration) error {
function nextConfiguration (line 224) | func nextConfiguration(current Configuration, currentIndex uint64, chang...
function encodePeers (line 307) | func encodePeers(configuration Configuration, trans Transport) []byte {
function decodePeers (line 329) | func decodePeers(buf []byte, trans Transport) (Configuration, error) {
function EncodeConfiguration (line 352) | func EncodeConfiguration(configuration Configuration) []byte {
function DecodeConfiguration (line 362) | func DecodeConfiguration(buf []byte) Configuration {
FILE: configuration_test.go
function TestConfiguration_Configuration_Clone (line 35) | func TestConfiguration_Configuration_Clone(t *testing.T) {
function TestConfiguration_configurations_Clone (line 46) | func TestConfiguration_configurations_Clone(t *testing.T) {
function TestConfiguration_hasVote (line 65) | func TestConfiguration_hasVote(t *testing.T) {
function TestConfiguration_checkConfiguration (line 80) | func TestConfiguration_checkConfiguration(t *testing.T) {
function TestConfiguration_nextConfiguration_table (line 232) | func TestConfiguration_nextConfiguration_table(t *testing.T) {
function TestConfiguration_nextConfiguration_prevIndex (line 251) | func TestConfiguration_nextConfiguration_prevIndex(t *testing.T) {
function TestConfiguration_nextConfiguration_checkConfiguration (line 289) | func TestConfiguration_nextConfiguration_checkConfiguration(t *testing.T) {
function TestConfiguration_encodeDecodePeers (line 301) | func TestConfiguration_encodeDecodePeers(t *testing.T) {
function TestConfiguration_encodeDecodeConfiguration (line 325) | func TestConfiguration_encodeDecodeConfiguration(t *testing.T) {
FILE: discard_snapshot.go
type DiscardSnapshotStore (line 16) | type DiscardSnapshotStore struct
method Create (line 32) | func (d *DiscardSnapshotStore) Create(version SnapshotVersion, index, ...
method List (line 38) | func (d *DiscardSnapshotStore) List() ([]*SnapshotMeta, error) {
method Open (line 44) | func (d *DiscardSnapshotStore) Open(id string) (*SnapshotMeta, io.Read...
type DiscardSnapshotSink (line 23) | type DiscardSnapshotSink struct
method Write (line 50) | func (d *DiscardSnapshotSink) Write(b []byte) (int, error) {
method Close (line 55) | func (d *DiscardSnapshotSink) Close() error {
method ID (line 60) | func (d *DiscardSnapshotSink) ID() string {
method Cancel (line 65) | func (d *DiscardSnapshotSink) Cancel() error {
function NewDiscardSnapshotStore (line 26) | func NewDiscardSnapshotStore() *DiscardSnapshotStore {
FILE: discard_snapshot_test.go
function TestDiscardSnapshotStoreImpl (line 8) | func TestDiscardSnapshotStoreImpl(t *testing.T) {
function TestDiscardSnapshotSinkImpl (line 15) | func TestDiscardSnapshotSinkImpl(t *testing.T) {
FILE: file_snapshot.go
constant testPath (line 25) | testPath = "permTest"
constant snapPath (line 26) | snapPath = "snapshots"
constant metaFilePath (line 27) | metaFilePath = "meta.json"
constant stateFilePath (line 28) | stateFilePath = "state.bin"
constant tmpSuffix (line 29) | tmpSuffix = ".tmp"
type FileSnapshotStore (line 34) | type FileSnapshotStore struct
method testPermissions (line 135) | func (f *FileSnapshotStore) testPermissions() error {
method Create (line 160) | func (f *FileSnapshotStore) Create(version SnapshotVersion, index, ter...
method List (line 226) | func (f *FileSnapshotStore) List() ([]*SnapshotMeta, error) {
method getSnapshots (line 245) | func (f *FileSnapshotStore) getSnapshots() ([]*fileSnapshotMeta, error) {
method readMeta (line 292) | func (f *FileSnapshotStore) readMeta(name string) (*fileSnapshotMeta, ...
method Open (line 314) | func (f *FileSnapshotStore) Open(id string) (*SnapshotMeta, io.ReadClo...
method ReapSnapshots (line 366) | func (f *FileSnapshotStore) ReapSnapshots() error {
type snapMetaSlice (line 44) | type snapMetaSlice
method Len (line 535) | func (s snapMetaSlice) Len() int {
method Less (line 539) | func (s snapMetaSlice) Less(i, j int) bool {
method Swap (line 549) | func (s snapMetaSlice) Swap(i, j int) {
type FileSnapshotSink (line 47) | type FileSnapshotSink struct
method ID (line 386) | func (s *FileSnapshotSink) ID() string {
method Write (line 392) | func (s *FileSnapshotSink) Write(b []byte) (int, error) {
method Close (line 397) | func (s *FileSnapshotSink) Close() error {
method Cancel (line 450) | func (s *FileSnapshotSink) Cancel() error {
method finalize (line 468) | func (s *FileSnapshotSink) finalize() error {
method writeMeta (line 501) | func (s *FileSnapshotSink) writeMeta() error {
type fileSnapshotMeta (line 65) | type fileSnapshotMeta struct
type bufferedFile (line 72) | type bufferedFile struct
method Read (line 77) | func (b *bufferedFile) Read(p []byte) (n int, err error) {
method Close (line 81) | func (b *bufferedFile) Close() error {
function NewFileSnapshotStoreWithLogger (line 88) | func NewFileSnapshotStoreWithLogger(base string, retain int, logger hclo...
function NewFileSnapshotStore (line 123) | func NewFileSnapshotStore(base string, retain int, logOutput io.Writer) ...
function snapshotName (line 153) | func snapshotName(term, index uint64) string {
FILE: file_snapshot_test.go
function TestFileSnapshotStoreImpl (line 15) | func TestFileSnapshotStoreImpl(t *testing.T) {
function TestFileSnapshotSinkImpl (line 22) | func TestFileSnapshotSinkImpl(t *testing.T) {
function TestFileSS_CreateSnapshotMissingParentDir (line 29) | func TestFileSS_CreateSnapshotMissingParentDir(t *testing.T) {
function TestFileSS_CreateSnapshot (line 54) | func TestFileSS_CreateSnapshot(t *testing.T) {
function TestFileSS_CancelSnapshot (line 162) | func TestFileSS_CancelSnapshot(t *testing.T) {
function TestFileSS_Retention (line 198) | func TestFileSS_Retention(t *testing.T) {
function TestFileSS_BadPerm (line 247) | func TestFileSS_BadPerm(t *testing.T) {
function TestFileSS_MissingParentDir (line 278) | func TestFileSS_MissingParentDir(t *testing.T) {
function TestFileSS_Ordering (line 297) | func TestFileSS_Ordering(t *testing.T) {
FILE: fsm.go
type FSM (line 16) | type FSM interface
type BatchingFSM (line 54) | type BatchingFSM interface
type FSMSnapshot (line 74) | type FSMSnapshot interface
method runFSM (line 86) | func (r *Raft) runFSM() {
function fsmRestoreAndMeasure (line 269) | func fsmRestoreAndMeasure(logger hclog.Logger, fsm FSM, source io.ReadCl...
FILE: future.go
type Future (line 14) | type Future interface
type IndexFuture (line 27) | type IndexFuture interface
type ApplyFuture (line 36) | type ApplyFuture interface
type ConfigurationFuture (line 49) | type ConfigurationFuture interface
type SnapshotFuture (line 58) | type SnapshotFuture interface
type LeadershipTransferFuture (line 69) | type LeadershipTransferFuture interface
type errorFuture (line 74) | type errorFuture struct
method Error (line 78) | func (e errorFuture) Error() error {
method Response (line 82) | func (e errorFuture) Response() interface{} {
method Index (line 86) | func (e errorFuture) Index() uint64 {
type deferError (line 92) | type deferError struct
method init (line 99) | func (d *deferError) init() {
method Error (line 103) | func (d *deferError) Error() error {
method respond (line 121) | func (d *deferError) respond(err error) {
type configurationChangeFuture (line 136) | type configurationChangeFuture struct
type bootstrapFuture (line 143) | type bootstrapFuture struct
type logFuture (line 152) | type logFuture struct
method Response (line 159) | func (l *logFuture) Response() interface{} {
method Index (line 163) | func (l *logFuture) Index() uint64 {
type shutdownFuture (line 167) | type shutdownFuture struct
method Error (line 171) | func (s *shutdownFuture) Error() error {
type userSnapshotFuture (line 184) | type userSnapshotFuture struct
method Open (line 194) | func (u *userSnapshotFuture) Open() (*SnapshotMeta, io.ReadCloser, err...
type userRestoreFuture (line 208) | type userRestoreFuture struct
type reqSnapshotFuture (line 220) | type reqSnapshotFuture struct
type restoreFuture (line 231) | type restoreFuture struct
type verifyFuture (line 238) | type verifyFuture struct
method vote (line 274) | func (v *verifyFuture) vote(leader bool) {
type leadershipTransferFuture (line 248) | type leadershipTransferFuture struct
type configurationsFuture (line 257) | type configurationsFuture struct
method Configuration (line 263) | func (c *configurationsFuture) Configuration() Configuration {
method Index (line 268) | func (c *configurationsFuture) Index() uint64 {
type appendFuture (line 297) | type appendFuture struct
method Start (line 304) | func (a *appendFuture) Start() time.Time {
method Request (line 308) | func (a *appendFuture) Request() *AppendEntriesRequest {
method Response (line 312) | func (a *appendFuture) Response() *AppendEntriesResponse {
FILE: future_test.go
function TestDeferFutureSuccess (line 11) | func TestDeferFutureSuccess(t *testing.T) {
function TestDeferFutureError (line 23) | func TestDeferFutureError(t *testing.T) {
function TestDeferFutureConcurrent (line 36) | func TestDeferFutureConcurrent(t *testing.T) {
FILE: fuzzy/apply_src.go
type applySource (line 13) | type applySource struct
method reset (line 28) | func (a *applySource) reset() {
method nextEntry (line 32) | func (a *applySource) nextEntry() []byte {
method apply (line 48) | func (a *applySource) apply(t *testing.T, c *cluster, n uint) *cluster...
function newApplySource (line 19) | func newApplySource(seed string) *applySource {
type clusterApplier (line 41) | type clusterApplier struct
method apply (line 54) | func (ca *clusterApplier) apply(t *testing.T, c *cluster, n uint) {
method stop (line 65) | func (ca *clusterApplier) stop() {
FILE: fuzzy/cluster.go
type appliedItem (line 20) | type appliedItem struct
type cluster (line 25) | type cluster struct
method CreateAndAddNode (line 90) | func (c *cluster) CreateAndAddNode(t *testing.T, logWriter io.Writer, ...
method RemoveNode (line 109) | func (c *cluster) RemoveNode(t *testing.T, name string) *raftNode {
method Leader (line 131) | func (c *cluster) Leader(timeout time.Duration) *raftNode {
method LeaderPlus (line 159) | func (c *cluster) LeaderPlus(n int) []*raftNode {
method Stop (line 179) | func (c *cluster) Stop(t *testing.T, maxWait time.Duration) {
method WaitTilUptoDate (line 188) | func (c *cluster) WaitTilUptoDate(t *testing.T, maxWait time.Duration) {
method appliedIndexes (line 215) | func (c *cluster) appliedIndexes() map[string]uint64 {
method generateNApplies (line 223) | func (c *cluster) generateNApplies(s *applySource, n uint) [][]byte {
method leadershipTransfer (line 231) | func (c *cluster) leadershipTransfer(leaderTimeout time.Duration) raft...
method sendNApplies (line 241) | func (c *cluster) sendNApplies(leaderTimeout time.Duration, data [][]b...
method checkApplyFutures (line 253) | func (c *cluster) checkApplyFutures(futures []applyFutureWithData) uin...
method ApplyN (line 267) | func (c *cluster) ApplyN(t *testing.T, leaderTimeout time.Duration, s ...
method VerifyFSM (line 273) | func (c *cluster) VerifyFSM(t *testing.T) {
method RecordState (line 295) | func (c *cluster) RecordState(t *testing.T) {
method VerifyLog (line 339) | func (c *cluster) VerifyLog(t *testing.T, applyCount uint64) {
type Logger (line 37) | type Logger interface
type LoggerAdapter (line 43) | type LoggerAdapter struct
method Log (line 48) | func (a *LoggerAdapter) Log(v ...interface{}) {
method Logf (line 53) | func (a *LoggerAdapter) Logf(s string, v ...interface{}) {
function newRaftCluster (line 57) | func newRaftCluster(t *testing.T, logWriter io.Writer, namePrefix string...
function nodeName (line 105) | func nodeName(prefix string, num uint) string {
function containsNode (line 148) | func containsNode(nodes []*raftNode, n *raftNode) bool {
type applyFutureWithData (line 236) | type applyFutureWithData struct
function copyDir (line 314) | func copyDir(target, src string) {
function copyFile (line 324) | func copyFile(target, src string) error {
function assertLogEntryEqual (line 406) | func assertLogEntryEqual(t *testing.T, node string, exp *raft.Log, act *...
FILE: fuzzy/fsm.go
type logHash (line 17) | type logHash struct
method Add (line 21) | func (l *logHash) Add(d []byte) {
type applyItem (line 28) | type applyItem struct
method set (line 34) | func (a *applyItem) set(l *raft.Log) {
type fuzzyFSM (line 41) | type fuzzyFSM struct
method Apply (line 48) | func (f *fuzzyFSM) Apply(l *raft.Log) interface{} {
method WriteTo (line 63) | func (f *fuzzyFSM) WriteTo(fn string) error {
method Snapshot (line 77) | func (f *fuzzyFSM) Snapshot() (raft.FSMSnapshot, error) {
method Restore (line 82) | func (f *fuzzyFSM) Restore(r io.ReadCloser) error {
method Persist (line 94) | func (f *fuzzyFSM) Persist(sink raft.SnapshotSink) error {
method Release (line 105) | func (f *fuzzyFSM) Release() {
FILE: fuzzy/fsm_batch.go
method ApplyBatch (line 12) | func (f *fuzzyFSM) ApplyBatch(logs []*raft.Log) []interface{} {
FILE: fuzzy/leadershiptransfer_test.go
function TestRaft_FuzzyLeadershipTransfer (line 15) | func TestRaft_FuzzyLeadershipTransfer(t *testing.T) {
type LeadershipTransferMode (line 42) | type LeadershipTransferMode
type LeadershipTransfer (line 44) | type LeadershipTransfer struct
method Report (line 52) | func (lt *LeadershipTransfer) Report(t *testing.T) {
method PreRPC (line 56) | func (lt *LeadershipTransfer) PreRPC(s, t string, r *raft.RPC) error {
method nap (line 60) | func (lt *LeadershipTransfer) nap() {
method PostRPC (line 65) | func (lt *LeadershipTransfer) PostRPC(src, target string, r *raft.RPC,...
method PreRequestVote (line 69) | func (lt *LeadershipTransfer) PreRequestVote(src, target string, v *ra...
method PreAppendEntries (line 73) | func (lt *LeadershipTransfer) PreAppendEntries(src, target string, v *...
FILE: fuzzy/membership_test.go
function init (line 17) | func init() {
function TestRaft_AddMembership (line 30) | func TestRaft_AddMembership(t *testing.T) {
function TestRaft_AddRemoveNodesNotLeader (line 52) | func TestRaft_AddRemoveNodesNotLeader(t *testing.T) {
function TestRaft_RemoveLeader (line 80) | func TestRaft_RemoveLeader(t *testing.T) {
function TestRaft_RemovePartitionedNode (line 100) | func TestRaft_RemovePartitionedNode(t *testing.T) {
FILE: fuzzy/node.go
type raftNode (line 16) | type raftNode struct
function newRaftNode (line 26) | func newRaftNode(logger hclog.Logger, tc *transports, h TransportHooks, ...
FILE: fuzzy/partition_test.go
function TestRaft_LeaderPartitions (line 19) | func TestRaft_LeaderPartitions(t *testing.T) {
type Partitioner (line 47) | type Partitioner struct
method PartitionOff (line 66) | func (p *Partitioner) PartitionOff(l Logger, nodes []*raftNode) int {
method Heal (line 80) | func (p *Partitioner) Heal(l Logger, pGroup int) {
method String (line 91) | func (p *Partitioner) String() string {
method HealAll (line 116) | func (p *Partitioner) HealAll(l Logger) {
method Report (line 123) | func (p *Partitioner) Report(t *testing.T) {
method PreRPC (line 127) | func (p *Partitioner) PreRPC(s, t string, r *raft.RPC) error {
method PostRPC (line 138) | func (p *Partitioner) PostRPC(s, t string, req *raft.RPC, res *raft.RP...
method PreRequestVote (line 142) | func (p *Partitioner) PreRequestVote(src, target string, v *raft.Reque...
method PreAppendEntries (line 146) | func (p *Partitioner) PreAppendEntries(src, target string, v *raft.App...
function NewPartitioner (line 55) | func NewPartitioner() *Partitioner {
FILE: fuzzy/resolve.go
function resolveDirectory (line 21) | func resolveDirectory(dir string, create bool) (string, error) {
FILE: fuzzy/simple_test.go
function TestRaft_NoIssueSanity (line 12) | func TestRaft_NoIssueSanity(t *testing.T) {
FILE: fuzzy/slowvoter_test.go
function TestRaft_SlowSendVote (line 15) | func TestRaft_SlowSendVote(t *testing.T) {
function TestRaft_SlowRecvVote (line 28) | func TestRaft_SlowRecvVote(t *testing.T) {
type SlowVoterMode (line 40) | type SlowVoterMode
constant SlowSend (line 43) | SlowSend SlowVoterMode = iota
constant SlowRecv (line 44) | SlowRecv
type SlowVoter (line 47) | type SlowVoter struct
method Report (line 69) | func (sv *SlowVoter) Report(t *testing.T) {
method PreRPC (line 73) | func (sv *SlowVoter) PreRPC(s, t string, r *raft.RPC) error {
method nap (line 77) | func (sv *SlowVoter) nap() {
method PostRPC (line 82) | func (sv *SlowVoter) PostRPC(src, target string, r *raft.RPC, res *raf...
method PreRequestVote (line 92) | func (sv *SlowVoter) PreRequestVote(src, target string, v *raft.Reques...
method PreAppendEntries (line 99) | func (sv *SlowVoter) PreAppendEntries(src, target string, v *raft.Appe...
function NewSlowVoter (line 55) | func NewSlowVoter(slowNodes ...string) *SlowVoter {
FILE: fuzzy/transport.go
type appendEntries (line 24) | type appendEntries struct
type transports (line 33) | type transports struct
method AddNode (line 46) | func (tc *transports) AddNode(n string, hooks TransportHooks) *transpo...
function newTransports (line 39) | func newTransports(log hclog.Logger) *transports {
type TransportHooks (line 57) | type TransportHooks interface
type transport (line 68) | type transport struct
method Consumer (line 90) | func (t *transport) Consumer() <-chan raft.RPC {
method LocalAddr (line 95) | func (t *transport) LocalAddr() raft.ServerAddress {
method sendRPC (line 99) | func (t *transport) sendRPC(target string, req interface{}, resp inter...
method TimeoutNow (line 175) | func (t *transport) TimeoutNow(id raft.ServerID, target raft.ServerAdd...
method AppendEntries (line 180) | func (t *transport) AppendEntries(id raft.ServerID, target raft.Server...
method DumpLog (line 194) | func (t *transport) DumpLog(dir string) {
method RequestVote (line 220) | func (t *transport) RequestVote(id raft.ServerID, target raft.ServerAd...
method RequestPreVote (line 225) | func (t *transport) RequestPreVote(id raft.ServerID, target raft.Serve...
method InstallSnapshot (line 231) | func (t *transport) InstallSnapshot(id raft.ServerID, target raft.Serv...
method EncodePeer (line 237) | func (t *transport) EncodePeer(id raft.ServerID, p raft.ServerAddress)...
method DecodePeer (line 242) | func (t *transport) DecodePeer(p []byte) raft.ServerAddress {
method SetHeartbeatHandler (line 250) | func (t *transport) SetHeartbeatHandler(cb func(rpc raft.RPC)) {
method AppendEntriesPipeline (line 255) | func (t *transport) AppendEntriesPipeline(id raft.ServerID, target raf...
function newTransport (line 78) | func newTransport(node string, tc *transports, hooks TransportHooks) *tr...
function firstIndex (line 205) | func firstIndex(a *raft.AppendEntriesRequest) uint64 {
function lastIndex (line 212) | func lastIndex(a *raft.AppendEntriesRequest) uint64 {
type appendEntry (line 267) | type appendEntry struct
method Request (line 276) | func (e *appendEntry) Request() *raft.AppendEntriesRequest {
method Response (line 280) | func (e *appendEntry) Response() *raft.AppendEntriesResponse {
method Start (line 285) | func (e *appendEntry) Start() time.Time {
method Error (line 289) | func (e *appendEntry) Error() error {
method Respond (line 294) | func (e *appendEntry) Respond(err error) {
type pipeline (line 300) | type pipeline struct
method run (line 308) | func (p *pipeline) run() {
method AppendEntries (line 317) | func (p *pipeline) AppendEntries(args *raft.AppendEntriesRequest, resp...
method Consumer (line 329) | func (p *pipeline) Consumer() <-chan raft.AppendFuture {
method Close (line 334) | func (p *pipeline) Close() error {
FILE: fuzzy/verifier.go
type appendEntriesVerifier (line 16) | type appendEntriesVerifier struct
method Report (line 22) | func (v *appendEntriesVerifier) Report(t *testing.T) {
method Init (line 30) | func (v *appendEntriesVerifier) Init() {
method PreRPC (line 37) | func (v *appendEntriesVerifier) PreRPC(src, target string, r *raft.RPC...
method PostRPC (line 41) | func (v *appendEntriesVerifier) PostRPC(src, target string, req *raft....
method PreRequestVote (line 45) | func (v *appendEntriesVerifier) PreRequestVote(src, target string, rv ...
method PreAppendEntries (line 49) | func (v *appendEntriesVerifier) PreAppendEntries(src, target string, r...
FILE: inmem_snapshot.go
type InmemSnapshotStore (line 15) | type InmemSnapshotStore struct
method Create (line 37) | func (m *InmemSnapshotStore) Create(version SnapshotVersion, index, te...
method List (line 68) | func (m *InmemSnapshotStore) List() ([]*SnapshotMeta, error) {
method Open (line 79) | func (m *InmemSnapshotStore) Open(id string) (*SnapshotMeta, io.ReadCl...
type InmemSnapshotSink (line 22) | type InmemSnapshotSink struct
method Write (line 94) | func (s *InmemSnapshotSink) Write(p []byte) (n int, err error) {
method Close (line 101) | func (s *InmemSnapshotSink) Close() error {
method ID (line 106) | func (s *InmemSnapshotSink) ID() string {
method Cancel (line 111) | func (s *InmemSnapshotSink) Cancel() error {
function NewInmemSnapshotStore (line 28) | func NewInmemSnapshotStore() *InmemSnapshotStore {
FILE: inmem_snapshot_test.go
function TestInmemSnapshotStoreImpl (line 13) | func TestInmemSnapshotStoreImpl(t *testing.T) {
function TestInmemSnapshotSinkImpl (line 20) | func TestInmemSnapshotSinkImpl(t *testing.T) {
function TestInmemSS_CreateSnapshot (line 27) | func TestInmemSS_CreateSnapshot(t *testing.T) {
function TestInmemSS_OpenSnapshotTwice (line 125) | func TestInmemSS_OpenSnapshotTwice(t *testing.T) {
FILE: inmem_store.go
type InmemStore (line 14) | type InmemStore struct
method FirstIndex (line 35) | func (i *InmemStore) FirstIndex() (uint64, error) {
method LastIndex (line 42) | func (i *InmemStore) LastIndex() (uint64, error) {
method GetLog (line 49) | func (i *InmemStore) GetLog(index uint64, log *Log) error {
method StoreLog (line 61) | func (i *InmemStore) StoreLog(log *Log) error {
method StoreLogs (line 66) | func (i *InmemStore) StoreLogs(logs []*Log) error {
method DeleteRange (line 82) | func (i *InmemStore) DeleteRange(min, max uint64) error {
method Set (line 102) | func (i *InmemStore) Set(key []byte, val []byte) error {
method Get (line 110) | func (i *InmemStore) Get(key []byte) ([]byte, error) {
method SetUint64 (line 121) | func (i *InmemStore) SetUint64(key []byte, val uint64) error {
method GetUint64 (line 129) | func (i *InmemStore) GetUint64(key []byte) (uint64, error) {
function NewInmemStore (line 25) | func NewInmemStore() *InmemStore {
FILE: inmem_transport.go
function NewInmemAddr (line 15) | func NewInmemAddr() ServerAddress {
type inmemPipeline (line 20) | type inmemPipeline struct
method decodeResponses (line 272) | func (i *inmemPipeline) decodeResponses() {
method AppendEntries (line 311) | func (i *inmemPipeline) AppendEntries(args *AppendEntriesRequest, resp...
method Consumer (line 360) | func (i *inmemPipeline) Consumer() <-chan AppendFuture {
method Close (line 364) | func (i *inmemPipeline) Close() error {
type inmemPipelineInflight (line 33) | type inmemPipelineInflight struct
type InmemTransport (line 40) | type InmemTransport struct
method SetHeartbeatHandler (line 74) | func (i *InmemTransport) SetHeartbeatHandler(cb func(RPC)) {
method Consumer (line 78) | func (i *InmemTransport) Consumer() <-chan RPC {
method LocalAddr (line 83) | func (i *InmemTransport) LocalAddr() ServerAddress {
method AppendEntriesPipeline (line 89) | func (i *InmemTransport) AppendEntriesPipeline(id ServerID, target Ser...
method AppendEntries (line 103) | func (i *InmemTransport) AppendEntries(id ServerID, target ServerAddre...
method RequestVote (line 116) | func (i *InmemTransport) RequestVote(id ServerID, target ServerAddress...
method RequestPreVote (line 128) | func (i *InmemTransport) RequestPreVote(id ServerID, target ServerAddr...
method InstallSnapshot (line 141) | func (i *InmemTransport) InstallSnapshot(id ServerID, target ServerAdd...
method TimeoutNow (line 154) | func (i *InmemTransport) TimeoutNow(id ServerID, target ServerAddress,...
method makeRPC (line 166) | func (i *InmemTransport) makeRPC(target ServerAddress, args interface{...
method EncodePeer (line 203) | func (i *InmemTransport) EncodePeer(id ServerID, p ServerAddress) []by...
method DecodePeer (line 208) | func (i *InmemTransport) DecodePeer(buf []byte) ServerAddress {
method Connect (line 214) | func (i *InmemTransport) Connect(peer ServerAddress, t Transport) {
method Disconnect (line 222) | func (i *InmemTransport) Disconnect(peer ServerAddress) {
method DisconnectAll (line 241) | func (i *InmemTransport) DisconnectAll() {
method Close (line 254) | func (i *InmemTransport) Close() error {
function NewInmemTransportWithTimeout (line 53) | func NewInmemTransportWithTimeout(addr ServerAddress, timeout time.Durat...
function NewInmemTransport (line 68) | func NewInmemTransport(addr ServerAddress) (ServerAddress, *InmemTranspo...
function newInmemPipeline (line 259) | func newInmemPipeline(trans *InmemTransport, peer *InmemTransport, addr ...
FILE: inmem_transport_test.go
function TestInmemTransportImpl (line 13) | func TestInmemTransportImpl(t *testing.T) {
function TestInmemTransportWriteTimeout (line 26) | func TestInmemTransportWriteTimeout(t *testing.T) {
FILE: integ_test.go
function CheckInteg (line 20) | func CheckInteg(t *testing.T) {
function IsInteg (line 27) | func IsInteg() bool {
type RaftEnv (line 31) | type RaftEnv struct
method Release (line 43) | func (r *RaftEnv) Release() {
method Shutdown (line 50) | func (r *RaftEnv) Shutdown() {
method Restart (line 60) | func (r *RaftEnv) Restart(t *testing.T) {
function MakeRaft (line 74) | func MakeRaft(tb testing.TB, conf *Config, bootstrap bool) *RaftEnv {
function WaitFor (line 131) | func WaitFor(env *RaftEnv, state RaftState) error {
function WaitForAny (line 143) | func WaitForAny(state RaftState, envs []*RaftEnv) (*RaftEnv, error) {
function WaitFuture (line 160) | func WaitFuture(f Future) error {
function NoErr (line 168) | func NoErr(err error, tb testing.TB) {
function CheckConsistent (line 175) | func CheckConsistent(envs []*RaftEnv, t *testing.T) {
function logBytes (line 214) | func logBytes(i, sz int) []byte {
function TestRaft_Integ (line 225) | func TestRaft_Integ(t *testing.T) {
function TestRaft_RestartFollower_LongInitialHeartbeat (line 364) | func TestRaft_RestartFollower_LongInitialHeartbeat(t *testing.T) {
function TestRaft_PreVote_LeaderSpam (line 500) | func TestRaft_PreVote_LeaderSpam(t *testing.T) {
FILE: log.go
type LogType (line 14) | type LogType
method String (line 47) | func (lt LogType) String() string {
constant LogCommand (line 18) | LogCommand LogType = iota
constant LogNoop (line 21) | LogNoop
constant LogAddPeerDeprecated (line 26) | LogAddPeerDeprecated
constant LogRemovePeerDeprecated (line 31) | LogRemovePeerDeprecated
constant LogBarrier (line 38) | LogBarrier
constant LogConfiguration (line 43) | LogConfiguration
type Log (line 68) | type Log struct
type LogStore (line 112) | type LogStore interface
type MonotonicLogStore (line 141) | type MonotonicLogStore interface
function oldestLog (line 145) | func oldestLog(s LogStore) (Log, error) {
function emitLogStoreMetrics (line 177) | func emitLogStoreMetrics(s LogStore, prefix []string, interval time.Dura...
FILE: log_cache.go
type LogCache (line 16) | type LogCache struct
method IsMonotonic (line 38) | func (c *LogCache) IsMonotonic() bool {
method GetLog (line 46) | func (c *LogCache) GetLog(idx uint64, log *Log) error {
method StoreLog (line 62) | func (c *LogCache) StoreLog(log *Log) error {
method StoreLogs (line 66) | func (c *LogCache) StoreLogs(logs []*Log) error {
method FirstIndex (line 80) | func (c *LogCache) FirstIndex() (uint64, error) {
method LastIndex (line 84) | func (c *LogCache) LastIndex() (uint64, error) {
method DeleteRange (line 88) | func (c *LogCache) DeleteRange(min, max uint64) error {
function NewLogCache (line 25) | func NewLogCache(capacity int, store LogStore) (*LogCache, error) {
FILE: log_cache_test.go
function TestLogCache (line 13) | func TestLogCache(t *testing.T) {
type errorStore (line 96) | type errorStore struct
method StoreLogs (line 104) | func (e *errorStore) StoreLogs(logs []*Log) error {
method failNext (line 117) | func (e *errorStore) failNext(count int) {
function TestLogCacheWithBackendStoreError (line 124) | func TestLogCacheWithBackendStoreError(t *testing.T) {
FILE: log_test.go
function TestOldestLog (line 15) | func TestOldestLog(t *testing.T) {
function TestEmitsLogStoreMetrics (line 72) | func TestEmitsLogStoreMetrics(t *testing.T) {
function testSetupMetrics (line 117) | func testSetupMetrics(t *testing.T) *metrics.InmemSink {
function getCurrentGaugeValue (line 127) | func getCurrentGaugeValue(t *testing.T, sink *metrics.InmemSink, name st...
FILE: net_transport.go
constant rpcAppendEntries (line 23) | rpcAppendEntries uint8 = iota
constant rpcRequestVote (line 24) | rpcRequestVote
constant rpcInstallSnapshot (line 25) | rpcInstallSnapshot
constant rpcTimeoutNow (line 26) | rpcTimeoutNow
constant rpcRequestPreVote (line 27) | rpcRequestPreVote
constant DefaultTimeoutScale (line 30) | DefaultTimeoutScale = 256 * 1024
constant DefaultMaxRPCsInFlight (line 36) | DefaultMaxRPCsInFlight = 2
constant connReceiveBufferSize (line 40) | connReceiveBufferSize = 256 * 1024
constant connSendBufferSize (line 44) | connSendBufferSize = 256 * 1024
constant minInFlightForPipelining (line 54) | minInFlightForPipelining = 2
type NetworkTransport (line 81) | type NetworkTransport struct
method setupStreamContext (line 285) | func (n *NetworkTransport) setupStreamContext() {
method getStreamContext (line 292) | func (n *NetworkTransport) getStreamContext() context.Context {
method SetHeartbeatHandler (line 301) | func (n *NetworkTransport) SetHeartbeatHandler(cb func(rpc RPC)) {
method CloseStreams (line 308) | func (n *NetworkTransport) CloseStreams() {
method Close (line 333) | func (n *NetworkTransport) Close() error {
method Consumer (line 346) | func (n *NetworkTransport) Consumer() <-chan RPC {
method LocalAddr (line 351) | func (n *NetworkTransport) LocalAddr() ServerAddress {
method IsShutdown (line 356) | func (n *NetworkTransport) IsShutdown() bool {
method getPooledConn (line 366) | func (n *NetworkTransport) getPooledConn(target ServerAddress) *netConn {
method getConnFromAddressProvider (line 383) | func (n *NetworkTransport) getConnFromAddressProvider(id ServerID, tar...
method getProviderAddressOrFallback (line 388) | func (n *NetworkTransport) getProviderAddressOrFallback(id ServerID, t...
method getConn (line 403) | func (n *NetworkTransport) getConn(target ServerAddress) (*netConn, er...
method returnConn (line 432) | func (n *NetworkTransport) returnConn(conn *netConn) {
method AppendEntriesPipeline (line 448) | func (n *NetworkTransport) AppendEntriesPipeline(id ServerID, target S...
method AppendEntries (line 466) | func (n *NetworkTransport) AppendEntries(id ServerID, target ServerAdd...
method RequestVote (line 471) | func (n *NetworkTransport) RequestVote(id ServerID, target ServerAddre...
method RequestPreVote (line 476) | func (n *NetworkTransport) RequestPreVote(id ServerID, target ServerAd...
method genericRPC (line 481) | func (n *NetworkTransport) genericRPC(id ServerID, target ServerAddres...
method InstallSnapshot (line 507) | func (n *NetworkTransport) InstallSnapshot(id ServerID, target ServerA...
method EncodePeer (line 545) | func (n *NetworkTransport) EncodePeer(id ServerID, p ServerAddress) []...
method DecodePeer (line 551) | func (n *NetworkTransport) DecodePeer(buf []byte) ServerAddress {
method TimeoutNow (line 556) | func (n *NetworkTransport) TimeoutNow(id ServerID, target ServerAddres...
method listen (line 561) | func (n *NetworkTransport) listen() {
method handleConn (line 604) | func (n *NetworkTransport) handleConn(connCtx context.Context, conn ne...
method handleCommand (line 636) | func (n *NetworkTransport) handleCommand(r *bufio.Reader, dec *codec.D...
type NetworkTransportConfig (line 116) | type NetworkTransportConfig struct
type ServerAddressProvider (line 173) | type ServerAddressProvider interface
type StreamLayer (line 179) | type StreamLayer interface
type netConn (line 186) | type netConn struct
method Release (line 194) | func (n *netConn) Release() error {
type netPipeline (line 198) | type netPipeline struct
method decodeResponses (line 838) | func (n *netPipeline) decodeResponses() {
method AppendEntries (line 861) | func (n *netPipeline) AppendEntries(args *AppendEntriesRequest, resp *...
method Consumer (line 891) | func (n *netPipeline) Consumer() <-chan AppendFuture {
method Close (line 896) | func (n *netPipeline) Close() error {
function NewNetworkTransportWithConfig (line 211) | func NewNetworkTransportWithConfig(
function NewNetworkTransport (line 251) | func NewNetworkTransport(
function NewNetworkTransportWithLogger (line 273) | func NewNetworkTransportWithLogger(
function decodeResponse (line 768) | func decodeResponse(conn *netConn, resp interface{}) (bool, error) {
function sendRPC (line 790) | func sendRPC(conn *netConn, rpcType uint8, args interface{}) error {
function newNetPipeline (line 814) | func newNetPipeline(trans *NetworkTransport, conn *netConn, maxInFlight ...
FILE: net_transport_test.go
type testAddrProvider (line 22) | type testAddrProvider struct
method ServerAddr (line 26) | func (t *testAddrProvider) ServerAddr(id ServerID) (ServerAddress, err...
function TestNetworkTransport_CloseStreams (line 30) | func TestNetworkTransport_CloseStreams(t *testing.T) {
function TestNetworkTransport_StartStop (line 138) | func TestNetworkTransport_StartStop(t *testing.T) {
function TestNetworkTransport_Heartbeat_FastPath (line 146) | func TestNetworkTransport_Heartbeat_FastPath(t *testing.T) {
function makeAppendRPC (line 203) | func makeAppendRPC() AppendEntriesRequest {
function makeAppendRPCResponse (line 220) | func makeAppendRPCResponse() AppendEntriesResponse {
function TestNetworkTransport_AppendEntries (line 228) | func TestNetworkTransport_AppendEntries(t *testing.T) {
function TestNetworkTransport_AppendEntriesPipeline (line 280) | func TestNetworkTransport_AppendEntriesPipeline(t *testing.T) {
function TestNetworkTransport_AppendEntriesPipeline_CloseStreams (line 349) | func TestNetworkTransport_AppendEntriesPipeline_CloseStreams(t *testing....
function TestNetworkTransport_AppendEntriesPipeline_MaxRPCsInFlight (line 440) | func TestNetworkTransport_AppendEntriesPipeline_MaxRPCsInFlight(t *testi...
function TestNetworkTransport_RequestVote (line 539) | func TestNetworkTransport_RequestVote(t *testing.T) {
function TestNetworkTransport_InstallSnapshot (line 600) | func TestNetworkTransport_InstallSnapshot(t *testing.T) {
function TestNetworkTransport_EncodeDecode (line 675) | func TestNetworkTransport_EncodeDecode(t *testing.T) {
function TestNetworkTransport_EncodeDecode_AddressProvider (line 692) | func TestNetworkTransport_EncodeDecode_AddressProvider(t *testing.T) {
function TestNetworkTransport_PooledConn (line 710) | func TestNetworkTransport_PooledConn(t *testing.T) {
function makeTransport (line 810) | func makeTransport(t *testing.T, useAddrProvider bool, addressOverride s...
type testCountingWriter (line 825) | type testCountingWriter struct
method Write (line 830) | func (tw testCountingWriter) Write(p []byte) (n int, err error) {
type testCountingStreamLayer (line 839) | type testCountingStreamLayer struct
method Accept (line 843) | func (sl testCountingStreamLayer) Accept() (net.Conn, error) {
method Close (line 848) | func (sl testCountingStreamLayer) Close() error {
method Addr (line 852) | func (sl testCountingStreamLayer) Addr() net.Addr {
method Dial (line 856) | func (sl testCountingStreamLayer) Dial(address ServerAddress, timeout ...
function TestNetworkTransport_ListenBackoff (line 863) | func TestNetworkTransport_ListenBackoff(t *testing.T) {
FILE: observer.go
type Observation (line 12) | type Observation struct
type LeaderObservation (line 24) | type LeaderObservation struct
type PeerObservation (line 32) | type PeerObservation struct
type FailedHeartbeatObservation (line 38) | type FailedHeartbeatObservation struct
type ResumedHeartbeatObservation (line 44) | type ResumedHeartbeatObservation struct
type FilterFn (line 55) | type FilterFn
type Observer (line 58) | type Observer struct
method GetNumObserved (line 97) | func (or *Observer) GetNumObserved() uint64 {
method GetNumDropped (line 102) | func (or *Observer) GetNumDropped() uint64 {
function NewObserver (line 87) | func NewObserver(channel chan Observation, blocking bool, filter FilterF...
method RegisterObserver (line 107) | func (r *Raft) RegisterObserver(or *Observer) {
method DeregisterObserver (line 114) | func (r *Raft) DeregisterObserver(or *Observer) {
method observe (line 121) | func (r *Raft) observe(o interface{}) {
FILE: peersjson.go
function ReadPeersJSON (line 18) | func ReadPeersJSON(path string) (Configuration, error) {
type configEntry (line 52) | type configEntry struct
function ReadConfigJSON (line 67) | func ReadConfigJSON(path string) (Configuration, error) {
FILE: peersjson_test.go
function TestPeersJSON_BadConfiguration (line 14) | func TestPeersJSON_BadConfiguration(t *testing.T) {
function TestPeersJSON_ReadPeersJSON (line 34) | func TestPeersJSON_ReadPeersJSON(t *testing.T) {
function TestPeersJSON_ReadConfigJSON (line 82) | func TestPeersJSON_ReadConfigJSON(t *testing.T) {
FILE: progress.go
constant snapshotRestoreMonitorInterval (line 16) | snapshotRestoreMonitorInterval = 10 * time.Second
type snapshotRestoreMonitor (line 19) | type snapshotRestoreMonitor struct
method run (line 50) | func (m *snapshotRestoreMonitor) run(ctx context.Context) {
method runOnce (line 71) | func (m *snapshotRestoreMonitor) runOnce() {
method StopAndWait (line 86) | func (m *snapshotRestoreMonitor) StopAndWait() {
function startSnapshotRestoreMonitor (line 30) | func startSnapshotRestoreMonitor(
type CountingReader (line 93) | type CountingReader interface
type countingReader (line 98) | type countingReader struct
method Read (line 105) | func (r *countingReader) Read(p []byte) (n int, err error) {
method Count (line 113) | func (r *countingReader) Count() int64 {
function newCountingReader (line 119) | func newCountingReader(r io.Reader) *countingReader {
type countingReadCloser (line 123) | type countingReadCloser struct
method Close (line 135) | func (c countingReadCloser) Close() error {
method WrappedReadCloser (line 139) | func (c countingReadCloser) WrappedReadCloser() io.ReadCloser {
function newCountingReadCloser (line 128) | func newCountingReadCloser(rc io.ReadCloser) *countingReadCloser {
type ReadCloserWrapper (line 144) | type ReadCloserWrapper interface
FILE: raft-compat/prevote_test.go
function TestRaft_PreVote_BootStrap_PreVote (line 16) | func TestRaft_PreVote_BootStrap_PreVote(t *testing.T) {
function TestRaft_PreVote_Rollback (line 156) | func TestRaft_PreVote_Rollback(t *testing.T) {
FILE: raft-compat/rolling_upgrade_test.go
function TestRaft_RollingUpgrade (line 20) | func TestRaft_RollingUpgrade(t *testing.T) {
function TestRaft_ReplaceUpgrade (line 163) | func TestRaft_ReplaceUpgrade(t *testing.T) {
FILE: raft-compat/testcluster/cluster.go
type RaftUIT (line 15) | type RaftUIT struct
method NumLogs (line 25) | func (r RaftUIT) NumLogs() int {
method GetLocalAddr (line 29) | func (r RaftUIT) GetLocalAddr() string {
method GetRaft (line 33) | func (r RaftUIT) GetRaft() interface{} {
method GetStore (line 37) | func (r RaftUIT) GetStore() interface{} {
method GetLocalID (line 41) | func (r RaftUIT) GetLocalID() string {
method GetLeaderID (line 45) | func (r RaftUIT) GetLeaderID() string {
type RaftLatest (line 67) | type RaftLatest struct
method NumLogs (line 77) | func (r RaftLatest) NumLogs() int {
method GetLocalAddr (line 81) | func (r RaftLatest) GetLocalAddr() string {
method GetRaft (line 85) | func (r RaftLatest) GetRaft() interface{} {
method GetStore (line 88) | func (r RaftLatest) GetStore() interface{} {
method GetLocalID (line 92) | func (r RaftLatest) GetLocalID() string {
method GetLeaderID (line 96) | func (r RaftLatest) GetLeaderID() string {
type RaftNode (line 101) | type RaftNode interface
type RaftCluster (line 110) | type RaftCluster struct
method ID (line 50) | func (r *RaftCluster) ID(i int) string {
method Addr (line 53) | func (r *RaftCluster) Addr(i int) string {
method Raft (line 57) | func (r *RaftCluster) Raft(id string) interface{} {
method Store (line 62) | func (r *RaftCluster) Store(id string) interface{} {
method GetLeader (line 131) | func (r *RaftCluster) GetLeader() RaftNode {
method Len (line 140) | func (r *RaftCluster) Len() int {
method AddNode (line 144) | func (r *RaftCluster) AddNode(node RaftNode) {
method DeleteNode (line 148) | func (r *RaftCluster) DeleteNode(id string) {
method GetIndex (line 153) | func (r *RaftCluster) GetIndex(id string) int {
function NewRaftCluster (line 114) | func NewRaftCluster(t *testing.T, f func(t *testing.T, id string) RaftNo...
function NewPreviousRaftCluster (line 123) | func NewPreviousRaftCluster(t *testing.T, count int, name string) RaftCl...
function NewUITRaftCluster (line 127) | func NewUITRaftCluster(t *testing.T, count int, name string) RaftCluster {
function InitUIT (line 164) | func InitUIT(t *testing.T, id string) RaftNode {
function InitUITWithStore (line 168) | func InitUITWithStore(t *testing.T, id string, store *raftprevious.Inmem...
function InitPrevious (line 195) | func InitPrevious(t *testing.T, id string) RaftNode {
function InitPreviousWithStore (line 200) | func InitPreviousWithStore(t *testing.T, id string, store *raft.InmemSto...
function convertLogToUIT (line 227) | func convertLogToUIT(ll *raftprevious.Log) *raft.Log {
function convertLogToPrevious (line 237) | func convertLogToPrevious(ll *raft.Log) *raftprevious.Log {
function convertInMemStoreToPrevious (line 254) | func convertInMemStoreToPrevious(s *raft.InmemStore) *raftprevious.Inmem...
function convertInMemStoreToUIT (line 285) | func convertInMemStoreToUIT(s *raftprevious.InmemStore) *raft.InmemStore {
FILE: raft-compat/utils/test_utils.go
function WaitForNewLeader (line 16) | func WaitForNewLeader(t *testing.T, oldLeader string, c testcluster.Raft...
type future (line 50) | type future interface
function WaitFuture (line 54) | func WaitFuture(t *testing.T, f future) {
FILE: raft.go
constant minCheckInterval (line 22) | minCheckInterval = 10 * time.Millisecond
constant oldestLogGaugeInterval (line 23) | oldestLogGaugeInterval = 10 * time.Second
constant rpcUnexpectedCommandError (line 24) | rpcUnexpectedCommandError = "unexpected command"
method getRPCHeader (line 36) | func (r *Raft) getRPCHeader() RPCHeader {
method checkRPCHeader (line 46) | func (r *Raft) checkRPCHeader(rpc RPC) error {
function getSnapshotVersion (line 76) | func getSnapshotVersion(protocolVersion ProtocolVersion) SnapshotVersion {
type commitTuple (line 84) | type commitTuple struct
type leaderState (line 90) | type leaderState struct
method setLeader (line 101) | func (r *Raft) setLeader(leaderAddr ServerAddress, leaderID ServerID) {
method requestConfigChange (line 116) | func (r *Raft) requestConfigChange(req configurationChangeRequest, timeo...
method run (line 136) | func (r *Raft) run() {
method runFollower (line 159) | func (r *Raft) runFollower() {
method liveBootstrap (line 262) | func (r *Raft) liveBootstrap(configuration Configuration) error {
method runCandidate (line 286) | func (r *Raft) runCandidate() {
method setLeadershipTransferInProgress (line 443) | func (r *Raft) setLeadershipTransferInProgress(v bool) {
method getLeadershipTransferInProgress (line 451) | func (r *Raft) getLeadershipTransferInProgress() bool {
method setupLeaderState (line 456) | func (r *Raft) setupLeaderState() {
method runLeader (line 469) | func (r *Raft) runLeader() {
method startStopReplication (line 583) | func (r *Raft) startStopReplication() {
method configurationChangeChIfStable (line 654) | func (r *Raft) configurationChangeChIfStable() chan *configurationChange...
method leaderLoop (line 668) | func (r *Raft) leaderLoop() {
method verifyLeader (line 966) | func (r *Raft) verifyLeader(v *verifyFuture) {
method leadershipTransfer (line 991) | func (r *Raft) leadershipTransfer(id ServerID, address ServerAddress, re...
method checkLeaderLease (line 1037) | func (r *Raft) checkLeaderLease() time.Duration {
method quorumSize (line 1087) | func (r *Raft) quorumSize() int {
method restoreUserSnapshot (line 1105) | func (r *Raft) restoreUserSnapshot(meta *SnapshotMeta, reader io.Reader)...
method appendConfigurationEntry (line 1204) | func (r *Raft) appendConfigurationEntry(future *configurationChangeFutur...
method dispatchLogs (line 1245) | func (r *Raft) dispatchLogs(applyLogs []*logFuture) {
method processLogs (line 1293) | func (r *Raft) processLogs(index uint64, futures map[uint64]*logFuture) {
method prepareLog (line 1363) | func (r *Raft) prepareLog(l *Log, future *logFuture) *commitTuple {
method processRPC (line 1391) | func (r *Raft) processRPC(rpc RPC) {
method processHeartbeat (line 1419) | func (r *Raft) processHeartbeat(rpc RPC) {
method appendEntries (line 1441) | func (r *Raft) appendEntries(rpc RPC, a *AppendEntriesRequest) {
method processConfigurationLogEntry (line 1586) | func (r *Raft) processConfigurationLogEntry(entry *Log) error {
method requestVote (line 1604) | func (r *Raft) requestVote(rpc RPC, req *RequestVoteRequest) {
method requestPreVote (line 1737) | func (r *Raft) requestPreVote(rpc RPC, req *RequestPreVoteRequest) {
method installSnapshot (line 1815) | func (r *Raft) installSnapshot(rpc RPC, req *InstallSnapshotRequest) {
method setLastContact (line 1957) | func (r *Raft) setLastContact() {
type voteResult (line 1963) | type voteResult struct
type preVoteResult (line 1968) | type preVoteResult struct
method electSelf (line 1977) | func (r *Raft) electSelf() <-chan *voteResult {
method preElectSelf (line 2051) | func (r *Raft) preElectSelf() <-chan *preVoteResult {
method persistVote (line 2131) | func (r *Raft) persistVote(term uint64, candidate []byte) error {
method setCurrentTerm (line 2142) | func (r *Raft) setCurrentTerm(t uint64) {
method setState (line 2153) | func (r *Raft) setState(state RaftState) {
method pickServer (line 2164) | func (r *Raft) pickServer() *Server {
method initiateLeadershipTransfer (line 2188) | func (r *Raft) initiateLeadershipTransfer(id *ServerID, address *ServerA...
method timeoutNow (line 2210) | func (r *Raft) timeoutNow(rpc RPC, req *TimeoutNowRequest) {
method setLatestConfiguration (line 2218) | func (r *Raft) setLatestConfiguration(c Configuration, i uint64) {
method setCommittedConfiguration (line 2225) | func (r *Raft) setCommittedConfiguration(c Configuration, i uint64) {
method getLatestConfiguration (line 2233) | func (r *Raft) getLatestConfiguration() Configuration {
FILE: raft_test.go
function TestRaft_StartStop (line 25) | func TestRaft_StartStop(t *testing.T) {
function TestRaft_AfterShutdown (line 30) | func TestRaft_AfterShutdown(t *testing.T) {
function TestRaft_LiveBootstrap (line 65) | func TestRaft_LiveBootstrap(t *testing.T) {
function TestRaft_LiveBootstrap_From_NonVoter (line 105) | func TestRaft_LiveBootstrap_From_NonVoter(t *testing.T) {
function TestRaft_RecoverCluster_NoState (line 130) | func TestRaft_RecoverCluster_NoState(t *testing.T) {
function TestRaft_RecoverCluster (line 151) | func TestRaft_RecoverCluster(t *testing.T) {
function TestRaft_HasExistingState (line 255) | func TestRaft_HasExistingState(t *testing.T) {
function TestRaft_SingleNode (line 297) | func TestRaft_SingleNode(t *testing.T) {
function TestRaft_TripleNode (line 340) | func TestRaft_TripleNode(t *testing.T) {
function TestRaft_LeaderFail (line 358) | func TestRaft_LeaderFail(t *testing.T) {
function TestRaft_BehindFollower (line 435) | func TestRaft_BehindFollower(t *testing.T) {
function TestRaft_ApplyNonLeader (line 475) | func TestRaft_ApplyNonLeader(t *testing.T) {
function TestRaft_ApplyConcurrent (line 502) | func TestRaft_ApplyConcurrent(t *testing.T) {
function TestRaft_ApplyConcurrent_Timeout (line 553) | func TestRaft_ApplyConcurrent_Timeout(t *testing.T) {
function TestRaft_JoinNode (line 593) | func TestRaft_JoinNode(t *testing.T) {
function TestRaft_JoinNode_ConfigStore (line 621) | func TestRaft_JoinNode_ConfigStore(t *testing.T) {
function TestRaft_RemoveFollower (line 689) | func TestRaft_RemoveFollower(t *testing.T) {
function TestRaft_RemoveLeader (line 730) | func TestRaft_RemoveLeader(t *testing.T) {
function TestRaft_RemoveLeader_NoShutdown (line 778) | func TestRaft_RemoveLeader_NoShutdown(t *testing.T) {
function TestRaft_RemoveFollower_SplitCluster (line 841) | func TestRaft_RemoveFollower_SplitCluster(t *testing.T) {
function TestRaft_AddKnownPeer (line 878) | func TestRaft_AddKnownPeer(t *testing.T) {
function TestRaft_RemoveUnknownPeer (line 917) | func TestRaft_RemoveUnknownPeer(t *testing.T) {
function TestRaft_SnapshotRestore (line 956) | func TestRaft_SnapshotRestore(t *testing.T) {
function TestRaft_RestoreSnapshotOnStartup_Monotonic (line 1016) | func TestRaft_RestoreSnapshotOnStartup_Monotonic(t *testing.T) {
function TestRaft_SnapshotRestore_Progress (line 1098) | func TestRaft_SnapshotRestore_Progress(t *testing.T) {
type lockedBytesBuffer (line 1194) | type lockedBytesBuffer struct
method Write (line 1199) | func (b *lockedBytesBuffer) Write(p []byte) (n int, err error) {
method String (line 1205) | func (b *lockedBytesBuffer) String() string {
function TestRaft_NoRestoreOnStart (line 1217) | func TestRaft_NoRestoreOnStart(t *testing.T) {
function TestRaft_SnapshotRestore_PeerChange (line 1260) | func TestRaft_SnapshotRestore_PeerChange(t *testing.T) {
function TestRaft_AutoSnapshot (line 1366) | func TestRaft_AutoSnapshot(t *testing.T) {
function TestRaft_UserSnapshot (line 1396) | func TestRaft_UserSnapshot(t *testing.T) {
function snapshotAndRestore (line 1434) | func snapshotAndRestore(t *testing.T, offset uint64, monotonicLogStore b...
function TestRaft_UserRestore (line 1572) | func TestRaft_UserRestore(t *testing.T) {
function TestRaft_SendSnapshotFollower (line 1598) | func TestRaft_SendSnapshotFollower(t *testing.T) {
function TestRaft_SendSnapshotAndLogsFollower (line 1640) | func TestRaft_SendSnapshotAndLogsFollower(t *testing.T) {
function TestRaft_ReJoinFollower (line 1694) | func TestRaft_ReJoinFollower(t *testing.T) {
function TestRaft_LeaderLeaseExpire (line 1771) | func TestRaft_LeaderLeaseExpire(t *testing.T) {
function TestRaft_Barrier (line 1838) | func TestRaft_Barrier(t *testing.T) {
function TestRaft_VerifyLeader (line 1866) | func TestRaft_VerifyLeader(t *testing.T) {
function TestRaft_VerifyLeader_Single (line 1883) | func TestRaft_VerifyLeader_Single(t *testing.T) {
function TestRaft_VerifyLeader_Fail (line 1900) | func TestRaft_VerifyLeader_Fail(t *testing.T) {
function TestRaft_VerifyLeader_PartialConnect (line 1940) | func TestRaft_VerifyLeader_PartialConnect(t *testing.T) {
function TestRaft_NotifyCh (line 1974) | func TestRaft_NotifyCh(t *testing.T) {
function TestRaft_AppendEntry (line 2005) | func TestRaft_AppendEntry(t *testing.T) {
function TestRaft_PreVoteMixedCluster (line 2068) | func TestRaft_PreVoteMixedCluster(t *testing.T) {
function TestRaft_PreVoteAvoidElectionWithPartition (line 2126) | func TestRaft_PreVoteAvoidElectionWithPartition(t *testing.T) {
function TestRaft_VotingGrant_WhenLeaderAvailable (line 2157) | func TestRaft_VotingGrant_WhenLeaderAvailable(t *testing.T) {
function TestRaft_ProtocolVersion_RejectRPC (line 2204) | func TestRaft_ProtocolVersion_RejectRPC(t *testing.T) {
function TestRaft_ProtocolVersion_Upgrade_1_2 (line 2236) | func TestRaft_ProtocolVersion_Upgrade_1_2(t *testing.T) {
function TestRaft_ProtocolVersion_Upgrade_2_3 (line 2279) | func TestRaft_ProtocolVersion_Upgrade_2_3(t *testing.T) {
function TestRaft_LeaderID_Propagated (line 2313) | func TestRaft_LeaderID_Propagated(t *testing.T) {
function TestRaft_LeadershipTransferInProgress (line 2342) | func TestRaft_LeadershipTransferInProgress(t *testing.T) {
function pointerToString (line 2360) | func pointerToString(s string) *string {
function TestRaft_LeadershipTransferPickServer (line 2364) | func TestRaft_LeadershipTransferPickServer(t *testing.T) {
function TestRaft_LeadershipTransfer (line 2402) | func TestRaft_LeadershipTransfer(t *testing.T) {
function TestRaft_LeadershipTransferWithOneNode (line 2417) | func TestRaft_LeadershipTransferWithOneNode(t *testing.T) {
function TestRaft_LeadershipTransferWithWrites (line 2433) | func TestRaft_LeadershipTransferWithWrites(t *testing.T) {
function TestRaft_LeadershipTransferWithSevenNodes (line 2488) | func TestRaft_LeadershipTransferWithSevenNodes(t *testing.T) {
function TestRaft_LeadershipTransferToInvalidID (line 2502) | func TestRaft_LeadershipTransferToInvalidID(t *testing.T) {
function TestRaft_LeadershipTransferToInvalidAddress (line 2518) | func TestRaft_LeadershipTransferToInvalidAddress(t *testing.T) {
function TestRaft_LeadershipTransferToBehindServer (line 2534) | func TestRaft_LeadershipTransferToBehindServer(t *testing.T) {
function TestRaft_LeadershipTransferToItself (line 2555) | func TestRaft_LeadershipTransferToItself(t *testing.T) {
function TestRaft_LeadershipTransferLeaderRejectsClientRequests (line 2572) | func TestRaft_LeadershipTransferLeaderRejectsClientRequests(t *testing.T) {
function TestRaft_LeadershipTransferLeaderReplicationTimeout (line 2606) | func TestRaft_LeadershipTransferLeaderReplicationTimeout(t *testing.T) {
function TestRaft_LeadershipTransferIgnoresNonvoters (line 2636) | func TestRaft_LeadershipTransferIgnoresNonvoters(t *testing.T) {
function TestRaft_LeadershipTransferStopRightAway (line 2659) | func TestRaft_LeadershipTransferStopRightAway(t *testing.T) {
function TestRaft_GetConfigurationNoBootstrap (line 2673) | func TestRaft_GetConfigurationNoBootstrap(t *testing.T) {
function TestRaft_LogStoreIsMonotonic (line 2710) | func TestRaft_LogStoreIsMonotonic(t *testing.T) {
function TestRaft_CacheLogWithStoreError (line 2745) | func TestRaft_CacheLogWithStoreError(t *testing.T) {
function TestRaft_ReloadConfig (line 2795) | func TestRaft_ReloadConfig(t *testing.T) {
function TestRaft_ReloadConfigValidates (line 2826) | func TestRaft_ReloadConfigValidates(t *testing.T) {
function TestRaft_InstallSnapshot_InvalidPeers (line 2881) | func TestRaft_InstallSnapshot_InvalidPeers(t *testing.T) {
function TestRaft_VoteNotGranted_WhenNodeNotInCluster (line 2902) | func TestRaft_VoteNotGranted_WhenNodeNotInCluster(t *testing.T) {
function TestRaft_ClusterCanRegainStability_WhenNonVoterWithHigherTermJoin (line 2974) | func TestRaft_ClusterCanRegainStability_WhenNonVoterWithHigherTermJoin(t...
function TestRaft_FollowerRemovalNoElection (line 3066) | func TestRaft_FollowerRemovalNoElection(t *testing.T) {
function waitForState (line 3123) | func waitForState(follower *Raft, state RaftState) {
function waitForLeader (line 3131) | func waitForLeader(c *cluster) error {
function TestRaft_runFollower_State_Transition (line 3144) | func TestRaft_runFollower_State_Transition(t *testing.T) {
function TestRaft_runFollower_ReloadTimeoutConfigs (line 3188) | func TestRaft_runFollower_ReloadTimeoutConfigs(t *testing.T) {
function TestRaft_PreVote_ShouldNotRejectLeader (line 3222) | func TestRaft_PreVote_ShouldNotRejectLeader(t *testing.T) {
function TestRaft_PreVote_ShouldRejectNonLeader (line 3264) | func TestRaft_PreVote_ShouldRejectNonLeader(t *testing.T) {
FILE: replication.go
constant maxFailureScale (line 17) | maxFailureScale = 12
constant failureWait (line 18) | failureWait = 10 * time.Millisecond
type followerReplication (line 33) | type followerReplication struct
method notifyAll (line 100) | func (s *followerReplication) notifyAll(leader bool) {
method cleanNotify (line 114) | func (s *followerReplication) cleanNotify(v *verifyFuture) {
method LastContact (line 121) | func (s *followerReplication) LastContact() time.Time {
method setLastContact (line 129) | func (s *followerReplication) setLastContact() {
method replicate (line 137) | func (r *Raft) replicate(s *followerReplication) {
method replicateTo (line 202) | func (r *Raft) replicateTo(s *followerReplication, lastIndex uint64) (sh...
method sendLatestSnapshot (line 299) | func (r *Raft) sendLatestSnapshot(s *followerReplication) (bool, error) {
method heartbeat (line 388) | func (r *Raft) heartbeat(s *followerReplication, stopCh chan struct{}) {
method pipelineReplicate (line 446) | func (r *Raft) pipelineReplicate(s *followerReplication) error {
method pipelineSend (line 512) | func (r *Raft) pipelineSend(s *followerReplication, p AppendPipeline, ne...
method pipelineDecode (line 534) | func (r *Raft) pipelineDecode(s *followerReplication, p AppendPipeline, ...
method setupAppendEntries (line 570) | func (r *Raft) setupAppendEntries(s *followerReplication, req *AppendEnt...
method setPreviousLog (line 587) | func (r *Raft) setPreviousLog(req *AppendEntriesRequest, nextIndex uint6...
method setNewLogs (line 614) | func (r *Raft) setNewLogs(req *AppendEntriesRequest, nextIndex, lastInde...
function appendStats (line 633) | func appendStats(peer string, start time.Time, logs float32, skipLegacy ...
method handleStaleTerm (line 646) | func (r *Raft) handleStaleTerm(s *followerReplication) {
function updateLastAppended (line 655) | func updateLastAppended(s *followerReplication, req *AppendEntriesReques...
FILE: saturation.go
type saturationMetric (line 22) | type saturationMetric struct
method sleeping (line 56) | func (s *saturationMetric) sleeping() {
method working (line 72) | func (s *saturationMetric) working() {
method report (line 95) | func (s *saturationMetric) report() {
function newSaturationMetric (line 44) | func newSaturationMetric(name []string, reportInterval time.Duration) *s...
FILE: saturation_test.go
function TestSaturationMetric (line 13) | func TestSaturationMetric(t *testing.T) {
function TestSaturationMetric_IncorrectUsage (line 54) | func TestSaturationMetric_IncorrectUsage(t *testing.T) {
FILE: snapshot.go
type SnapshotMeta (line 15) | type SnapshotMeta struct
type SnapshotStore (line 45) | type SnapshotStore interface
type SnapshotSink (line 63) | type SnapshotSink interface
method runSnapshots (line 72) | func (r *Raft) runSnapshots() {
method shouldSnapshot (line 106) | func (r *Raft) shouldSnapshot() bool {
method takeSnapshot (line 125) | func (r *Raft) takeSnapshot() (string, error) {
method compactLogsWithTrailing (line 216) | func (r *Raft) compactLogsWithTrailing(snapIdx uint64, lastLogIdx uint64...
method compactLogs (line 252) | func (r *Raft) compactLogs(snapIdx uint64) error {
method removeOldLogs (line 264) | func (r *Raft) removeOldLogs() error {
FILE: stable.go
type StableStore (line 8) | type StableStore interface
FILE: state.go
type RaftState (line 13) | type RaftState
method String (line 29) | func (s RaftState) String() string {
constant Follower (line 17) | Follower RaftState = iota
constant Candidate (line 20) | Candidate
constant Leader (line 23) | Leader
constant Shutdown (line 26) | Shutdown
type raftState (line 47) | type raftState struct
method getState (line 79) | func (r *raftState) getState() RaftState {
method setState (line 84) | func (r *raftState) setState(s RaftState) {
method getCurrentTerm (line 89) | func (r *raftState) getCurrentTerm() uint64 {
method setCurrentTerm (line 93) | func (r *raftState) setCurrentTerm(term uint64) {
method getLastLog (line 97) | func (r *raftState) getLastLog() (index, term uint64) {
method setLastLog (line 105) | func (r *raftState) setLastLog(index, term uint64) {
method getLastSnapshot (line 112) | func (r *raftState) getLastSnapshot() (index, term uint64) {
method setLastSnapshot (line 120) | func (r *raftState) setLastSnapshot(index, term uint64) {
method getCommitIndex (line 127) | func (r *raftState) getCommitIndex() uint64 {
method setCommitIndex (line 131) | func (r *raftState) setCommitIndex(index uint64) {
method getLastApplied (line 135) | func (r *raftState) getLastApplied() uint64 {
method setLastApplied (line 139) | func (r *raftState) setLastApplied(index uint64) {
method goFunc (line 145) | func (r *raftState) goFunc(f func()) {
method waitShutdown (line 153) | func (r *raftState) waitShutdown() {
method getLastIndex (line 159) | func (r *raftState) getLastIndex() uint64 {
method getLastEntry (line 167) | func (r *raftState) getLastEntry() (uint64, uint64) {
FILE: tcp_transport.go
type TCPStreamLayer (line 21) | type TCPStreamLayer struct
method Dial (line 99) | func (t *TCPStreamLayer) Dial(address ServerAddress, timeout time.Dura...
method Accept (line 104) | func (t *TCPStreamLayer) Accept() (c net.Conn, err error) {
method Close (line 109) | func (t *TCPStreamLayer) Close() (err error) {
method Addr (line 114) | func (t *TCPStreamLayer) Addr() net.Addr {
function NewTCPTransport (line 28) | func NewTCPTransport(
function NewTCPTransportWithLogger (line 42) | func NewTCPTransportWithLogger(
function NewTCPTransportWithConfig (line 56) | func NewTCPTransportWithConfig(
function newTCPTransport (line 67) | func newTCPTransport(bindAddr string,
FILE: tcp_transport_test.go
function TestTCPTransport_BadAddr (line 11) | func TestTCPTransport_BadAddr(t *testing.T) {
function TestTCPTransport_EmptyAddr (line 18) | func TestTCPTransport_EmptyAddr(t *testing.T) {
function TestTCPTransport_WithAdvertise (line 25) | func TestTCPTransport_WithAdvertise(t *testing.T) {
FILE: testing.go
function inmemConfig (line 24) | func inmemConfig(t testing.TB) *Config {
type MockFSM (line 38) | type MockFSM struct
method Apply (line 76) | func (m *MockFSM) Apply(log *Log) interface{} {
method Snapshot (line 84) | func (m *MockFSM) Snapshot() (FSMSnapshot, error) {
method Restore (line 91) | func (m *MockFSM) Restore(inp io.ReadCloser) error {
method Logs (line 103) | func (m *MockFSM) Logs() [][]byte {
type MockFSMConfigStore (line 45) | type MockFSMConfigStore struct
method StoreConfiguration (line 110) | func (m *MockFSMConfigStore) StoreConfiguration(index uint64, config C...
type WrappingFSM (line 50) | type WrappingFSM interface
function getMockFSM (line 54) | func getMockFSM(fsm FSM) *MockFSM {
type MockSnapshot (line 68) | type MockSnapshot struct
method Persist (line 118) | func (m *MockSnapshot) Persist(sink SnapshotSink) error {
method Release (line 130) | func (m *MockSnapshot) Release() {
type MockMonotonicLogStore (line 135) | type MockMonotonicLogStore struct
method IsMonotonic (line 140) | func (m *MockMonotonicLogStore) IsMonotonic() bool {
method FirstIndex (line 145) | func (m *MockMonotonicLogStore) FirstIndex() (uint64, error) {
method LastIndex (line 150) | func (m *MockMonotonicLogStore) LastIndex() (uint64, error) {
method GetLog (line 155) | func (m *MockMonotonicLogStore) GetLog(index uint64, log *Log) error {
method StoreLog (line 160) | func (m *MockMonotonicLogStore) StoreLog(log *Log) error {
method StoreLogs (line 165) | func (m *MockMonotonicLogStore) StoreLogs(logs []*Log) error {
method DeleteRange (line 170) | func (m *MockMonotonicLogStore) DeleteRange(min uint64, max uint64) er...
type testLoggerAdapter (line 177) | type testLoggerAdapter struct
method Write (line 182) | func (a *testLoggerAdapter) Write(d []byte) (int, error) {
function newTestLogger (line 196) | func newTestLogger(tb testing.TB) hclog.Logger {
function newTestLoggerWithPrefix (line 212) | func newTestLoggerWithPrefix(tb testing.TB, prefix string) hclog.Logger {
type cluster (line 223) | type cluster struct
method Merge (line 243) | func (c *cluster) Merge(other *cluster) {
method RemoveServer (line 252) | func (c *cluster) RemoveServer(id ServerID) {
method notifyFailed (line 264) | func (c *cluster) notifyFailed() {
method Failf (line 280) | func (c *cluster) Failf(format string, args ...interface{}) {
method FailNowf (line 291) | func (c *cluster) FailNowf(format string, args ...interface{}) {
method Close (line 297) | func (c *cluster) Close() {
method WaitEventChan (line 326) | func (c *cluster) WaitEventChan(ctx context.Context, filter FilterFn) ...
method WaitEvent (line 348) | func (c *cluster) WaitEvent(filter FilterFn, timeout time.Duration) {
method WaitForReplication (line 361) | func (c *cluster) WaitForReplication(fsmLength int) {
method pollState (line 394) | func (c *cluster) pollState(s RaftState) ([]*Raft, uint64) {
method GetInState (line 411) | func (c *cluster) GetInState(s RaftState) []*Raft {
method Leader (line 491) | func (c *cluster) Leader() *Raft {
method Followers (line 502) | func (c *cluster) Followers() []*Raft {
method WaitForFollowers (line 509) | func (c *cluster) WaitForFollowers(expFollowers int) []*Raft {
method FullyConnect (line 518) | func (c *cluster) FullyConnect() {
method Disconnect (line 531) | func (c *cluster) Disconnect(a ServerAddress) {
method Partition (line 544) | func (c *cluster) Partition(far []ServerAddress) {
method IndexOf (line 578) | func (c *cluster) IndexOf(r *Raft) int {
method EnsureLeader (line 589) | func (c *cluster) EnsureLeader(t *testing.T, expect ServerAddress) {
method EnsureSame (line 614) | func (c *cluster) EnsureSame(t *testing.T) {
method getConfiguration (line 681) | func (c *cluster) getConfiguration(r *Raft) Configuration {
method EnsureSamePeers (line 692) | func (c *cluster) EnsureSamePeers(t *testing.T) {
type MakeClusterOpts (line 719) | type MakeClusterOpts struct
function makeCluster (line 733) | func makeCluster(t *testing.T, opts *MakeClusterOpts) *cluster {
function MakeCluster (line 839) | func MakeCluster(n int, t *testing.T, conf *Config) *cluster {
function MakeClusterNoBootstrap (line 848) | func MakeClusterNoBootstrap(n int, t *testing.T, conf *Config) *cluster {
function MakeClusterCustom (line 856) | func MakeClusterCustom(t *testing.T, opts *MakeClusterOpts) *cluster {
function FileSnapTest (line 861) | func FileSnapTest(t *testing.T) (string, *FileSnapshotStore) {
FILE: testing_batch.go
function init (line 8) | func init() {
method ApplyBatch (line 16) | func (m *MockFSM) ApplyBatch(logs []*Log) []interface{} {
FILE: transport.go
type RPCResponse (line 12) | type RPCResponse struct
type RPC (line 18) | type RPC struct
method Respond (line 25) | func (r *RPC) Respond(resp interface{}, err error) {
type Transport (line 31) | type Transport interface
type WithPreVote (line 74) | type WithPreVote interface
type WithClose (line 85) | type WithClose interface
type LoopbackTransport (line 93) | type LoopbackTransport interface
type WithPeers (line 103) | type WithPeers interface
type AppendPipeline (line 112) | type AppendPipeline interface
type AppendFuture (line 126) | type AppendFuture interface
FILE: transport_test.go
constant TTInmem (line 14) | TTInmem = iota
constant numTestTransports (line 17) | numTestTransports
function NewTestTransport (line 20) | func NewTestTransport(ttype int, addr ServerAddress) (ServerAddress, Loo...
function TestTransport_StartStop (line 29) | func TestTransport_StartStop(t *testing.T) {
function TestTransport_AppendEntries (line 38) | func TestTransport_AppendEntries(t *testing.T) {
function TestTransport_AppendEntriesPipeline (line 102) | func TestTransport_AppendEntriesPipeline(t *testing.T) {
function TestTransport_RequestVote (line 184) | func TestTransport_RequestVote(t *testing.T) {
function TestTransport_InstallSnapshot (line 239) | func TestTransport_InstallSnapshot(t *testing.T) {
function TestTransport_EncodeDecode (line 310) | func TestTransport_EncodeDecode(t *testing.T) {
FILE: util.go
function newSeed (line 20) | func newSeed() int64 {
function randomTimeout (line 29) | func randomTimeout(minVal time.Duration) <-chan time.Time {
function min (line 38) | func min(a, b uint64) uint64 {
function max (line 46) | func max(a, b uint64) uint64 {
function generateUUID (line 54) | func generateUUID() string {
function asyncNotifyCh (line 70) | func asyncNotifyCh(ch chan struct{}) {
function drainNotifyCh (line 79) | func drainNotifyCh(ch chan struct{}) bool {
function overrideNotifyBool (line 93) | func overrideNotifyBool(ch chan bool, v bool) {
function decodeMsgPack (line 108) | func decodeMsgPack(buf []byte, out interface{}) error {
function encodeMsgPack (line 116) | func encodeMsgPack(in interface{}) (*bytes.Buffer, error) {
function backoff (line 129) | func backoff(base time.Duration, round, limit uint64) time.Duration {
function cappedExponentialBackoff (line 140) | func cappedExponentialBackoff(base time.Duration, round, limit uint64, c...
type uint64Slice (line 156) | type uint64Slice
method Len (line 158) | func (p uint64Slice) Len() int { return len(p) }
method Less (line 159) | func (p uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
method Swap (line 160) | func (p uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
FILE: util_test.go
function TestMsgpackEncodeTimeDefaultFormat (line 15) | func TestMsgpackEncodeTimeDefaultFormat(t *testing.T) {
function TestRandomTimeout (line 33) | func TestRandomTimeout(t *testing.T) {
function TestNewSeed (line 48) | func TestNewSeed(t *testing.T) {
function TestRandomTimeout_NoTime (line 59) | func TestRandomTimeout_NoTime(t *testing.T) {
function TestMin (line 66) | func TestMin(t *testing.T) {
function TestMax (line 78) | func TestMax(t *testing.T) {
function TestGenerateUUID (line 90) | func TestGenerateUUID(t *testing.T) {
function TestBackoff (line 110) | func TestBackoff(t *testing.T) {
function TestOverrideNotifyBool (line 132) | func TestOverrideNotifyBool(t *testing.T) {
Condensed preview — 91 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (769K chars).
[
{
"path": ".github/CODEOWNERS",
"chars": 600,
"preview": "# Each line is a file pattern followed by one or more owners.\n# More on CODEOWNERS files: https://help.github.com/en/git"
},
{
"path": ".github/dependabot.yml",
"chars": 708,
"preview": "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\nversion: 2\n\nupdates:\n - package-ecosystem: \"github-"
},
{
"path": ".github/pull_request_template.md",
"chars": 432,
"preview": "<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->\n## Description\n\n<!-- Provide a summary of what the PR does an"
},
{
"path": ".github/stale.yml",
"chars": 1817,
"preview": "# Copyright IBM Corp. 2013, 2025\n# SPDX-License-Identifier: MPL-2.0\n\n# Number of days of inactivity before an Issue beco"
},
{
"path": ".github/workflows/ci.yml",
"chars": 3024,
"preview": "name: ci\n\non:\n pull_request:\n branches: [\"main\"]\n push:\n branches: [\"main\"]\n tags: [\"*\"]\n\npermissions:\n cont"
},
{
"path": ".github/workflows/two-step-pr-approval.yml",
"chars": 493,
"preview": "name: Two-Stage PR Review Process\n\non:\n pull_request:\n types: [opened, synchronize, reopened, labeled, unlabeled, re"
},
{
"path": ".gitignore",
"chars": 279,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
},
{
"path": ".gitmodules",
"chars": 125,
"preview": "[submodule \"raft-compat/raft-latest\"]\n\tpath = raft-compat/raft-previous-version\n\turl = https://github.com/hashicorp/raft"
},
{
"path": ".golangci-lint.yml",
"chars": 2360,
"preview": "# Copyright IBM Corp. 2013, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nrun:\n deadline: 5m\n\nlinters-settings:\n govet:\n "
},
{
"path": ".travis.yml",
"chars": 617,
"preview": "# Copyright IBM Corp. 2013, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nlanguage: go\n\ngo:\n # Disabled until https://gith"
},
{
"path": "CHANGELOG.md",
"chars": 8997,
"preview": "# UNRELEASED\n\nIMPROVEMENETS\n\n* Added a flag to skip legacy duplicate telemetry. [GH-630](https://github.com/hashicorp/ra"
},
{
"path": "LICENSE",
"chars": 15917,
"preview": "Copyright IBM Corp. 2013, 2025\n\nMozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. “Contributor”\n\n means each"
},
{
"path": "Makefile",
"chars": 1325,
"preview": "DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)\nENV = $(shell go env GOPATH)\nGO_VERSION = $(shell go "
},
{
"path": "README.md",
"chars": 8492,
"preview": "raft [](https://github.com/hashicorp/raft/actio"
},
{
"path": "api.go",
"chars": 44829,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"s"
},
{
"path": "bench/bench.go",
"chars": 4513,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raftbench\n\n// raftbench provides common b"
},
{
"path": "bench_test.go",
"chars": 1085,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gith"
},
{
"path": "commands.go",
"chars": 5515,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\n// RPCHeader is a common sub-struct"
},
{
"path": "commitment.go",
"chars": 3323,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"sort\"\n\t\"sync\"\n)\n\n// Comm"
},
{
"path": "commitment_test.go",
"chars": 6075,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"testing\"\n)\n\nfunc makeCon"
},
{
"path": "config.go",
"chars": 16213,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"time\""
},
{
"path": "configuration.go",
"chars": 12029,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport \"fmt\"\n\n// ServerSuffrage det"
},
{
"path": "configuration_test.go",
"chars": 9807,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"string"
},
{
"path": "discard_snapshot.go",
"chars": 2109,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// Discard"
},
{
"path": "discard_snapshot_test.go",
"chars": 497,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport \"testing\"\n\nfunc TestDiscardS"
},
{
"path": "docs/README.md",
"chars": 5910,
"preview": "# Raft Developer Documentation\n\nThis documentation provides a high level introduction to the `hashicorp/raft`\nimplementa"
},
{
"path": "docs/apply.md",
"chars": 5901,
"preview": "# Raft Apply\n\nApply is the primary operation provided by raft. A client calls `raft.Apply` to apply\na command to the FSM"
},
{
"path": "docs/divergence.md",
"chars": 5380,
"preview": "# HashiCorp Raft Divergences\n\nIn 2013 HashiCorp created its own Raft implementation based on the just\nreleased [Raft pap"
},
{
"path": "file_snapshot.go",
"chars": 13511,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encodi"
},
{
"path": "file_snapshot_test.go",
"chars": 7742,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"ref"
},
{
"path": "fsm.go",
"chars": 8418,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\thcl"
},
{
"path": "future.go",
"chars": 7712,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"tim"
},
{
"path": "future_test.go",
"chars": 994,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfu"
},
{
"path": "fuzzy/apply_src.go",
"chars": 1438,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"hash/fnv\"\n\t\"math/rand\"\n"
},
{
"path": "fuzzy/cluster.go",
"chars": 11707,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"o"
},
{
"path": "fuzzy/fsm.go",
"chars": 2118,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"bufio\"\n\t\"encoding/binar"
},
{
"path": "fuzzy/fsm_batch.go",
"chars": 429,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\n//go:build batchtest\n\npackage fuzzy\n\nimport \"gith"
},
{
"path": "fuzzy/go.mod",
"chars": 788,
"preview": "module github.com/hashicorp/raft/fuzzy\n\ngo 1.20\n\nrequire (\n\tgithub.com/hashicorp/go-hclog v1.6.3\n\tgithub.com/hashicorp/g"
},
{
"path": "fuzzy/go.sum",
"chars": 17465,
"preview": "cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/DataDog/datadog-go v3.2.0+"
},
{
"path": "fuzzy/leadershiptransfer_test.go",
"chars": 2126,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\t"
},
{
"path": "fuzzy/membership_test.go",
"chars": 3642,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path"
},
{
"path": "fuzzy/node.go",
"chars": 2060,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t"
},
{
"path": "fuzzy/partition_test.go",
"chars": 3627,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"math/ra"
},
{
"path": "fuzzy/readme.md",
"chars": 2761,
"preview": "# Fuzzy Raft\n\nInspired by http://colin-scott.github.io/blog/2015/10/07/fuzzing-raft-for-fun-and-profit/ this package \nis"
},
{
"path": "fuzzy/resolve.go",
"chars": 1105,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n"
},
{
"path": "fuzzy/simple_test.go",
"chars": 552,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\n// "
},
{
"path": "fuzzy/slowvoter_test.go",
"chars": 2520,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\t"
},
{
"path": "fuzzy/transport.go",
"chars": 9419,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"error"
},
{
"path": "fuzzy/verifier.go",
"chars": 2302,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage fuzzy\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\""
},
{
"path": "go.mod",
"chars": 822,
"preview": "module github.com/hashicorp/raft\n\ngo 1.24.0\n\nretract v1.1.3 // Deleted original tag; module checksum may not be accurate"
},
{
"path": "go.sum",
"chars": 17416,
"preview": "cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/DataDog/datadog-go v3.2.0+"
},
{
"path": "inmem_snapshot.go",
"chars": 2819,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"sy"
},
{
"path": "inmem_snapshot_test.go",
"chars": 4142,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"reflect\"\n"
},
{
"path": "inmem_store.go",
"chars": 2898,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"errors\"\n\t\"sync\"\n)\n\n// In"
},
{
"path": "inmem_transport.go",
"chars": 9360,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"tim"
},
{
"path": "inmem_transport_test.go",
"chars": 2107,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gith"
},
{
"path": "integ_test.go",
"chars": 16758,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\""
},
{
"path": "log.go",
"chars": 7079,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tmetrics \""
},
{
"path": "log_cache.go",
"chars": 2214,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\n// LogCa"
},
{
"path": "log_cache_test.go",
"chars": 2844,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"syn"
},
{
"path": "log_test.go",
"chars": 3216,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\""
},
{
"path": "membership.md",
"chars": 7121,
"preview": "Simon (@superfell) and I (@ongardie) talked through reworking this library's cluster membership changes last Friday. We "
},
{
"path": "net_transport.go",
"chars": 26510,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"erro"
},
{
"path": "net_transport_test.go",
"chars": 23190,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\""
},
{
"path": "observer.go",
"chars": 4340,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"sync/atomic\"\n\t\"time\"\n)\n\n"
},
{
"path": "peersjson.go",
"chars": 2907,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n"
},
{
"path": "peersjson_test.go",
"chars": 3143,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"r"
},
{
"path": "progress.go",
"chars": 2758,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"sync\"\n\t"
},
{
"path": "raft-compat/go.mod",
"chars": 958,
"preview": "module github.com/hashicorp/raft/compat\n\ngo 1.20\n\nrequire github.com/stretchr/testify v1.11.1\n\nrequire (\n\tgithub.com/arm"
},
{
"path": "raft-compat/go.sum",
"chars": 17225,
"preview": "cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/DataDog/datadog-go v3.2.0+"
},
{
"path": "raft-compat/prevote_test.go",
"chars": 11098,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft_compat\n\nimport (\n\t\"github.com/hashic"
},
{
"path": "raft-compat/rolling_upgrade_test.go",
"chars": 10205,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft_compat\n\nimport (\n\t\"fmt\"\n\t\"github.com"
},
{
"path": "raft-compat/testcluster/cluster.go",
"chars": 7484,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage testcluster\n\nimport (\n\t\"fmt\"\n\t\"github.com"
},
{
"path": "raft-compat/utils/test_utils.go",
"chars": 1214,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage utils\n\nimport (\n\t\"fmt\"\n\t\"github.com/hashi"
},
{
"path": "raft.go",
"chars": 70355,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"container/list\""
},
{
"path": "raft_test.go",
"chars": 90824,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n"
},
{
"path": "replication.go",
"chars": 20671,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t"
},
{
"path": "saturation.go",
"chars": 3312,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"math\"\n\t\"time\"\n\n\t\"github."
},
{
"path": "saturation_test.go",
"chars": 3950,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gith"
},
{
"path": "snapshot.go",
"chars": 9024,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"gi"
},
{
"path": "stable.go",
"chars": 514,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\n// StableStore is used to provide s"
},
{
"path": "state.go",
"chars": 3939,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\n"
},
{
"path": "tag.sh",
"chars": 466,
"preview": "#!/usr/bin/env bash\n# Copyright IBM Corp. 2013, 2025\n# SPDX-License-Identifier: MPL-2.0\n\nset -e\n\n# The version must be s"
},
{
"path": "tcp_transport.go",
"chars": 3202,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"t"
},
{
"path": "tcp_transport_test.go",
"chars": 999,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"net\"\n\t\"testing\"\n)\n\nfunc "
},
{
"path": "testing.go",
"chars": 23770,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\""
},
{
"path": "testing_batch.go",
"chars": 670,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\n//go:build batchtest\n\npackage raft\n\nfunc init() {"
},
{
"path": "transport.go",
"chars": 5352,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"io\"\n\t\"time\"\n)\n\n// RPCRes"
},
{
"path": "transport_test.go",
"chars": 7328,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"reflect\"\n\t\"test"
},
{
"path": "util.go",
"chars": 3615,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\tcrand \"crypto/ra"
},
{
"path": "util_test.go",
"chars": 3568,
"preview": "// Copyright IBM Corp. 2013, 2025\n// SPDX-License-Identifier: MPL-2.0\n\npackage raft\n\nimport (\n\t\"bytes\"\n\t\"regexp\"\n\t\"testi"
}
]
About this extraction
This page contains the full source code of the hashicorp/raft GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 91 files (695.5 KB), approximately 206.4k tokens, and a symbol index with 929 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.