Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions hasura/functions/match/veto/auto_pick_expired_veto.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
CREATE OR REPLACE FUNCTION public.auto_pick_expired_veto(
_match_id uuid DEFAULT NULL,
_expected_pick_count int DEFAULT NULL
) RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
_match matches;
_lineup_id uuid;
_pick_type text;
_map_id uuid;
_side text;
_region text;
_available_regions text[];
_regions text[];
_picked boolean;
BEGIN
-- FOR UPDATE SKIP LOCKED so a slow iteration never blocks (or gets blocked
-- by) a real player pick landing on the same match row concurrently.
FOR _match IN
SELECT * FROM matches
WHERE status = 'Veto'
AND veto_pick_expires_at IS NOT NULL
AND veto_pick_expires_at <= NOW()
AND (_match_id IS NULL OR id = _match_id)
FOR UPDATE SKIP LOCKED
LOOP
-- Fencing token: a timer armed for turn N must never act on turn N+1,
-- so a job whose cancellation was missed (or that fired while we were
-- behind on events) is inert rather than wrong. The sweep pass passes
-- NULL and leans on the expiry predicate above instead.
CONTINUE WHEN _expected_pick_count IS NOT NULL
AND veto_pick_count(_match.id) != _expected_pick_count;

_picked := false;

-- Subtransaction per match: verify_region_veto_pick and
-- get_map_veto_picking_lineup_id both RAISE on states we can legitimately
-- observe, and one bad match must not roll back the rest of the batch.
BEGIN
IF _match.region IS NULL THEN
_lineup_id := get_region_veto_picking_lineup_id(_match);

IF _lineup_id IS NOT NULL THEN
_regions := sanitize_match_options_regions(_match.match_options_id);

SELECT array_agg(r) INTO _available_regions
FROM unnest(_regions) AS r
WHERE NOT EXISTS (
SELECT 1 FROM match_region_veto_picks mvp
WHERE mvp.match_id = _match.id AND lower(mvp.region) = lower(r)
);

-- Strictly greater than one: verify_region_veto_pick refuses to
-- ban the last available region, and auto_select_region_veto
-- locks that one in by itself.
IF COALESCE(array_length(_available_regions, 1), 0) > 1 THEN
_region := _available_regions[
1 + floor(random() * array_length(_available_regions, 1))::int
];

INSERT INTO match_region_veto_picks
(match_id, type, match_lineup_id, region, auto_picked)
VALUES (_match.id, 'Ban', _lineup_id, _region, true);

_picked := true;
END IF;
END IF;
ELSE
_pick_type := get_map_veto_type(_match);

IF _pick_type IS NOT NULL THEN
_lineup_id := get_map_veto_picking_lineup_id(_match);
END IF;

IF _pick_type IS NOT NULL AND _lineup_id IS NOT NULL THEN
IF _pick_type = 'Side' THEN
SELECT map_id INTO _map_id
FROM match_map_veto_picks
WHERE match_id = _match.id AND type = 'Pick'
ORDER BY created_at DESC
LIMIT 1;

IF _map_id IS NOT NULL THEN
_side := CASE WHEN random() < 0.5 THEN 'CT' ELSE 'TERRORIST' END;

INSERT INTO match_map_veto_picks
(match_id, type, match_lineup_id, map_id, side, auto_picked)
VALUES (_match.id, 'Side', _lineup_id, _map_id, _side, true);

_picked := true;
END IF;
ELSE
SELECT mp.map_id INTO _map_id
FROM match_options mo
INNER JOIN _map_pool mp ON mp.map_pool_id = mo.map_pool_id
LEFT JOIN match_map_veto_picks mvp
ON mvp.match_id = _match.id AND mvp.map_id = mp.map_id
WHERE mo.id = _match.match_options_id AND mvp IS NULL
ORDER BY random()
LIMIT 1;

IF _map_id IS NOT NULL THEN
INSERT INTO match_map_veto_picks
(match_id, type, match_lineup_id, map_id, auto_picked)
VALUES (_match.id, _pick_type, _lineup_id, _map_id, true);

_picked := true;
END IF;
END IF;
END IF;
END IF;
EXCEPTION WHEN OTHERS THEN
_picked := false;
RAISE WARNING 'auto_pick_expired_veto failed for match %: %', _match.id, SQLERRM;
END;

-- Nothing was inserted, so no AFTER INSERT trigger refreshed the deadline.
-- Clearing it is what stops this match being re-swept on every single pass
-- for the rest of its life.
IF NOT _picked THEN
UPDATE matches SET veto_pick_expires_at = NULL WHERE id = _match.id;
END IF;
END LOOP;
END;
$$;
23 changes: 23 additions & 0 deletions hasura/functions/match/veto/refresh_veto_pick_expiry.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CREATE OR REPLACE FUNCTION public.refresh_veto_pick_expiry(_match_id uuid) RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
_timeout int;
BEGIN
SELECT mo.veto_pick_timeout INTO _timeout
FROM matches m
INNER JOIN match_options mo ON mo.id = m.match_options_id
WHERE m.id = _match_id;

-- Guarded on status so this only bumps while veto is actually running:
-- create_match_map_from_veto / auto_select_region_veto may have already
-- flipped the match to Live in this same statement, in which case the
-- timer is meaningless and tbu_matches has already nulled it.
UPDATE matches
SET veto_pick_expires_at = CASE
WHEN COALESCE(_timeout, 0) > 0 THEN NOW() + (_timeout || ' seconds')::interval
ELSE NULL
END
WHERE id = _match_id AND status = 'Veto';
END;
$$;
6 changes: 6 additions & 0 deletions hasura/functions/match/veto/veto_pick_count.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE OR REPLACE FUNCTION public.veto_pick_count(_match_id uuid) RETURNS int
LANGUAGE sql STABLE
AS $$
SELECT (SELECT COUNT(*) FROM match_region_veto_picks WHERE match_id = _match_id)::int
+ (SELECT COUNT(*) FROM match_map_veto_picks WHERE match_id = _match_id)::int;
$$;
6 changes: 4 additions & 2 deletions hasura/functions/tournaments/clone_match_options.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ BEGIN
map_veto, timeout_setting, tech_timeout_setting, map_pool_id, type,
regions, prefer_dedicated_server, invite_code,
region_veto, ready_setting, check_in_setting, default_models, tv_delay,
auto_cancellation, match_mode, auto_cancel_duration, live_match_timeout
auto_cancellation, match_mode, auto_cancel_duration, live_match_timeout,
veto_pick_timeout
)
SELECT
overtime, knife_round, mr, best_of, coaches, number_of_substitutes,
map_veto, timeout_setting, tech_timeout_setting, map_pool_id, type,
regions, prefer_dedicated_server, invite_code,
region_veto, ready_setting, check_in_setting, default_models, tv_delay,
auto_cancellation, match_mode, auto_cancel_duration, live_match_timeout
auto_cancellation, match_mode, auto_cancel_duration, live_match_timeout,
veto_pick_timeout
FROM match_options
WHERE id = _match_options_id
RETURNING id INTO cloned_id;
Expand Down
6 changes: 4 additions & 2 deletions hasura/functions/tournaments/update_match_options_best_of.sql
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ BEGIN
overtime, knife_round, mr, best_of, coaches, number_of_substitutes,
map_veto, timeout_setting, tech_timeout_setting, map_pool_id, type,
regions, prefer_dedicated_server, invite_code,
region_veto, ready_setting, check_in_setting, default_models, tv_delay
region_veto, ready_setting, check_in_setting, default_models, tv_delay,
veto_pick_timeout
) VALUES (
match_options_record.overtime, match_options_record.knife_round,
match_options_record.mr, match_options_record.best_of,
Expand All @@ -62,7 +63,8 @@ BEGIN
match_options_record.prefer_dedicated_server, match_options_record.invite_code,
match_options_record.region_veto,
match_options_record.ready_setting, match_options_record.check_in_setting,
match_options_record.default_models, match_options_record.tv_delay
match_options_record.default_models, match_options_record.tv_delay,
match_options_record.veto_pick_timeout
)
RETURNING id INTO final_match_options_id;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ select_permissions:
columns:
- side
- type
- auto_picked
- created_at
- id
- map_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ insert_permissions:
- live_match_timeout
- auto_cancellation
- tv_delay
- veto_pick_timeout
- type
- halftime_pausematch
- round_restart_delay
Expand All @@ -94,6 +95,7 @@ insert_permissions:
- tech_timeout_setting
- timeout_setting
- tv_delay
- veto_pick_timeout
- type
- halftime_pausematch
- round_restart_delay
Expand Down Expand Up @@ -124,6 +126,7 @@ select_permissions:
- live_match_timeout
- auto_cancellation
- tv_delay
- veto_pick_timeout
- type
- halftime_pausematch
- round_restart_delay
Expand Down Expand Up @@ -154,6 +157,7 @@ update_permissions:
- live_match_timeout
- auto_cancellation
- tv_delay
- veto_pick_timeout
- type
- halftime_pausematch
- round_restart_delay
Expand Down Expand Up @@ -211,6 +215,7 @@ update_permissions:
- tech_timeout_setting
- timeout_setting
- tv_delay
- veto_pick_timeout
- type
- halftime_pausematch
- round_restart_delay
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ select_permissions:
columns:
- region
- type
- auto_picked
- created_at
- id
- match_id
Expand Down
2 changes: 2 additions & 0 deletions hasura/metadata/databases/default/tables/public_matches.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ select_permissions:
- source
- started_at
- status
- veto_pick_expires_at
- winning_lineup_id
computed_fields:
- can_assign_server
Expand Down Expand Up @@ -600,6 +601,7 @@ select_permissions:
- source
- started_at
- status
- veto_pick_expires_at
- winning_lineup_id
computed_fields:
- can_assign_server
Expand Down
12 changes: 12 additions & 0 deletions hasura/migrations/default/1876000000200_veto_pick_timeout/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
DROP INDEX IF EXISTS public.matches_veto_pick_expires_at_idx;

ALTER TABLE public.match_region_veto_picks DROP COLUMN IF EXISTS auto_picked;

ALTER TABLE public.match_map_veto_picks DROP COLUMN IF EXISTS auto_picked;

ALTER TABLE public.matches DROP COLUMN IF EXISTS veto_pick_expires_at;

ALTER TABLE public.match_options
DROP CONSTRAINT IF EXISTS match_options_veto_pick_timeout_check;

ALTER TABLE public.match_options DROP COLUMN IF EXISTS veto_pick_timeout;
23 changes: 23 additions & 0 deletions hasura/migrations/default/1876000000200_veto_pick_timeout/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
ALTER TABLE public.match_options
ADD COLUMN IF NOT EXISTS veto_pick_timeout integer NOT NULL DEFAULT 60;

-- 0 is the disable value, so this is >= 0 rather than the > 0 used by
-- auto_cancel_duration / live_match_timeout.
ALTER TABLE public.match_options
DROP CONSTRAINT IF EXISTS match_options_veto_pick_timeout_check;

ALTER TABLE public.match_options
ADD CONSTRAINT match_options_veto_pick_timeout_check CHECK (veto_pick_timeout >= 0);

ALTER TABLE public.matches
ADD COLUMN IF NOT EXISTS veto_pick_expires_at timestamptz;

ALTER TABLE public.match_map_veto_picks
ADD COLUMN IF NOT EXISTS auto_picked boolean NOT NULL DEFAULT false;

ALTER TABLE public.match_region_veto_picks
ADD COLUMN IF NOT EXISTS auto_picked boolean NOT NULL DEFAULT false;

CREATE INDEX IF NOT EXISTS matches_veto_pick_expires_at_idx
ON public.matches (veto_pick_expires_at)
WHERE veto_pick_expires_at IS NOT NULL;
1 change: 1 addition & 0 deletions hasura/triggers/match_map_veto_picks.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ LANGUAGE plpgsql
AS $$
BEGIN
PERFORM create_match_map_from_veto(NEW);
PERFORM refresh_veto_pick_expiry(NEW.match_id);
RETURN NEW;
END;
$$;
Expand Down
6 changes: 6 additions & 0 deletions hasura/triggers/match_options.sql
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ BEGIN
PERFORM setup_match_maps(_match_id, NEW.id);
END IF;

-- Retiming mid-veto: the resulting matches update re-fires match_events,
-- which re-arms the delayed job against the new deadline.
IF (NEW.veto_pick_timeout IS DISTINCT FROM OLD.veto_pick_timeout AND _match_status = 'Veto') THEN
PERFORM refresh_veto_pick_expiry(_match_id);
END IF;

RETURN NEW;
END;
$$;
Expand Down
1 change: 1 addition & 0 deletions hasura/triggers/match_region_veto_picks.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ LANGUAGE plpgsql
AS $$
BEGIN
PERFORM auto_select_region_veto(NEW);
PERFORM refresh_veto_pick_expiry(NEW.match_id);
RETURN NEW;
END;
$$;
Expand Down
17 changes: 17 additions & 0 deletions hasura/triggers/matches.sql
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ DECLARE
_match_options match_options%ROWTYPE;
_auto_cancellation boolean;
_auto_cancel_duration_override integer;
_veto_pick_timeout integer;
BEGIN
SELECT auto_cancellation, auto_cancel_duration INTO _auto_cancellation, _auto_cancel_duration_override FROM resolve_match_auto_cancel(NEW.id);
_auto_cancel_duration := COALESCE(_auto_cancel_duration_override, get_int_setting('auto_cancel_duration', 15))::text || ' minutes';
Expand Down Expand Up @@ -417,16 +418,30 @@ BEGIN
NEW.cancels_at = COALESCE(scheduled_at, NOW()) + (_auto_cancel_duration)::interval;
END IF;
NEW.ended_at = null;

-- Arm the first veto turn. Set here rather than via
-- refresh_veto_pick_expiry because this is a BEFORE trigger.
SELECT veto_pick_timeout INTO _veto_pick_timeout
FROM match_options
WHERE id = NEW.match_options_id;

IF COALESCE(_veto_pick_timeout, 0) > 0 THEN
NEW.veto_pick_expires_at = NOW() + (_veto_pick_timeout || ' seconds')::interval;
ELSE
NEW.veto_pick_expires_at = null;
END IF;
END IF;

IF NEW.status = 'WaitingForServer' AND OLD.status != 'WaitingForServer' THEN
NEW.cancels_at = null;
NEW.ended_at = null;
NEW.veto_pick_expires_at = null;
END IF;

IF (NEW.status = 'Canceled' AND OLD.status != 'Canceled') THEN
NEW.cancels_at = NOW();
NEW.ended_at = null;
NEW.veto_pick_expires_at = null;

DELETE FROM match_region_veto_picks WHERE match_id = NEW.id;
DELETE FROM match_map_veto_picks WHERE match_id = NEW.id;
Expand All @@ -436,6 +451,7 @@ BEGIN
NEW.started_at = NOW();
NEW.cancels_at = null;
NEW.ended_at = null;
NEW.veto_pick_expires_at = null;
END IF;

IF
Expand All @@ -445,6 +461,7 @@ BEGIN
THEN
NEW.ended_at = NOW();
NEW.cancels_at = null;
NEW.veto_pick_expires_at = null;
END IF;

PERFORM check_match_status(NEW);
Expand Down
1 change: 1 addition & 0 deletions src/draft-games/draft-game.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,7 @@ export class DraftGameService {
ready_setting: source.ready_setting,
tech_timeout_setting: source.tech_timeout_setting,
tv_delay: source.tv_delay,
veto_pick_timeout: source.veto_pick_timeout,
};

if (source.map_pool_id) {
Expand Down
Loading
Loading