Merge pull request #148 from hdmaniak2/fix-market-race-condition2

This commit is contained in:
Catalin Novgorodschi
2026-03-27 07:50:32 +02:00
committed by GitHub
2 changed files with 49 additions and 4 deletions
+18
View File
@@ -5599,6 +5599,24 @@ References: User ID/Message ID, Mode
return self::$merchantsUseCountCache[$vid];
}
/***************************
Function to acquire/release MySQL advisory lock for merchant operations
Prevents race conditions when sending merchants concurrently
***************************/
function getMerchantLock($vid, $timeout = 10)
{
$lockName = TB_PREFIX . 'merchant_' . (int)$vid;
$result = mysqli_query($this->dblink, "SELECT GET_LOCK('$lockName', $timeout) AS lock_acquired");
$row = mysqli_fetch_assoc($result);
return $row['lock_acquired'] == 1;
}
function releaseMerchantLock($vid)
{
$lockName = TB_PREFIX . 'merchant_' . (int)$vid;
mysqli_query($this->dblink, "SELECT RELEASE_LOCK('$lockName')");
}
function getMovement($type, $village, $mode, $use_cache = true) {
$array_passed = is_array($village);
+31 -4
View File
@@ -114,6 +114,10 @@ class Market
$resource = [$wtrans, $ctrans, $itrans, $crtrans];
$reqMerc = ceil((array_sum($resource) - 0.1) / $this->maxcarry);
// Acquire merchant lock to prevent race condition
$database->getMerchantLock($village->wid);
$this->used = $database->totalMerchantUsed($village->wid, false);
if($this->merchantAvail() > 0 && $reqMerc <= $this->merchantAvail())
{
$id = $post['getwref'];
@@ -129,10 +133,15 @@ class Market
$logging->addMarketLog($village->wid, 1, [$resource[0], $resource[1], $resource[2], $resource[3], $id]);
}
}
$database->releaseMerchantLock($village->wid);
header("Location: build.php?id=".$post['id']);
exit;
}
else $form->addError("error", TOO_FEW_MERCHANTS);
else
{
$database->releaseMerchantLock($village->wid);
$form->addError("error", TOO_FEW_MERCHANTS);
}
}
else $form->addError("error", TOO_FEW_RESOURCES);
}
@@ -197,6 +206,10 @@ class Market
if(($wood+$clay+$iron+$crop) > $this->maxcarry*$reqMerc) $reqMerc += 1;
}
// Acquire merchant lock to prevent race condition
$database->getMerchantLock($village->wid);
$this->used = $database->totalMerchantUsed($village->wid, false);
if($this->merchantAvail() > 0 && $reqMerc <= $this->merchantAvail())
{
if($database->modifyResource($village->wid,$wood,$clay,$iron,$crop,0))
@@ -207,12 +220,14 @@ class Market
$database->addMarket($village->wid,$post['rid1'],$post['m1'],$post['rid2'],$post['m2'],$time,$alliance,$reqMerc,0);
}
// Enough merchants
$database->releaseMerchantLock($village->wid);
header("Location: build.php?id=".$post['id']."&t=2");
exit;
}
else
{
// Not enough merchants
$database->releaseMerchantLock($village->wid);
header("Location: build.php?id=".$post['id']."&t=2&e3");
exit;
}
@@ -254,12 +269,23 @@ class Market
{
header("Location: build.php?id=".$get['id']."&t=1&e2");
exit;
} // We don't have enough merchants
elseif($reqMerc > $this->merchantAvail()){
} // We don't have enough merchants (initial check before lock)
elseif($reqMerc > $this->merchantAvail()){
header("Location: build.php?id=".$get['id']."&t=1&e3");
exit;
}
// Acquire merchant lock to prevent race condition
$database->getMerchantLock($village->wid);
$this->used = $database->totalMerchantUsed($village->wid, false);
if($reqMerc > $this->merchantAvail())
{
$database->releaseMerchantLock($village->wid);
header("Location: build.php?id=".$get['id']."&t=1&e3");
exit;
}
$myresource = $hisresource = [ 1=> 0, 0, 0, 0];
$myresource[$infoarray['wtype']] = $infoarray['wamt'];
$mysendid = $database->sendResource($myresource[1],$myresource[2],$myresource[3],$myresource[4],$reqMerc,0);
@@ -284,6 +310,7 @@ class Market
$database->setMarketAcc($get['g']);
$database->removeAcceptedOffer($get['g']);
$logging->addMarketLog($village->wid, 2, [$infoarray['vref'], $get['g']]);
$database->releaseMerchantLock($village->wid);
header("Location: build.php?id=" . $get['id']);
exit;
}