databendlabs/databend · error
invalid lease state
Error message
invalid lease state: {state} What it means
The cluster lease keeper tracks lease state in an atomic. After a renew failure, finish_renew_failure transitions the state: it accepts LEASE_ACQUIRED or an already-failed state, honors shutdown requests, and for any other state treats the internal state machine as broken, panicking via unreachable! with "invalid lease state".
Solutions
- Upgrade to a version where lease state transitions tolerate concurrent shutdown/delete states.
- Capture and report the actual state value in logs before restarting the node.
- Restart the query node to rebuild a clean lease state.
- Reduce concurrent shutdown/renew races by draining renewals before shutdown.
Example fix
// before
Err(state) => unreachable!("invalid lease state: {state}"),
// after
Err(state) => {
tracing::warn!("unexpected lease state {} after renew failure", state);
RenewResult::Failed(error)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before renewing, check the node is not shutting down and lease is healthy
if keeper.is_shutdown() || keeper.lease_state() == LEASE_DELETED {
return; // skip renew, rebuild lease
} Type guard
fn lease_state_valid(state: isize) -> bool {
state == LEASE_ACQUIRED || state == LEASE_RENEW_FAILED || state == LEASE_SHUTDOWN_REQUESTED
} Try / catch
match renew_result {
Ok(_) => {},
Err(e) if e.to_string().contains("invalid lease state") => {
// reinitialize the lease keeper on the node
}
Err(e) => return Err(e),
} Prevention
- Drain background renewal tasks before node shutdown
- Add tolerated states (LEASE_DELETED, LEASE_EXPIRED) to transition handlers
- Monitor meta-service connectivity to reduce renew-failure races
When it happens
Trigger: A concurrent shutdown/renew/delete changes the lease state between the compare_exchange and the error branch, leaving a state (e.g. LEASE_DELETED) that finish_renew_failure does not expect.
Common situations: Node shutdown or metadata (meta-service) disconnection racing with lease renewal; rapid connect/disconnect churn in clustered deployments.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- current_page has taken
- expect cluster.
- internal error: entered unreachable code
- rename_database: src (db) should exist
- _ => unreachable!()
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/b91093858ec79773.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/service/src/locks/lease_keeper.rs:200
}
fn is_retryable(error: &ErrorCode) -> bool {
matches!(
error.code(),
ErrorCode::META_SERVICE_ERROR | ErrorCode::TXN_RETRY_MAX_TIMES
)
}
fn finish_renew_failure(&self, error: ErrorCode) -> RenewResult {
match self.state.compare_exchange(
LEASE_RUNNING,
LEASE_RENEW_FAILED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) | Err(LEASE_RENEW_FAILED) => RenewResult::Failed(error),
Err(LEASE_SHUTDOWN_REQUESTED) => RenewResult::Shutdown,
Err(state) => unreachable!("invalid lease state: {state}"),
}
}
async fn try_delete<O: LeaseOps>(&self, max_retry_elapsed: Duration, ops: &O) -> Result<()> {
let mut backoff = set_backoff(
Some(Duration::from_millis(2)),
None,
Some(max_retry_elapsed),
);
loop {
match ops.delete().await {
Ok(()) => return Ok(()),
Err(error) if !Self::is_retryable(&error) => return Err(error),
Err(error) => {
let Some(delay) = backoff.next_backoff() else {
return Err(error);
};View on GitHub (pinned to 288d84d76e)