Skip to content

fix: Reopen a frozen DDP socket by correlating the health round trip - #7549

Open
Rohit3523 wants to merge 3 commits into
developfrom
ddp-probe-pong-correlation
Open

fix: Reopen a frozen DDP socket by correlating the health round trip#7549
Rohit3523 wants to merge 3 commits into
developfrom
ddp-probe-pong-correlation

Conversation

@Rohit3523

@Rohit3523 Rohit3523 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Proposed changes

After the phone sleeps long enough for the DDP ping to go stale, an open room stops showing new messages for the rest of the session. Leaving and re-entering makes them appear — which looks like a rendering bug but is not: that is RoomView refetching over REST while the server-side stream stays dead.

Two pieces of evidence used to answer "is this socket still usable?" were wrong. Either one alone can leave a dead session in place.

1. probe() had no request/response correlation. It sent {msg:'ping'} with no id and resolved on the first pong event. When the OS unfreezes a backgrounded app it flushes the frames buffered during the freeze, so a pong answering a pre-freeze ping — or one the client provoked by auto-replying to a queued server ping — vouched for a session the server had already dropped. The lastPing <= lastPingAtStart check could not close this, because onMessage refreshes lastPing before emitting: it only proved something arrived, not that the server answered us.

2. classifySocketHealth aged the socket against lastPing, which onMessage refreshes for every inbound frame — so it measures traffic, not heartbeat health. One flushed message made a socket frozen for minutes look seconds old, sending it to a round trip instead of straight to a reopen.

When both misfire nothing is reopened: no close, no connecting, redux stays connected, the resume login never runs, subscribeAll() never re-sends the streams, and the room is deaf until something else triggers a sync.

This PR makes the ping carry an id and accept only the pong echoing it, and adds a lastPongAt clock that only a real server answer advances.

File Change
patches/@rocket.chat+sdk+1.3.3-mobile.patch probe() puts an id on its ping and accepts only the matching pong, registered with on rather than once so an uncorrelated pong cannot consume the listener. Adds lastPongAt — advanced only by a pong and by the handshake reply, zeroed on reopenNow — exposed via a DDPDriver getter.
app/lib/services/socketHealth.ts Ages against lastPongAt, falling back to lastPing on a driver without the patch.
4 test files Deterministic repros for both defects, plus the new correlation contract.

The patch was regenerated from a pristine package extract and verified to reproduce node_modules byte-for-byte.

Correction to the root cause on the ticket

The ticket attributes this to the reopen emitting no close, leaving redux connected and skipping the login. That does not hold. createConnection() emits connecting, which connect.ts maps to connectRequest() and clears meteor.connected — so a real reopen does run the resume login. Verified two ways: a test in connect.test.ts driving the real connect reducer, and a device wire trace showing method: login {resume} followed by 25 sub frames.

The observations on the ticket are right; the attribution is one layer too low. The truth is simpler — no reopen happened at all, which by itself explains both the missing close and the missing login request. Flagging it because a fix aimed at the stated cause would have changed nothing.

Issue(s)

NATIVE-1471

How to test or reproduce

Affected screen: RoomView (any open room). Android emulator:

  1. Log user A into the app, open a room shared with user B.
  2. Freeze the device: screen off, then adb shell dumpsys battery unplug, then adb shell dumpsys deviceidle force-idle. Plain screen-off does not starve the socket.
  3. Hold the freeze in the 90–170s window. Under 90s the ping never goes stale; past ~200s the session ends on the login screen and heals correctly, which is a different outcome — "lock it longer to be safe" is wrong advice here.
  4. While frozen, send a message from user B (REST or a second client).
  5. Unlock with adb shell wm dismiss-keyguard. (input keyevent 82 opens the RN dev menu on debug builds.)

Before: the room does not show B's message, and messages sent live after unlock are also missing. Leaving and re-entering the room makes them appear.

After: B's message appears in the open room without leaving it, and messages sent live after unlock arrive too.

Signals beyond the UI — after unlock the wire shows, in order:

connect  →  method: login {resume}  →  sub stream-room-messages <rid>

Tip for repeated cycles: use adb shell input keyevent 223 / 224 (SLEEP / WAKEUP) rather than 26 (POWER). 26 is a toggle and drifts out of phase, which silently inverts the freeze phases.

Screenshots

Not applicable — no visual change. The observable difference is message delivery; evidence is the wire trace above and the soak results below.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable) — n/a
  • Any dependent changes have been merged and published in downstream modules — n/a, the SDK change ships in this repo's patches/

Further comments

Why this approach

A liveness check that cannot recognise its own answer is not a liveness check. DDP ping/pong carries an optional id and the server echoes it, so correlating is the smallest change that makes the round trip mean what its name says. It fails safe: an unanswered probe reopens rather than trusting a dead socket.

lastPongAt is defence in depth — with correlation in place, either fix alone resolves the reported symptom. It also stops a misleadingly-named field from being read as "last heartbeat" when it means "last frame".

Alternative considered and rejected: always reopen after any background gap. That discards the round-trip check and pays a full resume login, subscribeAll() and rooms refetch on every app switch.

Verification

  • Unit/integration: full suite green (2108 passing). The new repros fail without the fix and pass with it, driving the real patched driver over a mocked WebSocket. One red storyshot (HeadersWithFormatting) also fails on clean develop.
  • Live server: confirmed Rocket.Chat echoes the ping id — {msg:'ping',id:'probe-0'}{msg:'pong',id:'probe-0'}. This is a prerequisite: without the echo, probe() would always time out and every gray-zone foreground would pay a needless reconnect.
  • Device soak: 30 consecutive freeze cycles on an Android emulator, durations rotating through 95/110/125/140/155/168s to sample the whole window. All 30 produced the full recovery ladder, and all 30 delivered a message posted after unlock over the live stream.

Summary by CodeRabbit

  • Bug Fixes

    • Improved detection of frozen or unresponsive connections, including cases where stale network data could appear healthy.
    • Prevented outdated connection responses from interfering with newer connections.
    • Improved reconnection reliability and ensured interrupted sessions can resume authentication smoothly.
  • Improvements

    • Added more reliable connection health checks and coordinated reconnection handling.
    • Improved restoration of subscriptions, including user media updates, after reconnecting.

…ened

After the OS freezes a backgrounded app past the ping deadline, the server drops
the DDP session. On resume the app decided the socket was still usable, so it was
never reopened: no `close`, no `connecting`, no resume login, and `subscribeAll()`
never re-sent the streams. An open room went deaf for the rest of the session and
only appeared to heal because re-entering it refetches over REST.

Two pieces of evidence were wrong.

`probe()` had no request/response correlation. It sent `{msg:'ping'}` with no id
and resolved on the first `pong` event, so a pong flushed from the pre-freeze
receive buffer — or one the client provoked by auto-replying to a queued server
ping — vouched for a session that was already gone. Its `lastPing <=
lastPingAtStart` check could not close this: `onMessage` refreshes `lastPing`
before emitting, so it only proved something arrived. The ping now carries an id
and only the pong echoing it counts, registered with `on` rather than `once` so an
uncorrelated pong cannot consume the listener.

`classifySocketHealth` aged the socket against `lastPing`, which `onMessage`
refreshes for every inbound frame. The backlog the OS flushes on resume made a
socket frozen for minutes look seconds old, sending it to a round trip instead of
a reopen. It now ages against `lastPongAt`, advanced only by a pong and by the
handshake reply, and falls back to `lastPing` on a driver without the patch.

Verified against a live server, which echoes the ping id, and on an Android
emulator: the freeze now produces connect -> login {resume} -> stream-room-messages
re-subscribed, and messages sent during and after the freeze both arrive.

Note the reported cause — that the reopen emits no `close`, leaving redux
connected and skipping the login — does not hold: `createConnection` emits
`connecting`, which clears `meteor.connected`, so a real reopen does run the
login. Tests in connect.test.ts pin that behaviour against the real reducer.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The patch adds pong-aware socket health checks, correlated liveness probes, guarded socket replacement, immediate reconnection, resume-login coverage, and media subscription resynchronization.

Changes

DDP liveness and recovery

Layer / File(s) Summary
Socket lifecycle and recovery
patches/@rocket.chat+sdk+1.3.3-mobile.patch
Socket replacement, immediate reconnection, liveness reset, driver forwarding, and client driver retention were added.
Correlated liveness probes
patches/@rocket.chat+sdk+1.3.3-mobile.patch, app/lib/services/ddpSocket.test.ts
Probes now use unique ping IDs and accept only matching pong responses.
Pong-aware health classification
app/lib/services/socketHealth.ts, app/lib/services/__tests__/socketHealth.test.ts, app/lib/services/__tests__/socketHealth.integration.test.ts
Health checks use lastPongAt when available and cover frozen-socket recovery scenarios.
Connection resume and media subscriptions
app/lib/services/connect.test.ts, patches/@rocket.chat+sdk+1.3.3-mobile.patch
Silent socket replacement tests verify resume-login behavior. Media subscriptions are resynchronized with bounded readiness polling.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConnectReducer
  participant DDPDriver
  participant Socket
  participant DDPServer
  ConnectReducer->>DDPDriver: detect silent socket replacement
  DDPDriver->>Socket: probe()
  Socket->>DDPServer: send ping with unique ID
  DDPServer-->>Socket: return matching pong
  Socket-->>DDPDriver: resolve probe
  DDPDriver->>Socket: reopenNow() when health remains stale
  DDPDriver-->>ConnectReducer: emit disconnected and connected
  ConnectReducer->>DDPDriver: dispatch resume login
Loading

Possibly related PRs

Suggested labels: type: bug

Suggested reviewers: otaviostasiak

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 title clearly and concisely describes the main change: reopening frozen DDP sockets by correlating health probes.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (2)
  • NATIVE-1471: Request failed with status code 401
  • PROBE-0: Request failed with status code 401

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.

@Rohit3523

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

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)
patches/@rocket.chat+sdk+1.3.3-mobile.patch (1)

159-181: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Keep the reconnect guard until the connection attempt terminates.

Line 179 discards a createConnection() failure. Line 181 then calls cleanup(), which deletes reopenPromise and resolves waiting callers even when no open event occurred. A later health check can start another createConnection() while the first WebSocket is still connecting. Lines 70-76 then close that first socket.

Make the deadline cancel or fail the active attempt. Resolve only after open. Reject or return an explicit failure result on error and timeout. Add coverage for a replacement socket that never opens.

🤖 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 `@patches/`@rocket.chat+sdk+1.3.3-mobile.patch around lines 159 - 181, Update
the reconnect flow around reopenPromise and createConnection so the reconnect
guard remains active until the connection attempt actually terminates. Propagate
createConnection failures and make the deadline cancel or fail the active
attempt; do not resolve or delete reopenPromise unless open occurs, and reject
or return an explicit failure on error or timeout. Add coverage for a
replacement socket that never emits open, ensuring a later health check cannot
start a concurrent connection attempt.
🧹 Nitpick comments (1)
app/lib/services/connect.test.ts (1)

428-440: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add return types to the new named test helpers.

  • app/lib/services/connect.test.ts#L428-L440: declare (): () => IConnect.
  • app/lib/services/connect.test.ts#L454-L457: declare (): Array<{ type: string; credentials?: unknown }>.
  • app/lib/services/__tests__/socketHealth.integration.test.ts#L110-L112: declare : void.
  • app/lib/services/__tests__/socketHealth.integration.test.ts#L259-L261: declare : void.

As per coding guidelines, add explicit type annotations to function parameters and return types.

🤖 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 `@app/lib/services/connect.test.ts` around lines 428 - 440, Explicitly annotate
the named test helpers’ return types: app/lib/services/connect.test.ts lines
428-440 should return () => IConnect, and lines 454-457 should return Array<{
type: string; credentials?: unknown }>;
app/lib/services/__tests__/socketHealth.integration.test.ts lines 110-112 and
259-261 should return void. Add parameter annotations where required by the
coding guidelines without changing helper behavior.

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.

Inline comments:
In `@app/lib/services/ddpSocket.test.ts`:
- Around line 148-159: Update the test around socket.probe() to store both
returned promises, then await/assert their timeout results after
advanceTimersByTimeAsync(2000). Preserve the distinct-ID assertion while
ensuring both Promise<boolean> values are explicitly handled so rejections fail
the test.

---

Outside diff comments:
In `@patches/`@rocket.chat+sdk+1.3.3-mobile.patch:
- Around line 159-181: Update the reconnect flow around reopenPromise and
createConnection so the reconnect guard remains active until the connection
attempt actually terminates. Propagate createConnection failures and make the
deadline cancel or fail the active attempt; do not resolve or delete
reopenPromise unless open occurs, and reject or return an explicit failure on
error or timeout. Add coverage for a replacement socket that never emits open,
ensuring a later health check cannot start a concurrent connection attempt.

---

Nitpick comments:
In `@app/lib/services/connect.test.ts`:
- Around line 428-440: Explicitly annotate the named test helpers’ return types:
app/lib/services/connect.test.ts lines 428-440 should return () => IConnect, and
lines 454-457 should return Array<{ type: string; credentials?: unknown }>;
app/lib/services/__tests__/socketHealth.integration.test.ts lines 110-112 and
259-261 should return void. Add parameter annotations where required by the
coding guidelines without changing helper behavior.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 73eaccb9-bc86-441c-b0a7-b6f295bb3671

📥 Commits

Reviewing files that changed from the base of the PR and between 89071f3 and 8fd9ef8.

📒 Files selected for processing (6)
  • app/lib/services/__tests__/socketHealth.integration.test.ts
  • app/lib/services/__tests__/socketHealth.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/services/ddpSocket.test.ts
  • app/lib/services/socketHealth.ts
  • patches/@rocket.chat+sdk+1.3.3-mobile.patch
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/services/socketHealth.ts
  • app/lib/services/__tests__/socketHealth.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/services/__tests__/socketHealth.integration.test.ts
  • app/lib/services/ddpSocket.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Files:

  • app/lib/services/socketHealth.ts
  • app/lib/services/__tests__/socketHealth.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/services/__tests__/socketHealth.integration.test.ts
  • app/lib/services/ddpSocket.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Format JavaScript and TypeScript code with Oxfmt using the repository configuration: tabs, single quotes, 130-character width, no trailing commas, omitted arrow-function parentheses where possible, and same-line brackets.
Follow Oxlint rules configured in .oxlintrc.json, including the import, React, Jest, TypeScript, and React Native plugins.

Files:

  • app/lib/services/socketHealth.ts
  • app/lib/services/__tests__/socketHealth.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/services/__tests__/socketHealth.integration.test.ts
  • app/lib/services/ddpSocket.test.ts
🧠 Learnings (3)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/services/socketHealth.ts
  • app/lib/services/__tests__/socketHealth.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/services/__tests__/socketHealth.integration.test.ts
  • app/lib/services/ddpSocket.test.ts
📚 Learning: 2026-06-25T18:37:25.526Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.test.tsx:16-22
Timestamp: 2026-06-25T18:37:25.526Z
Learning: In Rocket.Chat ReactNative tests that mock selectors for `useAppSelector`, don’t require the mocked selector input to be typed as `IApplicationState` when the fixture only includes a partial Redux state slice (e.g., only `server` and `settings`). Requiring the full `IApplicationState` type in that scenario forces unsafe `as IApplicationState` casts and undermines type-safety. For these narrowly scoped selector-mock fixtures, use a less strict type (e.g., `any`) to keep the mock focused on the slice under test.

Applied to files:

  • app/lib/services/__tests__/socketHealth.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/services/__tests__/socketHealth.integration.test.ts
  • app/lib/services/ddpSocket.test.ts
📚 Learning: 2026-05-05T21:08:33.177Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7298
File: patches/@rocket.chat+sdk+1.3.3-mobile.patch:79-79
Timestamp: 2026-05-05T21:08:33.177Z
Learning: In the RocketChat/Rocket.Chat.ReactNative repo, for patches under patches/*.patch (especially those touching rocket.chat/sdk), remember that the patching sandbox's node_modules reflects the pre-patch state. Grepping node_modules for symbols (e.g., userDisconnectCloseCode = 4000 in node_modules/rocket.chat/sdk/lib/drivers/ddp.ts) can yield false positives. Review patches by inspecting the diff and applying it to a fresh copy of the SDK or diffing against the SDK source, rather than relying on node_modules. Ensure the patch actually introduces/updates symbols in the SDK source and run the test suite to validate behavior after applying the patch.

Applied to files:

  • patches/@rocket.chat+sdk+1.3.3-mobile.patch
🔇 Additional comments (6)
patches/@rocket.chat+sdk+1.3.3-mobile.patch (1)

5-142: LGTM!

Also applies to: 187-359, 372-384

app/lib/services/ddpSocket.test.ts (1)

15-16: LGTM!

Also applies to: 73-83, 109-146

app/lib/services/socketHealth.ts (1)

12-13: LGTM!

Also applies to: 37-41

app/lib/services/__tests__/socketHealth.test.ts (1)

18-18: LGTM!

Also applies to: 75-91

app/lib/services/__tests__/socketHealth.integration.test.ts (1)

2-2: LGTM!

Also applies to: 29-35, 54-56, 106-109, 144-144, 156-156, 190-190, 206-206, 235-235, 248-258, 262-327, 329-330

app/lib/services/connect.test.ts (1)

1-8: LGTM!

Also applies to: 422-427, 442-452, 459-489

Comment thread app/lib/services/ddpSocket.test.ts
The two `socket.probe()` promises were discarded, so a rejection would surface as an
unhandled rejection instead of failing the test, and neither timeout result was
asserted. Hold both and assert each resolves false.
@Rohit3523

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Rohit3523

Rohit3523 commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Baseline results on unfixed develop

Ran the ticket's reproduction 30 times on each side, same harness, same emulator, freeze durations rotating through 95/110/125/140/155/168s.

blind cycles
unfixed develop (clean install from lockfile) 1 / 30 (cycle 3)
this branch 0 / 30

"Blind" = a message posted after unlock never arrived over stream-room-messages, i.e. the ticket's worst-case symptom.

This does not statistically demonstrate the fix

0/30 vs 1/30 is a single event of difference; Fisher's exact gives p ≈ 1.0. At the ticket's own rate (3/28 ≈ 11%) you would need far more cycles per arm to separate the two. So the soak shows no regression and confirms the defect is still reachable on develop, but the evidence that this change fixes it rests on the deterministic unit/integration repros, which fail without it and pass with it.

And the one baseline failure is probably a different defect

Cycle 3's wire trace:

517s OUT connect      <- unfroze exactly on schedule (125s freeze, valid window)
519s OUT login
519s OUT sub-room     <- stream re-subscribed
 ... BLIVE-3 posted ~110s later, never received ...
827s OUT connect      <- cycle 4

It reopened, re-authenticated and re-subscribed, and still lost the message. This PR makes a frozen socket reopen; it cannot help a case where the reopen already happened. Candidate causes: the sub frame written but never registered server-side, a re-subscribe rejected as a duplicate id, or the sync/resubscribe ordering handled on room-resync-after-resubscribe. Not diagnosed — I did not record ready/nosub frames.

Harness caveat

11 of 30 baseline cycles overran their script because adb/curl intermittently stall on a dozing device. Cycle 3 was one of them, but its stall landed after unfreeze (proven by the on-time connect at 517s), so its freeze was a valid 125s and the result stands. For other stalled cycles the freeze itself overran, putting them outside the 90–170s window. Any future run should wrap every adb/curl in timeout so a hang cannot reshape a cycle.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.76.0.109501

@Rohit3523
Rohit3523 marked this pull request as ready for review August 5, 2026 14:30
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.

1 participant