Conversation
XCFramework BuildThis PR's XCFramework is available for testing. Add the following to your .package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/669")Built from d14165b |
jkmassel
force-pushed
the
jkmassel/silent-listener-failure
branch
from
September 15, 2026 22:39
41650f3 to
bfe5cf7
Compare
jkmassel
force-pushed
the
jkmassel/silent-listener-failure
branch
from
September 16, 2026 19:41
bfe5cf7 to
5a88648
Compare
jkmassel
added this pull request to stack #690
September 17, 2026 18:33
`HTTPServer.start` nils `stateUpdateHandler` on `.ready` — load-bearing for the start race, since it stops yielding into the `AsyncStream` the start timeout races — and nothing replaced it. If a listener ever failed after a successful bind it left no trace anywhere, while the server went on reporting a `port` and `token` that addressed a socket nobody was listening on. Replace the handler instead of clearing it, and log a post-start `.failed` or `.waiting`. That is the whole change. We assume this never fires. No listener here has been observed dying after a successful bind, there is no issue or report behind it, and the one hypothesis that was tested — TN2277 socket reclamation while suspended — was disproven on device: 30 minutes suspended across four cycles, still accepting TCP. The other candidates fail the *bind* instead, before `.ready`, which `start(...)` already handles by throwing. This exists so the assumption is falsifiable rather than invisible. Deliberately best-effort, and deliberately log-only: - A state arriving in the gap between `.ready` and this assignment is still missed. Closing that needs a re-read of the live state on the listener's queue plus one-shot reporting to keep the two observers from doubling up — real complexity to never miss an event that has never happened. - Nothing acts on it. Withdrawing the advertised endpoint would be worse in both modes: normally the page's fallback to a direct `POST /wp/v2/media` *succeeds*, silently skipping a host `MediaProcessor` and putting GutenbergKit back on the network a `MediaUploader` host explicitly took it off; under Lockdown Mode it fails at the CORS check, where this loopback server's permissive CORS is the only route that still works. Left alone, the page keeps addressing the dead port and `nativeMediaUploadMiddleware` surfaces a `fetch_error` without retrying — already the right outcome. - `.cancelled` is not logged. `stop()` and `deinit` are its only causes, so it is the ordinary teardown of every server. The handler captures only the port, so it holds neither the server nor the listener and cannot form a cycle with either. No API change.
jkmassel
force-pushed
the
jkmassel/silent-listener-failure
branch
from
September 18, 2026 21:40
5a88648 to
d14165b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
HTTPServer.startnilsstateUpdateHandleron.readyand nothing replaces it, so a listener that failed after a successful bind left no trace anywhere — while the server went on reporting aportandtokenthat addressed a socket nobody was listening on.Replace the handler instead of clearing it, and log a post-start
.failedor.waiting. That's the whole change. No API change, no behaviour change.Why?
Mostly so an assumption becomes falsifiable.
We assume this never fires. There's no issue behind it, no report, no Sentry. The one hypothesis that was actually tested — TN2277 socket reclamation while suspended — was disproven on device: iPhone 15 Pro, 30.5 minutes suspended across four cycles with memory pressure from five foregrounded apps, listener still accepting TCP. The other candidates fail the bind instead, before
.ready, whichstart(...)already handles by throwing. Lockdown Mode was root-caused to CORS andOrigin: file://sanitization, not a listener kill.So this isn't a fix for an observed failure. It's the difference between "we assume it doesn't happen" and "we'd find out if we were wrong," for about ten lines.
Deliberately not doing
Not guaranteed. A state arriving in the gap between
.readybeing delivered and this assignment is still missed —.readyis delivered on the listener's queue and consumed off it, so for that stretch the start race's handler is still installed and whatever it yields goes into anAsyncStreamnothing reads any more. Closing that needs a re-read of the live state on the listener's queue plus one-shot reporting to stop the two observers doubling up. That's real complexity to never miss an event that has never happened; best-effort is the right trade for a diagnostic.Nothing acts on it. In particular the advertised endpoint is not withdrawn, because falling back to the WebView's default path is wrong in both modes:
tokenAuthMiddlewaresupplies theAuthorizationheader andallowUniversalAccessFromFileURLsexempts thefile://page from CORS, so a directPOST /wp/v2/medialands — silently skipping a hostMediaProcessorand putting GutenbergKit back on the network aMediaUploaderhost explicitly took it off ("with GutenbergKit out of the network entirely",MediaHandlers.swift:185-189). Nobody notices, because it worked.Left alone, the page keeps addressing the dead port and
nativeMediaUploadMiddlewaresurfaces afetch_errorwithout retrying. That's already the right outcome: an error the user can act on.Endpoint withdrawal does have a real trigger — a host calling
stopMediaHandling()deliberately — and that lives in #625, further down this stack, where it belongs..cancelledis not logged.stop()anddeinitare its only causes, so it's the ordinary teardown of every server and would be pure noise.How?
Captures only the port, so it holds neither the server nor the listener and cannot form a cycle with either.
Testing Instructions
There is nothing to reproduce by hand — that's the point of the PR — and nothing user-facing changes. The existing suites cover that replacing the handler doesn't disturb the start race:
swift test— 587GutenbergKitTests+ 396GutenbergKitHTTPTestsxcodebuild test— 597 + 396make lint-swiftcleanNo new tests. A log line with no observable behaviour doesn't warrant one, and asserting that a handler was installed would pin the implementation rather than anything real.
Related