refactor(github): generalize property fetching, fix cancellation swallowing - #2828
Closed
PonceGL wants to merge 2 commits into
Closed
refactor(github): generalize property fetching, fix cancellation swallowing#2828PonceGL wants to merge 2 commits into
PonceGL wants to merge 2 commits into
Conversation
…lowing
GitHubAnnouncementPropertiesService only knew how to fetch the Play
Store announcement config. Split it into three pieces so any future
consumer (this repo's downloads feature, upstream or not) can read
its own properties file without knowing anything about Play Store
announcements:
- buildRawContentUrl: pure URL construction.
- fetchRawProperties(rawUrl): the actual HTTP GET + Properties
parsing, given any URL — not just github.com. This is what makes
the 404/error paths testable at all: fetchProperties() always
builds a real GitHub URL by design, so there is no seam to
intercept it in a unit test without hitting the network for real.
fetchRawProperties has none of that restriction, so tests point it
at a local com.sun.net.httpserver.HttpServer instead (JDK-included,
no new dependency).
- Properties.toPlayStoreAnnouncementRemoteConfig(): the mapping,
pulled out so it's testable directly against a hand-built
Properties with no network involved at all.
fetchPlayStoreAnnouncement() is now just fetchProperties(...).mapCatching { it.toPlayStoreAnnouncementRemoteConfig() }
— same signature, same defaults (including the stale
theovilardo/PixelPlay repo; that's a separate fix), same behavior on
200/404/error, verified with a characterization test written before
touching the mapping logic.
Also fixed: the original catch (e: Exception) silently turned a
cancelled coroutine into a Result.failure instead of letting
CancellationException propagate (AND-CONC-04). Worth being explicit
about what this does and doesn't achieve: HttpURLConnection is a
classic blocking-socket API that does not react to
Thread.interrupt(), which I confirmed empirically — neither
runInterruptible nor closing the connection from a cancellation
handler actually interrupts a call that's genuinely blocked in
getResponseCode(). So a cancellation that arrives while the network
call is in flight still has to wait for that call to finish on its
own (success, error, or its 10s timeout) before it can surface. What
the fix does guarantee: once a CancellationException does reach this
code, it is never swallowed. Making the call itself promptly
interruptible would mean moving off HttpURLConnection entirely
(OkHttp, e.g.) — out of scope here, noted in the KDoc for whoever
touches this file next.
10 new JVM tests, stable across repeated runs: the URL builder, the
mapping (including every boolean spelling and blank-vs-absent
values), fetchRawProperties against a real local server for
200/404/500/unreachable-host, and a deterministic cancellation test
(cancel before the call starts, rather than racing real socket
timing — which the empirical finding above shows doesn't actually
cancel anyway).
No consumer changes: MainActivity's fetchPlayStoreAnnouncement() call
is untouched.
Preparatory task for the downloads feature (P.9, cut from master —
generic, unrelated to that feature's UI).
Comments cited local-only planning files and rule IDs (docs/downloads/*.md, GEN-/AND- rule docs) that don't exist outside this machine and are meaningless to anyone reviewing the PR. Reworded to keep the same technical reasoning without the dangling references.
Author
|
Closing for now — reorganizing how this work is staged. It'll go through our fork first and we'll propose it upstream again, possibly bundled differently, once the larger feature it's part of is further along. Not a rejection, just a process change on our side. |
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
GitHubAnnouncementPropertiesService.fetchPlayStoreAnnouncement()mixed twoconcerns: the generic mechanics of reading a raw
.propertiesfile from aGitHub repo, and the Play Store announcement's own field mapping. Splitting
them lets other consumers (a remote feature-flag file, for instance) read
their own config without knowing anything about announcements. One of the
small independent fixes listed in #2813.
While generalizing it, found and fixed a real bug: the
catchwrappedExceptionbroadly, silently turning a coroutine cancellation into anordinary
Result.failureinstead of letting it propagate.Change
fetchProperties(owner, repo, branch, configPath): public, generic, nodefaults (a generic utility shouldn't default to any one caller's repo).
internalpieces:buildRawContentUrl(pure URLconstruction),
fetchRawProperties(rawUrl)(the actual HTTP call),Properties.toPlayStoreAnnouncementRemoteConfig()(the mapping).fetchProperties()itself always points at real GitHub with no way tointercept it in a test —
fetchRawPropertiesaccepts any URL, so testspoint it at a local
com.sun.net.httpserver.HttpServer(JDK, no newdependency) instead.
fetchPlayStoreAnnouncement()keeps its exact behavior and defaults(including the pre-rename repo name — that's a separate, already-open PR,
not this one).
catch (e: CancellationException) { throw e }before the broad catch —the actual fix.
Honest about what the cancellation fix does and doesn't guarantee:
HttpURLConnectionis a classic blocking-socket API and does not react toThread.interrupt()— verified empirically (triedrunInterruptibleand adisconnect()triggered frominvokeOnCompletion; neither achieves realcancellation of a call already blocked in
getResponseCode()). What thisfix guarantees is narrower but real: cancellation is never swallowed once
it does reach this code. Making the blocking call itself promptly
interruptible would mean replacing
HttpURLConnectionwith something thatsupports it (OkHttp) — out of scope here, noted in the code for whoever
touches this next.
Testing
10 JVM tests, stable across repeated runs. The cancellation test is
deterministic (cancel the coroutine before the call starts) rather than
timing-dependent, for the reason above — a during-flight cancellation test
would be flaky by construction against a blocking API that doesn't actually
interrupt.
assembleDebugsucceeds, full JVM baseline unaffected (5 pre-existingfailures, none new).