-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Added ODPManager implementation #322
Conversation
<Compile Include="Odp\IOdpSegmentManager.cs" /> | ||
<Compile Include="Odp\OdpConfig.cs" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was already declared on line 112/113 below. Added OdpManager.cs
in its place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please address.
/// <param name="apiHost">Host portion of the URL of ODP</param> | ||
/// <param name="segmentsToCheck">Audience segments to consider</param> | ||
/// <returns>True if settings were update otherwise False</returns> | ||
bool UpdateSettings(string apiKey, string apiHost, List<string> segmentsToCheck); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't it be OdpConfig in args
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OptimizelySDK/Odp/IOdpManager.cs
Outdated
/// <param name="userId">FS User ID</param> | ||
/// <param name="options">Options used during segment cache handling</param> | ||
/// <returns>Qualified segments for the user from the cache or the ODP server</returns> | ||
List<string> FetchQualifiedSegments(string userId, List<OdpSegmentOption> options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This should be Array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yes. Sorry. Old habit from previous work.
OptimizelySDK/Odp/IOdpManager.cs
Outdated
/// <summary> | ||
/// Sends signal to stop Event Manager and clean up ODP Manager use | ||
/// </summary> | ||
void Close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be Dispose. My understanding is if it's closed, then can't use again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OptimizelySDK/Odp/OdpConfig.cs
Outdated
/// </summary> | ||
/// <param name="toCompare">OdpConfig object to compare current instance against</param> | ||
/// <returns>True if equal otherwise False</returns> | ||
public bool Equals(OdpConfig toCompare) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the reason to add this method. This will raise exception if ApiKey is null. Secondly use equals method where needed in this class and implement IEqu... interface as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal of this is to control the equality determination of two ODP Configs.
Thanks for the callout on null exception. I'll change to use IEqualityComparer
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh maybe IEquatable<T>
might work instead of having a comparer. 🤔
public bool UpdateSettings(string apiKey, string apiHost, List<string> segmentsToCheck) | ||
{ | ||
var newConfig = new OdpConfig(apiKey, apiHost, segmentsToCheck); | ||
if (_odpConfig.Equals(newConfig)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't we use ==
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes Sir. I'll update once using the IEqualityComparer
from OdpConfig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried overriding the implicit operator, but it gets messy and unclear. I'd like to stay with the use of .Equals()
OptimizelySDK/Odp/OdpManager.cs
Outdated
|
||
_odpConfig = newConfig; | ||
|
||
EventManager.Flush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comments in a code, why flushing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OptimizelySDK/Odp/OdpManager.cs
Outdated
/// <returns>Qualified segments for the user from the cache or the ODP server</returns> | ||
public List<string> FetchQualifiedSegments(string userId, List<OdpSegmentOption> options) | ||
{ | ||
if (!_enabled || SegmentManager == null || !_odpConfig.IsReady()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a minor comment,
SegmentManager == null || !_enabled || !_odpConfig.IsReady()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can re-arrange (I think that's what you're asking)
I thought that perhaps !_enabled
would evaluate to true more often than the others, but happy to switch.
OptimizelySDK/Odp/OdpManager.cs
Outdated
Dictionary<string, object> data | ||
) | ||
{ | ||
if (!_enabled || EventManager == null || !_odpConfig.IsReady()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This validations should be in a single method and return true or false. please refactor it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My copy-paste finger started hurting, and I thought of that too.
L89, would have me injecting the Manager that should be checked for null
so I just left it.
Let me play with some options....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the comments @msohailhussain .
I'll hold for @jaeopt 's too before pushing to the repo
/// <param name="apiHost">Host portion of the URL of ODP</param> | ||
/// <param name="segmentsToCheck">Audience segments to consider</param> | ||
/// <returns>True if settings were update otherwise False</returns> | ||
bool UpdateSettings(string apiKey, string apiHost, List<string> segmentsToCheck); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OptimizelySDK/Odp/IOdpManager.cs
Outdated
/// <summary> | ||
/// Sends signal to stop Event Manager and clean up ODP Manager use | ||
/// </summary> | ||
void Close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OptimizelySDK/Odp/OdpConfig.cs
Outdated
/// </summary> | ||
/// <param name="toCompare">OdpConfig object to compare current instance against</param> | ||
/// <returns>True if equal otherwise False</returns> | ||
public bool Equals(OdpConfig toCompare) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal of this is to control the equality determination of two ODP Configs.
Thanks for the callout on null exception. I'll change to use IEqualityComparer
.
public bool UpdateSettings(string apiKey, string apiHost, List<string> segmentsToCheck) | ||
{ | ||
var newConfig = new OdpConfig(apiKey, apiHost, segmentsToCheck); | ||
if (_odpConfig.Equals(newConfig)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes Sir. I'll update once using the IEqualityComparer
from OdpConfig
OptimizelySDK/Odp/OdpManager.cs
Outdated
/// <returns>Qualified segments for the user from the cache or the ODP server</returns> | ||
public List<string> FetchQualifiedSegments(string userId, List<OdpSegmentOption> options) | ||
{ | ||
if (!_enabled || SegmentManager == null || !_odpConfig.IsReady()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can re-arrange (I think that's what you're asking)
I thought that perhaps !_enabled
would evaluate to true more often than the others, but happy to switch.
OptimizelySDK/Odp/OdpManager.cs
Outdated
Dictionary<string, object> data | ||
) | ||
{ | ||
if (!_enabled || EventManager == null || !_odpConfig.IsReady()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My copy-paste finger started hurting, and I thought of that too.
L89, would have me injecting the Manager that should be checked for null
so I just left it.
Let me play with some options....
OptimizelySDK/Odp/IOdpManager.cs
Outdated
/// <param name="userId">FS User ID</param> | ||
/// <param name="options">Options used during segment cache handling</param> | ||
/// <returns>Qualified segments for the user from the cache or the ODP server</returns> | ||
List<string> FetchQualifiedSegments(string userId, List<OdpSegmentOption> options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yes. Sorry. Old habit from previous work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. A few changes suggested.
OptimizelySDK/Odp/OdpManager.cs
Outdated
// NOTE: It should be rare but possible that odp public key is changed for the same datafile (sdkKey). | ||
// - Try to send all old events with the previous public key. | ||
// - If it fails to flush all the old events here (network error), remaining events will be discarded. | ||
EventManager.Flush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we guarantee all pending events use the old ODPConfig with the non-blocking flush?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good consideration. I think this .Flush()
should grab a copy of the _odpConfig
in the OdpEventManager
and inject it into the _flushSignal
object. The secondary thread would then have the config to use for flush requests.
Does this seem like a suitable solution, or is this a corner case?
How does Swift (or Python) handle this situation?
What's your direction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikechu-optimizely We can consider -
- (swift) the current key and host are captures in the flush thread (so they can be used until flushing completed) before updating odpConfig or
- (python) delay config update until flush is completed with the current key and host.
return false; | ||
} | ||
|
||
_odpConfig = newConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When ODPConfig is updated, the OdpConfig object itself is replaced in ODPManager, while copying fields in ODPEventManager and ODPSegmentManager. Can we make it consistent in either way?
OptimizelySDK/Odp/OdpManager.cs
Outdated
if (!EventManager.IsStarted) | ||
{ | ||
_logger.Log(LogLevel.DEBUG, | ||
"ODP identify event not dispatched (ODP not integrated)."); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can drop this. It should be rare and it'll be filtered out in EventManager anyway.
OptimizelySDK/Odp/OdpManager.cs
Outdated
{ | ||
if (EventManagerOrConfigNotReady()) | ||
{ | ||
_logger.Log(LogLevel.DEBUG, "ODP event not dispatched (ODP disabled)."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_logger.Log(LogLevel.DEBUG, "ODP event not dispatched (ODP disabled)."); | |
_logger.Log(LogLevel.ERROR, "ODP event not dispatched (ODP disabled)."); |
Implicit event sending (IdentifyUser) should be DEBUG level, but when clients try to send events when ODP is not ready, it should be ERROR.
OptimizelySDK/Odp/OdpManager.cs
Outdated
if (!EventManager.IsStarted) | ||
{ | ||
_logger.Log(LogLevel.DEBUG, | ||
"ODP event not dispatched (ODP not integrated)."); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can drop this too.
{ | ||
var manager = new OdpManager | ||
{ | ||
_odpConfig = _odpConfig, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_odpConfig can be null if not set withOdpConfig
. Is it safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put a check in the Build()
for you 😄
|
||
_errorHandler = _errorHandler ?? new NoOpErrorHandler(); | ||
|
||
if (_eventManager == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if _enabled == false, then we do not need instantiate EventManager and SegmentManager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more changes suggested.
@@ -81,27 +81,6 @@ public OdpConfig(string apiKey, string apiHost, List<string> segmentsToCheck) | |||
SegmentsToCheck = segmentsToCheck ?? new List<string>(0); | |||
} | |||
|
|||
/// <summary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the entire odpconfig is replaced for update, then we do not need t"volitile" for internal fields.
@@ -349,7 +349,7 @@ public void IdentifyUser(string userId) | |||
/// <param name="odpConfig">Configuration object containing new values</param> | |||
public void UpdateSettings(OdpConfig odpConfig) | |||
{ | |||
_odpConfig.Update(odpConfig.ApiKey, odpConfig.ApiHost, odpConfig.SegmentsToCheck); | |||
_odpConfig = odpConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use to replace _odpConfig, don't we need to set it to "volitile"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
volatile
can only be set on some simple/primitive types.
Replacing the config as a whole now (instead of individual field values on the OdpConfig) should not be good. I'm not seeing where a child thread updates on the config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait, this is a Reference type. Duh.
I can mark it as volatile
if you see where other threads may update the config on this instance of the EventManager.
@@ -149,7 +149,7 @@ private static string GetCacheKey(string userKey, string userValue) | |||
/// <param name="odpConfig">New ODP Configuration to apply</param> | |||
public void UpdateSettings(OdpConfig odpConfig) | |||
{ | |||
_odpConfig.Update(odpConfig.ApiKey, odpConfig.ApiHost, odpConfig.SegmentsToCheck); | |||
_odpConfig = odpConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
volitile
OptimizelySDK/Odp/OdpManager.cs
Outdated
_logger = _logger ?? new DefaultLogger(); | ||
_errorHandler = _errorHandler ?? new NoOpErrorHandler(); | ||
|
||
if (_odpConfig == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about setting if (odpConfig == null) _odpConfig = new ODPConfig()
default.
Initially that's the best they can do when initializing ODPManager.
OptimizelySDK/Odp/OdpManager.cs
Outdated
// NOTE: It should be rare but possible that odp public key is changed for the same datafile (sdkKey). | ||
// - Try to send all old events with the previous public key. | ||
// - If it fails to flush all the old events here (network error), remaining events will be discarded. | ||
EventManager.Flush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikechu-optimizely We can consider -
- (swift) the current key and host are captures in the flush thread (so they can be used until flushing completed) before updating odpConfig or
- (python) delay config update until flush is completed with the current key and host.
|
||
if (_eventManager == null) | ||
{ | ||
var eventApiManager = new OdpEventApiManager(_logger, _errorHandler); | ||
|
||
manager.EventManager = new OdpEventManager.Builder().WithOdpConfig(_odpConfig). | ||
manager.EventManager = new OdpEventManager.Builder(). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering why using builder for OdpEventManager while not for OdpSegmentManager. Looks weird with 2 different patterns for similar objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Segment Manager is not too complex to instantiate. There's not a lot of logic that needed to be in the constructor, so I let a standard constructor do that work.
With Event Manager and ODP Manager, the combinations of instantiation parameters and logic in those combinations warranted (in my opinion) to use the Builder Pattern.
In practice, I don't have a hard limit of constructor length before breaking the logic out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. One more fix for thread-safe.
OptimizelySDK/Odp/OdpManager.cs
Outdated
@@ -35,7 +35,7 @@ public class OdpManager : IOdpManager, IDisposable | |||
/// <summary> | |||
/// Configuration used to communicate with ODP | |||
/// </summary> | |||
private volatile OdpConfig _odpConfig; | |||
private OdpConfig _odpConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you remove "volitile" here? Multiple thread read/write it here too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Summary
Added ODPManager Implementation which does the following.
Test plan
Issues
FSSDK-8453