Skip to content

Commit

Permalink
✨ global & atomic
Browse files Browse the repository at this point in the history
  • Loading branch information
bnomei committed Aug 10, 2024
1 parent 49fe981 commit ab9c89d
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 212 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ Nitro speeds up the loading of content in your Kirby project.
a [key-value caching helper](https://github.com/bnomei/kirby3-lapse).
- If you load more, you should consider [Boost](https://github.com/bnomei/kirby3-boost)
or [Khulan](https://github.com/bnomei/kirby-mongodb) instead.
- If you need to process multiple requests fully concurrently you should not use this plugin. But from my experience
most Kirby projects do not need that.

## Global & Atomic Cache

The Nitro cache is a global cache. This means that the cache is shared between all HTTP_HOST environments. This will
make it behave like a single database connection.

The Nitro cache is by default an atomic cache. This means that the cache will block the cache file for the full duration
of your request to maintain data consistency. This will make it behave like a database with locks.

> [!WARNING]
> No matter how many php-fpm workers you have, only one will be running at a time when Nitro is in atomic mode! You have
> been warned! But this is the only way to guarantee data consistency, and it will still be wicked fast.
## Setup

Expand Down Expand Up @@ -129,6 +143,9 @@ return [

| bnomei.nitro. | Default | Description |
|-------------------|-----------------------|------------------------------------------------------------------------------|
| global | `true` | all HTTP_HOSTs will share the same cache |
| atomic | `true` | will lock the cache while a request is processed to achieve data consistency |
| sleep | `1000` | duration in MICRO seconds before checking the lock again |
| auto-clean-cache | `true` | will clean the cache once before the first get() |
| patch-dir-class | always on | monkey-patch the \Kirby\Filesystem\Dir class to use Nitro for caching |
| patch-files-class | `true` | monkey-patch the \Kirby\CMS\Files class to use Nitro for caching its content |
Expand Down
5 changes: 2 additions & 3 deletions classes/Nitro.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ public function flush(): void
{
// reset in memory cache as it will be written on destruct
// and thus would survive the flushing of the directories
$this->cache()->flush();
$this->cache()->flush(write: false);
$this->dir()->flush(write: false);

$internalDir = $this->options['cacheDir'];
if (Dir::exists($internalDir)) {
Expand All @@ -179,8 +180,6 @@ public function flush(): void
Dir::remove($dir);
}
}

$this->dir()->flush();
}

public static ?self $singleton = null;
Expand Down
14 changes: 8 additions & 6 deletions classes/Nitro/DirInventory.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,17 @@ public function set(string|array $key, ?array $input = null): void
$this->data[$key] = $input;
}

public function flush(): void
public function flush(bool $write = true): void
{
$file = $this->file();
if (file_exists($file)) {
unlink($file);
}

$this->data = [];
$this->isDirty = true;

if ($write) {
$file = $this->file();
if (file_exists($file)) {
unlink($file);
}
}
}

private function key(string|array $key): string
Expand Down
91 changes: 84 additions & 7 deletions classes/Nitro/SingleFileCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ public function __construct(array $options = [])
parent::__construct($options);

$this->options = array_merge([
'global' => option('bnomei.nitro.global'),
'atomic' => option('bnomei.nitro.atomic'),
'sleep' => option('bnomei.nitro.sleep'),
'auto-clean-cache' => option('bnomei.nitro.auto-clean-cache'),
'json-encode-flags' => option('bnomei.nitro.json-encode-flags'),
'cacheDir' => realpath(__DIR__.'/../').'/cache', // must be here as well for when used without nitro like as uuid cache
'max-dirty-cache' => intval(option('bnomei.nitro.max-dirty-cache')), // @phpstan-ignore-line
'debug' => option('debug'),
], $options);
Expand All @@ -36,11 +40,13 @@ public function __construct(array $options = [])
if ($this->options['auto-clean-cache']) {
$this->clean();
}

$this->atomic();
}

public function __destruct()
{
$this->write();
$this->write(lock: false);
}

public function key(string|array $key): string
Expand Down Expand Up @@ -134,13 +140,15 @@ public function remove(string|array $key): bool
/**
* {@inheritDoc}
*/
public function flush(): bool
public function flush(bool $write = true): bool
{
if (count($this->data) === 0) {
$this->isDirty++;
}
$this->data = [];
$this->write();
if ($write) {
$this->write();
}

return true;
}
Expand All @@ -155,19 +163,34 @@ private function clean(): void
protected function file(?string $key = null): string
{
/** @var FileCache $cache */
$cache = kirby()->cache('bnomei.nitro.sfc');
if ($this->options['global']) {

Check failure on line 166 in classes/Nitro/SingleFileCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Variable $cache in PHPDoc tag @var does not exist.

Check failure on line 166 in classes/Nitro/SingleFileCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Variable $cache in PHPDoc tag @var does not exist.
$cache = $this->options['cacheDir'];
} else {
$cache = kirby()->cache('bnomei.nitro.sfc')->root();

Check failure on line 169 in classes/Nitro/SingleFileCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to an undefined method Kirby\Cache\Cache::root().

Check failure on line 169 in classes/Nitro/SingleFileCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to an undefined method Kirby\Cache\Cache::root().
}

return $cache->root().'/single-file-cache.json';
return $cache.'/single-file-cache.json';
}

public function write(): bool
public function write(bool $lock = true): bool
{
$this->unlock();

if ($this->isDirty === 0) {
if ($lock) {
$this->unlock();
}

return false;
}
$this->isDirty = 0;

return F::write($this->file(), json_encode($this->data, $this->options['json-encode-flags']));
$success = F::write($this->file(), json_encode($this->data, $this->options['json-encode-flags']));
if ($lock) {
$this->lock();
}

return $success;
}

private static function isCallable(mixed $value): bool
Expand Down Expand Up @@ -203,4 +226,58 @@ public function count(): int
{
return count($this->data);
}

private function isLocked()

Check failure on line 230 in classes/Nitro/SingleFileCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Bnomei\Nitro\SingleFileCache::isLocked() has no return type specified.

Check failure on line 230 in classes/Nitro/SingleFileCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Bnomei\Nitro\SingleFileCache::isLocked() has no return type specified.
{
if (! $this->options['atomic']) {
return false;
}

return F::exists($this->file().'.lock');
}

public function lock(): bool
{
if (! $this->options['atomic']) {
return false;
}

return F::write($this->file().'.lock', date('c'));
}

public function unlock(): bool
{
if (! $this->options['atomic']) {
return false;
}

return F::remove($this->file().'.lock');
}

private function atomic(): bool
{
if (! $this->options['atomic']) {
return false;
}

// this is what makes it atomic
// get php max execution time
$maxExecutionTime = (int) ini_get('max_execution_time');
if ($maxExecutionTime === 0) {
$maxExecutionTime = 30; // default, might happen in xdebug mode
}
$maxCycles = $maxExecutionTime * 1000 * 1000; // seconds to microseconds
$sleep = $this->options['sleep'];

while ($this->isLocked()) {
$maxCycles -= $sleep;
if ($maxCycles <= 0) {
throw new \Exception('Something is very wrong. SingleFileCache could not get lock within '.$maxExecutionTime.' seconds! Are using xdebug breakpoints or maybe you need to forcibly `kirby nitro:unlock`?');
}

usleep($sleep);
}

return $this->lock();
}
}
14 changes: 14 additions & 0 deletions classes/NitroCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Bnomei;

use Kirby\Cache\FileCache;

class NitroCache extends FileCache
{
// make a magic call to all methods of the cache
public function __call($method, $args)

Check failure on line 10 in classes/NitroCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Bnomei\NitroCache::__call() has no return type specified.

Check failure on line 10 in classes/NitroCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Bnomei\NitroCache::__call() has parameter $args with no type specified.

Check failure on line 10 in classes/NitroCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Bnomei\NitroCache::__call() has parameter $method with no type specified.

Check failure on line 10 in classes/NitroCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Bnomei\NitroCache::__call() has no return type specified.

Check failure on line 10 in classes/NitroCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Bnomei\NitroCache::__call() has parameter $args with no type specified.

Check failure on line 10 in classes/NitroCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Bnomei\NitroCache::__call() has parameter $method with no type specified.
{
return call_user_func_array([Nitro::singleton()->cache(), $method], $args);

Check failure on line 12 in classes/NitroCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Parameter #1 $callback of function call_user_func_array expects callable(): mixed, array{Bnomei\Nitro\SingleFileCache, mixed} given.

Check failure on line 12 in classes/NitroCache.php

View workflow job for this annotation

GitHub Actions / phpstan

Parameter #1 $callback of function call_user_func_array expects callable(): mixed, array{Bnomei\Nitro\SingleFileCache, mixed} given.
}
}
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "bnomei/kirby-nitro",
"type": "kirby-plugin",
"version": "1.1.3",
"version": "2.0.0",
"description": "Nitro speeds up the loading of content in your Kirby project.",
"license": "MIT",
"authors": [
Expand Down
Loading

0 comments on commit ab9c89d

Please sign in to comment.