saloon icon indicating copy to clipboard operation
saloon copied to clipboard

Paginated Pool fails to send the pages correctly

Open MuathIbnHassan opened this issue 1 month ago • 1 comments

Using Pool with PagePaginated requests will send the same page for all concurrent requests, since $request is shallow cloned and $query is an ArrayStore object, it will be the same $query object for all concurrent requests if it was initialized before calling ->paginate()

this causes all requests sent together to have the same $query as well as other object properties

$connector = Connector::make();
$request = Request::make();

$request->query()->add('key', 'value'); // this will initialize the $query ArrayStore

$paginator = $connector->paginate($request); // this will clone $request internally

$actualPages = [];

$pool = $paginator
    ->setMaxPages(10)
    ->pool(
        concurrency: 5,
        responseHandler: function (Response $response) use (&$actualPages) {
            $actualPages[] = $response->json('meta.current_page');
        });

$promise = $pool->send();

$promise->wait();

$actualPages;

// expected: [1, 2, 3, 5, 6, 4, 7, 8, 9, 10];
// actual: [1, 6, 6, 6, 6, 6, 8, 7, 10, 10];

to avoid that we overrode the function applyPagination and changed it to:

    protected function applyPagination(Request $request): Request
    {
        if ($request->query()->get('page') === null) {
            (function () {
                $currentQuery = $this->query()->all();
                unset($this->query);
                $this->query()->merge($currentQuery);
            })->call($request);
        }

        return parent::applyPagination($request);
    }

$query, $headers, $config, and $delay are all objects, if any of them is initialized before cloning they will be shared between all requests, but i think only pagination needs the deep clone for query

MuathIbnHassan avatar Nov 19 '25 17:11 MuathIbnHassan

another solution is to add this to the Request class

    public function __clone(): void
    {
        if (isset($this->query)) {
            $this->query = clone $this->query;
        }

        if (isset($this->headers)) {
            $this->headers = clone $this->headers;
        }

        if (isset($this->config)) {
            $this->config = clone $this->config;
        }

        if (isset($this->middlewarePipeline)) {
            $this->middlewarePipeline = clone $this->middlewarePipeline;
        }

        if (isset($this->delay)) {
            $this->delay = clone $this->delay;
        }
    }

MuathIbnHassan avatar Nov 20 '25 14:11 MuathIbnHassan