From 07437f705b52888cea0fa282401b6df15ab860ce Mon Sep 17 00:00:00 2001 From: David Flatz Date: Thu, 11 May 2023 09:31:37 +0200 Subject: Use flock for locking to prevent stale locks, when process gets killed --- it_url.class | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/it_url.class b/it_url.class index 014610c..cfa2bef 100644 --- a/it_url.class +++ b/it_url.class @@ -605,7 +605,7 @@ static function get_cache($p = array()) { $fileexists = $filemtime !== true; - if ($lock = !$p['lock'] ?: it_url::_lock($path)) + if ($lock = !$p['lock'] ?: it_url::_lock($path, $p)) { # Touch existing file to prevent locking other getters while refreshing if ($fileexists) @@ -657,7 +657,7 @@ static function get_cache($p = array()) if ($filemtime = $isnewfile ? true : it_url::_expired($path, $p['maxage'])) # Outdated(non-zero int) or non-existant(true)? { - if ($lock = !$p['lock'] ?: it_url::_lock($path)) + if ($lock = !$p['lock'] ?: it_url::_lock($path, $p)) { # Touch existing file to prevent locking other getters while refreshing if ($filemtime !== true) @@ -759,10 +759,20 @@ static function _expired($path, $maxage, $randomexpire = 0) * @param $path File to lock * @return Lock handle if successfully locked file */ -static function _lock($path) +static function _lock($path, $p = []) { - $force = EDC('nocache') || (($mtime = @filemtime("$path.lock")) && (time() - $mtime > 30)); # expire forgotten locks - return @it::fopen("$path.lock", $force ? "w" : "x"); + if (!($fh = it::fopen("$path.lock", "w"))) + return false; + + if (!flock($fh, LOCK_EX | LOCK_NB)) + { + if (($mtime = @filemtime("$path.lock")) && (time() - $mtime > 30)) + it::error((array)$p['it_error'] + ['title' => "stale lock epired for $path"]); # FIXME 2023-07 DF remove stale lock expiration if never triggered + else + return false; + } + + return $fh; } /** @@ -790,16 +800,15 @@ static function _waitforlockedfile($path, $p) $sleeptime = 0.1; # seconds to wait per pass # wait until cache is ready, then read from cache - for ($maxpasses = $p['timeout'] / $sleeptime, $passes = 0; ($lockedbyother = file_exists("$path.lock")) && ($passes < $maxpasses); ++$passes) - { + for ($maxpasses = $p['timeout'] / $sleeptime, $passes = 0; !($lock = self::_lock("$path", $p)) && ($passes < $maxpasses); ++$passes) usleep($sleeptime * 1000000); - clearstatcache(); - } - if ($lockedbyother) + if (!$lock) it::error((array)$p['it_error'] + ['title' => ($passes < $maxpasses ? "error getting url" : "timeout") . " in it_url::get_cache(): url={$p['url']}, passes=$passes, maxpasses=$maxpasses, path=$path"]); + else + self::_unlock($path, $lock); - return !$lockedbyother && file_exists($path); + return $lock && file_exists($path); } /** -- cgit v1.2.3