Skip to content

Harden server with panic reduction and resource limits#14

Merged
Pajn merged 1 commit into
mainfrom
prod-hardening
Jun 30, 2026
Merged

Harden server with panic reduction and resource limits#14
Pajn merged 1 commit into
mainfrom
prod-hardening

Conversation

@Pajn

@Pajn Pajn commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Daily-rotating server logs in the socket directory, with tracing/panic output recorded there.
    • Unified, configurable log filtering (including --log-level / --log and environment-variable overrides) across foreground and detached server modes.
    • Tunable server resource limits (sessions, buffers, scrollback) via environment variables, enforced with clear rejection errors.
  • Bug Fixes
    • Improved robustness around queueing/broadcasting limits, id exhaustion handling, and mutex-poison recovery.
  • Documentation
    • Added/expanded configuration guide covering logging precedence and resource-limit behavior.
  • Tests
    • Updated interactive tests to use more reliable socket-path setup.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Pajn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a87e6b02-0a44-4283-b6aa-9bdca0a40524

📥 Commits

Reviewing files that changed from the base of the PR and between 43f940f and 73b4451.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • Cargo.toml
  • README.md
  • crates/embers-cli/src/bin/embers-cli.rs
  • crates/embers-cli/src/lib.rs
  • crates/embers-cli/src/main.rs
  • crates/embers-cli/tests/interactive.rs
  • crates/embers-core/Cargo.toml
  • crates/embers-core/src/diagnostics.rs
  • crates/embers-core/src/lib.rs
  • crates/embers-server/src/buffer_runtime.rs
  • crates/embers-server/src/config.rs
  • crates/embers-server/src/lib.rs
  • crates/embers-server/src/server.rs
  • crates/embers-server/src/state.rs
  • crates/embers-server/src/terminal_backend.rs
  • docs/configuration.md
📝 Walkthrough

Walkthrough

Adds rotating file logging for the detached server process (daily log rotation via tracing-appender, with a panic hook), propagates the resolved log filter from CLI into the detached server via environment, introduces env-var-configurable ResourceLimits (sessions, buffers, scrollback) enforced at creation time, switches outbound channels to bounded queues, and replaces panic-prone assumptions with graceful error handling throughout server internals and state code.

Changes

Server Logging, Resource Limits, and Hardening

Layer / File(s) Summary
Logging infrastructure
Cargo.toml, crates/embers-core/Cargo.toml, crates/embers-core/src/diagnostics.rs, crates/embers-core/src/lib.rs
Adds tracing-appender workspace dependency, implements init_server_tracing with daily-rotating file appender and stderr fallback, exports filename constants and retention ceiling, re-exports the new function from embers-core.
CLI log filter propagation and panic hook
crates/embers-cli/src/lib.rs, crates/embers-cli/src/bin/embers-cli.rs, crates/embers-cli/src/main.rs, crates/embers-cli/tests/interactive.rs
Introduces EMBERS_LOG_ENV_VAR constant with visible log-level alias, updates log_filter() to read from environment variable, threads resolved filter through run(), passes filter into detached-server child process via env, initializes server tracing and panic hook in run_server(), conditions foreground init_tracing on non-Serve commands, centralizes interactive test helpers for tempdir and socket-path derivation.
ResourceLimits configuration and defaults
crates/embers-server/src/config.rs, crates/embers-server/src/lib.rs, crates/embers-server/src/terminal_backend.rs
Defines environment-variable and default constants for max sessions, buffers, and scrollback, implements ResourceLimits with from_env() parsing/validation (rejects zero/empty/unparseable), wires limits into ServerConfig, re-exports ResourceLimits, reads max_scrollback_lines from env in terminal backend initialization.
Bounded channels and runtime limit enforcement
crates/embers-server/src/server.rs
Wires ResourceLimits into Runtime::new(), introduces OUTBOUND_CHANNEL_CAPACITY for bounded per-client queues, changes subscription sender from unbounded to bounded type, enforces max_sessions and max_buffers at creation returning ErrorCode::Conflict, guards open-history-buffer creation by max_buffers, changes broadcast from send to bounded try_send with slow-client detachment and warnings, recovers from poisoned locks in shutdown/intent paths, updates all test sites with resource limits and bounded channels, adds new async tests for session/buffer limit rejection.
Panic-free error handling: id exhaustion, state assumptions, and lock recovery
crates/embers-server/src/state.rs, crates/embers-server/src/buffer_runtime.rs, crates/embers-server/src/server.rs, crates/embers-server/src/terminal_backend.rs
Makes id allocation fallible with next_id_after_max() -> Option<u64> and checked_next_id() -> Result, rejects persisted workspaces with exhausted id spaces, saturates serialized next-id hints, refactors join_buffer_at_node tabs-container logic, hardens PTY retry error message and start_pipe flow, removes panicking assumptions in focus_revealed_buffer and record_buffer_update, recovers from poisoned mutex locks in take_buffer_shutdown_intent, shutdown_runtimes, and AlacrittyTerminalBackend metadata/activity paths, adds exhaustion and fallibility tests.
Operational documentation
docs/configuration.md, README.md
Adds complete configuration documentation covering socket path resolution, logging filter precedence and destinations (stderr vs rotating file), and resource limit enforcement semantics with memory bounds. Updates README with Logging and Resource Limits sections and cross-reference to docs/configuration.md.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as embers CLI
  participant Ensure as ensure_server_process
  participant ServerProc as Detached Server Process
  participant InitServer as init_server_tracing
  participant RollingAppender as Rolling File Appender
  participant Runtime as Runtime
  participant Client as Connected Client

  CLI->>CLI: log_filter = cli.log_filter()
  CLI->>Ensure: ensure_server_process(socket, log_filter)
  Ensure->>ServerProc: spawn with EMBERS_LOG=log_filter
  ServerProc->>InitServer: init_server_tracing(log_filter, socket_dir)
  InitServer->>RollingAppender: create daily-rotating appender
  ServerProc->>ServerProc: install panic hook for tracing
  Client->>Runtime: SessionRequest::Create
  Runtime->>Runtime: check sessions.len() vs max_sessions
  alt at limit
    Runtime-->>Client: ErrorCode::Conflict (EMBERS_MAX_SESSIONS)
  else under limit
    Runtime-->>Client: session created
  end
  Runtime->>Client: broadcast event via try_send
  alt channel full (slow client)
    Runtime->>Runtime: drop slow client subscription + warn
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Pajn/Embers#7: Both PRs modify crates/embers-server/src/buffer_runtime.rs in the PTY "runtime keeper" flow—specifically run_runtime_keeper/KeeperRuntime::start_pipe—so the main PR's changes overlap directly in the same codepath.

  • Pajn/Embers#13: The main PR's changes to crates/embers-server/src/buffer_runtime.rs (notably KeeperRuntime::start_pipe flow) overlap with the retrieved PR's implementation of buffer "output pipe" lifecycle logic in the same module.

Poem

🐇 A rabbit peers into the logs at dawn,
Files spinning round while the server rolls on.
"No more panics!" the keeper declares,
With bounded queues and limits declared.
The scrollback hops, the sessions stay sane —
A tidy warren, no resource disdain! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.56% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main focus of this changeset: hardening the server through panic reduction (via panic hooks and improved error recovery) and implementing configurable resource limits (sessions, buffers, scrollback).
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.

✏️ 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.

@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)
crates/embers-server/src/server.rs (1)

3164-3173: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Bounded send(...).await can indefinitely block connection teardown under backpressure.

When a client stops reading, the bounded outbound queue can fill and these send(...).await calls can block the read task indefinitely. That task then cannot finish detach/shutdown flows cleanly, and shutdown quiescing can hang waiting for connection tasks.

💡 Proposed fix (fail-fast on saturated outbound queue)
-        if outbound
-            .send(ServerEnvelope::Response(response))
-            .await
-            .is_err()
-        {
-            return Err(MuxError::transport("connection writer closed"));
-        }
+        match outbound.try_send(ServerEnvelope::Response(response)) {
+            Ok(()) => {}
+            Err(mpsc::error::TrySendError::Closed(_))
+            | Err(mpsc::error::TrySendError::Full(_)) => {
+                return Err(MuxError::transport("connection writer closed"));
+            }
+        }

Also applies to: 3180-3192, 3198-3207, 3218-3223

🤖 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 `@crates/embers-server/src/server.rs` around lines 3164 - 3173, The bounded
outbound queue can cause indefinite blocking when clients stop reading and
backpressure fills the queue, preventing proper connection teardown and shutdown
flows. Replace the blocking `.send(...).await` calls with a fail-fast approach
using `try_send()` instead, so that when the outbound queue is saturated, the
send operation fails immediately and returns an error rather than blocking the
read task. This change should be applied to all the protocol error response
sends in the connection handling logic, specifically in the locations around the
protocol_error_response calls and other outbound.send() invocations that occur
during frame processing and error handling.
🤖 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 `@crates/embers-server/src/server.rs`:
- Around line 940-953: The buffer limit check at the BufferRequest::Create
handler only guards direct buffer creation but does not prevent helper buffers
created via the open_history_buffer path (using state.create_helper_buffer) from
bypassing the max_buffers limit. Locate where open_history_buffer or
state.create_helper_buffer is called and add the same capacity guard (checking
if state.buffers.len() >= self.limits.max_buffers) before allowing helper buffer
creation, returning the same conflict error response if the limit would be
exceeded.

---

Outside diff comments:
In `@crates/embers-server/src/server.rs`:
- Around line 3164-3173: The bounded outbound queue can cause indefinite
blocking when clients stop reading and backpressure fills the queue, preventing
proper connection teardown and shutdown flows. Replace the blocking
`.send(...).await` calls with a fail-fast approach using `try_send()` instead,
so that when the outbound queue is saturated, the send operation fails
immediately and returns an error rather than blocking the read task. This change
should be applied to all the protocol error response sends in the connection
handling logic, specifically in the locations around the protocol_error_response
calls and other outbound.send() invocations that occur during frame processing
and error handling.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 18b96774-6b7c-44b7-81a4-427c86a7c5f6

📥 Commits

Reviewing files that changed from the base of the PR and between f0355bd and 3dc8d41.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • Cargo.toml
  • README.md
  • crates/embers-cli/src/bin/embers-cli.rs
  • crates/embers-cli/src/lib.rs
  • crates/embers-cli/src/main.rs
  • crates/embers-cli/tests/interactive.rs
  • crates/embers-core/Cargo.toml
  • crates/embers-core/src/diagnostics.rs
  • crates/embers-core/src/lib.rs
  • crates/embers-server/src/buffer_runtime.rs
  • crates/embers-server/src/config.rs
  • crates/embers-server/src/lib.rs
  • crates/embers-server/src/server.rs
  • crates/embers-server/src/state.rs
  • crates/embers-server/src/terminal_backend.rs
  • docs/configuration.md

Comment thread crates/embers-server/src/server.rs
@Pajn Pajn force-pushed the prod-hardening branch from 3dc8d41 to 43f940f Compare June 20, 2026 18:06
@Pajn

Pajn commented Jun 20, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
✅ 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.

@Pajn

Pajn commented Jun 21, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
✅ 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

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@crates/embers-server/src/config.rs`:
- Around line 115-126: The test function that calls parse_limit_env with unsafe
environment variable mutations lacks serialization guarantees and will race when
tests run in parallel. Add the serial_test crate as a test dependency and
annotate this test function with the #[serial] attribute to ensure it executes
serially and prevents concurrent access to the global environment state. This
guarantees the SAFETY comment's assumption of single-threaded execution holds
true even when cargo test runs multiple tests concurrently.

In `@crates/embers-server/src/terminal_backend.rs`:
- Line 159: The scrolling_history field is being initialized from the
environment using from_env() which can be overridden by client-supplied
environment overlays, allowing clients to bypass the operator-configured
scrollback limit. Instead of reading from the environment at runtime
initialization, use a server-enforced scrollback limit that is set from the
environment once during server startup and then reused consistently across all
buffer spawns, ensuring the limit cannot be bypassed by client-supplied
environment variables.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 3d801d66-283f-494e-8640-f1a83eba4177

📥 Commits

Reviewing files that changed from the base of the PR and between 3dc8d41 and 43f940f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • Cargo.toml
  • README.md
  • crates/embers-cli/src/bin/embers-cli.rs
  • crates/embers-cli/src/lib.rs
  • crates/embers-cli/src/main.rs
  • crates/embers-cli/tests/interactive.rs
  • crates/embers-core/Cargo.toml
  • crates/embers-core/src/diagnostics.rs
  • crates/embers-core/src/lib.rs
  • crates/embers-server/src/buffer_runtime.rs
  • crates/embers-server/src/config.rs
  • crates/embers-server/src/lib.rs
  • crates/embers-server/src/server.rs
  • crates/embers-server/src/state.rs
  • crates/embers-server/src/terminal_backend.rs
  • docs/configuration.md

Comment on lines +115 to +126
// SAFETY: single-threaded test; we set and remove the var within it.
let var = "EMBERS_TEST_PARSE_LIMIT_ENV";
for value in ["0", "", " ", "nope", "-5"] {
unsafe { std::env::set_var(var, value) };
assert_eq!(
parse_limit_env(var),
None,
"value {value:?} should be rejected"
);
}
unsafe { std::env::set_var(var, " 42 ") };
assert_eq!(parse_limit_env(var), Some(42));

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find env mutation sites in Rust tests/source.
rg -n --type rust 'std::env::set_var|std::env::remove_var' crates

# Check for explicit test serialization markers/usages.
rg -n --type rust '#\[(serial|serial_test::serial)\]|serial_test' .

# Check common CI/workflow files for single-threaded test execution.
fd -HI '.github|Cargo.toml|Makefile|justfile' | xargs -I{} sh -c 'echo "---- {} ----"; rg -n "test-threads|RUST_TEST_THREADS|cargo test" "{}" || true'

Repository: Pajn/Embers

Length of output: 323


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look at the test function definition and surrounding context in the file
echo "=== Test function context ==="
rg -B5 -A10 'parse_limit_env.*value' crates/embers-server/src/config.rs | head -40

# Check for any #[test] or serialization attributes on this test
echo -e "\n=== Test attributes around lines 115-130 ==="
sed -n '110,130p' crates/embers-server/src/config.rs

# Check Cargo.toml for test-related configurations
echo -e "\n=== Cargo.toml test configurations ==="
rg -A5 '\[\[test\]\]|\[profile.test\]|test-threads' Cargo.toml crates/embers-server/Cargo.toml 2>/dev/null || true

# Check if there's a .cargo/config.toml with test settings
echo -e "\n=== .cargo/config.toml ==="
cat .cargo/config.toml 2>/dev/null || echo "No .cargo/config.toml found"

Repository: Pajn/Embers

Length of output: 85


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the specific file and show lines around the test
echo "=== Lines 100-135 of config.rs ==="
sed -n '100,135p' crates/embers-server/src/config.rs

# Find the test function name containing this code
echo -e "\n=== Search for test function definition ==="
rg -B20 'parse_limit_env.*value.*None' crates/embers-server/src/config.rs | head -50

Repository: Pajn/Embers

Length of output: 1180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any CI configuration files that might control test execution
echo "=== GitHub Actions workflows ==="
find .github -type f -name '*.yml' -o -name '*.yaml' 2>/dev/null | while read f; do
  echo "---- $f ----"
  cat "$f" | grep -A5 -B5 'test\|TEST_THREADS\|test-threads' || echo "No test config found"
done

# Check root Cargo.toml for any profile configurations
echo -e "\n=== Root Cargo.toml test profile ==="
cat Cargo.toml | grep -A10 '\[profile.test\]' || echo "No test profile found"

# Final confirmation: check if serial_test is in dependencies anywhere
echo -e "\n=== Check for serial_test dependency ==="
rg 'serial_test|serial-test' --type toml . || echo "serial_test not found in dependencies"

Repository: Pajn/Embers

Length of output: 3897


Unsafe env mutation test lacks serialization guarantees.

This test mutates process-wide environment variables without any serialization mechanism. The SAFETY comment assumes single-threaded execution, but the test harness runs tests in parallel by default—cargo test --workspace in CI does not specify --test-threads=1, and no serial_test dependency or #[serial] attribute exists to enforce ordering. Concurrent test execution could race on std::env::set_var calls.

🤖 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 `@crates/embers-server/src/config.rs` around lines 115 - 126, The test function
that calls parse_limit_env with unsafe environment variable mutations lacks
serialization guarantees and will race when tests run in parallel. Add the
serial_test crate as a test dependency and annotate this test function with the
#[serial] attribute to ensure it executes serially and prevents concurrent
access to the global environment state. This guarantees the SAFETY comment's
assumption of single-threaded execution holds true even when cargo test runs
multiple tests concurrently.

Comment thread crates/embers-server/src/terminal_backend.rs Outdated
@Pajn Pajn force-pushed the prod-hardening branch from 43f940f to 73b4451 Compare June 30, 2026 05:38
@Pajn Pajn merged commit 5a24a72 into main Jun 30, 2026
4 checks passed
@Pajn Pajn deleted the prod-hardening branch June 30, 2026 05:44
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