Skip to content

Refactor thread-blocking RxJava calls to coroutine suspending .await()#6850

Open
Roniscend wants to merge 1 commit into
commons-app:mainfrom
Roniscend:Rxjava-Thread
Open

Refactor thread-blocking RxJava calls to coroutine suspending .await()#6850
Roniscend wants to merge 1 commit into
commons-app:mainfrom
Roniscend:Rxjava-Thread

Conversation

@Roniscend

@Roniscend Roniscend commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Description (required)
Replaced .blockingGet(), .blockingAwait(), and .blockingSingle() with non-blocking .await() equivalents across UploadWorker and WikidataEditService
Fixes #6835

Tests performed (required)

Tested 6.4.0-debug on Pixel(Android 16)

Copilot AI review requested due to automatic review settings April 14, 2026 18:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors some thread-blocking RxJava calls in upload + Wikidata-edit flows to coroutine-friendly suspending await() calls, aiming to reduce ANR / thread-starvation risk during uploads and Wikidata edits.

Changes:

  • Convert multiple blockingGet / blockingAwait / blockingSingle usages in UploadWorker to coroutine suspension via await().
  • Refactor parts of WikidataEditService to avoid a thread-blocking blockingFirst() by returning an Observable and composing asynchronously.
  • Make Wikidata claim creation APIs (createClaim, addImageAndMediaLegends) suspend and switch claim creation to await().

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.kt Removes one blocking Rx call and converts claim creation to coroutine await() with suspend APIs.
app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt Replaces blocking Rx calls during upload pipeline with coroutine await() and adjusts some DB update points.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
Comment thread app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
Comment thread app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
Comment thread app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
@Roniscend Roniscend force-pushed the Rxjava-Thread branch 2 times, most recently from b5f2ec1 to 73ce566 Compare April 14, 2026 18:53
Signed-off-by: Owm Dubey <owmdubey163@gmail.com>
@chrisdebian

Copy link
Copy Markdown

Traced both of these through to confirm before flagging:

.firstOrError().await() on addDepictionsAndCaptions() will throw NoSuchElementException in ordinary use, not just on error. That method returns Observable<Boolean>, and WikidataEditService.kt completes it empty in two real cases: explicitly via Observable.empty<Boolean>() when the entity-ID lookup fails, and implicitly whenever a contribution has no depicts and no captions selected — depictionEdits/captionEdits each return an empty observable in that case, so Observable.concat(empty, empty) is empty too. The old .blockingSubscribe() didn't care whether anything was emitted; .firstOrError() does. A plain upload with no depicts/captions added is a routine path, not an edge case.

The three CoroutineScope(Dispatchers.IO).launch { } sites (onProgress, onChunkUploaded, saveIntoUploadedStatus) create unscoped, detached coroutines instead of using the scope UploadWorker already has as a CoroutineWorker. CoroutineWorker exposes its own lifecycle-tied coroutineScope for exactly this purpose — using a fresh top-level scope instead means these writes have no cancellation tie-in if the worker is stopped mid-upload, and since onProgress fires repeatedly during a transfer, it can spawn several of these per upload with no ordering guarantee, where the previous blocking calls guaranteed each write completed in sequence before the next progress tick.

Both fixable — first probably wants something like .firstOrError().onErrorReturnItem(false) or checking emptiness before treating it as an error, and the second by replacing CoroutineScope(Dispatchers.IO) with the worker's own coroutineScope at all three sites. Happy to help test once updated if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor Pervasive Thread-Blocking RxJava Calls to Suspending .await() to Prevent ANRs and Thread Starvation

3 participants