Issue #24 resolved

Support asynchronous events

Jason R. Coombs
created an issue

Some users of pmxbot have expressed the need to sometimes direct the bot to transmit messages based on external events. The basic paradigm of pmxbot, however, is to run as a single-thread synchronously. Something should be devised to allow plugins to react to external events asynchronously (or cooperatively).

Comments (4)

  1. Jason R. Coombs reporter

    In the most recent version of pmxbot (1104.4), there's experimental support for plugins to start up a thread and transmit messages asynchronously. The bot instance is exposed as pmxbot.core._bot. It's exposed as a global, private variable because it may change, but we're using it internally, so feel free to give it a try. Here's an example of a plugin that uses the bot to relay messages from an external source:

    def _get_all_messages():
        "yield channel, msg forever, blocking"
    
    class RelayThread(threading.thread):
        def run(self):
            for channel, msg in self._get_all_message():
               pmxbot.core._bot.out(channel, msg, log=False)
    
    # set up a callback to start the thread immediately after on_welcome is triggered
    @execdelay('relayer', channel=None, howlong=0)
    def start_relayer(conn, event):
        RelayThread().start()
    

    I'm torn - this implementation is fairly simple, and it seems to work, but it's messy, and it bypasses most of the machinery of pmxbot. I'm tempted instead to provide another @exec decorator that runs synchronously during every idle period of the bot, and which passes the bot instance instead of only the underlying connection. Something like:

    def get_messages():
        "get any messages, if available"
    
    @exec_global('relayer', howlong=0, repeat=True)
    def relay_messages(bot):
         for channel, msg in get_messages():
              bot.out(channel, msg, log=False)
    

    Another alternative, very similar to the current paradigm, is to always have the output yielded or returned, but allow the target channel to be indicated in the result:

    @execdelay('relayer', channel=None, howlong=0, repeat=True)
    def relay_messages(conn, event):
        for channel, msg in get_messages():
           yield pmxbot.core.SwitchChannel(channel)
           yield msg
    

    That would enable a single handler to transmit messages to multiple channels (if appropriate), but maintains the synchronous mode.

    The more I think about it, the more I like this last mode, because it's more consistent with the pmxbot design.

  2. Jason R. Coombs reporter

    In pmxbot 1105.0, I've implemented the SwitchChannel Sentinel. In 1105.2, I've also implemented a FinalizerRegistry for registering callbacks that need to be invoked at exit (as a long-running thread or other service might need to be finalized to shut down properly). Using this new capability, I was able to implement fairly easily an HTTP interface for pmxbot (to relay messages from an HTTP POST) in jaraco.pmxbot.http.

    Note that setting howlong=0 and repeat=True in execdelay doesn't work and causes the IRC client to spin in an infinite loop when running the delay handlers. Currently, howlong must be > 0.

  3. Chris Jowett

    Just tested this out and it works great! Actually I am using your http API code as is, since I don't need anything fancy, but it gives me a really easy way to have external systems send messages into a channel without requiring the bot to constantly poll them!

    Awesome work as always!

  4. Log in to comment