Skip to content

fix: prevent memory leak in VideoMessageRecorder and AudioMessageRecorder#40040

Open
Tomeshwari-02 wants to merge 3 commits intoRocketChat:developfrom
Tomeshwari-02:develop
Open

fix: prevent memory leak in VideoMessageRecorder and AudioMessageRecorder#40040
Tomeshwari-02 wants to merge 3 commits intoRocketChat:developfrom
Tomeshwari-02:develop

Conversation

@Tomeshwari-02
Copy link
Copy Markdown

@Tomeshwari-02 Tomeshwari-02 commented Apr 3, 2026

What does this PR do?

Fixes a memory leak in both the video and audio message recorder components.

The setInterval timer was stored in useState, which caused:

  • Orphaned intervals continuing after component unmount
  • setState calls on unmounted components (React warning)
  • Memory buildup during extended chat sessions

Changes

  • Replaced useState<ReturnType<typeof setInterval>> with useRef for interval storage
  • Added clearInterval call in useEffect cleanup (VideoMessageRecorder)
  • Made handleUnmount synchronous so cleanup runs reliably on unmount (AudioMessageRecorder)
  • Added safety-net useEffect with empty deps to guarantee interval is cleared
  • Fixed console.log(error)console.error(error) in catch block

Resolves

#40039

Summary by CodeRabbit

  • Bug Fixes

    • Resolved a memory leak in audio and video message recording by reliably clearing timers and resetting recording state on stop and unmount, improving stability and resource usage.
    • Switched recorder error output to more prominent logging for easier diagnostics.
  • Chores

    • Added a patch release entry documenting the recorder fixes.

@Tomeshwari-02 Tomeshwari-02 requested a review from a team as a code owner April 3, 2026 10:52
@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot bot commented Apr 3, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 3, 2026

🦋 Changeset detected

Latest commit: d4bea70

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@rocket.chat/meteor Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/model-typings Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-client Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/ui-voip Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/abac Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch
@rocket.chat/server-fetch Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: db37d7a8-ca5e-433e-990e-f8b5f1aef679

📥 Commits

Reviewing files that changed from the base of the PR and between 47c08a8 and d4bea70.

📒 Files selected for processing (1)
  • apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx

Walkthrough

Replaced state-managed interval handles with ref-based recordingIntervalRef in audio and video recorders, added centralized clearRecordingInterval() and memoized callbacks, ensured synchronous interval cleanup on unmount and stop, and changed an audio error log to console.error.

Changes

Cohort / File(s) Summary
Audio Recorder
apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx
Replaced state recordingInterval with recordingIntervalRef; added clearRecordingInterval(); handleRecord stores setInterval in the ref; stopRecording and unmount cleanup call the clearer synchronously; changed console.log to console.error.
Video Recorder
apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx
Migrated interval storage to recordingIntervalRef; added memoized clearRecordingInterval(); refactored stopVideoRecording and handleRecord to use the ref; updated useEffect teardown to clear interval before stopping the recorder and dispatching stop action; adjusted hook dependencies.
Release Notes
.changeset/fix-recorder-interval-memory-leak.md
Added a changeset entry documenting a patch that fixes a memory leak where recorder setInterval callbacks were not cleared on unmount.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing a memory leak in VideoMessageRecorder and AudioMessageRecorder components by replacing state-managed intervals with refs.
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.


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
Contributor

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

Choose a reason for hiding this comment

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

No issues found across 2 files

Copy link
Copy Markdown
Contributor

@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

Caution

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

⚠️ Outside diff range comments (1)
apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx (1)

58-76: ⚠️ Potential issue | 🟠 Major

Guard handleRecord() when audioRecorder.start() outlives the component.

audioRecorder.start() is awaited at Line 66. If the component unmounts while permission/stream setup is still pending, cleanup runs first, but this continuation can still reach Lines 67-76 afterward, restart chat?.action.performContinuously(...), create a fresh interval, and call state setters on an unmounted component. That recreates the orphaned-timer path this PR is trying to remove. Add a teardown flag and bail out immediately after the await, stopping the recorder again if unmount already happened.

Possible guard
+	const didUnmountRef = useRef(false);
+
 	const handleRecord = useEffectEvent(async () => {
 		chat?.composer?.setRecordingMode(true);
@@
 		try {
 			await audioRecorder.start();
+			if (didUnmountRef.current) {
+				audioRecorder.stop(() => undefined);
+				return;
+			}
 			chat?.action.performContinuously('recording');
@@
 	useEffect(() => {
+		didUnmountRef.current = false;
 		handleRecord();
 
 		return () => {
+			didUnmountRef.current = true;
 			handleUnmount();
 		};
 	}, [handleUnmount, handleRecord]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx`
around lines 58 - 76, The async handleRecord function must bail out if the
component unmounted while awaiting audioRecorder.start(); add a local teardown
flag (e.g., let cancelled = false) that the cleanup/unmount path sets to true,
then immediately after await audioRecorder.start() check the flag and if true
call audioRecorder.stop()/cleanup and return; this prevents calling
chat?.action.performContinuously, creating recordingIntervalRef, or calling
setTime/setRecordingRoomId on an unmounted component—update the cleanup logic to
set the flag and clear any interval so the guard works with
recordingIntervalRef, chat?.composer?.setRecordingMode, and setRecordingRoomId.
🧹 Nitpick comments (1)
apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx (1)

108-114: Remove the second unmount-only cleanup.

The main effect cleanup at Lines 103-105 already calls handleUnmount(), and handleUnmount() already clears the interval. This extra effect only duplicates teardown and adds an inline comment plus an eslint-disable to keep it compiling.

Suggested simplification
-	// Ensure interval is cleared on unmount as a safety net
-	useEffect(() => {
-		return () => {
-			clearRecordingInterval();
-		};
-	// eslint-disable-next-line react-hooks/exhaustive-deps
-	}, []);

As per coding guidelines, "Avoid code comments in the implementation".

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

In
`@apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx`
around lines 108 - 114, Remove the redundant unmount-only effect in
AudioMessageRecorder.tsx: delete the useEffect block that only calls
clearRecordingInterval on unmount (the second effect around useEffect(() => {
return () => { clearRecordingInterval(); }; }, [])), since handleUnmount already
performs the same cleanup; ensure handleUnmount remains responsible for clearing
the interval and remove the related eslint-disable comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx`:
- Around line 115-118: The unmount cleanup currently only calls
clearRecordingInterval() and VideoRecorder.stop(), leaving the
UserAction.performContinuously interval running; update the component cleanup
(the returned function in VideoMessageRecorder’s effect/unmount path) to also
call UserAction.stop() when a recording is active (use the same recording
state/flag used by handleRecord() or check VideoRecorder.isRecording if
available) so the interval started by UserAction.performContinuously(...) is
cleared; ensure you reference and call UserAction.stop() alongside
clearRecordingInterval() and VideoRecorder.stop() to fully tear down the
recording-related timers.

---

Outside diff comments:
In
`@apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx`:
- Around line 58-76: The async handleRecord function must bail out if the
component unmounted while awaiting audioRecorder.start(); add a local teardown
flag (e.g., let cancelled = false) that the cleanup/unmount path sets to true,
then immediately after await audioRecorder.start() check the flag and if true
call audioRecorder.stop()/cleanup and return; this prevents calling
chat?.action.performContinuously, creating recordingIntervalRef, or calling
setTime/setRecordingRoomId on an unmounted component—update the cleanup logic to
set the flag and clear any interval so the guard works with
recordingIntervalRef, chat?.composer?.setRecordingMode, and setRecordingRoomId.

---

Nitpick comments:
In
`@apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx`:
- Around line 108-114: Remove the redundant unmount-only effect in
AudioMessageRecorder.tsx: delete the useEffect block that only calls
clearRecordingInterval on unmount (the second effect around useEffect(() => {
return () => { clearRecordingInterval(); }; }, [])), since handleUnmount already
performs the same cleanup; ensure handleUnmount remains responsible for clearing
the interval and remove the related eslint-disable comment.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f480a7eb-e646-4df4-adb0-a52fcd4d8e72

📥 Commits

Reviewing files that changed from the base of the PR and between 8ebf44b and 7c790b3.

📒 Files selected for processing (2)
  • apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx
  • apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx
  • apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx
🧠 Learnings (2)
📚 Learning: 2026-03-27T14:52:56.865Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39892
File: apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx:150-155
Timestamp: 2026-03-27T14:52:56.865Z
Learning: In Rocket.Chat, there are two different `ModalBackdrop` components with different prop APIs. During review, confirm the import source: (1) `rocket.chat/fuselage` `ModalBackdrop` uses `ModalBackdropProps` based on `BoxProps` (so it supports `onClick` and other Box/DOM props) and does not have an `onDismiss` prop; (2) `rocket.chat/ui-client` `ModalBackdrop` uses a narrower props interface like `{ children?: ReactNode; onDismiss?: () => void }` and handles Escape keypress and outside mouse-up, and it does not forward arbitrary DOM props such as `onClick`. Flag mismatched props (e.g., `onDismiss` passed to the fuselage component or `onClick` passed to the ui-client component) and ensure the usage matches the correct component being imported.

Applied to files:

  • apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx
  • apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx
📚 Learning: 2026-01-19T18:08:13.423Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38169
File: packages/ui-client/src/hooks/useGoToDirectMessage.ts:20-27
Timestamp: 2026-01-19T18:08:13.423Z
Learning: In React hooks, including custom hooks like useUserSubscriptionByName, must always be called unconditionally and in the same order on every render, per the Rules of Hooks. Passing empty strings or fallback values to hooks is the correct approach when dealing with potentially undefined values, rather than conditionally calling the hook based on whether the value exists.

Applied to files:

  • apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx

@coderabbitai coderabbitai bot removed the type: bug label Apr 3, 2026
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Tomeshwari-02 seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

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.

2 participants