#!/usr/bin/env python3 import threading import time from sfa.util.sfalogging import logger """ Callids: a simple mechanism to remember the call ids served so fas memory-only for now - thread-safe implemented as a (singleton) hash 'callid'->timestamp """ debug = False class _call_ids_impl (dict): _instance = None # 5 minutes sounds amply enough purge_timeout = 5 * 60 # when trying to get a lock retries = 10 # in ms wait_ms = 100 def __init__(self): self._lock = threading.Lock() # the only primitive # return True if the callid is unknown, False otherwise def already_handled(self, call_id): # if not provided in the call... if not call_id: return False has_lock = False for attempt in range(_call_ids_impl.retries): if debug: logger.debug("Waiting for lock (%d)" % attempt) if self._lock.acquire(False): has_lock = True if debug: logger.debug("got lock (%d)" % attempt) break time.sleep(float(_call_ids_impl.wait_ms) / 1000) # in the unlikely event where we can't get the lock if not has_lock: logger.warning( "_call_ids_impl.should_handle_call_id: could not acquire lock") return False # we're good to go if call_id in self: self._purge() self._lock.release() return True self[call_id] = time.time() self._purge() self._lock.release() if debug: logger.debug("released lock") return False def _purge(self): now = time.time() o_keys = [] for (k, v) in self.items(): if (now - v) >= _call_ids_impl.purge_timeout: o_keys.append(k) for k in o_keys: if debug: logger.debug("Purging call_id %r (%s)" % ( k, time.strftime("%H:%M:%S", time.localtime(self[k])))) del self[k] if debug: logger.debug("AFTER PURGE") for (k, v) in self.items(): logger.debug("%s -> %s" % (k, time.strftime("%H:%M:%S", time.localtime(v)))) def Callids(): if not _call_ids_impl._instance: _call_ids_impl._instance = _call_ids_impl() return _call_ids_impl._instance