From 3ade3fc3cc52d280e5c059a54d11d31bde11f988 Mon Sep 17 00:00:00 2001 From: bsiggel Date: Tue, 31 Mar 2026 07:00:35 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20add=20try/finally=20safety=20net=20for?= =?UTF-8?q?=20lock=20release=20in=20beteiligte=5Fsync=20handler=20?= =?UTF-8?q?=E2=80=93=20guarantees=20Redis=20key=20cleanup=20on=20any=20exi?= =?UTF-8?q?t=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../beteiligte/beteiligte_sync_event_step.py | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/steps/crm/beteiligte/beteiligte_sync_event_step.py b/src/steps/crm/beteiligte/beteiligte_sync_event_step.py index 30dbcf4..977ed8a 100644 --- a/src/steps/crm/beteiligte/beteiligte_sync_event_step.py +++ b/src/steps/crm/beteiligte/beteiligte_sync_event_step.py @@ -88,7 +88,7 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None: ctx.logger.warn(f"⏸️ Sync bereits aktiv für {entity_id}, überspringe") return - # Lock erfolgreich acquired - MUSS im finally block released werden! + # Lock erfolgreich acquired try: # 2. FETCH ENTITY VON ESPOCRM try: @@ -97,23 +97,23 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None: ctx.logger.error(f"❌ Fehler beim Laden von EspoCRM Entity: {e}") await sync_utils.release_sync_lock(entity_id, 'failed', str(e), increment_retry=True) return - + ctx.logger.info(f"📋 Entity geladen: {espo_entity.get('name')} (betnr: {espo_entity.get('betnr')})") - + betnr = espo_entity.get('betnr') sync_status = espo_entity.get('syncStatus', 'pending_sync') - + # Check Retry-Backoff - überspringe wenn syncNextRetry noch nicht erreicht sync_next_retry = espo_entity.get('syncNextRetry') if sync_next_retry and sync_status == 'failed': import datetime import pytz - + try: next_retry_ts = datetime.datetime.strptime(sync_next_retry, '%Y-%m-%d %H:%M:%S') next_retry_ts = pytz.UTC.localize(next_retry_ts) now_utc = datetime.datetime.now(pytz.UTC) - + if now_utc < next_retry_ts: remaining_minutes = int((next_retry_ts - now_utc).total_seconds() / 60) ctx.logger.info(f"⏸️ Retry-Backoff aktiv: Nächster Versuch in {remaining_minutes} Minuten") @@ -121,34 +121,34 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None: return except Exception as e: ctx.logger.warn(f"⚠️ Fehler beim Parsen von syncNextRetry: {e}") - + # 3. BESTIMME SYNC-AKTION - + # FALL A: Neu (kein betnr) → CREATE in Advoware if not betnr and action in ['create', 'sync_check']: ctx.logger.info(f"🆕 Neuer Beteiligter → CREATE in Advoware") await handle_create(entity_id, espo_entity, espocrm, advoware, sync_utils, mapper, ctx) - + # FALL B: Existiert (hat betnr) → UPDATE oder CHECK elif betnr: ctx.logger.info(f"♻️ Existierender Beteiligter (betNr: {betnr}) → UPDATE/CHECK") await handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_utils, mapper, ctx) - + # FALL C: DELETE (TODO: Implementierung später) elif action == 'delete': ctx.logger.warn(f"🗑️ DELETE noch nicht implementiert für {entity_id}") await sync_utils.release_sync_lock(entity_id, 'failed', 'Delete-Operation nicht implementiert') - + else: ctx.logger.warn(f"⚠️ Unbekannte Kombination: action={action}, betnr={betnr}") await sync_utils.release_sync_lock(entity_id, 'failed', f'Unbekannte Aktion: {action}') - + except Exception as e: - # Unerwarteter Fehler während Sync - GARANTIERE Lock-Release + # Unerwarteter Fehler während Sync - Lock-Release via finally ctx.logger.error(f"❌ Unerwarteter Fehler im Sync-Handler: {e}") import traceback ctx.logger.error(traceback.format_exc()) - + try: await sync_utils.release_sync_lock( entity_id, @@ -157,15 +157,16 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None: increment_retry=True ) except Exception as release_error: - # Selbst Lock-Release failed - logge kritischen Fehler - ctx.logger.critical(f"🚨 CRITICAL: Lock-Release failed für {entity_id}: {release_error}") - # Force Redis lock release - try: - lock_key = f"sync_lock:cbeteiligte:{entity_id}" - redis_client.delete(lock_key) - ctx.logger.info(f"✅ Redis lock manuell released: {lock_key}") - except: - pass + ctx.logger.critical(f"🚨 CRITICAL: release_sync_lock failed für {entity_id}: {release_error}") + raise + + finally: + # Safety net: force Redis lock release regardless of outcome + try: + force_lock_key = f"sync_lock:cbeteiligte:{entity_id}" + redis_client.delete(force_lock_key) + except Exception as cleanup_err: + ctx.logger.error(f"❌ Force Lock-Release fehlgeschlagen: {cleanup_err}") except Exception as e: # Fehler VOR Lock-Acquire - kein Lock-Release nötig