Skip to content
Merged
Changes from 1 commit
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
35 changes: 26 additions & 9 deletions Sources/CMAB/CmabService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,42 @@ 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 = combinedKey.hashValue
// Take absolute value to ensure positive number
let positiveHash = abs(hashValue)
// Use modulo to map to lock array index [0, NUM_LOCKS-1]
return positiveHash % Self.NUM_LOCKS
}

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