fix: merge main into workstation/swift-migration and port pending concurrency fixes - #983
Conversation
feat(network): accept native GoDaddy R1 certificate chains Add GoDaddy TLS Root CA - R1 to the default certificate set while retaining all existing trust anchors. Verify the embedded certificate against GoDaddy's published SHA-256 fingerprint to prevent accidental pin changes. #agentic
chore: (release) 9.4.1 Updates version to 9.4.1 across the mParticle ecosystem.
release-ecosystem-from-main.yml is registered in Actions as disabled_manually but its file is not on main at all - only on unmerged branches - so describing it as a workflow file in the tree that never runs was wrong. The real trap is that the workflow list and the tree disagree in both directions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Observed on this branch: the build-kits Pod Lint jobs fail as a batch on a CocoaPods CDN error, and a cancelled job is a timeout-minutes expiry or a superseded push under pull-request.yml's cancel-in-progress concurrency group - not a test result. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5 to 6. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](actions/setup-java@v5...v6) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* ci: stop simulator cloning and bound network/test hangs Two unrelated hangs were holding up CI. Both were unbounded, so they consumed a job's full budget instead of failing. native-tests: the iOS x mParticle-Apple-SDK-Swift cell repeatedly hit its 15-minute timeout. A single test, MPDeviceTests.testDictionaryDescription, took 156-459s across recent runs while every other test finished in under 1.6s; job duration tracked that test exactly. The same test takes 0.069s on tvOS in the same run, so the cause is simulator state, not the assertions. MPDevice reads UIDevice.current for name and identifierForVendor, which are XPC calls into simulator daemons, and mParticle-Apple-SDK-Swift.xcscheme is the only scheme in the repo with parallelizable="YES" - so xcodebuild clones and boots extra iOS simulators on a runner that just erased one, starving the daemons that test then blocks on. Disable parallel testing for the run. The suite is ~2s of tests; locally this is 2x faster end to end (33s vs 74s) because it no longer boots 8 simulator clones. Also wait for a real boot via simctl bootstatus - the action's wait_for_boot only waits for state=Booted, not for first-boot work to finish - and add a per-test timeout so a hung test fails in 2 minutes with an explicit diagnostic instead of silently eating the job timeout. The slowest genuine test on CI is ~30s, leaving 4x headroom. build-kits: Build rokt-sdk-plus-ios stalled 30 minutes inside a git fetch of stripe-ios (2.7 GB, reached transitively via rokt-payment-extension-ios), leaving orphaned git and git-remote-http processes. The job has no timeout-minutes, so only an unrelated concurrency cancel stopped it; otherwise it would have held a macOS runner for the 6-hour default. Make git abort a transfer stalled below 1 KB/s for 3 minutes, retry the resolve three times (matching the existing pod-lint-kits pattern), and add job timeouts so no hang here can run unbounded again. Healthy resolves take 2-5 minutes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: bound each SPM resolve attempt with a kill-and-retry watchdog The first version of this fix relied on git's own low-speed abort to break a stalled fetch. It does not fire: on run 33010942238 the stripe-ios fetch sat for 24.5 minutes with http.lowSpeedLimit/lowSpeedTime set, produced no error, and never returned - so the retry loop never got a turn and the step burned its whole 25-minute budget before failing. Bound each attempt explicitly instead. A watchdog kills the resolve and its surviving git children once an attempt passes ATTEMPT_TIMEOUT_SECONDS (480s, against a healthy 2-5 minute resolve), so a stalled attempt is retried rather than consuming the step. Drop to 2 attempts and a 20-minute step timeout, since an attempt can no longer run unbounded. Keep the git low-speed config: it is not sufficient on its own but still aborts genuinely slow transfers where it does apply. Verified against the extracted step body with a stubbed resolver: a healthy resolve exits 0 in 1s with no kills; a permanent hang is killed twice and fails bounded; a hang followed by a healthy retry is killed once and exits 0. Defaulted the timeout in-place so an unset variable cannot make the watchdog fire immediately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: run the resolve watchdog cleanup in the parent so it actually executes Bugbot caught that the watchdog's cleanup was dead code. The watchdog TERMed the resolve subshell and then slept 5s before escalating, but TERMing the resolve unblocks the parent's wait immediately, and the parent then kills the watchdog mid-sleep - so the kill -KILL and the pkill of leftover xcodebuild and git-remote-http children never ran. The retry could therefore start while the previous stalled fetch was still alive and race it over SwiftPM's cache locks, which is the exact failure the watchdog was added to prevent. The watchdog now only records that it fired (via a marker file) and sends TERM. Escalation and child cleanup run in the parent after wait returns, where they cannot be pre-empted, followed by a short grace period before the retry. Verified by extracting the step body, stubbing the resolver and replacing the pkill calls with probes: on the old code the cleanup probes fired 0 times out of 4 expected; they now fire 4/4 on a permanent hang and 2/2 when one attempt hangs and the retry succeeds. Cleanup still does not run on a non-timeout failure (0 probes), so a fast failure does not trigger a spurious pkill, and the healthy path is unchanged at exit 0 with no kills. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…inistic (#870) testSwitchWorkspaceKitsWithStop waited on two nested 10-second dispatch_after blocks, so it always burned at least 20 seconds of wall clock and depended on fixed timing rather than on the SDK actually being ready. Wait on the SDK's own readiness signals instead - MParticle.initialized after startWithOptions:, and the replacement shared instance after switchWorkspaceWithOptions: - by spinning the main run loop until the condition holds. The assertions are unchanged, so the test still checks the same behaviour; it now takes 0.11s instead of 20.2s. testActiveKitsRegistryThreadSafety tracked failure in a plain __block BOOL written from four concurrent blocks, which is itself an unsynchronized data race, and called XCTFail from those background queues. Guard the shared state with an NSLock, record only the first exception, and report it from the dispatch_group_notify block on the main queue. This commit does not change the flakiness of testActiveKitsRegistryThreadSafety itself: that crash is a real race in MPKitContainer (flushSerializedKits enumerates and mutates kitsRegistry without kitsSemaphore, reached via the early return in configureKits: before the lock is taken). Reproduced at 2 crashes in 10 runs of that test alone. Fixing it changes core SDK locking, so it is deliberately left for a separate, explicitly-reviewed change. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
docs: trim AGENTS.md to the non-derivable core
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](isaacs/minimatch@v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.18.1. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](lodash/lodash@4.17.21...4.18.1) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.18.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
….29.4 in /RNExample (#767) chore: bump @babel/plugin-transform-modules-systemjs in /RNExample Bumps [@babel/plugin-transform-modules-systemjs](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-modules-systemjs) from 7.25.9 to 7.29.4. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.29.4/packages/babel-plugin-transform-modules-systemjs) --- updated-dependencies: - dependency-name: "@babel/plugin-transform-modules-systemjs" dependency-version: 7.29.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [shell-quote](https://github.com/ljharb/shell-quote) from 1.8.1 to 1.10.0. - [Changelog](https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md) - [Commits](ljharb/shell-quote@v1.8.1...v1.10.0) --- updated-dependencies: - dependency-name: shell-quote dependency-version: 1.10.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ork (#948) * fix: code-sign the nested Swift framework inside the release XCFramework mParticle_Apple_SDK.framework embeds mParticle_Apple_SDK_Swift.framework as a nested framework, but make_artifacts.sh only signed the top-level xcframework wrapper. That only seals the wrapper's file manifest - it does not add a code signature to the Mach-O binaries inside. The outer framework gets re-signed by a consuming app's own build when it embeds it, but the nested Swift framework is just copied verbatim and is never re-signed by the consumer, so it reached apps with no code signature at all. iOS enforces signatures at launch on a physical device, which is why Simulator and archive/export builds (which do their own recursive re-sign pass) were unaffected while a Debug build on a device crashed with "Library not loaded ... missing code signature". Sign inside-out for every platform slice: nested framework first, then the framework that embeds it, before sealing the xcframework wrapper. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: force-replace existing signatures when signing xcframework slices Simulator platform slices come off the archive step already carrying an automatic ad-hoc signature (device slices do not), so plain `codesign -s` was silently failing with "is already signed" on those and leaving the old ad-hoc signature in place instead of the real one. Add --force so every slice actually gets signed with the intended identity. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
chore: (release) 9.4.2 Updates version to 9.4.2 across the mParticle ecosystem.
…ite (#878) * fix: close the kitsRegistry race and stabilise the Rokt test suite Three separate defects were behind the remaining native-tests failures. Taken together they move MPRoktTests from 6/10 to 15/15 locally, with no crashes, no async timeouts and no aborted runners. kitsRegistry data race (shipped code) ------------------------------------- kitsRegistry is guarded by kitsSemaphore in activeKitsRegistry and in configureKits:, but flushSerializedKits enumerated and mutated it with no lock held - reached through the early return in configureKits: that fires before the lock is taken. Concurrent locked mutation plus unlocked enumeration crashed testActiveKitsRegistryThreadSafety at 2 runs in 10 in isolation; that test was not flaky, it was catching this. flushSerializedKits now snapshots the set under kitsSemaphore and iterates the snapshot on the main queue without holding the lock - waiting on the semaphore from the main queue would let a long background critical section stall the main thread. Because the snapshot already carries the registers, the flush path calls a new freeKitRegister:integrationId: directly instead of freeKit:, so it no longer reads kitsRegistry unguarded either. freeKit: keeps the lookup for configureKits:, which calls it while already holding the semaphore, and stays lock-free because dispatch_semaphore is not recursive. freeKit: is private to the implementation with two callers, so there is no kit-facing surface here. Async budgets of 200ms (largest failure cluster) ------------------------------------------------ Every test in MPRoktTests gave the SDK 200ms to dispatch through [MParticle messageQueue] and reach the kit container. A slow shared runner misses that, and because 25 tests shared the budget and the path they failed together - the "16 failing tests" seen on CI were 16 identical "Exceeded timeout of 0.2 seconds" errors, not 16 distinct problems. Raised to a named 5s constant; XCTest and OCMock both return as soon as the expectation is satisfied, so this costs nothing when the machine is fast. The single rejection keeps a tighter 1s window, since a rejection must wait out its whole window. Mock lifetime (aborted the test runner) -------------------------------------- testConfirmUserNilUserWithEmailCallsIdentifyAndBlocksUntilCompletion captures the identify completion and deliberately never invokes it, so confirmUser's completion block stayed alive holding a class mock that tearDown then disposed. The next test to touch it aborted the process with "Attempt to use unknown class", taking the runner down and losing the rest of the suite. tearDown now drains work already queued on the SDK's message queue before the mocks are stopped, spinning the main run loop so blocks that queue back onto main can also finish, bounded at 2s so a wedged queue slows teardown rather than hanging the suite. The partial mock is also stopped before the object it wraps is released, rather than after. This fixed the residual async timeouts too: a stale mock meant forwards reached the real container, so those expectations never fired. Rejected along the way, both measured: moving assertions out of all 21 OCMArg matchers (10/15, no change) and draining the stuck completion itself (5/15, worse - invoking it mutates shared SDK state). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: replace the remaining workspace-switch fixed delays with condition waits The four workspace-switch tests still built on nested 10-second dispatch_after chains were the only failures left in the full ObjC suite once the Rokt fixes landed - they failed 4 runs out of 4 in full-suite context while passing alone, because a fixed delay is a bet on machine speed rather than on the SDK being ready. Converted all four to MPWaitForCondition, the helper added for testSwitchWorkspaceKitsWithStop. Where a test asserts an outcome that arrives via the flush on the main queue (registeredKits.count == 0), the wait condition includes that outcome rather than just "the switch completed", so it cannot race the flush. Assertions are otherwise unchanged. testSwitchWorkspaceOptions had a 10-second delay before it even called startWithOptions:, with nothing async pending; that one is removed rather than converted. WORKSPACE_SWITCHING_DELAY is now unused and gone, and MParticleTests has no dispatch_after calls left. Full ObjC suite before: 0 of 4 runs clean, 2-6 failures each, 538-4379s. After: 954 tests with 0 failures on 2 of 3 runs, ~75s. The remaining run failed in testActiveKitsRegistryThreadSafety, which is the kitsRegistry race and is not fully fixed - see the previous commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: guard brackets with its own lock, defer kit teardown off kitsSemaphore PR #878's own new stress tests crashed this branch's CI 2/2 times (once on iOS, once on tvOS, both process restarts mid-test) while passing every local run before this fix - isolated x60, full local suite x3. Reproducing at the whole-class level (MPKitContainerTests, not just the single test in isolation) eventually caught it locally too, at roughly the same low rate CI saw it, and pinned the culprit down to two separate issues: 1. brackets (an NSMutableDictionary ivar) was read by bracketForKit: and mutated by updateBracketsWithConfiguration:integrationId: with no synchronization at all - not even kitsSemaphore. Concurrent mutation of an NSMutableDictionary during a read is exactly the kind of crash "Restarting after unexpected exit" describes, and testBracketForKitThreadSafety hammers exactly this: 3 reader threads calling bracketForKit: while a 4th concurrently calls updateBracketsWithConfiguration:. Added a dedicated bracketsSemaphore (not kitsSemaphore itself) scoped to just this state, since updateBracketsWithConfiguration: is also called from inside configureKits:'s kitsSemaphore-locked region - dispatch_semaphore is not reentrant, so reusing kitsSemaphore here would deadlock there. 2. freeKitRegister: - reached from configureKits:'s deactivateKits cleanup while it holds kitsSemaphore - called stop(), did disk cleanup, and posted mParticleKitDidBecomeInactiveNotification synchronously, still holding that lock. All of that runs arbitrary kit and observer code, which stalls every other thread waiting on the lock for as long as it takes, or deadlocks outright if an observer calls back into a kitsSemaphore-guarded method. Deferred it to dispatch_async(main) the same way flushSerializedKits already does; the wrapperInstance detach itself stays inline, since that is what synchronizes with isActiveAndNotDisabled:'s reads on other threads. Validated after both fixes: MPKitContainerTests class x60, full local suite x3, 0 crashes, 0 failures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test: guard testBracketForKitThreadSafety's shared error state Same issue testActiveKitsRegistryThreadSafety had before it was fixed: a plain __block BOOL encounteredError shared across 4 concurrent blocks is itself an unsynchronized data race, and XCTFail is not documented as safe to call off the main thread. Applied the same NSLock-guarded firstException pattern, deferring the actual XCTFail to dispatch_group_notify's main-queue block. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: wait for deferred kit teardown before starting the next workspace Bugbot (Cursor) flagged this on the previous commit: freeKitRegister: defers stop(), disk cleanup, and posting mParticleKitDidBecomeInactive- Notification to dispatch_async(main), matching flushSerializedKits's existing pattern - deliberately, so that work never runs while kitsSemaphore is held. But resetForSwitchingWorkspaces: and reset: both proceed to their completion (which starts the next workspace's kits) right after flushSerializedKits/removeAllSideloadedKits return, with no guarantee the deferred teardown they just scheduled has actually run yet by then. For most call paths this is masked by GCD's FIFO ordering on a serial queue - the teardown block and the "start next workspace" block both end up on the main queue, in the order they were enqueued. But startWithKeyCallback: calls identifyNoDispatch:completion:, whose completion can fire on whatever thread the network layer calls back on - not necessarily main, and with no ordering relationship to the already-scheduled teardown at all. A kit whose stop() has process-wide effects (their example: something like Rokt's stop() calling a shared close()) can start its new instance and then have the old instance's deferred stop() run after, tearing down what was just started. Added a dispatch_group (kitTeardownGroup) that flushSerializedKits and freeKitRegister: enter synchronously - before returning, not inside the dispatched block itself, which would leave the same race one level in - and leave once their deferred work actually completes. notifyWhenKitTeardownComplete:block: lets a caller wait on it; resetForSwitchingWorkspaces:/reset: now use it in place of the raw executeOnMain: call, so completion() (and whatever it starts) only runs once every kit scheduled for teardown by this reset has actually finished stopping. Validated: MPKitContainerTests + MParticleTests together x55 (30 + 25, after ruling out an intermittent batch of failures as this machine's own load - no code changes between batches, and two subsequent batches came back clean), full local suite x3, 0 crashes, 0 failures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: scope the kit registry lock to the state it guards kitsRegistry is class-level state, created once in +initialize, but kitsSemaphore was a per-instance ivar created in -init. A lock scoped per-instance cannot protect state scoped per-class: two containers could mutate the same set while each held its own semaphore, so the lock could not do the job it was there for. Move kitsSemaphore to a static created alongside kitsRegistry in +initialize, and update the four sites that reached it through strongSelf-> - a static is not reachable through the instance, so those would not have compiled otherwise. Also guard removeAllSideloadedKits and removeKitsFromRegistryInvalidForWorkspaceSwitch, which mutated the static set with no lock at all. The [kitsRegistry copy] in each protects the enumeration but not the removeObject: that follows. Both are called only from mParticle.m, outside any locked region, so taking the lock cannot deadlock against an existing holder. testActiveKitsRegistryThreadSafety over 20 runs: 3 crashes, down from roughly 1 in 3. Reduced, not eliminated - something in this path is still unguarded, so this is a step rather than a fix. Note the tradeoff: a shared static lock means two containers now block each other where they previously raced. That is correct, but dispatch_semaphore is not recursive, so a nested cross-instance call taken under the lock would deadlock rather than race. No such path was found, but it is worth a reviewer's eye. #agentic * fix: synchronize the wrapperInstance handoff in flushSerializedKits Scoping kitsSemaphore to the class and closing the unlocked mutators (previous commit) cut the crash rate in testActiveKitsRegistryThreadSafety from ~1 in 3 to 3 in 20, but did not close it. Real crash reports (.ips, not just the generic "unexpected exit" xcodebuild prints) show all three remaining crashes faulting in objc_retain/objc_release inside isActiveAndNotDisabled:, called from activeKitsRegistryWhenLocked - a use-after-free on a single element, not a set-structure error. kitRegister.wrapperInstance is declared nonatomic, so it has no synchronization of its own; kitsSemaphore only protects code that remembers to acquire it. activeKitsRegistry does, reading wrapperInstance under the lock. But flushSerializedKits's dispatch_async(main) block called freeKitRegister:, which set wrapperInstance = nil, without the lock - deliberately, per the previous commit's comment, to avoid stalling the main thread across file I/O and a notification post. That left the one write this teardown needed to synchronize outside the one section that was supposed to cover it, and a background reader's objc_retain could land mid-write on the object being released. Split the detach from the teardown: flushSerializedKits now nils out wrapperInstance for every snapshotted kit while still holding kitsSemaphore - a pointer swap, not file I/O - then runs stop(), file cleanup and the notification afterward, unlocked, via a new teardownDetachedWrapperInstance: forIntegrationId: that no longer touches kitRegister.wrapperInstance at all. freeKit:/freeKitRegister: (called from configureKits: while it already holds the lock) are unchanged. testActiveKitsRegistryThreadSafety: 0 crashes in 60 runs (was 3/20, was ~1/3). Full ObjC suite x4: 0 crashes in any run. One assertion failure recurred in testSwitchWorkspaceSideloadedKits across those runs; bisected against the parent commit (this fix reverted) under the same load and it fails there too, with a different assertion failing on a different run - pre-existing timing-sensitive flakiness in that test's MPWaitForCondition wait under a heavily loaded machine, unrelated to this change. startKit: and registerSideloadedKits still read/write this registry unlocked. Nothing in the current suite calls them concurrently, so they are not implicated in anything observed, but they are the same category of bug and remain open. #agentic * fix: silence analyzer warning on the NSNull sentinel ternary CI's run-analyzer job flagged the previous commit: 'incompatible operand types (id<MPKitProtocol> _Nullable and NSNull * _Nonnull)' at the ?: that boxes a possibly-nil wrapperInstance for storage in detachedWrapperInstances (NSArray cannot hold nil directly). The idiom is correct at runtime - this is the standard way to carry an optional through an NSArray - clang's ternary type-unification just doesn't like the two branch types. Any new warning fails this job per its filter, so an explicit (id) cast on the protocol side unifies the branches without changing behavior. Verified locally: xcodebuild ... analyze under the same warning filters CI uses exits 0, and no warning is reported at this line (previously present). #agentic * fix: identify sideloaded kits by code, not by a soon-to-be-nil wrapperInstance testSwitchWorkspaceSideloadedKits went from passing to failing 4/4 on this PR's CI, on both platforms, always the same way: after switching workspaces, registeredKits.count stayed at 2 instead of dropping to 1, and anyOTAobject's wrapperInstance was nil instead of the new kit instance. That is a regression from the previous commit, not the pre-existing flakiness this branch already carries elsewhere. removeAllSideloadedKits identified sideloaded registers by asking [kitRegister.wrapperInstance respondsToSelector:@selector(sideloadedKitCode)]. Both call sites that matter - resetForSwitchingWorkspaces: and reset:, in mParticle.m - call flushSerializedKits immediately before removeAllSideloadedKits, every time. The previous commit made flushSerializedKits detach wrapperInstance to nil synchronously, before it returns, specifically so the detach happens under kitsSemaphore and closes the race with activeKitsRegistry. That also means wrapperInstance is already nil by the time removeAllSideloadedKits runs right after it - [nil respondsToSelector:] is NO, so the check stopped matching anything, and stale sideloaded kits from the previous workspace were never removed from kitsRegistry. initWithInstance:kitCode: assigns every sideloaded kit a code starting at sideloadedKitCodeStartValue (1e9), on the register itself, independent of wrapperInstance and unaffected by the detach. Switched the check to that. removeKitsFromRegistryInvalidForWorkspaceSwitch has the same wrapperInstance-dependent shape, but it runs before flushSerializedKits at both call sites, so it is not affected by this ordering and is left alone. testSwitchWorkspaceSideloadedKits: 19/20 (the one failure was a different, earlier assertion - "Sideloaded kit was not registered" on the very first wait, before any switch happens - not this regression's signature). testActiveKitsRegistryThreadSafety: 0 crashes in 20, unaffected by this change. MParticleTests + MPKitContainerTests together x4: 140/140, 0 failures each run. #agentic * fix: defer freeKitRegister's kit teardown off kitsSemaphore PR #878/#916's own new stress test, testActiveKitsRegistryThreadSafety, crashed CI 2/2 times (once on iOS, once on tvOS, both restarts mid-test) while passing 60/60 isolated local runs plus 3/3 full-suite local runs, both before and after this fix - consistent with a rare, CI-hardware- specific timing window rather than something reproducible on demand. Auditing the locked paths this test exercises turned up a real inconsistency with the architecture the rest of this stack established. flushSerializedKits (895696a, a40a516) deliberately detaches wrapperInstance to nil under kitsSemaphore, then defers stop(), disk cleanup and the mParticleKitDidBecomeInactiveNotification post to dispatch_async(main) - specifically so that arbitrary kit/observer code never runs while every other thread is blocked on the lock. freeKitRegister:integrationId: - reached via configureKits:'s deactivateKits cleanup, which this exact stress test's writer thread exercises on every iteration - detaches wrapperInstance the same way, but then called teardownDetachedWrapperInstance: synchronously, still holding kitsSemaphore. That both stalls every reader thread for however long stop()/disk I/O takes, and risks an outright deadlock if any observer of the notification calls back into a kitsSemaphore-guarded method, since dispatch_semaphore_t is not reentrant. Deferred the same way flushSerializedKits does. The detach itself stays inline under the lock, since that is what synchronizes with isActiveAndNotDisabled:'s reads on other threads. Validated: MPKitContainerTests class x20 and full local suite x3 (both modes previously used to validate this stack), 0 failures, 0 crash restarts. This does not reproduce the CI-only crash locally to confirm root cause directly - filed as the most concrete, evidence-backed lead found by auditing every locked path the failing test touches. #agentic
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* chore: (release) 9.4.3 Updates version to 9.4.3 across the mParticle ecosystem. * Update CHANGELOG for Rokt kit changes Updated CHANGELOG to reflect recent fixes and additions for the Rokt kit. --------- Co-authored-by: Thomson Thomas <125323226+thomson-t@users.noreply.github.com>
fix(release): publish correct notes and report signing status The publish workflow treated unreleased changelog entries as release notes, causing stale changes to appear while omitting the current release. Use the latest versioned changelog section for core and kit releases, remove the stale unreleased entries, and print deep code-signing verification without changing the script’s successful exit behavior. #agentic
…kstation-swift-ebb64b # Conflicts: # CHANGELOG.md # Framework/Info.plist # Kits/adjust/adjust-5/mParticle-Adjust-5.podspec # Kits/adobe/adobe-5/mParticle-Adobe-5.podspec # Kits/appsflyer/appsflyer-6/mParticle-AppsFlyer-6.podspec # Kits/apptentive/apptentive-6/mParticle-Apptentive-6.podspec # Kits/apptentive/apptentive-7/mParticle-Apptentive-7.podspec # Kits/apptimize/apptimize-3/mParticle-Apptimize-3.podspec # Kits/branchmetrics/branchmetrics-3/Sources/mParticle-BranchMetrics/MPKitBranchMetrics.m # Kits/branchmetrics/branchmetrics-3/mParticle-BranchMetrics-3.podspec # Kits/braze/braze-12/mParticle-Braze-12.podspec # Kits/braze/braze-13/mParticle-Braze-13.podspec # Kits/braze/braze-14/mParticle-Braze-14.podspec # Kits/braze/braze-17/mParticle-Braze-17.podspec # Kits/clevertap/clevertap-7/Sources/mParticle-CleverTap/MPKitCleverTap.m # Kits/clevertap/clevertap-7/mParticle-CleverTap-7.podspec # Kits/comscore/comscore-6/mParticle-ComScore-6.podspec # Kits/google-analytics-firebase-ga4/firebase-ga4-11/mParticle-FirebaseGA4-11.podspec # Kits/google-analytics-firebase-ga4/firebase-ga4-12/mParticle-FirebaseGA4-12.podspec # Kits/google-analytics-firebase/firebase-11/mParticle-Firebase-11.podspec # Kits/google-analytics-firebase/firebase-12/mParticle-Firebase-12.podspec # Kits/iterable/iterable-6/mParticle-Iterable-6.podspec # Kits/leanplum/leanplum-6/mParticle-Leanplum-6.podspec # Kits/localytics/localytics-6/mParticle-Localytics-6.podspec # Kits/localytics/localytics-7/mParticle-Localytics-7.podspec # Kits/onetrust/onetrust/mParticle-OneTrust.podspec # Kits/optimizely/optimizely-4/mParticle-Optimizely-4.podspec # Kits/optimizely/optimizely-5/mParticle-Optimizely-5.podspec # Kits/radar/radar-3/mParticle-Radar-3.podspec # Kits/rokt-sdk-plus/rokt-sdk-plus-ios/Package.swift # Kits/rokt-sdk-plus/rokt-sdk-plus-ios/RoktSDKPlus.podspec # Kits/rokt-sdk-plus/rokt-sdk-plus-ios/Sources/RoktSDKPlus/RoktSDKPlus.swift # Kits/rokt/rokt/Sources/mParticle-Rokt/MPKitRokt.m # Kits/rokt/rokt/mParticle-Rokt.podspec # Kits/singular/singular-12/mParticle-Singular-12.podspec # Kits/urbanairship/urbanairship-19/mParticle-UrbanAirship-19.podspec # Kits/urbanairship/urbanairship-20/mParticle-UrbanAirship-20.podspec # UnitTests/ObjCTests/MPConnectorTests.m # UnitTests/ObjCTests/MPRoktTests.m # UnitTests/ObjCTests/MParticleTests.m # VERSION # mParticle-Apple-SDK-ObjC.podspec # mParticle-Apple-SDK-Swift.podspec # mParticle-Apple-SDK.podspec # mParticle-Apple-SDK/Include/MPKitContainer.h # mParticle-Apple-SDK/Kits/MPKitContainer.m # mParticle-Apple-SDK/MPIConstants.m # mParticle-Apple-SDK/Network/MPConnector.m
…ce-detach fix flushSerializedKits (ported from main's #878/#916 during the merge) now detaches wrapperInstance to nil synchronously before removeAllSideloadedKits runs, so its old check (wrapperInstance respondsToSelector:sideloadedKitCode) stopped matching anything - exactly the regression #916 already fixed on main for the same reason. Identify sideloaded registers by their assigned code instead, which survives the detach.
PR SummaryMedium Risk Overview Kit container: Adds a dedicated Release artifacts: Versioning / CI: Tests: Tightens workspace-switch tests to wait on Reviewed by Cursor Bugbot for commit 500b1ea. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
🐦 Swift Migration ProgressProduction implementation code at
Objective-C retained by design: Core SDK 6,079 · SDK kit infrastructure 2,336 (+33) · Standalone kits 0. This PR's code movement
How this is measured
Generated with |
📦 SDK Size Impact ReportMeasures how much the SDK adds to an app's size (with-SDK minus without-SDK).
➡️ SDK size impact change is minimal. Raw measurementsTarget branch (workstation/swift-migration): {"baseline_app_size_kb":84,"baseline_executable_size_bytes":75464,"with_sdk_app_size_kb":2740,"with_sdk_executable_size_bytes":76312,"sdk_impact_kb":2656,"sdk_executable_impact_bytes":848,"xcframework_size_kb":7004}This PR: {"baseline_app_size_kb":84,"baseline_executable_size_bytes":75464,"with_sdk_app_size_kb":2740,"with_sdk_executable_size_bytes":76312,"sdk_impact_kb":2656,"sdk_executable_impact_bytes":848,"xcframework_size_kb":7012} |
033f721
into
workstation/swift-migration
Summary
git mergeofmain's tip (23 commits since fix: merge main into workstation/swift-migration and apply pending test fixes #915) intoworkstation/swift-migration, resolving 42 conflicted files.MPKitContainerExecutionAdapter.m+MPKitContainer.swift, which don't exist in the same shape as main's now-deletedMPKitContainer.m): a separatebracketsSemaphore, deferred wrapperInstance teardown (freeKitRegister:/teardownDetachedWrapperInstance:), and akitTeardownGroup(needed formParticle.m's already-auto-merged calls to compile at all).removeAllSideloadedKitsidentified sideloaded kits viawrapperInstance respondsToSelector:, which stopped matching once the ported fix started nil'ingwrapperInstanceearlier —testSwitchWorkspaceSideloadedKitscaught it. Fixed the same way main's fix: eliminate the kitsRegistry thread-safety crash #916 did (identify bycode).testSwitchWorkspaceOptions/testSwitchWorkspaceSideloadedKitsnow wait on.initialized, not just a signal that fires before startup finishes).9.4.3.MPKitRokt.m/MPConnector.m/their ObjC tests: took this branch's Swift-migrated versions where main's side tested now-removed APIs; ported forward one real regression test (GoDaddy cert-pinning fingerprint check) into the Swift suite.Test plan
xcodebuild build— cleanmParticle-Apple-SDKscheme) — 970/970 passingmParticle-Apple-SDK-Swiftscheme) — 893/894 passing; the one failure (MPPersistenceStoreTests.testMigratesVersion30RowsAndPurgesExpiredRecords) reproduces identically onworkstation/swift-migration's tip with none of this PR's changes — pre-existing, not touched heretrunk check— clean, no new issues🤖 Generated with Claude Code