3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-01 01:09:22 +01:00

Split Irc.reply() into _reply() to make 'networks.remote' actually thread-safe

Previously, the Irc.reply_lock check was in the reply() function itself: replacing it with another function checking for the same lock would delay execution,
but then run the wrong reply() code if another module used irc.reply() while 'remote' was executing.
This commit is contained in:
James Lu 2017-03-31 16:25:28 -07:00
parent 40fa4f71bc
commit 9d9b01839c
2 changed files with 33 additions and 22 deletions

View File

@ -62,7 +62,7 @@ class Irc(utils.DeprecatedAttributesObject):
self.connected = threading.Event() self.connected = threading.Event()
self.aborted = threading.Event() self.aborted = threading.Event()
self.reply_lock = threading.Lock() self.reply_lock = threading.RLock()
self.pingTimer = None self.pingTimer = None
@ -559,11 +559,12 @@ class Irc(utils.DeprecatedAttributesObject):
# replies across relay. # replies across relay.
self.callHooks([source, cmd, {'target': target, 'text': text}]) self.callHooks([source, cmd, {'target': target, 'text': text}])
def reply(self, text, notice=None, source=None, private=None, force_privmsg_in_private=False, def _reply(self, text, notice=None, source=None, private=None, force_privmsg_in_private=False,
loopback=True): loopback=True):
"""Replies to the last caller in the right context (channel or PM).""" """
Core of the reply() function - replies to the last caller in the right context
with self.reply_lock: (channel or PM).
"""
if private is None: if private is None:
# Allow using private replies as the default, if no explicit setting was given. # Allow using private replies as the default, if no explicit setting was given.
private = conf.conf['bot'].get("prefer_private_replies") private = conf.conf['bot'].get("prefer_private_replies")
@ -581,6 +582,16 @@ class Irc(utils.DeprecatedAttributesObject):
self.msg(target, text, notice=notice, source=source, loopback=loopback) self.msg(target, text, notice=notice, source=source, loopback=loopback)
def reply(self, *args, **kwargs):
"""
Replies to the last caller in the right context (channel or PM).
This function wraps around _reply() and can be monkey-patched in a thread-safe manner
to temporarily redirect plugin output to another target.
"""
with self.reply_lock:
self._reply(*args, **kwargs)
def error(self, text, **kwargs): def error(self, text, **kwargs):
"""Replies with an error to the last caller in the right context (channel or PM).""" """Replies with an error to the last caller in the right context (channel or PM)."""
# This is a stub to alias error to reply # This is a stub to alias error to reply

View File

@ -103,19 +103,19 @@ def remote(irc, source, args):
del kwargs['source'] del kwargs['source']
irc.reply(text, source=irc.pseudoclient.uid, **kwargs) irc.reply(text, source=irc.pseudoclient.uid, **kwargs)
old_reply = remoteirc.reply old_reply = remoteirc._reply
with remoteirc.reply_lock: with remoteirc.reply_lock:
try: # Remotely call the command (use the PyLink client as a dummy user). try: # Remotely call the command (use the PyLink client as a dummy user).
# Override the remote irc.reply() to send replies HERE. # Override the remote irc.reply() to send replies HERE.
log.debug('(%s) networks.remote: overriding reply() of IRC object %s', irc.name, netname) log.debug('(%s) networks.remote: overriding reply() of IRC object %s', irc.name, netname)
remoteirc.reply = types.MethodType(_remote_reply, remoteirc) remoteirc._reply = types.MethodType(_remote_reply, remoteirc)
world.services[args.service].call_cmd(remoteirc, remoteirc.pseudoclient.uid, world.services[args.service].call_cmd(remoteirc, remoteirc.pseudoclient.uid,
' '.join(args.command)) ' '.join(args.command))
finally: finally:
# Restore the original remoteirc.reply() # Restore the original remoteirc.reply()
log.debug('(%s) networks.remote: restoring reply() of IRC object %s', irc.name, netname) log.debug('(%s) networks.remote: restoring reply() of IRC object %s', irc.name, netname)
remoteirc.reply = old_reply remoteirc._reply = old_reply
# Remove the identification override after we finish. # Remove the identification override after we finish.
remoteirc.pseudoclient.account = '' remoteirc.pseudoclient.account = ''