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
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^10.4",
"phpbench/phpbench": "^1.2"
"phpunit/phpunit": "^11.5",
"phpbench/phpbench": "^1.4"
},
"suggest": {
"ext-pcntl": "Required for fork workers",
Expand Down
1,034 changes: 531 additions & 503 deletions composer.lock

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions src/Communication/Promise/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class Promise
*/
public function then(Closure $callback): static
{
if ($this->failed) {
return $this;
}
if ($this->resolved) {
$callback($this->value, ...$this->getAdditionalResolveArguments());
return $this;
Expand All @@ -78,6 +81,9 @@ public function then(Closure $callback): static
*/
public function catch(Closure $callback): static
{
if ($this->resolved) {
return $this;
}
if ($this->failed) {
$callback($this->exception, ...$this->getAdditionalRejectArguments());
return $this;
Expand Down Expand Up @@ -110,6 +116,7 @@ public function resolve(mixed $value = null): static
foreach ($this->fibers as $fiber) {
$fiber->resume($value);
}
$this->clearAfterResolveOrReject();
return $this;
}

Expand Down Expand Up @@ -140,9 +147,23 @@ public function reject(Exception $exception): static
foreach ($this->fibers as $fiber) {
$fiber->throw($exception);
}
$this->clearAfterResolveOrReject();
return $this;
}

/**
* Clear all success and exception handlers
*
* Frees up memory and removes references to avoid potential circular references.
*
* @return void
*/
protected function clearAfterResolveOrReject(): void
{
$this->successHandlers = [];
$this->exceptionHandlers = [];
}

/**
* Check if the exception matches the first argument type hint of the given callback
*
Expand Down
9 changes: 9 additions & 0 deletions src/Communication/Promise/TaskPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ public function getTask(): TaskInterface
return $this->task;
}

/**
* @inheritDoc
*/
protected function clearAfterResolveOrReject(): void
{
parent::clearAfterResolveOrReject();
unset($this->task);
}

/**
* @inheritDoc
*/
Expand Down
2 changes: 2 additions & 0 deletions src/Runtime/Runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ protected function runTask(RunTaskRequest $request): ResponseInterface
}
$result = $fiber->getReturn();
} catch (Exception $exception) {
$this->currentTaskRequest = null;
return (new ExceptionResponse($request->getRequestId(), $exception))->loadFromTask($request->task);
}
$this->currentTaskRequest = null;
return (new TaskResponse($request->getRequestId(), $result))->loadFromTask($request->task);
}
}
10 changes: 10 additions & 0 deletions test/Integration/SyncWorkerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ protected function createTaskmaster(): void
$this->taskmaster->addWorker(new SyncWorker());
}

public function testChildDestructMultipleTasks(): void
{
// This test does not work with the sync worker yet
// because the child and parent process are the same

// It's probably not a huge issue, but could be looked at
// again in the future
$this->markTestSkipped();
}

public function testSyncTask(): void
{
$task = new SyncTask();
Expand Down
60 changes: 60 additions & 0 deletions test/Integration/WorkerTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Task\InstanceTaskFactory;
use Aternos\Taskmaster\Task\TaskInterface;
use Aternos\Taskmaster\Taskmaster;
use Aternos\Taskmaster\Test\Util\Task\AdditionTask;
use Aternos\Taskmaster\Test\Util\Task\CallbackTask;
use Aternos\Taskmaster\Test\Util\Task\ChildExceptionTask;
use Aternos\Taskmaster\Test\Util\Task\DestructChildTask;
use Aternos\Taskmaster\Test\Util\Task\DestructParentTask;
use Aternos\Taskmaster\Test\Util\Task\DestructRegistry;
use Aternos\Taskmaster\Test\Util\Task\EmptyTask;
use Aternos\Taskmaster\Test\Util\Task\LargeChildTask;
use Aternos\Taskmaster\Test\Util\Task\LargeParentTask;
use Aternos\Taskmaster\Test\Util\Task\LargeTask;
use Aternos\Taskmaster\Test\Util\Task\ParentExceptionTask;
use Aternos\Taskmaster\Test\Util\Task\SynchronizedFieldTask;
Expand Down Expand Up @@ -77,6 +83,30 @@ public function testRunLargeTask(): void
$this->assertEquals(1_000_000, strlen($task->getResult()));
}

public function testRunManyLargeParentTasks(): void
{
$count = 0;
$taskFactory = new InstanceTaskFactory(LargeParentTask::class, 2_000);
$this->taskmaster->addTaskFactory($taskFactory);
foreach($this->taskmaster->waitAndHandleTasks() as $task) {
$count++;
}
$this->taskmaster->stop();
$this->assertEquals(2_000, $count);
}

public function testRunManyLargeChildTasks(): void
{
$count = 0;
$taskFactory = new InstanceTaskFactory(LargeChildTask::class, 2_000);
$this->taskmaster->addTaskFactory($taskFactory);
foreach($this->taskmaster->waitAndHandleTasks() as $task) {
$count++;
}
$this->taskmaster->stop();
$this->assertEquals(2_000, $count);
}

public function testGetTaskResultFromPromise(): void
{
$task = new AdditionTask(1, 2);
Expand All @@ -98,6 +128,36 @@ public function testRunMultipleTasks(): void
}
}

public function testParentDestructMultipleTasks(): void
{
DestructRegistry::clear();
$this->assertEquals(0, DestructRegistry::count());
$taskFactory = new InstanceTaskFactory(DestructParentTask::class, 100);
$this->taskmaster->addTaskFactory($taskFactory);
do {
$tasks = $this->taskmaster->update();
$ids = array_map(fn($task) => spl_object_id($task), $tasks);
unset($tasks);
foreach ($ids as $id) {
$this->assertFalse(DestructRegistry::has($id));
}
} while ($this->taskmaster->isWorking());
$this->taskmaster->stop();
$this->assertEquals(0, DestructRegistry::count());
}

public function testChildDestructMultipleTasks(): void
{
DestructRegistry::clear();
$taskFactory = new InstanceTaskFactory(DestructChildTask::class, 100);
$this->taskmaster->addTaskFactory($taskFactory);
foreach ($this->taskmaster->waitAndHandleTasks() as $task) {
$this->assertEquals(0, $task->getResult());
unset($task);
}
$this->taskmaster->stop();
}

public function testGetMultipleTasksFromWaitGenerator(): void
{
$this->addTasks(new AdditionTask(1, 2), 10);
Expand Down
24 changes: 24 additions & 0 deletions test/Util/Task/DestructChildTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Aternos\Taskmaster\Test\Util\Task;

use Aternos\Taskmaster\Task\OnChild;
use Aternos\Taskmaster\Task\Task;

class DestructChildTask extends Task
{
/**
* @inheritDoc
*/
#[OnChild] public function run(): int
{
$countBefore = DestructRegistry::count();
DestructRegistry::register($this);
return $countBefore;
}

public function __destruct()
{
DestructRegistry::unregister($this);
}
}
26 changes: 26 additions & 0 deletions test/Util/Task/DestructParentTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Aternos\Taskmaster\Test\Util\Task;

use Aternos\Taskmaster\Task\OnChild;
use Aternos\Taskmaster\Task\Task;

class DestructParentTask extends Task
{
public function __construct()
{
DestructRegistry::register($this);
}

/**
* @inheritDoc
*/
#[OnChild] public function run(): void
{
}

public function __destruct()
{
DestructRegistry::unregister($this);
}
}
59 changes: 59 additions & 0 deletions test/Util/Task/DestructRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Aternos\Taskmaster\Test\Util\Task;

class DestructRegistry
{
protected static array $objectIds = [];

/**
* @param object $object
* @return void
*/
public static function register(object $object): void
{
static::$objectIds[] = spl_object_id($object);
}

/**
* @param object $object
* @return void
*/
public static function unregister(object $object): void
{
$objectId = spl_object_id($object);
if (($key = array_search($objectId, static::$objectIds)) !== false) {
unset(static::$objectIds[$key]);
}
}

public static function clear(): void
{
static::$objectIds = [];
}

/**
* @return bool
*/
public static function empty(): bool
{
return empty(static::$objectIds);
}

/**
* @return int
*/
public static function count(): int
{
return count(static::$objectIds);
}

/**
* @param int $id
* @return bool
*/
public static function has(int $id): bool
{
return in_array($id, static::$objectIds);
}
}
3 changes: 2 additions & 1 deletion test/Util/Task/ErrorTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public function __construct(protected string $message)
#[OnChild]
public function run(): void
{
trigger_error($this->message, E_USER_ERROR);
// suppress deprecated warnings for now, not sure how to properly test this in the future
@trigger_error($this->message, E_USER_ERROR);
}
}
23 changes: 23 additions & 0 deletions test/Util/Task/LargeChildTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Aternos\Taskmaster\Test\Util\Task;

use Aternos\Taskmaster\Task\OnChild;
use Aternos\Taskmaster\Task\Task;

class LargeChildTask extends Task
{
#[OnChild] protected ?string $data = null;

public function __construct(#[OnChild] protected int $length = 100_000)
{
}

/**
* @inheritDoc
*/
#[OnChild] public function run(): void
{
$this->data = str_repeat("a", $this->length);
}
}
24 changes: 24 additions & 0 deletions test/Util/Task/LargeParentTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Aternos\Taskmaster\Test\Util\Task;

use Aternos\Taskmaster\Task\OnChild;
use Aternos\Taskmaster\Task\OnParent;
use Aternos\Taskmaster\Task\Task;

class LargeParentTask extends Task
{
#[OnParent] protected string $data;

public function __construct(int $length = 100_000)
{
$this->data = str_repeat("a", $length);
}

/**
* @inheritDoc
*/
#[OnChild] public function run(): void
{
}
}