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-5099] Update RoomStatus and ConnectionStatus interfaces #48

Closed
Closed
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
96 changes: 96 additions & 0 deletions chat-android/src/main/java/com/ably/chat/Connection.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,73 @@
package com.ably.chat

import io.ably.lib.types.ErrorInfo

/**
* Default timeout for transient states before we attempt handle them as a state change.
*/
const val TRANSIENT_TIMEOUT = 5000

/**
* The different states that the connection can be in through its lifecycle.
*/
enum class ConnectionStatus(val stateName: String) {
/**
* A temporary state for when the library is first initialized.
*/
Initialized("initialized"),

/**
* The library is currently connecting to Ably.
*/
Connecting("connecting"),

/**
* The library is currently connected to Ably.
*/
Connected("connected"),

/**
* The library is currently disconnected from Ably, but will attempt to reconnect.
*/
Disconnected("disconnected"),

/**
* The library is in an extended state of disconnection, but will attempt to reconnect.
*/
Suspended("suspended"),

/**
* The library is currently disconnected from Ably and will not attempt to reconnect.
*/
Failed("failed"),
}

/**
* Represents a change in the status of the connection.
*/
data class ConnectionStatusChange(
/**
* The new status of the connection.
*/
val current: ConnectionStatus,

/**
* The previous status of the connection.
*/
val previous: ConnectionStatus,

/**
* An error that provides a reason why the connection has
* entered the new status, if applicable.
*/
val error: ErrorInfo?,

/**
* The time in milliseconds that the client will wait before attempting to reconnect.
*/
val retryIn: Long?,
)

/**
* Represents a connection to Ably.
*/
Expand All @@ -8,4 +76,32 @@ interface Connection {
* The current status of the connection.
*/
val status: ConnectionStatus

/**
* The current error, if any, that caused the connection to enter the current status.
*/
val error: ErrorInfo?

/**
* Registers a listener that will be called whenever the connection status changes.
* @param listener The function to call when the status changes.
* @returns An object that can be used to unregister the listener.
*/
fun onStatusChange(listener: Listener): Subscription

/**
* An interface for listening to changes for the connection status
*/
fun interface Listener {
/**
* A function that can be called when the connection status changes.
* @param change The change in status.
*/
fun connectionStatusChanged(change: ConnectionStatusChange)
}

/**
* Removes all listeners that were added by the `onStatusChange` method.
*/
fun offAllStatusChange()
}
101 changes: 0 additions & 101 deletions chat-android/src/main/java/com/ably/chat/ConnectionStatus.kt

This file was deleted.

52 changes: 42 additions & 10 deletions chat-android/src/main/java/com/ably/chat/Room.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package com.ably.chat

import io.ably.lib.types.ErrorInfo
import io.ably.lib.util.Log.LogHandler
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -56,20 +57,37 @@ interface Room {
*/
val occupancy: Occupancy

/**
* Returns the room options.
*
* @returns A copy of the options used to create the room.
*/
val options: RoomOptions

/**
* (CHA-RS2)
* Returns an object that can be used to observe the status of the room.
* The current status of the room.
*
* @returns The status observable.
* @returns The current status.
*/
val status: RoomStatus

/**
* Returns the room options.
*
* @returns A copy of the options used to create the room.
* The current error, if any, that caused the room to enter the current status.
*/
val options: RoomOptions
val error: ErrorInfo?

/**
* Registers a listener that will be called whenever the room status changes.
* @param listener The function to call when the status changes.
* @returns An object that can be used to unregister the listener.
*/
fun onStatusChange(listener: RoomLifecycle.Listener): Subscription

/**
* Removes all listeners that were added by the `onStatusChange` method.
*/
fun offAllStatusChange()

/**
* Attaches to the room to receive events in realtime.
Expand Down Expand Up @@ -98,7 +116,6 @@ internal class DefaultRoom(
) : Room {

private val _logger = logger
override val status = DefaultStatus(logger)

/**
* RoomScope is a crucial part of the Room lifecycle. It manages sequential and atomic operations.
Expand Down Expand Up @@ -132,9 +149,18 @@ internal class DefaultRoom(
override val occupancy = DefaultOccupancy(
messages = messages,
)

private var _lifecycleManager: RoomLifecycleManager? = null

private val _statusLifecycle = DefaultRoomLifecycle(logger)
internal val statusLifecycle: DefaultRoomLifecycle
get() = _statusLifecycle

override val status: RoomStatus
get() = _statusLifecycle.status

override val error: ErrorInfo?
get() = _statusLifecycle.error

Comment on lines +154 to +163
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Thread-safety concerns identified in status lifecycle management

The current implementation of DefaultRoomLifecycle has potential thread-safety issues:

  • Mutable state (_status and _error) is accessed without synchronization
  • Status updates through setStatus method modify shared state and emit events without thread coordination
  • The EventEmitter base class implementation needs to be verified for thread-safe event handling

Recommendations:

  • Add @Synchronized to the setStatus methods
  • Consider using AtomicReference for _status and _error fields
  • Ensure EventEmitter base class has thread-safe listener management
🔗 Analysis chain

Verify thread-safety of status lifecycle management.

The status lifecycle implementation looks good, but let's verify the thread-safety of status updates.


Let me gather more information about the DefaultRoomLifecycle implementation to verify its thread-safety.


Let me check for any synchronization mechanisms in the RoomStatus.kt file and look for event listeners that might be involved in concurrent access.


Let me check the implementation of RoomStatusEventEmitter and AtomicCoroutineScope since they seem to be key components in handling concurrent operations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any concurrent modifications to _statusLifecycle
rg -A 5 "_statusLifecycle\." --type kotlin

# Look for other implementations of DefaultRoomLifecycle to verify thread-safety
ast-grep --pattern 'class $_ : DefaultRoomLifecycle {
  $$$
}'

Length of output: 10485


Script:

#!/bin/bash
# Look for DefaultRoomLifecycle implementation
ast-grep --pattern 'class DefaultRoomLifecycle {
  $$$
}'

# Search for concurrent access patterns in DefaultRoomLifecycle
rg -A 10 "class DefaultRoomLifecycle" --type kotlin

# Look for synchronization mechanisms
rg "@Synchronized|@Volatile|synchronized|AtomicReference" --type kotlin DefaultRoomLifecycle

Length of output: 1213


Script:

#!/bin/bash
# Check the complete implementation in RoomStatus.kt
rg -A 30 "class DefaultRoomLifecycle" chat-android/src/main/java/com/ably/chat/RoomStatus.kt

# Look for event listener implementations
ast-grep --pattern 'interface $_ : RoomLifecycle.Listener {
  $$$
}'

# Check if there are any concurrent collections used
rg "ConcurrentHashMap|Collections\.synchronized|concurrent\." --type kotlin

Length of output: 1440


Script:

#!/bin/bash
# Check RoomStatusEventEmitter implementation
rg -A 20 "class RoomStatusEventEmitter" --type kotlin

# Check AtomicCoroutineScope implementation
rg -A 20 "class AtomicCoroutineScope" --type kotlin

# Look for any synchronization in status updates
rg -B 5 -A 10 "setStatus" chat-android/src/main/java/com/ably/chat/RoomStatus.kt

Length of output: 8045

init {
/**
* TODO
Expand All @@ -143,15 +169,21 @@ internal class DefaultRoom(
* Currently, all features are initialized by default.
*/
val features = listOf(messages, presence, typing, reactions, occupancy)
_lifecycleManager = RoomLifecycleManager(roomScope, status, features, _logger)
_lifecycleManager = RoomLifecycleManager(roomScope, _statusLifecycle, features, _logger)
/**
* TODO
* Make sure previous release op. for same was a success.
* Make sure channels were removed using realtime.channels.release(contributor.channel.name);
* Once this is a success, set room to initialized, if not set it to failed and throw error.
* Note that impl. can change based on recent proposed changes to chat-room-lifecycle DR.
*/
this.status.setStatus(RoomLifecycle.Initialized)
this._statusLifecycle.setStatus(RoomStatus.Initialized)
}
Comment on lines +172 to +181
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Address TODO comments and verify initialization sequence.

The initialization logic contains multiple TODO comments indicating incomplete implementation:

  1. Feature initialization based on RoomOptions
  2. Channel release verification
  3. Proper error handling during initialization

These TODOs seem critical for proper room lifecycle management.

Would you like me to help create GitHub issues to track these TODOs? They should cover:

  • Feature initialization based on RoomOptions
  • Channel release verification
  • Error handling during initialization


override fun onStatusChange(listener: RoomLifecycle.Listener): Subscription = _statusLifecycle.onChange(listener)

override fun offAllStatusChange() {
_statusLifecycle.offAll()
}

override suspend fun attach() {
Expand Down
Loading