other(workflows): Run workflow detail prefetches on a dedicated concurrent queue#6916
Merged
Merged
Conversation
8fc769b to
e891d75
Compare
vegaro
approved these changes
Jun 8, 2026
Workflow prefetches previously serialized on the single backend OperationQueue (maxConcurrentOperationCount = 1), where each GetWorkflowOperation holds its slot through the whole CDN asset download. Add a dedicated workflows queue (cap 4) and route detail fetches (WorkflowsAPI.getWorkflow) to it so their CDN downloads overlap (up to 4 at a time). The list fetch stays on the serial queue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds testGetWorkflowDetailFetchesRunConcurrentlyUpToFour: fires six detail fetches and asserts exactly four cdnFetch closures are in flight at once and never more, proving the dedicated workflows queue both parallelizes and caps at four. The stub blocks on a background thread so MockHTTPClient keeps delivering envelopes on main. Verified non-vacuous via a serial-queue mutation (reaches 1, fails). Also rewords the routing test comment that wrongly claimed concurrency could not be observed here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses vegaro's review on #6916: - Route only prefetch detail fetches to the cap-4 workflows queue; on-demand getWorkflow stays on the serial queue (threads a prefetch flag through WorkflowManager.getWorkflow and WorkflowsAPI.getWorkflow). Keeps platform behavior aligned with Android, which only parallelizes the prefetch path. - Drop the redundant toNever(>4) assertion in the concurrency test; the final maxInFlight == 4 check is the authoritative cap assertion. - Document why the workflows queue intentionally keeps default QoS (no .background): prefetches gate offerings delivery. Tests: routing test now asserts prefetch -> workflows queue and on-demand/list -> serial queue; WorkflowManagerTests assert prefetch passes true and on-demand passes false. 51 workflow tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a5f4052 to
4776343
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.
Follow-up to the review on #6882 (#6882 (comment)), where we said parallelizing the workflow prefetches was worth doing but belonged in its own PR.
Right now every workflow detail fetch runs on the single backend
OperationQueue(maxConcurrentOperationCount = 1), andGetWorkflowOperationholds that one slot through the entire CDN asset download. So whenWorkflowManagerprefetches N workflows, their CDN downloads run strictly one after another, and since the list fetch gates offerings delivery, that latency is right on the critical path.This adds a dedicated workflows
OperationQueue(cap 4, matching Android'sCONCURRENT_BACKEND_CALLS/MAX_CONCURRENT_WORKFLOW_CDN_FETCHES) and routes the prefetch detail fetches to it. On-demandgetWorkflowand the list fetch (getWorkflows) stay on the serial queue. Because the operation holds its slot through the CDN download, a single cap-4 queue bounds both the concurrent envelope calls and the concurrent CDN downloads at 4.A couple of things worth knowing:
HTTPClient(that's its existingcurrentSerialRequestdesign, SDK-wide). What actually overlaps now is the CDN asset downloads, which escape that serial path (iOS 15+ viaFileRepositorytasks, pre-15 via the rawdataTaskinfetchRawData). That's the heavy part, so it's the win.getWorkflowon its default path. On-demand fetches and the list stay on the serial queue. Aprefetchflag threaded fromWorkflowManager.prefetchWorkflowsdown toWorkflowsAPI.getWorkflowselects the queue.The two-layer behavior (envelopes serialize, CDN downloads fan out) is the part most likely to be misread, so:
sequenceDiagram participant Q as Workflows queue (cap 4) participant H as HTTPClient (serial) participant CDN as CDN (FileRepository / dataTask) Note over Q: up to 4 prefetch operations run concurrently Q->>H: envelope A Q->>H: envelope B H-->>Q: A (serialized) H-->>Q: B (serialized) par overlap (the win) Q->>CDN: download A and Q->>CDN: download B end Note over Q: each op holds its slot until its CDN download finishesThere's a test (
testGetWorkflowDetailFetchesRunConcurrentlyUpToFour) that actually observes the concurrency: it fires six prefetch detail fetches and asserts exactly fourcdnFetchclosures are in flight at once and never more. The trick is that the operation holds its queue slot until thecdnFetchcompletion fires, so the stub blocks on a background thread (not main), which letsMockHTTPClientkeep delivering envelopes and the fetches genuinely overlap. Verified non-vacuous: routing the fetch back to the serial queue makes it reach only 1 and fail. A routing test (testPrefetchUsesWorkflowsQueueWhileOnDemandAndListUseSerialQueue) covers the split: prefetch lands on the workflows queue, on-demand and list on the serial queue.AI session context
AI Context
Metadata
facu/workflow-prefetch-parallel-queue)facu/workflow-prefetch-parallel-queueGoal
Parallelize workflow detail prefetches so their CDN asset downloads overlap instead of serializing on the SDK's single backend queue. Follow-up explicitly deferred from the #6882 review.
Initial Prompt
"Let's follow-up on #6882 (review)" — resolved to: implement the parallelization that vegaro's review (comment
3347636417) flagged and that the PR author deferred to a later PR.Important Follow-up Prompts
getWorkflowreverts to the serial queue; only prefetch uses the cap-4 queue.Agent Contribution
HTTPClient's internalcurrentSerialRequest(left untouched) and the cap-1OperationQueue+GetWorkflowOperationholding its slot through the CDN download (the part addressed).Backend.QueueProvider.createWorkflowsQueue()(cap 4), threadedworkflowsQueuethroughBackendConfiguration+Backend.init, added an optionalqueue:selector toaddCacheableOperation(defaults to the serial queue), and routedWorkflowsAPI.getWorkflowto the workflows queue.cdnFetchseam, and verified it is non-vacuous via a serial-queue mutation.Human Decisions
Key Implementation Decisions
getWorkflowand the list stay on the serial queue.prefetchflag (defaultfalse) is threaded fromWorkflowManager.prefetchWorkflows->WorkflowManager.getWorkflow->WorkflowsAPI.getWorkflow, which selects the queue.Files / Symbols Touched
Sources/Networking/Backend.swiftQueueProvider.createWorkflowsQueue(),maxConcurrentWorkflowOperations; wired intoBackend.init.Sources/Networking/BackendConfiguration.swiftworkflowsQueueproperty + init param;addCacheableOperation(..., queue:).Sources/Networking/WorkflowsAPI.swiftgetWorkflow(..., prefetch: Bool = false, ...)routes toworkflowsQueueonly whenprefetch == true, else the serial queue;getWorkflows(list) unchanged.Sources/Purchasing/WorkflowManager.swiftgetWorkflow(..., prefetch: Bool = false, ...)forwards the flag;prefetchWorkflowspassesprefetch: true.BaseBackendTest.swift,MockBackendConfiguration.swift,BasePurchasesTests.swift,BackendSubscriberAttributesTests.swift.MockWorkflowsAPIcaptures theprefetcharg.Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swifttestWorkflowsQueueAllowsFourConcurrentOperations,testPrefetchUsesWorkflowsQueueWhileOnDemandAndListUseSerialQueue,testGetWorkflowDetailFetchesRunConcurrentlyUpToFour.Tests/UnitTests/Purchasing/WorkflowManagerTests.swiftprefetch == trueand on-demand passesfalse.Dependencies / Config / Migrations
SystemInfo.workflowsEndpointEnabled.Validation
swift build: succeeded.xcodebuild test ... BackendGetWorkflowTests + WorkflowManagerTests + PurchasesWorkflowTests: 51 tests, 0 failures (after the prefetch-only change).testGetWorkflowDetailFetchesRunConcurrentlyUpToFourrun 3x in isolation: passed deterministically (~0.55s each), no flakiness.currentInFlightreaches 1, not 4), confirming it actually validates the cap. Reverted.Validation Gaps
cdnFetchseam (counting simultaneously in-flight closures), not the realFileRepository/URLSessiondownload path, and it does not modelFileRepository's same-URL coalescing (the stub ignores the URL; each fetch uses a distinctworkflowId). It proves the property this change owns (the workflows queue permits up to 4 concurrentcdnFetchinvocations and no more), not a measured end-to-end network speedup.-EnableWorkflowsEndpoint.CI-RevenueCatsuite not run locally (only the workflow/backend-adjacent subset).Review Focus
.background, unlike diagnostics) because prefetches gate offerings delivery, agree?Risks / Reviewer Notes
FileRepository.generateOrGetCachedFileURLcoalesces same-URL fetches via aJobKey-keyed in-flight task store, so they share one download.CallbackCacheaccess.CallbackCacheisAtomic-backed.WorkflowsCachefrom parallel completions.Atomiccontainers (cache(workflow:)uses.modify);WorkflowDetailProcessoris a statelessSendablewith only an immutablelet.Non-goals / Out of Scope
HTTPClient's internal request serialization.GetWorkflowOperationcompletion semantics.getWorkflow(left on the serial queue to match Android).Omitted Context
Note
Medium Risk
Changes internal networking concurrency for workflow prefetches/on-demand detail loads; bounded at 4 but overlaps CDN I/O and shares callback caches across concurrent operations.
Overview
Adds a dedicated RC Workflows Queue (
maxConcurrentOperationCount = 4) and wires it throughBackendConfigurationso workflow detail fetches no longer share the single serial backend queue.addCacheableOperationnow accepts an optionalqueue:(default remains the serialoperationQueue).WorkflowsAPI.getWorkflowenqueues onworkflowsQueueso multiple detail operations—and their CDN downloads while each operation holds its slot—can overlap up to four.getWorkflows(list) is unchanged on the serial queue.Test harnesses pass the new queue into
BackendConfiguration, with unit tests for queue settings, detail-vs-list routing, and a cap-4 concurrency check via stubbed CDN fetch.Reviewed by Cursor Bugbot for commit a5f4052. Bugbot is set up for automated code reviews on this repo. Configure here.