Skip to content

Commit d255f7e

Browse files
authored
Merge 6a02313 into 68b04b0
2 parents 68b04b0 + 6a02313 commit d255f7e

File tree

10 files changed

+348
-44
lines changed

10 files changed

+348
-44
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ use Tarantool\Client\Client;
9292
$client = Client::fromDsn('tcp://127.0.0.1');
9393
$client = Client::fromDsn('tcp://[fe80::1]:3301');
9494
$client = Client::fromDsn('tcp://user:pass@example.com:3301');
95-
$client = Client::fromDsn('tcp://user@example.com/?connect_timeout=5&max_retries=3');
95+
$client = Client::fromDsn('tcp://user@example.com/?connect_timeout=5.0&max_retries=3');
9696
$client = Client::fromDsn('unix:///var/run/tarantool/my_instance.sock');
9797
$client = Client::fromDsn('unix://user:pass@/var/run/tarantool/my_instance.sock?max_retries=3');
9898
```
@@ -122,8 +122,8 @@ The following options are available:
122122
Name | Type | Default | Description
123123
--- | :---: | :---: | ---
124124
*uri* | string | 'tcp://127.0.0.1:3301' | The connection uri that is used to create a `StreamConnection` object.
125-
*connect_timeout* | integer | 5 | The number of seconds that the client waits for a connect to a Tarantool server before throwing a `ConnectionFailed` exception.
126-
*socket_timeout* | integer | 5 | The number of seconds that the client waits for a respond from a Tarantool server before throwing a `CommunicationFailed` exception.
125+
*connect_timeout* | float | 5.0 | The number of seconds that the client waits for a connect to a Tarantool server before throwing a `ConnectionFailed` exception.
126+
*socket_timeout* | float | 5.0 | The number of seconds that the client waits for a respond from a Tarantool server before throwing a `CommunicationFailed` exception.
127127
*tcp_nodelay* | boolean | true | Whether the Nagle algorithm is disabled on a TCP connection.
128128
*persistent* | boolean | false | Whether to use a persistent connection.
129129
*username* | string | | The username for the user being authenticated.
@@ -147,8 +147,8 @@ use Tarantool\Client\Middleware\RetryMiddleware;
147147
use Tarantool\Client\Packer\PurePacker;
148148

149149
$connection = StreamConnection::createTcp('tcp://127.0.0.1:3301', [
150-
'socket_timeout' => 5,
151-
'connect_timeout' => 5,
150+
'socket_timeout' => 5.0,
151+
'connect_timeout' => 5.0,
152152
// ...
153153
]);
154154

@@ -182,7 +182,7 @@ $request = new CallRequest('box.stat');
182182
$response = $handler->handle($request);
183183
$data = $response->getBodyField(Keys::DATA);
184184
```
185-
185+
186186
The library ships with two handlers:
187187

188188
* `DefaultHandler` is used for handling low-level communication with a Tarantool server

src/Client.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ public static function fromOptions(array $options, ?Packer $packer = null) : sel
7676
$middleware[] = new AuthenticationMiddleware($options['username'], $options['password'] ?? '');
7777
}
7878

79-
$connection = StreamConnection::create($options['uri'] ?? StreamConnection::DEFAULT_URI, $connectionOptions);
79+
$connection = isset($options['uri'])
80+
? StreamConnection::create($options['uri'], $connectionOptions)
81+
: StreamConnection::createTcp(StreamConnection::DEFAULT_TCP_URI, $connectionOptions);
82+
8083
$handler = new DefaultHandler($connection, $packer ?? PackerFactory::create());
8184

8285
return $middleware
@@ -89,10 +92,10 @@ public static function fromDsn(string $dsn, ?Packer $packer = null) : self
8992
$dsn = Dsn::parse($dsn);
9093

9194
$connectionOptions = [];
92-
if (null !== $timeout = $dsn->getInt('connect_timeout')) {
95+
if (null !== $timeout = $dsn->getFloat('connect_timeout')) {
9396
$connectionOptions['connect_timeout'] = $timeout;
9497
}
95-
if (null !== $timeout = $dsn->getInt('socket_timeout')) {
98+
if (null !== $timeout = $dsn->getFloat('socket_timeout')) {
9699
$connectionOptions['socket_timeout'] = $timeout;
97100
}
98101
if (null !== $tcpNoDelay = $dsn->getBool('tcp_nodelay')) {

src/Connection/StreamConnection.php

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,54 +19,62 @@
1919

2020
final class StreamConnection implements Connection
2121
{
22-
public const DEFAULT_URI = 'tcp://127.0.0.1:3301';
22+
public const DEFAULT_TCP_URI = 'tcp://127.0.0.1:3301';
2323

24-
private const DEFAULT_OPTIONS = [
25-
'connect_timeout' => 5,
26-
'socket_timeout' => 5,
27-
'tcp_nodelay' => true,
28-
'persistent' => false,
29-
];
24+
/** @var string */
25+
private $uri;
3026

31-
/** @var resource|null */
32-
private $stream;
27+
/** @var float */
28+
private $connectTimeout;
29+
30+
/** @var float */
31+
private $socketTimeout;
32+
33+
/** @var bool */
34+
private $persistent;
3335

3436
/** @var resource|null */
3537
private $streamContext;
3638

37-
/** @var string */
38-
private $uri;
39-
40-
/** @var non-empty-array<string, mixed> */
41-
private $options;
39+
/** @var resource|null */
40+
private $stream;
4241

4342
/** @var Greeting|null */
4443
private $greeting;
4544

4645
/**
4746
* @param string $uri
48-
* @param array<string, mixed> $options
4947
*/
50-
private function __construct($uri, $options)
48+
private function __construct($uri, float $connectTimeout, float $socketTimeout, bool $persistent, bool $tcpNoDelay)
5149
{
5250
$this->uri = $uri;
53-
$this->options = $options + self::DEFAULT_OPTIONS;
54-
}
51+
$this->connectTimeout = $connectTimeout;
52+
$this->socketTimeout = $socketTimeout;
53+
$this->persistent = $persistent;
5554

56-
public static function createTcp(string $uri = self::DEFAULT_URI, array $options = []) : self
57-
{
58-
$self = new self($uri, $options);
59-
60-
if ($self->options['tcp_nodelay'] ?? false) {
61-
$self->streamContext = \stream_context_create(['socket' => ['tcp_nodelay' => true]]);
55+
if ($tcpNoDelay) {
56+
$this->streamContext = \stream_context_create(['socket' => ['tcp_nodelay' => true]]);
6257
}
58+
}
6359

64-
return $self;
60+
public static function createTcp(string $uri = self::DEFAULT_TCP_URI, array $options = []) : self
61+
{
62+
return new self($uri,
63+
$options['connect_timeout'] ?? 5.0,
64+
$options['socket_timeout'] ?? 5.0,
65+
$options['persistent'] ?? false,
66+
$options['tcp_nodelay'] ?? false
67+
);
6568
}
6669

6770
public static function createUds(string $uri, array $options = []) : self
6871
{
69-
return new self($uri, $options);
72+
return new self($uri,
73+
$options['connect_timeout'] ?? 5.0,
74+
$options['socket_timeout'] ?? 5.0,
75+
$options['persistent'] ?? false,
76+
false
77+
);
7078
}
7179

7280
public static function create(string $uri, array $options = []) : self
@@ -82,33 +90,36 @@ public function open() : Greeting
8290
return $this->greeting;
8391
}
8492

85-
$flags = $this->options['persistent']
93+
$flags = $this->persistent
8694
? \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_PERSISTENT
8795
: \STREAM_CLIENT_CONNECT;
8896

8997
$stream = $this->streamContext ? @\stream_socket_client(
9098
$this->uri,
9199
$errorCode,
92100
$errorMessage,
93-
(float) $this->options['connect_timeout'],
101+
$this->connectTimeout,
94102
$flags,
95103
$this->streamContext
96104
) : @\stream_socket_client(
97105
$this->uri,
98106
$errorCode,
99107
$errorMessage,
100-
(float) $this->options['connect_timeout'],
108+
$this->connectTimeout,
101109
$flags
102110
);
103111

104112
if (false === $stream) {
105113
throw ConnectionFailed::fromUriAndReason($this->uri, $errorMessage);
106114
}
107115

116+
$socketTimeoutSeconds = (int) $this->socketTimeout;
117+
$socketTimeoutMicroSeconds = (int) (($this->socketTimeout - $socketTimeoutSeconds) * 1000000);
118+
\stream_set_timeout($stream, $socketTimeoutSeconds, $socketTimeoutMicroSeconds);
119+
108120
$this->stream = $stream;
109-
\stream_set_timeout($this->stream, $this->options['socket_timeout']);
110121

111-
if ($this->options['persistent'] && \ftell($this->stream)) {
122+
if ($this->persistent && \ftell($stream)) {
112123
return $this->greeting = Greeting::unknown();
113124
}
114125

src/Dsn.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,19 @@ public function getInt(string $name, ?int $default = null) : ?int
181181
return $value;
182182
}
183183

184+
public function getFloat(string $name, ?float $default = null) : ?float
185+
{
186+
if (!isset($this->options[$name])) {
187+
return $default;
188+
}
189+
190+
if (false === $value = \filter_var($this->options[$name], \FILTER_VALIDATE_FLOAT)) {
191+
throw new \TypeError(\sprintf('DSN option "%s" must be of the type float', $name));
192+
}
193+
194+
return $value;
195+
}
196+
184197
/**
185198
* @psalm-return never-returns
186199
*/

tests/Integration/Connection/ConnectionTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public function testConnectTimedOut() : void
144144

145145
// @see http://stackoverflow.com/q/100841/1160901
146146
$host = '8.8.8.8';
147-
$connectTimeout = 2;
147+
$connectTimeout = 1.15;
148148

149149
$client = $clientBuilder->setConnectionOptions(['connect_timeout' => $connectTimeout])
150150
->setHost($host)
@@ -163,7 +163,7 @@ public function testConnectTimedOut() : void
163163
$time = microtime(true) - $start;
164164
self::assertMatchesRegularExpression('/Failed to connect to .+?: (Connection|Operation) timed out/', $e->getMessage());
165165
self::assertGreaterThanOrEqual($connectTimeout, $time);
166-
self::assertLessThanOrEqual($connectTimeout + 0.1, $time);
166+
self::assertLessThan($connectTimeout + 0.05, $time);
167167

168168
return;
169169
}

tests/Integration/Connection/ReadTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public function testUnableToReadResponse() : void
109109

110110
public function testSocketReadTimedOut() : void
111111
{
112-
$socketTimeout = 1;
112+
$socketTimeout = 1.15;
113113

114114
$client = ClientBuilder::createFromEnv()
115115
->setConnectionOptions(['socket_timeout' => $socketTimeout])
@@ -123,7 +123,7 @@ public function testSocketReadTimedOut() : void
123123
$time = microtime(true) - $start;
124124
self::assertSame('Read timed out', $e->getMessage());
125125
self::assertGreaterThanOrEqual($socketTimeout, $time);
126-
self::assertLessThanOrEqual($socketTimeout + 0.1, $time);
126+
self::assertLessThan($socketTimeout + 0.05, $time);
127127

128128
return;
129129
}

tests/Unit/ClientFactoryTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,46 @@ public function testFromDsnCreatesClientWithCustomPacker() : void
5656

5757
self::assertSame($packer, $client->getHandler()->getPacker());
5858
}
59+
60+
/**
61+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideClientArrayOptionsOfValidTypes
62+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideTcpExtraConnectionArrayOptionsOfValidTypes
63+
* @doesNotPerformAssertions
64+
*/
65+
public function testFromOptionsAcceptsOptionOfValidType(string $optionName, $optionValue, array $extraOptions = []) : void
66+
{
67+
Client::fromOptions([$optionName => $optionValue] + $extraOptions);
68+
}
69+
70+
/**
71+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideClientArrayOptionsOfInvalidTypes
72+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideTcpExtraConnectionArrayOptionsOfInvalidTypes
73+
*/
74+
public function testFromOptionsRejectsOptionOfInvalidType(string $optionName, $optionValue, string $expectedType, array $extraOptions = []) : void
75+
{
76+
$this->expectException(\TypeError::class);
77+
$this->expectExceptionMessageMatches("/must be of(?: the)? type $expectedType/");
78+
79+
Client::fromOptions([$optionName => $optionValue] + $extraOptions);
80+
}
81+
82+
/**
83+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideClientDsnOptionsOfValidTypes
84+
* @doesNotPerformAssertions
85+
*/
86+
public function testFromDsnAcceptsOptionOfValidType(string $query) : void
87+
{
88+
Client::fromDsn("tcp://tnt/?$query");
89+
}
90+
91+
/**
92+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideClientDsnOptionsOfInvalidTypes
93+
*/
94+
public function testFromDsnRejectsOptionOfInvalidType(string $query, string $optionName, string $expectedType) : void
95+
{
96+
$this->expectException(\TypeError::class);
97+
$this->expectExceptionMessageMatches("/\b$optionName\b.+?must be of(?: the)? type $expectedType/");
98+
99+
Client::fromDsn("tcp://tnt/?$query");
100+
}
59101
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the tarantool/client package.
5+
*
6+
* (c) Eugene Leonovich <gen.work@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Tarantool\Client\Tests\Unit\Connection;
15+
16+
use PHPUnit\Framework\TestCase;
17+
use Tarantool\Client\Connection\StreamConnection;
18+
19+
final class StreamConnectionTest extends TestCase
20+
{
21+
/**
22+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideConnectionArrayOptionsOfValidTypes
23+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideTcpExtraConnectionArrayOptionsOfValidTypes
24+
* @doesNotPerformAssertions
25+
*/
26+
public function testCreateTcpAcceptsOptionOfValidType(string $optionName, $optionValue) : void
27+
{
28+
StreamConnection::createTcp(StreamConnection::DEFAULT_TCP_URI, [$optionName => $optionValue]);
29+
}
30+
31+
/**
32+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideConnectionArrayOptionsOfValidTypes
33+
* @doesNotPerformAssertions
34+
*/
35+
public function testCreateUdsAcceptsOptionOfValidType(string $optionName, $optionValue) : void
36+
{
37+
StreamConnection::createUds('unix:///socket.sock', [$optionName => $optionValue]);
38+
}
39+
40+
/**
41+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideConnectionArrayOptionsOfInvalidTypes
42+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideTcpExtraConnectionArrayOptionsOfInvalidTypes
43+
*/
44+
public function testCreateTcpRejectsOptionOfInvalidType(string $optionName, $optionValue, string $expectedType) : void
45+
{
46+
$this->expectException(\TypeError::class);
47+
$this->expectExceptionMessageMatches("/must be of(?: the)? type $expectedType/");
48+
49+
StreamConnection::createTcp(StreamConnection::DEFAULT_TCP_URI, [$optionName => $optionValue]);
50+
}
51+
52+
/**
53+
* @dataProvider \Tarantool\Client\Tests\Unit\OptionsProvider::provideConnectionArrayOptionsOfInvalidTypes
54+
*/
55+
public function testCreateUdsRejectsOptionOfInvalidType(string $optionName, $optionValue, string $expectedType) : void
56+
{
57+
$this->expectException(\TypeError::class);
58+
$this->expectExceptionMessageMatches("/must be of(?: the)? type $expectedType/");
59+
60+
StreamConnection::createUds('unix:///socket.sock', [$optionName => $optionValue]);
61+
}
62+
}

0 commit comments

Comments
 (0)