Skip to content
Open
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
33 changes: 24 additions & 9 deletions Sources/CMAB/CmabService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,40 @@ class DefaultCmabService: CmabService {
private let cmabCache: LruCache<String, CmabCacheValue>
private let logger = OPTLoggerFactory.getLogger()

private static let NUM_LOCKS = 1000
private let locks: [NSLock]

init(cmabClient: CmabClient, cmabCache: LruCache<String, CmabCacheValue>) {
self.cmabClient = cmabClient
self.cmabCache = cmabCache
self.locks = (0..<Self.NUM_LOCKS).map { _ in NSLock() }
}

private func getLockIndex(userId: String, ruleId: String) -> Int {
let combinedKey = userId + ruleId
let hashValue = MurmurHash3.hash32(key: combinedKey)
let lockIndex = Int(hashValue) % Self.NUM_LOCKS
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The conversion from UInt32 to Int could result in a negative value on platforms where Int is 32-bit, leading to a potential crash when accessing the locks array. Use Int(hashValue % UInt32(Self.NUM_LOCKS)) to ensure the modulo operation happens before conversion.

Suggested change
let lockIndex = Int(hashValue) % Self.NUM_LOCKS
let lockIndex = Int(hashValue % UInt32(Self.NUM_LOCKS))

Copilot uses AI. Check for mistakes.

return lockIndex
}

func getDecision(config: ProjectConfig,
userContext: OptimizelyUserContext,
ruleId: String,
options: [OptimizelyDecideOption]) -> Result<CmabDecision, Error> {
var result: Result<CmabDecision, Error>!
let semaphore = DispatchSemaphore(value: 0)
getDecision(config: config,
userContext: userContext,
ruleId: ruleId, options: options) { _result in
result = _result
semaphore.signal()
let lockIdx = getLockIndex(userId: userContext.userId, ruleId: ruleId)
let lock = locks[lockIdx]
return lock.withLock {
var result: Result<CmabDecision, Error>!
let semaphore = DispatchSemaphore(value: 0)
Comment on lines +73 to +75
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

Using a semaphore to block while holding a lock can lead to deadlock if the asynchronous callback in getDecision attempts to acquire the same lock. The lock should be released before waiting on the semaphore, or the async operation should be moved outside the lock scope.

Copilot uses AI. Check for mistakes.

getDecision(config: config,
userContext: userContext,
ruleId: ruleId, options: options) { _result in
result = _result
semaphore.signal()
}
semaphore.wait()
return result
}
semaphore.wait()
return result
}

func getDecision(config: ProjectConfig,
Expand Down
Loading