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]; 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) { function getMovement($type, $village, $mode, $use_cache = true) {
$array_passed = is_array($village); $array_passed = is_array($village);
+31 -4
View File
@@ -114,6 +114,10 @@ class Market
$resource = [$wtrans, $ctrans, $itrans, $crtrans]; $resource = [$wtrans, $ctrans, $itrans, $crtrans];
$reqMerc = ceil((array_sum($resource) - 0.1) / $this->maxcarry); $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()) if($this->merchantAvail() > 0 && $reqMerc <= $this->merchantAvail())
{ {
$id = $post['getwref']; $id = $post['getwref'];
@@ -129,10 +133,15 @@ class Market
$logging->addMarketLog($village->wid, 1, [$resource[0], $resource[1], $resource[2], $resource[3], $id]); $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']); header("Location: build.php?id=".$post['id']);
exit; 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); else $form->addError("error", TOO_FEW_RESOURCES);
} }
@@ -197,6 +206,10 @@ class Market
if(($wood+$clay+$iron+$crop) > $this->maxcarry*$reqMerc) $reqMerc += 1; 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($this->merchantAvail() > 0 && $reqMerc <= $this->merchantAvail())
{ {
if($database->modifyResource($village->wid,$wood,$clay,$iron,$crop,0)) 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); $database->addMarket($village->wid,$post['rid1'],$post['m1'],$post['rid2'],$post['m2'],$time,$alliance,$reqMerc,0);
} }
// Enough merchants // Enough merchants
$database->releaseMerchantLock($village->wid);
header("Location: build.php?id=".$post['id']."&t=2"); header("Location: build.php?id=".$post['id']."&t=2");
exit; exit;
} }
else else
{ {
// Not enough merchants // Not enough merchants
$database->releaseMerchantLock($village->wid);
header("Location: build.php?id=".$post['id']."&t=2&e3"); header("Location: build.php?id=".$post['id']."&t=2&e3");
exit; exit;
} }
@@ -254,12 +269,23 @@ class Market
{ {
header("Location: build.php?id=".$get['id']."&t=1&e2"); header("Location: build.php?id=".$get['id']."&t=1&e2");
exit; exit;
} // We don't have enough merchants } // We don't have enough merchants (initial check before lock)
elseif($reqMerc > $this->merchantAvail()){ elseif($reqMerc > $this->merchantAvail()){
header("Location: build.php?id=".$get['id']."&t=1&e3"); header("Location: build.php?id=".$get['id']."&t=1&e3");
exit; 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 = $hisresource = [ 1=> 0, 0, 0, 0];
$myresource[$infoarray['wtype']] = $infoarray['wamt']; $myresource[$infoarray['wtype']] = $infoarray['wamt'];
$mysendid = $database->sendResource($myresource[1],$myresource[2],$myresource[3],$myresource[4],$reqMerc,0); $mysendid = $database->sendResource($myresource[1],$myresource[2],$myresource[3],$myresource[4],$reqMerc,0);
@@ -284,6 +310,7 @@ class Market
$database->setMarketAcc($get['g']); $database->setMarketAcc($get['g']);
$database->removeAcceptedOffer($get['g']); $database->removeAcceptedOffer($get['g']);
$logging->addMarketLog($village->wid, 2, [$infoarray['vref'], $get['g']]); $logging->addMarketLog($village->wid, 2, [$infoarray['vref'], $get['g']]);
$database->releaseMerchantLock($village->wid);
header("Location: build.php?id=" . $get['id']); header("Location: build.php?id=" . $get['id']);
exit; exit;
} }