Skip to content

Commit

Permalink
Merge branch 'main' into min-ios14-swift-mig
Browse files Browse the repository at this point in the history
  • Loading branch information
umair-ably committed May 20, 2024
2 parents fa57545 + e5efbdd commit f330227
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 424 deletions.
35 changes: 13 additions & 22 deletions Source/ARTGCD.m
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#import "ARTGCD.h"

@interface ARTScheduledBlockHandle ()

// Mark this as `atomic` to syncronize access to it from `_scheduledBlock` and `cancel`.
@property (atomic, copy, nullable) dispatch_block_t block;

@end

@implementation ARTScheduledBlockHandle {
dispatch_semaphore_t _semaphore;
dispatch_block_t _block;
dispatch_block_t _scheduledBlock;
}

Expand All @@ -11,44 +16,30 @@ - (instancetype)initWithDelay:(NSTimeInterval)delay queue:(dispatch_queue_t)queu
if (self == nil)
return nil;

// Use a sempaphore to coorindate state. We use a reference here to decouple it when creating a block we'll schedule.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

__weak ARTScheduledBlockHandle *weakSelf = self;
_scheduledBlock = dispatch_block_create(0, ^{
dispatch_block_t copiedBlock = nil;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
{
// Get a strong reference to self within our semaphore to avoid potential race conditions
ARTScheduledBlockHandle *strongSelf = weakSelf;
if (strongSelf != nil) {
copiedBlock = strongSelf->_block; // copied below
}
ARTScheduledBlockHandle *strongSelf = weakSelf;
if (strongSelf != nil) {
copiedBlock = strongSelf.block; // copied below
}
dispatch_semaphore_signal(semaphore);

// If our block is non-nil, our scheduled block was still valid by the time this was invoked
if (copiedBlock != nil) {
copiedBlock();
}
});

_block = [block copy];
_semaphore = semaphore;
self.block = block; // copied block

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC * delay)), queue, _scheduledBlock);

return self;
}

- (void)cancel {
// Cancel within our semaphore for predictable behavior if our block is invoked while we're cancelling
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
{
dispatch_block_cancel(_scheduledBlock);
_block = nil;
}
dispatch_semaphore_signal(_semaphore);
self.block = nil;
dispatch_block_cancel(_scheduledBlock);
}

- (void)dealloc {
Expand Down
3 changes: 2 additions & 1 deletion Source/ARTRealtimeChannel.m
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ - (void)emit:(ARTChannelEvent)event with:(ARTChannelStateChange *)data {

- (void)performTransitionToState:(ARTRealtimeChannelState)state withParams:(ARTChannelStateChangeParams *)params {
ARTLogDebug(self.logger, @"RT:%p C:%p (%@) channel state transitions from %tu - %@ to %tu - %@%@", _realtime, self, self.name, self.state_nosync, ARTRealtimeChannelStateToStr(self.state_nosync), state, ARTRealtimeChannelStateToStr(state), params.retryAttempt ? [NSString stringWithFormat: @" (result of %@)", params.retryAttempt.id] : @"");
ARTChannelStateChange *stateChange = [[ARTChannelStateChange alloc] initWithCurrent:state previous:self.state_nosync event:(ARTChannelEvent)state reason:params.errorInfo resumed:NO retryAttempt:params.retryAttempt];
ARTChannelStateChange *stateChange = [[ARTChannelStateChange alloc] initWithCurrent:state previous:self.state_nosync event:(ARTChannelEvent)state reason:params.errorInfo resumed:params.resumed retryAttempt:params.retryAttempt];
self.state = state;

if (params.storeErrorInfo) {
Expand Down Expand Up @@ -680,6 +680,7 @@ - (void)setAttached:(ARTProtocolMessage *)message {
} else {
params = [[ARTChannelStateChangeParams alloc] initWithState:ARTStateOk];
}
params.resumed = message.resumed;
[self performTransitionToState:ARTRealtimeChannelAttached withParams:params];
[self.presence onAttached:message];
[_attachedEventEmitter emit:nil with:nil];
Expand Down
5 changes: 5 additions & 0 deletions Source/PrivateHeaders/Ably/ARTChannelStateChangeParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ NS_SWIFT_NAME(ChannelStateChangeParams)

@property (nullable, nonatomic, readonly) ARTRetryAttempt *retryAttempt;

/**
The `resumed` value of the `ARTProtocolMessage` that triggered this state change.
*/
@property (nonatomic) BOOL resumed;

- (instancetype)init NS_UNAVAILABLE;

/**
Expand Down
1 change: 0 additions & 1 deletion Test/Test Utilities/TestUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1709,7 +1709,6 @@ extension ARTRealtime {
guard let transport = self.internal.transport as? TestProxyTransport else {
fail("TestProxyTransport is not set"); return
}
channel.internal.presence.startSync()
transport.send(syncMessage)
}
}
Expand Down
89 changes: 88 additions & 1 deletion Test/Tests/RealtimeClientChannelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,95 @@ class RealtimeClientChannelTests: XCTestCase {
}
}

// TODO: RTL2f
// RTL2f (connection resumption case)
func test__011a__Channel__EventEmitter__channel_states_and_events__ChannelStateChange_will_contain_a_resumed_boolean_attribute_with_value__true__if_the_bit_flag_RESUMED_was_included() throws {
let test = Test()
let options = try AblyTests.commonAppSetup(for: test)
options.tokenDetails = try getTestTokenDetails(for: test, ttl: 5.0)
let client = ARTRealtime(options: options)
defer { client.dispose(); client.close() }
let channel = client.channels.get(test.uniqueChannelName())

waitUntil(timeout: testTimeout) { done in
let partialDone = AblyTests.splitDone(4, done: done)
channel.on { stateChange in
switch stateChange.current {
case .attached:
XCTAssertFalse(stateChange.resumed)
partialDone()
default:
XCTAssertFalse(stateChange.resumed)
}
}
client.connection.once(.disconnected) { stateChange in
channel.off()
guard let error = stateChange.reason else {
fail("Error is nil"); done(); return
}
XCTAssertEqual(error.code, ARTErrorCode.tokenExpired.intValue)
XCTAssertEqual(channel.state, ARTRealtimeChannelState.attached)
client.connection.once(.connected) { stateChange in
XCTAssertEqual(channel.state, ARTRealtimeChannelState.attaching)
partialDone()
}
channel.on { stateChange in
switch stateChange.current {
case .attached:
XCTAssertTrue(stateChange.resumed)
partialDone()
default:
XCTAssertFalse(stateChange.resumed)
}
}
partialDone()
}
channel.attach()
}
}

// RTL2f (connection recovery case)
func test__011b__Channel__EventEmitter__channel_states_and_events__ChannelStateChange_will_contain_a_resumed_boolean_attribute_with_value__true__if_the_bit_flag_RESUMED_was_included_for_recovered_connection() throws {
let test = Test()
let options = try AblyTests.commonAppSetup(for: test)
let client = ARTRealtime(options: options)
defer { client.dispose(); client.close() }

let channelName = test.uniqueChannelName()
let channel = client.channels.get(channelName)

waitUntil(timeout: testTimeout) { done in
channel.on { stateChange in
switch stateChange.current {
case .attached:
XCTAssertFalse(stateChange.resumed)
done()
default:
XCTAssertFalse(stateChange.resumed)
}
}
channel.publish(nil, data: "A message")
}

options.recover = client.connection.createRecoveryKey()

let recoveredClient = ARTRealtime(options: options)
defer { recoveredClient.dispose(); recoveredClient.close() }

let recoveredChannel = recoveredClient.channels.get(channelName)

waitUntil(timeout: testTimeout) { done in
recoveredChannel.on { stateChange in
switch stateChange.current {
case .attached:
XCTAssertTrue(stateChange.resumed)
done()
default:
XCTAssertFalse(stateChange.resumed)
}
}
}
}

// RTL3

// RTL3a
Expand Down
Loading

0 comments on commit f330227

Please sign in to comment.