Skip to content

Comments

Avoid mutating global STDOUT & STDERR (#1837)#1

Open
MitchLewis930 wants to merge 1 commit intopr_051_beforefrom
pr_051_after
Open

Avoid mutating global STDOUT & STDERR (#1837)#1
MitchLewis930 wants to merge 1 commit intopr_051_beforefrom
pr_051_after

Conversation

@MitchLewis930
Copy link

@MitchLewis930 MitchLewis930 commented Jan 30, 2026

PR_051

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved stdout and stderr handling to prevent unintended shared-state side effects and ensure proper isolation of IO object references.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Puma's Events class now duplicates provided stdout and stderr IO objects during initialization to prevent unintended shared-state side effects. Corresponding tests were updated to reflect the new object identity behavior and verify sync attribute handling.

Changes

Cohort / File(s) Summary
Events IO Duplication
lib/puma/events.rb
Changed stdout and stderr initialization to use .dup() for provided IO objects, creating independent copies instead of direct references.
Test Updates
test/test_events.rb, test/test_tcp_logger.rb
Removed strict equality assertions for IO objects; replaced with inspected string comparisons. Added test for sync attribute independence. Updated TCP logger test setup to bind logger stdout to current process stdout.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 With dupes of streams so bright and true,
We copy deep, not share, we do—
No tangled state can fool our game,
Each IO object claims its name!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is minimal (only 'PR_051') and does not follow the provided template. It lacks explanation of the problem, motivation, decision-making, testing status, and required checklist items. Provide a comprehensive description following the template: explain the original problem, motivation for the changes, why duping IO objects was chosen, confirm testing was done, and complete the contributor checklist.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: avoiding mutation of global STDOUT & STDERR by using duped copies instead of direct assignments.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr_051_after

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 RuboCop (1.84.0)
lib/puma/events.rb

Error: The Layout/Tab cop has been renamed to Layout/IndentationStyle.
(obsolete configuration found in .rubocop.yml, please update it)
The Layout/TrailingBlankLines cop has been renamed to Layout/TrailingEmptyLines.
(obsolete configuration found in .rubocop.yml, please update it)
Performance/* has been extracted to the rubocop-performance gem.
(obsolete configuration found in .rubocop.yml, please update it)

test/test_events.rb

Error: The Layout/Tab cop has been renamed to Layout/IndentationStyle.
(obsolete configuration found in .rubocop.yml, please update it)
The Layout/TrailingBlankLines cop has been renamed to Layout/TrailingEmptyLines.
(obsolete configuration found in .rubocop.yml, please update it)
Performance/* has been extracted to the rubocop-performance gem.
(obsolete configuration found in .rubocop.yml, please update it)

test/test_tcp_logger.rb

Error: The Layout/Tab cop has been renamed to Layout/IndentationStyle.
(obsolete configuration found in .rubocop.yml, please update it)
The Layout/TrailingBlankLines cop has been renamed to Layout/TrailingEmptyLines.
(obsolete configuration found in .rubocop.yml, please update it)
Performance/* has been extracted to the rubocop-performance gem.
(obsolete configuration found in .rubocop.yml, please update it)


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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

🤖 Fix all issues with AI agents
In `@test/test_events.rb`:
- Around line 26-32: The test test_stdio_respects_sync mutates global
STDOUT.sync and doesn't restore it; wrap the body that sets STDOUT.sync = false
and asserts behavior of Puma::Events.stdio in a begin...ensure block that
captures the original value (orig = STDOUT.sync) and restores it in ensure
(STDOUT.sync = orig) to avoid test pollution, and replace the `assert
!STDOUT.sync` assertion with the Minitest idiom `refute STDOUT.sync` for
clarity.
🧹 Nitpick comments (2)
test/test_tcp_logger.rb (1)

21-21: Fix typo and consider test fragility.

The comment has a typo: capture_process_io should be capture_subprocess_io.

Using instance_variable_set to override internal state is fragile—if the implementation changes the instance variable name, this test will silently break. Consider adding a brief comment explaining why this workaround is necessary, or alternatively, verify if there's a cleaner approach (e.g., testing with a custom IO object that doesn't require subprocess capture).

✏️ Suggested fix for the typo
-    logger.instance_variable_set(:`@stdout`, $stdout) # ensure capture_process_io has access to the loggers output
+    logger.instance_variable_set(:`@stdout`, $stdout) # ensure capture_subprocess_io has access to the logger's output
test/test_events.rb (1)

21-24: Consider using fileno for more robust comparison.

Comparing inspect strings works but could be fragile if Ruby's inspect format changes. A more robust approach is to compare the underlying file descriptor:

♻️ Alternative using fileno
-    # events.stdout is a dup, so same file handle, different ruby object, but inspect should show the same file handle
-    assert_equal STDOUT.inspect, events.stdout.inspect
-    assert_equal STDERR.inspect, events.stderr.inspect
+    # events.stdout is a dup, so same file descriptor, different Ruby object
+    assert_equal STDOUT.fileno, events.stdout.fileno
+    assert_equal STDERR.fileno, events.stderr.fileno
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f89f194 and 70b28bb.

📒 Files selected for processing (3)
  • lib/puma/events.rb
  • test/test_events.rb
  • test/test_tcp_logger.rb
🔇 Additional comments (1)
lib/puma/events.rb (1)

32-33: LGTM! Duplicating IO objects prevents unintended side effects on global streams.

This change correctly addresses the issue of mutating shared IO objects (like STDOUT.sync = true). The duplication ensures the Events instance has its own independent IO streams.

One consideration: this assumes all passed IO-like objects respond to dup. Standard IO, StringIO, and File objects support this, but custom IO-like objects may not. If NullIO is used elsewhere, verify it inherits dup behavior correctly (it likely does via Object#dup).

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +26 to 32
def test_stdio_respects_sync
STDOUT.sync = false
events = Puma::Events.stdio

assert !STDOUT.sync
assert events.stdout.sync
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Restore global state after test to prevent test pollution.

This test modifies STDOUT.sync but doesn't restore it, which could affect subsequent tests. Add an ensure block to restore the original value.

Also, consider using refute instead of assert ! for better Minitest idiom.

🛡️ Proposed fix to restore global state
   def test_stdio_respects_sync
+    original_sync = STDOUT.sync
     STDOUT.sync = false
     events = Puma::Events.stdio
 
-    assert !STDOUT.sync
+    refute STDOUT.sync
     assert events.stdout.sync
+  ensure
+    STDOUT.sync = original_sync
   end
🤖 Prompt for AI Agents
In `@test/test_events.rb` around lines 26 - 32, The test test_stdio_respects_sync
mutates global STDOUT.sync and doesn't restore it; wrap the body that sets
STDOUT.sync = false and asserts behavior of Puma::Events.stdio in a
begin...ensure block that captures the original value (orig = STDOUT.sync) and
restores it in ensure (STDOUT.sync = orig) to avoid test pollution, and replace
the `assert !STDOUT.sync` assertion with the Minitest idiom `refute STDOUT.sync`
for clarity.

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.

2 participants