Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for non-blocking connection accept for inspector server. #62

Merged
merged 18 commits into from
Oct 16, 2023
155 changes: 150 additions & 5 deletions src/v8/inspector/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,37 @@ impl TcpServer {
self.server.local_addr()
}

/// Starts listening for and a new single websocket connection.
/// Starts listening for a new single websocket connection.
/// The socket attempts to accept a connection in a non-blocking
/// mode, meaning it would return [`std::io::ErrorKind::WouldBlock`]
/// in case there is no user connection.
///
/// Once the connection is accepted, it is returned to the user.
pub fn try_accept_next_websocket_connection(
self,
) -> Result<WebSocketServer, (Self, std::io::Error)> {
if let Err(e) = self.server.set_nonblocking(true) {
return Err((self, e));
}

let connection = match self.server.accept() {
Ok(connection) => connection,
Err(e) => {
if let Err(e) = self.server.set_nonblocking(false) {
log::warn!(
"Couldn't unset the blocking flag for the inspector server socket: {e:#?}"
);
}
return Err((self, e));
}
};

tungstenite::accept(connection.0)
.map(WebSocketServer::from)
.map_err(|e| (self, std::io::Error::new(std::io::ErrorKind::Other, e)))
}

/// Starts listening for a new single websocket connection.
/// Once the connection is accepted, it is returned to the user.
pub fn accept_next_websocket_connection(self) -> Result<WebSocketServer, std::io::Error> {
let connection = self.server.accept()?;
Expand Down Expand Up @@ -724,7 +754,13 @@ mod tests {
};

use super::ClientMessage;
use std::sync::{Arc, Mutex};
use std::sync::{atomic::AtomicU16, Arc, Mutex};

fn generate_port() -> u16 {
static LAST_PORT_USED: AtomicU16 = AtomicU16::new(9006u16);

LAST_PORT_USED.fetch_add(1, std::sync::atomic::Ordering::AcqRel)
}

/// This is to test the crash when setting a breakpoint.
/// It:
Expand Down Expand Up @@ -761,13 +797,13 @@ mod tests {
let lock_1 = stage_1.lock().unwrap();

// The remote debugging server port for the [WebSocketServer].
const PORT_V4: u16 = 9005;
let port = generate_port();
MeirShpilraien marked this conversation as resolved.
Show resolved Hide resolved
// The remote debugging server ip address for the [WebSocketServer].
const IP_V4: std::net::Ipv4Addr = std::net::Ipv4Addr::LOCALHOST;
// The full remote debugging server host name for the [WebSocketServer].
const LOCAL_HOST: std::net::SocketAddrV4 = std::net::SocketAddrV4::new(IP_V4, PORT_V4);
let host: std::net::SocketAddrV4 = std::net::SocketAddrV4::new(IP_V4, port);

let address = LOCAL_HOST.to_string();
let address = host.to_string();
let address = &address;

let fake_client = {
Expand Down Expand Up @@ -879,4 +915,113 @@ mod tests {
let res_utf8 = res.to_utf8().unwrap();
assert_eq!(res_utf8.as_str(), "2");
}

/// Tests that there is no timeout waiting for the connection, if
/// the client connection is attempted.
/// doesn't happen within the provided time limit. time limit.
#[test]
fn connection_accept_doesnt_timeout() {
use std::net::TcpStream;
use tungstenite::{handshake::server::NoCallback, HandshakeError, ServerHandshake};

// The remote debugging server port for the [WebSocketServer].
let port = generate_port();
// The remote debugging server ip address for the [WebSocketServer].
const IP_V4: std::net::Ipv4Addr = std::net::Ipv4Addr::LOCALHOST;
// The full remote debugging server host name for the [WebSocketServer].
let host: std::net::SocketAddrV4 = std::net::SocketAddrV4::new(IP_V4, port);

let address = host.to_string();
let address = &address;

// Let's create a server and start listening for the connections
// on the address provided, but not accepting those yet.
let mut server = TcpServer::new(address).expect("Couldn't create a tcp server");

let time_limit = std::time::Duration::from_millis(5000);
let mut current_waiting_time = std::time::Duration::ZERO;

let address = address.clone();

// The client thread, attempting to connect.
let client_thread =
std::thread::spawn(move || tungstenite::connect(format!("ws://{address}")));

// Now let's wait for the user to connect.
let _web_socket = 'accept_loop: loop {
let start_accepting_time = std::time::Instant::now();

match server.try_accept_next_websocket_connection() {
Ok(connection) => break 'accept_loop connection,
Err((s, e)) => {
if e.kind() != std::io::ErrorKind::WouldBlock {
assert_eq!(e.kind(), std::io::ErrorKind::Other);
assert!(e
.into_inner()
.unwrap()
.is::<HandshakeError<ServerHandshake<TcpStream, NoCallback>>>(),);

// When we reach here, we know that a connection
// has been attempted to be established, but was
// cut off due to us not receiving the WebSocket
// frames here, so we consider this a success.
let _ = client_thread.join().expect("Thread joined");
return;
}
server = s;
current_waiting_time += start_accepting_time.elapsed();

if current_waiting_time >= time_limit {
unreachable!("The connection is accepted.")
}
}
}
};

// By this time, the connection has been established, everything
// is good.
let _ = client_thread.join().expect("Thread joined");
}

/// Tests that there is a timeout waiting for the connection, if it
/// doesn't happen within the provided
#[test]
fn connection_accept_timesout() {
// The remote debugging server port for the [WebSocketServer].
let port = generate_port();
// The remote debugging server ip address for the [WebSocketServer].
const IP_V4: std::net::Ipv4Addr = std::net::Ipv4Addr::LOCALHOST;
// The full remote debugging server host name for the [WebSocketServer].
let host: std::net::SocketAddrV4 = std::net::SocketAddrV4::new(IP_V4, port);

let address = host.to_string();
let address = &address;

// Let's create a server and start listening for the connections
// on the address provided, but not accepting those yet.
let mut server = TcpServer::new(address).expect("Couldn't create a tcp server");

let time_limit = std::time::Duration::from_millis(1000);
let mut current_waiting_time = std::time::Duration::ZERO;

// Now let's wait for the user to connect.
let _web_socket = 'accept_loop: loop {
let start_accepting_time = std::time::Instant::now();

match server.try_accept_next_websocket_connection() {
Ok(connection) => break 'accept_loop connection,
Err((s, e)) => {
assert_eq!(e.kind(), std::io::ErrorKind::WouldBlock);
server = s;
current_waiting_time += start_accepting_time.elapsed();

if current_waiting_time >= time_limit {
return;
}
}
}
};

unreachable!("The connection is never accepted.");
}
}