Skip to content

Commit ad4db36

Browse files
Update poe.md: add Bug 14 (select polling fix), clarify DESTROY limitation
- Bug 14: 4-arg select() was marking pipes as always ready, causing POE event loop to busy-loop instead of blocking for timers - Updated Key Findings: DESTROY is not feasible via JVM GC (unreliable across JVM implementations, incompatible with Perl ref counting) - Added Phase 3.3 to progress tracking - Documented ses_session.t hang root cause (AnonEvent postback DESTROY) Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 333d39e commit ad4db36

1 file changed

Lines changed: 25 additions & 6 deletions

File tree

dev/modules/poe.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ POE 1.370
2828
└── HTTP::Request/Response PARTIAL (for Filter::HTTPD)
2929
```
3030

31-
## Bugs Fixed (Commits 743c26461 through 2777d2e46)
31+
## Bugs Fixed (Commits 743c26461 through f119640a5)
3232

3333
### Bug 1: `exists(&Errno::EINVAL)` fails in require context - FIXED
3434

@@ -115,6 +115,18 @@ foreach my $session (@children) {
115115

116116
**Fix**: Used original `blessId` directly as cache key (fixed before DestroyManager was removed).
117117

118+
### Bug 14: 4-arg select() marks pipes as always ready - FIXED (commit f119640a5)
119+
120+
**Root cause**: The NIO-based `selectWithNIO()` in `IOOperator.java` treated all non-socket handles (pipes, files) as unconditionally ready (`nonSocketReady++`). This caused `select()` to return immediately when monitoring POE's signal pipe, preventing the event loop from blocking for timer timeouts.
121+
122+
**Impact**: POE's `ses_session.t` hung at test 7 (before `POE::Kernel->run()`) because the event loop never slept — `select()` always returned immediately with the pipe "ready", POE tried to read (got nothing), and looped back.
123+
124+
**Fix**: Replaced the "always ready" assumption with proper polling:
125+
- `InternalPipeHandle.hasDataAvailable()` checks if data is actually in the pipe
126+
- Write ends and regular files remain always-ready
127+
- A poll loop with 10ms intervals respects the timeout parameter
128+
- Both pollable fds and NIO selector are checked each iteration
129+
118130
## Current Test Results (2026-04-04)
119131

120132
### Unit Tests (t/10_units/)
@@ -295,15 +307,22 @@ foreach my $session (@children) {
295307
- Fixed DestroyManager blessId collision with overloaded classes (Bug 13)
296308
- Removed DestroyManager — proxy reconstruction too fragile (close() corrupts proxy hash)
297309
- Updated dev/design/object_lifecycle.md with findings
310+
- [x] Phase 3.3: select() polling fix (2026-04-04, commit f119640a5)
311+
- Fixed 4-arg select() to poll pipe readiness instead of marking always ready (Bug 14)
312+
- select() now properly blocks when monitoring InternalPipeHandle with timeout
313+
- POE event loop no longer busy-loops; timer-based events fire correctly
298314

299-
### Key Findings (Phase 3.1-3.2)
315+
### Key Findings (Phase 3.1-3.3)
300316
- **foreach-push pattern**: Perl's foreach dynamically sees elements pushed during iteration.
301317
PerlOnJava's RuntimeArrayIterator was caching size at creation. This broke POE::Kernel->stop()
302318
which walks the session tree by pushing children during foreach.
303-
- **DESTROY proxy approach failed**: Java's Cleaner API requires that the cleaning action
304-
must NOT reference the tracked object (or it's never GC'd). This forces proxy reconstruction,
305-
which is inherently lossy — close() on proxy hash corrupts subsequent hash access.
306-
Scope-based ref counting is the recommended future approach (see object_lifecycle.md).
319+
- **DESTROY not feasible via GC**: Java's Cleaner/GC-based DESTROY is unreliable across JVM
320+
implementations and cannot guarantee deterministic timing. Perl's DESTROY depends on
321+
reference counting (fires immediately when last reference drops). The JVM's tracing GC
322+
is fundamentally incompatible with this semantic. DESTROY is not implemented.
323+
- **Impact on POE**: ses_session.t hangs because POE::Session::AnonEvent postbacks use
324+
DESTROY to decrement session refcounts. Without DESTROY, the server session's refcount
325+
never reaches zero, keeping the event loop alive. This is a known, documented limitation.
307326
- **Signal delivery**: `kill("ALRM", $$)` doesn't trigger %SIG handlers within POE event loop.
308327
ses_session.t tests 21-22 expect 5 SIGALRMs and 5 SIGPIPEs but get 0.
309328
- **require expression parsing**: `require File::Spec->catfile(...)` was parsed as

0 commit comments

Comments
 (0)