8000 [HttpKernel] Use flock() for HttpCache's lock files · l-vo/symfony@2668edd · GitHub
[go: up one dir, main page]

Skip to content

Commit 2668edd

Browse files
mpdudenicolas-grekas
authored andcommitted
[HttpKernel] Use flock() for HttpCache's lock files
1 parent c4069a4 commit 2668edd

File tree

3 files changed

+89
-45
lines changed

3 files changed

+89
-45
lines changed

src/Symfony/Component/HttpKernel/HttpCache/Store.php

Lines changed: 82 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Store implements StoreInterface
3838
public function __construct($root)
3939
{
4040
$this->root = $root;
41-
if (!is_dir($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) {
41+
if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) {
4242
throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root));
4343
}
4444
$this->keyCache = new \SplObjectStorage();
@@ -52,44 +52,40 @@ public function cleanup()
5252
{
5353
// unlock everything
5454
foreach ($this->locks as $lock) {
55-
if (file_exists($lock)) {
56-
@unlink($lock);
57-
}
55+
flock($lock, LOCK_UN);
56+
fclose($lock);
5857
}
5958

60-
$error = error_get_last();
61-
if (1 === $error['type'] && false === headers_sent()) {
62-
// send a 503
63-
header('HTTP/1.0 503 Service Unavailable');
64-
header('Retry-After: 10');
65-
echo '503 Service Unavailable';
66-
}
59+
$this->locks = array();
6760
}
6861

6962
/**
70-
* Locks the cache for a given Request.
63+
* Tries to lock the cache for a given Request, without blocking.
7164
*
7265
* @param Request $request A Request instance
7366
*
7467
* @return bool|string true if the lock is acquired, the path to the current lock otherwise
7568
*/
7669
public function lock(Request $request)
7770
{
78-
$path = $this->getPath($this->getCacheKey($request).'.lck');
79-
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
80-
return false;
81-
}
71+
$key = $this->getCacheKey($request);
8272

83-
$lock = @fopen($path, 'x');
84-
if (false !== $lock) {
85-
fclose($lock);
73+
if (!isset($this->locks[$key])) {
74+
$path = $this->getPath($key);
75+
if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
76+
return $path;
77+
}
78+
$h = fopen($path, 'cb');
79+
if (!flock($h, LOCK_EX | LOCK_NB)) {
80+
fclose($h);
8681

87-
$this->locks[] = $path;
82+
return $path;
83+
}
8884

89-
return true;
85+
$this->locks[$key] = $h;
9086
}
9187

92-
return !file_exists($path) ?: $path;
88+
return true;
9389
}
9490

9591
/**
@@ -101,17 +97,37 @@ public function lock(Request $request)
10197
*/
10298
public function unlock(Request $request)
10399
{
104-
$file = $this->getPath($this->getCacheKey($request).'.lck');
100+
$key = $this->getCacheKey($request);
101+
102+
if (isset($this->locks[$key])) {
103+
flock($this->locks[$key], LOCK_UN);
104+
fclose($this->locks[$key]);
105+
unset($this->locks[$key]);
106+
107+
return true;
108+
}
105109

106-
return is_file($file) ? @unlink($file) : false;
110+
return false;
107111
}
108112

109113
public function isLocked(Request $request)
110114
{
111-
$path = $this->getPath($this->getCacheKey($request).'.lck');
112-
clearstatcache(true, $path);
115+
$key = $this->getCacheKey($request);
116+
117+
if (isset($this->locks[$key])) {
118+
return true; // shortcut if lock held by this process
119+
}
120+
121+
if (!file_exists($path = $this->getPath($key))) {
122+
return false;
123+
}
113124

114-
return is_file($path);
125+
$h = fopen($path, 'rb');
126+
flock($h, LOCK_EX | LOCK_NB, $wouldBlock);
127+
flock($h, LOCK_UN); // release the lock we just acquired
128+
fclose($h);
129+
130+
return (bool) $wouldBlock;
115131
}
116132

117133
/**
@@ -144,7 +160,7 @@ public function lookup(Request $request)
144160
}
145161

146162
list($req, $headers) = $match;
147-
if (is_file($body = $this->getPath($headers['x-content-digest'][0]))) {
163+
if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) {
148164
return $this->restoreResponse($headers, $body);
149165
}
150166

@@ -291,7 +307,7 @@ private function requestsMatch($vary, $env1, $env2)
291307
*/
292308
private function getMetadata($key)
293309
{
294-
if (false === $entries = $this->load($key)) {
310+
if (!$entries = $this->load($key)) {
295311
return array();
296312
}
297313

@@ -307,7 +323,15 @@ private function getMetadata($key)
307323
*/
308324
public function purge($url)
309325
{
310-
if (is_file($path = $this->getPath($this->getCacheKey(Request::create($url))))) {
326+
$key = $this->getCacheKey(Request::create($url));
327+
328+
if (isset($this->locks[$key])) {
329+
flock($this->locks[$key], LOCK_UN);
330+
fclose($this->locks[$key]);
331+
unset($this->locks[$key]);
332+
}
333+
334+
if (file_exists($path = $this->getPath($key))) {
311335
unlink($path);
312336

313337
return true;
@@ -327,7 +351,7 @@ private function load($key)
327351
{
328352
$path = $this->getPath($key);
329353

330-
return is_file($path) ? file_get_contents($path) : false;
354+
return file_exists($path) ? file_get_contents($path) : false;
331355
}
332356

333357
/**
@@ -341,23 +365,36 @@ private function load($key)
341365
private function save($key, $data)
342366
{
343367
$path = $this->getPath($key);
344-
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
345-
return false;
346-
}
347368

348-
$tmpFile = tempnam(dirname($path), basename($path));
349-
if (false === $fp = @fopen($tmpFile, 'wb')) {
350-
return false;
351-
}
352-
@fwrite($fp, $data);
353-
@fclose($fp);
369+
if (isset($this->locks[$key])) {
370+
$fp = $this->locks[$key];
371+
@ftruncate($fp, 0);
372+
@fseek($fp, 0);
373+
$len = @fwrite($fp, $data);
374+
if (strlen($data) !== $len) {
375+
@ftruncate($fp, 0);
354376

355-
if ($data != file_get_contents($tmpFile)) {
356-
return false;
357-
}
377+
return false;
378+
}
379+
} else {
380+
if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
381+
return false;
382+
}
358383

359-
if (false === @rename($tmpFile, $path)) {
360-
return false;
384+
$tmpFile = tempnam(dirname($path), basename($path));
385+
if (false === $fp = @fopen($tmpFile, 'wb')) {
386+
return false;
387+
}
388+
@fwrite($fp, $data);
389+
@fclose($fp);
390+
391+
if ($data != file_get_contents($tmpFile)) {
392+
return false;
393+
}
394+
395+
if (false === @rename($tmpFile, $path)) {
396+
return false;
397+
}
361398
}
362399

363400
@chmod($path, 0666 & ~umask());

src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ protected function setUp()
5050

5151
protected function tearDown()
5252
{
53+
if ($this->cache) {
54+
$this->cache->getStore()->cleanup();
55+
}
5356
$this->kernel = null;
5457
$this->cache = null;
5558
$this->caches = null;

src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ class StoreTest extends \PHPUnit_Framework_TestCase
1919
{
2020
protected $request;
2121
protected $response;
22+
23+
/**
24+
* @var Store
25+
*/
2226
protected $store;
2327

2428
protected function setUp()

0 commit comments

Comments
 (0)
0