Puffins due to buffering. How it works?

The next question is the answer to the deadlock comment in the this answer. I am curious about how deadlocks occur, so I created a test program: there is a parent process that writes a lot of data to the child STDIN, while the child also writes a lot of data to the parent descriptor of the reader. It turns out that if the data size exceeds 80K (Ubuntu 16.04) there will be a dead end:

parent.pl

use feature qw(say); use strict; use warnings; use IPC::Open2; my $test_size = 80_000; # How many bytes to write? my $pid = open2( my $reader, my $writer, 'child.pl' ); my $long_string = '0123456789' x ($test_size / 10); printf "Parent: writing long string ( length: %d )\n", length $long_string; print $writer $long_string; close $writer; say "Parent: Trying to read childs ouput.."; my $output = do { local $/; <$reader> }; printf "Parent: Got output with length %d..\n", length $output; close $reader; say "Parent: Reaping child.."; waitpid( $pid, 0 ); my $child_exit_status = $? >> 8; say "Parent: Child exited with status: $child_exit_status"; 

child.pl

 use feature qw(say); use strict; use warnings; my $test_size = 80_000; # How many bytes to write? my $child_log_filename = 'childlog.txt'; open ( my $log, '>', $child_log_filename ) or die "Could not create log file: $!"; say $log "Child is running.."; my $long_string = '0123456789' x ($test_size / 10); say $log "Length of output string: " . length $long_string; say $long_string; my $input = do { local $/; <STDIN> }; say $log "Length of input string: " . length $input; exit 2; 

Why is this software deadlock?

+6
source share
1 answer

The reason for the deadlock is that the pipes are filled during recording, and no one reads them. According to pipe (7) :

If the process tries to write to the full channel (see below), then write (2) until a sufficient amount of data has been read so that the recording ends.

The pipe has a limited capacity. If the pipe is full, then record (2) will be blocked or malfunction, depending on whether the O_NONBLOCK flag is set. Different designs have different pipeline throughput limits. Applications should not rely on a specific capacity: the application should be designed so that the reading process consumes data as soon as it is available, so that the writing process does not remain blocked.

In versions of Linux prior to version 2.6.11, the pipe capacity was the same as the page size of the system (for example, 4096 bytes on the i386). Since Linux 2.6.11, the pipe capacity is 65536 bytes. Starting with Linux 2.6.35, the default capacity is 65536 bytes.

Therefore, the parent writes a long line to the child channel STDIN, the channel is filled, which leads to blocking the parent. At the same time, the child writes a long line to the channel for parents' readers, this is also filled, and the child block is blocked. Thus, the child waits for the parent to read from its reader channel, while the parent waits for the child to read its STDIN pipe. The so-called dead end.

I tried to solve this problem using select , fcntl , sysread and syswrite . Since syswrite will block if we try to write more than the bandwidth of the channel, I used fcntl to make the handler not block. In this case, syswrite will write as much as possible to the pipe, and then immediately return with the number of bytes that it actually wrote.

Note: only parent.pl needs to be changed (which is also necessary, since we should not assume that you have access to the original source). Here's the modified parent.pl that will prevent deadlock:

 use feature qw(say); use strict; use warnings; use Errno qw( EAGAIN ); use Fcntl; use IO::Select; use IPC::Open2; use constant READ_BUF_SIZE => 8192; use constant WRITE_BUF_SIZE => 8192; my $test_size = 80_000; # How many bytes to write? my $pid = open2( my $reader, my $writer, 'child.pl' ); make_filehandle_non_blocking( $writer ); my $long_string = '0123456789' x ($test_size / 10); printf "Parent: writing long string ( length: %d )\n", length $long_string; my $sel_writers = IO::Select->new( $writer ); my $sel_readers = IO::Select->new( $reader ); my $read_offset = 0; my $write_offset = 0; my $child_output = ''; while (1) { last if $sel_readers->count() == 0 && $sel_writers->count() == 0; my @sel_result = IO::Select::select( $sel_readers, $sel_writers, undef ); my @read_ready = @{ $sel_result[0] }; my @write_ready = @{ $sel_result[1] }; if ( @write_ready ) { my $bytes_written = syswrite $writer, $long_string, WRITE_BUF_SIZE, $write_offset; if ( !defined $bytes_written ) { die "syswrite failed: $!" if $! != EAGAIN; $bytes_written = 0; } $write_offset += $bytes_written; if ( $write_offset >= length $long_string ) { $sel_writers->remove( $writer ); close $writer; } } if ( @read_ready ) { my $bytes_read = sysread $reader, $child_output, READ_BUF_SIZE, $read_offset; if ( !defined $bytes_read ) { die "sysread failed: $!" if $! != EAGAIN; $bytes_read = 0; } elsif ( $bytes_read == 0 ) { $sel_readers->remove( $reader ); close $reader; } $read_offset += $bytes_read; } } printf "Parent: Got output with length %d..\n", length $child_output; say "Parent: Reaping child.."; waitpid( $pid, 0 ); my $child_exit_status = $? >> 8; say "Parent: Child exited with status: $child_exit_status"; sub make_filehandle_non_blocking { my ( $fh ) = @_; my $flags = fcntl $fh, F_GETFL, 0 or die "Couldn't get flags for file handle : $!\n"; fcntl $fh, F_SETFL, $flags | O_NONBLOCK or die "Couldn't set flags for file handle: $!\n"; } 
+3
source

Source: https://habr.com/ru/post/1011579/


All Articles