mirror of
https://github.com/amphp/parallel.git
synced 2025-04-21 02:01:53 +02:00
Add DelegatingWorkerPool
Some checks are pending
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, on Windows, windows-latest, nts, 8.3) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, on macOS, macos-latest, nts, 8.3) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, ubuntu-latest, nts, 8.1) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, ubuntu-latest, nts, 8.2) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, ubuntu-latest, nts, 8.3) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (none, true, with ext-parallel, ubuntu-latest, parallel, ts, 8.2, none, none) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (none, true, with ext-parallel, ubuntu-latest, parallel, ts, 8.3, none, none) (push) Waiting to run
Some checks are pending
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, on Windows, windows-latest, nts, 8.3) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, on macOS, macos-latest, nts, 8.3) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, ubuntu-latest, nts, 8.1) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, ubuntu-latest, nts, 8.2) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (false, ubuntu-latest, nts, 8.3) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (none, true, with ext-parallel, ubuntu-latest, parallel, ts, 8.2, none, none) (push) Waiting to run
Continuous Integration / PHP ${{ matrix.php-version }} ${{ matrix.job-description }} (none, true, with ext-parallel, ubuntu-latest, parallel, ts, 8.3, none, none) (push) Waiting to run
This commit is contained in:
parent
b4f39786c8
commit
7f8ca5472d
128
src/Worker/DelegatingWorkerPool.php
Normal file
128
src/Worker/DelegatingWorkerPool.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Amp\Parallel\Worker;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\DeferredFuture;
|
||||
use Amp\Parallel\Worker\Internal\PooledWorker;
|
||||
|
||||
final class DelegatingWorkerPool implements WorkerPool
|
||||
{
|
||||
/** @var array<int, Worker> */
|
||||
private array $workerStorage = [];
|
||||
|
||||
private int $pendingWorkerCount = 0;
|
||||
|
||||
/** @var \SplQueue<DeferredFuture<Worker|null>> */
|
||||
private readonly \SplQueue $waiting;
|
||||
|
||||
/**
|
||||
* @param int $limit Maximum number of workers to use from the delegate pool.
|
||||
*/
|
||||
public function __construct(private readonly WorkerPool $pool, private readonly int $limit)
|
||||
{
|
||||
$this->waiting = new \SplQueue();
|
||||
}
|
||||
|
||||
public function isRunning(): bool
|
||||
{
|
||||
return $this->pool->isRunning();
|
||||
}
|
||||
|
||||
public function isIdle(): bool
|
||||
{
|
||||
return $this->pool->isIdle();
|
||||
}
|
||||
|
||||
public function submit(Task $task, ?Cancellation $cancellation = null): Execution
|
||||
{
|
||||
$worker = $this->selectWorker();
|
||||
|
||||
$execution = $worker->submit($task, $cancellation);
|
||||
|
||||
$execution->getFuture()->finally(fn () => $this->push($worker))->ignore();
|
||||
|
||||
return $execution;
|
||||
}
|
||||
|
||||
private function selectWorker(): Worker
|
||||
{
|
||||
do {
|
||||
if (\count($this->workerStorage) + $this->pendingWorkerCount < $this->limit) {
|
||||
$this->pendingWorkerCount++;
|
||||
|
||||
try {
|
||||
$worker = $this->pool->getWorker();
|
||||
} finally {
|
||||
$this->pendingWorkerCount--;
|
||||
}
|
||||
} else {
|
||||
/** @var DeferredFuture<Worker|null> $waiting */
|
||||
$waiting = new DeferredFuture();
|
||||
$this->waiting->push($waiting);
|
||||
|
||||
$worker = $waiting->getFuture()->await();
|
||||
if (!$worker?->isRunning()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->workerStorage[\spl_object_id($worker)] = $worker;
|
||||
|
||||
return $worker;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private function push(Worker $worker): void
|
||||
{
|
||||
unset($this->workerStorage[\spl_object_id($worker)]);
|
||||
|
||||
if (!$this->waiting->isEmpty()) {
|
||||
$deferredFuture = $this->waiting->dequeue();
|
||||
$deferredFuture->complete($worker->isRunning() ? $worker : null);
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
{
|
||||
if (!$this->waiting->isEmpty()) {
|
||||
$exception = new WorkerException('The pool was shutdown before a worker could be obtained');
|
||||
$this->clearWaiting($exception);
|
||||
}
|
||||
|
||||
$this->pool->shutdown();
|
||||
}
|
||||
|
||||
public function kill(): void
|
||||
{
|
||||
if (!$this->waiting->isEmpty()) {
|
||||
$exception = new WorkerException('The pool was killed before a worker could be obtained');
|
||||
$this->clearWaiting($exception);
|
||||
}
|
||||
|
||||
$this->pool->kill();
|
||||
}
|
||||
|
||||
private function clearWaiting(\Throwable $exception): void
|
||||
{
|
||||
while (!$this->waiting->isEmpty()) {
|
||||
$deferredFuture = $this->waiting->dequeue();
|
||||
$deferredFuture->error($exception);
|
||||
}
|
||||
}
|
||||
|
||||
public function getWorker(): Worker
|
||||
{
|
||||
return new PooledWorker($this->selectWorker(), $this->push(...));
|
||||
}
|
||||
|
||||
public function getWorkerCount(): int
|
||||
{
|
||||
return \min($this->limit, $this->pool->getWorkerCount());
|
||||
}
|
||||
|
||||
public function getIdleWorkerCount(): int
|
||||
{
|
||||
return \min($this->limit, $this->pool->getIdleWorkerCount());
|
||||
}
|
||||
}
|
@ -5,8 +5,6 @@ namespace Amp\Parallel\Test\Worker;
|
||||
use Amp\Future;
|
||||
use Amp\Parallel\Context\StatusError;
|
||||
use Amp\Parallel\Test\Worker\Fixtures\TestTask;
|
||||
use Amp\Parallel\Worker\ContextWorkerFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerPool;
|
||||
use Amp\Parallel\Worker\Execution;
|
||||
use Amp\Parallel\Worker\Task;
|
||||
use Amp\Parallel\Worker\Worker;
|
||||
@ -180,15 +178,8 @@ abstract class AbstractPoolTest extends AbstractWorkerTest
|
||||
return $this->createPool(autoloadPath: $autoloadPath);
|
||||
}
|
||||
|
||||
protected function createPool(
|
||||
abstract protected function createPool(
|
||||
int $max = WorkerPool::DEFAULT_WORKER_LIMIT,
|
||||
?string $autoloadPath = null
|
||||
): WorkerPool {
|
||||
$factory = new ContextWorkerFactory(
|
||||
bootstrapPath: $autoloadPath,
|
||||
contextFactory: $this->createContextFactory(),
|
||||
);
|
||||
|
||||
return new ContextWorkerPool($max, $factory);
|
||||
}
|
||||
?string $autoloadPath = null,
|
||||
): WorkerPool;
|
||||
}
|
||||
|
@ -6,10 +6,8 @@ use Amp\Cancellation;
|
||||
use Amp\CancelledException;
|
||||
use Amp\DeferredCancellation;
|
||||
use Amp\Future;
|
||||
use Amp\Parallel\Context\ContextFactory;
|
||||
use Amp\Parallel\Context\StatusError;
|
||||
use Amp\Parallel\Test\Worker\Fixtures\CommunicatingTask;
|
||||
use Amp\Parallel\Worker\ContextWorkerFactory;
|
||||
use Amp\Parallel\Worker\Task;
|
||||
use Amp\Parallel\Worker\TaskCancelledException;
|
||||
use Amp\Parallel\Worker\TaskFailureError;
|
||||
@ -382,15 +380,5 @@ abstract class AbstractWorkerTest extends AsyncTestCase
|
||||
self::assertSame('out', $execution->await($cancellation));
|
||||
}
|
||||
|
||||
protected function createWorker(?string $autoloadPath = null): Worker
|
||||
{
|
||||
$factory = new ContextWorkerFactory(
|
||||
bootstrapPath: $autoloadPath,
|
||||
contextFactory: $this->createContextFactory(),
|
||||
);
|
||||
|
||||
return $factory->create();
|
||||
}
|
||||
|
||||
abstract protected function createContextFactory(): ContextFactory;
|
||||
abstract protected function createWorker(?string $autoloadPath = null): Worker;
|
||||
}
|
||||
|
24
test/Worker/DelegatingWorkerPoolTest.php
Normal file
24
test/Worker/DelegatingWorkerPoolTest.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Amp\Parallel\Test\Worker;
|
||||
|
||||
use Amp\Parallel\Context\ProcessContextFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerPool;
|
||||
use Amp\Parallel\Worker\DelegatingWorkerPool;
|
||||
use Amp\Parallel\Worker\WorkerPool;
|
||||
|
||||
class DelegatingWorkerPoolTest extends AbstractPoolTest
|
||||
{
|
||||
protected function createPool(
|
||||
int $max = WorkerPool::DEFAULT_WORKER_LIMIT,
|
||||
?string $autoloadPath = null,
|
||||
): WorkerPool {
|
||||
$pool = new ContextWorkerPool(
|
||||
limit: $max * 2,
|
||||
factory: new ContextWorkerFactory($autoloadPath, contextFactory: new ProcessContextFactory()),
|
||||
);
|
||||
|
||||
return new DelegatingWorkerPool($pool, $max);
|
||||
}
|
||||
}
|
@ -2,20 +2,22 @@
|
||||
|
||||
namespace Amp\Parallel\Test\Worker;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Parallel\Context\Context;
|
||||
use Amp\Parallel\Context\ContextFactory;
|
||||
use Amp\Parallel\Context\ProcessContextFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerPool;
|
||||
use Amp\Parallel\Worker\WorkerPool;
|
||||
|
||||
class ProcessPoolTest extends AbstractPoolTest
|
||||
{
|
||||
public function createContextFactory(): ContextFactory
|
||||
{
|
||||
return new class implements ContextFactory {
|
||||
public function start(array|string $script, ?Cancellation $cancellation = null): Context
|
||||
{
|
||||
return (new ProcessContextFactory())->start($script, cancellation: $cancellation);
|
||||
}
|
||||
};
|
||||
protected function createPool(
|
||||
int $max = WorkerPool::DEFAULT_WORKER_LIMIT,
|
||||
?string $autoloadPath = null,
|
||||
): WorkerPool {
|
||||
$factory = new ContextWorkerFactory(
|
||||
bootstrapPath: $autoloadPath,
|
||||
contextFactory: new ProcessContextFactory(),
|
||||
);
|
||||
|
||||
return new ContextWorkerPool($max, $factory);
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,19 @@
|
||||
|
||||
namespace Amp\Parallel\Test\Worker;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Parallel\Context\Context;
|
||||
use Amp\Parallel\Context\ContextFactory;
|
||||
use Amp\Parallel\Context\ProcessContextFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerFactory;
|
||||
use Amp\Parallel\Worker\Worker;
|
||||
|
||||
class ProcessWorkerTest extends AbstractWorkerTest
|
||||
{
|
||||
public function createContextFactory(): ContextFactory
|
||||
protected function createWorker(?string $autoloadPath = null): Worker
|
||||
{
|
||||
return new class implements ContextFactory {
|
||||
public function start(array|string $script, ?Cancellation $cancellation = null): Context
|
||||
{
|
||||
return (new ProcessContextFactory())->start($script, cancellation: $cancellation);
|
||||
}
|
||||
};
|
||||
$factory = new ContextWorkerFactory(
|
||||
bootstrapPath: $autoloadPath,
|
||||
contextFactory: new ProcessContextFactory(),
|
||||
);
|
||||
|
||||
return $factory->create();
|
||||
}
|
||||
}
|
||||
|
@ -2,25 +2,27 @@
|
||||
|
||||
namespace Amp\Parallel\Test\Worker;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Parallel\Context\Context;
|
||||
use Amp\Parallel\Context\ContextFactory;
|
||||
use Amp\Parallel\Context\ThreadContext;
|
||||
use Amp\Parallel\Context\ThreadContextFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerPool;
|
||||
use Amp\Parallel\Worker\WorkerPool;
|
||||
|
||||
class ThreadPoolTest extends AbstractPoolTest
|
||||
{
|
||||
public function createContextFactory(): ContextFactory
|
||||
{
|
||||
protected function createPool(
|
||||
int $max = WorkerPool::DEFAULT_WORKER_LIMIT,
|
||||
?string $autoloadPath = null,
|
||||
): WorkerPool {
|
||||
if (!ThreadContext::isSupported()) {
|
||||
$this->markTestSkipped('ext-parallel required');
|
||||
}
|
||||
|
||||
return new class implements ContextFactory {
|
||||
public function start(array|string $script, ?Cancellation $cancellation = null): Context
|
||||
{
|
||||
return (new ThreadContextFactory())->start($script, cancellation: $cancellation);
|
||||
}
|
||||
};
|
||||
$factory = new ContextWorkerFactory(
|
||||
bootstrapPath: $autoloadPath,
|
||||
contextFactory: new ThreadContextFactory(),
|
||||
);
|
||||
|
||||
return new ContextWorkerPool($max, $factory);
|
||||
}
|
||||
}
|
||||
|
@ -2,25 +2,24 @@
|
||||
|
||||
namespace Amp\Parallel\Test\Worker;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Parallel\Context\Context;
|
||||
use Amp\Parallel\Context\ContextFactory;
|
||||
use Amp\Parallel\Context\ThreadContext;
|
||||
use Amp\Parallel\Context\ThreadContextFactory;
|
||||
use Amp\Parallel\Worker\ContextWorkerFactory;
|
||||
use Amp\Parallel\Worker\Worker;
|
||||
|
||||
class ThreadWorkerTest extends AbstractWorkerTest
|
||||
{
|
||||
public function createContextFactory(): ContextFactory
|
||||
protected function createWorker(?string $autoloadPath = null): Worker
|
||||
{
|
||||
if (!ThreadContext::isSupported()) {
|
||||
$this->markTestSkipped('ext-parallel required');
|
||||
}
|
||||
|
||||
return new class implements ContextFactory {
|
||||
public function start(array|string $script, ?Cancellation $cancellation = null): Context
|
||||
{
|
||||
return (new ThreadContextFactory())->start($script, cancellation: $cancellation);
|
||||
}
|
||||
};
|
||||
$factory = new ContextWorkerFactory(
|
||||
bootstrapPath: $autoloadPath,
|
||||
contextFactory: new ThreadContextFactory(),
|
||||
);
|
||||
|
||||
return $factory->create();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user