Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
dadee8c
WIP: POE support - initial analysis and fix plan
fglock Apr 4, 2026
8c138d5
Fix exists(&sub) constant folding, add POSIX/Socket constants for POE
fglock Apr 4, 2026
15225a7
Fix indirect object syntax with variable class + parenthesized args
fglock Apr 4, 2026
84c58fa
Update POE plan: 35/53 test files passing
fglock Apr 4, 2026
4bb5f3f
Update POE plan: add Phase 3-5 roadmap and event loop test results
fglock Apr 4, 2026
c9f2295
Pre-populate %SIG with OS signal names like Perl does
fglock Apr 4, 2026
aacf5b3
Implement DESTROY support for blessed objects using java.lang.ref.Cle…
fglock Apr 4, 2026
1f44572
Fix foreach to see array modifications during iteration
fglock Apr 4, 2026
1b5e59f
Update POE plan: ses_session.t 35/41, document foreach-push and DESTR…
fglock Apr 4, 2026
318267f
Fix require expression parsing, non-blocking I/O, and 4-arg select
fglock Apr 4, 2026
eb009a0
Fix DestroyManager crash with overloaded classes (negative blessIds)
fglock Apr 4, 2026
777b694
Remove DestroyManager (Cleaner/proxy DESTROY) — proxy reconstruction …
fglock Apr 4, 2026
bbdbc78
Update poe.md: document DestroyManager removal, add Bugs 11-13
fglock Apr 4, 2026
1fb2f38
Fix 4-arg select() to properly poll pipe readiness instead of marking…
fglock Apr 4, 2026
5b3da3f
Update poe.md: add Bug 14 (select polling fix), clarify DESTROY limit…
fglock Apr 4, 2026
e66f097
Fix pipe fd registry mismatch and platform EAGAIN value
fglock Apr 4, 2026
7f66592
Update poe.md: document Bugs 15-17, Phase 3.4 signal pipe and postbac…
fglock Apr 4, 2026
fd975dc
Fix select() bitvector write-back and fd allocation collision
fglock Apr 5, 2026
772b789
Update poe.md: document Bugs 18-20, Phase 3.5 select/fd fixes
fglock Apr 5, 2026
8470fdc
Update poe.md: comprehensive Phase 4 test inventory and plan
fglock Apr 5, 2026
bc6d389
Add POSIX terminal/stat constants, sysconf, setsid for POE::Wheel::Ru…
fglock Apr 5, 2026
b139075
Update poe.md: Phase 4.1/4.2 complete, document I/O hang pattern and …
fglock Apr 5, 2026
da14035
Fix fileno() returning undef for regular file handles
fglock Apr 5, 2026
cecb354
Implement sysseek operator for JVM and interpreter backends
fglock Apr 5, 2026
5cccc73
Update poe.md: Phase 4.3 analysis complete, DESTROY is root cause of …
fglock Apr 5, 2026
33cfa4a
Add Phase 4.7 Windows platform support plan to poe.md
fglock Apr 5, 2026
5eb4af3
Add Windows platform support for errno, signals, and socket constants
fglock Apr 5, 2026
1651b95
Fix non-blocking pipe I/O and EBADF errno handling
fglock Apr 5, 2026
575fff5
Add Phase 4.8 plan: fix filehandle dup (open FH, ">&OTHER")
fglock Apr 5, 2026
c11283e
Implement refcounted filehandle duplication (DupIOHandle)
fglock Apr 5, 2026
9be9c99
Update POE plan: Phase 4.8 complete, updated test results and next steps
fglock Apr 5, 2026
082ceee
Fix regressions: goto in map/grep hang, parsimonious dup closes, glob…
fglock Apr 5, 2026
3223df9
Fix post-rebase compilation and add fd recycling
fglock Apr 5, 2026
e328a46
Wire DupIOHandle into duplicateFileHandle, fix warn tied $@ double-FETCH
fglock Apr 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
616 changes: 616 additions & 0 deletions dev/modules/poe.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/main/java/org/perlonjava/core/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ public final class Configuration {
* Automatically populated by Gradle/Maven during build.
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String gitCommitId = "a7261e446";
public static final String gitCommitId = "3223df950";

/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
* Automatically populated by Gradle/Maven during build.
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String gitCommitDate = "2026-04-04";
public static final String gitCommitDate = "2026-04-05";

// Prevent instantiation
private Configuration() {
Expand Down
188 changes: 188 additions & 0 deletions src/main/java/org/perlonjava/runtime/io/BorrowedIOHandle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package org.perlonjava.runtime.io;

import org.perlonjava.runtime.runtimetypes.RuntimeScalar;

import java.nio.charset.Charset;

import static org.perlonjava.runtime.runtimetypes.RuntimeIO.handleIOException;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarTrue;

/**
* A non-owning IOHandle wrapper for Perl's parsimonious dup semantics ({@code >&=} / {@code <&=}).
*
* <h3>Background: parsimonious dup in Perl</h3>
* <p>When Perl executes {@code open(F, ">&=STDOUT")}, it performs an {@code fdopen()} —
* creating a new FILE* that shares the same fd as STDOUT. The key semantic difference
* from a full dup ({@code >&}) is:</p>
* <ul>
* <li>Both handles share the <em>same</em> file descriptor (same fileno).</li>
* <li>Closing the new handle ({@code close F}) does <em>not</em> close the underlying
* resource — the original handle (STDOUT) remains fully operational.</li>
* <li>This is a lightweight alias — no new OS-level file descriptor is allocated.</li>
* </ul>
*
* <h3>Implementation</h3>
* <p>BorrowedIOHandle delegates all I/O operations to the underlying delegate IOHandle,
* but overrides {@link #close()} to only flush — never closing the delegate. This
* ensures that after {@code close F}, the original handle (e.g. STDOUT) keeps working.</p>
*
* <p>Unlike {@link DupIOHandle}, this wrapper:</p>
* <ul>
* <li>Does NOT allocate a new fd number (shares the delegate's fileno)</li>
* <li>Does NOT use reference counting (the delegate is never closed by us)</li>
* <li>Is much simpler — just a thin delegation layer with a close-guard</li>
* </ul>
*
* @see DupIOHandle for full dup semantics ({@code >&}) with reference counting
* @see IOOperator#openFileHandleDup(String, String) where this is created
*/
public class BorrowedIOHandle implements IOHandle {

/** The underlying handle we're borrowing — never closed by us. */
private final IOHandle delegate;
/** Per-instance closed flag. Once true, all I/O operations on THIS wrapper fail. */
private boolean closed = false;

/**
* Creates a BorrowedIOHandle wrapping the given delegate.
*
* @param delegate the underlying IOHandle to borrow (not owned — will not be closed)
*/
public BorrowedIOHandle(IOHandle delegate) {
this.delegate = delegate;
}

/**
* Returns the underlying delegate IOHandle.
*/
public IOHandle getDelegate() {
return delegate;
}

// ---- Delegated I/O operations (check closed state first) ----

@Override
public RuntimeScalar write(String string) {
if (closed) return handleClosed("write");
return delegate.write(string);
}

@Override
public RuntimeScalar flush() {
if (closed) return scalarTrue;
return delegate.flush();
}

@Override
public RuntimeScalar sync() {
if (closed) return scalarTrue;
return delegate.sync();
}

@Override
public RuntimeScalar doRead(int maxBytes, Charset charset) {
if (closed) return handleClosed("read");
return delegate.doRead(maxBytes, charset);
}

@Override
public RuntimeScalar fileno() {
if (closed) return handleClosed("fileno");
// Return the delegate's fileno — parsimonious dup shares the same fd
return delegate.fileno();
}

@Override
public RuntimeScalar eof() {
if (closed) return scalarTrue;
return delegate.eof();
}

@Override
public RuntimeScalar tell() {
if (closed) return handleClosed("tell");
return delegate.tell();
}

@Override
public RuntimeScalar seek(long pos, int whence) {
if (closed) return handleClosed("seek");
return delegate.seek(pos, whence);
}

@Override
public RuntimeScalar truncate(long length) {
if (closed) return handleClosed("truncate");
return delegate.truncate(length);
}

@Override
public RuntimeScalar flock(int operation) {
if (closed) return handleClosed("flock");
return delegate.flock(operation);
}

@Override
public RuntimeScalar bind(String address, int port) {
if (closed) return handleClosed("bind");
return delegate.bind(address, port);
}

@Override
public RuntimeScalar connect(String address, int port) {
if (closed) return handleClosed("connect");
return delegate.connect(address, port);
}

@Override
public RuntimeScalar listen(int backlog) {
if (closed) return handleClosed("listen");
return delegate.listen(backlog);
}

@Override
public RuntimeScalar accept() {
if (closed) return handleClosed("accept");
return delegate.accept();
}

@Override
public RuntimeScalar sysread(int length) {
if (closed) return handleClosed("sysread");
return delegate.sysread(length);
}

@Override
public RuntimeScalar syswrite(String data) {
if (closed) return handleClosed("syswrite");
return delegate.syswrite(data);
}

// ---- Close: flush only, do NOT close the delegate ----

/**
* Closes this borrowed handle.
*
* <p>Only flushes the delegate — does NOT close the underlying resource.
* This matches Perl's fdopen semantics where closing an fdopen'd FILE*
* does not invalidate the original handle.</p>
*/
@Override
public RuntimeScalar close() {
if (closed) {
return handleIOException(
new java.io.IOException("Handle is already closed."),
"Handle is already closed.");
}
closed = true;
// Only flush — never close the delegate. The original handle still owns it.
delegate.flush();
return scalarTrue;
}

private RuntimeScalar handleClosed(String operation) {
return handleIOException(
new java.io.IOException("Cannot " + operation + " on a closed handle."),
operation + " on closed handle failed");
}
}
6 changes: 3 additions & 3 deletions src/main/java/org/perlonjava/runtime/io/ClosedIOHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import org.perlonjava.runtime.runtimetypes.RuntimeIO;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;

import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarFalse;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarTrue;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.*;

public class ClosedIOHandle implements IOHandle {

Expand All @@ -27,7 +26,8 @@ public RuntimeScalar flush() {

@Override
public RuntimeScalar fileno() {
return RuntimeIO.handleIOError("Cannot get file number from a closed handle.");
// Perl 5: fileno() on a closed handle returns undef (not false)
return scalarUndef;
}

@Override
Expand Down
Loading
Loading