Skip to content
This repository has been archived by the owner on Mar 3, 2020. It is now read-only.

Commit

Permalink
Fixed scoring race condition
Browse files Browse the repository at this point in the history
  • Loading branch information
gsingh93 committed Jun 21, 2016
1 parent 6ea7894 commit f706253
Showing 1 changed file with 118 additions and 68 deletions.
186 changes: 118 additions & 68 deletions src/models/Level.php
Expand Up @@ -654,106 +654,156 @@ private static function levelFromRow(Map<string, string> $row): Level {
); );
} }


private static async function withLock(
string $lock_name,
int $team_id,
(function(): Awaitable<mixed>) $thunk,
): Awaitable<mixed> {
$lock_name = sprintf('%s/%s_%d', sys_get_temp_dir(), $lock_name, $team_id);
$lock = fopen($lock_name, 'w');

if ($lock === false) {
error_log('Failed to open lock file $lock_name');
return null;
}
if (!flock($lock, LOCK_EX)) {
fclose($lock);
return null;
}

$result = await $thunk();

// Release the scoring lock
flock($lock, LOCK_UN);
fclose($lock);

return $result;
}

// Score level. Works for quiz and flags. // Score level. Works for quiz and flags.
public static async function genScoreLevel( public static async function genScoreLevel(
int $level_id, int $level_id,
int $team_id, int $team_id,
): Awaitable<bool> { ): Awaitable<bool> {
$db = await self::genDb(); $result = await self::withLock(
'score_level_lock',
$team_id,
async function(): Awaitable<mixed> use ($level_id, $team_id) {
$db = await self::genDb();


// Check if team has already scored this level // Check if team has already scored this level
$previous_score = await ScoreLog::genPreviousScore($level_id, $team_id, false); $previous_score = await ScoreLog::genPreviousScore($level_id, $team_id, false);
if ($previous_score) { if ($previous_score) {
return false; return false;
} }


$level = await self::gen($level_id); $level = await self::gen($level_id);


// Calculate points to give // Calculate points to give
$points = $level->getPoints() + $level->getBonus(); $points = $level->getPoints() + $level->getBonus();


// Adjust bonus // Adjust bonus
await self::genAdjustBonus($level_id); await self::genAdjustBonus($level_id);


// Score! // Score!
await $db->queryf( await $db->queryf(
'UPDATE teams SET points = points + %d, last_score = NOW() WHERE id = %d LIMIT 1', 'UPDATE teams SET points = points + %d, last_score = NOW() WHERE id = %d LIMIT 1',
$points, $points,
$team_id, $team_id,
); );


// Log the score... // Log the score
await ScoreLog::genLogValidScore($level_id, $team_id, $points, $level->getType()); await ScoreLog::genLogValidScore($level_id, $team_id, $points, $level->getType());

return true;
}
);


return true; return boolval($result);
} }


// Score base. // Score base.
public static async function genScoreBase( public static async function genScoreBase(
int $level_id, int $level_id,
int $team_id, int $team_id,
): Awaitable<bool> { ): Awaitable<bool> {
$db = await self::genDb(); $result = await self::withLock(

'score_base_lock',
$level = await self::gen($level_id);

// Calculate points to give
$score = await ScoreLog::genPreviousScore($level_id, $team_id, false);
if ($score) {
$points = $level->getPoints();
} else {
$points = $level->getPoints() + $level->getBonus();
}

// Score!
await $db->queryf(
'UPDATE teams SET points = points + %d, last_score = NOW() WHERE id = %d LIMIT 1',
$points,
$team_id, $team_id,
async function(): Awaitable<mixed> use ($level_id, $team_id) {
$db = await self::genDb();

$level = await self::gen($level_id);

// Calculate points to give
$score = await ScoreLog::genPreviousScore($level_id, $team_id, false);
if ($score) {
$points = $level->getPoints();
} else {
$points = $level->getPoints() + $level->getBonus();
}

// Score!
await $db->queryf(
'UPDATE teams SET points = points + %d, last_score = NOW() WHERE id = %d LIMIT 1',
$points,
$team_id,
);

// Log the score...
await ScoreLog::genLogValidScore($level_id, $team_id, $points, $level->getType());

return true;
}
); );


// Log the score... return boolval($result);
await ScoreLog::genLogValidScore($level_id, $team_id, $points, $level->getType());

return true;
} }


// Get hint. // Get hint.
public static async function genLevelHint( public static async function genLevelHint(
int $level_id, int $level_id,
int $team_id, int $team_id,
): Awaitable<?string> { ): Awaitable<?string> {
$db = await self::genDb(); $result = await self::withLock(

'hint_lock',
$level = await self::gen($level_id);
$penalty = $level->getPenalty();

// Check if team has already gotten this hint or if the team has scored this already
// If so, hint is free
$hint = await HintLog::genPreviousHint($level_id, $team_id, false);
$score = await ScoreLog::genPreviousScore($level_id, $team_id, false);
if ($hint || $score) {
$penalty = 0;
}

// Make sure team has enough points to pay
$team = await Team::genTeam($team_id);
if ($team->getPoints() < $penalty) {
return null;
}

// Adjust points
await $db->queryf(
'UPDATE teams SET points = points - %d WHERE id = %d LIMIT 1',
$penalty,
$team_id, $team_id,
async function(): Awaitable<mixed> use ($level_id, $team_id) {
$db = await self::genDb();

$level = await self::gen($level_id);
$penalty = $level->getPenalty();

// Check if team has already gotten this hint or if the team has scored this already
// If so, hint is free
$hint = await HintLog::genPreviousHint($level_id, $team_id, false);
$score = await ScoreLog::genPreviousScore($level_id, $team_id, false);
if ($hint || $score) {
$penalty = 0;
}

// Make sure team has enough points to pay
$team = await Team::genTeam($team_id);
if ($team->getPoints() < $penalty) {
return null;
}

// Adjust points
await $db->queryf(
'UPDATE teams SET points = points - %d WHERE id = %d LIMIT 1',
$penalty,
$team_id,
);

// Log the hint
await HintLog::genLogGetHint($level_id, $team_id, $penalty);

// Hint!
return $level->getHint();
}
); );


// Log the hint return $result !== null ? strval($result) : null;
await HintLog::genLogGetHint($level_id, $team_id, $penalty);

// Hint!
return $level->getHint();
} }


// Get the IP from a base level. // Get the IP from a base level.
Expand Down

0 comments on commit f706253

Please sign in to comment.