An exploration of ideas for the API and implementation of the next generation RTMClient in Python
At a minimum, the RTMClient
must provide a means for application developers to bring their own multiprocessing
opinions and implementaitons.
The simplest implementation an app developer might use is exactly what we've recommended every developer use in the current version of the package: an infinite loop that polls for new data in the main thread of a single process.
from slackclient import RTMClient
# Initialize a client for a single workspace
client = RTMClient(os.environ["SLACK_TOKEN"])
# An example event handler
def handle_team_join(event_data):
pass
# Blocks until the websocket connection is set up and a `hello` message is recieved
client.start()
while True:
# Immediately returns event data that was ready to read from the websocket
# Returns a tuple of the event type and data. When there's no data ready, both are None
event_type, event_data = client.read()
if event_type == "team_join":
handle_team_join(event_data)
# ... add other event types here ...
# Slow down polling to prevent spinning too much
sleep(1)
RTMClient.read()
can potentially be a source of latency in the app.
In order to understand why, let's establish some
basic facts. First, the reason an app would use sleep(1)
in the manner shown above is to prevent a
spinlock, which just burns CPU cycles. However, finding the right amount of time
to wait (1
second in the previous example) is tricky. Let's call that number the polling interval. If the polling
interval is too long, then in a busy workspace the client will end up buffering many incoming events during the polling
interval, but only process one. This means that if the polling interval is longer than the average time between events, the
app will never be able to process all the events as it will never reach the end of the buffer. If the polling interval is
too short, then the app will waste CPU cycles.
The solution to this problem is to make RTMClient.read()
return all events in the buffer, instead of just one at a time.
This all events in the buffer to be processed before any wait time begins. It also allows for more sophistocated
multiprocessing solutions (besides this infinite loop) to more efficiently schedule processing.
I'd recommend that RTMClient.read()
should return an iterable of the tuple of (event_type, event_data)
. If that change
is made, the previous example becomes the following:
from slackclient import RTMClient
# Initialize a client for a single workspace
client = RTMClient(os.environ["SLACK_TOKEN"])
# An example event handler
def handle_team_join(event_data):
pass
# Blocks until the websocket connection is set up and a `hello` message is recieved
client.start()
while True:
# Immediately returns event data that was ready to read from the websocket
# Returns a tuple of the event type and data. When there's no data ready, both are None
for event_type, event_data in client.read():
if event_type == "team_join":
handle_team_join(event_data)
# ... add other event types here ...
# Slow down polling to prevent spinning too much
sleep(1)
That's a lot of boilerplate code that has to be exactly right, especially for a beginner. We have an opportunity to eliminate most of this to serve the most common use cases and beginners.
from slackclient import RTMClient, RTMDispatcher
client = RTMClient(os.environ["SLACK_TOKEN"])
rtm_event = RTMDispatcher(client)
@rtm_event(type="team_join")
def handle_team_join(event_data)
pass
client.start()