diff --git a/relay/src/message.rs b/relay/src/message.rs index 372f17e..58da534 100644 --- a/relay/src/message.rs +++ b/relay/src/message.rs @@ -73,7 +73,12 @@ pub(crate) enum FromTcpThreadMessage { Disconnected(String), /// A packet was sent to remote (In bytes) Sent(usize), - /// Error reported + /// Error reported. + /// + /// Nothing constructs this today: a failed write means the socket is gone, + /// so the TCP thread reports `Disconnected` and exits rather than emitting + /// a send error per dropped packet. Kept for a future transient-failure + /// case that can be reported without tearing the connection down. SendError(String), /// Mirror destination accepted the connection MirrorConnected(String), diff --git a/relay/src/state.rs b/relay/src/state.rs index 2b3f6e4..a1e6cc6 100644 --- a/relay/src/state.rs +++ b/relay/src/state.rs @@ -821,6 +821,9 @@ impl State { Ok(stream) => { human_log("TCP", "Successfully connected to iMotions server."); let _ = child_bichannel.set_is_conn_to_endpoint(true); + // Report it so the status line follows the real socket instead + // of waiting for a manual Check TCP Connection Status press. + let _ = child_bichannel.send_to_parent(FromTcpThreadMessage::Connected); stream } Err(e) => { @@ -856,16 +859,60 @@ impl State { } for packet in packets_for_fields(&fields) { + let bytes = packet.len(); let res = send_packet_and_debug(&mut stream, &packet); let _ = child_bichannel.set_is_conn_to_endpoint(res.is_ok()); + // Mirror regardless of the iMotions result: the mirror is + // a separate destination and should not lose a copy just + // because iMotions went away. mirror_send(&mut mirror, &packet, &mut child_bichannel); + + match res { + Ok(()) => { + // Report the write so the throughput block has + // something to count. + let _ = child_bichannel + .send_to_parent(FromTcpThreadMessage::Sent(bytes)); + } + Err(e) => { + // A write failure means the socket is gone, so stop + // at the first one and report it as a disconnect. + // Reporting each failed packet instead would write + // ~120 entries/sec to the event log and bury + // everything else in the GUI panel. + let reason = format!("TCP send failed: {}", e); + human_log("TCP", &reason); + // Same path #179 established for a failed connect: + // the GUI shows it and reap_tcp_thread clears the + // handle, so Connect TCP works again without + // pressing Disconnect TCP first. + let _ = child_bichannel.send_to_parent( + FromTcpThreadMessage::Disconnected(reason.clone()), + ); + bail!(reason); + } + } } } ToTcpThreadMessage::SendRaw(packet) => { - let res = send_packet_and_debug(&mut stream, &packet); - let _ = child_bichannel.set_is_conn_to_endpoint(res.is_ok()); - if let Err(e) = res { - human_log("TCP", &format!("Test packet failed: {}", e)); + match send_packet_and_debug(&mut stream, &packet) { + Ok(()) => { + let _ = child_bichannel.set_is_conn_to_endpoint(true); + // A test packet is still a packet on the wire, so it + // counts toward throughput like any other. + let _ = child_bichannel.send_to_parent( + FromTcpThreadMessage::Sent(packet.len()), + ); + } + Err(e) => { + let reason = format!("Test packet failed: {}", e); + human_log("TCP", &reason); + let _ = child_bichannel.set_is_conn_to_endpoint(false); + let _ = child_bichannel.send_to_parent( + FromTcpThreadMessage::Disconnected(reason.clone()), + ); + bail!(reason); + } } // The mirror gets the test packet too, so the button // checks both links at once. diff --git a/relay/src/update.rs b/relay/src/update.rs index b10ec37..0901e71 100644 --- a/relay/src/update.rs +++ b/relay/src/update.rs @@ -1012,6 +1012,49 @@ mod tests { ); } + #[test] + fn connecting_reports_status_without_a_manual_check() { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind failed"); + let addr = listener.local_addr().expect("local_addr failed").to_string(); + + let mut state = State::default(); + state.tcp_connect(addr).expect("tcp_connect failed"); + let (_stream, _) = listener.accept().expect("accept failed"); + + pump_update(&mut state, 50); + + // Before #181 the TCP thread never sent Connected, so this stayed false + // until Check TCP Connection Status was pressed. + assert!( + state.tcp_connected, + "status never flipped on its own: {:?}", + state.event_log + ); + + let _ = state.tcp_disconnect(); + } + + #[test] + fn sent_packets_make_the_throughput_block_appear() { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind failed"); + let addr = listener.local_addr().expect("local_addr failed").to_string(); + + let mut state = State::default(); + state.tcp_connect(addr).expect("tcp_connect failed"); + let (_stream, _) = listener.accept().expect("accept failed"); + assert!(wait_until(|| state.is_tcp_connected()), "TCP never connected"); + + let _ = update(&mut state, Message::SendPacket); + pump_update(&mut state, 50); + + // show_metrics gates metrics_block in view.rs. Nothing ever set it + // before #181, so the throughput indicator could never render. + assert!(state.packets_last_60s > 0, "no packets counted"); + assert!(state.show_metrics, "throughput block still hidden"); + + let _ = state.tcp_disconnect(); + } + #[test] fn send_packet_with_every_toggle_off_reports_an_error() { let mut state = State::default();