From d678f4ff0e04455e591eae4d66e80dc8ab7ce57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sun, 29 Nov 2020 15:58:29 +0100 Subject: [PATCH 1/2] Limit max. sleep duration per loop iteration --- classes/util/Loop.php | 47 +++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/classes/util/Loop.php b/classes/util/Loop.php index e8203d00..ae1f1b9c 100644 --- a/classes/util/Loop.php +++ b/classes/util/Loop.php @@ -17,20 +17,6 @@ */ class Loop { - /** - * Minimum time that we want to wait, between lock checks. In micro seconds. - * - * @var double - */ - private const MINIMUM_WAIT_US = 1e4; // 0.01 seconds - - /** - * Maximum time that we want to wait, between lock checks. In micro seconds. - * - * @var double - */ - private const MAXIMUM_WAIT_US = 5e5; // 0.50 seconds - /** * @var int The timeout in seconds. */ @@ -92,35 +78,38 @@ public function execute(callable $code) $this->looping = true; // At this time, the lock will time out. - $deadline = microtime(true) + $this->timeout; + $deadlineTs = microtime(true) + $this->timeout; + + $minWaitSecs = 0.1e-3; // 0.1 ms + $maxWaitSecs = max(0.05, min(25, $this->timeout / 120)); // 50 ms to 25 s, based on timeout $result = null; - for ($i = 0; $this->looping && microtime(true) < $deadline; ++$i) { + for ($i = 0; microtime(true) < $deadlineTs; ++$i) { $result = $code(); if (!$this->looping) { break; } // Calculate max time remaining, don't sleep any longer than that. - $usecRemaining = intval(($deadline - microtime(true)) * 1e6); - - // We've ran out of time. - if ($usecRemaining <= 0) { - throw TimeoutException::create($this->timeout); + $remainingSecs = $deadlineTs - microtime(true); + if ($remainingSecs <= 0) { + break; } - $min = min( - (int) self::MINIMUM_WAIT_US * 1.25 ** $i, - self::MAXIMUM_WAIT_US + $minSecs = min( + $minWaitSecs * 1.5 ** $i, + max($minWaitSecs, $maxWaitSecs / 2) + ); + $maxSecs = min($minSecs * 2, $maxWaitSecs); + $sleepMicros = min( + (int)($remainingSecs * 1e6), + random_int((int)($minSecs * 1e6), (int)($maxSecs * 1e6)) ); - $max = min($min * 2, self::MAXIMUM_WAIT_US); - - $usecToSleep = min($usecRemaining, random_int((int)$min, (int)$max)); - usleep($usecToSleep); + usleep($sleepMicros); } - if (microtime(true) >= $deadline) { + if (microtime(true) >= $deadlineTs) { throw TimeoutException::create($this->timeout); } From b3d64bcdd8bdb7bfca0d9e23bdee443d770f5ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sun, 29 Nov 2020 16:33:18 +0100 Subject: [PATCH 2/2] prevent looping with 0 us on systems with unprecise microtime --- classes/util/Loop.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/util/Loop.php b/classes/util/Loop.php index ae1f1b9c..0657568e 100644 --- a/classes/util/Loop.php +++ b/classes/util/Loop.php @@ -84,7 +84,7 @@ public function execute(callable $code) $maxWaitSecs = max(0.05, min(25, $this->timeout / 120)); // 50 ms to 25 s, based on timeout $result = null; - for ($i = 0; microtime(true) < $deadlineTs; ++$i) { + for ($i = 0; ; ++$i) { $result = $code(); if (!$this->looping) { break; @@ -102,7 +102,7 @@ public function execute(callable $code) ); $maxSecs = min($minSecs * 2, $maxWaitSecs); $sleepMicros = min( - (int)($remainingSecs * 1e6), + max(10, (int)($remainingSecs * 1e6)), random_int((int)($minSecs * 1e6), (int)($maxSecs * 1e6)) );