Skip to content

feat: remove promise.all to prevent resource exhaustion#2632

Open
Santosl2 wants to merge 22 commits into
WhiskeySockets:masterfrom
Santosl2:feat/add-concurrency-limit
Open

feat: remove promise.all to prevent resource exhaustion#2632
Santosl2 wants to merge 22 commits into
WhiskeySockets:masterfrom
Santosl2:feat/add-concurrency-limit

Conversation

@Santosl2

@Santosl2 Santosl2 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

This function can block the event loop for up to 2 seconds under a high volume of connections. Combined with 20+ concurrent connections on the same server, this can cause noticeable latency. The issue comes from using Promise.all for CPU-bound tasks. Replacing it with sequential execution plus periodic yields to the event loop prevents long event loop stalls and keeps the server more responsive.

This pull request refactors the way participant message nodes are created and encrypted in the makeMessagesSocket function. The main improvement is simplifying the logic by replacing the use of Promise.all with a sequential for...of loop, which enhances error handling and code clarity.

Refactoring and simplification of message encryption:

  • Replaced the use of Promise.all with a synchronous for...of loop to process message encryption for each recipient, allowing for immediate error handling and skipping invalid recipients.
  • Changed the logic to accumulate only successfully created nodes into the nodes array, removing the need to filter out null values after encryption attempts.

Code readability and maintainability:

  • Improved early exit for invalid recipient JIDs by using continue instead of returning null within the encryption loop.
  • Removed the unnecessary encryptionPromises array and its associated mapping logic, streamlining the code structure.
  • Added a blank line for readability before the createParticipantNodes function definition.

@whiskeysockets-bot

whiskeysockets-bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Thanks for opening this pull request and contributing to the project!

The next step is for the maintainers to review your changes. If everything looks good, it will be approved and merged into the main branch.

In the meantime, anyone in the community is encouraged to test this pull request and provide feedback.

✅ How to confirm it works

If you’ve tested this PR, please comment below with:

Tested and working ✅

This helps us speed up the review and merge process.

📦 To test this PR locally:

# NPM
npm install @whiskeysockets/baileys@Santosl2/Baileys-1#feat/add-concurrency-limit

# Yarn (v2+)
yarn add @whiskeysockets/baileys@Santosl2/Baileys-1#feat/add-concurrency-limit

# PNPM
pnpm add @whiskeysockets/baileys@Santosl2/Baileys-1#feat/add-concurrency-limit

If you encounter any issues or have feedback, feel free to comment as well.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR changes createParticipantNodes to encrypt recipients sequentially with per-recipient try/catch that logs and skips failures, yielding to the event loop between recipients. A small utility yieldEventLoop() is added.

Changes

Message Encryption Error Handling

Layer / File(s) Summary
Per-recipient encryption with error handling
src/Socket/messages-send.ts, src/Utils/yield-event-loop.ts
Replaces Promise.all batching with a sequential for...of encryption loop in createParticipantNodes, wrapping each encrypt call in try/catch, logging failures, accumulating successful encrypted nodes, calling yieldEventLoop() between recipients, and preserving existing encryption-type checks and the final empty-result guard.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hopping through recipients, one by one,
I catch the errors and let the sun—
Yield the loop, push nodes with care,
Logs mark the misses, successes share.
A quiet rabbit, fixing the run.

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title mentions 'remove promise.all' which is accurate to the changes, but the main objective is adding concurrency control via sequential processing, not merely removing Promise.all.

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


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Socket/messages-send.ts (1)

558-607: ⚠️ Potential issue | 🔴 Critical

Fix incorrect p-limit usage and remove any casts in createParticipantNodes (src/Socket/messages-send.ts, ~558-607)

  • patchedMessages.map(limit(async (...) => { ... }, {})) is invalid: limit(fn, ...args) returns a Promise, but .map() expects a mapper function, so this will fail at runtime; also {} would be passed as the async fn’s first argument.
  • Use patchedMessages.map(item => limit(async () => { const { recipientJid: jid, message: patchedMessage } = item; ... })) (or equivalent wrapper).
  • Remove (patchedMessages as any) and the : any parameter by typing patchedMessages and the callback properly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Socket/messages-send.ts` around lines 558 - 607, The mapped
encryptionPromises incorrectly calls limit as the mapper and uses any-casts;
change to map each patchedMessages item into a call that returns limit(async ()
=> { ... }) so limit receives the async function and map returns an array of
Promises (i.e., replace patchedMessages.map(limit(async ({ recipientJid: jid,
message: patchedMessage }: any) => { ... }, {})) with patchedMessages.map(item
=> limit(async () => { const { recipientJid: jid, message: patchedMessage } =
item; ... })) and remove the (patchedMessages as any) and : any parameter types
by properly typing patchedMessages and the callback; keep existing logic that
uses jidDecode, meId/meLid/meLidUser, dsmMessage, encodeWAMessage,
encryptionMutex.mutex, and signalRepository.encryptMessage and preserve the
try/catch and logger calls so behavior is unchanged.

Source: Coding guidelines

🧹 Nitpick comments (1)
src/Socket/messages-send.ts (1)

556-556: ⚡ Quick win

Consider making encryption concurrency configurable.

The hardcoded value of 10 may not be optimal for all deployment scenarios (serverless vs VPS, different CPU/memory constraints). Consider adding an encryptionConcurrency option to SocketConfig with 10 as the default.

♻️ Suggested approach

In the SocketConfig type (likely in src/Types/index.ts):

encryptionConcurrency?: number

Then at line 122:

const encryptionLimiter = pLimit({ concurrency: config.encryptionConcurrency ?? 10 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Socket/messages-send.ts` at line 556, Add a configurable encryption
concurrency option: extend the SocketConfig type (e.g., add
encryptionConcurrency?: number) and use it when constructing the pLimit limiter
instead of the hardcoded 10; update the code that defines "const limit =
pLimit({ concurrency: 10 })" to read the concurrency from config (fallback to 10
if undefined) so the pLimit call (and any references to "limit") honor
config.encryptionConcurrency.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Socket/messages-send.ts`:
- Around line 556-557: The pLimit instance (currently created as "const limit =
pLimit({ concurrency: 10 })" inside createParticipantNodes) must be moved to the
socket/module scope so all createParticipantNodes calls share a single
concurrency budget: create the limiter immediately after the existing
"encryptionMutex" declaration (same scope as encryptionMutex) and remove the
local "limit" declaration from inside createParticipantNodes; update references
in createParticipantNodes to use the now-top-level "limit" variable so
concurrent encryptions are globally limited.

---

Outside diff comments:
In `@src/Socket/messages-send.ts`:
- Around line 558-607: The mapped encryptionPromises incorrectly calls limit as
the mapper and uses any-casts; change to map each patchedMessages item into a
call that returns limit(async () => { ... }) so limit receives the async
function and map returns an array of Promises (i.e., replace
patchedMessages.map(limit(async ({ recipientJid: jid, message: patchedMessage }:
any) => { ... }, {})) with patchedMessages.map(item => limit(async () => { const
{ recipientJid: jid, message: patchedMessage } = item; ... })) and remove the
(patchedMessages as any) and : any parameter types by properly typing
patchedMessages and the callback; keep existing logic that uses jidDecode,
meId/meLid/meLidUser, dsmMessage, encodeWAMessage, encryptionMutex.mutex, and
signalRepository.encryptMessage and preserve the try/catch and logger calls so
behavior is unchanged.

---

Nitpick comments:
In `@src/Socket/messages-send.ts`:
- Line 556: Add a configurable encryption concurrency option: extend the
SocketConfig type (e.g., add encryptionConcurrency?: number) and use it when
constructing the pLimit limiter instead of the hardcoded 10; update the code
that defines "const limit = pLimit({ concurrency: 10 })" to read the concurrency
from config (fallback to 10 if undefined) so the pLimit call (and any references
to "limit") honor config.encryptionConcurrency.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ebc33fef-7283-44fb-b7c3-ce1abf48468f

📥 Commits

Reviewing files that changed from the base of the PR and between 78e7e4e and 7012911.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • package.json
  • src/Socket/messages-send.ts

Comment thread src/Socket/messages-send.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/Socket/messages-send.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Socket/messages-send.ts (1)

72-72: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unused import — pLimit is imported but never used.

The pLimit import is present but the code no longer uses it. The for...of loop at lines 559–608 executes encryptions sequentially (one at a time), not with a concurrency limit of 10 as stated in the PR objectives.

Either:

  1. Remove this unused import if sequential execution is intentional, or
  2. Restore the concurrency limiter at module scope (as the prior review suggested) to match the PR's stated goal of "capping parallel encryption tasks at 10"
Option 2: Restore concurrency limiting at module scope

At line 122, after encryptionMutex:

 const encryptionMutex = makeKeyedMutex()
+const encryptionLimiter = pLimit(10)

Then at lines 559–608, replace the sequential for...of with limited parallel execution:

-const nodes: BinaryNode[] = []
-
-for (const { recipientJid: jid, message: patchedMessage } of patchedMessages as any) {
-  try {
-    // ... encryption logic ...
-  } catch (err) {
-    logger.error({ jid, err }, 'Failed to encrypt for recipient')
-  }
-}
+const nodes = (await Promise.all(
+  (patchedMessages as { recipientJid: string; message: proto.IMessage }[]).map(
+    ({ recipientJid: jid, message: patchedMessage }) =>
+      encryptionLimiter(async () => {
+        try {
+          if (!jid) return null
+          // ... encryption logic returning node ...
+        } catch (err) {
+          logger.error({ jid, err }, 'Failed to encrypt for recipient')
+          return null
+        }
+      })
+  )
+)).filter((n): n is BinaryNode => n !== null)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Socket/messages-send.ts` at line 72, The import pLimit is unused: either
remove the import or reinstate the concurrency limiter to cap parallel
encryption tasks at 10; to fix, choose one approach—(A) delete the pLimit import
and keep the existing sequential encryption loop inside the function that
handles message encryption (look for the for...of loop iterating messages and
the encryptionMutex variable), or (B) restore a module-scoped limiter by
creating a pLimit(10) instance near encryptionMutex and replace the sequential
for...of with Promise-based limited tasks that call the existing per-message
encrypt function (the same function invoked inside the loop) via limiter(() =>
encryptMessage(...)) and await Promise.all on the scheduled tasks to maintain
the intended concurrency cap.
🧹 Nitpick comments (1)
src/Socket/messages-send.ts (1)

559-559: ⚡ Quick win

Avoid any in new code.

The cast patchedMessages as any bypasses type checking. Define a proper type or use the existing shape from patchMessageBeforeSending.

-for (const { recipientJid: jid, message: patchedMessage } of patchedMessages as any) {
+for (const { recipientJid: jid, message: patchedMessage } of patchedMessages as { recipientJid: string; message: proto.IMessage }[]) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Socket/messages-send.ts` at line 559, The loop currently bypasses type
checking with "patchedMessages as any"; replace that cast by giving
patchedMessages a proper typed array using the return/type shape from
patchMessageBeforeSending (e.g., an interface/alias with recipientJid and
patchedMessage/message fields) and use that type in the for...of signature (for
(const { recipientJid: jid, message: patchedMessage } of patchedMessages:
PatchedMessage[])), or import/reuse the existing return type from
patchMessageBeforeSending so the compiler can validate recipientJid and
patchedMessage instead of relying on any.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/Socket/messages-send.ts`:
- Line 72: The import pLimit is unused: either remove the import or reinstate
the concurrency limiter to cap parallel encryption tasks at 10; to fix, choose
one approach—(A) delete the pLimit import and keep the existing sequential
encryption loop inside the function that handles message encryption (look for
the for...of loop iterating messages and the encryptionMutex variable), or (B)
restore a module-scoped limiter by creating a pLimit(10) instance near
encryptionMutex and replace the sequential for...of with Promise-based limited
tasks that call the existing per-message encrypt function (the same function
invoked inside the loop) via limiter(() => encryptMessage(...)) and await
Promise.all on the scheduled tasks to maintain the intended concurrency cap.

---

Nitpick comments:
In `@src/Socket/messages-send.ts`:
- Line 559: The loop currently bypasses type checking with "patchedMessages as
any"; replace that cast by giving patchedMessages a proper typed array using the
return/type shape from patchMessageBeforeSending (e.g., an interface/alias with
recipientJid and patchedMessage/message fields) and use that type in the
for...of signature (for (const { recipientJid: jid, message: patchedMessage } of
patchedMessages: PatchedMessage[])), or import/reuse the existing return type
from patchMessageBeforeSending so the compiler can validate recipientJid and
patchedMessage instead of relying on any.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cf128699-4154-4f16-b00c-0af03cb1987c

📥 Commits

Reviewing files that changed from the base of the PR and between 6c7838f and 7972c05.

📒 Files selected for processing (1)
  • src/Socket/messages-send.ts

@Santosl2

Copy link
Copy Markdown
Contributor Author

Can run bartender @jlucaso1 @purpshell ?

@Santosl2 Santosl2 changed the title feat: add concurrency limit to prevent resource exhaustion feat: remove promise.all to prevent resource exhaustion Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants