Skip to content
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
30 changes: 14 additions & 16 deletions src/ManticoreSearch/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,7 @@ public function __construct(?string $url = null, ?string $authToken = null) {
}
$this->setServerUrl($url);
$this->setAuthToken($authToken);
$this->connectionPool = new ConnectionPool(
function () {
$client = new HttpClient($this->host, $this->port);
$client->set(['timeout' => -1]);
return $client;
}
);
$this->connectionPool = new ConnectionPool(fn() => $this->makeHttpClient());
$this->buddyVersion = Buddy::getVersion();
$this->clientMap = new Map;
}
Expand All @@ -98,16 +92,19 @@ function () {
* @return void
*/
public function __clone() {
$this->connectionPool = new ConnectionPool(
function () {
$client = new HttpClient($this->host, $this->port);
$client->set(['timeout' => -1]);
return $client;
}
);
$this->connectionPool = new ConnectionPool(fn() => $this->makeHttpClient());
$this->clientMap = new Map;
}

/**
* @return HttpClient
*/
protected function makeHttpClient(): HttpClient {
$client = new HttpClient($this->host, $this->port);
$client->set(['timeout' => -1]);
return $client;
}

/**
* Set server URL of Manticore searchd to send requests to
* @param string $url it supports http:// prefixed and not
Expand Down Expand Up @@ -368,14 +365,15 @@ protected function runAsyncRequest(string $path, string $request, array $headers
$client->setData($request);
$client->execute("/$path");
if ($client->errCode) {
$error = "Error while async request: {$client->errCode}: {$client->errMsg}";
$client->close();
$this->connectionPool->put($this->makeHttpClient());
/** @phpstan-ignore-next-line */
if ($client->errCode !== 104 || $try >= 3) {
$error = "Error while async request: {$client->errCode}: {$client->errMsg}";
throw new ManticoreSearchClientError($error);
}

Buddy::debug('Client: connection reset by peer, repeat: ' . (++$try));
$client->close();
goto request;
}
$result = $client->body;
Expand Down
76 changes: 76 additions & 0 deletions test/BuddyCore/Network/ManticoreSearch/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,80 @@ public function testResponseUrlSetOk(): void {
// $this->client->setServerUrl($url);
// }


public function testAsyncFailuresDoNotDrainConnectionPool(): void {
$executor = trim((string)shell_exec('command -v manticore-executor'));
if ($executor === '') {
$this->markTestSkipped('manticore-executor is required for the coroutine deadlock regression test');
}

$repoRoot = dirname(__DIR__, 4);
$autoloadCandidates = [
$repoRoot . '/vendor/autoload.php',
dirname($repoRoot, 2) . '/autoload.php',
];
$autoload = '';
foreach ($autoloadCandidates as $candidate) {
if (is_file($candidate)) {
$autoload = $candidate;
break;
}
}
$versionFile = $repoRoot . '/test/src/MOCK_APP_VERSION';
if ($autoload === '' || !is_file($versionFile)) {
$this->markTestSkipped('Required test bootstrap files are missing');
}

$scriptFile = tempnam(sys_get_temp_dir(), 'buddy-core-deadlock-');
if ($scriptFile === false) {
throw new \RuntimeException('Failed to create temporary script file');
}

$script = <<<'PHP'
<?php declare(strict_types=1);

require '__AUTOLOAD__';

use Manticoresearch\Buddy\Core\ManticoreSearch\Client;
use Manticoresearch\Buddy\Core\Tool\Buddy;
use Manticoresearch\Buddy\Core\Tool\ConfigManager;
use function Swoole\Coroutine\run;

Buddy::setVersionFile('__VERSION_FILE__');
ConfigManager::init();

run(static function (): void {
$client = new Client('http://127.0.0.1:9');
for ($i = 1; $i <= 100; $i++) {
try {
$client->sendRequest('SHOW STATUS');
echo "ok {$i}\n";
} catch (Throwable $e) {
echo "err {$i}: " . $e->getMessage() . "\n";
}
}
echo "completed\n";
});
PHP;
$script = str_replace(
['__AUTOLOAD__', '__VERSION_FILE__'],
[addslashes($autoload), addslashes($versionFile)],
$script
);
file_put_contents($scriptFile, $script);

try {
$output = [];
$returnVar = 0;
exec($executor . ' ' . escapeshellarg($scriptFile) . ' 2>&1', $output, $returnVar);
$stdout = implode(PHP_EOL, $output);
$this->assertStringContainsString('completed', $stdout);
$this->assertStringNotContainsString('[FATAL ERROR]', $stdout);
$this->assertStringNotContainsString('all coroutines (count: 1) are asleep - deadlock!', $stdout);
$this->assertStringNotContainsString('Channel::~Channel()', $stdout);
} finally {
@unlink($scriptFile);
}
}

}
Loading