mirror of
https://github.com/amphp/parallel.git
synced 2025-02-24 23:02:25 +01:00
Remove LocalObject and LocalStorage
This commit is contained in:
parent
789f6f5620
commit
b0ddd761a6
@ -1,186 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Threading;
|
||||
|
||||
use Icicle\Concurrent\Exception\InvalidArgumentError;
|
||||
use Icicle\Concurrent\Exception\LocalObjectError;
|
||||
|
||||
/**
|
||||
* A storage container that stores an object in non-threaded memory.
|
||||
*
|
||||
* The storage container keeps the wrapped object in memory that is local for
|
||||
* the current thread. The object stored can be of any type, and can even be (or
|
||||
* rather, especially) non-thread-safe or non-serializable objects.
|
||||
*
|
||||
* This is useful for storing references to non-thread-safe objects from within
|
||||
* a thread. Normally, only thread-safe or serializable objects are allowed to
|
||||
* be stored as member variables. You can wrap such objects in a `LocalObject`
|
||||
* to store a reference to it safely.
|
||||
*
|
||||
* To access the wrapped object, you must call `LocalObject::deref()` to fetch a
|
||||
* reference to the object. If you think of a `LocalObject` as a fancy pointer
|
||||
* instead of an actual object, you will be less likely to forget to call
|
||||
* `deref()` before using the object.
|
||||
*
|
||||
* Note that the wrapped object will become static, and will not be implicitly
|
||||
* destroyed by the garbage collector. To destroy the object, you must call
|
||||
* `LocalObject::free()` for the object to be destroyed.
|
||||
*/
|
||||
class LocalObject implements \Serializable
|
||||
{
|
||||
/**
|
||||
* @var int The object's local object ID.
|
||||
*/
|
||||
private $objectId;
|
||||
|
||||
/**
|
||||
* @var int The ID of the thread the object belongs to.
|
||||
*/
|
||||
private $threadId;
|
||||
|
||||
/**
|
||||
* @var int The next available object ID.
|
||||
*/
|
||||
private static $nextId = 0;
|
||||
|
||||
/**
|
||||
* Creates a new local object container.
|
||||
*
|
||||
* The object given will be assigned a new object ID and will have a
|
||||
* reference to it stored in memory local to the thread.
|
||||
*
|
||||
* @param object $object The object to store in the container.
|
||||
*/
|
||||
public function __construct($object)
|
||||
{
|
||||
if (!is_object($object)) {
|
||||
throw new InvalidArgumentError('Value is not an object.');
|
||||
}
|
||||
|
||||
// We can't use this object's hash as the ID because it may change as
|
||||
// the handle is passed around and serialized and unserialized.
|
||||
do {
|
||||
$this->objectId = self::$nextId++;
|
||||
} while (!$this->isFreed());
|
||||
$this->threadId = \Thread::getCurrentThreadId();
|
||||
|
||||
// Store the object in the thread-local array.
|
||||
$this->getStorageContainer()->offsetSet($this->objectId, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object stored in the container.
|
||||
*
|
||||
* @return object The stored object.
|
||||
*/
|
||||
public function deref()
|
||||
{
|
||||
if ($this->isFreed()) {
|
||||
throw new LocalObjectError('The object has already been freed.',
|
||||
$this->objectId,
|
||||
$this->threadId);
|
||||
}
|
||||
|
||||
return $this->getStorageContainer()[$this->objectId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the reference to the local object.
|
||||
*
|
||||
* If there are no other references to the object outside of this handle, it
|
||||
* will be destroyed by the garbage collector.
|
||||
*/
|
||||
public function free()
|
||||
{
|
||||
unset($this->getStorageContainer()[$this->objectId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the object has been freed.
|
||||
*
|
||||
* Note that this does not check if the object has been destroyed; it only
|
||||
* checks if this handle has freed its reference to the object.
|
||||
*
|
||||
* @return bool True if the object is freed, otherwise false.
|
||||
*/
|
||||
public function isFreed()
|
||||
{
|
||||
return !isset($this->getStorageContainer()[$this->objectId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the local object handle.
|
||||
*
|
||||
* Note that this does not serialize the object that is referenced, just the
|
||||
* object handle.
|
||||
*
|
||||
* @return string The serialized object handle.
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return serialize([$this->objectId, $this->threadId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes the local object handle.
|
||||
*
|
||||
* @param string $serialized The serialized object handle.
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
list($this->objectId, $this->threadId) = unserialize($serialized);
|
||||
|
||||
if ($this->threadId !== \Thread::getCurrentThreadId()) {
|
||||
throw new LocalObjectError('Local objects cannot be passed to other threads.',
|
||||
$this->objectId,
|
||||
$this->threadId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles cloning, which creates clones the local object and creates a new
|
||||
* local object handle.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$object = clone $this->deref();
|
||||
$this->__construct($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the object for debugging purposes.
|
||||
*
|
||||
* @return array An array of debugging information.
|
||||
*/
|
||||
public function __debugInfo()
|
||||
{
|
||||
if ($this->isFreed()) {
|
||||
return [
|
||||
'id' => $this->objectId,
|
||||
'object' => null,
|
||||
'freed' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $this->objectId,
|
||||
'object' => $this->deref(),
|
||||
'freed' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a global array of objects in non-threaded memory.
|
||||
*
|
||||
* @return \ArrayObject The non-threaded array.
|
||||
*/
|
||||
private function getStorageContainer()
|
||||
{
|
||||
static $container;
|
||||
|
||||
if ($container === null) {
|
||||
$container = new \ArrayObject();
|
||||
}
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Threading;
|
||||
|
||||
use Icicle\Concurrent\Exception\InvalidArgumentError;
|
||||
|
||||
/**
|
||||
* A storage container that stores data in the local thread only.
|
||||
*
|
||||
* The storage container acts like a dictionary, and can store any type of value,
|
||||
* including arrays and non-serializable or non-thread-safe objects. Numbers and
|
||||
* strings are valid keys.
|
||||
*/
|
||||
class LocalStorage implements \Countable, \IteratorAggregate, \ArrayAccess
|
||||
{
|
||||
/**
|
||||
* @var LocalObject A thread-local array object of items.
|
||||
*/
|
||||
private $array;
|
||||
|
||||
/**
|
||||
* Creates a new local storage container.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->array = new LocalObject(new \ArrayObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of items in the local storage.
|
||||
*
|
||||
* @return int The number of items.
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->array->deref());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an iterator for iterating over all the items in the local storage.
|
||||
*
|
||||
* @return \Traversable A new iterator.
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return $this->array->deref()->getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the local storage.
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->array->deref()->exchangeArray([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given item exists.
|
||||
*
|
||||
* @param int|string $key The key of the item to check.
|
||||
*
|
||||
* @return bool True if the item exists, otherwise false.
|
||||
*/
|
||||
public function offsetExists($key)
|
||||
{
|
||||
if (!is_int($key) && !is_string($key)) {
|
||||
throw new InvalidArgumentError('Key must be an integer or string.');
|
||||
}
|
||||
|
||||
return $this->array->deref()->offsetExists($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an item's value from the local storage.
|
||||
*
|
||||
* @param int|string $key The key of the item to get.
|
||||
*
|
||||
* @return mixed The item value.
|
||||
*/
|
||||
public function offsetGet($key)
|
||||
{
|
||||
if (!is_int($key) && !is_string($key)) {
|
||||
throw new InvalidArgumentError('Key must be an integer or string.');
|
||||
}
|
||||
|
||||
if (!$this->offsetExists($key)) {
|
||||
throw new InvalidArgumentError("The key '{$key}' does not exist.");
|
||||
}
|
||||
|
||||
return $this->array->deref()->offsetGet($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of an item.
|
||||
*
|
||||
* @param int|string $key The key of the item to set.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public function offsetSet($key, $value)
|
||||
{
|
||||
if (!is_int($key) && !is_string($key)) {
|
||||
throw new InvalidArgumentError('Key must be an integer or string.');
|
||||
}
|
||||
|
||||
$this->array->deref()->offsetSet($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the local storage.
|
||||
*
|
||||
* @param int|string $key The key of the item to remove.
|
||||
*/
|
||||
public function offsetUnset($key)
|
||||
{
|
||||
if (!is_int($key) && !is_string($key)) {
|
||||
throw new InvalidArgumentError('Key must be an integer or string.');
|
||||
}
|
||||
|
||||
$this->array->deref()->offsetUnset($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the local storage and all items.
|
||||
*/
|
||||
public function free()
|
||||
{
|
||||
$this->array->free();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a copy of all the items in the local storage.
|
||||
*
|
||||
* @return array An array of item keys and values.
|
||||
*/
|
||||
public function __debugInfo()
|
||||
{
|
||||
return $this->array->deref()->getArrayCopy();
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Tests\Concurrent\Threading;
|
||||
|
||||
use Icicle\Concurrent\Threading\LocalObject;
|
||||
use Icicle\Promise\Promise;
|
||||
use Icicle\Tests\Concurrent\TestCase;
|
||||
|
||||
/**
|
||||
* @group threading
|
||||
* @requires extension pthreads
|
||||
*/
|
||||
class LocalObjectTest extends TestCase
|
||||
{
|
||||
public function testConstructor()
|
||||
{
|
||||
$object = new LocalObject(new \stdClass());
|
||||
$this->assertInternalType('object', $object->deref());
|
||||
}
|
||||
|
||||
public function testClosureObject()
|
||||
{
|
||||
$object = new LocalObject(function () {});
|
||||
$this->assertInstanceOf('Closure', $object->deref());
|
||||
}
|
||||
|
||||
public function testResource()
|
||||
{
|
||||
$object = new LocalObject(new \stdClass());
|
||||
$object->deref()->resource = fopen(__FILE__, 'r');
|
||||
$this->assertInternalType('resource', $object->deref()->resource);
|
||||
fclose($object->deref()->resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError
|
||||
*/
|
||||
public function testNonObjectThrowsError()
|
||||
{
|
||||
$object = new LocalObject(42);
|
||||
}
|
||||
|
||||
public function testDerefIsOfCorrectType()
|
||||
{
|
||||
$object = new LocalObject(new \stdClass());
|
||||
$this->assertInstanceOf('stdClass', $object->deref());
|
||||
}
|
||||
|
||||
public function testDerefIsSameObject()
|
||||
{
|
||||
$object = new \stdClass();
|
||||
$local = new LocalObject($object);
|
||||
$this->assertSame($object, $local->deref());
|
||||
}
|
||||
|
||||
public function testNewObjectIsNotFreed()
|
||||
{
|
||||
$object = new LocalObject(new \stdClass());
|
||||
$this->assertFalse($object->isFreed());
|
||||
}
|
||||
|
||||
public function testFreeReleasesObject()
|
||||
{
|
||||
$object = new LocalObject(new \stdClass());
|
||||
$object->free();
|
||||
$this->assertTrue($object->isFreed());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Icicle\Concurrent\Exception\LocalObjectError
|
||||
*/
|
||||
public function testDerefThrowsErrorIfFreed()
|
||||
{
|
||||
$object = new LocalObject(new \stdClass());
|
||||
$object->free();
|
||||
$object->deref();
|
||||
}
|
||||
|
||||
public function testSerializeDoesntAffectObject()
|
||||
{
|
||||
$object = new \stdClass();
|
||||
$local = new LocalObject($object);
|
||||
$local = unserialize(serialize($local));
|
||||
$this->assertSame($object, $local->deref());
|
||||
}
|
||||
|
||||
public function testPromiseInThread()
|
||||
{
|
||||
$thread = \Thread::from(function () {
|
||||
require __DIR__.'/../../vendor/autoload.php';
|
||||
$promise = new LocalObject(new Promise(function ($resolve, $reject) {}));
|
||||
});
|
||||
|
||||
$thread->start();
|
||||
$thread->join();
|
||||
}
|
||||
|
||||
public function testCloneIsNewObject()
|
||||
{
|
||||
$object = new \stdClass();
|
||||
$local = new LocalObject($object);
|
||||
$clone = clone $local;
|
||||
$this->assertNotSame($local, $clone);
|
||||
$this->assertNotSame($object, $clone->deref());
|
||||
$this->assertNotEquals($local->__debugInfo()['id'], $clone->__debugInfo()['id']);
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Tests\Concurrent\Threading;
|
||||
|
||||
use Icicle\Concurrent\Threading\LocalStorage;
|
||||
use Icicle\Tests\Concurrent\TestCase;
|
||||
|
||||
/**
|
||||
* @group threading
|
||||
* @requires extension pthreads
|
||||
*/
|
||||
class LocalStorageTest extends TestCase
|
||||
{
|
||||
private $localStorage;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->localStorage = new LocalStorage();
|
||||
}
|
||||
|
||||
public function testCount()
|
||||
{
|
||||
$this->localStorage['a'] = 'Apple';
|
||||
$this->localStorage['b'] = 'Banana';
|
||||
$this->localStorage['c'] = 'Cherry';
|
||||
|
||||
$this->assertCount(3, $this->localStorage);
|
||||
}
|
||||
|
||||
public function testIterate()
|
||||
{
|
||||
$this->localStorage['a'] = 'Apple';
|
||||
$this->localStorage['b'] = 'Banana';
|
||||
$this->localStorage['c'] = 'Cherry';
|
||||
|
||||
foreach ($this->localStorage as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'a':
|
||||
$this->assertEquals('Apple', $value);
|
||||
break;
|
||||
case 'b':
|
||||
$this->assertEquals('Banana', $value);
|
||||
break;
|
||||
case 'c':
|
||||
$this->assertEquals('Cherry', $value);
|
||||
break;
|
||||
default:
|
||||
$this->fail('Invalid key returned from iterator.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testClear()
|
||||
{
|
||||
$this->localStorage['a'] = 'Apple';
|
||||
$this->localStorage['b'] = 'Banana';
|
||||
$this->localStorage['c'] = 'Cherry';
|
||||
|
||||
$this->localStorage->clear();
|
||||
$this->assertCount(0, $this->localStorage);
|
||||
$this->assertEmpty($this->localStorage);
|
||||
}
|
||||
|
||||
public function testIsset()
|
||||
{
|
||||
$this->localStorage['foo'] = 'bar';
|
||||
|
||||
$this->assertTrue(isset($this->localStorage['foo']));
|
||||
$this->assertFalse(isset($this->localStorage['baz']));
|
||||
}
|
||||
|
||||
public function testGetSet()
|
||||
{
|
||||
$this->localStorage['foo'] = 'bar';
|
||||
$this->assertEquals('bar', $this->localStorage['foo']);
|
||||
}
|
||||
|
||||
public function testUnset()
|
||||
{
|
||||
$this->localStorage['foo'] = 'bar';
|
||||
unset($this->localStorage['foo']);
|
||||
|
||||
$this->assertFalse(isset($this->localStorage['foo']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError
|
||||
*/
|
||||
public function testGetInvalidKeyThrowsError()
|
||||
{
|
||||
$value = $this->localStorage['does not exist'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError
|
||||
*/
|
||||
public function testAppendThrowsError()
|
||||
{
|
||||
$this->localStorage[] = 'value';
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError
|
||||
*/
|
||||
public function testSetFloatKeyThrowsError()
|
||||
{
|
||||
$this->localStorage[1.1] = 'value';
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Icicle\Concurrent\Exception\InvalidArgumentError
|
||||
*/
|
||||
public function testSetObjectKeyThrowsError()
|
||||
{
|
||||
$this->localStorage[new \stdClass()] = 'value';
|
||||
}
|
||||
|
||||
public function testClosure()
|
||||
{
|
||||
$this->localStorage['foo'] = function () {
|
||||
return 'Hello, World!';
|
||||
};
|
||||
|
||||
$this->assertInstanceOf('Closure', $this->localStorage['foo']);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user