Skip to content

Instantly share code, notes, and snippets.

@staabm
Last active February 12, 2023 20:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save staabm/ce225d4b01d7b4e2560848646e13d6fc to your computer and use it in GitHub Desktop.
Save staabm/ce225d4b01d7b4e2560848646e13d6fc to your computer and use it in GitHub Desktop.
Drop in replacement for the native php file session handler with lock-timeout support. Work arround PHP Bug #72345.
<?php
class FileSessionHandler implements SessionHandlerInterface
{
/**
* time in milliseconds how fast to poll the filesystem for a file-lock
* in case it cannot be acquired / is (b)locked by other resources.
*
* @var int
* @internal
*/
const LOCK_POLL_INTERVAL = 30;
private $savePath;
private $fd;
private $lockTimeout;
/**
* @param int $lockTimeout time in milliseconds how long the handler tries to acquire a file-lock
*/
public function __construct($lockTimeout) {
$this->lockTimeout = $lockTimeout;
}
public function open($savePath, $sessionName)
{
$this->savePath = $savePath;
if (!is_dir($this->savePath)) {
if (!@mkdir($this->savePath, 0770)) {
throw new Exception('Unable to create session dir '.$this->savePath);
}
}
return true;
}
public function close()
{
if (!$this->fd) return false;
flock($fd, LOCK_UN | LOCK_NB);
return fclose($this->fd);
}
public function read($id)
{
$this->fd = fopen("$this->savePath/sess_$id", "c+");
if (!$this->fd) {
throw new Exception("Unable to create session file $this->savePath/sess_$id");
}
$timeRemaining = $this->lockTimeout;
$sleepMillis = self::LOCK_POLL_INTERVAL;
while (!flock($this->fd, LOCK_EX | LOCK_NB)) {
usleep($sleepMillis * 1000);
$timeRemaining -= $sleepMillis;
if ($timeRemaining <= 0) {
fclose($this->fd);
throw new Exception('Unable to acquire session lock within '. $this->lockTimeout .'ms!');
}
}
return (string)@stream_get_contents($this->fd);
}
public function write($id, $data)
{
if (!$this->fd) return false;
rewind($this->fd);
ftruncate($this->fd, 0);
return fwrite($this->fd, $data) !== false;
}
public function destroy($id)
{
$file = "$this->savePath/sess_$id";
if (file_exists($file)) {
unlink($file);
}
return true;
}
public function gc($maxlifetime)
{
// we assume GC will be managed with different mechanics, e.g CRON
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment