Merge pull request #677 from Zankaria/refactor-queue

Refactor queue
This commit is contained in:
Lorenzo Yario 2024-04-03 12:17:50 -07:00 committed by GitHub
commit 98650ec2e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 188 additions and 81 deletions

View File

@ -2918,7 +2918,20 @@ function generation_strategy($fun, $array=array()) { global $config;
return 'rebuild'; return 'rebuild';
case 'defer': case 'defer':
// Ok, it gets interesting here :) // Ok, it gets interesting here :)
get_queue('generate')->push(serialize(array('build', $fun, $array, $action))); $queue = Queues::get_queue($config, 'generate');
if ($queue === false) {
if ($config['syslog']) {
_syslog(LOG_ERR, "Could not initialize generate queue, falling back to immediate rebuild strategy");
}
return 'rebuild';
}
$ret = $queue->push(serialize(array('build', $fun, $array, $action)));
if ($ret === false) {
if ($config['syslog']) {
_syslog(LOG_ERR, "Could not push item in the queue, falling back to immediate rebuild strategy");
}
return 'rebuild';
}
return 'ignore'; return 'ignore';
case 'build_on_load': case 'build_on_load':
return 'delete'; return 'delete';

View File

@ -1,39 +1,84 @@
<?php <?php
class Lock {
function __construct($key) { global $config; class Locks {
if ($config['lock']['enabled'] == 'fs') { private static function filesystem(string $key): Lock|false {
$key = str_replace('/', '::', $key); $key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key); $key = str_replace("\0", '', $key);
$this->f = fopen("tmp/locks/$key", "w"); $fd = fopen("tmp/locks/$key", "w");
} if ($fd === false) {
return false;
} }
// Get a shared lock return new class($fd) implements Lock {
function get($nonblock = false) { global $config; // Resources have no type in php.
if ($config['lock']['enabled'] == 'fs') { private mixed $f;
function __construct($fd) {
$this->f = $fd;
}
public function get(bool $nonblock = false): Lock|false {
$wouldblock = false; $wouldblock = false;
flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock); flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock);
if ($nonblock && $wouldblock) return false; if ($nonblock && $wouldblock) {
return false;
} }
return $this; return $this;
} }
// Get an exclusive lock public function get_ex(bool $nonblock = false): Lock|false {
function get_ex($nonblock = false) { global $config;
if ($config['lock']['enabled'] == 'fs') {
$wouldblock = false; $wouldblock = false;
flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock); flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock);
if ($nonblock && $wouldblock) return false; if ($nonblock && $wouldblock) {
return false;
} }
return $this; return $this;
} }
// Free a lock public function free(): Lock {
function free() { global $config;
if ($config['lock']['enabled'] == 'fs') {
flock($this->f, LOCK_UN); flock($this->f, LOCK_UN);
}
return $this; return $this;
} }
};
}
/**
* No-op. Can be used for mocking.
*/
public static function none(): Lock|false {
return new class() implements Lock {
public function get(bool $nonblock = false): Lock|false {
return $this;
}
public function get_ex(bool $nonblock = false): Lock|false {
return $this;
}
public function free(): Lock {
return $this;
}
};
}
public static function get_lock(array $config, string $key): Lock|false {
if ($config['lock']['enabled'] == 'fs') {
return self::filesystem($key);
} else {
return self::none();
}
}
}
interface Lock {
// Get a shared lock
public function get(bool $nonblock = false): Lock|false;
// Get an exclusive lock
public function get_ex(bool $nonblock = false): Lock|false;
// Free a lock
public function free(): Lock;
} }

View File

@ -1,49 +1,98 @@
<?php <?php
class Queue { class Queues {
function __construct($key) { global $config; private static $queues = array();
if ($config['queue']['enabled'] == 'fs') {
$this->lock = new Lock($key);
/**
* This queue implementation isn't actually ordered, so it works more as a "bag".
*/
private static function filesystem(string $key, Lock $lock): Queue {
$key = str_replace('/', '::', $key); $key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key); $key = str_replace("\0", '', $key);
$this->key = "tmp/queue/$key/"; $key = "tmp/queue/$key/";
}
return new class($key, $lock) implements Queue {
private Lock $lock;
private string $key;
function __construct(string $key, Lock $lock) {
$this->lock = $lock;
$this->key = $key;
} }
function push($str) { global $config; public function push(string $str): bool {
if ($config['queue']['enabled'] == 'fs') {
$this->lock->get_ex(); $this->lock->get_ex();
file_put_contents($this->key.microtime(true), $str); $ret = file_put_contents($this->key . microtime(true), $str);
$this->lock->free(); $this->lock->free();
} return $ret !== false;
return $this;
} }
function pop($n = 1) { global $config; public function pop(int $n = 1): array {
if ($config['queue']['enabled'] == 'fs') {
$this->lock->get_ex(); $this->lock->get_ex();
$dir = opendir($this->key); $dir = opendir($this->key);
$paths = array(); $paths = array();
while ($n > 0) { while ($n > 0) {
$path = readdir($dir); $path = readdir($dir);
if ($path === FALSE) break; if ($path === false) {
elseif ($path == '.' || $path == '..') continue; break;
else { $paths[] = $path; $n--; } } elseif ($path == '.' || $path == '..') {
continue;
} else {
$paths[] = $path;
$n--;
} }
}
$out = array(); $out = array();
foreach ($paths as $v) { foreach ($paths as $v) {
$out[] = file_get_contents($this->key . $v); $out[] = file_get_contents($this->key . $v);
unlink($this->key . $v); unlink($this->key . $v);
} }
$this->lock->free(); $this->lock->free();
return $out; return $out;
} }
};
}
/**
* No-op. Can be used for mocking.
*/
public static function none(): Queue {
return new class() implements Queue {
public function push(string $str): bool {
return true;
}
public function pop(int $n = 1): array {
return array();
}
};
}
public static function get_queue(array $config, string $name): Queue|false {
if (!isset(self::$queues[$name])) {
if ($config['queue']['enabled'] == 'fs') {
$lock = Locks::get_lock($config, $name);
if ($lock === false) {
return false;
}
self::$queues[$name] = self::filesystem($name, $lock);
} else {
self::$queues[$name] = self::none();
}
}
return self::$queues[$name];
} }
} }
// Don't use the constructor. Use the get_queue function. interface Queue {
$queues = array(); // Push a string in the queue.
public function push(string $str): bool;
function get_queue($name) { global $queues; // Get a string from the queue.
return $queues[$name] = isset ($queues[$name]) ? $queues[$name] : new Queue($name); public function pop(int $n = 1): array;
} }