fix(swarm-authelia-bridge): lock the load-insert-publish sequence
This commit is contained in:
parent
d30f149338
commit
405ed85550
1 changed files with 20 additions and 0 deletions
|
|
@ -86,6 +86,17 @@ fn read_secret_file(path: &str) -> Result<String> {
|
||||||
struct AppState {
|
struct AppState {
|
||||||
config: Config,
|
config: Config,
|
||||||
http: reqwest::Client,
|
http: reqwest::Client,
|
||||||
|
/// Serializes `handle`'s load → insert → publish sequence. Without this,
|
||||||
|
/// two `EnsureAgentIdentity` requests landing close together (real: the
|
||||||
|
/// controller's job worker claims and spawns nodes without waiting for
|
||||||
|
/// each to finish, and `SwarmResourceKind` declares no resource dep
|
||||||
|
/// between two `CreateIdentity` jobs, so they run concurrently) both
|
||||||
|
/// `load_store` the same snapshot, both insert their own agent, and
|
||||||
|
/// whichever `publish`es second silently drops the first agent's entry —
|
||||||
|
/// the store is a plain file, not a database with its own concurrency
|
||||||
|
/// control. `tokio::sync::Mutex`, not `std`'s: held across the `.await`s
|
||||||
|
/// in `generate_password` and `publish`.
|
||||||
|
write_lock: tokio::sync::Mutex<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -101,6 +112,7 @@ async fn main() -> Result<()> {
|
||||||
let state = Arc::new(AppState {
|
let state = Arc::new(AppState {
|
||||||
config,
|
config,
|
||||||
http: reqwest::Client::new(),
|
http: reqwest::Client::new(),
|
||||||
|
write_lock: tokio::sync::Mutex::new(()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
|
@ -187,6 +199,14 @@ async fn authorize(state: &AppState, headers: &HeaderMap) -> Result<(), Response
|
||||||
async fn handle(state: &AppState, name: String) -> Result<BridgeResponse> {
|
async fn handle(state: &AppState, name: String) -> Result<BridgeResponse> {
|
||||||
store::validate_username(&name)?;
|
store::validate_username(&name)?;
|
||||||
let cfg = &state.config;
|
let cfg = &state.config;
|
||||||
|
|
||||||
|
// Held across the whole load → insert → publish sequence, not just the
|
||||||
|
// write — two concurrent `EnsureAgentIdentity` calls must not both read
|
||||||
|
// the same on-disk snapshot before either publishes. See the field's
|
||||||
|
// doc comment on `AppState::write_lock` for why this is reachable in
|
||||||
|
// practice, not just theoretically.
|
||||||
|
let _write_guard = state.write_lock.lock().await;
|
||||||
|
|
||||||
let mut user_store = store::load_store(&cfg.store_path, &cfg.users_file)?;
|
let mut user_store = store::load_store(&cfg.store_path, &cfg.users_file)?;
|
||||||
if user_store.users.contains_key(&name) {
|
if user_store.users.contains_key(&name) {
|
||||||
return Ok(BridgeResponse::AlreadyExists);
|
return Ok(BridgeResponse::AlreadyExists);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue