From ad03058ede486b95617ddcef887fd1de633c93bf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 14 Oct 2024 11:56:29 -0300 Subject: [PATCH] Implement CHA-RL3c (From the same spec as referenced in 25e5052.) Resolves #52. --- Sources/AblyChat/RoomLifecycleManager.swift | 6 ++- .../RoomLifecycleManagerTests.swift | 46 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Sources/AblyChat/RoomLifecycleManager.swift b/Sources/AblyChat/RoomLifecycleManager.swift index c13c894d..bf197a30 100644 --- a/Sources/AblyChat/RoomLifecycleManager.swift +++ b/Sources/AblyChat/RoomLifecycleManager.swift @@ -717,7 +717,11 @@ internal actor RoomLifecycleManager { // CHA-RL3b changeStatus(to: .released) return - case .releasing, .initialized, .attached, .attaching, .detaching, .suspended, .failed: + case let .releasing(releaseOperationID): + // CHA-RL3c + // swiftlint:disable:next force_try + return try! await resultOfOperationWithID(releaseOperationID, waitingOperationID: operationID).get() + case .initialized, .attached, .attaching, .detaching, .suspended, .failed: break } diff --git a/Tests/AblyChatTests/RoomLifecycleManagerTests.swift b/Tests/AblyChatTests/RoomLifecycleManagerTests.swift index 45d3be84..0a5b25fe 100644 --- a/Tests/AblyChatTests/RoomLifecycleManagerTests.swift +++ b/Tests/AblyChatTests/RoomLifecycleManagerTests.swift @@ -650,6 +650,52 @@ struct RoomLifecycleManagerTests { #expect(await contributor.channel.detachCallCount == 0) } + // @specPartial CHA-RL3c - This is marked as specPartial because at time of writing the spec point CHA-RL3c has been accidentally duplicated to specify two separate behaviours. TODO: change this one to @spec once spec fixed (see discussion in https://github.com/ably/specification/pull/200#discussion_r1763770348) + @Test + func release_whenReleasing() async throws { + // Given: A RoomLifecycleManager with a RELEASE lifecycle operation in progress, and hence in the RELEASING state + let contributorDetachOperation = SignallableChannelOperation() + let contributor = createContributor( + // This allows us to prolong the execution of the RELEASE triggered in (1) + detachBehavior: contributorDetachOperation.behavior + ) + let manager = await createManager(contributors: [contributor]) + + let firstReleaseOperationID = UUID() + let secondReleaseOperationID = UUID() + + let statusChangeSubscription = await manager.onChange(bufferingPolicy: .unbounded) + // Wait for the manager to enter RELEASING; this our sign that the DETACH operation triggered in (1) has started + async let releasingStatusChange = statusChangeSubscription.first { $0.current == .releasing } + + // (1) This is the "RELEASE lifecycle operation in progress" mentioned in Given + async let _ = manager.performReleaseOperation(testsOnly_forcingOperationID: firstReleaseOperationID) + _ = await releasingStatusChange + + let operationWaitEventSubscription = await manager.testsOnly_subscribeToOperationWaitEvents() + async let secondReleaseWaitingForFirstReleaseEvent = operationWaitEventSubscription.first { operationWaitEvent in + operationWaitEvent == .init(waitingOperationID: secondReleaseOperationID, waitedOperationID: firstReleaseOperationID) + } + + // When: `performReleaseOperation()` is called on the lifecycle manager + async let secondReleaseResult: Void = manager.performReleaseOperation(testsOnly_forcingOperationID: secondReleaseOperationID) + + // Then: + // - the manager informs us that the second RELEASE operation is waiting for first RELEASE operation to complete + // - when the first RELEASE completes, the second RELEASE operation also completes + // - the second RELEASE operation does not perform any side-effects (which we check here by confirming that the CHA-RL3d contributor detach only happened once, i.e. was only performed by the first RELEASE operation) + + _ = try #require(await secondReleaseWaitingForFirstReleaseEvent) + + // Allow the first RELEASE to complete + contributorDetachOperation.complete(result: .success) + + // Check that the second RELEASE completes + await secondReleaseResult + + #expect(await contributor.channel.detachCallCount == 1) + } + // @specPartial CHA-RL3c - Haven’t implemented the part that refers to "transient disconnect timeouts"; TODO do this (https://github.com/ably-labs/ably-chat-swift/issues/48) @Test func release_transitionsToReleasing() async throws {