Skip to content

Commit a261899

Browse files
committed
Mutex lock and testcases added
1 parent 68146a1 commit a261899

File tree

2 files changed

+129
-12
lines changed

2 files changed

+129
-12
lines changed

optimizely/optimizely_user_context.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def __init__(self, optimizely_client, user_id, user_attributes=None):
4646

4747
self._user_attributes = user_attributes.copy() if user_attributes else {}
4848
self.lock = threading.Lock()
49-
self.forced_decisions = {}
49+
with self.lock:
50+
self.forced_decisions = {}
5051
self.log = logger.SimpleLogger(min_level=enums.LogLevels.INFO)
5152

5253
# decision context
@@ -72,8 +73,9 @@ def _clone(self):
7273

7374
user_context = OptimizelyUserContext(self.client, self.user_id, self.get_user_attributes())
7475

75-
if self.forced_decisions:
76-
user_context.forced_decisions = copy.deepcopy(self.forced_decisions)
76+
with self.lock:
77+
if self.forced_decisions:
78+
user_context.forced_decisions = copy.deepcopy(self.forced_decisions)
7779

7880
return user_context
7981

@@ -167,7 +169,8 @@ def set_forced_decision(self, OptimizelyDecisionContext, OptimizelyForcedDecisio
167169
context = OptimizelyDecisionContext
168170
decision = OptimizelyForcedDecision
169171

170-
self.forced_decisions[context] = decision
172+
with self.lock:
173+
self.forced_decisions[context] = decision
171174

172175
return True
173176

@@ -207,9 +210,10 @@ def remove_forced_decision(self, OptimizelyDecisionContext):
207210
self.log.logger.error(OptimizelyDecisionMessage.SDK_NOT_READY)
208211
return False
209212

210-
if self.forced_decisions[OptimizelyDecisionContext]:
211-
del self.forced_decisions[OptimizelyDecisionContext]
212-
return True
213+
with self.lock:
214+
if self.forced_decisions[OptimizelyDecisionContext]:
215+
del self.forced_decisions[OptimizelyDecisionContext]
216+
return True
213217

214218
return False
215219

@@ -226,17 +230,19 @@ def remove_all_forced_decisions(self):
226230
self.log.logger.error(OptimizelyDecisionMessage.SDK_NOT_READY)
227231
return False
228232

229-
self.forced_decisions.clear()
233+
with self.lock:
234+
self.forced_decisions.clear()
230235

231236
return True
232237

233238
def find_forced_decision(self, OptimizelyDecisionContext):
234239

235-
if not self.forced_decisions:
236-
return None
240+
with self.lock:
241+
if not self.forced_decisions:
242+
return None
237243

238-
# must allow None to be returned for the Flags only case
239-
return self.forced_decisions.get(OptimizelyDecisionContext)
244+
# must allow None to be returned for the Flags only case
245+
return self.forced_decisions.get(OptimizelyDecisionContext)
240246

241247
def find_validated_forced_decision(self, OptimizelyDecisionContext, options):
242248

tests/test_user_context.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import json
1414

1515
import mock
16+
import threading
1617

1718
from optimizely import optimizely, decision_service
1819
from optimizely.decision.optimizely_decide_option import OptimizelyDecideOption as DecideOption
@@ -1793,3 +1794,113 @@ def test_forced_decision_return_status(self):
17931794
self.assertTrue(status)
17941795
status = user_context.remove_all_forced_decisions()
17951796
self.assertTrue(status)
1797+
1798+
def test_forced_decision_clone_return_valid_forced_decision(self):
1799+
"""
1800+
Should return valid forced decision on cloning.
1801+
"""
1802+
opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features))
1803+
user_context = OptimizelyUserContext(opt_obj, "test_user", {})
1804+
1805+
context_with_flag = OptimizelyUserContext.OptimizelyDecisionContext('f1', None)
1806+
decision_for_flag = OptimizelyUserContext.OptimizelyForcedDecision('v1')
1807+
context_with_rule = OptimizelyUserContext.OptimizelyDecisionContext('f1', 'r1')
1808+
decision_for_rule = OptimizelyUserContext.OptimizelyForcedDecision('v2')
1809+
context_with_empty_rule = OptimizelyUserContext.OptimizelyDecisionContext('f1', '')
1810+
decision_for_empty_rule = OptimizelyUserContext.OptimizelyForcedDecision('v3')
1811+
1812+
user_context.set_forced_decision(context_with_flag, decision_for_flag)
1813+
user_context.set_forced_decision(context_with_rule, decision_for_rule)
1814+
user_context.set_forced_decision(context_with_empty_rule, decision_for_empty_rule)
1815+
1816+
user_context_2 = user_context._clone()
1817+
self.assertEqual(user_context_2.user_id, 'test_user')
1818+
self.assertEqual(user_context_2.get_user_attributes(), {})
1819+
self.assertIsNotNone(user_context_2.forced_decisions)
1820+
1821+
self.assertEqual(user_context_2.get_forced_decision(context_with_flag).variation_key, 'v1')
1822+
self.assertEqual(user_context_2.get_forced_decision(context_with_rule).variation_key, 'v2')
1823+
self.assertEqual(user_context_2.get_forced_decision(context_with_empty_rule).variation_key, 'v3')
1824+
1825+
context_with_rule = OptimizelyUserContext.OptimizelyDecisionContext('x', 'y')
1826+
decision_for_rule = OptimizelyUserContext.OptimizelyForcedDecision('z')
1827+
user_context.set_forced_decision(context_with_rule, decision_for_rule)
1828+
self.assertEqual(user_context.get_forced_decision(context_with_rule).variation_key, 'z')
1829+
self.assertIsNone(user_context_2.get_forced_decision(context_with_rule))
1830+
1831+
def test_forced_decision_sync_return_correct_number_of_calls(self):
1832+
"""
1833+
Should return valid number of call on running forced decision calls in thread.
1834+
"""
1835+
opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features))
1836+
user_context = OptimizelyUserContext(opt_obj, "test_user", {})
1837+
context_1 = OptimizelyUserContext.OptimizelyDecisionContext('f1', None)
1838+
decision_1 = OptimizelyUserContext.OptimizelyForcedDecision('v1')
1839+
context_2 = OptimizelyUserContext.OptimizelyDecisionContext('f2', None)
1840+
decision_2 = OptimizelyUserContext.OptimizelyForcedDecision('v1')
1841+
1842+
with mock.patch(
1843+
'optimizely.optimizely_user_context.OptimizelyUserContext.set_forced_decision'
1844+
) as set_forced_decision_mock, mock.patch(
1845+
'optimizely.optimizely_user_context.OptimizelyUserContext.get_forced_decision'
1846+
) as get_forced_decision_mock, mock.patch(
1847+
'optimizely.optimizely_user_context.OptimizelyUserContext.remove_forced_decision'
1848+
) as remove_forced_decision_mock, mock.patch(
1849+
'optimizely.optimizely_user_context.OptimizelyUserContext.remove_all_forced_decisions'
1850+
) as remove_all_forced_decisions_mock, mock.patch(
1851+
'optimizely.optimizely_user_context.OptimizelyUserContext._clone'
1852+
) as clone_mock:
1853+
def set_forced_decision_loop(user_context, context, decision):
1854+
for x in range(100):
1855+
user_context.set_forced_decision(context, decision)
1856+
1857+
def get_forced_decision_loop(user_context, context):
1858+
for x in range(100):
1859+
user_context.get_forced_decision(context)
1860+
1861+
def remove_forced_decision_loop(user_context, context):
1862+
for x in range(100):
1863+
user_context.remove_forced_decision(context)
1864+
1865+
def remove_all_forced_decisions_loop(user_context):
1866+
for x in range(100):
1867+
user_context.remove_all_forced_decisions()
1868+
1869+
def clone_loop(user_context):
1870+
for x in range(100):
1871+
user_context._clone()
1872+
1873+
set_thread_1 = threading.Thread(target=set_forced_decision_loop, args=(user_context, context_1, decision_1))
1874+
set_thread_2 = threading.Thread(target=set_forced_decision_loop, args=(user_context, context_2, decision_2))
1875+
set_thread_3 = threading.Thread(target=get_forced_decision_loop, args=(user_context, context_1))
1876+
set_thread_4 = threading.Thread(target=get_forced_decision_loop, args=(user_context, context_2))
1877+
set_thread_5 = threading.Thread(target=remove_forced_decision_loop, args=(user_context, context_1))
1878+
set_thread_6 = threading.Thread(target=remove_forced_decision_loop, args=(user_context, context_2))
1879+
set_thread_7 = threading.Thread(target=remove_all_forced_decisions_loop, args=(user_context,))
1880+
set_thread_8 = threading.Thread(target=clone_loop, args=(user_context,))
1881+
1882+
# Starting the threads
1883+
set_thread_1.start()
1884+
set_thread_2.start()
1885+
set_thread_3.start()
1886+
set_thread_4.start()
1887+
set_thread_5.start()
1888+
set_thread_6.start()
1889+
set_thread_7.start()
1890+
set_thread_8.start()
1891+
1892+
# Waiting for all the threads to finish executing
1893+
set_thread_1.join()
1894+
set_thread_2.join()
1895+
set_thread_3.join()
1896+
set_thread_4.join()
1897+
set_thread_5.join()
1898+
set_thread_6.join()
1899+
set_thread_7.join()
1900+
set_thread_8.join()
1901+
1902+
self.assertEqual(200, set_forced_decision_mock.call_count)
1903+
self.assertEqual(200, get_forced_decision_mock.call_count)
1904+
self.assertEqual(200, remove_forced_decision_mock.call_count)
1905+
self.assertEqual(100, remove_all_forced_decisions_mock.call_count)
1906+
self.assertEqual(100, clone_mock.call_count)

0 commit comments

Comments
 (0)