Skip to content

Auto reconnect RTM#297

Merged
Roach merged 24 commits intomasterfrom
roach/rtm-reconnect
Mar 20, 2018
Merged

Auto reconnect RTM#297
Roach merged 24 commits intomasterfrom
roach/rtm-reconnect

Conversation

@Roach
Copy link
Contributor

@Roach Roach commented Mar 3, 2018

Summary

Adds auto_reconnect to RTM

Requirements (place an x in each [ ])

@codecov
Copy link

codecov bot commented Mar 3, 2018

Codecov Report

Merging #297 into master will increase coverage by 12.6%.
The diff coverage is 71.73%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #297      +/-   ##
==========================================
+ Coverage   59.82%   72.43%   +12.6%     
==========================================
  Files          10       10              
  Lines         341      370      +29     
  Branches       62       66       +4     
==========================================
+ Hits          204      268      +64     
+ Misses        121       92      -29     
+ Partials       16       10       -6
Impacted Files Coverage Δ
slackclient/client.py 51.66% <60%> (+15.15%) ⬆️
slackclient/server.py 82.14% <73.17%> (+21.84%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bca6bcc...2f5be5e. Read the comment docs.

result = json.loads(response_body)
except ValueError as json_decode_error:
raise ParseResponseError(response_body, json_decode_error)
if self.server:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just noticed that this check didn't make it over. was it superfluous in the first place? can self.server ever not be set?

Copy link
Contributor Author

@Roach Roach Mar 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aoberoi I don't think there's a way it wouldn't be set...

It's set when the client is initialized:
https://github.com/slackapi/python-slackclient/blob/master/slackclient/client.py#L30-L33

@Roach Roach added RTM labels Mar 18, 2018
@Roach Roach added this to the 1.x milestone Mar 18, 2018
@Roach Roach self-assigned this Mar 18, 2018

def test_server(server):
def test_server():
server = Server("valid_token", connect=False, )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing comma cool here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

def test_server():
server = Server("valid_token", connect=False, )
assert type(server) == Server
assert server == "valid_token"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the server is coerced into a string? is that a useful feature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea. It's a Preexisting Feature™

I've never used it, but I'm sure someone has at some point during the history of this project.

assert userbyname != "someotheruser"
user_by_name = server.users.find('fakeuser')
assert type(user_by_name) == User
assert user_by_name == "fakeuser"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users are coerced into a string? is that a useful feature?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm especially worried about this because we'd like to de-emphasize programmatic usage of username.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is from a refactor of the "SearchList" class that allowed searching through the client's cache by username or user ID. I just cleaned up the names for readability.

userbyid = server.users.find('invaliduser')
assert type(userbyid) != User
user_by_id = server.users.find('invaliduser')
assert type(user_by_id) != User
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, its type isn't a user, but what is the type? maybe that's what the assertion should check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made it check for None instead 👍

json={"ok": False}
)

with pytest.raises(SlackConnectionError) as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure what this is meant to do. how i'm currently reading this: "when a SlackConnectionError happens, ping the server". what does this accomplish other than swallowing the error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to write assertions about raised exceptions, you can use pytest.raises as a context manager

https://docs.pytest.org/en/latest/assert.html#assertions-about-expected-exceptions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, but there's no assertion about the error in the body

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe i misunderstood. server.ping() causes the error, then wrapping it in pytest.raises(ErrorName) asserts that the error occurred and it is of that type?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the call to rtm_connect instead of ping, to more accurately reflect what it's testing

server.rtm_connect(auto_reconnect=True, reconnect=True, use_rtm_start=False)

for call in rsps.calls:
assert call.request.url in [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain how this test works? i don't see how the timeout could have been hit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more context to the test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure i understand. what i see is connecting when the last_connected_at time is now will immediately cause another connection attempt. that sounds like the opposite of what your comment says should happen.

try:
self.server.rtm_connect(use_rtm_start=with_team_state, **kwargs)
return True
if self.server.connected:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return self.server.connected

response_body = self.server.api_call(method, timeout=timeout, **kwargs)
try:
result = json.loads(response_body)
except ValueError as json_decode_error:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep this up here so it's more clear what Errors you handle for which lines

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was still there, just moved way down into the wrong scope...

if method == 'im.open':
if "ok" in result and result["ok"]:
self.server.attach_channel(kwargs["user"], result["channel"]["id"])
# If the call was for a `group` or `im` endpoint, attach the group data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found that inline comments like this are some combination of three things:

  1. A good sign to break out into a separate function
  2. Suggest better naming of variables
  3. Superfluous and potentially going to be out-of-date after a refactor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realized these could just reuse server.parse_channel_data is they were passed as a single-item array

elif self.reconnect_attempt > 0:
# Back off after the the first attempt
if (time.time() - self.last_connected_at) < 180:
# Random offset to prevent stampeding reconnects of multiple RTM clients
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call the variable backoff_offset_multiplyer and then you'll be covered and don't need the comment

if self.reconnect_attempt == 5:
raise SlackConnectionError("RTM connection failed, reached max reconnects.")
elif self.reconnect_attempt > 0:
# Back off after the the first attempt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add some debug logging in this chunk of the code like you did below, that also "cheats" and gives you a chance to add a little more documentation inline with the code

json=rtm_start_fixture
)

server.rtm_connect(auto_reconnect=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want some tests that do rsps.add multiple times, so you can fail a few times and ensure you reconnect after N tries, right? (I might've missed this tho).

Essentially have ~3 rsps.add, the first two return a 4xx and then you get a 200 for the third. That'll prove your reconnect handles errors properly 💪

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reconnect logic only tests for when the socket is disconnected, then reconnected. It's not handling connection retries. That's part of another project 😄



def test_rtm_reconnect_timeout_recently_connected(server, rtm_start_fixture):
# If reconnected recently, server must wait to reconnect and increment the counter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to make these docstrings to tie these to the function for some python-y tooling.

# If this is an auto reconnect, rate limit reconnect attempts
if self.auto_reconnect and reconnect:
# Raise a SlackConnectionError after 5 retries within 3 minutes
if self.reconnect_attempt == 5:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a value that shouldn't be hidden down here in the guts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I'm not quite sure where else it could go 🤔

# Random offset to prevent stampeding reconnects of multiple RTM clients
offset = random.randint(1, 4)
reconnect_timeout = (offset * self.reconnect_attempt * self.reconnect_attempt)
logging.debug("Reconnecting in {} seconds".format(reconnect_timeout))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logging.debug("Reconnecting in %d seconds", reconnect_timeout)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL

@Roach Roach merged commit 07cce9b into master Mar 20, 2018
@Roach Roach deleted the roach/rtm-reconnect branch March 20, 2018 23:53
lavabyrd pushed a commit that referenced this pull request Apr 25, 2019
* Added reconnect logic to RTM client
* Added a lot more tests
* updated RTM test fixture

* Disable testing for 3.6 on Windows until 3.6.5 release due to a windows web socket bug: https://bugs.python.org/issue32394
c-goosen pushed a commit to c-goosen/python-slackclient that referenced this pull request Jun 18, 2019
* Added reconnect logic to RTM client
* Added a lot more tests
* updated RTM test fixture

* Disable testing for 3.6 on Windows until 3.6.5 release due to a windows web socket bug: https://bugs.python.org/issue32394
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants