Skip to content

Commit

Permalink
Stache Locks (#2794)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonvarga authored Nov 4, 2020
1 parent 3e2df72 commit 457e056
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 1 deletion.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"pixelfear/composer-dist-plugin": "^0.1.4",
"spatie/blink": "^1.1.2",
"statamic/stringy": "^3.1",
"symfony/lock": "^5.1",
"symfony/var-exporter": "^4.3",
"symfony/yaml": "^4.1 || ^5.1",
"ueberdosis/html-to-prosemirror": "^1.0",
Expand Down
18 changes: 18 additions & 0 deletions config/stache.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,22 @@
//
],

/*
|--------------------------------------------------------------------------
| Locking
|--------------------------------------------------------------------------
|
| In order to prevent concurrent requests from updating the Stache at
| the same and wasting resources, it will be "locked" so subsequent
| requests will have to wait until the first has been completed.
|
| https://statamic.dev/stache#locks
|
*/

'lock' => [
'enabled' => true,
'timeout' => 30,
],

];
40 changes: 40 additions & 0 deletions src/Http/Middleware/StacheLock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Statamic\Http\Middleware;

use Closure;
use Statamic\Facades\Stache;

class StacheLock
{
public function handle($request, Closure $next)
{
if (! config('statamic.stache.lock.enabled', true)) {
return $next($request);
}

$start = time();
$lock = Stache::lock('stache-warming');

while (! $lock->acquire()) {
if (time() - $start >= config('statamic.stache.lock.timeout', 30)) {
return $this->outputRefreshResponse($request);
}

sleep(1);
}

$lock->release();

return $next($request);
}

private function outputRefreshResponse($request)
{
$html = $request->ajax() || $request->wantsJson()
? __('Service Unavailable')
: sprintf('<meta http-equiv="refresh" content="1; URL=\'%s\'" />', $request->getUri());

return response($html, 503, ['Retry-After' => 1]);
}
}
1 change: 1 addition & 0 deletions src/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public function register()
protected function registerMiddlewareGroup()
{
$this->app->make(Router::class)->middlewareGroup('statamic.web', [
\Statamic\Http\Middleware\StacheLock::class,
\Statamic\Http\Middleware\Localize::class,
\Statamic\StaticCaching\Middleware\Cache::class,
]);
Expand Down
34 changes: 34 additions & 0 deletions src/Stache/NullLockStore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Statamic\Stache;

use Symfony\Component\Lock\BlockingStoreInterface;
use Symfony\Component\Lock\Key;

class NullLockStore implements BlockingStoreInterface
{
public function save(Key $key)
{
//
}

public function delete(Key $key)
{
//
}

public function exists(Key $key)
{
//
}

public function putOffExpiration(Key $key, float $ttl)
{
//
}

public function waitAndSave(Key $key)
{
//
}
}
23 changes: 22 additions & 1 deletion src/Stache/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
namespace Statamic\Stache;

use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
use Statamic\Facades\File;
use Statamic\Facades\Site;
use Statamic\Stache\Query\EntryQueryBuilder;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;

class ServiceProvider extends LaravelServiceProvider
{
public function register()
{
$this->app->singleton(Stache::class, function () {
return new Stache;
return (new Stache)->setLockFactory($this->locks());
});

$this->app->alias(Stache::class, 'stache');
Expand All @@ -35,4 +38,22 @@ public function boot()
return app($config['class'])->directory($config['directory']);
})->all());
}

private function locks()
{
if (config('statamic.stache.lock.enabled', true)) {
$store = $this->createFileLockStore();
} else {
$store = new NullLockStore;
}

return new LockFactory($store);
}

private function createFileLockStore()
{
File::makeDirectory($dir = storage_path('statamic/stache-locks'));

return new FlockStore($dir);
}
}
24 changes: 24 additions & 0 deletions src/Stache/Stache.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use Statamic\Facades\File;
use Statamic\Stache\Stores\Store;
use Statamic\Support\Str;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Wilderborn\Partyline\Facade as Partyline;

class Stache
Expand All @@ -16,6 +18,8 @@ class Stache
protected $stores;
protected $startTime;
protected $updateIndexes = true;
protected $lockFactory;
protected $locks = [];

public function __construct()
{
Expand Down Expand Up @@ -95,11 +99,15 @@ public function warm()
{
Partyline::comment('Warming Stache...');

$lock = tap($this->lock('stache-warming'))->acquire(true);

$this->startTimer();

$this->stores()->each->warm();

$this->stopTimer();

$lock->release();
}

public function instance()
Expand Down Expand Up @@ -173,4 +181,20 @@ public function shouldUpdateIndexes()
{
return $this->updateIndexes;
}

public function setLockFactory(LockFactory $lockFactory)
{
$this->lockFactory = $lockFactory;

return $this;
}

public function lock($name): LockInterface
{
if (isset($this->locks[$name])) {
return $this->locks[$name];
}

return $this->locks[$name] = $this->lockFactory->createLock($name);
}
}

0 comments on commit 457e056

Please sign in to comment.