Re: Waiting for a contended resource

Richard van der Hoff <[email protected]>
Newsgroups gmane.comp.python.twisted
Message-ID <[email protected]>
Thank you for all for all the answers so far, particularly to Ilya and 
Jean-Paul who provided some very helpful code samples.

It's interesting to realise that, by avoiding locking, we can end up 
with a much more efficient implementation. I'll have to figure out how 
widely we can apply this technique - and how often it's going to be 
worth rewriting things to allow that. Thanks for some useful pointers!

Richard


On 12/03/18 20:00, Jean-Paul Calderone wrote:
> On Mon, Mar 12, 2018 at 3:52 PM, Ilya Skriblovsky 
> <[email protected] <mailto:[email protected]>> wrote:
>
>     Hi, Richard,
>
>     I've used class like this to cache the result of Expensive
>     Calculation:
>
>     class DeferredCache:
>     pending = None
>     result = None
>     failure = None
>
>     def __init__(self, expensive_func):
>       self.expensive_func = expensive_func
>
>     def __call__(self):
>       if self.pending is None:
>           def on_ready(result):
>               self.result = result
>           def on_fail(failure):
>               self.failure = failure
>
>           self.pending =
>     defer.maybeDeferred(self.expensive_func).addCallbacks(on_ready,
>     on_fail)
>
>       return self.pending.addCallback(self._return_result)
>
>
> This seems like basically a correct answer to me. However, I suggest 
> one small change.
>
> You probably want to create and return a new Deferred for each 
> result.  If you don't, then your internal `pending` Deferred is now 
> reachable by application code.
>
> As written, an application might (very, very reasonably):
>
>     d = getResource()
>     d.addCallback(long_async_operation)
>
> Now `pending` has `long_async_operation` as a callback on its chain.  
> This will prevent anyone else from getting a result until 
> `long_async_operation` is done.
>
> You can fix this by:
>
>     result = Deferred()
> self.pending.addCallback(self._return_result).chainDeferred(result)
>     return result
>
> Now the application can only reach `result`.  Nothing they do to 
> `result` will make much difference to `pending` because 
> `chainDeferred` only puts `callback` (and `errback`) onto `pending`'s 
> callback chain.  `callback` and `errback` don't wait on anything.
>
> You have to be a little careful with `chainDeferred` because it 
> doesn't have the recursion-avoidance logic that implicit chaining 
> has.  However, that doesn't matter in this particular case because the 
> chain depth is fixed at two (`pending` and `result`).  The problems 
> only arise if you extend the chain out in this direction without bound.
>
> Jean-Paul
>
>     def _return_result(self, _):
>       return self.failure or self.result
>
>     Using it you can get rid of DeferredLocks:
>
>         deferred_cache = DeferredCache(do_expensive_calculation)
>
>         def getResource():
>             return deferred_cache()
>
>     It will start `expensive_func` on the first call. The second and
>     consequtive calls will return deferreds that resolves with the
>     result when expensive_func is done. If you call it when result is
>     already here, it will return alread-fired deferred.
>
>     Of course, it will require some more work if you need to pass
>     arguments to `expensive_func` and memoize results per arguments
>     values.
>
>     -- ilya
>

_______________________________________________
Twisted-Python mailing list
[email protected]
https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.