diff --git a/make_post_sell/models/shop.py b/make_post_sell/models/shop.py index 59b5f19..aa02a5d 100644 --- a/make_post_sell/models/shop.py +++ b/make_post_sell/models/shop.py @@ -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()