Skip to content

Commit 83ac29b

Browse files
committed
Create lazy connection only on demand (on first command)
1 parent 200067d commit 83ac29b

File tree

5 files changed

+226
-93
lines changed

5 files changed

+226
-93
lines changed

README.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,18 +171,25 @@ $connection->query(…);
171171
This method immediately returns a "virtual" connection implementing the
172172
[`ConnectionInterface`](#connectioninterface) that can be used to
173173
interface with your MySQL database. Internally, it lazily creates the
174-
underlying database connection (which may take some time) and will
175-
queue all outstanding requests until the underlying connection is ready.
174+
underlying database connection (which may take some time) only once the
175+
first request is invoked on this instance and will queue all outstanding
176+
requests until the underlying connection is ready.
176177

177178
From a consumer side this means that you can start sending queries to the
178-
database right away while the connection may still be pending. It will
179-
ensure that all commands will be executed in the order they are enqueued
180-
once the connection is ready. If the database connection fails, it will
181-
emit an `error` event, reject all outstanding commands and `close` the
182-
connection as described in the `ConnectionInterface`. In other words, it
183-
behaves just like a real connection and frees you from having to deal
179+
database right away while the actual connection may still be outstanding.
180+
It will ensure that all commands will be executed in the order they are
181+
enqueued once the connection is ready. If the database connection fails,
182+
it will emit an `error` event, reject all outstanding commands and `close`
183+
the connection as described in the `ConnectionInterface`. In other words,
184+
it behaves just like a real connection and frees you from having to deal
184185
with its async resolution.
185186

187+
Note that creating the underlying connection will be deferred until the
188+
first request is invoked. Accordingly, any eventual connection issues
189+
will be detected once this instance is first used. Similarly, calling
190+
`quit()` on this instance before invoking any requests will succeed
191+
immediately and will not wait for an actual underlying connection.
192+
186193
Depending on your particular use case, you may prefer this method or the
187194
underlying `createConnection()` which resolves with a promise. For many
188195
simple use cases it may be easier to create a lazy connection.
@@ -210,9 +217,9 @@ $factory->createLazyConnection('localhost');
210217
```
211218

212219
This method respects PHP's `default_socket_timeout` setting (default 60s)
213-
as a timeout for establishing the connection and waiting for successful
214-
authentication. You can explicitly pass a custom timeout value in seconds
215-
(or use a negative number to not apply a timeout) like this:
220+
as a timeout for establishing the underlying connection and waiting for
221+
successful authentication. You can explicitly pass a custom timeout value
222+
in seconds (or use a negative number to not apply a timeout) like this:
216223

217224
```php
218225
$factory->createLazyConnection('localhost?timeout=0.5');

src/Factory.php

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -211,18 +211,25 @@ public function createConnection($uri)
211211
* This method immediately returns a "virtual" connection implementing the
212212
* [`ConnectionInterface`](#connectioninterface) that can be used to
213213
* interface with your MySQL database. Internally, it lazily creates the
214-
* underlying database connection (which may take some time) and will
215-
* queue all outstanding requests until the underlying connection is ready.
214+
* underlying database connection (which may take some time) only once the
215+
* first request is invoked on this instance and will queue all outstanding
216+
* requests until the underlying connection is ready.
216217
*
217218
* From a consumer side this means that you can start sending queries to the
218-
* database right away while the connection may still be pending. It will
219-
* ensure that all commands will be executed in the order they are enqueued
220-
* once the connection is ready. If the database connection fails, it will
221-
* emit an `error` event, reject all outstanding commands and `close` the
222-
* connection as described in the `ConnectionInterface`. In other words, it
223-
* behaves just like a real connection and frees you from having to deal
219+
* database right away while the actual connection may still be outstanding.
220+
* It will ensure that all commands will be executed in the order they are
221+
* enqueued once the connection is ready. If the database connection fails,
222+
* it will emit an `error` event, reject all outstanding commands and `close`
223+
* the connection as described in the `ConnectionInterface`. In other words,
224+
* it behaves just like a real connection and frees you from having to deal
224225
* with its async resolution.
225226
*
227+
* Note that creating the underlying connection will be deferred until the
228+
* first request is invoked. Accordingly, any eventual connection issues
229+
* will be detected once this instance is first used. Similarly, calling
230+
* `quit()` on this instance before invoking any requests will succeed
231+
* immediately and will not wait for an actual underlying connection.
232+
*
226233
* Depending on your particular use case, you may prefer this method or the
227234
* underlying `createConnection()` which resolves with a promise. For many
228235
* simple use cases it may be easier to create a lazy connection.
@@ -250,9 +257,9 @@ public function createConnection($uri)
250257
* ```
251258
*
252259
* This method respects PHP's `default_socket_timeout` setting (default 60s)
253-
* as a timeout for establishing the connection and waiting for successful
254-
* authentication. You can explicitly pass a custom timeout value in seconds
255-
* (or use a negative number to not apply a timeout) like this:
260+
* as a timeout for establishing the underlying connection and waiting for
261+
* successful authentication. You can explicitly pass a custom timeout value
262+
* in seconds (or use a negative number to not apply a timeout) like this:
256263
*
257264
* ```php
258265
* $factory->createLazyConnection('localhost?timeout=0.5');
@@ -263,6 +270,6 @@ public function createConnection($uri)
263270
*/
264271
public function createLazyConnection($uri)
265272
{
266-
return new LazyConnection($this->createConnection($uri));
273+
return new LazyConnection($this, $uri);
267274
}
268275
}

src/Io/LazyConnection.php

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,84 +4,102 @@
44

55
use React\MySQL\ConnectionInterface;
66
use Evenement\EventEmitter;
7-
use React\Promise\PromiseInterface;
87
use React\MySQL\Exception;
8+
use React\MySQL\Factory;
99

1010
/**
1111
* @internal
1212
* @see \React\MySQL\Factory::createLazyConnection()
1313
*/
1414
class LazyConnection extends EventEmitter implements ConnectionInterface
1515
{
16+
private $factory;
17+
private $uri;
1618
private $connecting;
1719
private $closed = false;
1820
private $busy = false;
1921

20-
public function __construct(PromiseInterface $connecting)
22+
public function __construct(Factory $factory, $uri)
2123
{
22-
$this->connecting = $connecting;
24+
$this->factory = $factory;
25+
$this->uri = $uri;
26+
}
27+
28+
private function connecting()
29+
{
30+
if ($this->connecting === null) {
31+
$this->connecting = $this->factory->createConnection($this->uri);
32+
33+
$this->connecting->then(function (ConnectionInterface $connection) {
34+
// connection completed => forward error and close events
35+
$connection->on('error', function ($e) {
36+
$this->emit('error', [$e]);
37+
});
38+
$connection->on('close', function () {
39+
$this->close();
40+
});
41+
}, function (\Exception $e) {
42+
// connection failed => emit error if connection is not already closed
43+
if ($this->closed) {
44+
return;
45+
}
2346

24-
$connecting->then(function (ConnectionInterface $connection) {
25-
// connection completed => forward error and close events
26-
$connection->on('error', function ($e) {
2747
$this->emit('error', [$e]);
28-
});
29-
$connection->on('close', function () {
3048
$this->close();
3149
});
32-
}, function (\Exception $e) {
33-
// connection failed => emit error if connection is not already closed
34-
if ($this->closed) {
35-
return;
36-
}
50+
}
3751

38-
$this->emit('error', [$e]);
39-
$this->close();
40-
});
52+
return $this->connecting;
4153
}
4254

4355
public function query($sql, array $params = [])
4456
{
45-
if ($this->connecting === null) {
57+
if ($this->closed) {
4658
return \React\Promise\reject(new Exception('Connection closed'));
4759
}
4860

49-
return $this->connecting->then(function (ConnectionInterface $connection) use ($sql, $params) {
61+
return $this->connecting()->then(function (ConnectionInterface $connection) use ($sql, $params) {
5062
return $connection->query($sql, $params);
5163
});
5264
}
5365

5466
public function queryStream($sql, $params = [])
5567
{
56-
if ($this->connecting === null) {
68+
if ($this->closed) {
5769
throw new Exception('Connection closed');
5870
}
5971

6072
return \React\Promise\Stream\unwrapReadable(
61-
$this->connecting->then(function (ConnectionInterface $connection) use ($sql, $params) {
73+
$this->connecting()->then(function (ConnectionInterface $connection) use ($sql, $params) {
6274
return $connection->queryStream($sql, $params);
6375
})
6476
);
6577
}
6678

6779
public function ping()
6880
{
69-
if ($this->connecting === null) {
81+
if ($this->closed) {
7082
return \React\Promise\reject(new Exception('Connection closed'));
7183
}
7284

73-
return $this->connecting->then(function (ConnectionInterface $connection) {
85+
return $this->connecting()->then(function (ConnectionInterface $connection) {
7486
return $connection->ping();
7587
});
7688
}
7789

7890
public function quit()
7991
{
80-
if ($this->connecting === null) {
92+
if ($this->closed) {
8193
return \React\Promise\reject(new Exception('Connection closed'));
8294
}
8395

84-
return $this->connecting->then(function (ConnectionInterface $connection) {
96+
// not already connecting => no need to connect, simply close virtual connection
97+
if ($this->connecting === null) {
98+
$this->close();
99+
return \React\Promise\resolve();
100+
}
101+
102+
return $this->connecting()->then(function (ConnectionInterface $connection) {
85103
return $connection->quit();
86104
});
87105
}
@@ -95,12 +113,13 @@ public function close()
95113
$this->closed = true;
96114

97115
// either close active connection or cancel pending connection attempt
98-
$this->connecting->then(function (ConnectionInterface $connection) {
99-
$connection->close();
100-
});
101-
$this->connecting->cancel();
102-
103-
$this->connecting = null;
116+
if ($this->connecting !== null) {
117+
$this->connecting->then(function (ConnectionInterface $connection) {
118+
$connection->close();
119+
});
120+
$this->connecting->cancel();
121+
$this->connecting = null;
122+
}
104123

105124
$this->emit('close');
106125
$this->removeAllListeners();

tests/FactoryTest.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,22 @@ public function testCancelConnectDuringAuthenticationWillCloseConnection()
353353
})));
354354
}
355355

356-
public function testConnectLazyWithValidAuthWillRunUntilQuit()
356+
public function testConnectLazyWithAnyAuthWillQuitWithoutRunning()
357+
{
358+
$this->expectOutputString('closed.');
359+
360+
$loop = \React\EventLoop\Factory::create();
361+
$factory = new Factory($loop);
362+
363+
$uri = 'mysql://random:pass@host';
364+
$connection = $factory->createLazyConnection($uri);
365+
366+
$connection->quit()->then(function () {
367+
echo 'closed.';
368+
});
369+
}
370+
371+
public function testConnectLazyWithValidAuthWillRunUntilQuitAfterPing()
357372
{
358373
$this->expectOutputString('closed.');
359374

@@ -363,14 +378,16 @@ public function testConnectLazyWithValidAuthWillRunUntilQuit()
363378
$uri = $this->getConnectionString();
364379
$connection = $factory->createLazyConnection($uri);
365380

381+
$connection->ping();
382+
366383
$connection->quit()->then(function () {
367384
echo 'closed.';
368385
});
369386

370387
$loop->run();
371388
}
372389

373-
public function testConnectLazyWithInvalidAuthWillEmitErrorAndClose()
390+
public function testConnectLazyWithInvalidAuthWillEmitErrorAndCloseAfterPing()
374391
{
375392
$loop = \React\EventLoop\Factory::create();
376393
$factory = new Factory($loop);
@@ -381,6 +398,8 @@ public function testConnectLazyWithInvalidAuthWillEmitErrorAndClose()
381398
$connection->on('error', $this->expectCallableOnce());
382399
$connection->on('close', $this->expectCallableOnce());
383400

401+
$connection->ping();
402+
384403
$loop->run();
385404
}
386405

0 commit comments

Comments
 (0)