Repository navigation
fix(dataplane): make the self-hosted environment worker path usable #2818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ | |
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
| import reactor.core.publisher.Mono; | ||
| import reactor.core.scheduler.Schedulers; | ||
|
|
||
| /** | ||
| * REST surface for out-of-process Environment Workers on {@code self_hosted} environments. | ||
|
|
@@ -76,19 +77,20 @@ public Mono<ResponseEntity<EnvironmentWorkQueue.WorkItem>> poll( | |
| @RequestParam("workerId") String workerId, | ||
| @RequestParam(name = "timeoutMs", defaultValue = "25000") long timeoutMs) { | ||
| return Mono.fromCallable( | ||
| () -> { | ||
| try { | ||
| Optional<EnvironmentWorkQueue.WorkItem> item = | ||
| workQueue.poll(environmentId, workerId, timeoutMs); | ||
| return item.map(this::withSessionMetadata) | ||
| .map(ResponseEntity::ok) | ||
| .orElseGet(() -> ResponseEntity.noContent().build()); | ||
| } catch (InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| throw new ResponseStatusException( | ||
| HttpStatus.SERVICE_UNAVAILABLE, "Poll interrupted"); | ||
| } | ||
| }); | ||
| () -> { | ||
| try { | ||
| Optional<EnvironmentWorkQueue.WorkItem> item = | ||
| workQueue.poll(environmentId, workerId, timeoutMs); | ||
| return item.map(this::withSessionMetadata) | ||
| .map(ResponseEntity::ok) | ||
| .orElseGet(() -> ResponseEntity.noContent().build()); | ||
| } catch (InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| throw new ResponseStatusException( | ||
| HttpStatus.SERVICE_UNAVAILABLE, "Poll interrupted"); | ||
| } | ||
| }) | ||
| .subscribeOn(Schedulers.boundedElastic()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| private EnvironmentWorkQueue.WorkItem withSessionMetadata(EnvironmentWorkQueue.WorkItem item) { | ||
|
|
@@ -109,15 +111,17 @@ public Mono<List<EnvironmentWorkQueue.WorkItem>> listWork( | |
| @RequestParam(value = "state", required = false) String state, | ||
| Authentication auth) { | ||
| requireUserAuth(auth); | ||
| return Mono.fromCallable(() -> workQueue.list(environmentId, state)); | ||
| return Mono.fromCallable(() -> workQueue.list(environmentId, state)) | ||
| .subscribeOn(Schedulers.boundedElastic()); | ||
| } | ||
|
|
||
| /** Returns per-status counts and oldest queued age for the environment. */ | ||
| @GetMapping("/work/stats") | ||
| public Mono<CoordinationStore.WorkStats> workStats( | ||
| @PathVariable("id") String environmentId, Authentication auth) { | ||
| requireUserAuth(auth); | ||
| return Mono.fromCallable(() -> workQueue.stats(environmentId)); | ||
| return Mono.fromCallable(() -> workQueue.stats(environmentId)) | ||
| .subscribeOn(Schedulers.boundedElastic()); | ||
| } | ||
|
|
||
| /** Returns a single work item by id. */ | ||
|
|
@@ -128,14 +132,15 @@ public Mono<EnvironmentWorkQueue.WorkItem> getWork( | |
| Authentication auth) { | ||
| requireUserAuth(auth); | ||
| return Mono.fromCallable( | ||
| () -> | ||
| workQueue | ||
| .get(workId) | ||
| .orElseThrow( | ||
| () -> | ||
| new ResponseStatusException( | ||
| HttpStatus.NOT_FOUND, | ||
| "Unknown work item: " + workId))); | ||
| () -> | ||
| workQueue | ||
| .get(workId) | ||
| .orElseThrow( | ||
| () -> | ||
| new ResponseStatusException( | ||
| HttpStatus.NOT_FOUND, | ||
| "Unknown work item: " + workId))) | ||
| .subscribeOn(Schedulers.boundedElastic()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The write-side endpoints in this controller are still on the event loop: |
||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This moves the handler bodies off the event loop, but failure #1 in the description (401 on every worker request) lives upstream of them and is not touched here:
EnvironmentKeyAuthFilter.filtercallsmatchesApiKey->apiKeyVerifier.matcheseagerly, and the bound method isControlPlaneClient::verifyEnvironmentKey, which still ends inbodyToMono(Map.class).block()(ControlPlaneClient.java:496-511). A WebFluxWebFilterruns on the Netty event loop, so thatblock()still throwsblock()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-epoll-N, the catch swallows it asreturn false, and the filter falls through without settingenv:<id>— the controller then 401s before any of thesesubscribeOnoperators are reached. The PR's file list is only these two controllers, so no worker route can actually authenticate yet. Is a second commit (making the filter reactive, e.g.Mono.fromCallable(...).subscribeOn(boundedElastic())around the verification, or a reactiveverifyEnvironmentKey) missing from the push?