Remove LocalObject and LocalStorage

This commit is contained in:
Aaron Piotrowski 2015-08-29 01:03:46 -05:00
parent 789f6f5620
commit b0ddd761a6
4 changed files with 0 additions and 555 deletions

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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']);
}
}

View File

@ -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']);
}
}