Skip to content

Commit 4579151

Browse files
committed
feat(host): poll sockets during __hl_sleep for async networking
When networking is enabled, __hl_sleep now uses libc::poll() on all open host sockets instead of a plain sleep. This lets the guest's scheduler wake early when socket I/O is ready, enabling async networking with a single vCPU. Also adds net_poll hcall for non-blocking socket readiness checks. Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent c2092b3 commit 4579151

1 file changed

Lines changed: 135 additions & 6 deletions

File tree

host/src/lib.rs

Lines changed: 135 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,15 +1030,27 @@ fn register_internal_tools(
10301030
ec.store(code, Ordering::Relaxed);
10311031
Ok(serde_json::json!({}))
10321032
});
1033+
// Create socket table early so __hl_sleep can poll sockets while sleeping.
1034+
let socket_table = network.map(|_| Arc::new(Mutex::new(SocketTable::new())));
1035+
10331036
let sc = sleep_cancel.clone();
1037+
let st_for_sleep = socket_table.clone();
10341038
tools.register("__hl_sleep", move |args| {
10351039
let ns = args["ns"].as_u64().unwrap_or(0).min(MAX_SLEEP_NS);
10361040
if ns > 0 {
1041+
#[cfg(unix)]
1042+
if let Some(ref table) = st_for_sleep {
1043+
return hl_sleep_poll_sockets(&sc, table, ns);
1044+
}
10371045
sc.wait(Duration::from_nanos(ns));
10381046
}
10391047
Ok(serde_json::json!({}))
10401048
});
1041-
network.map(|policy| register_net_tools(tools, policy, listen_ports))
1049+
1050+
if let (Some(policy), Some(ref table)) = (network, &socket_table) {
1051+
register_net_tools(tools, policy, listen_ports, table.clone());
1052+
}
1053+
socket_table
10421054
}
10431055

10441056
// ---------------------------------------------------------------------------
@@ -1605,14 +1617,125 @@ fn handle_net_getsockname(
16051617
}
16061618
}
16071619

1620+
/// Poll host sockets for readiness using the real OS `poll()` syscall.
1621+
///
1622+
/// Input: `{"fds": [{"fd": N, "events": N}, ...], "timeout_ms": N}`
1623+
/// Output: `{"ready": [{"fd": N, "revents": N}, ...]}`
1624+
///
1625+
/// `events`/`revents` use standard POSIX poll flags (POLLIN=1, POLLOUT=4).
1626+
#[cfg(unix)]
1627+
fn handle_net_poll(
1628+
table: &Mutex<SocketTable>,
1629+
args: &serde_json::Value,
1630+
) -> Result<serde_json::Value> {
1631+
use serde_json::json;
1632+
use std::os::unix::io::AsRawFd;
1633+
1634+
let fds_val = args
1635+
.get("fds")
1636+
.and_then(|v| v.as_array())
1637+
.ok_or_else(|| anyhow!("net_poll: missing 'fds' array"))?;
1638+
let timeout_ms = args
1639+
.get("timeout_ms")
1640+
.and_then(|v| v.as_i64())
1641+
.unwrap_or(0) as libc::c_int;
1642+
1643+
let tbl = table.lock().unwrap();
1644+
let mut pollfds: Vec<libc::pollfd> = Vec::new();
1645+
let mut guest_fds: Vec<u64> = Vec::new();
1646+
1647+
for entry in fds_val {
1648+
let fd = entry
1649+
.get("fd")
1650+
.and_then(|v| v.as_u64())
1651+
.ok_or_else(|| anyhow!("net_poll: entry missing 'fd'"))?;
1652+
let events = entry
1653+
.get("events")
1654+
.and_then(|v| v.as_i64())
1655+
.unwrap_or(0) as i16;
1656+
if let Ok(sock) = tbl.get_socket(fd) {
1657+
pollfds.push(libc::pollfd {
1658+
fd: sock.as_raw_fd(),
1659+
events,
1660+
revents: 0,
1661+
});
1662+
guest_fds.push(fd);
1663+
}
1664+
}
1665+
drop(tbl);
1666+
1667+
if pollfds.is_empty() {
1668+
return Ok(json!({"ready": []}));
1669+
}
1670+
1671+
let _ret = unsafe {
1672+
libc::poll(
1673+
pollfds.as_mut_ptr(),
1674+
pollfds.len() as libc::nfds_t,
1675+
timeout_ms,
1676+
)
1677+
};
1678+
1679+
let mut ready = Vec::new();
1680+
for (i, pfd) in pollfds.iter().enumerate() {
1681+
if pfd.revents != 0 {
1682+
ready.push(json!({
1683+
"fd": guest_fds[i],
1684+
"revents": pfd.revents as i64,
1685+
}));
1686+
}
1687+
}
1688+
Ok(json!({"ready": ready}))
1689+
}
1690+
1691+
/// `__hl_sleep` variant: poll all sockets in the table while sleeping.
1692+
/// Returns early if any socket becomes ready.
1693+
#[cfg(unix)]
1694+
fn hl_sleep_poll_sockets(
1695+
sc: &SleepCancel,
1696+
table: &Mutex<SocketTable>,
1697+
ns: u64,
1698+
) -> Result<serde_json::Value> {
1699+
use serde_json::json;
1700+
use std::os::unix::io::AsRawFd;
1701+
1702+
let tbl = table.lock().unwrap();
1703+
let mut pollfds: Vec<libc::pollfd> = tbl
1704+
.sockets
1705+
.values()
1706+
.map(|hs| libc::pollfd {
1707+
fd: hs.socket.as_raw_fd(),
1708+
events: libc::POLLIN | libc::POLLOUT | libc::POLLERR,
1709+
revents: 0,
1710+
})
1711+
.collect();
1712+
drop(tbl);
1713+
1714+
if pollfds.is_empty() {
1715+
sc.wait(Duration::from_nanos(ns));
1716+
return Ok(json!({}));
1717+
}
1718+
1719+
let timeout_ms = ((ns / 1_000_000) as libc::c_int).max(1).min(30_000);
1720+
let ret = unsafe {
1721+
libc::poll(
1722+
pollfds.as_mut_ptr(),
1723+
pollfds.len() as libc::nfds_t,
1724+
timeout_ms,
1725+
)
1726+
};
1727+
1728+
Ok(json!({"socket_ready": ret > 0}))
1729+
}
1730+
16081731
// ---------------------------------------------------------------------------
16091732

16101733
fn register_net_tools(
16111734
tools: &mut ToolRegistry,
16121735
policy: &NetworkPolicy,
16131736
listen_ports: Option<&ListenPorts>,
1614-
) -> Arc<Mutex<SocketTable>> {
1615-
let table = Arc::new(Mutex::new(SocketTable::new()));
1737+
table: Arc<Mutex<SocketTable>>,
1738+
) {
16161739
let policy = Arc::new(policy.clone());
16171740

16181741
let t = table.clone();
@@ -1678,7 +1801,11 @@ fn register_net_tools(
16781801
handle_net_getsockname(&t, &args)
16791802
});
16801803

1681-
table
1804+
#[cfg(unix)]
1805+
{
1806+
let t = table.clone();
1807+
tools.register("net_poll", move |args| handle_net_poll(&t, &args));
1808+
}
16821809
}
16831810

16841811
/// Routes incoming fs_* tool calls to the matching `FsSandbox` by
@@ -3594,7 +3721,8 @@ mod tests {
35943721
fn net_getsockopt_returns_correct_type_for_dgram() {
35953722
let mut reg = ToolRegistry::new();
35963723
let policy = NetworkPolicy::AllowAll;
3597-
register_net_tools(&mut reg, &policy, None);
3724+
let table = Arc::new(Mutex::new(SocketTable::new()));
3725+
register_net_tools(&mut reg, &policy, None, table);
35983726

35993727
let req = br#"{"name":"net_socket","args":{"family":2,"type":2}}"#;
36003728
let resp = std::str::from_utf8(&reg.dispatch(req)).unwrap().to_string();
@@ -3695,7 +3823,8 @@ mod tests {
36953823

36963824
let mut reg = ToolRegistry::new();
36973825
let policy = NetworkPolicy::AllowAll;
3698-
register_net_tools(&mut reg, &policy, None);
3826+
let table = Arc::new(Mutex::new(SocketTable::new()));
3827+
register_net_tools(&mut reg, &policy, None, table);
36993828

37003829
// Create a socket
37013830
let req = br#"{"name":"net_socket","args":{"family":2,"type":2}}"#;

0 commit comments

Comments
 (0)