WIP: Halibut with async client support.
Background
This PR adds support for complete async Client. The Service is still sync and currently must remain sync.
Previously the Client had to make sync RPC calls. This was problematic since not only are threads unable to be returned to the thread pull, but we had sync code placing work onto the thread pool and then waiting for that work to be complete. All of this would lead to situations in which the client would run out of threads and then get deadlocked waiting for tasks to complete which would not be completed.
The change here adds support for making the Client side async, while the "contract" stays sync. This means Client and Service would both depend on a sync Interface and the Service would implement that sync interface. However this change allows the Client to use a async version of that interface in place of the sync one.
An example is captured in the test but for understanding what you have is:
interface Foo {
string Bar();
}
The Service would implement that interface and the Client would have a dependency on that interface.
To get async the Service would need to make a copy of that interface making every call async and appending "Async" to the end of every method e.g.
interface FooAsync {
Task<string> BarAsync();
}
The Client then gets a proxy for that by telling Halibut what the contract is (in this case Foo) as well as the async version of the interface (in this case FooAsync). For example:
var foo = octopus.CreateClient<IFoo, IFooAsync>("poll://SQ-TENTAPOLL", cert);
var s = await foo.BarAsync();
Under the hood halibut will map BarAsync to Bar Client side, and will then send the RPC call to Bar. In this way the Service (ie tentacle) does not need to change.
Curly bits
The AsyncManualResetEvent in the queue.
The polling request queue had to become all async, I think that meant that I could not longer make use of this line. In this PR I just made a dependency on Nito.AsyncEx.AsyncManualResetEvent which halibuts version of AsyncManualResetEvent depends on. The main difference between the two is:
_tcs.TrySetResultWithBackgroundContinuations()
Where halibut puts those continuations on the background (within a lock) while Nito does not. It is not clear to me why we have done this, I could not find a PR outlines that reason.
I also have not tried to use the Halibut AsyncManualResetEvent with that one difference reverted.
netframework 4.5 support.
It did not work immediately in that framework so I stopped building for that version.
How to review this PR
Quality :heavy_check_mark:
Pre-requisites
- [ ] I have read How we use GitHub Issues for help deciding when and where it's appropriate to make an issue.
- [ ] I have considered informing or consulting the right people, according to the ownership map.
- [ ] I have considered appropriate testing for my change.