perf(recording): reduce in-recording memory pressure hotspots#1828
perf(recording): reduce in-recording memory pressure hotspots#1828JYZ-LESLIE wants to merge 2 commits into
Conversation
|
/claim #109 |
| let mut writer = BufWriter::new(file); | ||
| if let Err(e) = serde_json::to_writer(&mut writer, &events) { | ||
| tracing::error!( | ||
| "Failed to serialize cursor data to {}: {}", | ||
| output_path.display(), | ||
| e | ||
| ); | ||
| } |
There was a problem hiding this comment.
Silent flush failure on BufWriter drop.
serde_json::to_writer serializes in chunks via Write::write_all, so BufWriter's internal buffer typically still holds the last few KB when serialization finishes. Rust's BufWriter drop calls flush() but discards the error — if it fails (e.g. disk full), the cursor file is silently truncated. An explicit writer.flush() before the block ends captures and logs that error.
| let mut writer = BufWriter::new(file); | |
| if let Err(e) = serde_json::to_writer(&mut writer, &events) { | |
| tracing::error!( | |
| "Failed to serialize cursor data to {}: {}", | |
| output_path.display(), | |
| e | |
| ); | |
| } | |
| let mut writer = BufWriter::new(file); | |
| if let Err(e) = serde_json::to_writer(&mut writer, &events) { | |
| tracing::error!( | |
| "Failed to serialize cursor data to {}: {}", | |
| output_path.display(), | |
| e | |
| ); | |
| } else if let Err(e) = writer.flush() { | |
| tracing::error!( | |
| "Failed to flush cursor data to {}: {}", | |
| output_path.display(), | |
| e | |
| ); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/cursor.rs
Line: 66-73
Comment:
Silent flush failure on BufWriter drop. `serde_json::to_writer` serializes in chunks via `Write::write_all`, so BufWriter's internal buffer typically still holds the last few KB when serialization finishes. Rust's `BufWriter` drop calls `flush()` but discards the error — if it fails (e.g. disk full), the cursor file is silently truncated. An explicit `writer.flush()` before the block ends captures and logs that error.
```suggestion
let mut writer = BufWriter::new(file);
if let Err(e) = serde_json::to_writer(&mut writer, &events) {
tracing::error!(
"Failed to serialize cursor data to {}: {}",
output_path.display(),
e
);
} else if let Err(e) = writer.flush() {
tracing::error!(
"Failed to flush cursor data to {}: {}",
output_path.display(),
e
);
}
```
How can I resolve this? If you propose a fix, please make it concise.| @@ -1246,6 +1263,22 @@ mod tests { | |||
| assert_eq!(normal, DEFAULT_MP4_MUXER_BUFFER_SIZE); | |||
| assert_eq!(instant, DEFAULT_MP4_MUXER_BUFFER_SIZE_INSTANT); | |||
| } | |||
|
|
|||
| #[test] | |||
| fn invalid_instant_override_falls_back_to_global_override() { | |||
| unsafe { | |||
| std::env::set_var("CAP_MP4_MUXER_BUFFER_SIZE", "80"); | |||
| std::env::set_var("CAP_MP4_MUXER_BUFFER_SIZE_INSTANT", "not_a_number"); | |||
| } | |||
| let normal = get_mp4_muxer_buffer_size(false); | |||
| let instant = get_mp4_muxer_buffer_size(true); | |||
| unsafe { | |||
| std::env::remove_var("CAP_MP4_MUXER_BUFFER_SIZE"); | |||
| std::env::remove_var("CAP_MP4_MUXER_BUFFER_SIZE_INSTANT"); | |||
| } | |||
| assert_eq!(normal, 80); | |||
| assert_eq!(instant, 80); | |||
| } | |||
There was a problem hiding this comment.
Env-var tests are not safe to run in parallel. Both new tests (
instant_env_override_takes_precedence_over_global_override and invalid_instant_override_falls_back_to_global_override) mutate process-wide env vars (CAP_MP4_MUXER_BUFFER_SIZE and CAP_MP4_MUXER_BUFFER_SIZE_INSTANT) without any locking. Because cargo test runs unit tests on multiple threads by default, these tests — and the pre-existing ones in this module — can observe each other's set_var/remove_var calls, producing spurious failures or incorrect passing results. The serial_test crate (or a shared Mutex in a lazy_static) is the idiomatic fix.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/output_pipeline/macos.rs
Line: 1237-1281
Comment:
Env-var tests are not safe to run in parallel. Both new tests (`instant_env_override_takes_precedence_over_global_override` and `invalid_instant_override_falls_back_to_global_override`) mutate process-wide env vars (`CAP_MP4_MUXER_BUFFER_SIZE` and `CAP_MP4_MUXER_BUFFER_SIZE_INSTANT`) without any locking. Because `cargo test` runs unit tests on multiple threads by default, these tests — and the pre-existing ones in this module — can observe each other's `set_var`/`remove_var` calls, producing spurious failures or incorrect passing results. The `serial_test` crate (or a shared `Mutex` in a `lazy_static`) is the idiomatic fix.
How can I resolve this? If you propose a fix, please make it concise.|
Addressed both Greptile findings in 97f8b7c:
Validation note: Rust toolchain is not available in this runner, so I could not execute cargo test locally here. |
/claim #109
Summary
This PR takes a scoped pass on issue #109 (memory/CPU pressure during recording), focusing on two low-risk hotspots that scale with recording duration.
1) Cursor incremental flush path: remove full-vector clone/string allocation
crates/recording/src/cursor.rsto write directly to file viaserde_json::to_writer.to_vec()+to_string_pretty()full-copy path for cursor events.2) Lower default instant muxer queue depth + per-mode override
DEFAULT_MP4_MUXER_BUFFER_SIZE_INSTANTfrom240to96incrates/recording/src/output_pipeline/macos.rs.CAP_MP4_MUXER_BUFFER_SIZE_INSTANTenv override for instant mode.CAP_MP4_MUXER_BUFFER_SIZEas the global fallback.Why this helps
Validation
cargo/rustcnot installed), so I could not execute Rust tests locally here.git diff --checkto ensure clean patch formatting.Greptile Summary
This PR reduces in-recording memory pressure by (1) rewriting cursor flush serialization to stream directly to disk via
BufWriter/serde_json::to_writerinstead of cloning the full event vector and pretty-printing it on every interval, and (2) cutting the instant-mode MP4 muxer queue default from 240 to 96 frames with a new per-mode env override.cursor.rs: The streaming serialization path avoids the O(n) allocation on every 5-second tick; change-detection tracking (last_flushed_cursor_counts) correctly skips no-op flushes when no new events have arrived.macos.rs: The env-var precedence logic (CAP_MP4_MUXER_BUFFER_SIZE_INSTANT\u2192CAP_MP4_MUXER_BUFFER_SIZE\u2192 default) is clean, and the new tests cover the key precedence/fallback combinations.Confidence Score: 3/5
The muxer queue change is safe to merge; the cursor serialization rewrite has a real silent-data-loss gap in the BufWriter flush path that should be addressed before shipping.
The cursor flush path drops the BufWriter without an explicit flush call — on a disk-full or I/O error, the final buffered bytes are silently discarded and the cursor file is left truncated with no log message. This can happen during any flush in a long recording and would produce corrupted cursor replay data.
crates/recording/src/cursor.rs — specifically the BufWriter drop path in flush_cursor_data.
Important Files Changed
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "perf(recording): reduce cursor flush ove..." | Re-trigger Greptile