|
13 | 13 | import uuid |
14 | 14 | import json |
15 | 15 | import hashlib |
| 16 | +import threading |
16 | 17 |
|
17 | 18 | from typing import Optional, List, TypedDict |
18 | 19 | from optimizely.cmab.cmab_client import DefaultCmabClient |
|
21 | 22 | from optimizely.project_config import ProjectConfig |
22 | 23 | from optimizely.decision.optimizely_decide_option import OptimizelyDecideOption |
23 | 24 | from optimizely import logger as _logging |
| 25 | +from optimizely.lib import pymmh3 as mmh3 |
| 26 | +NUM_LOCK_STRIPES = 1000 |
24 | 27 |
|
25 | 28 |
|
26 | 29 | class CmabDecision(TypedDict): |
@@ -52,10 +55,25 @@ def __init__(self, cmab_cache: LRUCache[str, CmabCacheValue], |
52 | 55 | self.cmab_cache = cmab_cache |
53 | 56 | self.cmab_client = cmab_client |
54 | 57 | self.logger = logger |
| 58 | + self.locks = [threading.Lock() for _ in range(NUM_LOCK_STRIPES)] |
| 59 | + |
| 60 | + def _get_lock_index(self, user_id: str, rule_id: str) -> int: |
| 61 | + """Calculate the lock index for a given user and rule combination.""" |
| 62 | + # Create a hash of user_id + rule_id for consistent lock selection |
| 63 | + hash_input = f"{user_id}{rule_id}" |
| 64 | + hash_value = mmh3.hash(hash_input, seed=0) & 0xFFFFFFFF # Convert to unsigned |
| 65 | + return hash_value % NUM_LOCK_STRIPES |
55 | 66 |
|
56 | 67 | def get_decision(self, project_config: ProjectConfig, user_context: OptimizelyUserContext, |
57 | 68 | rule_id: str, options: List[str]) -> CmabDecision: |
58 | 69 |
|
| 70 | + lock_index = self._get_lock_index(user_context.user_id, rule_id) |
| 71 | + with self.locks[lock_index]: |
| 72 | + return self._get_decision(project_config, user_context, rule_id, options) |
| 73 | + |
| 74 | + def _get_decision(self, project_config: ProjectConfig, user_context: OptimizelyUserContext, |
| 75 | + rule_id: str, options: List[str]) -> CmabDecision: |
| 76 | + |
59 | 77 | filtered_attributes = self._filter_attributes(project_config, user_context, rule_id) |
60 | 78 |
|
61 | 79 | if OptimizelyDecideOption.IGNORE_CMAB_CACHE in options: |
|
0 commit comments