-
Notifications
You must be signed in to change notification settings - Fork 339
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
FhirClient SearchAsync not thread safe #1110
Comments
FhirClient is not thread-safe. It holds on to state from the current operation. |
That's right. By design, most classes (just like the .NET framework by the way) are not thread-safe. The FhirClient is pretty lightweight to construct and indeed keeps state per request (like the result of the last request). @gstoian is re-architecting a new FhirClient based on @BradBarnich's previous work - so this would be a good moment to discuss whether the FhirClient should or should not be thread-safe. |
@BradBarnich, @ewoutkramer Thanks for the information. I figured it was intended to be thread safe based on this documentation: https://ewoutkramer.github.io/fhir-net-api/client-setup.html : In our case, we are only dealing with a single FHIR server, but in some cases need to "multithread" (using async/await and something like Parallel.ForEach with a MaxDegreeOfParallism, not actually dealing with creating Threads ourselves). So if it is ok to essentially create a separate FhirClient per concurrent processing workflow (i.e. for the life of a given Task, that may make multiple requests based on a single quantity of incoming data), then we will just do that for now. Thanks again!! |
Good catch, I can see how that causes confusion. @mbaltus would you be able to change that into something that does not suggest to use a single client per thread? |
@mbaltus - can you change this line. Also, we need to take the documentation on github.io offline - its wildly outdated. |
Updated the documentation AND redirected the users away from the github.io pages. |
@ewoutkramer I see that you updated the documentation to show the following:
Can you verify that this is still the case in the new 2.x version that uses HttpClient? Or is that new version threadsafe and the proper usage when used in parallel (using the async framework) is to just create a single FhirClient per endpoint even if it may be used in parallel (like in https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreach?view=net-5.0 for example)? |
Yes, option b. We want to keep the options open that we cache stuff or do non-threadsafe things in our implementations. There are only a few classes (mostly around caching) in the SDK that are threadsafe. So we could do it if there's a strong usecase to have a single instance. But I haven't heard one so far. |
@ewoutkramer sorry I'm still confused about what we are supposed to do here. We need to process data in parallel using a mechanism similar to the one shown in my first message back in #1110 (comment) We now have realized that we need to use the 2.x fhir-net-api (and we do like that FhirClient now uses HttpClient under the hood), so we are in a bit of a bind as to how to use it in parallel. If you look at the test code I posted when I created this ticket, which one of the following should be the suggested usage for FhirClient 2.0?
UPDATE:.. A 3rd option we are kicking around is generating a pool of FhirClients whose size would be equal to the "max parallelism" that would be allocated and assigned (blocking until one becomes available), and so perhaps it would avoid the thread safety issues inherent in using a single FhirClient, but also prevent issues described in the above article with instantiating umpteen million FhirClients (each of which creates a new HttpClient) |
I was going to say:
This is your solution. But then I read:
Well, I guess that is right. Because looking at the code, if you'd fire a large number of parrallel request you'd literally be swamping the target server and (on a lower level) the available interrupts on your own machine. The target server's owner (unless that is you), might not really enjoy being targeted this way! So, the more advanced approach on having a pool seem to make sense, but that of course limits your parrallelism. |
I think this topic may need to be revisited. Support for thread safety is a fundamental feature of a RESTful client. |
What would be needed? Getting a fresh (stateful) client out of a pool of clients for the same endpoint with the same authentication settings like discussed above? I personally have never needed thread safety, instead have created a new instance of the client, so I'd like to get a better feel of where the client is deficient currently. |
I guess what we need to do is centered around these best-practices? https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0 |
@ewoutkramer @brianpos so are you saying that if we use the version with feature #2055 that we should now be safe to use HttpClientFactory to create a single HttpClient (that we maintain and dispose ourselves) passed into new instances of FhirClient constructor (as opposed to a pool of clients or a single FhirClient, both approaches as described above each have their own drawbacks)? Put another way, this gets around the issues described in https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ but that the reused HttpClient should itself be threadsafe? |
I am able to reproduce this issue pretty reliably, using both Hl7.Fhir.STU3 0.96.0.0 and 1.3.0 (When we run this, one of those Assert.IsTrue()'s eventually fail for us). Here is my test code (uses NUnit 3.10.0). There are some helper methods and whatnot that we've implemented (i.e. the "retryPolicyFactory" just supports the ability to retry any operation a few times if they fail for any reason, the _patientRootOid could be just any old OID, and the FhirClient here is actually a wrapper because we need to inject an OAUTH token header for each request), but none of that should have a bearing on the results of the test.
Using _numParallelThreads = 15 for the purposes of this test.
Also, when we spin up multiple FhirClients (or possibly create a new one at the beginning of every iteration of the Parallel.ForEach), this issue does not occur. So is that a valid solution or workaround (i.e. to ensure each separately running Task is using its own FhirClient instance)?
We were under the impression that a single FhirClient instance should be used by multiple concurrent Tasks, but per this test that does not appear to be the case.
If anyone is able to reproduce this issue, please comment back here as this is becoming a real operational headache for us. Thanks!
`[Test]
public async System.Threading.Tasks.Task TestFhirClientCallsInParallel()
The text was updated successfully, but these errors were encountered: