From cfe25e6aa966144a9c96d1ba2c301fd5e1bad79b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 2 Nov 2023 09:35:33 +0000 Subject: replace ProcessIO with untied PublicInbox::IO 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. --- lib/PublicInbox/IO.pm | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 lib/PublicInbox/IO.pm (limited to 'lib/PublicInbox/IO.pm') 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 +# License: AGPL-3.0+ + +# 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; -- cgit v1.2.3-24-ge0c7