-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enforce new connection when last activity > than (idle interval + TTL) #719
Conversation
Source/ARTRealtime.m
Outdated
NSTimeInterval intervalSinceLast = [[NSDate date] timeIntervalSinceDate:_lastActivity]; | ||
|
||
// We want to enforce a new connection also when there hasn't been activity for longer than (idle interval + TTL) | ||
if(self.connection.state_nosync == ARTRealtimeClosing || intervalSinceLast > (_maxIdleInterval + _connectionStateTtl)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think that could easily create too many connections. I'll check that in more detail tomorrow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed, tests are failing and from the tests...
[15:26:38]: ▸ ✗ Connection__connection_request_fails__should_transition_to_Failed_state_because_the_token_is_invalid_and_not_renewable, expected to equal <15>, got <50>
[15:26:38]: ▸ ✗ Connection__connection_request_fails__should_transition_to_Failed_state_because_the_token_is_invalid_and_not_renewable, Should have only one connection request fail
this is not good.
Source/ARTRealtime.m
Outdated
NSTimeInterval intervalSinceLast = [[NSDate date] timeIntervalSinceDate:_lastActivity]; | ||
|
||
// We want to enforce a new connection also when there hasn't been activity for longer than (idle interval + TTL) | ||
if(self.connection.state_nosync == ARTRealtimeClosing || intervalSinceLast > (_maxIdleInterval + _connectionStateTtl)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed, tests are failing and from the tests...
[15:26:38]: ▸ ✗ Connection__connection_request_fails__should_transition_to_Failed_state_because_the_token_is_invalid_and_not_renewable, expected to equal <15>, got <50>
[15:26:38]: ▸ ✗ Connection__connection_request_fails__should_transition_to_Failed_state_because_the_token_is_invalid_and_not_renewable, Should have only one connection request fail
this is not good.
Took another stab deb79a7 |
Have a look at the tests in ably/ably-ruby#146 for the cases we need to test |
@paddybyers Will do |
I do think we should have test cases included before approving the PR. |
@funkyboy I think we should get into the habit with spec changes of writing tests that fail, then making them pass. This approach IMHO is in most cases wrong. If you look back at how we worked with @ricardopereira in the past, he wrote tests that failed but met the spec, then Toni wrote the code. I am not sure anyone can review this code in any meaningful way without tests. |
@mattheworiordan will do. Won't merge this without having tests, which I am working on. |
@mattheworiordan you can read
as the counterpart of the following Ruby
|
Ok, so then if the client is running in it's own thread, whilst the test is sleeping for X time, what's stopping it from reconnecting itself automatically? |
It puts to sleep the whole test context, on the main thread: https://github.com/ably/ably-ios/blob/develop/Spec/TestUtilities.swift#L498. The client also belongs to that thread. |
@ricardopereira added test. Ready for another look. |
@funkyboy I don't think that is sufficient. See https://github.com/ably/ably-ruby/pull/146/files#diff-6994e351b062efe17b0b5c88ac75991f for comparison. I test both positive & negative conditions, as well as the channel attach requirements in the specification. I don't believe this is comprehensive enough. |
Oh you have added more Ruby tests since yesterday :)
|
…sed since last activity
@mattheworiordan @paddybyers @ricardopereira ready for another look |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few small comments
client.connection.once(.connected) { _ in | ||
expect(client.connection.id).toNot(beNil()) | ||
connectionId = client.connection.id! | ||
client.connectionStateTtl = customTtlInterval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the client really allow these intervals to be changed once it is instanced? Or is this a test thing that allows us to "hack" these values on a running client? Obviously if customers tried the same thing they'd get very odd and unpredictable behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For our customers connectionStateTtl
is a private readonly property.
We have a bunch of methods to expose and set private variables for whitebox testing.
connectionStateTtl
is one of those https://github.com/ably/ably-ios/blob/develop/Source/ARTRealtime%2BPrivate.h#L51
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for clarifying
Spec/RealtimeClientConnection.swift
Outdated
@@ -2690,6 +2690,42 @@ class RealtimeClientConnection: QuickSpec { | |||
} | |||
} | |||
} | |||
|
|||
it("reattaches to the same channels after a new connection has been established") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we not includ the specification IDs in all tests so they are greppable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattheworiordan done 3bb7493
Spec/RealtimeClientConnection.swift
Outdated
client.connection.once(.connecting) { _ in | ||
client.connection.once(.connected) { _ in | ||
expect(client.connection.id).toNot(equal(connectionId)) | ||
let newChannel = client.channels.get(channelName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you instancing a new channel? The title of this tests says "reattaches to the same channels after a new connection has been established", yet you've just called get to get a new channel. Should you not be checking that the existing channel object you have emits an additional attached event once the connection becomes connected, indicating that it's been reattached by the client? See https://github.com/ably/ably-ruby/pull/146/files#diff-6994e351b062efe17b0b5c88ac75991fR714
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattheworiordan done d1c7a3c
Spec/RealtimeClientConnection.swift
Outdated
// We want this to be > than the sum of the two above | ||
let reconnectAfterInterval: TimeInterval = 5.0 | ||
client = AblyTests.newRealtime(options) | ||
client.connect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@funkyboy I usually add a defer
closing the current connection. The connection will remain active. Something like defer { client.close() }
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ricardopereira done 8565908
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good but I think you should close the current realtime connections.
@mattheworiordan If you don't have any further observations, I'd merge this. |
@funkyboy I was not issued with a request to review this again so I was not aware it was awaiting a review. This looks good to me. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks
After talking to @paddybyers and after working on the Java version of this feature I realized that these tests are wrong, in that they should wait for the
|
After another discussion about RTN15g it turns out that this (fresh connection after connectionTtl + idle timer has passed) doesn't need to go through the suspended state. I tweaked the tests so that the reconnection happens via the state machine instead of calling |
3917a81
to
4ea8dc0
Compare
Ok, logic seems good |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is merged, but I believe there are issues here
expect(client.maxIdleInterval).to(equal(customIdleInterval)) | ||
client.connection.once(.connecting) { _ in | ||
let reconnectionInterval = Date().timeIntervalSince(disconnectedAt) | ||
expect(reconnectionInterval).to(beGreaterThan(client.connectionStateTtl + client.maxIdleInterval)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this the case? I don't see any explicit request to pause / delay the reconnecting. What has caused this delay then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattheworiordan It was this option, which tries the reconnection after 5s the client has been in a DISCONNECTED
state https://github.com/ably/ably-ios/blob/develop/Spec/RealtimeClientConnection.swift#L2657
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this is not according to the spec. The client will retry to reconnect immediately after disconnect, see https://docs.ably.io/client-lib-development-guide/features/#RTN15a, not wait disconnectedRetryTimeout
. It only waits disconnectedRetryTimeout
on subsequent requests.
} | ||
} | ||
} | ||
client.onDisconnected() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this done before the attach is completed? The sequence of events for this test is wrong i.e. the initial attach
may only happen once the connection becomes connected again, in fact that is very likely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattheworiordan I don't understand. Would you prefer to see this within the attach
block and right after this assertion? https://github.com/ably/ably-ios/blob/develop/Spec/RealtimeClientConnection.swift#L2708
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly, otherwise how do we know that the channel attached first and thus gets reattached after reconnected?
client.connection.once(.disconnected) { _ in | ||
client.connection.once(.connecting) { _ in | ||
client.connection.once(.connected) { _ in | ||
expect(client.connection.id).toNot(equal(connectionId)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the connection ID changing? There is no pause as far as I can see.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This it
block uses the same settings declared up here https://github.com/ably/ably-ios/blob/develop/Spec/RealtimeClientConnection.swift#L2655
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, there should be no pause according to https://docs.ably.io/client-lib-development-guide/features/#RTN15a
client.connection.once(.connecting) { _ in | ||
client.connection.once(.connected) { _ in | ||
expect(client.connection.id).toNot(equal(connectionId)) | ||
channel.once(.attached) { _ in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are not checking that the attached ChannelStateChange has a flag indicating loss of continuity on the channel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattheworiordan you mean I should check if resumed
is true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
options.disconnectedRetryTimeout = 5.0 | ||
var client: ARTRealtime! | ||
var connectionId = "" | ||
let customTtlInterval: TimeInterval = 20.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why even set this, surely a standard connection should reconnect automatically without any custom settings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattheworiordan good spot. This is not needed.
@mattheworiordan Made an issue with your feedback on this PR after merge. |
Enforces a new connection when the last activity is > than
(_maxIdleInterval + _connectionStateTtl)
Mimics this behavior in JS, called when a connection gets started.
cc @paddybyers