mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-14 10:13:05 +02:00
731b862e6f
This is consistent with the way BackRest and BackRest test were renamed way back in 18fd2523
.
More modules will be moving to pgBackRestDoc soon so renaming now reduces churn later.
282 lines
9.9 KiB
Perl
282 lines
9.9 KiB
Perl
####################################################################################################################################
|
|
# Buffered Handle IO
|
|
####################################################################################################################################
|
|
package pgBackRestTest::Common::Io::Buffered;
|
|
use parent 'pgBackRestTest::Common::Io::Filter';
|
|
|
|
use strict;
|
|
use warnings FATAL => qw(all);
|
|
use Carp qw(confess);
|
|
use English '-no_match_vars';
|
|
|
|
use Exporter qw(import);
|
|
our @EXPORT = qw();
|
|
use IO::Select;
|
|
use Time::HiRes qw(gettimeofday);
|
|
|
|
use pgBackRestDoc::Common::Exception;
|
|
use pgBackRestDoc::Common::Log;
|
|
|
|
use pgBackRestTest::Common::Io::Base;
|
|
use pgBackRestTest::Common::Io::Handle;
|
|
use pgBackRestTest::Common::Wait;
|
|
|
|
####################################################################################################################################
|
|
# Package name constant
|
|
####################################################################################################################################
|
|
use constant COMMON_IO_BUFFERED => __PACKAGE__;
|
|
push @EXPORT, qw(COMMON_IO_BUFFERED);
|
|
|
|
####################################################################################################################################
|
|
# CONSTRUCTOR
|
|
####################################################################################################################################
|
|
sub new
|
|
{
|
|
my $class = shift;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$oParent,
|
|
$iTimeout,
|
|
$lBufferMax,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '->new', \@_,
|
|
{name => 'oParent', trace => true},
|
|
{name => 'iTimeout', default => 0, trace => true},
|
|
{name => 'lBufferMax', default => COMMON_IO_BUFFER_MAX, trace => true},
|
|
);
|
|
|
|
# Bless with new class
|
|
my $self = $class->SUPER::new($oParent);
|
|
bless $self, $class;
|
|
|
|
# Set read handle so select object is created
|
|
$self->handleReadSet($self->handleRead());
|
|
|
|
# Set variables
|
|
$self->{iTimeout} = $iTimeout;
|
|
$self->{lBufferMax} = $lBufferMax;
|
|
|
|
# Initialize buffer
|
|
$self->{tBuffer} = '';
|
|
$self->{lBufferSize} = 0;
|
|
$self->{lBufferPos} = 0;
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'self', value => $self}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# read - buffered read from a handle with optional blocking
|
|
####################################################################################################################################
|
|
sub read
|
|
{
|
|
my $self = shift;
|
|
my $tBufferRef = shift;
|
|
my $iRequestSize = shift;
|
|
my $bBlock = shift;
|
|
|
|
# Set working variables
|
|
my $iRemainingSize = $iRequestSize;
|
|
|
|
# If there is data left over in the buffer from lineRead then use it
|
|
my $lBufferRemaining = $self->{lBufferSize} - $self->{lBufferPos};
|
|
|
|
if ($lBufferRemaining > 0)
|
|
{
|
|
my $iReadSize = $iRequestSize < $lBufferRemaining ? $iRequestSize : $lBufferRemaining;
|
|
|
|
$$tBufferRef .= substr($self->{tBuffer}, $self->{lBufferPos}, $iReadSize);
|
|
$self->{lBufferPos} += $iReadSize;
|
|
|
|
$iRemainingSize -= $iReadSize;
|
|
}
|
|
|
|
# If this is a blocking read then loop until all bytes have been read, else error. If not blocking read until the request size
|
|
# has been met or EOF.
|
|
my $fTimeStart = gettimeofday();
|
|
my $fRemaining = $self->timeout();
|
|
|
|
while ($iRemainingSize > 0 && $fRemaining > 0)
|
|
{
|
|
# Check if the sysread call will block
|
|
if ($self->pending() || $self->{oReadSelect}->can_read($fRemaining))
|
|
{
|
|
# Read data into the buffer
|
|
my $iReadSize = $self->parent()->read($tBufferRef, $iRemainingSize);
|
|
|
|
# Check for EOF
|
|
if ($iReadSize == 0)
|
|
{
|
|
if ($bBlock)
|
|
{
|
|
$self->error(ERROR_FILE_READ, "unable to read ${iRequestSize} byte(s) due to EOF from " . $self->id());
|
|
}
|
|
else
|
|
{
|
|
return $iRequestSize - $iRemainingSize;
|
|
}
|
|
}
|
|
|
|
# Update remaining size and return when it reaches 0
|
|
$iRemainingSize -= $iReadSize;
|
|
}
|
|
|
|
# Calculate time remaining before timeout
|
|
$fRemaining = $self->timeout() - (gettimeofday() - $fTimeStart);
|
|
};
|
|
|
|
# Throw an error if timeout happened before required bytes were read
|
|
if ($iRemainingSize != 0 && $bBlock)
|
|
{
|
|
$self->error(
|
|
ERROR_FILE_READ, "unable to read ${iRequestSize} byte(s) after " . $self->timeout() . ' second(s) from ' . $self->id());
|
|
}
|
|
|
|
return $iRequestSize - $iRemainingSize;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# readLine - read the next lf-terminated line.
|
|
####################################################################################################################################
|
|
sub readLine
|
|
{
|
|
my $self = shift;
|
|
my $bIgnoreEOF = shift;
|
|
my $bError = shift;
|
|
|
|
# Try to find the next linefeed
|
|
my $iLineFeedPos = index($self->{tBuffer}, "\n", $self->{lBufferPos});
|
|
|
|
# If no linefeed was found then load more data
|
|
if ($iLineFeedPos == -1)
|
|
{
|
|
my $fRemaining = $self->timeout();
|
|
my $fTimeStart = gettimeofday();
|
|
|
|
# Load data
|
|
do
|
|
{
|
|
# If the buffer already has data and the buffer position is not 0 then trim it so there's room for more data
|
|
if ($self->{lBufferPos} != 0)
|
|
{
|
|
$self->{tBuffer} = substr($self->{tBuffer}, $self->{lBufferPos});
|
|
$self->{lBufferSize} = $self->{lBufferSize} - $self->{lBufferPos};
|
|
$self->{lBufferPos} = 0;
|
|
}
|
|
|
|
# Load data into the buffer
|
|
my $iBufferRead = 0;
|
|
|
|
if ($self->pending() || $self->{oReadSelect}->can_read($fRemaining))
|
|
{
|
|
$iBufferRead = $self->parent()->read(
|
|
\$self->{tBuffer},
|
|
$self->{lBufferSize} >= $self->bufferMax() ? $self->bufferMax() : $self->bufferMax() - $self->{lBufferSize});
|
|
|
|
# Check for EOF
|
|
if ($iBufferRead == 0)
|
|
{
|
|
# Return undef if EOF is ignored
|
|
if (defined($bIgnoreEOF) && $bIgnoreEOF)
|
|
{
|
|
return;
|
|
}
|
|
|
|
# Else throw an error
|
|
$self->error(ERROR_FILE_READ, 'unexpected EOF reading line from ' . $self->id());
|
|
}
|
|
}
|
|
|
|
# If data was read then check for a linefeed
|
|
if ($iBufferRead > 0)
|
|
{
|
|
$self->{lBufferSize} += $iBufferRead;
|
|
|
|
$iLineFeedPos = index($self->{tBuffer}, "\n");
|
|
}
|
|
|
|
# Calculate time remaining before timeout
|
|
if ($iLineFeedPos == -1)
|
|
{
|
|
$fRemaining = $self->timeout() - (gettimeofday() - $fTimeStart);
|
|
}
|
|
}
|
|
while ($iLineFeedPos == -1 && $fRemaining > 0);
|
|
|
|
# If not linefeed was found within the timeout throw error
|
|
if ($iLineFeedPos == -1)
|
|
{
|
|
if (!defined($bError) || $bError)
|
|
{
|
|
$self->error(
|
|
ERROR_FILE_READ, 'unable to read line after ' . $self->timeout() . ' second(s) from ' . $self->id());
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
# Return the line that was found and adjust the buffer position
|
|
my $strLine = substr($self->{tBuffer}, $self->{lBufferPos}, $iLineFeedPos - $self->{lBufferPos});
|
|
$self->{lBufferPos} = $iLineFeedPos + 1;
|
|
|
|
return $strLine;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# writeLine - write a string and \n terminate it
|
|
####################################################################################################################################
|
|
sub writeLine
|
|
{
|
|
my $self = shift;
|
|
my $strBuffer = shift;
|
|
|
|
$strBuffer .= "\n";
|
|
return $self->parent()->write(\$strBuffer);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# Getters/Setters
|
|
####################################################################################################################################
|
|
sub timeout {shift->{iTimeout}};
|
|
sub bufferMax {shift->{lBufferMax}};
|
|
|
|
####################################################################################################################################
|
|
# handleReadSet - create a select object when read handle is set
|
|
####################################################################################################################################
|
|
sub handleReadSet
|
|
{
|
|
my $self = shift;
|
|
my $fhRead = shift;
|
|
|
|
$self->parent()->handleReadSet($fhRead);
|
|
|
|
# Create select object to make IO waits more efficient
|
|
$self->{oReadSelect} = IO::Select->new();
|
|
$self->{oReadSelect}->add($self->handleRead());
|
|
|
|
# Check if the read handle has a pending method. This should be checked before can_read for SSL sockets.
|
|
$self->{bPending} = defined($fhRead) && $fhRead->can('pending') ? true : false;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# Are bytes pending that won't show up in select?
|
|
####################################################################################################################################
|
|
sub pending
|
|
{
|
|
my $self = shift;
|
|
|
|
return ($self->{bPending} && $self->handleRead()->pending() ? true : false);
|
|
}
|
|
|
|
1;
|