Skip to content
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

[ECO-5117] Fix channel ATTACH/DETACH state checks #1046

Merged
merged 2 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions lib/src/main/java/io/ably/lib/realtime/ChannelBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,19 @@ private void attachImpl(final boolean forceReattach, final CompletionListener li
if(!forceReattach) {
/* check preconditions */
switch(state) {
case attaching:
case attaching: //RTL4h
if(listener != null) {
on(new ChannelStateCompletionListener(listener, ChannelState.attached, ChannelState.failed));
}
return;
case detaching: //RTL4h
pendingAttachRequest = new AttachRequest(forceReattach,listener);
return;
case attached:
case attached: //RTL4a
callCompletionListenerSuccess(listener);
return;
case failed: //RTL4g
this.reason = null;
default:
}
}
Expand Down Expand Up @@ -312,19 +314,28 @@ private void detachImpl(CompletionListener listener) throws AblyException {
Log.v(TAG, "detach(); channel = " + name);
/* check preconditions */
switch(state) {
case initialized:
case initialized: // RTL5a
case detached: {
callCompletionListenerSuccess(listener);
return;
}
case detaching:
case detaching: //RTL5i
if (listener != null) {
on(new ChannelStateCompletionListener(listener, ChannelState.detached, ChannelState.failed));
}
return;
case attaching: //RTL5i
pendingDetachRequest = new DetachRequest(listener);
return;
case failed: //RTL5b
ErrorInfo error = this.reason != null ?
this.reason : new ErrorInfo("Channel state is failed", 90000);
callCompletionListenerError(listener, error);
return;
case suspended: //RTL5j
setState(ChannelState.detached, null);
callCompletionListenerSuccess(listener);
return;
default:
}
ConnectionManager connectionManager = ably.connection.connectionManager;
Expand Down
133 changes: 132 additions & 1 deletion lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,7 @@ public void attach_success_callback() {
Helpers.CompletionWaiter waiter = new Helpers.CompletionWaiter();
channel.attach(waiter);
new ChannelWaiter(channel).waitFor(ChannelState.attached);
assertEquals("Verify failed state reached", channel.state, ChannelState.attached);
assertEquals("Verify attached state reached", channel.state, ChannelState.attached);

/* Verify onSuccess callback gets called */
waiter.waitFor();
Expand All @@ -944,6 +944,59 @@ public void attach_success_callback() {
}
}

@Test
public void attach_success_callback_for_channel_in_failed_state() {
AblyRealtime ably = null;
try {
ClientOptions opts = createOptions(testVars.keys[0].keyStr);
ably = new AblyRealtime(opts);

/* wait until connected */
(new ConnectionWaiter(ably.connection)).waitFor(ConnectionState.connected);

/* create a channel and attach */
final Channel channel = ably.channels.get("attach_success");
ChannelWaiter channelWaiter = new ChannelWaiter(channel);
channel.attach();
channelWaiter.waitFor(ChannelState.attached);

// Simulate connection failure
ably.connection.connectionManager.requestState(
new ConnectionManager.StateIndication(
ConnectionState.failed,
new ErrorInfo("Simulated connection failure", 40000)
)
);

// Wait for the channel to reach the failed state
channelWaiter.waitFor(ChannelState.failed);

assertNotNull(channel.reason);
assertEquals("Simulated connection failure", channel.reason.message);

ably.connect();

Helpers.CompletionWaiter attachListener = new Helpers.CompletionWaiter();
channel.attach(attachListener);

channelWaiter.waitFor(ChannelState.attaching);
assertNull(channel.reason);
channelWaiter.waitFor(ChannelState.attached);

assertEquals("Verify attached state reached", ChannelState.attached, channel.state);

/* Verify onSuccess callback gets called */
attachListener.waitFor();
assertTrue(attachListener.success);
} catch (AblyException e) {
e.printStackTrace();
fail("init0: Unexpected exception instantiating library");
} finally {
if(ably != null)
ably.close();
}
}

/**
* When client failed to attach to a channel, verify
* attach {@code CompletionListener#onError(ErrorInfo)}
Expand Down Expand Up @@ -1015,6 +1068,84 @@ public void detach_success_callback_initialized() {
}
}

@Test
public void detach_success_callback_on_suspended_state() {
AblyRealtime ably = null;
try {
ClientOptions opts = createOptions(testVars.keys[0].keyStr);
ably = new AblyRealtime(opts);

/* wait until connected */
(new ConnectionWaiter(ably.connection)).waitFor(ConnectionState.connected);

/* create a channel and attach */
final Channel channel = ably.channels.get("detach_success");
ChannelWaiter channelWaiter = new ChannelWaiter(channel);
channel.attach();
channelWaiter.waitFor(ChannelState.attached);

ably.connection.connectionManager.requestState(ConnectionState.suspended);

channelWaiter.waitFor(ChannelState.suspended);
assertEquals("Verify suspended state reached", ChannelState.suspended, channel.state);

/* detach */
Helpers.CompletionWaiter detachWaiter = new Helpers.CompletionWaiter();
channel.detach(detachWaiter);

/* Verify onSuccess callback gets called */
detachWaiter.waitFor();
assertTrue(detachWaiter.success);
} catch (AblyException e) {
e.printStackTrace();
fail("init0: Unexpected exception instantiating library");
} finally {
if(ably != null)
ably.close();
}
}

@Test
public void detach_failure_callback_on_failed_state() {
AblyRealtime ably = null;
try {
ClientOptions opts = createOptions(testVars.keys[0].keyStr);
ably = new AblyRealtime(opts);

/* wait until connected */
(new ConnectionWaiter(ably.connection)).waitFor(ConnectionState.connected);

/* create a channel and attach */
final Channel channel = ably.channels.get("detach_failure");
ChannelWaiter channelWaiter = new ChannelWaiter(channel);
channel.attach();
channelWaiter.waitFor(ChannelState.attached);

// Simulate connection failure
ably.connection.connectionManager.requestState(ConnectionState.failed);

channelWaiter.waitFor(ChannelState.failed);
assertEquals("Verify failed state reached", ChannelState.failed, channel.state);

/* detach */
Helpers.CompletionWaiter detachWaiter = new Helpers.CompletionWaiter();
channel.detach(detachWaiter);

/* Verify onSuccess callback gets called */
detachWaiter.waitFor();
assertFalse(detachWaiter.success);
assertNotNull(detachWaiter.error);
assertEquals("Channel state is failed", detachWaiter.error.message);
assertEquals(90000, detachWaiter.error.code);
} catch (AblyException e) {
e.printStackTrace();
fail("init0: Unexpected exception instantiating library");
} finally {
if(ably != null)
ably.close();
}
}

/**
* When client detaches from a channel successfully after attached state,
* verify attach {@code CompletionListener#onSuccess()} gets called.
Expand Down
Loading