@@ -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
16101733fn 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