about summary refs log tree commit homepage
path: root/lib/PublicInbox/IO.pm
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2023-11-02 09:35:33 +0000
committerEric Wong <e@80x24.org>2023-11-03 06:39:30 +0000
commitcfe25e6aa966144a9c96d1ba2c301fd5e1bad79b (patch)
tree4faa9092c422aafca9568e547634096da12667e9 /lib/PublicInbox/IO.pm
parent16957ad7c4edcbf43294e67c8db06167594f0660 (diff)
downloadpublic-inbox-cfe25e6aa966144a9c96d1ba2c301fd5e1bad79b.tar.gz
This fixes two major problems with the use of tie for filehandles:

* no way to do fcntl, stat, etc. calls directly on the tied handle,
  forcing callers to use the `tied' perlop to access the underlying
  IO::Handle

* needing separate classes to handle blocking and non-blocking I/O

As a result, Git->cleanup_if_unlinked, InputPipe->consume,
and Qspawn->_yield_start have fewer bizzare bits and we
can call `$io->blocking(0)' directly instead of
`(tied *$io)->{fh}->blocking(0)'

Having a PublicInbox::IO class will also allow us to support
custom read buffering which allows inspecting the current state.
Diffstat (limited to 'lib/PublicInbox/IO.pm')
-rw-r--r--lib/PublicInbox/IO.pm54
1 files changed, 54 insertions, 0 deletions
diff --git a/lib/PublicInbox/IO.pm b/lib/PublicInbox/IO.pm
new file mode 100644
index 00000000..63850a52
--- /dev/null
+++ b/lib/PublicInbox/IO.pm
@@ -0,0 +1,54 @@
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# supports reaping of children tied to a pipe or socket
+package PublicInbox::IO;
+use v5.12;
+use parent qw(IO::Handle);
+use PublicInbox::DS qw(awaitpid);
+
+# TODO: this can probably be the new home for read_all, try_cat
+# and maybe even buffered read/readline...
+
+sub waitcb { # awaitpid callback
+        my ($pid, $errref, $cb, @args) = @_;
+        $$errref = $?; # sets .cerr for _close
+        $cb->($pid, @args) if $cb;
+}
+
+sub attach_pid ($$;@) {
+        my ($io, $pid, @cb_arg) = @_;
+        bless $io, __PACKAGE__;
+        # we share $err (and not $self) with awaitpid to avoid a ref cycle
+        ${*$io}{pi_io_reap} = [ $$, $pid, \(my $err) ];
+        awaitpid($pid, \&waitcb, \$err, @cb_arg);
+        $io;
+}
+
+sub attached_pid {
+        my ($io) = @_;
+        ${${*$io}{pi_io_reap} // []}[1];
+}
+
+# caller cares about error result if they call close explicitly
+# reap->[2] may be set before this is called via waitcb
+sub close {
+        my ($io) = @_;
+        my $ret = $io->SUPER::close;
+        my $reap = delete ${*$io}{pi_io_reap};
+        return $ret unless $reap && $reap->[0] == $$;
+        ${$reap->[2]} // (my $w = awaitpid($reap->[1])); # sets [2]
+        ($? = ${$reap->[2]}) ? '' : $ret;
+}
+
+sub DESTROY {
+        my ($io) = @_;
+        my $reap = delete ${*$io}{pi_io_reap};
+        if ($reap && $reap->[0] == $$) {
+                $io->SUPER::close;
+                awaitpid($reap->[1]);
+        }
+        $io->SUPER::DESTROY;
+}
+
+1;