1

I'm writing a program like

results = []
for i in range(30):
    x = 4 ** 5 ** i
    results.append(x)

However, when i grows bigger (and the result is not too big to raise OverflowError), it will take a long time to calculate the result. So I want to set a timeout mechanic that will continue if the computation time exceeds, say, 1 second.

I tried to use eventlet package like

from eventlet import Timeout
with Timeout(1) as timeout:
    x = 4 ** 5 ** 20

But the timeout is not working. (Maybe simply because I'm executing only one line of code.) Other methods I found from StackOverflow failed as well...

Is there a possible, programming way to set a timeout for this "simple but complicated" calculation? Or, is there an alternative way? (e.g. Exit when the result is too big...)

Thanks for your help!

  • The power operator in python is, unlike any other, right associative. That means `a ** b ** c == a ** (b ** c)`. Since `5 ** 20 == 9.536743e+13` that means `4 ** 5 ** 20` will be 10^26 bits long. Your computer has about 10^11 bits of memory. Instead of timing out this operation (which would require some kind of thread), just don't do it when you won't get an answer. – FHTMitchell Jul 10 '18 at 09:57
  • Thanks. Actually I'm facing a string in every loop and using `eval` to evaluate it... Some calculations are so subtle that it cannot raise an `OverflowError`. How to build a criteria would be a problem... – Yuzhang Xie Jul 10 '18 at 10:06
  • you're using eval? https://stackoverflow.com/questions/1832940/why-is-using-eval-a-bad-practice – FHTMitchell Jul 10 '18 at 10:06
  • Yes. I found `eval` from some articles, and `eval` may be a "quick and dirty" solution to some problems (though it's pretty bad)... I may as well find an alternative way to solve my problem. – Yuzhang Xie Jul 10 '18 at 10:18
  • However this question still exists without `eval`... – Yuzhang Xie Jul 10 '18 at 10:19

1 Answers1

1

First of all, trying to use eventlet for this is the wrong tool. eventlet is primarily for non-blocking I/O using coroutines in the form of greenlets. It's got little to do with CPU-bound problems (in fact the docs for eventlet.Timeout read):

If the code block in the try/finally or with-block never cooperatively yields, the timeout cannot be raised. In Eventlet, this should rarely be a problem, but be aware that you cannot time out CPU-only operations with this class.

What this means is that if you have some block of code that's performing a CPU-bound operation, you still won't break out of it because the code never yields to another thread. This is more for cases where you might have some code that, say, checks if a socket has data to read on it, and if not yields.

If you want to set an interrupt after some time period to interrupt a long-running calculation, you can use signal.alarm and set a SIGALRM handler. You can wrap this all up in a context manager like:

>>> import signal
>>> from contextlib import contextmanager
>>> class TimeoutError(RuntimeError): pass
...
>>> @contextmanager
... def timeout(seconds):
...     def handler(*args):
...         raise TimeoutError("timed out after {} seconds".format(seconds))
...     orig_handler = signal.signal(signal.SIGALRM, handler)
...     signal.alarm(seconds)
...     try:
...         yield
...     finally:
...         signal.alarm(0)
...         signal.signal(signal.SIGALRM, orig_handler)
...
>>> with timeout(10):
...     while True: pass
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 4, in handler
__main__.TimeoutError: timed out after 10 seconds
>>> with timeout(10):
...     print(1 + 1)
...
2
>>> with timeout(10):
...     4 ** 5 ** 20
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 4, in handler
__main__.TimeoutError: timed out after 10 seconds

Note: This will only work on *NIX systems, though there are ways to do this on Windows as well.

Iguananaut
  • 21,810
  • 5
  • 50
  • 63
  • I'm sorry, but if I run `4 ** 5 ** 20` it doesn't work. – Yuzhang Xie Jul 10 '18 at 11:04
  • It works for me. What Python version are you using? What did you pass for the timeout? – Iguananaut Jul 10 '18 at 17:58
  • I'm using Python 3.6.3 on Linux (Ubuntu 16.04 LTS), and I set a timeout of 3 seconds. It just stuck there. – Yuzhang Xie Jul 11 '18 at 04:30
  • @YuzhangXie Please see this log: https://gist.github.com/embray/2186319d8e68158b40273684200706a8#file-stackoverflow-51262008-log (this was Python 3.6.5 instead of 3.6.3, but I don't think that should make a difference--also I don't know how you installed Python 3.6; I had to install it from a PPA). – Iguananaut Jul 11 '18 at 10:39
  • You're not trying to run this code in a thread are you? Because that makes things a little more complicated. This is a proof of concept only. – Iguananaut Jul 11 '18 at 10:46