Skip to content

Commit fa0d7bb

Browse files
committed
feat(host): add Windows socket polling via WSAPoll
Implement hl_sleep_poll_sockets and handle_net_poll for Windows using WSAPoll from winsock2, matching the Unix libc::poll() implementations. This enables HTTP server examples (go-http, dotnet-http) to run on Windows by waking the guest when socket I/O arrives. Restores go-http and dotnet-http in Windows CI matrices. Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent f531f19 commit fa0d7bb

3 files changed

Lines changed: 122 additions & 8 deletions

File tree

.github/workflows/test-examples.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,8 @@ jobs:
448448
- python-agent-driver
449449
- powershell
450450
- networking-py
451-
# go-http and dotnet-http excluded: no Windows runtime test for them
451+
- go-http
452+
- dotnet-http
452453
steps:
453454
- uses: actions/checkout@v4
454455

@@ -600,8 +601,14 @@ jobs:
600601
- example: networking-py
601602
args: "--port 8080 -- /echo_server_test.py"
602603
expect: "SUCCESS: bind\\+listen on port 8080 allowed"
603-
# go-http and dotnet-http are excluded from Windows: they need
604-
# hl_sleep_poll_sockets (Unix-only) to wake the guest on I/O.
604+
- example: go-http
605+
args: "--port 8080 -- /bin/server"
606+
expect: "Hello from Hyperlight-Unikraft!"
607+
http_port: "8080"
608+
- example: dotnet-http
609+
args: "--port 8080 -- /app/KestrelHyperlight"
610+
expect: "Hello from Kestrel on Hyperlight!"
611+
http_port: "8080"
605612
steps:
606613
- uses: actions/checkout@v4
607614

host/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ nix = { version = "0.29", features = ["fs"] }
4444
libc = "0.2"
4545

4646
[target.'cfg(windows)'.dependencies]
47-
windows-sys = { version = "0.61", features = ["Win32_System_IO", "Win32_System_Ioctl", "Win32_Storage_FileSystem"] }
47+
windows-sys = { version = "0.61", features = ["Win32_System_IO", "Win32_System_Ioctl", "Win32_Storage_FileSystem", "Win32_Networking_WinSock"] }
4848

host/src/lib.rs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,6 @@ fn register_internal_tools(
10381038
tools.register("__hl_sleep", move |args| {
10391039
let ns = args["ns"].as_u64().unwrap_or(0).min(MAX_SLEEP_NS);
10401040
if ns > 0 {
1041-
#[cfg(unix)]
10421041
if let Some(ref table) = st_for_sleep {
10431042
return hl_sleep_poll_sockets(&sc, table, ns);
10441043
}
@@ -1699,11 +1698,80 @@ fn handle_net_poll(
16991698
Ok(json!({"ready": ready}))
17001699
}
17011700

1701+
#[cfg(windows)]
1702+
fn handle_net_poll(
1703+
table: &Mutex<SocketTable>,
1704+
args: &serde_json::Value,
1705+
) -> Result<serde_json::Value> {
1706+
use serde_json::json;
1707+
use std::os::windows::io::AsRawSocket;
1708+
use windows_sys::Win32::Networking::WinSock::{WSAPoll, POLLNVAL, WSAPOLLFD};
1709+
1710+
let fds_val = args
1711+
.get("fds")
1712+
.and_then(|v| v.as_array())
1713+
.ok_or_else(|| anyhow!("net_poll: missing 'fds' array"))?;
1714+
let timeout_ms = args
1715+
.get("timeout_ms")
1716+
.and_then(|v| v.as_i64())
1717+
.unwrap_or(0)
1718+
.clamp(0, i32::MAX as i64) as i32;
1719+
1720+
let tbl = table.lock().unwrap();
1721+
let mut pollfds: Vec<WSAPOLLFD> = Vec::new();
1722+
let mut guest_fds: Vec<u64> = Vec::new();
1723+
let mut ready = Vec::new();
1724+
1725+
for entry in fds_val {
1726+
let fd = entry
1727+
.get("fd")
1728+
.and_then(|v| v.as_u64())
1729+
.ok_or_else(|| anyhow!("net_poll: entry missing 'fd'"))?;
1730+
let raw_events = entry.get("events").and_then(|v| v.as_i64()).unwrap_or(0);
1731+
if !(0..=i16::MAX as i64).contains(&raw_events) {
1732+
return Err(anyhow!("net_poll: events {raw_events} out of i16 range"));
1733+
}
1734+
let events = raw_events as i16;
1735+
if let Ok(sock) = tbl.get_socket(fd) {
1736+
pollfds.push(WSAPOLLFD {
1737+
fd: sock.as_raw_socket() as usize,
1738+
events,
1739+
revents: 0,
1740+
});
1741+
guest_fds.push(fd);
1742+
} else {
1743+
ready.push(json!({ "fd": fd, "revents": POLLNVAL as i64 }));
1744+
}
1745+
}
1746+
drop(tbl);
1747+
1748+
if pollfds.is_empty() {
1749+
return Ok(json!({"ready": ready}));
1750+
}
1751+
1752+
let ret = unsafe { WSAPoll(pollfds.as_mut_ptr(), pollfds.len() as u32, timeout_ms) };
1753+
1754+
if ret < 0 {
1755+
let err = std::io::Error::last_os_error();
1756+
return Err(anyhow!("net_poll: WSAPoll() failed: {err}"));
1757+
}
1758+
1759+
for (i, pfd) in pollfds.iter().enumerate() {
1760+
if pfd.revents != 0 {
1761+
ready.push(json!({
1762+
"fd": guest_fds[i],
1763+
"revents": pfd.revents as i64,
1764+
}));
1765+
}
1766+
}
1767+
Ok(json!({"ready": ready}))
1768+
}
1769+
17021770
/// `__hl_sleep` variant: poll all sockets in the table while sleeping.
17031771
/// Returns early if any socket becomes ready.
17041772
#[cfg(unix)]
17051773
fn hl_sleep_poll_sockets(
1706-
sc: &SleepCancel,
1774+
_sc: &SleepCancel,
17071775
table: &Mutex<SocketTable>,
17081776
ns: u64,
17091777
) -> Result<serde_json::Value> {
@@ -1723,7 +1791,7 @@ fn hl_sleep_poll_sockets(
17231791
drop(tbl);
17241792

17251793
if pollfds.is_empty() {
1726-
sc.wait(Duration::from_nanos(ns));
1794+
_sc.wait(Duration::from_nanos(ns));
17271795
return Ok(json!({}));
17281796
}
17291797

@@ -1746,6 +1814,46 @@ fn hl_sleep_poll_sockets(
17461814
Ok(json!({"socket_ready": ret > 0}))
17471815
}
17481816

1817+
#[cfg(windows)]
1818+
fn hl_sleep_poll_sockets(
1819+
_sc: &SleepCancel,
1820+
table: &Mutex<SocketTable>,
1821+
ns: u64,
1822+
) -> Result<serde_json::Value> {
1823+
use serde_json::json;
1824+
use std::os::windows::io::AsRawSocket;
1825+
use windows_sys::Win32::Networking::WinSock::{
1826+
WSAPoll, POLLERR, POLLRDNORM, POLLWRNORM, WSAPOLLFD,
1827+
};
1828+
1829+
let tbl = table.lock().unwrap();
1830+
let mut pollfds: Vec<WSAPOLLFD> = tbl
1831+
.sockets
1832+
.values()
1833+
.map(|hs| WSAPOLLFD {
1834+
fd: hs.socket.as_raw_socket() as usize,
1835+
events: (POLLRDNORM | POLLWRNORM | POLLERR) as i16,
1836+
revents: 0,
1837+
})
1838+
.collect();
1839+
drop(tbl);
1840+
1841+
if pollfds.is_empty() {
1842+
_sc.wait(Duration::from_nanos(ns));
1843+
return Ok(json!({}));
1844+
}
1845+
1846+
let timeout_ms = ((ns / 1_000_000) as i32).clamp(1, 30_000);
1847+
let ret = unsafe { WSAPoll(pollfds.as_mut_ptr(), pollfds.len() as u32, timeout_ms) };
1848+
1849+
if ret < 0 {
1850+
let err = std::io::Error::last_os_error();
1851+
return Err(anyhow!("hl_sleep WSAPoll failed: {err}"));
1852+
}
1853+
1854+
Ok(json!({"socket_ready": ret > 0}))
1855+
}
1856+
17491857
// ---------------------------------------------------------------------------
17501858

17511859
fn register_net_tools(
@@ -1819,7 +1927,6 @@ fn register_net_tools(
18191927
handle_net_getsockname(&t, &args)
18201928
});
18211929

1822-
#[cfg(unix)]
18231930
{
18241931
let t = table.clone();
18251932
tools.register("net_poll", move |args| handle_net_poll(&t, &args));

0 commit comments

Comments
 (0)