Skip to content

Commit ab9c89d

Browse files
committed
✨ global & atomic
1 parent 49fe981 commit ab9c89d

12 files changed

+363
-212
lines changed

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ Nitro speeds up the loading of content in your Kirby project.
3737
a [key-value caching helper](https://github.com/bnomei/kirby3-lapse).
3838
- If you load more, you should consider [Boost](https://github.com/bnomei/kirby3-boost)
3939
or [Khulan](https://github.com/bnomei/kirby-mongodb) instead.
40+
- If you need to process multiple requests fully concurrently you should not use this plugin. But from my experience
41+
most Kirby projects do not need that.
42+
43+
## Global & Atomic Cache
44+
45+
The Nitro cache is a global cache. This means that the cache is shared between all HTTP_HOST environments. This will
46+
make it behave like a single database connection.
47+
48+
The Nitro cache is by default an atomic cache. This means that the cache will block the cache file for the full duration
49+
of your request to maintain data consistency. This will make it behave like a database with locks.
50+
51+
> [!WARNING]
52+
> 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
53+
> been warned! But this is the only way to guarantee data consistency, and it will still be wicked fast.
4054
4155
## Setup
4256

@@ -129,6 +143,9 @@ return [
129143

130144
| bnomei.nitro. | Default | Description |
131145
|-------------------|-----------------------|------------------------------------------------------------------------------|
146+
| global | `true` | all HTTP_HOSTs will share the same cache |
147+
| atomic | `true` | will lock the cache while a request is processed to achieve data consistency |
148+
| sleep | `1000` | duration in MICRO seconds before checking the lock again |
132149
| auto-clean-cache | `true` | will clean the cache once before the first get() |
133150
| patch-dir-class | always on | monkey-patch the \Kirby\Filesystem\Dir class to use Nitro for caching |
134151
| patch-files-class | `true` | monkey-patch the \Kirby\CMS\Files class to use Nitro for caching its content |

classes/Nitro.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ public function flush(): void
167167
{
168168
// reset in memory cache as it will be written on destruct
169169
// and thus would survive the flushing of the directories
170-
$this->cache()->flush();
170+
$this->cache()->flush(write: false);
171+
$this->dir()->flush(write: false);
171172

172173
$internalDir = $this->options['cacheDir'];
173174
if (Dir::exists($internalDir)) {
@@ -179,8 +180,6 @@ public function flush(): void
179180
Dir::remove($dir);
180181
}
181182
}
182-
183-
$this->dir()->flush();
184183
}
185184

186185
public static ?self $singleton = null;

classes/Nitro/DirInventory.php

+8-6
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,17 @@ public function set(string|array $key, ?array $input = null): void
6363
$this->data[$key] = $input;
6464
}
6565

66-
public function flush(): void
66+
public function flush(bool $write = true): void
6767
{
68-
$file = $this->file();
69-
if (file_exists($file)) {
70-
unlink($file);
71-
}
72-
7368
$this->data = [];
7469
$this->isDirty = true;
70+
71+
if ($write) {
72+
$file = $this->file();
73+
if (file_exists($file)) {
74+
unlink($file);
75+
}
76+
}
7577
}
7678

7779
private function key(string|array $key): string

classes/Nitro/SingleFileCache.php

+84-7
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ public function __construct(array $options = [])
2121
parent::__construct($options);
2222

2323
$this->options = array_merge([
24+
'global' => option('bnomei.nitro.global'),
25+
'atomic' => option('bnomei.nitro.atomic'),
26+
'sleep' => option('bnomei.nitro.sleep'),
2427
'auto-clean-cache' => option('bnomei.nitro.auto-clean-cache'),
2528
'json-encode-flags' => option('bnomei.nitro.json-encode-flags'),
29+
'cacheDir' => realpath(__DIR__.'/../').'/cache', // must be here as well for when used without nitro like as uuid cache
2630
'max-dirty-cache' => intval(option('bnomei.nitro.max-dirty-cache')), // @phpstan-ignore-line
2731
'debug' => option('debug'),
2832
], $options);
@@ -36,11 +40,13 @@ public function __construct(array $options = [])
3640
if ($this->options['auto-clean-cache']) {
3741
$this->clean();
3842
}
43+
44+
$this->atomic();
3945
}
4046

4147
public function __destruct()
4248
{
43-
$this->write();
49+
$this->write(lock: false);
4450
}
4551

4652
public function key(string|array $key): string
@@ -134,13 +140,15 @@ public function remove(string|array $key): bool
134140
/**
135141
* {@inheritDoc}
136142
*/
137-
public function flush(): bool
143+
public function flush(bool $write = true): bool
138144
{
139145
if (count($this->data) === 0) {
140146
$this->isDirty++;
141147
}
142148
$this->data = [];
143-
$this->write();
149+
if ($write) {
150+
$this->write();
151+
}
144152

145153
return true;
146154
}
@@ -155,19 +163,34 @@ private function clean(): void
155163
protected function file(?string $key = null): string
156164
{
157165
/** @var FileCache $cache */
158-
$cache = kirby()->cache('bnomei.nitro.sfc');
166+
if ($this->options['global']) {
167+
$cache = $this->options['cacheDir'];
168+
} else {
169+
$cache = kirby()->cache('bnomei.nitro.sfc')->root();
170+
}
159171

160-
return $cache->root().'/single-file-cache.json';
172+
return $cache.'/single-file-cache.json';
161173
}
162174

163-
public function write(): bool
175+
public function write(bool $lock = true): bool
164176
{
177+
$this->unlock();
178+
165179
if ($this->isDirty === 0) {
180+
if ($lock) {
181+
$this->unlock();
182+
}
183+
166184
return false;
167185
}
168186
$this->isDirty = 0;
169187

170-
return F::write($this->file(), json_encode($this->data, $this->options['json-encode-flags']));
188+
$success = F::write($this->file(), json_encode($this->data, $this->options['json-encode-flags']));
189+
if ($lock) {
190+
$this->lock();
191+
}
192+
193+
return $success;
171194
}
172195

173196
private static function isCallable(mixed $value): bool
@@ -203,4 +226,58 @@ public function count(): int
203226
{
204227
return count($this->data);
205228
}
229+
230+
private function isLocked()
231+
{
232+
if (! $this->options['atomic']) {
233+
return false;
234+
}
235+
236+
return F::exists($this->file().'.lock');
237+
}
238+
239+
public function lock(): bool
240+
{
241+
if (! $this->options['atomic']) {
242+
return false;
243+
}
244+
245+
return F::write($this->file().'.lock', date('c'));
246+
}
247+
248+
public function unlock(): bool
249+
{
250+
if (! $this->options['atomic']) {
251+
return false;
252+
}
253+
254+
return F::remove($this->file().'.lock');
255+
}
256+
257+
private function atomic(): bool
258+
{
259+
if (! $this->options['atomic']) {
260+
return false;
261+
}
262+
263+
// this is what makes it atomic
264+
// get php max execution time
265+
$maxExecutionTime = (int) ini_get('max_execution_time');
266+
if ($maxExecutionTime === 0) {
267+
$maxExecutionTime = 30; // default, might happen in xdebug mode
268+
}
269+
$maxCycles = $maxExecutionTime * 1000 * 1000; // seconds to microseconds
270+
$sleep = $this->options['sleep'];
271+
272+
while ($this->isLocked()) {
273+
$maxCycles -= $sleep;
274+
if ($maxCycles <= 0) {
275+
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`?');
276+
}
277+
278+
usleep($sleep);
279+
}
280+
281+
return $this->lock();
282+
}
206283
}

classes/NitroCache.php

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Bnomei;
4+
5+
use Kirby\Cache\FileCache;
6+
7+
class NitroCache extends FileCache
8+
{
9+
// make a magic call to all methods of the cache
10+
public function __call($method, $args)
11+
{
12+
return call_user_func_array([Nitro::singleton()->cache(), $method], $args);
13+
}
14+
}

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "bnomei/kirby-nitro",
33
"type": "kirby-plugin",
4-
"version": "1.1.3",
4+
"version": "2.0.0",
55
"description": "Nitro speeds up the loading of content in your Kirby project.",
66
"license": "MIT",
77
"authors": [

0 commit comments

Comments
 (0)