fix: add try/finally for lock release in bankverbindungen_sync handler – prevents 15-min lock leaks on exception
This commit is contained in:
@@ -55,69 +55,69 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None:
|
|||||||
mapper = BankverbindungenMapper()
|
mapper = BankverbindungenMapper()
|
||||||
notification_mgr = NotificationManager(espocrm_api=espocrm, context=ctx)
|
notification_mgr = NotificationManager(espocrm_api=espocrm, context=ctx)
|
||||||
|
|
||||||
|
lock_key = f"sync_lock:cbankverbindungen:{entity_id}"
|
||||||
|
acquired = False
|
||||||
try:
|
try:
|
||||||
# 1. ACQUIRE LOCK
|
# 1. ACQUIRE LOCK
|
||||||
lock_key = f"sync_lock:cbankverbindungen:{entity_id}"
|
|
||||||
acquired = redis_client.set(lock_key, "locked", nx=True, ex=900) # 15min TTL
|
acquired = redis_client.set(lock_key, "locked", nx=True, ex=900) # 15min TTL
|
||||||
|
|
||||||
if not acquired:
|
if not acquired:
|
||||||
ctx.logger.warn(f"⏸️ Sync bereits aktiv für {entity_id}, überspringe")
|
ctx.logger.warn(f"⏸️ Sync bereits aktiv für {entity_id}, überspringe")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2. FETCH ENTITY VON ESPOCRM
|
# 2. FETCH ENTITY VON ESPOCRM
|
||||||
try:
|
try:
|
||||||
espo_entity = await espocrm.get_entity('CBankverbindungen', entity_id)
|
espo_entity = await espocrm.get_entity('CBankverbindungen', entity_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ctx.logger.error(f"❌ Fehler beim Laden von EspoCRM Entity: {e}")
|
ctx.logger.error(f"❌ Fehler beim Laden von EspoCRM Entity: {e}")
|
||||||
redis_client.delete(lock_key)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
ctx.logger.info(f"📋 Entity geladen: {espo_entity.get('name', 'Unbenannt')} (IBAN: {espo_entity.get('iban', 'N/A')})")
|
ctx.logger.info(f"📋 Entity geladen: {espo_entity.get('name', 'Unbenannt')} (IBAN: {espo_entity.get('iban', 'N/A')})")
|
||||||
|
|
||||||
advoware_id = espo_entity.get('advowareId')
|
advoware_id = espo_entity.get('advowareId')
|
||||||
beteiligte_id = espo_entity.get('cBeteiligteId') # Parent Beteiligter
|
beteiligte_id = espo_entity.get('cBeteiligteId') # Parent Beteiligter
|
||||||
|
|
||||||
if not beteiligte_id:
|
if not beteiligte_id:
|
||||||
ctx.logger.error(f"❌ Keine cBeteiligteId gefunden - Bankverbindung muss einem Beteiligten zugeordnet sein")
|
ctx.logger.error(f"❌ Keine cBeteiligteId gefunden - Bankverbindung muss einem Beteiligten zugeordnet sein")
|
||||||
redis_client.delete(lock_key)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Hole betNr vom Parent
|
# Hole betNr vom Parent
|
||||||
parent = await espocrm.get_entity('CBeteiligte', beteiligte_id)
|
parent = await espocrm.get_entity('CBeteiligte', beteiligte_id)
|
||||||
betnr = parent.get('betnr')
|
betnr = parent.get('betnr')
|
||||||
|
|
||||||
if not betnr:
|
if not betnr:
|
||||||
ctx.logger.error(f"❌ Parent Beteiligter {beteiligte_id} hat keine betNr")
|
ctx.logger.error(f"❌ Parent Beteiligter {beteiligte_id} hat keine betNr")
|
||||||
redis_client.delete(lock_key)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# 3. BESTIMME SYNC-AKTION
|
# 3. BESTIMME SYNC-AKTION
|
||||||
|
|
||||||
# FALL A: Neu (kein advowareId) → CREATE in Advoware
|
# FALL A: Neu (kein advowareId) → CREATE in Advoware
|
||||||
if not advoware_id and action in ['create', 'sync_check']:
|
if not advoware_id and action in ['create', 'sync_check']:
|
||||||
await handle_create(entity_id, betnr, espo_entity, espocrm, advoware, mapper, ctx, redis_client, lock_key)
|
await handle_create(entity_id, betnr, espo_entity, espocrm, advoware, mapper, ctx, redis_client, lock_key)
|
||||||
|
|
||||||
# FALL B: Existiert (hat advowareId) → UPDATE oder CHECK (nicht unterstützt!)
|
# FALL B: Existiert (hat advowareId) → UPDATE oder CHECK (nicht unterstützt!)
|
||||||
elif advoware_id and action in ['update', 'sync_check']:
|
elif advoware_id and action in ['update', 'sync_check']:
|
||||||
await handle_update(entity_id, betnr, advoware_id, espo_entity, espocrm, notification_mgr, ctx, redis_client, lock_key)
|
await handle_update(entity_id, betnr, advoware_id, espo_entity, espocrm, notification_mgr, ctx, redis_client, lock_key)
|
||||||
|
|
||||||
# FALL C: DELETE (nicht unterstützt!)
|
# FALL C: DELETE (nicht unterstützt!)
|
||||||
elif action == 'delete':
|
elif action == 'delete':
|
||||||
await handle_delete(entity_id, betnr, advoware_id, espo_entity, espocrm, notification_mgr, ctx, redis_client, lock_key)
|
await handle_delete(entity_id, betnr, advoware_id, espo_entity, espocrm, notification_mgr, ctx, redis_client, lock_key)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
ctx.logger.warn(f"⚠️ Unbekannte Kombination: action={action}, advowareId={advoware_id}")
|
ctx.logger.warn(f"⚠️ Unbekannte Kombination: action={action}, advowareId={advoware_id}")
|
||||||
redis_client.delete(lock_key)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ctx.logger.error(f"❌ Unerwarteter Fehler im Sync-Handler: {e}")
|
ctx.logger.error(f"❌ Unerwarteter Fehler im Sync-Handler: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
ctx.logger.error(traceback.format_exc())
|
ctx.logger.error(traceback.format_exc())
|
||||||
|
raise
|
||||||
try:
|
|
||||||
redis_client.delete(lock_key)
|
finally:
|
||||||
except:
|
if acquired:
|
||||||
pass
|
try:
|
||||||
|
redis_client.delete(lock_key)
|
||||||
|
except Exception as cleanup_err:
|
||||||
|
ctx.logger.error(f"❌ Lock-Release fehlgeschlagen: {cleanup_err}")
|
||||||
|
|
||||||
|
|
||||||
async def handle_create(entity_id, betnr, espo_entity, espocrm, advoware, mapper, ctx, redis_client, lock_key) -> None:
|
async def handle_create(entity_id, betnr, espo_entity, espocrm, advoware, mapper, ctx, redis_client, lock_key) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user