diff options
| author | Lester Caine <lester@lsces.co.uk> | 2026-05-21 12:50:09 +0100 |
|---|---|---|
| committer | Lester Caine <lester@lsces.co.uk> | 2026-05-21 12:50:09 +0100 |
| commit | 0b5a8ac7b3bc8422f01e959b522d041ea021324a (patch) | |
| tree | ddfed395918275a8abc7af795f71acc60c49d42c | |
| parent | b5dcd70d18485812eb58e8e4e91bad1c2cc11e43 (diff) | |
| download | liberty-0b5a8ac7b3bc8422f01e959b522d041ea021324a.tar.gz liberty-0b5a8ac7b3bc8422f01e959b522d041ea021324a.tar.bz2 liberty-0b5a8ac7b3bc8422f01e959b522d041ea021324a.zip | |
Fix addHit() race condition and stuck Firebird transaction
Replaced SELECT-then-INSERT/UPDATE with UPDATE-first pattern: attempt
UPDATE and only INSERT if zero rows were affected. Eliminates the race
window where two concurrent requests both see no row and both try to
INSERT, which left an uncommitted Firebird transaction blocking reads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rwxr-xr-x | includes/classes/LibertyContent.php | 18 |
1 files changed, 8 insertions, 10 deletions
diff --git a/includes/classes/LibertyContent.php b/includes/classes/LibertyContent.php index 52fe1af..932cc71 100755 --- a/includes/classes/LibertyContent.php +++ b/includes/classes/LibertyContent.php @@ -1824,17 +1824,15 @@ class LibertyContent extends LibertyBase implements BitCacheable { global $gBitUser,$gBitSystem; if( empty( $_REQUEST['post_comment_submit'] ) && empty( $_REQUEST['post_comment_request'] ) ) { if( BitBase::verifyId( $this->mContentId ) && (( $gBitUser->isRegistered() && !$this->isOwner() ) || ( $gBitUser->getField( 'user_id' ) == ANONYMOUS_USER_ID )) && ( $gBitSystem->isFeatureActive( 'users_count_admin_pageviews' ) || !$gBitUser->isAdmin() ) ) { - if( $this->mDb->getOne( "SELECT `content_id` FROM `".BIT_DB_PREFIX."liberty_content_hits` WHERE `content_id`=?", [ $this->mContentId ])) { - $query = "UPDATE `".BIT_DB_PREFIX."liberty_content_hits` SET `hits`=`hits`+1, `last_hit`= ? WHERE `content_id` = ?"; - } else { - $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_content_hits` ( `hits`, `last_hit`, `content_id` ) VALUES ( ?,?,? )"; - $bindVars[] = 1; + $now = $gBitSystem->getUTCTime(); + try { + $this->mDb->query( "UPDATE `".BIT_DB_PREFIX."liberty_content_hits` SET `hits`=`hits`+1, `last_hit`=? WHERE `content_id`=?", [ $now, $this->mContentId ] ); + if( $this->mDb->Affected_Rows() == 0 ) { + $this->mDb->query( "INSERT INTO `".BIT_DB_PREFIX."liberty_content_hits` (`hits`,`last_hit`,`content_id`) VALUES (1,?,?)", [ $now, $this->mContentId ] ); + } + } catch( \Exception $e ) { + // Race: concurrent request inserted between our UPDATE (0 rows) and INSERT — harmless } - $bindVars[] = $gBitSystem->getUTCTime(); - $bindVars[] = $this->mContentId; - $this->StartTrans(); - $result = $this->mDb->query( $query, $bindVars ); - $this->CompleteTrans(); } } return true; |
