From fbc301c284490b161c604d4973989ade08b9eb65 Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Wed, 13 May 2026 16:41:41 -0400 Subject: [PATCH 1/3] Fix Sentry JETPACK-ANDROID-1GDC/1GEB/1GAF/1G8Z: move Reader DB writes off main Volley's ResponseDeliveryRunnable runs callbacks on the main thread, and two Reader paths then performed a SQLite compileStatement + execute on that callback. On a busy SQLite connection pool this exceeded the ANR budget. ReaderTagTable.setTagLastUpdated (called from ReaderPostRepository.requestPostsWithTag and requestPostsForLatestStream) now runs on a background Thread. ReaderBlogActions.handleUpdateBlogInfoResponse now performs ReaderBlogTable.addOrUpdateBlog on a background Thread and posts UpdateBlogInfoListener.onResult back on the main thread, since the existing callers (ReaderSiteHeaderView, ReaderFetchSiteUseCase) expect the result on main. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ui/reader/actions/ReaderBlogActions.java | 16 ++++++++++------ .../ui/reader/repository/ReaderPostRepository.kt | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java index f8cf767d0971..fd6ee248e51f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java @@ -1,5 +1,7 @@ package org.wordpress.android.ui.reader.actions; +import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; import android.util.Pair; @@ -456,12 +458,14 @@ private static void handleUpdateBlogInfoResponse(JSONObject jsonObject, UpdateBl return; } - ReaderBlog blogInfo = ReaderBlog.fromJson(jsonObject); - ReaderBlogTable.addOrUpdateBlog(blogInfo); - - if (infoListener != null) { - infoListener.onResult(blogInfo); - } + final ReaderBlog blogInfo = ReaderBlog.fromJson(jsonObject); + // Move the INSERT OR REPLACE off the main thread; callers expect onResult on main. + new Thread(() -> { + ReaderBlogTable.addOrUpdateBlog(blogInfo); + if (infoListener != null) { + new Handler(Looper.getMainLooper()).post(() -> infoListener.onResult(blogInfo)); + } + }).start(); } /* diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt index 475157150f62..788babca2467 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt @@ -116,7 +116,7 @@ class ReaderPostRepository @Inject constructor( if (updateAction == ReaderPostServiceStarter.UpdateAction.REQUEST_NEWER || updateAction == ReaderPostServiceStarter.UpdateAction.REQUEST_REFRESH ) { - ReaderTagTable.setTagLastUpdated(tag) + Thread { ReaderTagTable.setTagLastUpdated(tag) }.start() } handleUpdatePostsResponse(tag, jsonObject, updateAction, resultListener) } @@ -352,7 +352,7 @@ class ReaderPostRepository @Inject constructor( if (updateAction == ReaderPostServiceStarter.UpdateAction.REQUEST_NEWER || updateAction == ReaderPostServiceStarter.UpdateAction.REQUEST_REFRESH ) { - ReaderTagTable.setTagLastUpdated(tag) + Thread { ReaderTagTable.setTagLastUpdated(tag) }.start() } handleUpdatePostsResponse(tag, jsonObject, updateAction, resultListener) } From a34ca924c4833e9756788aa9b69a9005be7f5f65 Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Mon, 18 May 2026 08:34:18 -0400 Subject: [PATCH 2/3] Use applicationScope.launch instead of Thread for setTagLastUpdated Both call sites already have applicationScope + ioDispatcher injected, and the newer functions in this file use the same coroutine pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../android/ui/reader/repository/ReaderPostRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt index 788babca2467..291a6122f541 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt @@ -116,7 +116,7 @@ class ReaderPostRepository @Inject constructor( if (updateAction == ReaderPostServiceStarter.UpdateAction.REQUEST_NEWER || updateAction == ReaderPostServiceStarter.UpdateAction.REQUEST_REFRESH ) { - Thread { ReaderTagTable.setTagLastUpdated(tag) }.start() + applicationScope.launch(ioDispatcher) { ReaderTagTable.setTagLastUpdated(tag) } } handleUpdatePostsResponse(tag, jsonObject, updateAction, resultListener) } @@ -352,7 +352,7 @@ class ReaderPostRepository @Inject constructor( if (updateAction == ReaderPostServiceStarter.UpdateAction.REQUEST_NEWER || updateAction == ReaderPostServiceStarter.UpdateAction.REQUEST_REFRESH ) { - Thread { ReaderTagTable.setTagLastUpdated(tag) }.start() + applicationScope.launch(ioDispatcher) { ReaderTagTable.setTagLastUpdated(tag) } } handleUpdatePostsResponse(tag, jsonObject, updateAction, resultListener) } From 24d7925c0a227c8beb31196421645d289f088438 Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Mon, 18 May 2026 09:09:23 -0400 Subject: [PATCH 3/3] Use shared executor for Reader blog-info DB writes Replaces the per-response new Thread() in handleUpdateBlogInfoResponse with a static single-thread ExecutorService + static main-thread Handler. DB writes now serialize through one shared worker, avoiding thread churn on response bursts. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ui/reader/actions/ReaderBlogActions.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java index fd6ee248e51f..4a3fe9f4eb32 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java @@ -34,8 +34,15 @@ import java.net.HttpURLConnection; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class ReaderBlogActions { + // Serial worker for Reader DB writes triggered from Volley response callbacks. Using a single + // shared executor avoids spawning a thread per response and lets SQLite writes queue naturally. + private static final ExecutorService DB_EXECUTOR = Executors.newSingleThreadExecutor(); + private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); + public static class BlockedBlogResult { public long blogId; public long feedId; @@ -460,12 +467,12 @@ private static void handleUpdateBlogInfoResponse(JSONObject jsonObject, UpdateBl final ReaderBlog blogInfo = ReaderBlog.fromJson(jsonObject); // Move the INSERT OR REPLACE off the main thread; callers expect onResult on main. - new Thread(() -> { + DB_EXECUTOR.execute(() -> { ReaderBlogTable.addOrUpdateBlog(blogInfo); if (infoListener != null) { - new Handler(Looper.getMainLooper()).post(() -> infoListener.onResult(blogInfo)); + MAIN_HANDLER.post(() -> infoListener.onResult(blogInfo)); } - }).start(); + }); } /*