Skip to content

Commit 5754452

Browse files
authored
Feat: Add thread-safe UserAttributes in read/write operations. (#253)
- Copying UserAttributes to make it thread safe. - Made All fields in OptimizelyUserContext private and added getter methods
1 parent 4fd3067 commit 5754452

File tree

5 files changed

+83
-49
lines changed

5 files changed

+83
-49
lines changed

OptimizelySDK.Tests/OptimizelyTest.cs

+18-19
Original file line numberDiff line numberDiff line change
@@ -198,18 +198,18 @@ public void TestCreateUserContext()
198198
{ "location", "San Francisco" }
199199
};
200200
var optlyUserContext = Optimizely.CreateUserContext(TestUserId, attribute);
201-
Assert.AreEqual(TestUserId, optlyUserContext.UserId);
202-
Assert.AreEqual(Optimizely, optlyUserContext.Optimizely);
203-
Assert.AreEqual(attribute, optlyUserContext.Attributes);
201+
Assert.AreEqual(TestUserId, optlyUserContext.GetUserId());
202+
Assert.AreEqual(Optimizely, optlyUserContext.GetOptimizely());
203+
Assert.AreEqual(attribute, optlyUserContext.GetAttributes());
204204
}
205205

206206
[Test]
207207
public void TestCreateUserContextWithoutAttributes()
208208
{
209209
var optlyUserContext = Optimizely.CreateUserContext(TestUserId);
210-
Assert.AreEqual(TestUserId, optlyUserContext.UserId);
211-
Assert.AreEqual(Optimizely, optlyUserContext.Optimizely);
212-
Assert.IsTrue(optlyUserContext.Attributes.Count == 0);
210+
Assert.AreEqual(TestUserId, optlyUserContext.GetUserId());
211+
Assert.AreEqual(Optimizely, optlyUserContext.GetOptimizely());
212+
Assert.IsTrue(optlyUserContext.GetAttributes().Count == 0);
213213
}
214214

215215
[Test]
@@ -230,13 +230,13 @@ public void TestCreateUserContextMultipleAttribute()
230230
};
231231
var optlyUserContext2 = Optimizely.CreateUserContext("userId2", attribute2);
232232

233-
Assert.AreEqual("userId1", optlyUserContext1.UserId);
234-
Assert.AreEqual(Optimizely, optlyUserContext1.Optimizely);
235-
Assert.AreEqual(attribute1, optlyUserContext1.Attributes);
233+
Assert.AreEqual("userId1", optlyUserContext1.GetUserId());
234+
Assert.AreEqual(Optimizely, optlyUserContext1.GetOptimizely());
235+
Assert.AreEqual(attribute1, optlyUserContext1.GetAttributes());
236236

237-
Assert.AreEqual("userId2", optlyUserContext2.UserId);
238-
Assert.AreEqual(Optimizely, optlyUserContext2.Optimizely);
239-
Assert.AreEqual(attribute2, optlyUserContext2.Attributes);
237+
Assert.AreEqual("userId2", optlyUserContext2.GetUserId());
238+
Assert.AreEqual(Optimizely, optlyUserContext2.GetOptimizely());
239+
Assert.AreEqual(attribute2, optlyUserContext2.GetAttributes());
240240
}
241241

242242
[Test]
@@ -249,20 +249,19 @@ public void TestChangeAttributeDoesNotEffectValues()
249249
{ "location", "San Francisco" }
250250
};
251251
var optlyUserContext = Optimizely.CreateUserContext(userId, attribute);
252-
Assert.AreEqual(TestUserId, optlyUserContext.UserId);
253-
Assert.AreEqual(Optimizely, optlyUserContext.Optimizely);
254-
Assert.AreEqual(attribute, optlyUserContext.Attributes);
252+
Assert.AreEqual(TestUserId, optlyUserContext.GetUserId());
253+
Assert.AreEqual(Optimizely, optlyUserContext.GetOptimizely());
254+
Assert.AreEqual(attribute, optlyUserContext.GetAttributes());
255255

256256
attribute = new UserAttributes
257257
{
258258
{ "device_type", "iPhone" },
259259
{ "level", "low" },
260260
{ "location", "San Francisco" }
261261
};
262-
userId = "InvalidUser";
263-
Assert.AreEqual("testUserId", optlyUserContext.UserId);
264-
Assert.AreEqual(Optimizely, optlyUserContext.Optimizely);
265-
Assert.AreNotEqual(attribute, optlyUserContext.Attributes);
262+
Assert.AreEqual("testUserId", optlyUserContext.GetUserId());
263+
Assert.AreEqual(Optimizely, optlyUserContext.GetOptimizely());
264+
Assert.AreNotEqual(attribute, optlyUserContext.GetAttributes());
266265
}
267266

268267
#endregion

OptimizelySDK.Tests/OptimizelyUserContextTest.cs

+20-20
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,19 @@ public void OptimizelyUserContextWithAttributes()
6565
var attributes = new UserAttributes() { { "house", "GRYFFINDOR" } };
6666
OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, attributes, ErrorHandlerMock.Object, LoggerMock.Object);
6767

68-
Assert.AreEqual(user.Optimizely, Optimizely);
69-
Assert.AreEqual(user.UserId, UserID);
70-
Assert.AreEqual(user.Attributes, attributes);
68+
Assert.AreEqual(user.GetOptimizely(), Optimizely);
69+
Assert.AreEqual(user.GetUserId(), UserID);
70+
Assert.AreEqual(user.GetAttributes(), attributes);
7171
}
7272

7373
[Test]
7474
public void OptimizelyUserContextNoAttributes()
7575
{
7676
OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, null, ErrorHandlerMock.Object, LoggerMock.Object);
7777

78-
Assert.AreEqual(user.Optimizely, Optimizely);
79-
Assert.AreEqual(user.UserId, UserID);
80-
Assert.True(user.Attributes.Count == 0);
78+
Assert.AreEqual(user.GetOptimizely(), Optimizely);
79+
Assert.AreEqual(user.GetUserId(), UserID);
80+
Assert.True(user.GetAttributes().Count == 0);
8181
}
8282

8383
[Test]
@@ -91,9 +91,9 @@ public void SetAttribute()
9191
user.SetAttribute("k3", 100);
9292
user.SetAttribute("k4", 3.5);
9393

94-
Assert.AreEqual(user.Optimizely, Optimizely);
95-
Assert.AreEqual(user.UserId, UserID);
96-
var newAttributes = user.Attributes;
94+
Assert.AreEqual(user.GetOptimizely(), Optimizely);
95+
Assert.AreEqual(user.GetUserId(), UserID);
96+
var newAttributes = user.GetAttributes();
9797
Assert.AreEqual(newAttributes["house"], "GRYFFINDOR");
9898
Assert.AreEqual(newAttributes["k1"], "v1");
9999
Assert.AreEqual(newAttributes["k2"], true);
@@ -109,9 +109,9 @@ public void SetAttributeNoAttribute()
109109
user.SetAttribute("k1", "v1");
110110
user.SetAttribute("k2", true);
111111

112-
Assert.AreEqual(user.Optimizely, Optimizely);
113-
Assert.AreEqual(user.UserId, UserID);
114-
var newAttributes = user.Attributes;
112+
Assert.AreEqual(user.GetOptimizely(), Optimizely);
113+
Assert.AreEqual(user.GetUserId(), UserID);
114+
var newAttributes = user.GetAttributes();
115115
Assert.AreEqual(newAttributes["k1"], "v1");
116116
Assert.AreEqual(newAttributes["k2"], true);
117117
}
@@ -125,7 +125,7 @@ public void SetAttributeOverride()
125125
user.SetAttribute("k1", "v1");
126126
user.SetAttribute("house", "v2");
127127

128-
var newAttributes = user.Attributes;
128+
var newAttributes = user.GetAttributes();
129129
Assert.AreEqual(newAttributes["k1"], "v1");
130130
Assert.AreEqual(newAttributes["house"], "v2");
131131
}
@@ -136,15 +136,15 @@ public void SetAttributeNullValue()
136136
var attributes = new UserAttributes() { { "k1", null } };
137137
OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, attributes, ErrorHandlerMock.Object, LoggerMock.Object);
138138

139-
var newAttributes = user.Attributes;
139+
var newAttributes = user.GetAttributes();
140140
Assert.AreEqual(newAttributes["k1"], null);
141141

142142
user.SetAttribute("k1", true);
143-
newAttributes = user.Attributes;
143+
newAttributes = user.GetAttributes();
144144
Assert.AreEqual(newAttributes["k1"], true);
145145

146146
user.SetAttribute("k1", null);
147-
newAttributes = user.Attributes;
147+
newAttributes = user.GetAttributes();
148148
Assert.AreEqual(newAttributes["k1"], null);
149149
}
150150

@@ -154,14 +154,14 @@ public void SetAttributeToOverrideAttribute()
154154
OptimizelyUserContext user = new OptimizelyUserContext(Optimizely, UserID, null, ErrorHandlerMock.Object, LoggerMock.Object);
155155

156156

157-
Assert.AreEqual(user.Optimizely, Optimizely);
158-
Assert.AreEqual(user.UserId, UserID);
157+
Assert.AreEqual(user.GetOptimizely(), Optimizely);
158+
Assert.AreEqual(user.GetUserId(), UserID);
159159

160160
user.SetAttribute("k1", "v1");
161-
Assert.AreEqual(user.Attributes["k1"], "v1");
161+
Assert.AreEqual(user.GetAttributes()["k1"], "v1");
162162

163163
user.SetAttribute("k1", true);
164-
Assert.AreEqual(user.Attributes["k1"], true);
164+
Assert.AreEqual(user.GetAttributes()["k1"], true);
165165
}
166166

167167
#region decide

OptimizelySDK/Entity/UserAttributes.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2018, Optimizely
2+
* Copyright 2017-2018,2020, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,12 +14,17 @@
1414
* limitations under the License.
1515
*/
1616
using System.Collections.Generic;
17-
using OptimizelySDK.Logger;
1817

1918
namespace OptimizelySDK.Entity
2019
{
2120
public class UserAttributes : Dictionary<string, object>
2221
{
23-
22+
public UserAttributes() : base()
23+
{
24+
}
25+
26+
public UserAttributes(IDictionary<string, object> dictionary) : base(dictionary)
27+
{
28+
}
2429
}
2530
}

OptimizelySDK/Optimizely.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ internal OptimizelyDecision Decide(OptimizelyUserContext user,
741741
ErrorHandler, Logger);
742742
}
743743

744-
var userId = user?.UserId;
744+
var userId = user?.GetUserId();
745745

746746
var flag = config.GetFeatureFlagFromKey(key);
747747
if (string.IsNullOrEmpty(flag.Key))
@@ -752,8 +752,7 @@ internal OptimizelyDecision Decide(OptimizelyUserContext user,
752752
ErrorHandler, Logger);
753753
}
754754

755-
var userAttributes = user.Attributes;
756-
755+
var userAttributes = user.GetAttributes();
757756
var decisionEventDispatched = false;
758757
var allOptions = GetAllOptions(options);
759758
var decisionReasons = DefaultDecisionReasons.NewInstance(allOptions);
@@ -864,7 +863,6 @@ internal Dictionary<string, OptimizelyDecision> DecideAll(OptimizelyUserContext
864863
return DecideForKeys(user, allFlagKeys, options);
865864
}
866865

867-
868866
internal Dictionary<string, OptimizelyDecision> DecideForKeys(OptimizelyUserContext user,
869867
string[] keys,
870868
OptimizelyDecideOption[] options)

OptimizelySDK/OptimizelyUserContext.cs

+35-3
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ public class OptimizelyUserContext
3131
private IErrorHandler ErrorHandler;
3232
private object mutex = new object();
3333
// userID for Optimizely user context
34-
public string UserId { get; }
34+
private string UserId;
3535
// user attributes for Optimizely user context.
36-
public UserAttributes Attributes { get; }
36+
private UserAttributes Attributes;
3737
// Optimizely object to be used.
38-
public Optimizely Optimizely { get; }
38+
private Optimizely Optimizely;
3939

4040
public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, IErrorHandler errorHandler, ILogger logger)
4141
{
@@ -46,6 +46,38 @@ public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttribute
4646
UserId = userId;
4747
}
4848

49+
/// <summary>
50+
/// Returns Optimizely instance associated with the UserContext.
51+
/// </summary>
52+
/// <returns> Optimizely instance.</returns>
53+
public Optimizely GetOptimizely()
54+
{
55+
return Optimizely;
56+
}
57+
58+
/// <summary>
59+
/// Returns UserId associated with the UserContext
60+
/// </summary>
61+
/// <returns>UserId of this instance.</returns>
62+
public string GetUserId()
63+
{
64+
return UserId;
65+
}
66+
67+
/// <summary>
68+
/// Returns copy of UserAttributes associated with UserContext.
69+
/// </summary>
70+
/// <returns>copy of UserAttributes.</returns>
71+
public UserAttributes GetAttributes()
72+
{
73+
UserAttributes copiedAttributes = null;
74+
lock(mutex) {
75+
copiedAttributes = new UserAttributes(Attributes);
76+
}
77+
78+
return copiedAttributes;
79+
}
80+
4981
/// <summary>
5082
/// Set an attribute for a given key.
5183
/// </summary>

0 commit comments

Comments
 (0)