Skip to content

Commit 63e2539

Browse files
authored
Merge pull request #164 from kernel/hypeship/direct-vm-fs-logs
Route browser fs and logs endpoints directly to the VM
2 parents ecaa457 + 94c0e23 commit 63e2539

7 files changed

Lines changed: 1895 additions & 46 deletions

File tree

src/kernel/_base_client.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,9 @@ def _calculate_retry_timeout(
772772
timeout = sleep_seconds * jitter
773773
return timeout if timeout >= 0 else 0
774774

775+
def _should_retry_on_connection_error(self, _request: httpx.Request) -> bool:
776+
return True
777+
775778
def _should_retry(self, response: httpx.Response) -> bool:
776779
# Note: this is not a standard header
777780
should_retry_header = response.headers.get("x-should-retry")
@@ -1012,7 +1015,7 @@ def request(
10121015
except httpx.TimeoutException as err:
10131016
log.debug("Encountered httpx.TimeoutException", exc_info=True)
10141017

1015-
if remaining_retries > 0:
1018+
if remaining_retries > 0 and self._should_retry_on_connection_error(request):
10161019
self._sleep_for_retry(
10171020
retries_taken=retries_taken,
10181021
max_retries=max_retries,
@@ -1026,7 +1029,7 @@ def request(
10261029
except Exception as err:
10271030
log.debug("Encountered Exception", exc_info=True)
10281031

1029-
if remaining_retries > 0:
1032+
if remaining_retries > 0 and self._should_retry_on_connection_error(request):
10301033
self._sleep_for_retry(
10311034
retries_taken=retries_taken,
10321035
max_retries=max_retries,
@@ -1596,7 +1599,7 @@ async def request(
15961599
except httpx.TimeoutException as err:
15971600
log.debug("Encountered httpx.TimeoutException", exc_info=True)
15981601

1599-
if remaining_retries > 0:
1602+
if remaining_retries > 0 and self._should_retry_on_connection_error(request):
16001603
await self._sleep_for_retry(
16011604
retries_taken=retries_taken,
16021605
max_retries=max_retries,
@@ -1610,7 +1613,7 @@ async def request(
16101613
except Exception as err:
16111614
log.debug("Encountered Exception", exc_info=True)
16121615

1613-
if remaining_retries > 0:
1616+
if remaining_retries > 0 and self._should_retry_on_connection_error(request):
16141617
await self._sleep_for_retry(
16151618
retries_taken=retries_taken,
16161619
max_retries=max_retries,

src/kernel/_client.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,18 @@
3838
from .lib.browser_routing.routing import (
3939
BrowserRouteCache,
4040
BrowserRoutingConfig,
41-
strip_direct_vm_auth,
41+
prepare_direct_vm_request,
4242
rewrite_direct_vm_options,
4343
browser_routing_config_from_env,
44+
install_direct_vm_auth_stripping,
45+
is_stale_direct_vm_auth_response,
4446
should_retry_stale_direct_vm_auth,
47+
install_stale_direct_vm_auth_eviction,
48+
install_async_direct_vm_auth_stripping,
4549
maybe_evict_browser_route_from_response,
50+
should_retry_direct_vm_connection_error,
51+
install_async_stale_direct_vm_auth_eviction,
52+
direct_vm_request_body_is_known_unreplayable,
4653
maybe_populate_browser_route_cache_from_response,
4754
)
4855

@@ -205,6 +212,8 @@ def __init__(
205212
)
206213
self.browser_route_cache = _browser_route_cache or BrowserRouteCache()
207214
self._browser_routing = browser_routing_config_from_env()
215+
install_direct_vm_auth_stripping(self._client)
216+
install_stale_direct_vm_auth_eviction(self._client)
208217

209218
@cached_property
210219
def deployments(self) -> DeploymentsResource:
@@ -369,13 +378,21 @@ def _prepare_options(self, options: Any) -> Any:
369378

370379
@override
371380
def _prepare_request(self, request: httpx.Request) -> None:
372-
strip_direct_vm_auth(request, cache=self.browser_route_cache)
381+
prepare_direct_vm_request(request, cache=self.browser_route_cache)
382+
383+
@override
384+
def _should_retry_on_connection_error(self, request: httpx.Request) -> bool:
385+
return should_retry_direct_vm_connection_error(request)
373386

374387
@override
375388
def _should_retry(self, response: httpx.Response) -> bool:
376-
if should_retry_stale_direct_vm_auth(response):
377-
maybe_evict_browser_route_from_response(response, cache=self.browser_route_cache)
378-
return True
389+
if direct_vm_request_body_is_known_unreplayable(response.request):
390+
return False
391+
if is_stale_direct_vm_auth_response(response):
392+
# The route was already evicted by the response hook; retry only when
393+
# the body can be rebuilt, otherwise the caller sees the original auth
394+
# failure and a later call goes to the control plane.
395+
return should_retry_stale_direct_vm_auth(response)
379396
return super()._should_retry(response)
380397

381398
@override
@@ -594,6 +611,8 @@ def __init__(
594611
)
595612
self.browser_route_cache = _browser_route_cache or BrowserRouteCache()
596613
self._browser_routing = browser_routing_config_from_env()
614+
install_async_direct_vm_auth_stripping(self._client)
615+
install_async_stale_direct_vm_auth_eviction(self._client)
597616

598617
@cached_property
599618
def deployments(self) -> AsyncDeploymentsResource:
@@ -758,13 +777,21 @@ async def _prepare_options(self, options: Any) -> Any:
758777

759778
@override
760779
async def _prepare_request(self, request: httpx.Request) -> None:
761-
strip_direct_vm_auth(request, cache=self.browser_route_cache)
780+
prepare_direct_vm_request(request, cache=self.browser_route_cache)
781+
782+
@override
783+
def _should_retry_on_connection_error(self, request: httpx.Request) -> bool:
784+
return should_retry_direct_vm_connection_error(request)
762785

763786
@override
764787
def _should_retry(self, response: httpx.Response) -> bool:
765-
if should_retry_stale_direct_vm_auth(response):
766-
maybe_evict_browser_route_from_response(response, cache=self.browser_route_cache)
767-
return True
788+
if direct_vm_request_body_is_known_unreplayable(response.request):
789+
return False
790+
if is_stale_direct_vm_auth_response(response):
791+
# The route was already evicted by the response hook; retry only when
792+
# the body can be rebuilt, otherwise the caller sees the original auth
793+
# failure and a later call goes to the control plane.
794+
return should_retry_stale_direct_vm_auth(response)
768795
return super()._should_retry(response)
769796

770797
@override

src/kernel/lib/browser_routing/raw_http.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import httpx
88

99
from .util import sanitize_curl_raw_params
10-
from .routing import BrowserRoute
10+
from .routing import BrowserRoute, mark_direct_vm_headers, direct_vm_request_extensions
1111
from ..._types import Body, Timeout, NotGiven, not_given
1212
from ..._models import FinalRequestOptions
1313

@@ -33,7 +33,7 @@ def request_via_browser_route(
3333
method=method.upper(),
3434
url=route.base_url.rstrip("/") + "/curl/raw",
3535
params=query,
36-
headers=headers or {},
36+
headers=mark_direct_vm_headers(headers),
3737
content=_normalize_binary_content(content),
3838
json_data=json,
3939
timeout=_normalize_timeout(timeout),
@@ -70,6 +70,7 @@ def stream_via_browser_route(
7070
headers=request_headers,
7171
content=_normalize_binary_content(content),
7272
timeout=_normalize_timeout(effective_timeout),
73+
extensions=direct_vm_request_extensions(cache=parent.browser_route_cache),
7374
) as response:
7475
yield response
7576

@@ -93,7 +94,7 @@ async def async_request_via_browser_route(
9394
method=method.upper(),
9495
url=route.base_url.rstrip("/") + "/curl/raw",
9596
params=query,
96-
headers=headers or {},
97+
headers=mark_direct_vm_headers(headers),
9798
content=_normalize_binary_content(content),
9899
json_data=json,
99100
timeout=_normalize_timeout(timeout),
@@ -130,6 +131,7 @@ async def async_stream_via_browser_route(
130131
headers=request_headers,
131132
content=_normalize_binary_content(content),
132133
timeout=_normalize_timeout(effective_timeout),
134+
extensions=direct_vm_request_extensions(cache=parent.browser_route_cache),
133135
) as response:
134136
yield response
135137

0 commit comments

Comments
 (0)