use std::io::Write;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use serde_json::{Value, json};
use tempfile::TempDir;
use super::super::*;
use super::{awaited, frame, subject, watching};
use crate::registry::presence::Presence;
use crate::test_support::wire::{EPHEMERAL, material, mint};
use crate::watch::NoRepaint;
use crate::wire::material::Role;
use crate::wire::server::{Answerer, Listener};
struct Holds {
release: Arc<AtomicUsize>,
served: Arc<AtomicUsize>,
}
impl Answerer for Holds {
fn answer(
&self,
_client: &crate::registry::Client,
request: Value,
) -> Box<dyn Iterator<Item = Value>> {
if request["op"] == "follow" {
let release = Arc::clone(&self.release);
return Box::new((0..=1).filter_map(move |i| {
while release.load(Ordering::Relaxed) <= i {
std::thread::yield_now();
}
(i == 0).then(|| frame("held, then said"))
}));
}
self.served.fetch_add(1, Ordering::Relaxed);
Box::new(std::iter::once(
json!({"ok": true, "kind": "balls", "rows": []}),
))
}
}
#[test]
fn a_held_read_does_not_delay_the_standing_set() {
let tmp = TempDir::new().expect("tmp");
mint(tmp.path());
let release = Arc::new(AtomicUsize::new(0));
let served = Arc::new(AtomicUsize::new(0));
let listener = Listener::bind(
&material(tmp.path(), Role::Server, EPHEMERAL),
Arc::new(Holds {
release: Arc::clone(&release),
served: Arc::clone(&served),
}),
Presence::default(),
)
.expect("bind");
let at = crate::wire::loopback(&listener.address());
let lane_seat =
crate::wire::client::Seat::open(&material(tmp.path(), Role::Window, &at)).expect("seat");
let asker_seat =
crate::wire::client::Seat::open(&material(tmp.path(), Role::Window, &at)).expect("seat");
let (mut tail, end) = pair();
watching(&mut tail);
let mut lane = Lane::new(lane_seat, end, Arc::new(NoRepaint));
let held = std::thread::spawn(move || lane.turn());
for _ in 0..3 {
assert!(
asker_seat.answered(&json!({"op": "balls"})).is_ok(),
"the serial pass is not behind the held read"
);
}
assert_eq!(served.load(Ordering::Relaxed), 3);
release.store(1, Ordering::Relaxed);
assert!(
awaited(&mut tail, "held, then said"),
"and the held frame lands when it is written"
);
release.store(2, Ordering::Relaxed);
assert!(held.join().expect("the lane's turn ends"));
}
#[test]
fn the_engine_holds_one_connection_across_every_growth_of_the_tail() {
use crate::boundary::tests::{agent, snapshot};
use crate::git_tree::AgentState;
let tmp = TempDir::new().expect("tmp");
let data = tmp.path().join("data");
let ws = crate::binding::workspace_path(&data, "alba");
std::fs::create_dir_all(ws.join("repo.git")).expect("the workspace marker");
let step = ws.join("steps").join("c-1").join("001");
std::fs::create_dir_all(&step).expect("step dir");
let file = step.join("response.json");
let cell = crate::state::new_snapshot_cell(Arc::new(snapshot(
&ws,
"alba",
vec![agent("c-1", AgentState::InFlight, 100)],
vec![],
)));
let state_root = tmp.path().join("state");
std::fs::create_dir_all(&state_root).expect("state root");
crate::registry::register(&state_root, &crate::registry::window(), "alba")
.expect("seat the window");
let intake =
crate::wire::intake::Intake::new(Arc::new(crate::boundary::consumer::ConsumerCtx {
yog_binary: std::path::PathBuf::from("/no/such/yog"),
world: crate::test_support::no_world(),
lernie: crate::cli_outbound::Cli::new("/no/such/lernie"),
bl: crate::cli_outbound::Cli::new("/no/such/bl"),
state_root: state_root.clone(),
home: std::path::PathBuf::from("/home/x"),
yog_data_root: data,
balls_state_root: tmp.path().join("balls"),
ui_path: state_root.join("ui.json"),
cell: Arc::clone(&cell),
presence: Presence::default(),
mailbox: crate::registry::mailbox::Mailbox::default(),
clock: Arc::new(crate::ui_state::SystemClock),
}));
mint(tmp.path());
let listener = Listener::bind(
&material(tmp.path(), Role::Server, EPHEMERAL),
Arc::new(intake) as Arc<dyn Answerer>,
Presence::default(),
)
.expect("bind");
let seat = crate::wire::client::Seat::open(&material(
tmp.path(),
Role::Window,
&crate::wire::loopback(&listener.address()),
))
.expect("seat");
assert!(
seat.ask(&json!({"op": "balls"})).expect("answered").len() == 1,
"every other query is still one frame and a terminator"
);
let writer = std::thread::spawn(move || {
for word in ["one ", "two ", "three"] {
let mut at = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&file)
.expect("append");
writeln!(
at,
"{{\"type\":\"content_delta\",\"index\":0,\
\"delta\":{{\"text_delta\":\"{word}\"}}}}"
)
.expect("write");
drop(at);
std::thread::sleep(std::time::Duration::from_millis(40));
}
crate::state::publish_snapshot(
&cell,
Arc::new(snapshot(
&ws,
"alba",
vec![agent("c-1", AgentState::Quiescent, 100)],
vec![],
)),
);
});
let mut said: Vec<String> = Vec::new();
seat.followed(&subject(), &mut |landed| {
if let Ok(crate::boundary::reply::Reply::Follow(stream)) = landed {
said.push(stream.text.unwrap_or_default());
}
true
})
.expect("the held read ends cleanly");
writer.join().expect("the writer finishes");
assert_eq!(
said.last().map(String::as_str),
Some("one two three"),
"the last frame carries the whole tail: {said:?}"
);
assert!(
said.len() > 1,
"and the connection was held across the growths rather than answering once: {said:?}"
);
}