Skip to content

Release 1.1.0 #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 27, 2023
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
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Shmop is an easy to use set of functions that allows PHP to read, write, create
Shared memory an IPC1 mechanism native to UNIX. In essence, it’s about two processes sharing a common
segment of memory that they can both read to and write from to communicate with one another.

Locks and semaphores are used to ensure atomic access so that multiple PHP processes can concurrently use the same shared memory safely.

## Installing

Require this package, with [Composer](https://getcomposer.org/), in the root directory of your project.
Expand Down Expand Up @@ -62,4 +64,28 @@ Cache::store('memory')->put('some_key', ['value' => 'text']);
use Illuminate\Support\Facades\Cache;

$data = Cache::store('memory')->get('some_key');
```
```

## About memory limits
Garbage collection (by removing expired items) will be performed when the cache is near the size limit.
If the garbage collection fails to reduce the size of the cache below the size limit,
then the cache will be invalidated and the underlying memory segment is marked for deletion.

Running out of memory will generate a warning or a notice in your logs, no matter if it is resolved by
a garbage collection or by segment deletion.

Note: **items that are stored as "forever" may be removed when the cache reaches its size limit**.

### Recreating the memory block
When recreating the memory block, the newest size limit defined in the Laravel config file will be used.

### Manually marking the memory segment for deletion
There are use cases to this, such as wanting to refresh the memory block now instead of waiting for
another "out of memory" event. In this case, you may do the following:

```php
// the deletion will be managed by the OS kernel , and will happen at a future time
Cache::store('memory')->getStore()->requestDeletion();
```

This usage will not trigger any warnings or notices since this is an action taken deliberately.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"require": {
"php" : "^7.3|^8.0",
"ext-shmop": "*",
"ext-sysvsem": "*",
"illuminate/support": "^8.0|^9.0",
"illuminate/cache": "^8.0|^9.0"
},
Expand Down
21 changes: 21 additions & 0 deletions src/Cache/MemoryBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,34 @@ public function getMode()
/**
* Gets the current shared memory size.
*
* Note that this is the size specified by the user.
*
* This size can be different from the size obtained from getSizeInMemory()
* because, e.g., the user has changed the size limit but the change is not yet communicated to the OS kernel.
*
* @return int
*/
public function getSize()
{
return $this->size;
}

/**
* Gets the current shared memory size.
*
* Note that this is the size of the actual memory block managed by the OS kernel.
*
* This size can be different from the size obtained from getSize().
* When the specified memory size is updated by the user,
* the OS must delete and recreate the memory block so that the new size can be applied.
*
* @return int
*/
public function getSizeInMemory()
{
return shmop_size($this->id);
}

/**
* Makes a System V IPC key from pathname and a project identifier.
*
Expand Down
105 changes: 102 additions & 3 deletions src/Cache/MemoryStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,22 @@ class MemoryStore implements StoreInterface
/** @var \Sanchescom\Cache\MemoryBlock */
protected $memory;

/** @var resource */
private $semaphore;

/** @var bool */
private $ignoreNextLock;

/**
* @param \Sanchescom\Cache\MemoryBlock
*/
public function __construct(MemoryBlock $memoryBlock)
{
$this->memory = $memoryBlock;

$this->semaphore = sem_get(ftok(__FILE__, 's'));

$this->ignoreNextLock = false;
}

/**
Expand Down Expand Up @@ -59,6 +69,8 @@ public function get($key)
/**
* Store an item in the cache for a given number of seconds.
*
* This action is atomic.
*
* @param string $key
* @param mixed $value
* @param int $seconds
Expand All @@ -67,6 +79,13 @@ public function get($key)
*/
public function put($key, $value, $seconds)
{
sem_acquire($this->semaphore, $this->ignoreNextLock);

if ($this->ignoreNextLock) {
// needed to handle "increment from nothing" case
$this->ignoreNextLock = false;
}

$storage = $this->getStorage();

if ($storage === false) {
Expand All @@ -81,22 +100,30 @@ public function put($key, $value, $seconds)

$this->setStorage($storage);

sem_release($this->semaphore);

return true;
}

/**
* Increment the value of an item in the cache.
*
* This action is atomic.
*
* @param string $key
* @param mixed $value
*
* @return int|bool
*/
public function increment($key, $value = 1)
{
sem_acquire($this->semaphore);

$storage = $this->getStorage();

if (!$storage || !isset($storage[$key])) {
$this->ignoreNextLock = true;

$this->forever($key, $value);

return $storage[$key]['value'];
Expand All @@ -106,12 +133,16 @@ public function increment($key, $value = 1)

$this->setStorage($storage);

sem_release($this->semaphore);

return $storage[$key]['value'];
}

/**
* Decrement the value of an item in the cache.
*
* This action is atomic (managed by increment()).
*
* @param string $key
* @param mixed $value
*
Expand All @@ -125,6 +156,8 @@ public function decrement($key, $value = 1)
/**
* Store an item in the cache indefinitely.
*
* This action is atomic (managed by put()).
*
* @param string $key
* @param mixed $value
*
Expand All @@ -138,12 +171,16 @@ public function forever($key, $value)
/**
* Remove an item from the cache.
*
* This action is atomic.
*
* @param string $key
*
* @return bool
*/
public function forget($key)
{
sem_acquire($this->semaphore);

$storage = $this->getStorage();

if ($storage === false) {
Expand All @@ -156,38 +193,85 @@ public function forget($key)

$this->setStorage($storage);

sem_release($this->semaphore);

return true;
}

sem_release($this->semaphore);

return false;
}

/**
* Remove all items from the cache.
*
* This action is atomic.
*
* @return bool
*/
public function flush()
{
sem_acquire($this->semaphore);

$this->setStorage([]);

sem_release($this->semaphore);

return true;
}

/**
* Save data in memory storage
* Save data in memory storage.
*
* If the given data will exceed the in-system memory block size limit,
* then a garbage collection (GC) is performed on the data array where expired items are discarded.
* A new data array containing the survivors of the GC will be created.
*
* After the GC, if the new data array will still exceed the in-system memory block size limit,
* then the in-system memory block will be marked for deletion.
* Updated settings, e.g. the updated memory size, will then be applied when the memory block is recreated.
*
* @param $data
*
* @return void
*/
protected function setStorage($data)
{
$this->memory->write($this->serialize($data));
$serial = (string) $this->serialize($data);
$memorySize = $this->memory->getSizeInMemory();

if (strlen($serial) > $memorySize) {
$timeNow = $this->currentTime();

foreach ($data as $key => $details) {
$expiresAt = $details['expiresAt'] ?? 0;

if ($expiresAt !== 0 && $timeNow > $expiresAt) {
unset($data[$key]);
}
}

$message = "Laravel Memory Cache: Out of memory (Unix allocated $memorySize); ";

$serial = (string) $this->serialize($data);

if (strlen($serial) > $memorySize) {
$message .= 'the segment will be recreated.';
trigger_error($message, E_USER_WARNING);

$this->memory->delete();
} else {
$message .= 'garbage collection was performed.';
trigger_error($message, E_USER_NOTICE);
}
}

$this->memory->write($serial);
}

/**
* Get data from memory storage
* Get data from memory storage.
*
* @return array
*/
Expand Down Expand Up @@ -253,4 +337,19 @@ protected function unserialize($value)
{
return is_numeric($value) ? $value : @unserialize($value);
}

/**
* Requests to the OS kernel that the underlying shared memory segment should be deleted.
*
* This allows the memory segment to be recreated later with updated parameters.
*
* The timing of deletion is managed by the OS kernel, but this usually happens after
* all relevant processes are disconnected from the shared memory segment.
*
* @return void
*/
public function requestDeletion()
{
$this->memory->delete();
}
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy