use super::StdioProvider;
use super::process::spawn_process;
use super::transport::{Lifecycle, reader_loop};
use crate::provider::{ErrorKind, ProviderError, ProviderResult};
use serde_json::Value;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
pub(super) fn backoff_for(restart_attempt: u32) -> Duration {
match restart_attempt {
1 => Duration::from_secs(1),
2 => Duration::from_secs(2),
3 => Duration::from_secs(5),
_ => Duration::from_secs(30),
}
}
impl StdioProvider {
pub(super) fn request(&self, method: &str, params: Value) -> ProviderResult<Value> {
self.ensure_alive()?;
self.exchange(method, params, true)
}
fn ensure_alive(&self) -> ProviderResult<()> {
let mut routing = self.shared.routing.lock();
let mut waited = false;
loop {
match routing.lifecycle {
Lifecycle::Alive => return Ok(()),
Lifecycle::Respawning => {
waited = true;
self.shared.changed.wait(&mut routing);
}
Lifecycle::Dead if waited => {
let reason = routing.restart_error.clone();
drop(routing);
return Err(match reason {
Some(why) => ProviderError::new(
ErrorKind::Provider,
format!("provider restart failed: {why}"),
),
None => ProviderError::new(
ErrorKind::Provider,
"provider restarting — try again",
),
});
}
Lifecycle::Dead => {
routing.lifecycle = Lifecycle::Respawning;
routing.restart_error = None;
let attempt = routing.restarts + 1;
drop(routing);
return self.rebuild(attempt);
}
}
}
}
fn rebuild(&self, attempt: u32) -> ProviderResult<()> {
let mut guard = RebuildGuard {
provider: self,
attempt,
armed: true,
};
std::thread::sleep(backoff_for(attempt));
if self.closed.load(Ordering::Acquire) {
let err = ProviderError::other("provider dropped during restart");
guard.armed = false;
self.finish_rebuild(attempt, Err(&err));
return Err(err);
}
if let Some(handle) = self.reader.lock().take() {
let _ = handle.join();
}
let env: Vec<(&str, &str)> = self
.env
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
let (process, stdout) = match spawn_process(&self.command, &env, self.stderr_mode) {
Ok(pair) => pair,
Err(e) => {
guard.armed = false;
self.finish_rebuild(attempt, Err(&e));
return Err(e);
}
};
let reader = std::thread::spawn({
let shared = Arc::clone(&self.shared);
move || reader_loop(stdout, shared)
});
*self.process.lock() = process;
*self.reader.lock() = Some(reader);
let proof = self.handshake();
if proof.is_ok() {
*self.notice.lock() = Some(format!(
"provider restarted (attempt {attempt}, backoff {}s)",
backoff_for(attempt).as_secs()
));
}
let outcome: Result<(), &ProviderError> = proof.as_ref().map(|_| ());
guard.armed = false;
self.finish_rebuild(attempt, outcome);
proof.map(|_| ())
}
fn finish_rebuild(&self, attempt: u32, outcome: Result<(), &ProviderError>) {
let mut routing = self.shared.routing.lock();
match outcome {
Ok(()) => {
routing.lifecycle = Lifecycle::Alive;
routing.restarts = attempt;
routing.restart_error = None;
}
Err(e) => {
routing.lifecycle = Lifecycle::Dead;
routing.restart_error = Some(e.message.clone());
}
}
drop(routing);
self.shared.changed.notify_all();
match outcome {
Ok(()) => *self.failure_noticed.lock() = false,
Err(e) => {
let mut noticed = self.failure_noticed.lock();
if !*noticed {
*noticed = true;
*self.notice.lock() = Some(format!(
"provider keeps failing to restart ({}) — running without it",
e.message
));
}
}
}
}
}
struct RebuildGuard<'a> {
provider: &'a StdioProvider,
attempt: u32,
armed: bool,
}
impl Drop for RebuildGuard<'_> {
fn drop(&mut self) {
if self.armed {
let err = ProviderError::other("rebuild aborted unexpectedly");
self.provider.finish_rebuild(self.attempt, Err(&err));
}
}
}
#[cfg(test)]
mod tests;