Skip to content

Commit 243c733

Browse files
authored
Merge pull request #102 from clue-labs/unsolicited-error
Ignore unsolicited server error when not executing any commands
2 parents 955069f + a78c5ed commit 243c733

File tree

2 files changed

+36
-41
lines changed

2 files changed

+36
-41
lines changed

src/Io/Parser.php

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace React\MySQL\Io;
44

5-
use Evenement\EventEmitter;
65
use React\MySQL\Commands\AuthenticateCommand;
76
use React\MySQL\Commands\QueryCommand;
87
use React\MySQL\Commands\QuitCommand;
@@ -12,7 +11,7 @@
1211
/**
1312
* @internal
1413
*/
15-
class Parser extends EventEmitter
14+
class Parser
1615
{
1716
const PHASE_GOT_INIT = 1;
1817
const PHASE_AUTH_SENT = 2;
@@ -72,9 +71,6 @@ class Parser extends EventEmitter
7271

7372
public $protocalVersion = 0;
7473

75-
protected $errno = 0;
76-
protected $errmsg = '';
77-
7874
private $buffer;
7975

8076
protected $connectOptions;
@@ -142,14 +138,15 @@ public function parse($data)
142138
if ($response === 0xFF) {
143139
// error packet before handshake means we did not exchange capabilities and error does not include SQL state
144140
$this->phase = self::PHASE_AUTH_ERR;
145-
$this->errno = $this->buffer->readInt2();
146-
$this->errmsg = $this->buffer->read($this->pctSize - $len + $this->buffer->length());
147-
$this->debug(sprintf("Error Packet:%d %s\n", $this->errno, $this->errmsg));
141+
142+
$code = $this->buffer->readInt2();
143+
$exception = new Exception($this->buffer->read($this->pctSize - $len + $this->buffer->length()), $code);
144+
$this->debug(sprintf("Error Packet:%d %s\n", $code, $exception->getMessage()));
148145

149146
// error during init phase also means we're not currently executing any command
150147
// simply reject the first outstanding command in the queue (AuthenticateCommand)
151148
$this->currCommand = $this->executor->dequeue();
152-
$this->onError();
149+
$this->onError($exception);
153150
return;
154151
}
155152

@@ -181,12 +178,12 @@ public function parse($data)
181178

182179
if ($fieldCount === 0xFF) {
183180
// error packet
184-
$this->errno = $this->buffer->readInt2();
181+
$code = $this->buffer->readInt2();
185182
$this->buffer->skip(6); // skip SQL state
186-
$this->errmsg = $this->buffer->read($this->pctSize - $len + $this->buffer->length());
187-
$this->debug(sprintf("Error Packet:%d %s\n", $this->errno, $this->errmsg));
183+
$exception = new Exception($this->buffer->read($this->pctSize - $len + $this->buffer->length()), $code);
184+
$this->debug(sprintf("Error Packet:%d %s\n", $code, $exception->getMessage()));
188185

189-
$this->onError();
186+
$this->onError($exception);
190187
$this->nextRequest();
191188
} elseif ($fieldCount === 0x00 && $this->rsState !== self::RS_STATE_ROW) {
192189
// Empty OK Packet terminates a query without a result set (UPDATE, INSERT etc.)
@@ -278,15 +275,16 @@ private function onResultRow($row)
278275
$command->emit('result', array($row));
279276
}
280277

281-
protected function onError()
278+
private function onError(Exception $error)
282279
{
283-
$command = $this->currCommand;
284-
$this->currCommand = null;
280+
// reject current command with error if we're currently executing any commands
281+
// ignore unsolicited server error in case we're not executing any commands (connection will be dropped)
282+
if ($this->currCommand !== null) {
283+
$command = $this->currCommand;
284+
$this->currCommand = null;
285285

286-
$error = new Exception($this->errmsg, $this->errno);
287-
$this->errmsg = '';
288-
$this->errno = 0;
289-
$command->emit('error', array($error));
286+
$command->emit('error', array($error));
287+
}
290288
}
291289

292290
protected function onResultDone()
@@ -315,9 +313,8 @@ protected function onSuccess()
315313
$command->emit('success');
316314
}
317315

318-
protected function onClose()
316+
public function onClose()
319317
{
320-
$this->emit('close');
321318
if ($this->currCommand !== null) {
322319
$command = $this->currCommand;
323320
$this->currCommand = null;

tests/Io/ParserTest.php

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,44 +11,43 @@
1111

1212
class ParserTest extends BaseTestCase
1313
{
14-
public function testClosingStreamEmitsCloseEvent()
14+
public function testClosingStreamEmitsErrorForCurrentCommand()
1515
{
1616
$stream = new ThroughStream();
17-
$connection = $this->getMockBuilder('React\MySQL\ConnectionInterface')->disableOriginalConstructor()->getMock();
18-
$executor = new Executor($connection);
17+
$executor = new Executor();
1918

2019
$parser = new Parser($stream, $executor);
2120
$parser->start();
2221

23-
$parser->on('close', $this->expectCallableOnce());
22+
$command = new QueryCommand();
23+
$command->on('error', $this->expectCallableOnce());
24+
25+
// hack to inject command as current command
26+
$ref = new \ReflectionProperty($parser, 'currCommand');
27+
$ref->setAccessible(true);
28+
$ref->setValue($parser, $command);
2429

2530
$stream->close();
2631
}
2732

28-
public function testClosingStreamEmitsErrorForCurrentCommand()
33+
public function testUnexpectedErrorWithoutCurrentCommandWillBeIgnored()
2934
{
3035
$stream = new ThroughStream();
31-
$connection = $this->getMockBuilder('React\MySQL\ConnectionInterface')->disableOriginalConstructor()->getMock();
32-
$executor = new Executor($connection);
36+
37+
$executor = new Executor();
3338

3439
$parser = new Parser($stream, $executor);
3540
$parser->start();
3641

37-
$command = new QueryCommand();
38-
$command->on('error', $this->expectCallableOnce());
42+
$stream->on('close', $this->expectCallableNever());
3943

40-
// hack to inject command as current command
41-
$ref = new \ReflectionProperty($parser, 'currCommand');
42-
$ref->setAccessible(true);
43-
$ref->setValue($parser, $command);
44-
45-
$stream->close();
44+
$stream->write("\x33\0\0\0" . "\x0a" . "mysql\0" . str_repeat("\0", 44));
45+
$stream->write("\x17\0\0\0" . "\xFF" . "\x10\x04" . "Too many connections");
4646
}
4747

4848
public function testSendingErrorFrameDuringHandshakeShouldEmitErrorOnFollowingCommand()
4949
{
5050
$stream = new ThroughStream();
51-
$connection = $this->getMockBuilder('React\MySQL\ConnectionInterface')->disableOriginalConstructor()->getMock();
5251

5352
$command = new QueryCommand();
5453
$command->on('error', $this->expectCallableOnce());
@@ -58,7 +57,7 @@ public function testSendingErrorFrameDuringHandshakeShouldEmitErrorOnFollowingCo
5857
$error = $e;
5958
});
6059

61-
$executor = new Executor($connection);
60+
$executor = new Executor();
6261
$executor->enqueue($command);
6362

6463
$parser = new Parser($stream, $executor);
@@ -74,12 +73,11 @@ public function testSendingErrorFrameDuringHandshakeShouldEmitErrorOnFollowingCo
7473
public function testSendingIncompleteErrorFrameDuringHandshakeShouldNotEmitError()
7574
{
7675
$stream = new ThroughStream();
77-
$connection = $this->getMockBuilder('React\MySQL\ConnectionInterface')->disableOriginalConstructor()->getMock();
7876

7977
$command = new QueryCommand();
8078
$command->on('error', $this->expectCallableNever());
8179

82-
$executor = new Executor($connection);
80+
$executor = new Executor();
8381
$executor->enqueue($command);
8482

8583
$parser = new Parser($stream, $executor);

0 commit comments

Comments
 (0)