mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-26 20:59:27 +01:00
Create a commands.process function which runs a function inside a separate process.
This is the only way to limit the execution time of a possibly long-running python statement. Use this on String.re, due to the possibility of pathologically long re matching in python. This allows us to remove the 'trusted-only' restriction on string.re. In the future, this should probably be used in other places that take user-supplied regexps, such as 'misc last --regexp', for example, as well as other potentially long-running tasks that can block the bot. Signed-off-by: James McCoy <jamessan@users.sourceforge.net>
This commit is contained in:
parent
d691a91636
commit
a2985c37d6
@ -34,10 +34,10 @@ import binascii
|
||||
import supybot.utils as utils
|
||||
from supybot.commands import *
|
||||
import supybot.plugins as plugins
|
||||
import supybot.commands as commands
|
||||
import supybot.ircutils as ircutils
|
||||
import supybot.callbacks as callbacks
|
||||
|
||||
|
||||
class String(callbacks.Plugin):
|
||||
def ord(self, irc, msg, args, letter):
|
||||
"""<letter>
|
||||
@ -141,10 +141,10 @@ class String(callbacks.Plugin):
|
||||
s = 'You probably don\'t want to match the empty string.'
|
||||
irc.error(s)
|
||||
else:
|
||||
irc.reply(f(text))
|
||||
re = wrap(re, [('checkCapability', 'trusted'),
|
||||
first('regexpMatcher', 'regexpReplacer'),
|
||||
'text'])
|
||||
v = commands.process(f, text, timeout=10)
|
||||
irc.reply(v)
|
||||
re = thread(wrap(re, [first('regexpMatcher', 'regexpReplacer'),
|
||||
'text']))
|
||||
|
||||
def xor(self, irc, msg, args, password, text):
|
||||
"""<password> <text>
|
||||
|
@ -970,6 +970,23 @@ class CommandThread(world.SupyThread):
|
||||
finally:
|
||||
self.cb.threaded = self.originalThreaded
|
||||
|
||||
class CommandProcess(world.SupyProcess):
|
||||
"""Just does some extra logging and error-recovery for commands that need
|
||||
to run in processes.
|
||||
"""
|
||||
def __init__(self, target=None, args=(), kwargs={}):
|
||||
self.command = args[0]
|
||||
self.cb = target.im_self
|
||||
procName = 'Process #%s (for %s.%s)' % (world.processesSpawned,
|
||||
self.cb.name(),
|
||||
self.command)
|
||||
log.debug('Spawning process %s (args: %r)', procName, args)
|
||||
self.__parent = super(CommandProcess, self)
|
||||
self.__parent.__init__(target=target, name=procName,
|
||||
args=args, kwargs=kwargs)
|
||||
|
||||
def run(self):
|
||||
self.__parent.run()
|
||||
|
||||
class CanonicalString(registry.NormalizedString):
|
||||
def normalize(self, s):
|
||||
|
@ -33,10 +33,12 @@ Includes wrappers for commands.
|
||||
"""
|
||||
|
||||
import time
|
||||
import Queue
|
||||
import types
|
||||
import getopt
|
||||
import inspect
|
||||
import threading
|
||||
import multiprocessing
|
||||
|
||||
from . import callbacks, conf, ircdb, ircmsgs, ircutils, log, utils, world
|
||||
|
||||
@ -59,6 +61,29 @@ def thread(f):
|
||||
f(self, irc, msg, args, *L, **kwargs)
|
||||
return utils.python.changeFunctionName(newf, f.func_name, f.__doc__)
|
||||
|
||||
def process(f, *args, **kwargs):
|
||||
"""Runs a function in a subprocess.
|
||||
Takes an extra timeout argument, which, if supplied, limits the length
|
||||
of execution of target function to <timeout> seconds."""
|
||||
timeout = kwargs.pop('timeout')
|
||||
q = multiprocessing.Queue()
|
||||
def newf(f, q, *args, **kwargs):
|
||||
r = f(*args, **kwargs)
|
||||
q.put(r)
|
||||
targetArgs = (f, q,) + args
|
||||
p = world.SupyProcess(target=newf,
|
||||
args=targetArgs, kwargs=kwargs)
|
||||
p.start()
|
||||
p.join(timeout)
|
||||
if p.is_alive():
|
||||
p.terminate()
|
||||
q.put("Function call aborted due to timeout.")
|
||||
try:
|
||||
v = q.get(block=False)
|
||||
except Queue.Empty:
|
||||
v = "Nothing returned."
|
||||
return v
|
||||
|
||||
class UrlSnarfThread(world.SupyThread):
|
||||
def __init__(self, *args, **kwargs):
|
||||
assert 'url' in kwargs
|
||||
|
10
src/world.py
10
src/world.py
@ -37,6 +37,7 @@ import sys
|
||||
import time
|
||||
import atexit
|
||||
import threading
|
||||
import multiprocessing
|
||||
|
||||
if sys.version_info >= (2, 5, 0):
|
||||
import re as sre
|
||||
@ -63,6 +64,15 @@ class SupyThread(threading.Thread):
|
||||
super(SupyThread, self).__init__(*args, **kwargs)
|
||||
log.debug('Spawning thread %q.', self.getName())
|
||||
|
||||
processesSpawned = 1 # Starts at one for the initial process.
|
||||
class SupyProcess(multiprocessing.Process):
|
||||
def __init__(self, *args, **kwargs):
|
||||
global processesSpawned
|
||||
processesSpawned += 1
|
||||
super(SupyProcess, self).__init__(*args, **kwargs)
|
||||
log.debug('Spawning process %q.', self.name)
|
||||
|
||||
|
||||
commandsProcessed = 0
|
||||
|
||||
ircs = [] # A list of all the IRCs.
|
||||
|
Loading…
Reference in New Issue
Block a user