Use dirty bit instead of drop for async ring reforge debounce
When a reforge is already running, set a dirty flag so the thread loops once more after finishing — picks up latest product state from rapid saves without queueing multiple reforges.
This commit is contained in:
parent
3ba8802d9a
commit
bda4d39858
1 changed files with 30 additions and 18 deletions
|
|
@ -692,45 +692,57 @@ def reforge_discovery_ring(shop):
|
|||
return ring
|
||||
|
||||
|
||||
# Tracks which shops have a background reforge in progress.
|
||||
_reforge_locks = {}
|
||||
_reforge_locks_guard = threading.Lock()
|
||||
# Tracks which shops have a background reforge in progress and whether
|
||||
# another reforge was requested while one was already running (dirty bit).
|
||||
_reforge_running = {} # shop_id_str -> True while a thread is active
|
||||
_reforge_dirty = {} # shop_id_str -> True if another trigger arrived mid-run
|
||||
_reforge_guard = threading.Lock()
|
||||
|
||||
|
||||
def reforge_discovery_ring_async(shop_id, session_factory):
|
||||
"""Reforge the discovery ring in a background thread.
|
||||
|
||||
Uses the old ring until the new one is ready. If a reforge is already
|
||||
running for this shop, the call is a no-op (the in-flight reforge will
|
||||
pick up the latest products).
|
||||
running for this shop, sets a dirty bit so the thread runs once more
|
||||
after it finishes (picking up the latest product state). Never queues
|
||||
more than one pending reforge.
|
||||
"""
|
||||
shop_id_str = str(shop_id)
|
||||
|
||||
with _reforge_locks_guard:
|
||||
if shop_id_str in _reforge_locks:
|
||||
return # already running, skip
|
||||
_reforge_locks[shop_id_str] = True
|
||||
with _reforge_guard:
|
||||
if shop_id_str in _reforge_running:
|
||||
_reforge_dirty[shop_id_str] = True
|
||||
return
|
||||
_reforge_running[shop_id_str] = True
|
||||
|
||||
def _reforge():
|
||||
try:
|
||||
from sqlalchemy.orm import Session
|
||||
session = Session(bind=session_factory().get_bind())
|
||||
try:
|
||||
shop = session.get(Shop, shop_id)
|
||||
if shop is None:
|
||||
return
|
||||
ring = compute_discovery_ring(shop)
|
||||
shop.discovery_ring = ring
|
||||
session.commit()
|
||||
log.info("Ring reforged for shop %s (%d products)", shop.name, len(ring))
|
||||
while True:
|
||||
shop = session.get(Shop, shop_id)
|
||||
if shop is None:
|
||||
return
|
||||
ring = compute_discovery_ring(shop)
|
||||
shop.discovery_ring = ring
|
||||
session.commit()
|
||||
log.info("Ring reforged for shop %s (%d products)", shop.name, len(ring))
|
||||
|
||||
with _reforge_guard:
|
||||
if _reforge_dirty.pop(shop_id_str, False):
|
||||
session.expire_all()
|
||||
continue # dirty bit set, run again
|
||||
break
|
||||
except Exception:
|
||||
session.rollback()
|
||||
log.exception("Background ring reforge failed for shop %s", shop_id_str)
|
||||
finally:
|
||||
session.close()
|
||||
finally:
|
||||
with _reforge_locks_guard:
|
||||
_reforge_locks.pop(shop_id_str, None)
|
||||
with _reforge_guard:
|
||||
_reforge_running.pop(shop_id_str, None)
|
||||
_reforge_dirty.pop(shop_id_str, None)
|
||||
|
||||
t = threading.Thread(target=_reforge, daemon=True)
|
||||
t.start()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue