Skip to content

Commit

Permalink
Fix: Revamp db session storage to work in exclusive locking mode like…
Browse files Browse the repository at this point in the history
… native file session storage works
  • Loading branch information
Shadow243 committed Nov 25, 2024
1 parent e2f4851 commit 2657f72
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 2 deletions.
2 changes: 1 addition & 1 deletion config/database.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
| CREATE TABLE hm_user_session (hm_id varchar(250) primary key not null, data text, date timestamp);
|
| MySQL or SQLite:
| CREATE TABLE hm_user_session (hm_id varchar(180), data longblob, date timestamp, primary key (hm_id));
| CREATE TABLE hm_user_session (hm_id varchar(180), data longblob, lock INTEGER DEFAULT 0, date timestamp, primary key (hm_id));
|
|
| DB Authentication
Expand Down
106 changes: 106 additions & 0 deletions lib/session_db.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ class Hm_DB_Session extends Hm_PHP_Session {
/* DB handle */
protected $dbh;

/*
* Database driver type from site config
*/
private $db_driver;

/*
* Timeout for acquiring locks (seconds)
*/
private $lock_timeout = 10;

/**
* Create a new session
* @return boolean|integer|array
Expand All @@ -31,6 +41,7 @@ public function insert_session_row() {
* @return bool true on success
*/
public function connect() {
$this->db_driver = $this->site_config->get('db_driver', false);
return ($this->dbh = Hm_DB::connect($this->site_config)) ? true : false;
}

Expand Down Expand Up @@ -66,12 +77,17 @@ public function start_new($request) {
*/
public function start_existing($key) {
$this->session_key = $key;
if (!$this->acquire_lock($key)) {
Hm_Debug::add('DB SESSION: Failed to acquire lock');
return;
}
$data = $this->get_session_data($key);
if (is_array($data)) {
Hm_Debug::add('LOGGED IN');
$this->active = true;
$this->data = $data;
}
$this->release_lock($key);
}

/**
Expand Down Expand Up @@ -169,4 +185,94 @@ public function db_start($request) {
}
}
}

/**
* Acquire a lock for the session (unified for all DB types)
* @param string $key session key
* @return bool true if lock acquired, false otherwise
*/
private function acquire_lock($key) {
$lock_name = 'session_lock_' . substr(hash('sha256', $key), 0, 51);
$query = '';
$params = [];

switch ($this->db_driver) {
case 'mysql':
$query = 'SELECT GET_LOCK(:lock_name, :timeout)';
$params = [':lock_name' => $lock_name, ':timeout' => $this->lock_timeout];
break;

case 'pgsql':
$query = 'SELECT pg_try_advisory_lock(:hash_key)';
$params = [':hash_key' => crc32($lock_name)];
break;

case 'sqlite':
$query = 'UPDATE hm_user_session SET lock=1 WHERE hm_id=? AND lock=0';
$params = [$key];
break;

default:
Hm_Debug::add('DB SESSION: Unsupported db_driver for locking: ' . $this->db_driver);
return false;
}

$result = Hm_DB::execute($this->dbh, $query, $params);
if ($this->db_driver == 'mysql') {
return isset($result['GET_LOCK(?, ?)']) && $result['GET_LOCK(?, ?)'] == 1;
}
if ($this->db_driver == 'pgsql') {
return isset($result['pg_try_advisory_lock']) && $result['pg_try_advisory_lock'] === true;
}

if ($this->db_driver == 'sqlite') {
return isset($result[0]) && $result[0] == 1;
}
return false;
}

/**
* Release a lock for the session (unified for all DB types)
* @param string $key session key
* @return bool true if lock released, false otherwise
*/
private function release_lock($key) {
$query = '';
$params = [];

$lock_name = "session_lock_" . substr(hash('sha256', $key), 0, 51);
switch ($this->db_driver) {
case 'mysql':
$query = 'SELECT RELEASE_LOCK(:lock_name)';
$params = [':lock_name' => $lock_name];
break;

case 'pgsql':
$query = 'SELECT pg_advisory_unlock(:hash_key)';
$params = [':hash_key' => crc32($lock_name)];
break;

case 'sqlite':
$query = 'UPDATE hm_user_session SET lock=0 WHERE hm_id=?';
$params = [$key];
break;

default:
Hm_Debug::add('DB SESSION: Unsupported db_driver for unlocking: ' . $this->db_driver);
return false;
}
$result = Hm_DB::execute($this->dbh, $query, $params);
if ($this->db_driver == 'mysql') {
return isset($result['GET_LOCK(?, ?)']) && $result['GET_LOCK(?, ?)'] == 1;
}
if ($this->db_driver == 'pgsql') {
return isset($result['pg_advisory_unlock']) && $result['pg_advisory_unlock'] === true;
}

if ($this->db_driver == 'sqlite') {
return isset($result[0]) && $result[0] == 1;
}
Hm_Debug::add('DB SESSION: Lock release failed. Query: ' . $query . ' Parameters: ' . json_encode($params));
return false;
}
}
5 changes: 4 additions & 1 deletion scripts/setup_database.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,11 @@
if (strcasecmp($session_type, 'DB')==0) {
printf("Creating database table hm_user_session ...\n");

if ($db_driver == 'mysql' || $db_driver == 'sqlite') {
if ($db_driver == 'mysql') {
$stmt = "{$create_table} hm_user_session (hm_id varchar(255), data longblob, date timestamp, primary key (hm_id));";
} elseif($db_driver == 'sqlite') {
//0 means unlocked, 1 means locked
$stmt = "{$create_table} hm_user_session (hm_id varchar(255), data longblob, lock INTEGER DEFAULT 0, date timestamp, primary key (hm_id));";
} elseif ($db_driver == 'pgsql') {
$stmt = "{$create_table} hm_user_session (hm_id varchar(255) primary key not null, data text, date timestamp);";
} else {
Expand Down

0 comments on commit 2657f72

Please sign in to comment.