Skip to content

Commit 8c72430

Browse files
committed
Skip proxy when virtual port is free
Benchmark (redis, 50 connections, 50K ops): Sandbox without port remap: ~70K rps (~10% overhead) Port remap before: ~42K rps (~46% overhead) Port remap after: ~71K rps (~10% overhead) Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent d66f877 commit 8c72430

File tree

1 file changed

+33
-3
lines changed

1 file changed

+33
-3
lines changed

src/sandlock/_port_remap.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,26 @@ class PortMap:
5757
def real_port(self, virtual: int, family: int = _AF_INET) -> int | None:
5858
"""Get or allocate the real port for a virtual port.
5959
60-
Allocates a free real port from the kernel on first use.
61-
If proxy=True, also starts listening on the virtual port.
60+
If the virtual port is free, reserves it and returns it unchanged
61+
(no rewrite, no proxy — the sandbox binds the port directly).
62+
Only allocates a different real port + proxy when the virtual port
63+
is already taken by another sandbox.
6264
Returns None if allocation fails.
6365
"""
6466
with self._lock:
6567
if virtual in self._virtual_to_real:
6668
return self._virtual_to_real[virtual]
6769

68-
# Ask the kernel for a free port
70+
# Fast path: try to reserve the virtual port itself.
71+
# If successful, the sandbox binds directly — no proxy needed.
72+
real = self._try_reserve_port(virtual, family)
73+
if real is not None:
74+
# virtual == real: _remap_sockaddr sees no change, skips rewrite
75+
self._virtual_to_real[virtual] = real
76+
self._real_to_virtual[real] = virtual
77+
return real
78+
79+
# Slow path: virtual port taken, allocate a different real port
6980
real = self._allocate_real_port(family)
7081
if real is None:
7182
return None
@@ -106,6 +117,25 @@ def close(self) -> None:
106117
self._proxy_sockets.clear()
107118
self._proxy_threads.clear()
108119

120+
def _try_reserve_port(self, port: int, family: int) -> int | None:
121+
"""Check if a port is available by probe-and-close.
122+
123+
Returns the port if free, None if already in use.
124+
No holder socket is kept — the sandbox binds the port directly.
125+
There is a tiny race window between the probe and the sandbox's
126+
bind(); if another process grabs the port, the sandbox gets
127+
EADDRINUSE, which is a normal error applications already handle.
128+
"""
129+
af = socket.AF_INET6 if family == _AF_INET6 else socket.AF_INET
130+
s = socket.socket(af, socket.SOCK_STREAM)
131+
try:
132+
s.bind(("::1" if af == socket.AF_INET6 else "127.0.0.1", port))
133+
return port
134+
except OSError:
135+
return None
136+
finally:
137+
s.close()
138+
109139
def _allocate_real_port(self, family: int) -> int | None:
110140
"""Bind a socket to port 0 to get a free port from the kernel."""
111141
try:

0 commit comments

Comments
 (0)