diff --git a/docs/configuration.md b/docs/configuration.md index 1f71bf1..d112f85 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -26,6 +26,7 @@ sineflow_elasticsearch: profiling_backtrace: false logging: true bulk_batch_size: 1000 + request_timeout: 300 indices: _base: @@ -81,6 +82,7 @@ And here is the breakdown: * `profiling` *(default: true)*: Enable or disable profiling. The default setup makes use of Elasticsearch client's profiling to gather information for the Symfony profiler toolbar, which is extremely useful in development. * `logging` *(default: true)*: When enabled, the bundle uses Symfony's 'logger' service to log Elasticsearch events in the 'sfes' channel. Using symfony/monolog-bundle, the logging can be easily controlled. For example the 'sfes' channel can be redirected to a rotating file log. * `bulk_batch_size` *(default: 1000)*: This is currently used only when using the **rebuildIndex()** method of the index manager. + * `request_timeout` *(default: 300)*: The timeout for HTTP requests to Elasticsearch in seconds. This applies to all synchronous operations including reindex, bulk operations, and long-running queries. Increase this value if you have long-running operations that exceed the default timeout. * `indices`: Here you define the Elasticsearch indexes you have. The key here is the name of the index manager, which determines how it will be accessible in the application. In the example above, we have an index manager named **products**, which would be accessible as **$container->get('sfes.index.products')**. It is important to note here the use of **'_'** in front of the index manager name. When defined like that, this will be an abstract definition, i.e. no manager will actually be created from that definition. This is very useful when you have common setting for several indices, as you can define a template for them all and not have to duplicate stuff. diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index dc07502..2f202a0 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -137,6 +137,10 @@ private function getConnectionsNode(): NodeDefinition ->defaultValue(1000) ->info('The number of requests to send at once, when doing bulk operations') ->end() + ->scalarNode('request_timeout') + ->defaultValue(300) + ->info('Timeout for HTTP requests to Elasticsearch in seconds (default: 300)') + ->end() ->end() ->end(); diff --git a/src/Finder/Finder.php b/src/Finder/Finder.php index a5b724e..c22562c 100644 --- a/src/Finder/Finder.php +++ b/src/Finder/Finder.php @@ -8,6 +8,7 @@ use Psr\Cache\InvalidArgumentException; use Sineflow\ElasticsearchBundle\Document\DocumentInterface; use Sineflow\ElasticsearchBundle\DTO\IndicesToDocumentClasses; +use Sineflow\ElasticsearchBundle\Exception\InvalidIndexManagerException; use Sineflow\ElasticsearchBundle\Finder\Adapter\KnpPaginatorAdapter; use Sineflow\ElasticsearchBundle\Finder\Adapter\ScrollAdapter; use Sineflow\ElasticsearchBundle\Manager\ConnectionManager; @@ -44,7 +45,10 @@ public function __construct( * * @param string $documentClass FQN or short notation (i.e. App:Product) * + * @throws ClientResponseException * @throws InvalidArgumentException + * @throws InvalidIndexManagerException + * @throws ServerResponseException */ public function get(string $documentClass, string|int $id, int $resultType = self::RESULTS_OBJECT): DocumentInterface|array|null { @@ -81,9 +85,10 @@ public function get(string $documentClass, string|int $id, int $resultType = sel * @param array $additionalRequestParams Additional params to pass to the ES client's search() method * @param int|null $totalHits (out param) The total hits of the query response * - * @throws NoNodeAvailableException if all the hosts are offline - * @throws ClientResponseException if the status code of response is 4xx - * @throws ServerResponseException if the status code of response is 5xx + * @throws NoNodeAvailableException if all the hosts are offline + * @throws ClientResponseException if the status code of response is 4xx + * @throws ServerResponseException if the status code of response is 5xx + * @throws InvalidIndexManagerException */ public function find(array $documentClasses, array $searchBody, int $resultsType = self::RESULTS_OBJECT, array $additionalRequestParams = [], ?int &$totalHits = null): array|KnpPaginatorAdapter|ScrollAdapter|DocumentIterator { @@ -134,9 +139,10 @@ public function find(array $documentClasses, array $searchBody, int $resultsType * @param int $resultsType Bitmask value determining how the results are returned * @param int|null $totalHits (out param) The total hits of the query response * - * @throws NoNodeAvailableException if all the hosts are offline - * @throws ClientResponseException if the status code of response is 4xx - * @throws ServerResponseException if the status code of response is 5xx + * @throws NoNodeAvailableException if all the hosts are offline + * @throws ClientResponseException if the status code of response is 4xx + * @throws ServerResponseException if the status code of response is 5xx + * @throws InvalidIndexManagerException */ public function scroll(array $documentClasses, string &$scrollId, string $scrollTime = self::SCROLL_TIME, int $resultsType = self::RESULTS_OBJECT, ?int &$totalHits = null): array|bool|DocumentIterator { @@ -216,6 +222,10 @@ public function getTargetIndices(array $documentClasses): array * @param array $raw The raw results as received from Elasticsearch * @param int $resultsType Bitmask value determining how the results are returned * @param string[] $documentClasses The ES entity classes that may be returned from the search + * + * @throws ClientResponseException + * @throws InvalidIndexManagerException + * @throws ServerResponseException */ public function parseResult(array $raw, int $resultsType, ?array $documentClasses = null): array|DocumentIterator { @@ -246,8 +256,14 @@ public function parseResult(array $raw, int $resultsType, ?array $documentClasse * Returns a mapping of live indices to the document classes that represent them * * @param string[] $documentClasses + * + * @throws ClientResponseException + * @throws ServerResponseException + * @throws InvalidIndexManagerException + * + * @internal */ - private function getIndicesToDocumentClasses(array $documentClasses): IndicesToDocumentClasses + public function getIndicesToDocumentClasses(array $documentClasses): IndicesToDocumentClasses { $indicesToDocumentClasses = new IndicesToDocumentClasses(); diff --git a/src/Manager/ConnectionManager.php b/src/Manager/ConnectionManager.php index ea0a410..cd10d15 100644 --- a/src/Manager/ConnectionManager.php +++ b/src/Manager/ConnectionManager.php @@ -107,6 +107,15 @@ public function getClient(): Client $clientBuilder->setSSLCert($this->connectionSettings['ssl_cert']); } + // Configure HTTP client timeout + $httpClientOptions = []; + if (isset($this->connectionSettings['request_timeout'])) { + $httpClientOptions['timeout'] = $this->connectionSettings['request_timeout']; + } + if (!empty($httpClientOptions)) { + $clientBuilder->setHttpClientOptions($httpClientOptions); + } + if ($this->logger) { $clientBuilder->setLogger($this->logger); } diff --git a/tests/Unit/DependencyInjection/ElasticsearchExtensionTest.php b/tests/Unit/DependencyInjection/ElasticsearchExtensionTest.php index b4fa423..43b79c0 100644 --- a/tests/Unit/DependencyInjection/ElasticsearchExtensionTest.php +++ b/tests/Unit/DependencyInjection/ElasticsearchExtensionTest.php @@ -118,6 +118,7 @@ public static function getData() 'ssl_ca' => null, 'ssl_key' => null, 'ssl_cert' => null, + 'request_timeout' => 300, ], ]; diff --git a/tests/Unit/ElasticsearchBundleTest.php b/tests/Unit/ElasticsearchBundleTest.php index 3f81398..e096a6d 100644 --- a/tests/Unit/ElasticsearchBundleTest.php +++ b/tests/Unit/ElasticsearchBundleTest.php @@ -40,7 +40,7 @@ public function testPassesRegistered(): void $finder = new Finder(); $finder->files()->in(__DIR__.'/../../src/DependencyInjection/Compiler/'); - /** @var $file SplFileInfo */ + /** @var SplFileInfo $file */ foreach ($finder as $file) { $passName = \str_replace('.php', '', $file->getFilename()); // Check whether pass is not blacklisted and not added by bundle.