fix(ptica): ключ синглтон-лока kn-свипа зависит от множества, а не от порядка (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m22s
CI / backend-tests (pull_request) Successful in 17m12s
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m22s
CI / backend-tests (pull_request) Successful in 17m12s
devs_key = ",".join(developers) — список джойнился как пришёл, а приходит он прямо
из тела запроса (`developers` в admin_scrape, порядок произвольный). Один и тот же
набор в другом порядке давал ДРУГОЙ ключ: синглтон молча переставал быть
синглтоном, два свипа шли параллельно по одним и тем же разработчикам.
Второе следствие того же корня: force_release_lock строит ключ этой же функцией.
Оператор, снимающий залипший лок и перечисливший разработчиков в ином порядке,
молча не снимал ничего.
sorted(set(...)) закрывает оба случая: порядок и повторы ('A','A' ≡ 'A').
Про переход: формат ключа меняется, поэтому лок, удерживаемый ПРЯМО СЕЙЧАС под
старым ключом, осиротеет и истечёт по TTL. Практического риска нет — kn-свип
не отрабатывает с 28.06 (DOM.РФ отдаёт страницу блокировки, см. #2443).
Тесты сравнивают ЗНАЧЕНИЯ ключей, поэтому красное на origin/main — про разошедшиеся
ключи, а не про отсутствующую функцию:
один набор в двух порядках → два разных ключа → падает
повтор в списке меняет ключ → падает
разные наборы остаются разными — контроль, зелёный с обеих сторон
регион остаётся в ключе — контроль, зелёный с обеих сторон
None/[] по-прежнему дают '*' — контроль, зелёный с обеих сторон
Первый контроль не для симметрии: ловит «починку» через огрубление ключа
(отбросить developers вовсе) — тогда свипы по разным разработчикам блокировали бы
друг друга.
Прогоны: -k "scrape_kn or lock" — 114 passed rc=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
497d2fa6ad
commit
fd2fc945a7
2 changed files with 73 additions and 1 deletions
|
|
@ -40,7 +40,20 @@ _RELEASE_LOCK_LUA = (
|
|||
|
||||
|
||||
def _lock_key(region_code: int, developers: list[str] | None) -> str:
|
||||
devs_key = ",".join(developers) if developers else "*"
|
||||
"""Ключ синглтон-лока. Зависит от МНОЖЕСТВА разработчиков, не от их порядка.
|
||||
|
||||
#2464: раньше список джойнился как пришёл, а приходит он прямо из тела запроса
|
||||
(`developers` в admin_scrape). Один и тот же набор, поданный в другом порядке,
|
||||
давал ДРУГОЙ ключ — и синглтон молча переставал быть синглтоном: два свипа шли
|
||||
параллельно по одним и тем же разработчикам.
|
||||
|
||||
Второе следствие того же: `force_release_lock` строит ключ этой же функцией.
|
||||
Оператор, снимающий залипший лок и перечисливший разработчиков в ином порядке,
|
||||
молча не снимал ничего.
|
||||
|
||||
sorted(set(...)) закрывает оба: порядок и повторы ('A','A' ≡ 'A').
|
||||
"""
|
||||
devs_key = ",".join(sorted(set(developers))) if developers else "*"
|
||||
return f"scrape:kn:lock:{region_code}:{devs_key}"
|
||||
|
||||
|
||||
|
|
|
|||
59
backend/tests/services/test_2464_kn_lock_key.py
Normal file
59
backend/tests/services/test_2464_kn_lock_key.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""Ключ синглтон-лока kn-свипа зависит от множества, а не от порядка (#2464).
|
||||
|
||||
`developers` приходит прямо из тела запроса (`admin_scrape`), порядок произвольный.
|
||||
Раньше список джойнился как пришёл: один и тот же набор в другом порядке давал ДРУГОЙ
|
||||
ключ — синглтон молча переставал быть синглтоном, два свипа шли параллельно по одним и
|
||||
тем же разработчикам.
|
||||
|
||||
Второе следствие того же корня: `force_release_lock` строит ключ этой же функцией.
|
||||
Оператор, снимающий залипший лок и перечисливший разработчиков в ином порядке, молча не
|
||||
снимал ничего.
|
||||
|
||||
Тесты сравнивают ЗНАЧЕНИЯ ключей — на origin/main они не совпадают, то есть красное
|
||||
означает «ключи разошлись», а не «функции нет».
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
|
||||
|
||||
|
||||
def _key(region: int, devs: list[str] | None) -> str:
|
||||
from app.workers.tasks.scrape_kn import _lock_key
|
||||
|
||||
return _lock_key(region, devs)
|
||||
|
||||
|
||||
def test_same_set_different_order_gives_same_key() -> None:
|
||||
"""Порядок не должен создавать второй лок для того же набора."""
|
||||
a = _key(66, ["6208_0", "1234_5", "9999_1"])
|
||||
b = _key(66, ["9999_1", "6208_0", "1234_5"])
|
||||
assert a == b, f"один набор дал два ключа:\n {a}\n {b}"
|
||||
|
||||
|
||||
def test_duplicates_do_not_change_the_key() -> None:
|
||||
"""Повтор в списке — тот же набор, тот же лок."""
|
||||
assert _key(66, ["6208_0", "6208_0"]) == _key(66, ["6208_0"])
|
||||
|
||||
|
||||
def test_different_sets_stay_separate() -> None:
|
||||
"""Контроль: разные наборы обязаны остаться разными локами.
|
||||
|
||||
Ловит «починку» через огрубление ключа (напр. отбросить developers вовсе) —
|
||||
тогда свипы по разным разработчикам блокировали бы друг друга.
|
||||
"""
|
||||
assert _key(66, ["A"]) != _key(66, ["B"])
|
||||
assert _key(66, ["A"]) != _key(66, ["A", "B"])
|
||||
|
||||
|
||||
def test_region_still_separates_locks() -> None:
|
||||
"""Контроль: регион остаётся частью ключа."""
|
||||
assert _key(66, ["A"]) != _key(74, ["A"])
|
||||
|
||||
|
||||
def test_empty_and_none_mean_all_developers() -> None:
|
||||
"""Контроль: прежняя семантика '*' для пустого/None сохранена."""
|
||||
assert _key(66, None).endswith(":*")
|
||||
assert _key(66, []).endswith(":*")
|
||||
Loading…
Add table
Reference in a new issue