Compare commits
5 Commits
eda50d290a
...
cuddle-ple
Author | SHA1 | Date | |
---|---|---|---|
|
775be2ebf6 | ||
c334dba445 | |||
aaf3a72d3b | |||
643d87895b | |||
21c1507ebe |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.3] - 2025-07-06
|
||||
|
||||
### Added
|
||||
- do publish
|
||||
- allow readme
|
||||
|
||||
## [0.1.2] - 2025-07-04
|
||||
|
||||
### Added
|
||||
- update basic example with a more simple acquire and run function
|
||||
|
||||
### Docs
|
||||
- update master
|
||||
|
||||
## [0.1.1] - 2025-07-04
|
||||
|
||||
### Added
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -645,7 +645,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "noleader"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
|
@@ -3,7 +3,7 @@ members = ["crates/*"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.1"
|
||||
version = "0.1.3"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
58
README.md
58
README.md
@@ -22,9 +22,9 @@ This library is still young and the API is subject to change.
|
||||
|
||||
## Intended use-case
|
||||
|
||||
Noleader is not built for distributed consensus, or fast re-election produces. It take upwards to a minute to get reelected, state is the users responsibility to handle.
|
||||
Noleader is not built for distributed consensus, or fast re-election procedures. It take upwards to a minute to get re-elected, state is the users responsibility to handle.
|
||||
|
||||
Noleader is pretty much just a distributed lock, intended for use-cases where the use wants to only have a single node scheduling work etc.
|
||||
Noleader is pretty much just a distributed lock, intended for use-cases where the user wants to only have a single node scheduling work etc.
|
||||
|
||||
Good alternatives are:
|
||||
|
||||
@@ -71,58 +71,40 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Ensure the KV bucket exists
|
||||
leader.create_bucket().await?;
|
||||
|
||||
// Spawn the election loop
|
||||
tokio::spawn({
|
||||
let leader = leader.clone();
|
||||
async move {
|
||||
// Attempts to acquire election loop, will call inner function if it wins, if it loses it will retry over again.
|
||||
// Will block until either the inner function returns and error, or the election processes crashes, intended to allow the application to properly restart
|
||||
leader
|
||||
.start(CancellationToken::default())
|
||||
.await
|
||||
.expect("leadership loop failed");
|
||||
}
|
||||
});
|
||||
.acquire_and_run({
|
||||
move |token| {
|
||||
let leader_id = leader_id.clone();
|
||||
|
||||
// Do work while we are the leader
|
||||
leader
|
||||
.do_while_leader(|cancel_token| async move {
|
||||
async move {
|
||||
loop {
|
||||
if cancel_token.is_cancelled() {
|
||||
break;
|
||||
if token.is_cancelled() {
|
||||
return Ok(());
|
||||
}
|
||||
tracing::info!("🔑 I am the leader—doing work");
|
||||
|
||||
tracing::info!(leader_id, "do work as leader");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## API Overview
|
||||
## Examples
|
||||
|
||||
* **`Leader::new(bucket: &str, key: &str, client: async_nats::Client) -> Leader`**
|
||||
Create a new election participant.
|
||||
* **`create_bucket(&self) -> anyhow::Result<()>`**
|
||||
Ensures the KV bucket exists (no-op if already created).
|
||||
* **`start(&self, token: CancellationToken) -> anyhow::Result<()>`**
|
||||
Begins the background leader-election loop; renews TTL on success or retries on failure.
|
||||
* **`do_while_leader<F, Fut>(&self, f: F) -> anyhow::Result<()>`**
|
||||
Runs your closure as long as you hold leadership; cancels immediately on loss.
|
||||
* **`leader_id(&self) -> Uuid`**
|
||||
Returns your unique candidate ID.
|
||||
* **`is_leader(&self) -> Status`**
|
||||
Returns `Status::Leader` or `Status::Candidate`, taking shutdown into account.
|
||||
See the examples folder in ./crates/noleader/examples
|
||||
|
||||
### Types
|
||||
## Architecture
|
||||
|
||||
Noleader uses a simple election stealing
|
||||
|
||||
```rust
|
||||
pub enum Status {
|
||||
Leader,
|
||||
Candidate,
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
@@ -1,7 +1,12 @@
|
||||
[package]
|
||||
name = "noleader"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
readme = "../../README.md"
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
repository = "https://git.front.kjuulh.io/kjuulh/noleader"
|
||||
authors = ["kjuulh <contact@kasperhermansen.com>"]
|
||||
description = "A small leader election package using NATS keyvalue store as the distributed locking mechanism. Does not require a min / max set of nodes"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
|
Reference in New Issue
Block a user