Skip to content

Commit

Permalink
Allow for non-blocking connection accept for inspector server. (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
iddm authored Oct 16, 2023
1 parent b9c1240 commit f035f9e
Showing 1 changed file with 150 additions and 5 deletions.
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();
// 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.");
}
}

0 comments on commit f035f9e

Please sign in to comment.