Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support suggest.buildAll and add a plugin to execute queries without waiting for Solr's response #1119

Merged
merged 34 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e5b9251
support suggest.buildAll
mkalkbrenner Jan 3, 2024
9df8626
formatted code
mkalkbrenner Jan 3, 2024
179ffd9
register noresponserequest plugin
mkalkbrenner Jan 3, 2024
13b4ab9
fixed plugin registration in test
mkalkbrenner Jan 3, 2024
9eb457c
handle timeout exception
mkalkbrenner Jan 3, 2024
7f9d814
simplified event handling
mkalkbrenner Jan 3, 2024
ba05bfe
fixed HTTP fake headers in tests
mkalkbrenner Jan 3, 2024
8528cee
fixed test
mkalkbrenner Jan 3, 2024
c61a6bf
renamed plugin
mkalkbrenner Jan 3, 2024
ea79511
added integration test
mkalkbrenner Jan 3, 2024
5f2c6bf
added assertions
mkalkbrenner Jan 3, 2024
02391c3
increase test coverage
mkalkbrenner Jan 3, 2024
608e6b0
formatted code
mkalkbrenner Jan 3, 2024
7cbcd1e
Merge branch 'master' into buildAll_noresponse
mkalkbrenner Jan 5, 2024
138fa6a
added comments and improved test
mkalkbrenner Jan 5, 2024
3941925
minimal documentation
mkalkbrenner Jan 5, 2024
73a2a48
Merge branch 'master' into buildAll_noresponse
mkalkbrenner Jan 5, 2024
76dde2e
fixed code format
mkalkbrenner Jan 5, 2024
daca358
Take connection timeout into account
thomascorthals Jan 5, 2024
639bd2d
Skip example for lack of a default suggester
thomascorthals Jan 5, 2024
3b1cdd2
respect connection timeout
mkalkbrenner Jan 5, 2024
a1f39f8
throw any exception that is not related to timeouts
mkalkbrenner Jan 5, 2024
7f75472
Doc fixes
thomascorthals Jan 5, 2024
40dc27e
Merge branch 'buildAll_noresponse' of github.com:solariumphp/solarium…
mkalkbrenner Jan 5, 2024
2d547c0
Merge branch 'buildAll_noresponse' of github.com:solariumphp/solarium…
mkalkbrenner Jan 5, 2024
31b91b9
fixed coding style
mkalkbrenner Jan 5, 2024
bdd65b7
detect connection errors for non Curl adapters
mkalkbrenner Jan 5, 2024
0bd967c
get microtime as float
mkalkbrenner Jan 5, 2024
0ea6e1f
et the adapter before throwing an exception
mkalkbrenner Jan 5, 2024
7c6a2fa
coding style
mkalkbrenner Jan 5, 2024
ea5a71b
Fix unit test
thomascorthals Jan 5, 2024
4a5db99
fixed tests
mkalkbrenner Jan 5, 2024
207fa84
Merge branch 'buildAll_noresponse' of github.com:solariumphp/solarium…
mkalkbrenner Jan 5, 2024
058006a
Additional test coverage
thomascorthals Jan 5, 2024
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Option `buildAll` for Suggesters
- NoWaitForResponseRequest Plugin

### Fixed
- PHP 8.2 deprecations for Solarium\QueryType\Server\Collections results

Expand Down
30 changes: 30 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,36 @@ htmlFooter();

```

NoWaitForResponseRequest plugin
-------------------------------

Long-running requests like suggest.buildAll might exceed timeouts. This plugin "tries" to convert the request in a kind of fire-and-forget and doesn't wait for Solr's response. Most reliable if the [cURL client adapter](client-and-adapters.md#curl-adapter) is used.

```php
<?php

require_once __DIR__.'/init.php';
htmlHeader();

// create a client instance
$client = new Solarium\Client($adapter, $eventDispatcher, $config);

// get a suggester query instance and add setting to build all suggesters
$suggester = $client->createSuggester();
$suggester->setBuildAll(true);

// don't wait until all suggesters have been built
$plugin = $client->getPlugin('nowaitforresponserequest');

// this executes the query without waiting for the response
$client->suggester($suggester);

// don't forget to remove the plugin again if you do need the response from further requests
$client->removePlugin($plugin);

htmlFooter();
```

ParallelExecution plugin
------------------------

Expand Down
22 changes: 22 additions & 0 deletions examples/7.9-plugin-nowaitforresponserequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

require_once __DIR__.'/init.php';
htmlHeader();

// create a client instance
$client = new Solarium\Client($adapter, $eventDispatcher, $config);

// get a suggester query instance and add setting to build all suggesters
$suggester = $client->createSuggester();
$suggester->setBuildAll(true);

// don't wait until all suggesters have been built
$plugin = $client->getPlugin('nowaitforresponserequest');

// this executes the query without waiting for the response
$client->suggester($suggester);

// don't forget to remove the plugin again if you do need the response from further requests
$client->removePlugin($plugin);

htmlFooter();
1 change: 1 addition & 0 deletions examples/execute_all.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
'7.5.3.2-plugin-bufferedupdate-lite-benchmarks-xml.php', // takes too long for a workflow, can be run manually
'7.5.3.3-plugin-bufferedupdate-benchmarks-json.php', // takes too long for a workflow, can be run manually
'7.5.3.4-plugin-bufferedupdate-lite-benchmarks-json.php', // takes too long for a workflow, can be run manually
'7.9-plugin-nowaitforresponserequest.php', // there is no default suggester included with techproducts
];

// examples that can't be run against this Solr version
Expand Down
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ <h2>Examples</h2>
<li><a href="7.7.1-plugin-minimumscorefilter-grouping.php">7.7.1 Minimum score filter for select queries using grouping</a></li>
</ul>
<li><a href="7.8-plugin-postbigextractrequest.php">7.8 Post Big Extract Requests</a></li>
<li><a href="7.9-plugin-nowaitforresponserequest.php">7.9 No Wait For Response Request</a></li>
</ul>

</ul>
Expand Down
22 changes: 22 additions & 0 deletions src/Component/ComponentTraits/SuggesterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,26 @@ public function getReload(): ?bool
{
return $this->getOption('reload');
}

/**
* Set buildAll option.
*
* @param bool $buildAll
*
* @return SuggesterInterface Provides fluent interface
*/
public function setBuildAll(bool $buildAll): SuggesterInterface
{
return $this->setOption('buildAll', $buildAll);
}

/**
* Get buildAll option.
*
* @return bool|null
*/
public function getBuildAll(): ?bool
{
return $this->getOption('buildAll');
}
}
16 changes: 16 additions & 0 deletions src/Component/SuggesterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,20 @@ public function setReload(bool $reload): self;
* @return bool|null
*/
public function getReload(): ?bool;

/**
* Set buildAll option.
*
* @param bool $buildAll
*
* @return self Provides fluent interface
*/
public function setBuildAll(bool $buildAll): self;

/**
* Get buildAll option.
*
* @return bool|null
*/
public function getBuildAll(): ?bool;
}
10 changes: 7 additions & 3 deletions src/Core/Client/Adapter/Curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ public function execute(Request $request, Endpoint $endpoint): Response
public function getResponse(\CurlHandle $handle, $httpResponse): Response
{
if (CURLE_OK !== curl_errno($handle)) {
throw new HttpException(sprintf('HTTP request failed, %s', curl_error($handle)));
$errno = curl_errno($handle);
$error = curl_error($handle);
curl_close($handle);
throw new HttpException(sprintf('HTTP request failed, %s', $error), $errno);
}

$httpCode = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
Expand Down Expand Up @@ -85,7 +88,7 @@ public function createHandle(Request $request, Endpoint $endpoint): \CurlHandle

$handler = curl_init();
curl_setopt($handler, CURLOPT_URL, $uri);
curl_setopt($handler, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handler, CURLOPT_RETURNTRANSFER, $options['return_transfer']);
if (!(\function_exists('ini_get') && ini_get('open_basedir'))) {
curl_setopt($handler, CURLOPT_FOLLOWLOCATION, true);
}
Expand Down Expand Up @@ -211,10 +214,11 @@ protected function init()
*/
protected function createOptions(Request $request, Endpoint $endpoint): array
{
$options = [
$options = $this->options + [
'timeout' => $this->timeout,
'connection_timeout' => $this->connectionTimeout ?? $this->timeout,
'proxy' => $this->proxy,
'return_transfer' => true,
];
foreach ($request->getHeaders() as $headerLine) {
list($header, $value) = explode(':', $headerLine);
Expand Down
7 changes: 7 additions & 0 deletions src/Core/Client/Adapter/TimeoutAwareInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ interface TimeoutAwareInterface
*/
public const DEFAULT_TIMEOUT = 5;

/**
* Fast timeout that should be used if the client should not wait for the result.
*
* @see \Solarium\Plugin\NoWaitForResponseRequest
*/
public const FAST_TIMEOUT = 1;

/**
* @param int $timeoutInSeconds
*
Expand Down
2 changes: 2 additions & 0 deletions src/Core/Client/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use Solarium\Plugin\CustomizeRequest\CustomizeRequest;
use Solarium\Plugin\Loadbalancer\Loadbalancer;
use Solarium\Plugin\MinimumScoreFilter\MinimumScoreFilter;
use Solarium\Plugin\NoWaitForResponseRequest;
use Solarium\Plugin\ParallelExecution\ParallelExecution;
use Solarium\Plugin\PostBigExtractRequest;
use Solarium\Plugin\PostBigRequest;
Expand Down Expand Up @@ -243,6 +244,7 @@ class Client extends Configurable implements ClientInterface
*/
protected $pluginTypes = [
'loadbalancer' => Loadbalancer::class,
'nowaitforresponserequest' => NoWaitForResponseRequest::class,
'postbigrequest' => PostBigRequest::class,
'postbigextractrequest' => PostBigExtractRequest::class,
'customizerequest' => CustomizeRequest::class,
Expand Down
145 changes: 145 additions & 0 deletions src/Plugin/NoWaitForResponseRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

/*
* This file is part of the Solarium package.
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code.
*/

namespace Solarium\Plugin;

use Solarium\Core\Client\Adapter\ConnectionTimeoutAwareInterface;
use Solarium\Core\Client\Adapter\Curl;
use Solarium\Core\Client\Adapter\TimeoutAwareInterface;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Event\Events;
use Solarium\Core\Event\PreExecuteRequest;
use Solarium\Core\Plugin\AbstractPlugin;
use Solarium\Exception\HttpException;

/**
* NoWaitForResponseRequest plugin.
*
* Long-running requests like suggest.buildAll might exceed timeouts.
* This plugin "tries" to convert the request in a kind of fire-and-forget.
* Most reliable if using the Curl adapter.
*/
class NoWaitForResponseRequest extends AbstractPlugin
{
/**
* Event hook to adjust client settings just before query execution.
*
* @param object $event
*
* @return self Provides fluent interface
*
* @throws \Solarium\Exception\HttpException
*/
public function preExecuteRequest($event): self
{
// We need to accept event proxies or decorators.
/** @var PreExecuteRequest $event */
$request = $event->getRequest();
$queryString = $request->getQueryString();

if (Request::METHOD_GET === $request->getMethod()) {
// GET requests usually expect a result. Since the purpose of this
// plugin is to trigger a long-running command and to not wait for
// its result, POST is the correct method.
// Depending on the HTTP configuration, GET requests could be
// cached. If this plugin is used, someone usually wants to build a
// dictionary or suggester and caching has to be avoided. Even if
// Solr accepts GET requests for these tasks, POST is the correct
// method.
$charset = $request->getParam('ie') ?? 'utf-8';
$request->setMethod(Request::METHOD_POST);
thomascorthals marked this conversation as resolved.
Show resolved Hide resolved
$request->setContentType(Request::CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED, ['charset' => $charset]);
$request->setRawData($queryString);
$request->clearParams();
}

$timeout = TimeoutAwareInterface::DEFAULT_TIMEOUT;
if ($this->client->getAdapter() instanceof TimeoutAwareInterface) {
$timeout = $this->client->getAdapter()->getTimeout();
if (($this->client->getAdapter() instanceof ConnectionTimeoutAwareInterface) && ($this->client->getAdapter()->getConnectionTimeout() > 0)) {
$this->client->getAdapter()->setTimeout($this->client->getAdapter()->getConnectionTimeout() + TimeoutAwareInterface::FAST_TIMEOUT);
} else {
$this->client->getAdapter()->setTimeout(TimeoutAwareInterface::FAST_TIMEOUT);
}
}

if ($this->client->getAdapter() instanceof Curl) {
$this->client->getAdapter()->setOption('return_transfer', false);
}

$exception = null;
$microtime1 = microtime(true);
try {
$this->client->getAdapter()->execute($request, $event->getEndpoint());
} catch (HttpException $e) {

Check warning on line 81 in src/Plugin/NoWaitForResponseRequest.php

View check run for this annotation

Codecov / codecov/patch

src/Plugin/NoWaitForResponseRequest.php#L81

Added line #L81 was not covered by tests
// We expect to run into a timeout.
$microtime2 = microtime(true);

Check warning on line 83 in src/Plugin/NoWaitForResponseRequest.php

View check run for this annotation

Codecov / codecov/patch

src/Plugin/NoWaitForResponseRequest.php#L83

Added line #L83 was not covered by tests

if (($this->client->getAdapter() instanceof Curl) && (CURLE_OPERATION_TIMEDOUT != $e->getCode())) {

Check warning on line 85 in src/Plugin/NoWaitForResponseRequest.php

View check run for this annotation

Codecov / codecov/patch

src/Plugin/NoWaitForResponseRequest.php#L85

Added line #L85 was not covered by tests
// An unexpected exception occurred.
$exception = $e;

Check warning on line 87 in src/Plugin/NoWaitForResponseRequest.php

View check run for this annotation

Codecov / codecov/patch

src/Plugin/NoWaitForResponseRequest.php#L87

Added line #L87 was not covered by tests
} else {
$time_passed = $microtime2 - $microtime1;
if (($this->client->getAdapter() instanceof ConnectionTimeoutAwareInterface) && ($time_passed > $this->client->getAdapter()->getConnectionTimeout()) && ($time_passed < ($this->client->getAdapter()->getConnectionTimeout() + TimeoutAwareInterface::FAST_TIMEOUT))) {

Check warning on line 90 in src/Plugin/NoWaitForResponseRequest.php

View check run for this annotation

Codecov / codecov/patch

src/Plugin/NoWaitForResponseRequest.php#L89-L90

Added lines #L89 - L90 were not covered by tests
// A connection timeout occurred, so the POST request has not been sent.
$exception = $e;

Check warning on line 92 in src/Plugin/NoWaitForResponseRequest.php

View check run for this annotation

Codecov / codecov/patch

src/Plugin/NoWaitForResponseRequest.php#L92

Added line #L92 was not covered by tests
}
}
} catch (\Exception $exception) {

Check warning on line 95 in src/Plugin/NoWaitForResponseRequest.php

View check run for this annotation

Codecov / codecov/patch

src/Plugin/NoWaitForResponseRequest.php#L95

Added line #L95 was not covered by tests
// Throw this exception after resetting the adapter.
}

if ($this->client->getAdapter() instanceof TimeoutAwareInterface) {
// Restore the previous timeout.
$this->client->getAdapter()->setTimeout($timeout);
}

if ($this->client->getAdapter() instanceof Curl) {
$this->client->getAdapter()->setOption('return_transfer', true);
}

if ($exception) {
throw $exception;

Check warning on line 109 in src/Plugin/NoWaitForResponseRequest.php

View check run for this annotation

Codecov / codecov/patch

src/Plugin/NoWaitForResponseRequest.php#L109

Added line #L109 was not covered by tests
}

$response = new Response('', ['HTTP/1.0 200 OK']);
$event->setResponse($response);

return $this;
}

/**
* Plugin init function.
*
* Register event listeners.
*/
protected function initPluginType()
{
$dispatcher = $this->client->getEventDispatcher();
if (is_subclass_of($dispatcher, '\Symfony\Component\EventDispatcher\EventDispatcherInterface')) {
// NoWaitForResponseRequest has to act on PRE_EXECUTE_REQUEST before Loadbalancer (priority 0)
// and after PostBigRequest (priority 10). Set priority to 5.
$dispatcher->addListener(Events::PRE_EXECUTE_REQUEST, [$this, 'preExecuteRequest'], 5);
}
}

/**
* Plugin cleanup function.
*
* Unregister event listeners.
*/
public function deinitPlugin()
{
$dispatcher = $this->client->getEventDispatcher();
if (is_subclass_of($dispatcher, '\Symfony\Component\EventDispatcher\EventDispatcherInterface')) {
$dispatcher->removeListener(Events::PRE_EXECUTE_REQUEST, [$this, 'preExecuteRequest']);
}
}
}
1 change: 1 addition & 0 deletions src/QueryType/Suggester/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Query extends BaseQuery implements SuggesterInterface, QueryInterface
'omitheader' => true,
'build' => false,
'reload' => false,
'buildAll' => false,
];

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/Core/Client/Adapter/HttpTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function testExecute()
$mock->expects($this->once())
->method('getData')
->with($this->equalTo('http://127.0.0.1:8983/solr/'), $this->isType('resource'))
->willReturn([$data, ['HTTP 1.1 200 OK']]);
->willReturn([$data, ['HTTP/1.1 200 OK']]);

$mock->execute($request, $endpoint);
}
Expand Down
Loading
Loading