Skip to content

Commit 573cf47

Browse files
committed
added basic connection pool tests
1 parent f8dbddc commit 573cf47

File tree

3 files changed

+160
-16
lines changed

3 files changed

+160
-16
lines changed

src/Bolt/ConnectionPool.php

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,12 @@
1414
namespace Laudis\Neo4j\Bolt;
1515

1616
use Generator;
17-
use Laudis\Neo4j\Contracts\AuthenticateInterface;
1817
use Laudis\Neo4j\Contracts\ConnectionFactoryInterface;
1918
use Laudis\Neo4j\Contracts\ConnectionInterface;
2019
use Laudis\Neo4j\Contracts\ConnectionPoolInterface;
2120
use Laudis\Neo4j\Contracts\SemaphoreInterface;
2221
use Laudis\Neo4j\Databags\ConnectionRequestData;
2322
use Laudis\Neo4j\Databags\SessionConfiguration;
24-
use Laudis\Neo4j\Databags\SslConfiguration;
25-
use Psr\Http\Message\UriInterface;
26-
2723
use function method_exists;
2824
use function microtime;
2925
use function shuffle;
@@ -57,23 +53,32 @@ public function acquire(SessionConfiguration $config): Generator
5753
$generator = $this->semaphore->wait();
5854
$start = microtime(true);
5955

60-
// If the generator is valid, it means we are waiting to acquire a new connection.
61-
// This means we can use this time to check if we can reuse a connection or should throw a timeout exception.
62-
while ($generator->valid()) {
63-
$continue = yield microtime(true) - $start;
64-
$generator->send($continue);
65-
if ($continue === false) {
66-
return null;
56+
return (function () use ($generator, $start, $config) {
57+
// If the generator is valid, it means we are waiting to acquire a new connection.
58+
// This means we can use this time to check if we can reuse a connection or should throw a timeout exception.
59+
while ($generator->valid()) {
60+
$continue = yield microtime(true) - $start;
61+
$generator->send($continue);
62+
if ($continue === false) {
63+
return null;
64+
}
65+
66+
$connection = $this->returnAnyAvailableConnection($config);
67+
if ($connection !== null) {
68+
return $connection;
69+
}
6770
}
6871

6972
$connection = $this->returnAnyAvailableConnection($config);
7073
if ($connection !== null) {
7174
return $connection;
7275
}
73-
}
7476

75-
return $this->returnAnyAvailableConnection($config) ??
76-
$this->factory->createConnection($this->data, $config);
77+
$connection = $this->factory->createConnection($this->data, $config);
78+
$this->activeConnections[] = $connection;
79+
80+
return $connection;
81+
})();
7782
}
7883

7984
public function release(ConnectionInterface $connection): void

src/Common/GeneratorHelper.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public static function getReturnFromGenerator(Generator $generator, float $timeo
3232
if ($timeout) {
3333
self::guardTiming($start, $timeout);
3434
}
35+
$generator->next();
3536
}
3637

3738
return $generator->getReturn();

tests/Unit/BoltConnectionPoolTest.php

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

33
declare(strict_types=1);
44

5+
/*
6+
* This file is part of the Neo4j PHP Client and Driver package.
7+
*
8+
* (c) Nagels <https://nagels.tech>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
514
namespace Laudis\Neo4j\Tests\Unit;
615

16+
use Generator;
17+
use Laudis\Neo4j\Authentication\Authenticate;
718
use Laudis\Neo4j\Bolt\ConnectionPool;
8-
use Monolog\Test\TestCase;
19+
use Laudis\Neo4j\Common\GeneratorHelper;
20+
use Laudis\Neo4j\Common\Uri;
21+
use Laudis\Neo4j\Contracts\ConnectionFactoryInterface;
22+
use Laudis\Neo4j\Contracts\ConnectionInterface;
23+
use Laudis\Neo4j\Contracts\SemaphoreInterface;
24+
use Laudis\Neo4j\Databags\ConnectionRequestData;
25+
use Laudis\Neo4j\Databags\SessionConfiguration;
26+
use Laudis\Neo4j\Databags\SslConfiguration;
27+
use function microtime;
28+
use PHPUnit\Framework\MockObject\MockObject;
29+
use PHPUnit\Framework\TestCase;
30+
use function sleep;
931

1032
class BoltConnectionPoolTest extends TestCase
1133
{
1234
private ConnectionPool $pool;
35+
/** @var SemaphoreInterface&MockObject */
36+
private SemaphoreInterface $semaphore;
37+
/** @var ConnectionFactoryInterface&MockObject */
38+
private ConnectionFactoryInterface $factory;
1339

1440
protected function setUp(): void
1541
{
1642
parent::setUp();
1743

18-
$this->pool = new ConnectionPool()
44+
$this->setupPool((function (): Generator {
45+
yield 1.0;
46+
47+
return true;
48+
})());
49+
}
50+
51+
public function testSimpleAcquire(): void
52+
{
53+
$generator = $this->pool->acquire(SessionConfiguration::default());
54+
55+
$connection = GeneratorHelper::getReturnFromGenerator($generator);
56+
57+
self::assertInstanceOf(ConnectionInterface::class, $connection);
58+
}
59+
60+
public function testTimingAcquire(): void
61+
{
62+
$generator = $this->pool->acquire(SessionConfiguration::default());
63+
$time = microtime(true);
64+
65+
sleep(1);
66+
67+
$result = $generator->current();
68+
$delta = microtime(true) - $time;
69+
70+
$generator->next();
71+
$generator->getReturn();
72+
73+
self::assertEqualsWithDelta($delta, $result, 0.05);
74+
self::assertEqualsWithDelta(1.0, $result, 0.05);
75+
}
76+
77+
public function testMultipleWaits(): void
78+
{
79+
$this->setupPool((function (): Generator {
80+
yield 1.0;
81+
yield 2.0;
82+
yield 3.0;
83+
84+
return true;
85+
})());
86+
87+
$generator = $this->pool->acquire(SessionConfiguration::default());
88+
$count = 0;
89+
while ($generator->valid()) {
90+
++$count;
91+
$generator->next();
92+
}
93+
94+
static::assertEquals(3, $count);
95+
}
96+
97+
public function testRelease(): void
98+
{
99+
$this->semaphore->expects(self::once())->method('post');
100+
101+
$this->pool->release($this->createMock(ConnectionInterface::class));
102+
}
103+
104+
public function testReleaseReference(): void
105+
{
106+
$connection = $this->pool->acquire(SessionConfiguration::default());
107+
$connection->next();
108+
$connection = $connection->getReturn();
109+
110+
static::assertInstanceOf(ConnectionInterface::class, $connection);
111+
112+
// We use a refCount instead of checking for garbage collection as
113+
// the underlying libraries for mocking keep references throughout the system
114+
$refCount = $this->refCount($connection);
115+
116+
$this->pool->release($connection);
117+
118+
static::assertEquals($refCount - 1, $this->refCount($connection));
119+
}
120+
121+
/**
122+
* @param object $var
123+
*/
124+
private function refCount($var): int
125+
{
126+
ob_start();
127+
debug_zval_dump($var);
128+
$dump = ob_get_clean();
129+
130+
$matches = [];
131+
preg_match('/refcount\(([0-9]+)/', $dump, $matches);
132+
133+
$count = (int) ($matches[1] ?? '0');
134+
135+
// 3 references are added, including when calling debug_zval_dump()
136+
return $count - 3;
137+
}
138+
139+
private function setupPool(Generator $semaphoreGenerator): void
140+
{
141+
$this->semaphore = $this->createMock(SemaphoreInterface::class);
142+
$this->semaphore->method('wait')
143+
->willReturn($semaphoreGenerator);
144+
145+
$this->factory = $this->createMock(ConnectionFactoryInterface::class);
146+
$this->factory->method('createConnection')
147+
->willReturn($this->createMock(ConnectionInterface::class));
148+
149+
$this->pool = new ConnectionPool(
150+
$this->semaphore, $this->factory, new ConnectionRequestData(
151+
Uri::create(''),
152+
Authenticate::disabled(),
153+
'',
154+
SslConfiguration::default()
155+
)
156+
);
19157
}
20158
}

0 commit comments

Comments
 (0)