Skip to content

Fix upload failure modes in UploadService and UploadWorker#22656

Closed
nbradbury wants to merge 7 commits intotrunkfrom
issue/upload-fixes
Closed

Fix upload failure modes in UploadService and UploadWorker#22656
nbradbury wants to merge 7 commits intotrunkfrom
issue/upload-fixes

Conversation

@nbradbury
Copy link
Contributor

@nbradbury nbradbury commented Mar 4, 2026

Description

Upload failures have long been mentioned in app reviews, so I decided to sic Claude on the problem. It turns out UploadService and UploadWorker have several failure modes that cause uploads to silently fail or never recover. This PR addresses six issues across four files:

  1. Silent publish when offline (UploadUtils): publishPost() silently skipped the upload with no user feedback. Now shows a "no network" toast.
  2. Orphaned media after service restart (UploadService): Media stuck in QUEUED/UPLOADING/FAILED states was abandoned forever when the service was killed. Added recoverInterruptedMediaUploads() in onCreate() with a process-lifetime tracking set to prevent recovery loops. Also removed the stale TODO and commented-out code block that this replaces.
  3. UploadWorker swallows exceptions (UploadWorker): doWork() could crash without returning Result.retry(). Wrapped in try/catch so WorkManager can retry.
  4. Network constraint too restrictive (UploadWorker): Changed from NOT_ROAMING to CONNECTED so uploads work on roaming networks.
  5. No backoff policy (UploadWorker): Added BackoffPolicy.EXPONENTIAL (10 min) to both one-time and periodic work requests.
  6. Silent error drop (PostUploadHandler): onPostUploaded silently discarded events for untracked posts. Now logs a warning with post ID, current uploading post ID, and any error details.

Testing instructions

Offline publish toast:

  1. Enable airplane mode
  2. Open an existing draft post and tap Publish
  • Verify a "no network" toast is shown
  • Verify the post is not lost (still in drafts)

Upload recovery after service kill:

  1. Start uploading a post with a large image attachment
  2. Force-stop the app mid-upload
  3. Reopen the app
  • Verify logcat shows "UploadService > Recovering N interrupted media uploads"
  • Verify the media upload resumes

UploadWorker resilience:

  1. Trigger an auto-upload (e.g., save a draft with pending media)
  2. Check logcat for "UploadWorker started" / "UploadWorker finished"
  • Verify no crashes if the upload starter throws an exception (worker retries instead)

nbradbury and others added 2 commits March 4, 2026 12:48
- Show toast when publishing without network instead of silently skipping
- Recover interrupted media uploads (QUEUED/UPLOADING/FAILED) on service
  restart with loop prevention via process-lifetime tracking set
- Wrap UploadWorker.doWork() in try/catch returning Result.retry()
- Change network constraint from NOT_ROAMING to CONNECTED
- Add exponential backoff policy (10 min) to work requests
- Log warning when onPostUploaded receives event for untracked post

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove stale TODO and 18 lines of commented-out recovery code in
  unpackMediaIntent(), now superseded by recoverInterruptedMediaUploads()
- Remove double blank line in UploadService
- Use string template in UploadWorker
- Use expression-body for getUploadConstraints()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dangermattic
Copy link
Collaborator

dangermattic commented Mar 4, 2026

1 Warning
⚠️ PR is not assigned to a milestone.

Generated by 🚫 Danger

nbradbury and others added 2 commits March 4, 2026 13:24
Extract backoff delay literal to a named constant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract helper methods to flatten nested loops and conditionals:
- recoverMediaForSite() handles per-site recovery
- collectNewMedia() and collectAndResetMedia() handle queue collection
- resetToQueued() consolidates state reset + dispatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wpmobilebot
Copy link
Contributor

wpmobilebot commented Mar 4, 2026

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
FlavorJalapeno
Build TypeDebug
Versionpr22656-bbce4e0
Build Number1486
Application IDcom.jetpack.android.prealpha
Commitbbce4e0
Installation URL7surnog5ac7s8
Note: Google Login is not supported on these builds.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Mar 4, 2026

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
FlavorJalapeno
Build TypeDebug
Versionpr22656-bbce4e0
Build Number1486
Application IDorg.wordpress.android.prealpha
Commitbbce4e0
Installation URL6uh0irl1gcqio
Note: Google Login is not supported on these builds.

@codecov
Copy link

codecov bot commented Mar 4, 2026

Codecov Report

❌ Patch coverage is 0% with 58 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.10%. Comparing base (f46a937) to head (e47b991).

Files with missing lines Patch % Lines
...rg/wordpress/android/ui/uploads/UploadService.java 0.00% 31 Missing ⚠️
...in/java/org/wordpress/android/util/UploadWorker.kt 0.00% 21 Missing ⚠️
...ordpress/android/ui/uploads/PostUploadHandler.java 0.00% 5 Missing ⚠️
.../org/wordpress/android/ui/uploads/UploadUtils.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            trunk   #22656      +/-   ##
==========================================
- Coverage   38.11%   38.10%   -0.02%     
==========================================
  Files        2263     2263              
  Lines      115897   115943      +46     
  Branches    16091    16104      +13     
==========================================
  Hits        44175    44175              
- Misses      68096    68142      +46     
  Partials     3626     3626              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nbradbury nbradbury marked this pull request as ready for review March 4, 2026 19:44
@nbradbury nbradbury requested review from adalpari and dcalhoun March 4, 2026 19:44
@adalpari
Copy link
Contributor

adalpari commented Mar 5, 2026

When I follow the steps, I can see the post is saved in drafts or published )depending the case), but the iterrupted media file is not uploaded at all.

Screenshot 2026-03-05 at 10 37 44
2026-03-05 10:33:31.332 11910-11910 GutenbergView           com.jetpack.android.beta             E  WV.j13@fd74fba
2026-03-05 10:33:38.187 11910-12059 FileCache               com.jetpack.android.beta             W  File exceeds maximum size limit: uri=content://media/picker_get_content/0/com.android.providers.media.photopicker/media/1000008184, size=149MB, limit=100MB
2026-03-05 10:33:38.187 11910-12059 GutenbergView           com.jetpack.android.beta             W  Failed to copy content URI to cache, using original: content://media/picker_get_content/0/com.android.providers.media.photopicker/media/1000008184
2026-03-05 10:33:38.360 11910-11910 GutenbergView           com.jetpack.android.beta             E  WV.j13@5c64ece
2026-03-05 10:33:38.638 11910-12533 ColorUtils              com.jetpack.android.beta             W  expected specified color aspects (0:0:0:0)
2026-03-05 10:33:38.643 11910-12533 CCodec                  com.jetpack.android.beta             W  This behavior is subject to change. It is recommended that app developers double check whether the requested max input size is in reasonable range.
2026-03-05 10:33:38.644 11910-12533 ck.android.beta         com.jetpack.android.beta             E  Failed to query component interface for required system resources: 6
2026-03-05 10:33:38.665 11910-12532 ck.android.beta         com.jetpack.android.beta             W  AIBinder_linkToDeath is being called with a non-null cookie and no onUnlink callback set. Use AIBinder_DeathRecipient_setOnUnlinked to manage the lifetime of the cookie. This will become an abort.
2026-03-05 10:33:38.684 11910-12218 chromium                com.jetpack.android.beta             E  [ERROR:media/gpu/android/frame_info_helper.cc:231] Guessed coded size incorrectly. Expected 1920x1080, got 1920x1088
2026-03-05 10:33:46.233 11910-11910 EventBus                com.jetpack.android.beta             W  Subscriber to unregister was not registered before: class org.wordpress.android.ui.posts.EditorJetpackSocialViewModel
2026-03-05 10:33:46.242 11910-11910 cr_AwContents           com.jetpack.android.beta             W  WebView.destroy() called while WebView is still attached to window.
2026-03-05 10:33:46.690  1833-10727 AppOps                  system_server                        E  Bad call made by uid 1000. Package "com.jetpack.android.beta" does not belong to uid 1000.
---------------------------- PROCESS ENDED (11910) for package com.jetpack.android.beta ----------------------------
---------------------------- PROCESS STARTED (12572) for package com.jetpack.android.beta ----------------------------
2026-03-05 10:33:58.044 12572-12572 WordPress-API           com.jetpack.android.beta             D  UploadStore onRegister
2026-03-05 10:33:58.104 12572-12572 pool-16-thread-         com.jetpack.android.beta             W  type=1400 audit(0.0:1748): avc:  denied  { read } for  name="version" dev="proc" ino=4026532005 scontext=u:r:untrusted_app:s0:c126,c257,c512,c768 tcontext=u:object_r:proc_version:s0 tclass=file permissive=0 app=com.jetpack.android.beta
2026-03-05 10:33:58.435 12572-12572 ck.android.beta         com.jetpack.android.beta             E  Invalid resource ID 0x00000000.
2026-03-05 10:33:58.610 12572-12701 JobInfo                 com.jetpack.android.beta             W  Requested important-while-foreground flag for job2147473669 is ignored and takes no effect
2026-03-05 10:33:58.933 12572-12572 JobInfo                 com.jetpack.android.beta             W  Job 'com.jetpack.android.beta/org.wordpress.android.ui.notifications.services.NotificationsUpdateJobService#7000' has a deadline with functional constraints and an extremely short time window of 0 ms (delay=0, deadline=0). The functional constraints are not likely to be satisfied when the job runs.
2026-03-05 10:33:58.941 12572-12572 JobInfo                 com.jetpack.android.beta             W  Job 'com.jetpack.android.beta/org.wordpress.android.ui.notifications.services.NotificationsUpdateJobService#7000' has a deadline with functional constraints and an extremely short time window of 0 ms (delay=0, deadline=0). The functional constraints are not likely to be satisfied when the job runs.
2026-03-05 10:33:58.949 12572-12572 WindowOnBackDispatcher  com.jetpack.android.beta             W  OnBackInvokedCallback is not enabled for the application.
                                                                                                    Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.
2026-03-05 10:33:58.974 12572-12577 ck.android.beta         com.jetpack.android.beta             W  userfaultfd: MOVE ioctl seems unsupported: Try again
2026-03-05 10:33:58.981 12572-12572 SQLiteLog               com.jetpack.android.beta             W  (28) double-quoted string literal: "116"
2026-03-05 10:33:59.069 12572-12572 ck.android.beta         com.jetpack.android.beta             E  Invalid resource ID 0x00000000.
2026-03-05 10:33:59.126 12572-12572 chromium                com.jetpack.android.beta             W  [WARNING:android_webview/browser/network_service/net_helpers.cc:137] HTTP Cache size is: 33590894
2026-03-05 10:33:59.343 12572-12572 TextView                com.jetpack.android.beta             W  onProvideContentCaptureStructure(): calling assumeLayout()
2026-03-05 10:33:59.375 12572-12832 cr_media                com.jetpack.android.beta             W  BLUETOOTH_CONNECT permission is missing.
2026-03-05 10:33:59.375 12572-12832 cr_media                com.jetpack.android.beta             W  getBluetoothAdapter() requires BLUETOOTH permission
2026-03-05 10:33:59.375 12572-12832 cr_media                com.jetpack.android.beta             W  registerBluetoothIntentsIfNeeded: Requires BLUETOOTH permission
2026-03-05 10:33:59.391 12572-12572 Glide                   com.jetpack.android.beta             W  Load failed for [] with dimensions [145x145]
                                                                                                    class com.bumptech.glide.load.engine.GlideException: Failed to load resource
2026-03-05 10:33:59.477 31256-31283 Finsky                  com.android.vending                  E  [70] ItemStore: getItems RPC failed for item com.jetpack.android.beta
2026-03-05 10:33:59.761 12572-12572 Glide                   com.jetpack.android.beta             W  Load failed for [] with dimensions [126x126]
                                                                                                    class com.bumptech.glide.load.engine.GlideException: Failed to load resource
2026-03-05 10:33:59.762 12572-12572 Glide                   com.jetpack.android.beta             W  Load failed for [] with dimensions [126x126]
                                                                                                    class com.bumptech.glide.load.engine.GlideException: Failed to load resource
2026-03-05 10:33:59.763 12572-12572 Glide                   com.jetpack.android.beta             W  Load failed for [] with dimensions [126x126]
                                                                                                    class com.bumptech.glide.load.engine.GlideException: Failed to load resource
2026-03-05 10:33:59.802 12572-12594 WordPress-UTILS         com.jetpack.android.beta             E  Feature flag values synced
2026-03-05 10:33:59.803 12572-12594 NosaraEvent             com.jetpack.android.beta             E  Cannot add the property: feature-flags-enabled to the event. Property name must match: ^[a-z_][a-z0-9_]*$
2026-03-05 10:33:59.832 12572-12676 WordPress-UTILS         com.jetpack.android.beta             E  Remote field config values synced
2026-03-05 10:34:00.048 12572-12572 GutenbergView           com.jetpack.android.beta             E  WV.j13@fc8de53
2026-03-05 10:34:00.185 12572-12796 WordPress-STATS         com.jetpack.android.beta             I  🔵 Tracked: auto_upload_post_invoked, Properties: {"upload_action":"UPLOAD_AS_DRAFT","post_status":"draft"}
2026-03-05 10:34:00.185 12572-12796 WordPress-POSTS         com.jetpack.android.beta             D  UploadStarter for post (isPage: false) title: , action: UPLOAD_AS_DRAFT
2026-03-05 10:34:00.186 12572-12796 WordPress-API           com.jetpack.android.beta             D  Dispatching action: UploadAction-INCREMENT_NUMBER_OF_AUTO_UPLOAD_ATTEMPTS
2026-03-05 10:34:00.267 12572-12572 WordPress-MAIN          com.jetpack.android.beta             I  UploadService > Created
2026-03-05 10:34:00.268 12572-12572 WordPress-MEDIA         com.jetpack.android.beta             I  MediaUploadHandler > Created
2026-03-05 10:34:00.271 12572-12572 WordPress-POSTS         com.jetpack.android.beta             I  PostUploadHandler > Created
2026-03-05 10:34:01.158 12572-12572 WordPress-MAIN          com.jetpack.android.beta             I  UploadService > Started from UploadStarter#upload
2026-03-05 10:34:01.176 12572-12572 WordPress-STATS         com.jetpack.android.beta             I  🔵 Tracked: notifications_upload_post_error_retry
2026-03-05 10:34:01.203 12572-12923 WordPress-POSTS         com.jetpack.android.beta             D  PostUploadHandler - UPLOAD_AS_DRAFT. Post: 
2026-03-05 10:34:01.366 12572-12572 GutenbergView           com.jetpack.android.beta             E  WV.j13@147b414
2026-03-05 10:34:01.370 12572-12572 WordPress-SETTINGS      com.jetpack.android.beta             E  An error occurred while updating the post formats with type: INVALID_RESPONSE
2026-03-05 10:34:01.546 12572-12572 VibratorInfo            com.jetpack.android.beta             E  Invalid frequency profile received from HAL. resonantFrequencyHz=147.12048, frequenciesHz=null, outputAccelerationsGs=null
2026-03-05 10:34:02.613 12572-12821 CachedAssetInterceptor  com.jetpack.android.beta             D  Serving cached asset: https://s0.wp.com/wp-content/mu-plugins/jetpack-plugin/moon/_inc/build/videopress/js/gutenberg-video-upload.min.js?m=1755006225i&ver=15.7-a.0
2026-03-05 10:34:02.622 12572-12821 CachedAssetInterceptor  com.jetpack.android.beta             D  Serving cached asset: https://s0.wp.com/wp-content/mu-plugins/jetpack-plugin/moon/modules/videopress/js/videopress-add-resumable-upload-support.js?m=1685131895i&ver=1
2026-03-05 10:34:02.966 12572-12572 WordPress-API           com.jetpack.android.beta             D  Dispatching action: UploadAction-PUSHED_POST
2026-03-05 10:34:02.999 12572-12572 WordPress-POSTS         com.jetpack.android.beta             I  PostUploadHandler > Completed
2026-03-05 10:34:03.011 12572-12572 WordPress-MAIN          com.jetpack.android.beta             I  UploadService > Completed
2026-03-05 10:34:03.024 12572-12572 WordPress-MAIN          com.jetpack.android.beta             I  UploadService > Destroyed
2026-03-05 10:34:04.243 12572-12577 ck.android.beta         com.jetpack.android.beta             W  Cleared Reference was only reachable from finalizer (only reported once)

@nbradbury
Copy link
Contributor Author

I can see the post is saved in drafts or published )depending the case), but the iterrupted media file is not uploaded at all.

Which editor is this with?

@adalpari
Copy link
Contributor

adalpari commented Mar 5, 2026

I can see the post is saved in drafts or published )depending the case), but the iterrupted media file is not uploaded at all.

Which editor is this with?

I didn't modify the Editor config. So, I assume GutembergKit?

@nbradbury
Copy link
Contributor Author

@adalpari Scratch that last question, I asked Claude to look into this and it identified the problem. I'll work on a fix for this.

PR #22656 added recoverInterruptedMediaUploads() in UploadService.onCreate() to recover
media uploads interrupted by a process kill. A reviewer (adalpari) found that recovery doesn't
work: the post uploads but the interrupted media is never re-uploaded.
Root cause: AppInitializer.init() (line 360) calls sanitizeMediaUploadStateForSite() on
a background thread during app startup. This marks all UPLOADING/QUEUED media as FAILED before UploadService.onCreate() gets a chance to recover them. The FAILED recovery path then requires hasValidFile(), which fails for content:// URIs and files that exceeded the FileCache size
limit (as in the reviewer's 149MB video test).

nbradbury and others added 2 commits March 5, 2026 07:33
Remove startup sanitizeMediaUploadStateForSite() call that races with
UploadService.onCreate() recovery, marking media as FAILED before it
can be re-queued. Also remove hasValidFile() guard from FAILED media
recovery since it rejects content:// URIs and large cached files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unnecessary null checks in onCreate(), merge duplicate
collectNewMedia/collectAndResetMedia into a single collectMedia()
method, fix stale Javadoc, and use String.format for log message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 5, 2026

@nbradbury
Copy link
Contributor Author

Some of this is duplicated in #22652 so I'll close this and revisit it once that PR is merged.

@nbradbury nbradbury closed this Mar 5, 2026
@nbradbury nbradbury deleted the issue/upload-fixes branch March 5, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants