Skip to content

fix: harden warmup timer calculation and lifecycle - #37

Merged
Rushaway merged 6 commits into
masterfrom
fix/warmup-timer-lifecycle
Sep 14, 2026
Merged

Rushaway merged 6 commits into
masterfrom
fix/warmup-timer-lifecycle

Conversation

@Rushaway

@Rushaway Rushaway commented Sep 9, 2026

Copy link
Copy Markdown
Member

Audit finding — warmup timer subsystem

1. ConVars were being overwritten from InitWarmup()

InitWarmup() clamped and derived values by writing them back into the
operator's ConVars
:

g_cvDynamic.IntValue = 0;                     // invalid map size -> disables the feature for the session
g_cvDynamicRatio.IntValue = 1 / iMapSize;     // clamps the operator's configured ratio
g_cvWarmuptime.IntValue = g_cvWarmupMaxTime.IntValue; // permanently lowers the configured warmup time

After a single map with an unusual size, the server config was silently and
permanently changed until the next exec. The clamping/derivation now runs on
local copies and the result is stored in a plugin-owned g_iWarmupTime.

2. Stale dynamic warmup duration across maps

g_iDynamicWarmupTime was never reset at the top of InitWarmup(), so a map
where dynamic warmup was disabled could reuse the value computed for a previous
map. It is now reset every call.

3. Duplicate OnWarmupTimer timers

The repeating timer was created without keeping its handle. InitWarmup() runs
from OnMapStart and from the sm_warmup change hook; when it ran again
while a timer was still alive (e.g. sm_warmup 0; sm_warmup 1 in one frame),
g_bWarmup was true again by the next tick and the old timer kept running
next to the new one — the countdown then ticked twice per second and the HUD
text printed twice. The timer handle is now tracked and killed before a new one
is started, cleared in OnMapEnd, and cleared whenever the timer stops itself.

Build is validated by CI (spcomp 1.12.x). No API/behaviour change for
consumers of the natives/forward.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings September 9, 2026 16:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Timer-handle clearing in OnWarmupTimer should be guarded by if (timer == g_hWarmupTimer) to prevent a stale timer instance from clobbering the handle of a newly created timer.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR hardens TeamManager’s warmup timer subsystem by preventing server ConVars from being mutated during warmup initialization, ensuring dynamic warmup duration is recalculated safely per map, and making the repeating warmup timer lifecycle deterministic (tracked, killed/replaced, and cleared correctly).

Changes:

  • Compute warmup duration from local ConVar copies and store the resolved value in a plugin-owned g_iWarmupTime instead of writing back to ConVars.
  • Track the repeating warmup timer via g_hWarmupTimer, kill any existing timer before starting a new one, and clear the handle when warmup stops / map ends.
  • Initialize warmup from OnPluginStart() to support mid-map plugin loads.
File summaries
File Description
addons/sourcemod/scripting/TeamManager.sp Reworks warmup duration calculation and adds explicit timer-handle tracking/cleanup to avoid ConVar mutation, stale values, and duplicate timers.
Review details

Suppressed comments (1)

addons/sourcemod/scripting/TeamManager.sp:228

  • Same handle-safety issue when the warmup ends: g_hWarmupTimer should only be cleared if the timer instance being stopped is the one currently tracked, so a previous/stale timer can’t clobber a newly created handle.
	if (g_iWarmup >= iTime)
	{
		g_hWarmupTimer = INVALID_HANDLE;
		EndWarmUp();
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread addons/sourcemod/scripting/TeamManager.sp
Rushaway and others added 2 commits September 11, 2026 22:45
- Stop overwriting operator ConVars (sm_warmuptime, sm_warmuptime_dynamic,
  sm_warmup_dynamic_ratio) from InitWarmup(). The clamping/derivation now
  works on local copies, so a single bad map no longer permanently changes
  the server configuration.
- Reset the resolved warmup duration on every InitWarmup() call. The old
  g_iDynamicWarmupTime kept a stale value across maps when dynamic warmup
  was toggled.
- Track the repeating warmup timer in a handle and kill it before starting
  a new one. Toggling sm_warmup (or any other re-init) could previously
  leave two OnWarmupTimer timers running at once, double-counting the
  countdown. The handle is cleared in OnMapEnd and whenever the timer
  stops itself.
- Run InitWarmup() at the end of OnPluginStart so a mid-map plugin load
  (where OnMapStart never fires) still starts the warmup system.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
OnWarmupTimer() cleared g_hWarmupTimer unconditionally in both of its
early-return paths. If a stale callback from an old timer fired after
InitWarmup() had already replaced it with a newer one, it would wipe
out the handle of the still-running timer, reintroducing the
duplicate-timer bug the handle tracking was meant to fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Rushaway
Rushaway force-pushed the fix/warmup-timer-lifecycle branch from 9df5271 to 6346140 Compare September 11, 2026 20:46
Rushaway and others added 4 commits September 14, 2026 21:01
SourceMod already synthesizes an OnMapStart forward call for plugins
loaded after the map has started, so mid-map plugin loads are not
actually inert without special-casing. The extra InitWarmup() call in
OnPluginStart ran before the map state was reliable and just forced a
premature create-then-kill cycle on the warmup timer ahead of the real
OnMapStart-driven init.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Rushaway
Rushaway merged commit 0ab2be3 into master Sep 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants