-
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!: Add support for provider shutdown and status. #158
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #158 +/- ##
==========================================
+ Coverage 93.91% 94.30% +0.39%
==========================================
Files 20 21 +1
Lines 542 650 +108
Branches 39 54 +15
==========================================
+ Hits 509 613 +104
- Misses 20 23 +3
- Partials 13 14 +1 ☔ View full report in Codecov by Sentry. |
Signed-off-by: Ryan Lamb <[email protected]>
73c15d9
to
22b07b0
Compare
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param> | ||
public void SetProvider(FeatureProvider featureProvider) | ||
public async Task SetProvider(FeatureProvider featureProvider) |
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.
So, returning a task here is a breaking API change. This seems like the most appropriate action. Await the method if you care that it finishes initialization, don't await if you do not.
Other options would be adding another method, but this method would just be async without a way to know it finished, versus being sync.
This all applies to named providers 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.
Yea I've been thinking about this myself its quite common to offer bother sync and async libraries especially when working on a old legacy project in full framework. Lets just leave it for now and see if anyone raise concern before going ahead an introduce a sync based methods.
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.
So, returning a task here is a breaking API change. This seems like the most appropriate action. Await the method if you care that it finishes initialization, don't await if you do not.
Other options would be adding another method, but this method would just be async without a way to know it finished, versus being sync.
"events" are the mechanism by which you can call this synchronously and "know" the outcome (your READY/ERROR event handlers will run). I know they don't exist on this SDK yet, but they will eventually.
Other options would be adding another method
This is what's been done in Java and JS, and what the spec recommends: https://openfeature.dev/specification/sections/flag-evaluation/#requirement-118
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.
So, the spec makes a lot of sense for Java. Complete-able futures are not super convenient, and you would probably need something like kotlin on top to make a useful API out of it.
Dotnet though has reasonably proper async, and the general means would be a method you either await, or don't await, or synchronously wait.
The language in the spec doesn't really work, and I am not sure what would.
Usually if you have a sync and async method you would have.
doTheThing
and doTheThingAsync
. But, doTheThing
would be synchronous, not async without completion.
The spec recommends setProviderAndWait
, which implies sync, but I don't think we actually want to call .Wait
on behalf of the user. They can make the choice to asynchronously await, not await or wait, or to synchronously wait, and we don't have to worry about quirks in their thread execution model that could lead to things like a deadlock.
Either approach will result in an API that is not intuitive for the ecosystem. It can be done, it is just unfortunate.
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.
(To elaborate AndWait
or Wait
in general have been used to provide sync APIs where the base API is async, so it would be a confusing name)
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 think, if the API is fully async, then we don't need to worry about the Async suffix. It really is a case of libraries that implemented things synchronously, and then needed async methods as well.
The interface for this API is almost all Async, and it happened that there was this sync method, so making it async would provide a consistent async interface.
It may be worth checking the rest of the API to see if other items should move to async with this.
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 guess the client is really where things are async, and the "Api" doesn't have that many methods that are async, or would conceivably be async.)
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'm fine with keeping it all async and not providing a sync api. I think since we aren't 1.x.x yet its the best time to make a breaking change to SetProvider, i think we just need to call it out in the release notes.
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 SDK is already version 1.3.1
but we can release a version 2.0
if it makes sense. I think it does make sense in this case since it simplifies waiting for a provider to initialize and the migration process should be straightforward.
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'm fine with the breaking change, especially if we agree we'll do the same breaking change in JS, which has the same async/await semantics.
{ | ||
this._featureProviderLock.ExitWriteLock(); | ||
} | ||
await this._repository.SetProvider(featureProvider, this.GetContext()).ConfigureAwait(false); |
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 refactored the collection management out of the Api and moved it into the ProviderRepository. This is similar to the Java SDK.
@@ -20,6 +21,74 @@ public void OpenFeature_Should_Be_Singleton() | |||
openFeature.Should().BeSameAs(openFeature2); | |||
} | |||
|
|||
[Fact] |
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 added some basic tests here, but the tests for the registry are much more comprehensive.
public async Task SetProvider( | ||
FeatureProvider featureProvider, | ||
EvaluationContext context, | ||
Action<FeatureProvider> afterSet = 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.
All these "actions" can be used to facilitate implementing events support. I didn't want to put too much stuff together, so this should be a nice middle ground.
Sorry for the delayed response. Now that we have the ability to perform initialization from within the provider, it makes sense to make this an async call. Unfortunately, that means it will be a breaking change, but it should be easy for users to absorb this change. I did bring this up during the last community call, and no one had any concerns. I think I'll also do something similar in the JS SDK. Before we release a 2.0 version, could the rest of the SDK be audited to make sure there are no other breaking changes we should consider? |
this._providersLock.ExitWriteLock(); | ||
} | ||
|
||
await InitProvider(featureProvider, context, afterInitialization, afterError).ConfigureAwait(false); |
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.
At first I thought it was necessary to put this in the locked block, but I don't think it matters, since the important thing is to maintain thread safety of the provider dict, not make any guarantees about provider initialization order.
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.
Testing looks good. I'm OK with the breaking change as discussed, but in that case I think we should probably wait for events and release both of these changes together.
Co-authored-by: Todd Baert <[email protected]> Signed-off-by: Ryan Lamb <[email protected]>
Should I re-target this PR to a feature branch? |
I would think it's fine to merge we just don't release/publish the next version until events are done. |
No no, we'll just refrain from releasing if possible until events. If we need to release for some reason, it won't be a huge problem to release this on its own. The worse case would be we have another breaking change for events (not likely, it didn't happen in other SDKs) and we release a v3. |
This PR
Adds support for provider initialization, shutdown, and status.
This PR does not add support for events, but does put several items in place to facilitate their implementation.
Related Issues
Partially Addresses #126
Notes
Follow-up Tasks
How to test