Test:

    >>> from common_cache import Cache
    >>> import time
    >>> cache = Cache(expire=1, cleanup_func=basic_cleanup, regularly_cleanup=False)
    >>> cache.put('a', value=0)
    >>> cache.put('b', value=1)
    >>> cache.put('c', value=2)
    >>> cache.put('d', value=3, expire=3)
    >>> cache.size()
    4
    >>> time.sleep(1)
    >>> cache.put('e', 4)
    >>> cache.get('a') == None
    True
    >>> cache.get('d') == None
    False

def basic_cleanup(self):
    """
    Test:

    >>> from common_cache import Cache
    >>> import time
    >>> cache = Cache(expire=1, cleanup_func=basic_cleanup, regularly_cleanup=False)
    >>> cache.put('a', value=0)
    >>> cache.put('b', value=1)
    >>> cache.put('c', value=2)
    >>> cache.put('d', value=3, expire=3)
    >>> cache.size()
    4
    >>> time.sleep(1)
    >>> cache.put('e', 4)
    >>> cache.get('a') == None
    True
    >>> cache.get('d') == None
    False
    """
    next_expire = None
    keys_to_delete = []
    if self.expire is not None and self.expire > 0:
        # cleanup invalid cache item until the meet valid cache item and record next expire time
        for k, item in self.cache_items.items():
            if item.is_dead():
                keys_to_delete.append(k)
            else:
                next_expire = item.remaining_survival_time()
                break

    # if direct delete will to cause an error: OrderedDict mutated during iteration
    # so use delay delete
    for k in keys_to_delete:
        self.cache_items.pop(k)

    # if reach the upper limit of capacity then will execute evict by eviction strategy
    while (len(self.cache_items) > self.capacity):
        evicted_keys = self.evict_func(cache_dict=self.cache_items, evict_number=self.evict_number)
        self.logger.debug('Evict operation is completed, count: %s, keys: %s' % (len(evicted_keys), evicted_keys))

    return next_expire