fix(omsdk): run OMID activation on the main thread; disable on failure - #12
Merged
Conversation
Calling KontextAds.createSession() from a background coroutine ran Omid.activate() off-main, where OMID's internal new Handler() throws "Can't create handler ... Looper.prepare()". OMID sets its isActive flag before that throw, so it was left half-initialized and its guard no-oped every retry; the next session then started a "valid" OMID session whose process-global TreeWalker dereferenced a never-initialized WeakReference<Context> and crashed the app on the main thread. OmManager now posts Omid.activate() to the main Looper (non-blocking, so it can't deadlock a caller holding the main thread) and tracks activation state process-globally with a sticky permanentlyUnavailable guard: if activation ever fails, OMID is disabled process-wide so the TreeWalker is never armed. Session start already runs on the main thread after preload, after the posted activation completes. No public API changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 tasks
- activate() from a background thread returns without blocking, throwing, or deadlocking (guards both the off-main crash and the runBlocking-on-main ANR; posted work drained via the main Looper). - a failed activation is sticky: createSession returns null afterward. - activation state is shared across OmManager instances (process-global). - add an internal resetActivationStateForTest() hook + @before reset so the static flags don't leak across cases. The happy path (globallyActivated=true) can't be unit-tested — the OMID AAR isn't on the test classpath, so Class.forName always throws here; it is covered by the on-device character-switch repro instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
JakubMrozek
added a commit
to kontextso/sdk-kotlin
that referenced
this pull request
Jun 1, 2026
* chore(deps): bump KontextKit 0.0.7 → 0.0.8 (OMID off-main activation fix) KontextKit 0.0.8 runs OMID's Omid.activate() on the main thread and disables OMID process-wide if activation ever fails, fixing a crash when KontextAds.createSession() is called from a background coroutine. See kontextso/kontextkit-android#12. Blocked on the KontextKit 0.0.8 release to Maven Central — draft until then. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: update SDK version assertions 4.0.3 → 4.0.4 The SDK version flows from libs.versions.toml (sdk-kotlin = 4.0.4) into BuildConfig.SDK_VERSION → SDKInfo.VERSION; ConfigurationTest and PreloadTest assert it literally, so they track the version bump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Problem
A consumer calls
KontextAds.createSession()from a background coroutine (Dispatchers.Default, viaChatViewModel.initializeSdk → KontextAdProvider.initKontext). That ranOmid.activate()off the main thread, where OMID's internalnew Handler()throws:OMID flips its own
isActiveflag before that throw, so the SDK was left half-initialized — its activate-guard then no-ops every retry. The next session would start a "valid" OMID session whose process-globalTreeWalkerdereferences a never-initializedWeakReference<Context>, crashing the app on the main thread:Signature in logs:
om-session-started {valid=false}(activation threw) →{valid=true}(next session) → crash.Fix
OmManager:activate()is called off-main it postsOmid.activate()to the mainLooper(fire-and-forget, non-blocking — a blocking post-and-await would deadlock a caller that is itself holding the main thread, e.g.runBlockingon main). OMID session start already happens later on the main thread (after preload + ad-done), so the posted activation always completes first.globallyActivated/permanentlyUnavailable@Volatilecompanion flags. If activation ever fails (throws or leaves OMID inactive), OMID is disabled process-wide so theTreeWalkeris never armed and the crash is structurally impossible.No public API changes. For off-main callers, viewability is restored (not merely suppressed).
Verification
Driven through a multi-Activity character-switch repro that calls
createSession()off-main (the field-crash pattern), on an emulator with the real OMID AAR:valid=false → valid=true → crash.valid=trueon every session across repeated character switches, no crash, no ANR, OMID genuinely active.valid=falseeverywhere, no crash../gradlew :build spotlessCheck detekt(incl. unit tests) green.Consumer note
Calling
createSession()on the main thread is a valid immediate workaround on the integration side; this change makes it safe from any thread. Server-side OM kill-switch (omit theomblock from the bid) remains the no-release tourniquet.🤖 Generated with Claude Code