Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix faulty write buffer behavior when sending large data chunks over TLS (Mac OS X only) #150

Merged
merged 3 commits into from
May 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 5 additions & 16 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,17 @@ matrix:
- php: hhvm-3.18
install:
- composer require phpunit/phpunit:^5 --dev --no-interaction # requires legacy phpunit
- os: osx
- name: Mac OS X
os: osx
language: generic
php: 7.0 # just to look right on travis
env:
- PACKAGE: php70
before_install:
- curl -s http://getcomposer.org/installer | php
- mv composer.phar /usr/local/bin/composer
allow_failures:
- php: hhvm-3.18
- os: osx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove it from allowed failures as well? Since this now includes a fix for Mac OS of significant impact I would feel more secure that we secure we force it to also pass on OS X

Copy link
Member Author

@clue clue Apr 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we can.

The test suite works just fine on all platforms (including Mac OS X) before applying this PR and thereafter. I don't think this PR has any effect on whether Mac OS X build failures should be reported, but I can see that this could be useful in the long run.

We've put this on allow_failures because Travis' Mac OS X integration felt somewhat beta (#112), but I don't think we've seen significant problems since then, so I'm fine with it either way.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed 👍


install:
# OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
- |
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
brew tap homebrew/homebrew-php
echo "Installing PHP ..."
brew install "${PACKAGE}"
brew install "${PACKAGE}"-xdebug
brew link "${PACKAGE}"
echo "Installing composer ..."
curl -s http://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
fi
- composer install --no-interaction

script:
Expand Down
26 changes: 6 additions & 20 deletions src/WritableResourceStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,8 @@ public function close()
public function handleWrite()
{
$error = null;
\set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
$error = array(
'message' => $errstr,
'number' => $errno,
'file' => $errfile,
'line' => $errline
);
\set_error_handler(function ($_, $errstr) use (&$error) {
$error = $errstr;
});

if ($this->writeChunkSize === -1) {
Expand All @@ -130,25 +125,16 @@ public function handleWrite()

\restore_error_handler();

// Only report errors if *nothing* could be sent.
// Only report errors if *nothing* could be sent and an error has been raised.
// Ignore non-fatal warnings if *some* data could be sent.
// Any hard (permanent) error will fail to send any data at all.
// Sending excessive amounts of data will only flush *some* data and then
// report a temporary error (EAGAIN) which we do not raise here in order
// to keep the stream open for further tries to write.
// Should this turn out to be a permanent error later, it will eventually
// send *nothing* and we can detect this.
if ($sent === 0 || $sent === false) {
if ($error !== null) {
$error = new \ErrorException(
$error['message'],
0,
$error['number'],
$error['file'],
$error['line']
);
}

$this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . ($error !== null ? $error->getMessage() : 'Unknown error'), 0, $error)));
if (($sent === 0 || $sent === false) && $error !== null) {
$this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . $error)));
$this->close();

return;
Expand Down
13 changes: 11 additions & 2 deletions tests/FunctionalInternetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,20 @@ public function testUploadKilobyteSecure()
$this->assertNotEquals('', $buffer);
}

public function testUploadBiggerBlockSecureRequiresSmallerChunkSize()
public function testUploadBiggerBlockSecure()
{
$size = 50 * 1000;
// A few dozen kilobytes should be enough to verify this works.
// Underlying buffer sizes are platform-specific, so let's increase this
// a bit to trigger different behavior on Linux vs Mac OS X.
$size = 136 * 1000;

$stream = stream_socket_client('tls://httpbin.org:443');

// PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
// chunks of data over TLS streams at once.
// We work around this by limiting the write chunk size to 8192 bytes
// here to also support older PHP versions.
// See https://github.com/reactphp/socket/issues/105
$loop = Factory::create();
$stream = new DuplexResourceStream(
$stream,
Expand Down