fix(peer): drain streamed install senders after completion
A streamed install sender kept the original frame sink alive outside the producer task. After the producer sent Complete, or an Error for a provider failure, the forwarding loop still had a live mpsc sender in scope and waited forever for another frame. Move the sink into the producer so the channel closes when the producer exits. That lets the QUIC writer close, the request task return, and the outbound TransferGuard drop after successful streamed installs and provider-side failures. The peer-cli harness now keeps the outbound-transfer map it passes into the peer runtime and exposes per-game counts in status. S39 asserts that the source has no active outbound transfer for cnctw after the streamed install finishes, which catches the sender-side lifecycle leak that receiver-only assertions missed. The peer-cli README and scenario table document that status field and expectation. Test Plan: - just fmt - just test - just clippy - git diff --check - git diff --cached --check - python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S39 S40 --build-image - python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S41 S42 S43 S44 S45 S46 S47 Refs: NEXT_STEPS.md streamed install lifecycle hardening
This commit was merged in pull request #27.
This commit is contained in:
@@ -19,6 +19,7 @@ use lanspread_peer::{
|
||||
ExternalUnrarStreamProvider,
|
||||
InstallOperation,
|
||||
NoopStreamInstallProvider,
|
||||
OutboundTransfers,
|
||||
PeerCommand,
|
||||
PeerEvent,
|
||||
PeerGameDB,
|
||||
@@ -119,6 +120,7 @@ struct SharedState {
|
||||
state: RwLock<CliState>,
|
||||
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
||||
catalog: Arc<RwLock<GameCatalog>>,
|
||||
active_outbound_transfers: OutboundTransfers,
|
||||
notify: Notify,
|
||||
games_dir: PathBuf,
|
||||
state_dir: PathBuf,
|
||||
@@ -137,6 +139,7 @@ async fn main() -> eyre::Result<()> {
|
||||
let (tx_events, rx_events) = mpsc::unbounded_channel();
|
||||
let peer_game_db = Arc::new(RwLock::new(PeerGameDB::new()));
|
||||
let catalog = Arc::new(RwLock::new(catalog));
|
||||
let active_outbound_transfers: OutboundTransfers = Arc::new(RwLock::new(HashMap::new()));
|
||||
let unrar_for_streaming = args.unrar.clone().or_else(default_unrar_program);
|
||||
let unpacker: Arc<dyn lanspread_peer::Unpacker> = match args.unrar.clone() {
|
||||
Some(path) => Arc::new(ExternalUnrarUnpacker::new(path)),
|
||||
@@ -155,7 +158,7 @@ async fn main() -> eyre::Result<()> {
|
||||
catalog.clone(),
|
||||
PeerStartOptions {
|
||||
state_dir: Some(args.state_dir.clone()),
|
||||
active_outbound_transfers: None,
|
||||
active_outbound_transfers: Some(active_outbound_transfers.clone()),
|
||||
stream_install_provider: Some(stream_install_provider),
|
||||
},
|
||||
)?;
|
||||
@@ -165,6 +168,7 @@ async fn main() -> eyre::Result<()> {
|
||||
state: RwLock::new(CliState::default()),
|
||||
peer_game_db,
|
||||
catalog: catalog.clone(),
|
||||
active_outbound_transfers,
|
||||
notify: Notify::new(),
|
||||
games_dir: args.games_dir.clone(),
|
||||
state_dir: args.state_dir.clone(),
|
||||
@@ -313,12 +317,20 @@ async fn handle_command(
|
||||
async fn status(shared: &SharedState) -> eyre::Result<Value> {
|
||||
let state = shared.state.read().await;
|
||||
let peer_count = shared.peer_game_db.read().await.peer_snapshots().len();
|
||||
let active_outbound_transfers = {
|
||||
let active = shared.active_outbound_transfers.read().await;
|
||||
active
|
||||
.iter()
|
||||
.map(|(game_id, transfers)| (game_id.clone(), transfers.len()))
|
||||
.collect::<HashMap<_, _>>()
|
||||
};
|
||||
Ok(json!({
|
||||
"local_peer": state.local_peer.clone(),
|
||||
"peer_count": peer_count,
|
||||
"local_games": state.local_games.len(),
|
||||
"remote_games": state.remote_games.len(),
|
||||
"active_operations": active_operations_json(&state.active_operations),
|
||||
"active_outbound_transfers": active_outbound_transfers,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user