Skip to content

fix(plugin-manager): parallel, non-blocking Plugins page update check#2667

Open
elibosley wants to merge 3 commits into
masterfrom
fix/plugins-page-nonblocking-update-check
Open

fix(plugin-manager): parallel, non-blocking Plugins page update check#2667
elibosley wants to merge 3 commits into
masterfrom
fix/plugins-page-nonblocking-update-check

Conversation

@elibosley

@elibosley elibosley commented Jun 18, 2026

Copy link
Copy Markdown
Member

Problem

The Installed Plugins page (Tools → Plugins) can sit with every row spinning checking… indefinitely — looking like the page is hung.

Root cause

The page already loads in two passes:

  • Pass 1 — ShowPlugins.php?init=true: renders the table from local .plg data (one simplexml parse per plugin). Fast.
  • Pass 2 — loadlist(): for every plugin, serially, check_plugin()plugin('check', …) spawns the plugin script which wgets that plugin's pluginURL. It's one request that returns only after the slowest plugin resolves, then patches all rows at once.

So a single slow or unreachable plugin URL blocks Pass 2 and leaves all rows stuck on checking….

Fix — take the network check off the critical path and parallelize it

  • ShowPlugins.php: add a single-plugin mode ?one=<name> that restricts the glob to one plugin and returns just its vid-/sid- line (goes through the normal update-check path).
  • Plugins.page: replace the batched loadlist() post-init sweep with a bounded-concurrency (6) fan-out — one ?one= request per installed plugin, each with a 20s AJAX timeout. Rows resolve independently; the Update All count is aggregated client-side.
  • Fail-safe: on timeout/error a row resolves to cannot check instead of spinning forever. The page is never gated on update checks.

A slow/unreachable plugin now affects only its own row, bounded by one timeout — never the whole page. No new caching introduced (deliberately — see OS-457 for why persistent attribute caching, attempted in the closed #2352, is unnecessary here).

Scope / not in this PR

  • The manual Check For Updates button still runs the backend plugin checkall (now via the task tray); it's user-initiated with visible progress, so it isn't a "page hang." Could be unified onto this parallel path later.
  • Phase 2 in OS-457 (a single-parse plugin_attributes() to drop the remaining per-plugin exec spawns for changes/alert) is a follow-up.

Testing

  • php -l clean on both files.
  • Needs live verification on a box with several plugins (one with a slow/unreachable URL): rows should fill in independently and a stalled one should land on cannot check within the timeout while the rest update normally. CI will build an installable PR plugin for this.

Ref: OS-457 · prior art: #2352 (closed), #2421 (merged)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Plugin update checks now run in parallel (with controlled concurrency) after the plugin list becomes available.
    • Update status is evaluated per plugin, enabling faster results and independent row updates.
    • The “Update All Plugins” control appears or hides based on whether all checks complete and whether updates are available.
    • Added support to target update checking for a single plugin when requested.
  • Bug Fixes
    • If a plugin check fails or times out, only that plugin is marked as “cannot check,” without affecting other plugins’ results.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@elibosley, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 38 minutes and 53 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 95e0140e-ebf4-4bd2-b070-5a396a04a488

📥 Commits

Reviewing files that changed from the base of the PR and between 23b4b19 and 367758d.

📒 Files selected for processing (2)
  • emhttp/plugins/dynamix.plugin.manager/Plugins.page
  • emhttp/plugins/dynamix.plugin.manager/include/ShowPlugins.php

Walkthrough

The PR replaces the single bulk plugin update sweep with a per-plugin parallel checking mechanism. ShowPlugins.php gains a one query parameter to scope its scan to one .plg file. Plugins.page adds checkPlugin(), checkPlugins(), and a pluginUpdates counter, wiring them into the post-init path instead of loadlist().

Changes

Per-plugin parallel update check

Layer / File(s) Summary
ShowPlugins.php single-plugin scope
emhttp/plugins/dynamix.plugin.manager/include/ShowPlugins.php
Parses $_GET['one'] and rewrites the $plugins glob to a single .plg file path when $one is set, narrowing the update-check loop to the targeted plugin.
checkPlugin / checkPlugins orchestration
emhttp/plugins/dynamix.plugin.manager/Plugins.page
Adds pluginUpdates counter, checkPlugin(plg) AJAX helper (updates row via updateInfo(), marks row "cannot check" on failure/timeout), and checkPlugins() orchestrator (max-6 concurrency, disables/enables #checkall, shows/hides #updateall). Replaces loadlist() call in the initlist() completion path.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop, hop, six at a time,
Each plugin checked — a parallel rhyme!
No more waiting for the whole row to sweep,
One .plg file wakes from its sleep.
The "Update All" button appears just in time,
Bounded concurrency — simply sublime! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR explicitly documents that it deliberately does NOT implement the caching approach from issue #2352, choosing instead to parallelize update checks. This represents a different solution path to the same problem. Clarify whether this PR is intended to supersede or complement #2352, or provide documentation explaining the rationale for this alternative approach.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: replacing sequential update checks with parallelized non-blocking requests.
Out of Scope Changes check ✅ Passed All changes (ShowPlugins.php single-plugin mode, parallel AJAX fan-out, timeout handling) are directly related to fixing the hung plugins page issue described in the PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/plugins-page-nonblocking-update-check

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2026.06.22.2045
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2667/webgui-pr-2667.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates
  • Post-merge behavior: This preview stays available after merge until preview storage expires or it is manually cleaned up

📝 Modified Files:

Click to expand file list
emhttp/plugins/dynamix.plugin.manager/Plugins.page
emhttp/plugins/dynamix.plugin.manager/include/ShowPlugins.php

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2667, or run:

plugin remove webgui-pr-2667

🤖 This comment is automatically generated and will be updated with each new push to this PR.

elibosley and others added 2 commits June 19, 2026 10:37
…blocking

The Installed Plugins page renders the list quickly from local .plg data,
but the per-plugin update check ran as a single serial request:
ShowPlugins.php looped every plugin and, for each, spawned the plugin
script to wget that plugin's pluginURL. The request returned all-or-nothing,
so one slow or unreachable plugin URL left every row stuck spinning
'checking...' — the page appears hung.

Take the network check off the critical path and parallelize it:
- ShowPlugins.php: add a single-plugin mode (?one=<name>) that restricts the
  glob to one plugin and returns just its vid-/sid- line.
- Plugins.page: replace the batched loadlist() post-init sweep with a
  bounded-concurrency (6) fan-out, one ?one= request per installed plugin,
  each with a 20s AJAX timeout. Rows resolve independently; the 'Update All'
  count is aggregated client-side.
- Fail-safe: on timeout/error a row resolves to 'cannot check' instead of
  spinning forever. The page is never gated on update checks.

A slow/unreachable plugin now only affects its own row, bounded by one
timeout, never the whole page. No new caching introduced.

Ref: OS-457

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
class 'remove' is on both the checkbox and the button of each plugin row,
so the fan-out queued every plugin twice. Dedupe by data attribute so each
plugin is checked once.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@elibosley elibosley force-pushed the fix/plugins-page-nonblocking-update-check branch from 23b4b19 to b3a1b4b Compare June 19, 2026 14:37
The Plugins page tablesorter sorts on the Status column first
(sortList [[4,0],[1,0]]). Each per-plugin check rewrites that cell's
data rank, and the post-check trigger('update') re-applied the sort,
so rows jumped around as results landed one by one.

Use trigger('updateCache') instead: it refreshes the parsed cache (so a
later header-click sort still uses current status) without re-sorting,
so rows fill in place and the list no longer reorders while checking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant