fix: имя воркера — свободное worker-N из Redis, а не hostname (#110)
Два процесса на одном хосте получали один WORKER_ID и делили персональную очередь: задачу без url в открытое окно забирал процесс без этого окна. Теперь при старте воркер занимает наименьшее свободное имя worker-N (SET NX на heartbeat-ключ), heartbeat продлевает его только при своём instance; если имя занял другой процесс — воркер останавливается. Имена и персональные очереди переиспользуются. WORKER_ID из env убран, в /health — instance, host, pid. make dev при выходе гасит и api, и worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f7d60563d
commit
502b6e1003
8 files changed
+236
-23
No files matched your search
@@ -72,3 +72,32 @@ async def test_live_window_requires_live_worker(store):
|
||||
await store.heartbeat("w1", {"browser_slots": 1, "free_slots": 0})
|
||||
assert await store.live_window("s") == {"worker_id": "w1", "idle_timeout": "5", "client_id": "7"}
|
||||
assert await store.workers() == [{"worker_id": "w1", "browser_slots": 1, "free_slots": 0}]
|
||||
|
||||
|
||||
async def test_worker_names_are_unique_and_reused(store):
|
||||
"""Имена worker-N: занятое не выдаётся, освободившееся — переиспользуется (#110)."""
|
||||
assert await store.acquire_worker_name({"instance": "a"}) == "worker-1"
|
||||
assert await store.acquire_worker_name({"instance": "b"}) == "worker-2"
|
||||
await store.remove_worker("worker-1", "a")
|
||||
assert await store.acquire_worker_name({"instance": "c"}) == "worker-1"
|
||||
assert await store.redis.ttl("robotex:worker:worker-1") == task_store.HEARTBEAT_TTL
|
||||
|
||||
|
||||
async def test_heartbeat_of_taken_name_is_refused(store):
|
||||
"""Имя истекло и его занял другой процесс: старый владелец его не продлит и не удалит."""
|
||||
assert await store.acquire_worker_name({"instance": "old"}) == "worker-1"
|
||||
assert await store.heartbeat("worker-1", {"instance": "old", "windows": 1})
|
||||
await store.redis.delete("robotex:worker:worker-1") # TTL истёк, пока Redis был недоступен
|
||||
assert await store.acquire_worker_name({"instance": "new"}) == "worker-1"
|
||||
|
||||
assert not await store.heartbeat("worker-1", {"instance": "old"})
|
||||
await store.remove_worker("worker-1", "old")
|
||||
assert (await store.workers())[0]["instance"] == "new"
|
||||
|
||||
|
||||
async def test_expired_own_name_is_renewed(store):
|
||||
"""Имя истекло, но никто его не занял — heartbeat просто продлевает его."""
|
||||
assert await store.acquire_worker_name({"instance": "a"}) == "worker-1"
|
||||
await store.redis.delete("robotex:worker:worker-1")
|
||||
assert await store.heartbeat("worker-1", {"instance": "a"})
|
||||
assert await store.worker_alive("worker-1")
|
||||
Reference in new issue
Block a user