Skip to content

POE support (#433)#436

Open
fglock wants to merge 32 commits intomasterfrom
feature/poe-support
Open

POE support (#433)#436
fglock wants to merge 32 commits intomasterfrom
feature/poe-support

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 4, 2026

Summary

POE 1.370 (Perl Object Environment) event-driven multitasking framework support for PerlOnJava.

Current status: 38/53 unit+resource tests pass, 14/35 event loop tests pass. Core event loop works (alarms, aliases, sessions, NFA, signals, I/O multiplexing).

Bugs fixed (22 root causes)

  1. exists(&Errno::EINVAL) — constant folding bypass for & sigil
  2. Symbol.pm $VERSION not set in Java module
  3. Indirect object syntax import $package () parser fix
  4. POSIX missing errno/signal constants, uname(), sigprocmask()
  5. ConcurrentModificationException in hash each() iteration
  6. Socket.pm missing IPPROTO constants
  7. %SIG not pre-populated with signal names
  8. foreach not seeing array modifications during iteration
  9. require File::Spec->catfile(...) parsed as module name
  10. Non-blocking I/O for pipe handles (EAGAIN support)
  11. 4-arg select() — pipe readiness polling, bitvector write-back
  12. Signal pipe fd registry mismatch — pipes invisible to select()
  13. Platform EAGAIN value (macOS=35, Linux=11)
  14. Fd allocation collision between FileDescriptorTable and RuntimeIO
  15. socketpair() stream initialization
  16. Socket pack_sockaddr_un/unpack_sockaddr_un stubs
  17. 80+ POSIX constants (stat, terminal I/O, baud rates, sysconf)
  18. fileno() returning undef for regular file handles
  19. sysseek operator for JVM backend
  20. Non-blocking syswrite for pipe handles, EBADF errno
  21. Refcounted filehandle duplication (DupIOHandle) — proper open(FH, ">&OTHER") semantics
  22. findFileHandleByDescriptor() fallback to RuntimeIO fileno registry

Key test results

Test Result Notes
Unit tests (t/10_units/) 38/53 pass Up from ~15
ses_session.t 37/41 4 failures from DESTROY counts
ses_nfa.t 39/39 NFA state machine perfect
k_alarms.t 37/37 Alarm scheduling perfect
k_aliases.t 20/20 Session aliases perfect
k_selects.t 17/17 File handle watchers perfect
filehandles.t 131/132 1 TODO test
01_sysrw.t 17/17 Driver I/O perfect
15_kernel_internal.t 12/12 Kernel internals perfect

Remaining work (priority order)

  1. Phase 4.5: DESTROY workaround — highest impact (20-30+ tests across 5+ wheel files)
  2. Phase 4.6: TIOCSWINSZ stub — low effort, unblocks k_signals_rerun
  3. Phase 4.9: Storable path fix — low effort, 3 filter tests
  4. Phase 4.10: HTTP::Message bytes — 58 tests in 03_http.t
  5. Phase 4.7: Windows platform — CI critical (errno, %SIG, Socket constants)

See dev/modules/poe.md for full plan with implementation details.

Test plan

  • Fix exists(&sub) in require context
  • Fix indirect object syntax
  • Add POSIX constants and functions
  • Fix hash each() ConcurrentModificationException
  • Pre-populate %SIG with signal names
  • Fix foreach array modification visibility
  • Implement 4-arg select() with proper I/O multiplexing
  • Fix pipe fd registration and errno handling
  • Add Socket AF_UNIX stubs
  • Add POSIX terminal/file constants
  • Implement sysseek operator
  • Fix fileno() for regular files
  • Implement refcounted filehandle duplication (DupIOHandle)
  • Implement DESTROY workaround for blessed object cleanup
  • Add TIOCSWINSZ stub
  • Fix Storable path for POE test runner
  • Windows platform support

Generated with Devin

@fglock fglock force-pushed the feature/poe-support branch 3 times, most recently from 2e1fda1 to ad4db36 Compare April 4, 2026 19:54
@fglock fglock changed the title WIP: POE support (#433) POE support (#433) Apr 5, 2026
fglock and others added 26 commits April 5, 2026 21:19
Analyzed POE 1.370 test results (~15/97 pass). Identified 7 root causes:
- P0: exists(&sub) fails in require context (blocks IO::Socket::INET)
- P0: use vars globals not visible across files under strict
- P1: Symbol.pm $VERSION not set in Java module
- P1: POSIX missing errno/signal constants and uname()
- P1: indirect method call syntax (import $pkg ())
- P2: POE constants as barewords cascade from P0
- P3: IO::Tty/IO::Pty need native PTY (JVM limitation)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Fix ConstantFoldingVisitor corrupting AST for exists(&Errno::EINVAL)
  by skipping folding of & (code sigil) operands
- Add PF_UNSPEC and SOMAXCONN to Socket module (needed by IO::Pipely)
- Add POSIX signal constants (SIGHUP..SIGTSTP) with macOS/Linux values
- Add POSIX errno constants (EPERM..ERANGE)
- Add POSIX::uname(), sigprocmask() stub, SigSet/SigAction stubs
- Add SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK/SIG_DFL/SIG_IGN/SIG_ERR
- Set Symbol::$VERSION to 1.09

Result: use POE now loads successfully.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add parsingIndirectObject flag to Parser to allow $var( pattern
  in indirect object context (e.g., import $package (), new $class (args))
- Fix ConcurrentModificationException in hash each() by snapshotting
  entries at iterator creation time, matching Perl tolerance for
  hash modification during each() iteration
- These fixes unblock POE::Filter::Reference (import $package () syntax)
  and POE::Resource::Aliases (each + delete pattern)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Perl's %SIG hash is pre-populated with all available OS signal names
as keys (with undef values). Modules like POE rely on `keys %SIG` to
discover which signals are available. Without this, POE's signal
handling was completely broken because _data_sig_initialize() found
no signals to register.

The fix adds a constructor to RuntimeSigHash that populates the hash
with POSIX signals and platform-specific signals (macOS: EMT, INFO,
IOT; Linux: CLD, STKFLT, PWR, IOT).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…aner

Uses Cleaner to detect when blessed objects become unreachable and
schedules DESTROY calls on the main thread at safe points. Per-blessId
cache makes the check O(1) for repeated bless calls. Exceptions in
DESTROY are caught and printed as "(in cleanup)" warnings matching Perl
behavior.

- DestroyManager: new class managing Cleaner registration, pending queue,
  and proxy object creation for DESTROY calls
- ReferenceOperators: call registerForDestroy at bless time
- PerlSignalQueue: check pending DESTROYs at safe points
- PerlLanguageProvider: run global destruction after END blocks

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Perl foreach iterates over elements pushed to the array during the
loop. The iterator was caching the array size at creation time, missing
new elements. Now checks elements.size() dynamically on each hasNext().

This fixes POE::Kernel->stop() which uses the foreach-push pattern to
walk the session tree.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…OY findings

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Fix parser to handle require File::Spec->catfile(...) as an expression
  rather than treating File::Spec as a module name. This allows POE to
  dynamically load Time::HiRes via require.

- Add non-blocking I/O support for internal pipe handles: InternalPipeHandle
  now supports setBlocking/isBlocking, and sysread returns undef with
  EAGAIN (errno 11) when non-blocking and no data available.

- Fix IO::Handle::blocking() to properly delegate to the underlying handle
  blocking state, and fix argument passing (shift on glob copy issue).

- Add FileDescriptorTable for managing file descriptors and implement
  4-argument select() (pselect-style) for POE I/O multiplexing.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
DestroyManager.registerForDestroy used Math.abs(blessId) as the cache
key, but overloaded classes have negative blessIds (-1, -2, ...).
Math.abs(-1) = 1 collided with normal class IDs, causing getBlessStr
to return null and triggering a NullPointerException in
normalizeVariableName.

Fix: use the original blessId directly as the cache key in all three
places (registerForDestroy, invalidateDestroyCache, processPendingDestroys).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…is fragile

The Cleaner-based DESTROY implementation used proxy objects to reconstruct
blessed references after GC. This caused:
- close() inside DESTROY corrupting proxy hash access (File::Temp bug)
- Overloaded class blessId collisions (Math.abs on negative IDs)
- Incomplete reconstruction for tied/magic/overloaded objects

Tied variable DESTROY (TieScalar.tiedDestroy) is unaffected — it uses
scope-based cleanup. Updated dev/design/object_lifecycle.md with findings
and future directions (scope-based ref counting recommended over GC proxy).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Bug 9: Updated to ATTEMPTED AND REVERTED with explanation of proxy
  reconstruction failures (close() corruption, blessId collision)
- Bug 11: require File::Spec->catfile() parser fix
- Bug 12: Non-blocking I/O for pipe handles
- Bug 13: DestroyManager Math.abs(blessId) collision
- Added Phase 3.2 to progress tracking
- Updated Key Findings with DESTROY proxy failure analysis

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… always ready

The previous NIO-based select() implementation marked all non-socket
handles (pipes, files) as unconditionally ready. This broke POE's
event loop: select() returned immediately when monitoring the signal
pipe, preventing the event loop from sleeping for timer timeouts.

Fix: Replace the "always ready" assumption with proper polling:
- For InternalPipeHandle: use hasDataAvailable() to check if data
  is actually available for reading
- For write ends of pipes and regular files: still treat as ready
- Implement a poll loop with 10ms intervals that respects timeout
- Check both pollable fds and NIO selector in each iteration

This fixes the regression where POE's ses_session.t hung at test 7
(before POE::Kernel->run()) because select() never blocked.

Note: ses_session.t still hangs due to missing DESTROY support
(postback refcounts never decrement), but this is a pre-existing
limitation, not a regression from this change.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ation

- 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>
Bug 15: pipe() created RuntimeIO objects but never registered them in
RuntimeIO.filenoToIO, making pipes invisible to select(). POE signal
pipe (used for asynchronous signal delivery) was affected - select()
could not detect data written to the signal pipe.

Fix: Added registerExternalFd() to RuntimeIO that registers a RuntimeIO
at a specific fd number (matching FileDescriptorTable fd) and advances
nextFileno to prevent future collisions. Called from IOOperator.pipe()
after creating the pipe handles.

Bug 16: InternalPipeHandle.sysread() set errno to 11 (Linux EAGAIN) on
all platforms. On macOS EAGAIN is 35, so POE check failed producing
Resource deadlock avoided errors. Fixed to use ErrnoVariable.EAGAIN()
for platform-correct values. Also fixed in CustomFileChannel.java.

ses_session.t: 7/41 -> 37/41 (remaining 4 are expected DESTROY failures)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…k fixes

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Three bugs fixed:

1. select() bitvector write-back: The 4-arg select() created copies of
   the bitvector arguments but never wrote the modified copies back to
   the original variables. This meant select() never actually cleared
   or modified the output bitvectors, causing spurious read-readiness
   detection in POE's event loop.

2. Fd allocation collision: FileDescriptorTable (used by pipe()) and
   RuntimeIO (used by socket/socketpair/accept) had separate fd
   counters that got out of sync. After socketpair allocated fds 5+
   from RuntimeIO, FileDescriptorTable still started at 5, causing
   pipe() to allocate fds that collided with existing socket fds.
   Fixed by cross-synchronizing both counters.

3. socketpair() stream initialization: The SocketIO constructor used
   by socketpair() didn't initialize inputStream/outputStream, causing
   sysread() to fail with "No input stream available". Fixed by using
   the Socket-based constructor which initializes streams. Also made
   sysread() fall back to channel-based I/O when streams are unavailable.

POE test results:
- k_selects: 5/17 → 17/17 (all pass)
- ses_session: 37/41 (unchanged, DESTROY-related failures)
- ses_nfa: 39/39 (unchanged)
- k_alarms: 37/37 (unchanged)
- k_aliases: 20/20 (unchanged)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
k_selects.t now 17/17 (was 5/17).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Inventoried all 35 POE event loop test files (~596 tests total):
- 12 fully passing (175 tests)
- 5 blocked by missing Socket pack_sockaddr_un stubs
- 7 blocked by missing POSIX terminal constants
- 1 running but failing (wheel_readwrite 15/28)
- 8 skipped (platform/network)
- Several fork-dependent (JVM limitation)

Phase 4 plan:
4.1: Socket pack_sockaddr_un stubs → unblock SocketFactory
4.2: POSIX constants → unblock Wheel::Run/FollowTail
4.3: Debug wheel_readwrite I/O failures
4.4: Test SocketFactory-dependent wheel tests

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…n/FollowTail

Phase 4.2: Add comprehensive POSIX constants needed by POE wheels.

POSIX.pm changes:
- Add stat permission constants (S_IRUSR, S_IWUSR, S_IXUSR, etc.) to
  constant-generation loop
- Add terminal I/O constants (ECHO, ICANON, OPOST, TCSANOW, BRKINT,
  ICRNL, ISTRIP, IXON, CSIZE, PARENB, baud rates, etc.)
- Add S_IS* file type test functions (S_ISBLK, S_ISCHR, S_ISDIR,
  S_ISFIFO, S_ISLNK, S_ISREG, S_ISSOCK) as pure Perl
- Add setsid() and sysconf() function stubs
- Add _SC_OPEN_MAX constant

POSIX.java changes:
- Add 14 stat permission constant methods
- Add 66 terminal I/O constant methods with macOS/Linux detection
- Add setsid() (returns PID as approximation)
- Add sysconf() (supports _SC_OPEN_MAX via ulimit -n)
- Add _SC_OPEN_MAX constant (macOS=5, Linux=4)

Results:
- POE::Wheel::FollowTail now loads and runs (4/10 pass)
- POE::Wheel::Run now loads and runs (42/103: 6 pass, 36 skip)
- Socket pack_sockaddr_un/unpack_sockaddr_un stubs added

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…blockers

Updated test results with precise pass/fail counts from fresh test run.
Documented three remaining root causes:
- Event loop I/O hang: select() callbacks don't fire for pipe/socket watchers
  (affects wheel_readwrite, wheel_sf_tcp, wheel_accept, wheel_sf_udp)
- Missing sysseek operator (blocks FollowTail)
- Missing TIOCSWINSZ ioctl constant (blocks Wheel::Run child processes)

Reprioritized Phase 4.3-4.5 based on impact analysis.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Regular file open(), JAR resource open(), scalar-backed open(), and pipe
open() all created IO handles without assigning a file descriptor number.
This caused fileno() to return undef for these handle types, breaking
POE select()-based I/O monitoring which needs fileno to register handles.

Added assignFileno() calls after handle creation in all four open paths.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
sysseek was only implemented in the interpreter backend. Added JVM backend
support by routing through CoreOperatorResolver, EmitBinaryOperatorNode,
OperatorHandler, and CompileBinaryOperator.

Unlike seek (returns 1/0), sysseek returns the new position on success,
'0 but true' when position is 0, or undef on failure. This unblocks
POE::Wheel::FollowTail which uses sysseek for file position tracking.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…wheel hangs

Documented findings from Phase 4.3 investigation:
- fileno() fix for regular file handles (Bug 21)
- sysseek implementation for JVM backend (Bug 22)
- Root cause analysis: all wheel test hangs are caused by DESTROY not being
  called when wheels go out of scope. I/O subsystem (select, sysread, fileno,
  sysseek) verified working correctly in isolation.
- Documented POE::Wheel DESTROY cleanup pattern and 6 workaround options
- Recommended Option A: trigger DESTROY on hash delete/set

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Documented 6 Windows compatibility issues found via analysis:
- Critical: EAGAIN/errno resolution broken (strerror stub)
- High: Missing Windows errno table, Unix-only signals in %SIG
- Medium: POSIX.java and Socket.java lack Windows branches
- Low: sysconf uses ulimit -n

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
fglock and others added 6 commits April 5, 2026 21:21
- FFMPosixWindows.strerror(): expand from 10 entries to full Windows CRT
  errno table (1-42 standard + 100-143 Winsock/MSVC extended)
- ErrnoHash.java: add buildWindowsTable() with MSVC errno values
- Errno.pm: add $^O eq 'MSWin32' branch with Windows CRT errno values
- RuntimeSigHash.java: add Windows-only signal list (INT, TERM, ABRT,
  FPE, ILL, SEGV, BREAK) instead of full POSIX signal set
- POSIX.java: add IS_WINDOWS flag, fix EAGAIN/ETXTBSY errno constants
  for Windows, fix sysconf to not run ulimit -n on Windows
- Socket.java: make SOL_SOCKET, AF_INET6, SO_REUSEADDR, SO_KEEPALIVE,
  SO_BROADCAST, SO_LINGER, SO_ERROR, IP_TOS, IPV6_V6ONLY, SO_REUSEPORT
  platform-aware with correct Windows Winsock2 values

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- InternalPipeHandle: Implement non-blocking syswrite with buffer capacity
  checking (returns EAGAIN when 64KB pipe buffer is full)
- InternalPipeHandle: Add createPair() factory method with shared writerClosed
  flag for proper EOF detection in non-blocking reads
- InternalPipeHandle: Increase pipe buffer from 1KB to 64KB (matching OS pipes)
- InternalPipeHandle: Simplify blocking doRead/sysread using direct read()
  instead of polling loop, with proper Pipe broken/closed EOF handling
- ErrnoVariable: Add EBADF constant with strerror probe support
- IOOperator: Set $! to numeric EBADF (not just string) for sysread/syswrite
  errors on closed or wrong-direction handles
- IOOperator: Set $! after warn() calls to prevent warn from clobbering errno
  when STDERR is closed
- Add sys/ioctl.ph stub for POE::Wheel::Run compatibility

POE test results: 01_sysrw.t now passes 15/17 (was 4/17), signals.t 46/46

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
duplicateFileHandle() currently shares the same IOHandle object between
original and duplicate, so closing one invalidates the other. This blocks
01_sysrw.t tests 16-17 and the STDERR save/restore pattern used by many
test suites.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add DupIOHandle wrapper that enables proper Perl dup semantics:
- Each dup'd handle has independent closed state and fd number
- Shared reference count tracks all duplicates
- Underlying resource only closed when last dup is closed
- Original handle preserves its fileno after duplication

Also fix findFileHandleByDescriptor to check RuntimeIO's fileno
registry, fixing "Bad file descriptor" errors when opening by
fd number (e.g. open($fh, ">&6")).

Fixes POE::Driver::SysRW tests 16-17 (dup/close/reopen cycle).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Mark Phase 4.8 (DupIOHandle) as complete with results
- Update test tables: 15_kernel_internal 12/12, 01_sysrw 17/17,
  filehandles 131/132, signals 46/46, ses_nfa 39/39
- Clean up Remaining Phases: consolidate into clear next steps
  with implementation details for each phase
- Add Phase 4.9 (Storable) and 4.10 (HTTP::Message) plans
- Summary: 38/53 unit+resource pass, 14/35 event loop pass

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…-based handle lookup

Three fixes and comprehensive documentation:

1. op/array.t hang (176->0->176 FIXED): map/grep/all/any in ListOperators.java
   were missing control flow checks after RuntimeCode.apply(). When goto LABEL
   was used inside a map block, the RuntimeControlFlowList marker was silently
   discarded, causing an infinite loop (unshift grew the array each iteration).
   Added isNonLocalGoto() checks that propagate the marker to the caller.

2. io/dup.t regression (25->20->23 IMPROVED): Parsimonious dup (>&= / <&=)
   was returning the same RuntimeIO object, so close F on a parsimonious dup
   of STDOUT closed STDOUT itself. Created BorrowedIOHandle -- a non-owning
   wrapper that delegates all I/O but only flushes on close (never closes the
   delegate), matching Perl fdopen() semantics.

3. Named handle lookup via glob table: openFileHandleDup() now resolves
   named handles (STDIN/STDOUT/STDERR and user-defined) via GlobalVariable
   getGlobalIO() instead of a switch on static RuntimeIO fields. This is
   essential because standard handles can be redirected at runtime via
   open(STDOUT, ">file"). The glob table always reflects the current handle.

4. Thorough documentation added to all IO dup-related code including
   DupIOHandle, BorrowedIOHandle, FileDescriptorTable, and IOOperator methods.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the feature/poe-support branch from 861fe96 to 082ceee Compare April 5, 2026 19:23
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