Skip to content

Commit a2727e9

Browse files
msohailhussainMichael Ng
authored and
Michael Ng
committed
feat(dispose): Added Disposable interface (#170)
1 parent 173f072 commit a2727e9

9 files changed

+149
-44
lines changed

OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public void TestWaitUntilValidDatafileIsNotGivenOrTimedout()
180180
new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null}
181181
};
182182

183-
var configManager = new TestPollingProjectConfigManager(TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(1300), true, LoggerMock.Object, data.ToArray());
183+
var configManager = new TestPollingProjectConfigManager(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(2500), true, LoggerMock.Object, data.ToArray());
184184
configManager.Start();
185185
// after 3rd attempt should be released with null.
186186
var config = configManager.GetConfig();

OptimizelySDK.Tests/ConfigTest/TestPollingProjectConfigManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* Copyright 2019, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");

OptimizelySDK.Tests/EntityTests/FeatureVariableTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* Copyright 2019, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");

OptimizelySDK.Tests/OptimizelyTest.cs

+88-2
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,7 @@ public void Cleanup()
124124
#region OptimizelyHelper
125125
private class OptimizelyHelper
126126
{
127-
static Type[] ParameterTypes = new[]
128-
{
127+
static Type[] ParameterTypes = {
129128
typeof(string),
130129
typeof(IEventDispatcher),
131130
typeof(ILogger),
@@ -3417,5 +3416,92 @@ public void TestGetFeatureVariableIntegerReturnsDefaultValueWithComplexAudienceC
34173416
}
34183417

34193418
#endregion // Test Audience Combinations
3419+
3420+
#region Disposable Optimizely
3421+
3422+
[Test]
3423+
public void TestOptimizelyDisposeAlsoDisposedConfigManager()
3424+
{
3425+
var httpManager = new HttpProjectConfigManager.Builder()
3426+
.WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z")
3427+
.WithLogger(LoggerMock.Object)
3428+
.WithPollingInterval(TimeSpan.FromMilliseconds(5000))
3429+
.WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500))
3430+
.Build();
3431+
var optimizely = new Optimizely(httpManager);
3432+
optimizely.Dispose();
3433+
3434+
Assert.True(optimizely.Disposed);
3435+
Assert.True(httpManager.Disposed);
3436+
}
3437+
3438+
[Test]
3439+
public void TestDisposeInvalidateObject()
3440+
{
3441+
var httpManager = new HttpProjectConfigManager.Builder()
3442+
.WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z")
3443+
.WithLogger(LoggerMock.Object)
3444+
.WithPollingInterval(TimeSpan.FromMilliseconds(5000))
3445+
.WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500))
3446+
.Build();
3447+
var optimizely = new Optimizely(httpManager);
3448+
optimizely.Dispose();
3449+
3450+
Assert.False(optimizely.IsValid);
3451+
}
3452+
3453+
[Test]
3454+
public void TestAfterDisposeAPIsNoLongerValid()
3455+
{
3456+
var httpManager = new HttpProjectConfigManager.Builder()
3457+
.WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z")
3458+
.WithDatafile(TestData.Datafile)
3459+
.WithLogger(LoggerMock.Object)
3460+
.WithPollingInterval(TimeSpan.FromMilliseconds(50000))
3461+
.WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500))
3462+
.Build(true);
3463+
var optimizely = new Optimizely(httpManager);
3464+
httpManager.Start();
3465+
var activate = optimizely.Activate("test_experiment", TestUserId, new UserAttributes() {
3466+
{ "device_type", "iPhone" }, { "location", "San Francisco" } });
3467+
Assert.NotNull(activate);
3468+
optimizely.Dispose();
3469+
var activateAfterDispose = optimizely.Activate("test_experiment", TestUserId, new UserAttributes() {
3470+
{ "device_type", "iPhone" }, { "location", "San Francisco" } });
3471+
Assert.Null(activateAfterDispose);
3472+
}
3473+
3474+
[Test]
3475+
public void TestNonDisposableConfigManagerDontCrash()
3476+
{
3477+
var fallbackConfigManager = new FallbackProjectConfigManager(Config);
3478+
3479+
var optimizely = new Optimizely(fallbackConfigManager);
3480+
optimizely.Dispose();
3481+
Assert.True(optimizely.Disposed);
3482+
}
3483+
3484+
[Test]
3485+
public void TestAfterDisposeAPIsShouldNotCrash()
3486+
{
3487+
var fallbackConfigManager = new FallbackProjectConfigManager(Config);
3488+
3489+
var optimizely = new Optimizely(fallbackConfigManager);
3490+
optimizely.Dispose();
3491+
Assert.True(optimizely.Disposed);
3492+
3493+
Assert.IsNull(optimizely.GetVariation(string.Empty, string.Empty));
3494+
Assert.IsNull(optimizely.Activate(string.Empty, string.Empty));
3495+
optimizely.Track(string.Empty, string.Empty);
3496+
Assert.IsFalse(optimizely.IsFeatureEnabled(string.Empty, string.Empty));
3497+
Assert.AreEqual(optimizely.GetEnabledFeatures(string.Empty).Count, 0);
3498+
Assert.IsNull(optimizely.GetFeatureVariableBoolean(string.Empty, string.Empty, string.Empty));
3499+
Assert.IsNull(optimizely.GetFeatureVariableString(string.Empty, string.Empty, string.Empty));
3500+
Assert.IsNull(optimizely.GetFeatureVariableDouble(string.Empty, string.Empty, string.Empty));
3501+
Assert.IsNull(optimizely.GetFeatureVariableInteger(string.Empty, string.Empty, string.Empty));
3502+
3503+
}
3504+
3505+
#endregion
34203506
}
34213507
}

OptimizelySDK/Config/DatafileProjectConfig.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ public static ProjectConfig Create(string content, ILogger logger, IErrorHandler
321321
DatafileProjectConfig config = GetConfig(content);
322322

323323
config.Logger = logger ?? new NoOpLogger();
324-
config.ErrorHandler = errorHandler ?? new NoOpErrorHandler();
324+
config.ErrorHandler = errorHandler ?? new NoOpErrorHandler(logger);
325325

326326
config.Initialize();
327327

OptimizelySDK/Config/HttpProjectConfigManager.cs

+30-32
Original file line numberDiff line numberDiff line change
@@ -78,40 +78,38 @@ private string GetRemoteDatafileResponse()
7878

7979
return content.Result;
8080
}
81-
#elif NET40
82-
//TODO: Need to revise this method.
83-
private string GetRemoteDatafileResponse()
84-
{
85-
var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(Url);
86-
87-
// Send If-Modified-Since header if Last-Modified-Since header contains any value.
88-
if (!string.IsNullOrEmpty(LastModifiedSince))
89-
request.Headers.Add("If-Modified-Since", LastModifiedSince);
90-
var result = (System.Net.HttpWebResponse)request.GetResponse();
91-
92-
// TODO: Need to revise this code.
93-
if (result.StatusCode != System.Net.HttpStatusCode.OK) {
94-
Logger.Log(LogLevel.ERROR, "Unexpected response from event endpoint, status: " + result.StatusCode);
95-
}
96-
var lastModified = result.Headers.GetValues("Last-Modified");
97-
if(!string.IsNullOrEmpty(lastModified.First()))
98-
{
99-
LastModifiedSince = lastModified.First();
100-
}
101-
102-
var encoding = System.Text.Encoding.ASCII;
103-
using (var reader = new System.IO.StreamReader(result.GetResponseStream(), encoding)) {
104-
string responseText = reader.ReadToEnd();
105-
return responseText;
106-
}
107-
}
81+
#elif NET40
82+
private string GetRemoteDatafileResponse()
83+
{
84+
var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(Url);
85+
86+
// Send If-Modified-Since header if Last-Modified-Since header contains any value.
87+
if (!string.IsNullOrEmpty(LastModifiedSince))
88+
request.Headers.Add("If-Modified-Since", LastModifiedSince);
89+
var result = (System.Net.HttpWebResponse)request.GetResponse();
90+
91+
// TODO: Need to revise this code.
92+
if (result.StatusCode != System.Net.HttpStatusCode.OK) {
93+
Logger.Log(LogLevel.ERROR, "Unexpected response from event endpoint, status: " + result.StatusCode);
94+
}
95+
var lastModified = result.Headers.GetValues("Last-Modified");
96+
if(!string.IsNullOrEmpty(lastModified.First()))
97+
{
98+
LastModifiedSince = lastModified.First();
99+
}
100+
101+
var encoding = System.Text.Encoding.ASCII;
102+
using (var reader = new System.IO.StreamReader(result.GetResponseStream(), encoding)) {
103+
string responseText = reader.ReadToEnd();
104+
return responseText;
105+
}
106+
}
108107
#else
109-
private string GetRemoteDatafileResponse()
110-
{
111-
return null;
112-
}
108+
private string GetRemoteDatafileResponse()
109+
{
110+
return null;
111+
}
113112
#endif
114-
115113
protected override ProjectConfig Poll()
116114
{
117115
var datafile = GetRemoteDatafileResponse();

OptimizelySDK/Config/PollingProjectConfigManager.cs

+14-3
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ namespace OptimizelySDK.Config
2929
/// Instances of this class, must implement the <see cref="Poll()"/> method
3030
/// which is responsible for fetching a given ProjectConfig.
3131
/// </summary>
32-
public abstract class PollingProjectConfigManager : ProjectConfigManager
32+
public abstract class PollingProjectConfigManager : ProjectConfigManager, IDisposable
3333
{
34+
public bool Disposed { get; private set; }
35+
3436
private TimeSpan PollingInterval;
3537
public bool IsStarted { get; private set; }
3638
private bool scheduleWhenFinished = false;
@@ -70,7 +72,7 @@ public PollingProjectConfigManager(TimeSpan period, TimeSpan blockingTimeout, bo
7072
/// </summary>
7173
public void Start()
7274
{
73-
if (IsStarted)
75+
if (IsStarted && !Disposed)
7476
{
7577
Logger.Log(LogLevel.WARN, "Manager already started.");
7678
return;
@@ -86,6 +88,7 @@ public void Start()
8688
/// </summary>
8789
public void Stop()
8890
{
91+
if (Disposed) return;
8992
// don't call now and onwards.
9093
SchedulerService.Change(-1, -1);
9194

@@ -100,6 +103,8 @@ public void Stop()
100103
/// <returns>ProjectConfig</returns>
101104
public ProjectConfig GetConfig()
102105
{
106+
if (Disposed) return null;
107+
103108
if (IsStarted)
104109
{
105110
try
@@ -153,13 +158,19 @@ public bool SetConfig(ProjectConfig projectConfig)
153158
CompletableConfigManager.TrySetResult(true);
154159

155160
NotifyOnProjectConfigUpdate?.Invoke();
161+
156162

157163
return true;
158164
}
159165

160-
public void Dispose()
166+
public virtual void Dispose()
161167
{
168+
if (Disposed) return;
169+
170+
SchedulerService.Change(-1, -1);
162171
SchedulerService.Dispose();
172+
CurrentProjectConfig = null;
173+
Disposed = true;
163174
}
164175

165176
/// <summary>

OptimizelySDK/Optimizely.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
namespace OptimizelySDK
3131
{
32-
public class Optimizely : IOptimizely
32+
public class Optimizely : IOptimizely, IDisposable
3333
{
3434
private Bucketer Bucketer;
3535

@@ -90,6 +90,7 @@ public static String SDK_TYPE
9090
public const string EVENT_KEY = "Event Key";
9191
public const string FEATURE_KEY = "Feature Key";
9292
public const string VARIABLE_KEY = "Variable Key";
93+
public bool Disposed { get; private set; }
9394

9495
/// <summary>
9596
/// Optimizely constructor for managing Full Stack .NET projects.
@@ -781,5 +782,14 @@ private object GetTypeCastedVariableValue(string value, FeatureVariable.Variable
781782

782783
return result;
783784
}
784-
}
785+
786+
public void Dispose()
787+
{
788+
if (Disposed) return;
789+
790+
Disposed = true;
791+
(ProjectConfigManager as IDisposable)?.Dispose();
792+
ProjectConfigManager = null;
793+
}
794+
}
785795
}

OptimizelySDK/ProjectConfig.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2019, Optimizely
2+
* Copyright 2019, 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.

0 commit comments

Comments
 (0)