Skip to content

Commit

Permalink
Merge pull request #1046 from ably/fix/channel-state-checks-before-de…
Browse files Browse the repository at this point in the history
…tach-attach

[ECO-5117] Fix channel ATTACH/DETACH state checks
  • Loading branch information
sacOO7 authored Nov 20, 2024
2 parents dc4d464 + 47cd42f commit 17624ce
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 5 deletions.
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

0 comments on commit 17624ce

Please sign in to comment.