File Coverage

blib/lib/Hg/Lib/Server/Pipe.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Hg::Lib::Server::Pipe;
2              
3 2     2   138218 use Symbol 'gensym';
  2         4777  
  2         196  
4             #use IPC::Open3 qw[ open3 ];
5 2     2   6121 use IPC::Open2 qw[ open2 ];
  2         11511  
  2         173  
6              
7 2     2   29 use Carp;
  2         5  
  2         137  
8              
9 2     2   2793 use POSIX qw[ :sys_wait_h ];
  2         20720  
  2         24  
10 2     2   13823 use Try::Tiny;
  2         5383  
  2         132  
11              
12 2     2   6474 use Moo;
  2         50842  
  2         15  
13 2     2   15332 use MooX::Types::MooseLike::Base qw[ :all ];
  0            
  0            
14              
15             sub forceArray {
16             sub { 'ARRAY' eq ref $_[0] ? $_[0] : [ $_[0] ] }
17             }
18              
19              
20             with 'MooX::Attributes::Shadow::Role';
21              
22             shadowable_attrs( qw[ hg args path configs ] );
23              
24             has _pid => (
25             is => 'rwp',
26             predicate => 1,
27             clearer => 1,
28             init_arg => undef
29             );
30             has _write => ( is => 'rwp', init_arg => undef );
31             has _read => ( is => 'rwp', init_arg => undef );
32             has _error => ( is => 'rwp', init_arg => undef );
33             has cmd => ( is => 'rwp' );
34              
35             # path to hg executable
36             has hg => (
37             is => 'ro',
38             default => sub { 'hg' },
39             coerce => forceArray,
40             isa => sub {
41             is_Str( $_ )
42             or die( "'hg' attribute must be string\n" )
43             foreach @{ shift() };
44             },
45             );
46              
47             # arguments to hg
48             has args => (
49             is => 'ro',
50             coerce => forceArray,
51             default => sub { [] },
52             );
53              
54             has path => (
55             is => 'ro',
56             predicate => 1,
57             );
58              
59             has configs => (
60             is => 'ro',
61             coerce => forceArray,
62             default => sub { [] },
63             );
64              
65             has cmd => (
66             is => 'lazy',
67             init_arg => undef,
68             );
69              
70              
71             sub _build_cmd {
72              
73             my $self = shift;
74              
75             my @cmd = (
76             @{ $self->hg },
77             qw[ --config ui.interactive=True
78             serve
79             --cmdserver pipe
80             ],
81             );
82              
83             push @cmd, '-R', $self->path if $self->has_path;
84              
85             push @cmd, map { ( '--config' => $_ ) } @{ $self->configs };
86              
87             push @cmd, @{ $self->args };
88              
89             return \@cmd;
90             }
91              
92             sub BUILD {
93              
94             shift()->open;
95              
96             }
97              
98             sub open {
99              
100             my $self = shift;
101              
102             my ( $write, $read );
103             my $error = gensym();
104              
105             my $pid;
106              
107             try {
108              
109             $pid = open2( $read, $write, @{ $self->cmd } );
110             # $pid = open3( $write, $read, $error, @{ $self->cmd } );
111              
112             # there's probably not enough time elapsed between starting
113             # the child process and checking for its existence, but this
114             # doesn't cost much
115             _check_on_child( $pid, status => 'alive' );
116              
117             }
118             catch {
119              
120             croak( $_ );
121              
122             };
123              
124              
125             $self->_set__pid( $pid );
126             $self->_set__write( $write );
127             $self->_set__read( $read );
128             $self->_set__error( $error );
129              
130             }
131              
132             sub DEMOLISH {
133              
134             shift()->close;
135              
136             }
137              
138             sub read {
139              
140             my $self = shift;
141              
142             # use aliased data in @_ to prevent copying
143             return $self->_read->sysread( @_ );
144             }
145              
146             # always use aliased $_[0] as buffer to prevent copying
147             # call as get_chunk( $buf )
148             sub get_chunk {
149              
150             my $self = shift;
151              
152             # catch pipe errors from child
153             local $SIG{'PIPE'} = sub { croak( "SIGPIPE on read from server\n" ) };
154              
155             my $nr = $self->read( $_[0], 5 );
156             croak( "error reading chunk header from server: $!\n" )
157             unless defined $nr;
158              
159             $nr > 0
160             or croak( "unexpected end-of-file getting chunk header from server\n" );
161              
162             my ( $ch, $len ) = unpack( 'A[1] l>', $_[0] );
163              
164             if ( $ch =~ /IL/ ) {
165              
166             return $ch, $len;
167              
168             }
169              
170             else {
171              
172             $self->read( $_[0], $len ) == $len
173             or croak(
174             "unexpected end-of-file reading $len bytes from server channel $ch\n"
175             );
176              
177             return $ch;
178             }
179              
180             }
181              
182             sub close {
183              
184             my $self = shift;
185              
186             # if the command server was created, see if it's
187             # still hanging around
188             if ( $self->_has_pid ) {
189              
190             $self->_write->close;
191              
192             _check_on_child( $self->_pid, status => 'exit', wait => 1 );
193              
194             $self->_clear_pid;
195             }
196              
197             return;
198              
199             }
200              
201             sub _check_on_child {
202              
203             my $pid = shift;
204             my %opt = @_;
205              
206             my $flags = WUNTRACED | ( $opt{wait} ? 0 : WNOHANG );
207             my $status = waitpid( $pid, $flags );
208              
209             # if the child exitted, it had better have been a clean death;
210             # anything else is not ok.
211             if ( $pid == $status ) {
212              
213             die( "unexpected exit of child with status ",
214             WEXITSTATUS( $? ), "\n" )
215             if WIFEXITED( $? ) && WEXITSTATUS( $? ) != 0;
216              
217             die( "unexpected exit of child with signal ",
218             WTERMSIG( $? ), "\n" )
219             if WIFSIGNALED( $? );
220              
221             }
222              
223             if ( $opt{status} eq 'alive' ) {
224              
225             die( "unexpected exit of child\n" )
226             if $pid == $status || -1 == $status;
227              
228             }
229              
230             elsif ( $opt{status} eq 'exit' ) {
231              
232             # is the child still alive
233             die( "child still alive\n" )
234             unless $pid == $status || -1 == $status;
235              
236             }
237              
238             else {
239              
240             die( "internal error: unknown child status requested\n" );
241              
242             }
243              
244             }
245             1;