Skip to content

⚡ Optimize plugin detection with concurrent operations#26

Merged
sunnylqm merged 1 commit intomasterfrom
perf-optimize-plugin-detection-15521046730303659627
Apr 14, 2026
Merged

⚡ Optimize plugin detection with concurrent operations#26
sunnylqm merged 1 commit intomasterfrom
perf-optimize-plugin-detection-15521046730303659627

Conversation

@sunnylqm
Copy link
Copy Markdown
Collaborator

@sunnylqm sunnylqm commented Apr 14, 2026

💡 What: The optimization implemented
The checkPlugins function in src/utils/check-plugin.ts was refactored to use Promise.all for concurrent plugin detection instead of a sequential for...of loop.

🎯 Why: The performance problem it solves
Previously, each plugin's detect method was awaited sequentially. This caused an N+1 async operation bottleneck where the total detection time was the sum of all individual detection times. In environments with multiple plugins or slow filesystem checks, this led to unnecessary delays during the bundling process.

📊 Measured Improvement:
Using a simulated benchmark with 3 plugins, each having a 100ms artificial delay:

  • Baseline: ~311ms (Sequential)
  • Optimized: ~102ms (Concurrent)
  • Improvement: ~209ms reduction (~67% faster)

The optimization ensures that the total detection time is bounded by the slowest individual detection rather than the sum of all. Functionality and side-effect ordering (logging and parameter merging) have been preserved exactly.


PR created automatically by Jules for task 15521046730303659627 started by @sunnylqm

Summary by CodeRabbit

  • Performance

    • Plugin detection now executes in parallel, improving initialization speed when multiple plugins are present.
  • Tests

    • Added comprehensive tests for plugin detection, validating detection accuracy and concurrent execution performance.

Replaced the sequential `for...of` loop in `checkPlugins` with `Promise.all` to execute `plugin.detect()` calls concurrently. This significantly reduces the time required for the detection phase, especially as the number of plugins grows.

The results are processed in the original order to ensure deterministic behavior for logging and merging `bundleParams`.

Measured improvement:
- Baseline (3 plugins, 100ms each): ~311ms
- Optimized (3 plugins, 100ms each): ~102ms
- Speedup: ~3x for the simulated case.

Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

The checkPlugins() function refactors from sequential plugin detection (for...of loop) to concurrent detection using Promise.all(). Plugin detection results—including success status and any errors—are collected in parallel, then processed to merge bundle parameters and log outcomes.

Changes

Cohort / File(s) Summary
Plugin Detection Concurrency
src/utils/check-plugin.ts
Refactored detection from sequential loop to parallel Promise.all(plugins.map(...)) to evaluate all plugins concurrently. Each detection captures result as { isEnabled, error } and post-processes aggregated results for logging and parameter merging.
Concurrent Behavior Test
tests/check-plugin.test.ts
New test file validating concurrent detection: mocks dependencies, creates three plugins with 100ms delays each, and asserts total runtime under 250ms to confirm parallel execution rather than sequential (300ms+).

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant checkPlugins as checkPlugins()
    participant Promise as Promise.all()
    participant P1 as plugin1.detect()
    participant P2 as plugin2.detect()
    participant P3 as plugin3.detect()
    
    Caller->>checkPlugins: call checkPlugins()
    checkPlugins->>Promise: map plugins to detect calls
    Promise->>P1: initiate detect()
    Promise->>P2: initiate detect()
    Promise->>P3: initiate detect()
    par Concurrent Execution
        P1-->>Promise: resolve { isEnabled, error }
        P2-->>Promise: resolve { isEnabled, error }
        P3-->>Promise: resolve { isEnabled, error }
    end
    Promise-->>checkPlugins: all results array
    loop Process Results
        checkPlugins->>checkPlugins: log errors or merge params
    end
    checkPlugins-->>Caller: return merged params
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hops of parallel glory!
Where plugins dance together fast,
No waiting in the queue—each hops alone,
Then meets at Promise's finish line with glee,
Our burrow's speedy now! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main optimization: concurrent plugin detection. It directly relates to the core change (Promise.all instead of sequential awaits) and matches the PR's primary objective.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf-optimize-plugin-detection-15521046730303659627

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/check-plugin.test.ts (1)

53-70: Timing-only assertion is likely flaky under CI load.

Line 70 can fail on slow runners even when implementation is correct. Consider asserting concurrency behavior directly (e.g., all three detect() calls started before releasing a shared gate), then keep timing as a soft signal.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/check-plugin.test.ts` around lines 53 - 70, The test relies solely on a
duration check which is flaky; instead modify the test that calls checkPlugins()
to assert concurrency by instrumenting the three plugin detect implementations
(the p1/p2/p3 detect functions used by checkPlugins()) to set a "started" flag
when invoked and then await a shared gate Promise before finishing; assert all
three started flags are true before resolving the gate to prove they ran
concurrently, and keep the duration / expect(duration).toBeLessThan(...) only as
a secondary, non-blocking signal (or relax its threshold) while retaining the
same result equality assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/check-plugin.test.ts`:
- Around line 50-73: The test mutates pluginConfig.plugins directly and only
restores it at the end, so wrap the mutation and the assertions in a
try/finally: save originalPlugins, replace pluginConfig.plugins with mockPlugins
before running checkPlugins(), then in the finally block always restore
pluginConfig.plugins using the saved originalPlugins (use the same splice
restoration logic shown); ensure the timing/assertion code (start/await
checkPlugins()/end/expect(duration)... expect(result)...) remains inside the try
so cleanup always runs even on failure.

---

Nitpick comments:
In `@tests/check-plugin.test.ts`:
- Around line 53-70: The test relies solely on a duration check which is flaky;
instead modify the test that calls checkPlugins() to assert concurrency by
instrumenting the three plugin detect implementations (the p1/p2/p3 detect
functions used by checkPlugins()) to set a "started" flag when invoked and then
await a shared gate Promise before finishing; assert all three started flags are
true before resolving the gate to prove they ran concurrently, and keep the
duration / expect(duration).toBeLessThan(...) only as a secondary, non-blocking
signal (or relax its threshold) while retaining the same result equality
assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6a134ac9-32e9-48ad-8e54-ca5573472911

📥 Commits

Reviewing files that changed from the base of the PR and between 62c129a and 1a11104.

📒 Files selected for processing (2)
  • src/utils/check-plugin.ts
  • tests/check-plugin.test.ts

Comment on lines +50 to +73
const originalPlugins = [...pluginConfig.plugins];
(pluginConfig.plugins as any).splice(0, pluginConfig.plugins.length, ...mockPlugins);

const start = Date.now();
const result = await checkPlugins();
const end = Date.now();
const duration = end - start;

console.log(`Duration with optimized implementation: ${duration}ms`);

expect(result).toEqual({
sentry: false, // default
sourcemap: false, // default
p1: true,
p2: true,
p3: true
} as any);

// Now it's concurrent, so we expect around 100ms.
// We'll allow some buffer, but it should definitely be less than 250ms.
expect(duration).toBeLessThan(250);

// Restore original plugins
(pluginConfig.plugins as any).splice(0, pluginConfig.plugins.length, ...originalPlugins);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Always restore pluginConfig.plugins via finally.

At Line 51, global test state is mutated. If an assertion fails before Line 73, cleanup is skipped and later tests may fail for the wrong reason.

Proposed fix
 const originalPlugins = [...pluginConfig.plugins];
 (pluginConfig.plugins as any).splice(0, pluginConfig.plugins.length, ...mockPlugins);

-const start = Date.now();
-const result = await checkPlugins();
-const end = Date.now();
-const duration = end - start;
-
-console.log(`Duration with optimized implementation: ${duration}ms`);
-
-expect(result).toEqual({
-  sentry: false, // default
-  sourcemap: false, // default
-  p1: true,
-  p2: true,
-  p3: true
-} as any);
-
-// Now it's concurrent, so we expect around 100ms.
-// We'll allow some buffer, but it should definitely be less than 250ms.
-expect(duration).toBeLessThan(250);
-
-// Restore original plugins
-(pluginConfig.plugins as any).splice(0, pluginConfig.plugins.length, ...originalPlugins);
+try {
+  const start = Date.now();
+  const result = await checkPlugins();
+  const end = Date.now();
+  const duration = end - start;
+
+  console.log(`Duration with optimized implementation: ${duration}ms`);
+
+  expect(result).toEqual({
+    sentry: false, // default
+    sourcemap: false, // default
+    p1: true,
+    p2: true,
+    p3: true
+  } as any);
+
+  // Now it's concurrent, so we expect around 100ms.
+  // We'll allow some buffer, but it should definitely be less than 250ms.
+  expect(duration).toBeLessThan(250);
+} finally {
+  (pluginConfig.plugins as any).splice(0, pluginConfig.plugins.length, ...originalPlugins);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/check-plugin.test.ts` around lines 50 - 73, The test mutates
pluginConfig.plugins directly and only restores it at the end, so wrap the
mutation and the assertions in a try/finally: save originalPlugins, replace
pluginConfig.plugins with mockPlugins before running checkPlugins(), then in the
finally block always restore pluginConfig.plugins using the saved
originalPlugins (use the same splice restoration logic shown); ensure the
timing/assertion code (start/await checkPlugins()/end/expect(duration)...
expect(result)...) remains inside the try so cleanup always runs even on
failure.

@sunnylqm sunnylqm merged commit 2bc7645 into master Apr 14, 2026
4 of 6 checks passed
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