Skip to content

area: upload: Fix duplicate validation bypass and thread starvation#6869

Open
Lame-Fortuna wants to merge 2 commits into
commons-app:mainfrom
Lame-Fortuna:deduplicate
Open

area: upload: Fix duplicate validation bypass and thread starvation#6869
Lame-Fortuna wants to merge 2 commits into
commons-app:mainfrom
Lame-Fortuna:deduplicate

Conversation

@Lame-Fortuna
Copy link
Copy Markdown

@Lame-Fortuna Lame-Fortuna commented Apr 23, 2026

Description
Fixes #6401
Duplicate validation was silently failing on fresh multi-uploads. The ViewPager was skipping per-image checks, and concurrent SHA-1 lookups were starving the IO thread. This PR ensures images are validated exactly when the user swipes to them.

What changes did you make and why?

  • IO Thread Starvation: Swapped .blockingGet() for .await() in CustomSelectorUtils.kt to unblock the coroutine during SHA-1 network lookups.
  • Per-Image Validation: Overloaded checkImageQuality in UploadMediaDetailsContract and UploadMediaPresenter to allow single-parameter (index) validation without breaking existing call sites.
  • Lifecycle Trigger: Called checkImageQuality in onBecameVisible (UploadMediaDetailFragment) so validation fires reliably on each ViewPager swipe.

Additional Notes / Out of Scope:

Tests performed (required)
Tested prodDebug on XI M2101K7BI (API 33).
Selected multiple existing Commons images and verified the duplicate popup triggers correctly for each specific image as it becomes visible in the ViewPager.

Screenshots (for UI changes only)
Before
photo_2026-04-25_20-40-41
photo_2026-04-25_20-40-08
photo_2026-04-25_20-40-17
photo_2026-04-25_20-40-22

After
photo_2026-04-25_20-41-53
photo_2026-04-25_20-42-03
photo_2026-04-25_20-41-58

@RitikaPahwa4444
Copy link
Copy Markdown
Collaborator

@Lame-Fortuna are all these changes absolutely necessary to solve the linked issue? If not, could you please keep the diff minimal, it helps review the changes quickly. Thanks!

@RitikaPahwa4444
Copy link
Copy Markdown
Collaborator

Also, could you please add the Before and After screencasts to the PR description?

@Lame-Fortuna
Copy link
Copy Markdown
Author

@RitikaPahwa4444 Well, these changes are necessary for unblocking the thread and triggering checks for duplicates for all images:

swapping .blockingGet() for .await() in CustomSelectorUtils.kt

adding the new method in UploadMediaPresenter.kt and UploadMediaDetailsContract.kt, and triggering it via onBecameVisible in UploadMediaDetailFragment.kt

The changes in strings.xml and the step_count string were just fixing linting issues and typos. Let me know if you want me to revert those.

P.S. Added the screenshots

@RitikaPahwa4444
Copy link
Copy Markdown
Collaborator

The changes in strings.xml and the step_count string were just fixing linting issues and typos. Let me know if you want me to revert those.

Yes please, we can take it up separately 🙂

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR targets the upload flow’s duplicate/quality validation reliability during multi-image uploads by reducing IO thread blocking during SHA-1 existence checks and by triggering per-item validation when a ViewPager page becomes visible.

Changes:

  • Replaced an RxJava blockingGet() with coroutine await() during SHA-1 existence checks to avoid blocking threads.
  • Extended the media details contract/presenter with verifyImageQuality(index) and invoked it from UploadMediaDetailFragment.onBecameVisible().
  • Minor contract comment cleanup and added lint suppression around duplicate popup formatting.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt Switches SHA-1 duplicate lookup from blocking to suspending await.
app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt Adds verifyImageQuality API and implementation to re-run stored quality checks by index.
app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt Exposes verifyImageQuality(index) on the presenter contract.
app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt Triggers validation on page visibility, but also removes step title binding and adds a new lint suppression.
Comments suppressed due to low confidence (2)

app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt:244

  • initializeFragment() no longer sets tvTitle (step count/title), and there doesn’t appear to be any other assignment in this fragment. This likely removes the step indicator from the UI. Please restore setting tvTitle (and fix the underlying string formatting issue in resources if that’s why it was removed).
    internal fun initializeFragment() {
        if (_binding == null) {
            return
        }
        binding.tooltip.setOnClickListener {
            showInfoAlert(
                R.string.media_detail_step_title,
                R.string.media_details_tooltip
            )
        }

app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt:96

  • Now that this uses await(), coroutine cancellation will throw CancellationException, which is currently caught by the broad catch (e: Exception) and converted into ImageLoader.Result.ERROR. That can break structured cancellation and lead to work continuing after the calling coroutine is cancelled. Consider rethrowing CancellationException (or catching it separately) and only mapping non-cancellation failures to ERROR.
                } catch (e: Exception) {
                    if (e is UnknownHostException) {
                        Timber.e(e, "Network Connection Error")
                    }
                    result = ImageLoader.Result.ERROR
                    e.printStackTrace()
                }

💡 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/mediaDetails/UploadMediaPresenter.kt Outdated
Comment thread app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt Outdated
@Lame-Fortuna
Copy link
Copy Markdown
Author

@RitikaPahwa4444 Reverted those unnecessary changes and applied those Copilot suggestions.

Comment thread app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt
* Replaced synchronous .blockingGet() with .await() in CustomSelectorUtils.kt to prevent IO dispatcher starvation.

* Overloaded checkImageQuality in UploadMediaDetailsContract and Presenter to allow single-parameter (index) validation without breaking existing call sites.

* Triggered image verification in UploadMediaDetailFragment's onBecameVisible to ensure validation fires on ViewPager swipe.

Fixes commons-app#6401
@RitikaPahwa4444
Copy link
Copy Markdown
Collaborator

@Lame-Fortuna do you have any performance improvements stats or profiling data for the the thread starvation that you've fixed for the custom selector?

The linked issue is for the normal selector, I tested it out and it works great :)

I'll have to test it out for the custom selector, are there any steps I could follow in case there's visible improvement?

@github-actions
Copy link
Copy Markdown

✅ Generated APK variants!

@Lame-Fortuna
Copy link
Copy Markdown
Author

Lame-Fortuna commented May 11, 2026

@RitikaPahwa4444 No, I do not have exact performance stats or profiling data.

The change was a preventative fix, swapping .blockingGet() for .await() stops the underlying IO thread from freezing during concurrent SHA-1 network lookups and allows the coroutine to suspend properly in the Custom Selector.

If needed, testing it by selecting a large batch of images should feel noticeably more responsive. However, there is still the issue with delayed popups (duplicate warnings sometimes arriving late via the network and stacking). I left that specific sync/race condition out of scope of this PR, as that requires digging deeper.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicates not flagged on fresh install

4 participants