misunderstanding of timeouts

1,008 views
Skip to first unread message

Jan-Philip Gehrcke

unread,
Oct 16, 2012, 6:15:59 PM10/16/12
to gev...@googlegroups.com
Hello,

obviously I don't understand a fundamental concept of timeouts in gevent/greenlets. Consider this code:


import gevent
import time

def t1():
    with gevent.Timeout(seconds=1):
        time.sleep(2)
        return True

def t2():
    def test():
        time.sleep(2)
        return True
    g = gevent.spawn(test)
    return g.get(timeout=1)
  
def main():
    print t1()
    print t2()

if __name__ == "__main__":
    main()


I tested it on Linux and Windows with Python 2.7 and gevent 0.13.8. It prints two times "True". I expected it to raise a timeout exception in function t1 as well as in t2. In the docs it is stated that

"If a blocking function is called or an intense calculation is ongoing during
which no switches occur, :class:`Timeout` is powerless."

However, time.sleep() is no intense calculation :-) and the described behavior does not change if I split the time.sleeps into several smaller sleeps or add some other tasks (like a HTTP request to a remote resource) in between the sleeps.

What am I missing here? Looks like "no switches occur". I would be grateful if someone explained the architectural background leading to my observation. In which context do these timeout methods make sense? My goal actually is to just timeout-control various pretty common functions in my software.

I have to add that both, gevent and eventlet provide the with_timeout(seconds, function, *args, **kwds) method that by its nature suggests that it is applicable to any function. Obviously, this one also does not raise an exception for me in a very simple test case. Ironically, just FYI, the eventlet docs show this example:

data = with_timeout(30, urllib2.open, 'http://www.google.com/', timeout_value="")

Great, basically something like this is my use case. However, first of all, it does not work because urllib2 has no `open` attribute (the line obviously has never been tested). After adjusting this line, the timeout is -- of course -- never raised for me.


Thanks for your insights in advance,

Jan-Philip

Damjan

unread,
Oct 17, 2012, 12:41:52 AM10/17/12
to gevent: coroutine-based Python network library

> I tested it on Linux and Windows with Python 2.7 and gevent 0.13.8. It
> prints two times "True". I expected it to raise a timeout exception in
> function t1 as well as in t2. In the docs it is stated that
>
> "If a blocking function is called or an intense calculation is ongoing
> during
> which no switches occur, :class:`Timeout` is powerless."
>
> However, time.sleep() is no intense calculation :-) and the described
> behavior does not change if I split the time.sleeps into several smaller
> sleeps or add some other tasks (like a HTTP request to a remote resource)
> in between the sleeps.
>
> What am I missing here? Looks like "no switches occur".

That's true. gevent is using cooperative scheduling of the greenlets
in single OS process/thread. And in your case there's nothing to
interrupt that time.sleep(2) call.
But, if you use gevent.sleep(2) which instead of sleeping in the OS,
will schedule the gevent hub (which runs the main I/O loop) you'll see
how it'll start behaving as you were expecting.

The general takeaway from this is that, you need to make sure every
blocking operation goes through the gevent hub so that it can run the
IO loop and schedule the greenlets. That's why you'd use gevent.socket
instead of socket.socket etc.

A call to urllib2 internally uses socket.socket, which will also block
in the OS and not run the gevent hub, and again that's why that
timeout feature didn't work. One solution to this problem is to call
gevent.monkey.patch_all() which will replace the standard blocking
Python APIs with gevent compatible ones.
The other is to use packages that are specifically made to work with
gevent.


--
damjan

Ian Epperson

unread,
Oct 17, 2012, 1:07:36 AM10/17/12
to gev...@googlegroups.com
Another way to put this is that time.sleep stops everything and nothing else can happen.  When you run your function t1, you're scheduling something to happen at time +1 in the hub, but then stop everything for 2 seconds (never going back to the gevent hub, never letting anything else run) then returning True... then letting the hub run afterwards.  By the time you called back to the hub, the function had already completed - it ran longer than the timeout, but the hub had no way to stop it before that happened.

As damjan suggested, if change the sleep from time.sleep to gevent.sleep, then when the sleep function is called, the thread suspends and passes execution back to the hub.  Once there, the hub is running again and it can do what you've told it: wait a second, generate the exception and halt further scheduled execution of the function (return from the gevent.sleep).

Because of this, it's very important to ensure that anything that may block execution is friendly to gevent.  If you have a very long running function that does some intense calculation that takes many seconds to complete and it makes no calls to the hub, then the hub will not run.  One solution may be to add periodic gevent.sleep(0) calls - this suspends the thread, allows the hub to run anything it needs to, then resumes the calculation without waiting.

Ian.
--
This email is intended for the use of the individual addressee(s) named above and may contain information that is confidential, privileged or unsuitable for overly sensitive persons with low self-esteem, no sense of humor or irrational religious beliefs. If you are not the intended recipient, any dissemination, distribution or copying of this email is not authorized (either explicitly or implicitly) and constitutes an irritating social faux pas. Unless the word absquatulation has been used in its correct context somewhere other than in this warning, it does not have any legal or grammatical use and may be ignored. No animals were harmed in the transmission of this email, although the yorkshire terrier next door is living on borrowed time, let me tell you. Those of you with an overwhelming fear of the unknown will be gratified to learn that there is no hidden message revealed by reading this warning backwards, so just ignore that Alert Notice from Microsoft: However, by pouring a complete circle of salt around yourself and your computer you can ensure that no harm befalls you and your pets. If you have received this email in error, please add some nutmeg and egg whites and place it in a warm oven for 40 minutes. Whisk briefly and let it stand for 2 hours before icing.

Dan Callaghan

unread,
Oct 16, 2012, 6:39:15 PM10/16/12
to gevent
Excerpts from Jan-Philip Gehrcke's message of 2012-10-17 08:15:59 +1000:
The real time.sleep function is not aware of gevent's event loop and
just blocks the whole process. Try calling gevent's sleep function
instead -- either directly calling gevent.sleep, or using
gevent.monkey.patch_all() before calling time.sleep.

Gevent is *co-operative* multi-tasking which means your code must only
call blocking functions with co-operate with gevent's event loop.

--
Dan Callaghan <d...@djc.id.au>

Jan-Philip Gehrcke

unread,
Oct 17, 2012, 6:56:17 AM10/17/12
to gev...@googlegroups.com
Thanks for all insights so far. This is what I understood: the function I want to timeout-control has to explicitly allow for context switches to the gevent hub. When I use the monkey patch method any blocking call in that function leads to context switches and the Timeout exception is raised as expected.

However, when I don't use monkey patching, as I have got from Ian's answer, I can use for example gevent.sleep(0) in order to provoke a context switch. I've tried that:

import gevent
import time

def f(gsleep):
    with gevent.Timeout(seconds=1):
        time.sleep(2)
        gevent.sleep(gsleep)
        return True

print f(0) # prints True
print f(0.01) # prints True
print f(0.1) # raises Timeout


time.sleep(2) makes sure that we've exceeded the timeout and I would like to detect it afterwards just before returning True. gevent.sleep(0) seems to be too short for the hub(?) to realize that the timeout occurred, as seems 0.01 s. 0.1 s then is enough.

One conclusion is that using gevent's Timeout without monkey patching all blocking calls in the function to be timeout-controlled is kind of senseless. Would you agree? Maybe I should use a method based on Python's signal module.

Jan-Philip

Danil "Eleneldil G. Arilou" Lavrentyuk

unread,
Oct 17, 2012, 7:06:45 AM10/17/12
to gev...@googlegroups.com
I've tried you example and have got the Timeout exception even with 0.00001 s.
But with gevent.sleep(0) -- no Timeout exception has been raised.

2012/10/17 Jan-Philip Gehrcke <jgeh...@googlemail.com>:

Aleksandar Kordic

unread,
Oct 17, 2012, 7:07:55 AM10/17/12
to gev...@googlegroups.com
> One conclusion is that using gevent's Timeout without monkey patching all
> blocking calls in the function to be timeout-controlled is kind of
> senseless. Would you agree? Maybe I should use a method based on Python's
> signal module.
>
> Jan-Philip

The important rule is not to use blocking (not patched) functions at
all. You render gevent useless when using time.sleep() .
Reply all
Reply to author
Forward
0 new messages