My own solution (for now - still waiting for other suggestions, so if you got a better/different soltution, go ahead!):
1) I modified the lock
and unlock
methods to count lock/unlock calls and only access the semaphore if it hasn't been locked yet / has been unlocked as often as locked.
2) I added a modify
method to my SHM
class that takes the key of a variable to modify and a callback. It then locks, gets the variable using the getter (no additional locking because of 1) ), calls the callback and passes the variable to it, afterwards it uses the setter (again: no additional locking) to write the new value back, then it frees the semaphore.
The modify
method can work in two modes: either the callback can take the variable as reference and modify it (default), or you can tell modify()
to instead assign the return value of the callback to the function. This provides maximum flexibility.
The modified SHM class is below (still no error-handling, but modified free
and lock
behave nicely so it can be added):
<?php namespace Utilities;
//TODO: ERROR HANDLING
class SHM {
private static function getIdentifier ($identFile, $projId) {
return ftok($identFile, $projId);
}
private static $defaultSize = 10000;
private $sem = NULL;
private $shm = NULL;
private $identFile;
private $projId;
private $size;
private $locked=0;
public function __construct($identFile, $projId, $size=NULL) {
if ($size === NULL) $size = self::$defaultSize;
$this->identFile = $identFile;
$this->projId = $projId;
$this->size = $size;
}
public function __destruct() {
if ($this->sem) {
$this->lock();
if ($this->shm) {
shm_detach($this->shm);
}
$this->free();
}
}
public function clean () {
$this->lock();
shm_remove($this->shm);
$this->free();
sem_remove($this->sem);
$this->shm = NULL;
$this->sem = NULL;
}
public function __isset($key) {
return $this->exists($key);
}
public function __get($key) {
return $this->get($key);
}
public function __set($key, $val) {
return $this->set($key, $val);
}
public function __unset($key) {
return $this->remove($key);
}
public function exists ($key) {
return shm_has_var($this->getShm(), $key);
}
public function get ($key, $lock=true) {
if ($this->exists ($key)) {
if ($lock) $this->lock();
$var = shm_get_var($this->getShm(), $key);
if ($lock) $this->free();
return $var;
} else return NULL;
}
public function set ($key, $var, $lock=true) {
if ($lock) $this->lock();
shm_put_var($this->getShm(), $key, $var);
if ($lock) $this->free();
}
public function modify ($key, $action, $useReturn = false) {
$var = $this->get($key);
$result = $action($var);
if ($useReturn) {
$var = $result;
}
$this->set($key, $var);
}
public function remove ($key, $lock=true) {
if ($this->exists ($key)) {
if ($lock) $this->lock();
$result = shm_remove_var($this->getShm(), $key);
if ($lock) $this->free();
return $result;
} else return NULL;
}
private function getSem () {
if ($this->sem === NULL) {
$this->sem = sem_get(self::getIdentifier($this->identFile, $this->projId));
}
return $this->sem;
}
private function getShm () {
if ($this->shm === NULL) {
$this->shm = shm_attach(self::getIdentifier($this->identFile, $this->projId), $this->size);
}
return $this->shm;
}
private function lock () {
if ($this->locked == 0) {
$result = sem_acquire($this->getSem());
if (!$result) return 0;
}
return ++$this->locked;
}
private function free () {
if ($this->locked == 1) {
$result = sem_release($this->getSem());
if (!$result) return 0;
}
return --$this->locked;
}
}