Skip to content

Fix memory leak by ensuring rb_call_super is called in Watcher_Detach#91

Merged
ioquatix merged 5 commits intosocketry:mainfrom
Watson1978:fix-memory-leak
Mar 20, 2026
Merged

Fix memory leak by ensuring rb_call_super is called in Watcher_Detach#91
ioquatix merged 5 commits intosocketry:mainfrom
Watson1978:fix-memory-leak

Conversation

@Watson1978
Copy link
Copy Markdown
Collaborator

@Watson1978 Watson1978 commented Mar 19, 2026

Fix #89

Overview

This PR fixes a memory leak where detached watchers are not garbage collected because they remain in the @watchers array.

Root Cause

In the C extension (watcher.h), the Watcher_Detach and Watcher_Disable macros were not properly calling rb_call_super(0, 0) in all code paths.
Because super was bypassed, the Ruby-level cleanup logic (which removes the watcher from the loop's @watchers array) was never executed, causing the memory leak.

Important Note on Thread Safety and detach_race_condition_spec.rb

During the investigation of this fix,
we conducted a 10,000-iteration stress test on detach_race_condition_spec.rb and deeply analyzed the intermittent SEGV on macOS occurring inside ev_io_stop sometimes.

The SEGV is an architectural limitation, not a memory management bug in this patch. cool.io releases the Ruby GVL (via rb_nogvl) to run the libev event loop (ev_run). However, libev is strictly not thread-safe. When a separate Ruby thread calls detach (and subsequently ev_io_stop) while the event loop thread is concurrently modifying its internal arrays (e.g., anfds), it causes memory corruption inside libev.

Fixing this specific thread-safety limitation would require a major architectural rewrite (e.g., using ev_async for cross-thread message passing instead of calling ev_io_stop directly from another thread). Therefore, this PR strictly focuses on resolving the memory leak (#89), which is fully fixed by these changes.

Types of Changes

  • Bug fix.

Contribution

Instead of manually calling `ev_stop` within the C macro, delegate
the disablement process to the existing Ruby-level `disable` method
via `rb_funcall`.

This refactoring ensures that when a watcher is detached while
still enabled, all internal states are safely and correctly updated.
Specifically, it guarantees that the `@active_watchers` counter
is decremented (preventing potential event loop hangs) and the
`enabled` flag is properly set to 0.
On macOS (kqueue), calling `ev_stop` multiple times on the same
watcher or after it has been partially decommissioned can trigger
a Segmentation Fault. By checking `watcher_data->enabled` before
invoking the C-level stop function, we prevent these race conditions
and reentrancy issues.
@Watson1978 Watson1978 marked this pull request as draft March 19, 2026 09:08
@Watson1978 Watson1978 marked this pull request as ready for review March 20, 2026 07:59
@Watson1978
Copy link
Copy Markdown
Collaborator Author

@ioquatix Could you please review this? Thanks.

@@ -26,20 +26,15 @@

#define Watcher_Detach(watcher_type, watcher) \
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you mind making a follow up PR to to make this an inline function? These macros are getting a bit out of control.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I completely agree about the macros—they made debugging quite challenging this time!

@ioquatix ioquatix merged commit 5e3b518 into socketry:main Mar 20, 2026
17 of 18 checks passed
@ioquatix
Copy link
Copy Markdown
Member

Do you want me to make a patch release?

@Watson1978
Copy link
Copy Markdown
Collaborator Author

@ioquatix Yes, please! That would be fantastic.

This memory leak (#89) has been causing significant issues in long-running processes, so a patch release would be incredibly helpful for us and the community.

@Watson1978 Watson1978 deleted the fix-memory-leak branch March 20, 2026 08:48
@ioquatix ioquatix self-assigned this Mar 21, 2026
@ioquatix ioquatix added this to the v1.9.4 milestone Mar 21, 2026
@ioquatix
Copy link
Copy Markdown
Member

Fixed in v1.9.4

@ioquatix ioquatix removed their assignment Mar 21, 2026
@Watson1978
Copy link
Copy Markdown
Collaborator Author

Thanks

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.

Memory leak with version 1.9.2

2 participants