use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use tempfile::TempDir;
use super::manager::{ManagedError, ManagedTmuxDriver, SessionManager};
use super::pane_identity::PaneIdentity;
use super::record::{ManagedSessionId, ManagedSessionState};
use super::tests::{FakeTmuxDriver, bind_pane, seed_record};
const RENDEZVOUS: Duration = Duration::from_millis(500);
struct ByIdDriver {
inner: Arc<FakeTmuxDriver>,
current: Mutex<Option<String>>,
arrivals: Mutex<usize>,
arrived: Condvar,
}
impl ByIdDriver {
fn rendezvous(&self) {
let mut n = self.arrivals.lock().unwrap();
*n += 1;
self.arrived.notify_all();
let _ = self
.arrived
.wait_timeout_while(n, RENDEZVOUS, |n| *n < 2)
.unwrap();
}
}
impl ManagedTmuxDriver for ByIdDriver {
fn create_session(&self, name: &str, workdir: &str) -> Result<(), ManagedError> {
*self.current.lock().unwrap() = Some(name.to_owned());
self.inner.create_session(name, workdir)
}
fn kill_session(&self, name: &str) -> Result<(), ManagedError> {
self.inner.kill_session(name)
}
fn send_line(&self, name: &str, text: &str) -> Result<(), ManagedError> {
self.inner.send_line(name, text)
}
fn capture(&self, name: &str, lines: usize) -> Result<String, ManagedError> {
self.inner.capture(name, lines)
}
fn list_sessions(&self) -> Result<Vec<String>, ManagedError> {
self.inner.list_sessions()
}
fn pane_exists(&self, name: &str, pane_id: &str) -> bool {
self.inner.pane_exists(name, pane_id)
}
fn pane_identity(&self, pane_id: &str) -> Result<PaneIdentity, ManagedError> {
self.inner.pane_identity(pane_id)
}
fn rename_session_id(
&self,
_name: &str,
session_id: &str,
new: &str,
) -> Result<(), ManagedError> {
self.rendezvous();
let mut current = self.current.lock().unwrap();
let now = current.clone().unwrap_or_default();
self.inner.rename_session_id(&now, session_id, new)?;
*current = Some(new.to_owned());
Ok(())
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn two_concurrent_renames_of_one_live_record_leave_tmux_and_the_record_in_step() {
let dir = TempDir::new().unwrap();
let inner = FakeTmuxDriver::new();
*inner.pane_id_override.lock().unwrap() = Some("%1".into());
let driver = Arc::new(ByIdDriver {
inner: inner.clone(),
current: Mutex::new(None),
arrivals: Mutex::new(0),
arrived: Condvar::new(),
});
let mgr = Arc::new(
SessionManager::new(dir.path(), driver.clone())
.await
.expect("manager"),
);
let id = ManagedSessionId::new();
seed_record(&mgr, &dir, id, ManagedSessionState::Active, false).await;
bind_pane(&mgr, &id).await;
let (m1, m2) = (Arc::clone(&mgr), Arc::clone(&mgr));
let first = tokio::spawn(async move { m1.rename(&id, "tm-race-a").await });
let second = tokio::spawn(async move { m2.rename(&id, "tm-race-b").await });
let (r1, r2) = (first.await.expect("join"), second.await.expect("join"));
let record = mgr.get(&id).await.expect("get").tmux_name;
let live = inner.list_sessions().expect("list");
assert_eq!(
live,
vec![record.clone()],
"tmux holds {live:?} but the record says '{record}' (results: {r1:?} / {r2:?})"
);
assert!(r1.is_ok() && r2.is_ok(), "{r1:?} / {r2:?}");
}