From 0b6398115965861ba68755d1a652e2513502b28c Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sun, 28 Oct 2018 10:18:24 -0500 Subject: [PATCH] Allow any serializable callable --- src/Internal/ParallelTask.php | 8 ++++ src/functions.php | 15 +++--- test/Fixture/TestCallables.php | 17 +++++++ test/ParallelTest.php | 83 ++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 test/Fixture/TestCallables.php diff --git a/src/Internal/ParallelTask.php b/src/Internal/ParallelTask.php index 8656052..7876ab4 100644 --- a/src/Internal/ParallelTask.php +++ b/src/Internal/ParallelTask.php @@ -25,6 +25,14 @@ class ParallelTask implements Task { public function run(Environment $environment) { $callable = \unserialize($this->function, ['allowed_classes' => true]); + if ($callable instanceof \__PHP_Incomplete_Class) { + throw new \Error('When using a class instance as a callable, the class must be autoloadable'); + } + + if (\is_array($callable) && $callable[0] instanceof \__PHP_Incomplete_Class) { + throw new \Error('When using a class instance method as a callable, the class must be autoloadable'); + } + return $callable(...$this->args); } } diff --git a/src/functions.php b/src/functions.php index 77341aa..0b248cc 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,7 +4,6 @@ namespace Amp\ParallelFunctions; use Amp\MultiReasonException; use Amp\Parallel\Worker\Pool; -use Amp\ParallelFunctions\Internal\ParallelTask; use Amp\Promise; use Opis\Closure\SerializableClosure; use function Amp\call; @@ -22,19 +21,17 @@ use function Amp\Promise\any; */ function parallel(callable $callable, Pool $pool = null): callable { try { - if (\is_string($callable)) { - $payload = \serialize($callable); - } elseif ($callable instanceof \Closure) { - $payload = \serialize(new SerializableClosure($callable)); - } else { - throw new \Error('Unsupported callable type: ' . \gettype($callable)); + if ($callable instanceof \Closure) { + $callable = new SerializableClosure($callable); } + + $payload = \serialize($callable); } catch (\Exception $e) { - throw new \Error('Unsupported callable: ' . $e->getMessage()); + throw new \Error('Unsupported callable: ' . $e->getMessage(), 0, $e); } return function (...$args) use ($pool, $payload): Promise { - $task = new ParallelTask($payload, $args); + $task = new Internal\ParallelTask($payload, $args); return $pool ? $pool->enqueue($task) : enqueue($task); }; } diff --git a/test/Fixture/TestCallables.php b/test/Fixture/TestCallables.php new file mode 100644 index 0000000..64b17f0 --- /dev/null +++ b/test/Fixture/TestCallables.php @@ -0,0 +1,17 @@ +assertSame(1, Promise\wait($callable())); } + + public function testClassStaticMethod() { + $callable = [TestCallables::class, 'staticMethod']; + $result = $callable(1); + $callable = parallel($callable); + + $this->assertSame($result, Promise\wait($callable(1))); + } + + public function testClassInstanceMethod() { + $instance = new TestCallables; + + $callable = [$instance, 'instanceMethod']; + $result = $callable(1); + $callable = parallel($callable); + + $this->assertSame($result, Promise\wait($callable(1))); + } + + public function testCallableClass() { + $callable = new TestCallables; + $result = $callable(1); + $callable = parallel($callable); + + $this->assertSame($result, Promise\wait($callable(1))); + } + + public function testUnserializableCallable() { + $this->expectException(\Error::class); + $this->expectExceptionMessage("Unsupported callable: Serialization of 'class@anonymous' is not allowed"); + + $callable = new class { + public function __invoke() { + } + }; + + $callable = parallel($callable); + } + + public function testUnserializableClassInstance() { + $this->expectException(\Error::class); + $this->expectExceptionMessage('Uncaught Error in worker with message "When using a class instance as a callable, the class must be autoloadable"'); + + $callable = new UnserializableClass; + + $callable = parallel($callable); + + Promise\wait($callable()); + } + + public function testUnserializableClassInstanceMethod() { + $this->expectException(\Error::class); + $this->expectExceptionMessage('Uncaught Error in worker with message "When using a class instance method as a callable, the class must be autoloadable"'); + + $callable = [new UnserializableClass, 'instanceMethod']; + + $callable = parallel($callable); + + Promise\wait($callable()); + } + + public function testUnserializableClassStaticMethod() { + $this->expectException(\Error::class); + $this->expectExceptionMessage('Uncaught Error in worker with message "Class \'Amp\\ParallelFunctions\\Test\\UnserializableClass\' not found"'); + + $callable = [UnserializableClass::class, 'staticMethod']; + + $callable = parallel($callable); + + Promise\wait($callable()); + } }