exec code with timeout?

  • Thread starter OKB (not okblacke)
  • Start date
O

OKB (not okblacke)

I am fiddling around with a Python-based MUD which allows users to
code MUD objects in Python. This code is executed from within the
server code with "exec". However, sometimes errors in user code can
result in infinite loops, which cause the MUD to hang. I am wondering
if there is a way to put a timeout on the exec, so that it returns
control to the main program if the user code doesn't return within a
certain amount of time.

I am not concerned with security per se here -- in general I can
assume that any users who are using the MUD are trusted and will not
write malicious code. I am chiefly aiming to catch simple,
unintentional programming errors. For instance, I don't want the MUD
to hang just because someone innocently forgot to include a break
inside a "while 1:" block. All I need is a very crude form of control
-- a simple timeout would do.

I have looked at the thread modules, hoping that I could exec the
user code in a separate thread, sleep in the main thread, and then at
the end of the time limit terminate the user code thread. However,
there doesn't seem to be any way to terminate a subthread from within
the main thread. Am I entirely out of luck here, or is there some
module than can do what I want? (I am working primarily on Windows at
this point, but I would like a solution that works on as many
platforms as as possible.)
 
G

Graham Fawcett

OKB said:
I am fiddling around with a Python-based MUD which allows users to
code MUD objects in Python. This code is executed from within the
server code with "exec".
Danger, Will Robinson! Unchecked exec() calls are the path to ruin!
However, sometimes errors in user code can
result in infinite loops, which cause the MUD to hang. I am wondering
if there is a way to put a timeout on the exec, so that it returns
control to the main program if the user code doesn't return within a
certain amount of time.

I am not concerned with security per se here -- in general I can
assume that any users who are using the MUD are trusted and will not
write malicious code.

Hanlon's Razor: "Never attribute to malice that which can be adequately
explained by stupidity."
I am chiefly aiming to catch simple,
unintentional programming errors. For instance, I don't want the MUD
to hang just because someone innocently forgot to include a break
inside a "while 1:" block. All I need is a very crude form of control
-- a simple timeout would do.

The Python 2.3 C API now provides a function, PyThreadState_SetAsyncExc,
that
will let you raise an exception in another thread asynchronously, thereby
taking it down. But I don't think there's a Python wrapper function for
it. I
suppose you could write your own...

The RestrictedPython module in Zope may suit your needs; it was designed
for a
similar use scenario. Search the list, there was a discussion about
RestrictedPython not long ago.


-- Graham
 
O

OKB (not okblacke)

Graham said:
The RestrictedPython module in Zope may suit your needs; it was
designed for a
similar use scenario. Search the list, there was a discussion about
RestrictedPython not long ago.

Thanks. I looked at the page that was referenced from here, but it
says RestrictedPython "doesn't implement resource limitations, like
preventing scripts from eating up all available RAM or simply never
terminating". Preventing scripts from never terminating is exactly what
I am interested in. (This isn't a halting problem kind of thing -- I
just want to unceremoniously kill exec-ed code that doesn't terminate in
a given amount of time.) Also, it seems a bit heavyweight. The page
implies that it uses a modified compiler, etc.; I don't know how that
would work with running interactively edited code.
 
G

Graham Fawcett

OKB said:
Graham Fawcett wrote:




Thanks. I looked at the page that was referenced from here, but it
says RestrictedPython "doesn't implement resource limitations, like
preventing scripts from eating up all available RAM or simply never
terminating". Preventing scripts from never terminating is exactly what
I am interested in. (This isn't a halting problem kind of thing -- I
just want to unceremoniously kill exec-ed code that doesn't terminate in
a given amount of time.) Also, it seems a bit heavyweight. The page
implies that it uses a modified compiler, etc.; I don't know how that
would work with running interactively edited code.

Hmm... well, again I'd suggest the PyThreadState_SetAsyncExc function. It
shouldn't be that hard to wrap... I'd try it myself, but haven't made it
to 2.3 yet. :(

Or, if you're in Unix-land, how about forking? Let the user code
run in the child fork, and let the other half take care of the timeout?
It's easy to kill processes.

-- G
 
A

Alex Martelli

Graham Fawcett wrote:
...
...
Hmm... well, again I'd suggest the PyThreadState_SetAsyncExc function. It
shouldn't be that hard to wrap... I'd try it myself, but haven't made it
to 2.3 yet. :(

Nope -- the attacking script can easily use try/except to defeat attempts
to kill it via PyThreadState_SetAsyncExc . The latter isn't meant for any
"adversarial" situation, but just for potentially _buggy_ threads.

Or, if you're in Unix-land, how about forking? Let the user code
run in the child fork, and let the other half take care of the timeout?
It's easy to kill processes.

That's definitely the best way to go, particularly if you can couple
it with some mechanism such as the BSDs' "jail" concept -- let the
operating system deal with malicious processes (your task in terms of
architecture design then boils down to the inevitable one of defining
the "least-privileges" each given process absolutely HAS to have).


Alex
 
A

Alex Martelli

John said:
Alex Martelli said:
Graham Fawcett wrote: [...]
Hmm... well, again I'd suggest the PyThreadState_SetAsyncExc function.
It shouldn't be that hard to wrap... I'd try it myself, but haven't
made it
to 2.3 yet. :(

Nope -- the attacking script can easily use try/except to defeat attempts
to kill it via PyThreadState_SetAsyncExc . The latter isn't meant for
any "adversarial" situation, but just for potentially _buggy_ threads.
[...]

Which is what the OP asked for, actually -- the users are trusted.

I think you're right -- I had not understood the OP's specs correctly.
In this case, the OP's application seems to be exactly THE typical
use case for the new functionality (which has been kept in C only to
avoid attracting _inappropriate_ use...). See my lightning talk at
OSCON, slides at http://www.strakt.com/docs/os03_threads_interrupt.pdf ,
for a brief explanations of the how's & why and C source code to
wrap the new call if you KNOW it's exactly what you need.


Alex
 
P

Paul Moore

Alex Martelli said:
John said:
Alex Martelli said:
Nope -- the attacking script can easily use try/except to defeat attempts
to kill it via PyThreadState_SetAsyncExc . The latter isn't meant for
any "adversarial" situation, but just for potentially _buggy_ threads.
[...]

Which is what the OP asked for, actually -- the users are trusted.

I think you're right -- I had not understood the OP's specs correctly.
In this case, the OP's application seems to be exactly THE typical
use case for the new functionality (which has been kept in C only to
avoid attracting _inappropriate_ use...). See my lightning talk at
OSCON, slides at http://www.strakt.com/docs/os03_threads_interrupt.pdf ,
for a brief explanations of the how's & why and C source code to
wrap the new call if you KNOW it's exactly what you need.

If
1. You run the code you want to interrupt in the main thread
2. You don't mind the interrupt being a KeyboardInterrupt exception

then you can write the code in pure Python, as follows:
.... def wait(sec):
.... time.sleep(sec)
.... thread.interrupt_main()
.... thread.start_new_thread(wait, (sec,))

Example:
.... pass
....

-- 5 seconds later...

Traceback (most recent call last):
File "<stdin>", line 2, in ?
KeyboardInterrupt

Hope this helps,
Paul.
 
O

OKB (not okblacke)

Paul said:
... def wait(sec):
... time.sleep(sec)
... thread.interrupt_main()
... thread.start_new_thread(wait, (sec,))

This looks perfect! Many thanks.
 
P

Paul Moore

Paul Moore said:
If
1. You run the code you want to interrupt in the main thread
2. You don't mind the interrupt being a KeyboardInterrupt exception

then you can write the code in pure Python, as follows:

A module to wrap PyThreadState_SetAsyncExc isn't exactly hard, either.
Here's one in its entirety (async.c). I'm not sure I believe the
argument in the reference manual that this function is not exposed to
Python to avoid accidental mistakes - why not expose it, but with a
suitably "internal" name, and documentation warnings (much like
sys._getframe). It could easily be thread._async_exc(), for example.

Anyway, here's the code if it's of any interest...

Paul

#include <Python.h>

static PyObject *
async_exc(PyObject *self, PyObject *args)
{
PyObject *exc;
int id;
int n;

if (!PyArg_ParseTuple(args, "iO:async_exc", &id, &exc))
return NULL;

n = PyThreadState_SetAsyncExc(id, exc);
if (n > 1) {
/* Problem - clear the exception... */
PyThreadState_SetAsyncExc(id, NULL);
return NULL;
}

Py_INCREF(Py_None);
return Py_None;
}

static PyMethodDef ModuleMethods[] = {
{"async_exc", async_exc, METH_VARARGS,
"Raise an exception in the given thread."},
{NULL, NULL, 0, NULL} /* Sentinel */
};

PyMODINIT_FUNC
initasync(void)
{
Py_InitModule("async", ModuleMethods);
}
 
J

JCM

OKB (not okblacke) said:
I am fiddling around with a Python-based MUD which allows users to
code MUD objects in Python. This code is executed from within the
server code with "exec". However, sometimes errors in user code can
result in infinite loops, which cause the MUD to hang. I am wondering
if there is a way to put a timeout on the exec, so that it returns
control to the main program if the user code doesn't return within a
certain amount of time.

We've done exactly this for our own MUD project :)

The way we got around the infinite loop problem was to have the main
Python thread be the one that runs all unknown MUD-code. You can use
signals (on unix) to raise an exception in the main thread. I don't
remember offhand what function we used--maybe os.kill?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

use of exec() 12
exec with partial globals 5
Remote SSH and Configuring code help 0
Cancel threads after timeout 0
Help with code 0
Trouble with code 2
Need help with this Python code. 2
Code efficiency 3

Members online

No members online now.

Forum statistics

Threads
474,113
Messages
2,570,688
Members
47,269
Latest member
VitoYwo03

Latest Threads

Top