-
Notifications
You must be signed in to change notification settings - Fork 10
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
Add config change handler for Bulkhead. Simplify BulkheadConfig. #64
Add config change handler for Bulkhead. Simplify BulkheadConfig. #64
Conversation
@@ -45,7 +46,7 @@ public BulkheadFactory(IMetricEvents metricEvents, IBulkheadConfig bulkheadConfi | |||
if (_bulkheads.TryGetValue(key, out Lazy<SemaphoreBulkheadHolder> holder) && holder.IsValueCreated) | |||
{ | |||
var bulkhead = holder.Value.Bulkhead; | |||
_metricEvents.BulkheadGauge(bulkhead.Name, "semaphore", _bulkheadConfig.GetMaxConcurrent(key), bulkhead.CountAvailable); | |||
_metricEvents.BulkheadGauge(bulkhead.Name, "semaphore", _config.GetBulkheadConfiguration(key.Name).MaxConcurrent, bulkhead.CountAvailable); |
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.
Would it be better to pass in a GroupKey
to GetBulkheadConfiguration
?
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 had considered that but decided that it'll be easier to implement Configuration in any adapter if that will be just the string. So for simplicity, I've left it as a string.
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.
More of an opinion, but I think it's clearer to the calling code if it just has to pass in a GroupKey
, since then callers don't have to worry about translating a GroupKey
to its appropriate configuration string - the configuration code can know what to do with the GroupKey
and do it consistently for any call. GroupKey
is the abstraction over bulkhead/breaker identifiers and I've always preferred to use that within Mjolnir if it's available instead of raw strings.
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.
Not a strong argument, but sort of relevant: http://wiki.c2.com/?StringlyTyped
public void OnNext(BulkheadConfiguration value) | ||
{ | ||
var newValue = _expression(value); | ||
var hasChanged = !Equals(_currentValue, newValue); |
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.
Does this work, particularly for reference types? If T
is a String
, what does this evaluate to if they're the same value?
Given that this is Object.Equals
and string
s are reference types, I'm concerned this'll do an object reference comparison.
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 work as string has "Equals" overriden. From Object code:
public virtual bool Equals(object obj)
{
return RuntimeHelpers.Equals(this, obj);
}
public static bool Equals(object objA, object objB)
{
if (objA == objB)
return true;
if (objA == null || objB == null)
return false;
return objA.Equals(objB);
}
I've also checked with my debugger and it seems to work on my machine.
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.
Ah cool, thanks for checking.
namespace Hudl.Mjolnir.Config | ||
{ | ||
/// <summary> | ||
/// Used only for configs which will never change. |
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.
Minor, but is this true? If observables aren't used, configuration values can still change, correct? It's just that they won't fire change events. A re-read of the configuration property may yield a different value, though, right?
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.
In that case "Static" may be a bit misleading as the type name here. Could go with something like NonObservableBulkheadConfiguration
?
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.
For the first comment: It will never change since this is only used for DefaultMjolnirConfig which is implemented inside the library already. If someone will provide different config they have to implement their own implementation of BulkheadConfiguration.
I'll make that class internal!
Also I'll change its name.
|
||
namespace Hudl.Mjolnir.Config | ||
{ | ||
internal static class BulkheadConfigurationObserverCreationExtensions |
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.
Minor: Since these are extensions on BulkheadConfiguration
, I'd expect this to be named BulkheadConfigurationExtensions
.
@@ -0,0 +1,11 @@ | |||
{ | |||
"testconfig": { | |||
"isenabled": true, |
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.
Does case matter here? The case of these keys is inconsistent.
|
||
/// <summary> | ||
/// Per-breaker configuration. breaker-key is the argument passed to the Command constructor. | ||
/// </summary> | ||
protected Dictionary<string, BreakerConfiguration> BreakerConfigurations { get; set; } | ||
public Dictionary<string, BreakerConfiguration> BreakerConfigurations { get; set; } |
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.
Should the keys for these Dictionaries be GroupKey
s now that it's the argument to access config?
{ | ||
if (key == null) return DefaultBulkheadConfiguration; | ||
if (string.IsNullOrEmpty(key.Name)) return DefaultBulkheadConfiguration; |
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.
IsNullOrWhiteSpace
@@ -100,11 +102,11 @@ public CommandConfiguration GetCommandConfiguration(string groupKey = null) | |||
/// <param name="key">Bulkhead configuration for a given key. Default value returned if non-existent or | |||
/// null.</param> | |||
/// <returns></returns> | |||
public BulkheadConfiguration GetBulkheadConfiguration(string key = null) | |||
public BulkheadConfiguration GetBulkheadConfiguration(GroupKey key) |
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.
Are the other methods (GetCommandConfiguration
, GetBreakerConfiguration
) switching over to GroupKey
instead of string
as well?
|
||
public void OnError(Exception error) | ||
{ | ||
throw new NotImplementedException(); |
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.
Is this the best approach here? i.e. is it impossible for OnError
to happen, or should we have better handling here?
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.
Good find. I'll change to no-op.
FWIW With my implementation, it's not possible for OnError or OnCompleted to be fired.
} | ||
catch (Exception) | ||
{ | ||
//Purely to make sure that other change events still fire |
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 this be clearer about why we're ignoring the exception? Should we at least log here?
/// </summary> | ||
/// <param name="observer">Mjolnir configuration observer which is acting upon configuration change</param> | ||
/// <returns></returns> | ||
public abstract IDisposable Subscribe(IObserver<MjolnirConfiguration> observer); |
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.
Is it possible to have this class (MjolnirConfiguration
) handle all of the observer logic and make this internal
instead of public abstract
? It looks like most of the implementations of this are the same - do inheriting classes need their own control over how the subscriptions work?
BulkheadConfiguration bulkheadConfiguration; | ||
return BulkheadConfigurations.TryGetValue(key, out bulkheadConfiguration) ? | ||
return BulkheadConfigurations.TryGetValue(key.Name, out bulkheadConfiguration) ? |
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.
Is it possible for the Dictionary to have a null value for any key? It might be good to protect against returning null
values from this method to prevent NullReferenceExceptions
when callers access properties on the returned value, since the calling code assumes non-null objects returned.
|
||
|
||
/// <summary> | ||
/// Allows subscribtions for configuration change. Whenever any property change in MjolnirConfig all |
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.
"subscribtions"
I've added
IObservable
intoMjolnirConfiguration
.I've made it abstract, so someone using the library may wire-up their implementation of Config change handler.
I've added
NonObservableMjolnirConfiguration
with no-op implementation for default config.I've added sample wire-up using Microsoft JSON configuration.
I've added a couple test which should test change handlers using sample configuration.