3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-23 19:19:31 +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.aborted = threading.Event()
self.reply_lock = threading.Lock()
self.reply_lock = threading.RLock()
self.pingTimer = None
@ -559,27 +559,38 @@ class Irc(utils.DeprecatedAttributesObject):
# replies across relay.
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):
"""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
(channel or PM).
"""
if private is None:
# Allow using private replies as the default, if no explicit setting was given.
private = conf.conf['bot'].get("prefer_private_replies")
# Private reply is enabled, or the caller was originally a PM
if private or (self.called_in in self.users):
if not force_privmsg_in_private:
# For private replies, the default is to override the notice=True/False argument,
# and send replies as notices regardless. This is standard behaviour for most
# IRC services, but can be disabled if force_privmsg_in_private is given.
notice = True
target = self.called_by
else:
target = self.called_in
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:
if private is None:
# Allow using private replies as the default, if no explicit setting was given.
private = conf.conf['bot'].get("prefer_private_replies")
# Private reply is enabled, or the caller was originally a PM
if private or (self.called_in in self.users):
if not force_privmsg_in_private:
# For private replies, the default is to override the notice=True/False argument,
# and send replies as notices regardless. This is standard behaviour for most
# IRC services, but can be disabled if force_privmsg_in_private is given.
notice = True
target = self.called_by
else:
target = self.called_in
self.msg(target, text, notice=notice, source=source, loopback=loopback)
self._reply(*args, **kwargs)
def error(self, text, **kwargs):
"""Replies with an error to the last caller in the right context (channel or PM)."""

View File

@ -103,19 +103,19 @@ def remote(irc, source, args):
del kwargs['source']
irc.reply(text, source=irc.pseudoclient.uid, **kwargs)
old_reply = remoteirc.reply
old_reply = remoteirc._reply
with remoteirc.reply_lock:
try: # Remotely call the command (use the PyLink client as a dummy user).
# Override the remote irc.reply() to send replies HERE.
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,
' '.join(args.command))
finally:
# Restore the original remoteirc.reply()
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.
remoteirc.pseudoclient.account = ''