Search code examples
pythonmultithreadingdeadlockgevent

gevent/threading causes some deadlock


I have this code, whose purpose is to dedupe requests.

def dedup_requests(f):
    pending = {}

    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        key = _make_call_key(args, kwargs)
        if key not in pending:
            pending[key] = gevent.spawn(f, *args, **kwargs)
        result = pending[key].get()
        if key in pending:
            del pending[key]
        return result

    return wrapped

I suspect it is causing a deadlock somehow (this happens once in awhile, and I can't reproduce it).

It happens both when using threading and gevent.

Is the recurring use of get allowed?

Can this code even produce a deadlock when threading is not involved?

Note that it runs under other gevent tasks, so spawned tasks might spawn additional tasks, in case that's an issue.


Solution

  • Though I still don't exactly understand the source of the deadlock (my best guess is that get doesn't really work as expected when called more than once), this seems to work:

    from gevent import lock
    
    def queue_identical_calls(f, max_size=100):
        pending = {}
    
        @functools.wraps(f)
        def wrapped(*args, **kwargs):
            key = _make_call_key(args, kwargs)
            if key not in pending:
                pending[key] = lock.BoundedSemaphore(1)
            lock_for_current_call = pending[key]
            lock_for_current_call.acquire()
            result = f(*args, **kwargs)
            lock_for_current_call.release()
    
            if len(pending) > max_size:
                pending.clear()
    
            return result
    
        return wrapped