-
Notifications
You must be signed in to change notification settings - Fork 560
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
Expose ConcurrencyMode property in CallBackBehaviorAttribute. #4348
Expose ConcurrencyMode property in CallBackBehaviorAttribute. #4348
Conversation
To test it, you have a callback contract with an operation which takes a parameter which says how long to sleep before returning. You have a service method which defines the callback contract operation as Task based which allows you to make a second call before the first one has finished. In the callback method you increment (using Interlocked.Increment to prevent race conditions) a number then sleep (if callback is implemented async, using `await Task.Delay). After the sleep/delay you set a ManualResetEvent so the main test code can be woken up where it can check the number of callbacks called. Callback service code: Test code: If the callback is concurrent, the second call will execute while the first call is still running. This means the second call's ManualResetEvent.Set will happen first and the counter will be 2. If they are not concurrent, then the first calls ManualResetEvent will execute first and counter value will be 1. Re-entrant is really difficult to test. I'm inclined to deprecate it and maybe modify the implementation code to throw if Reentrant is specified. It doesn't play well with Task's. I'll discuss with @HongGit and let you know. For now just write tests for Single and Multiple. |
3a9a0eb
to
50e29c8
Compare
src/System.Private.ServiceModel/src/System/ServiceModel/Description/TypeLoader.cs
Show resolved
Hide resolved
…age with Action 'CallWithWait' while closing.
// *** EXECUTE *** \\ | ||
channel = factory.CreateChannel(); | ||
channel.DoWork(); | ||
// *** SETUP *** \\ |
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 don't see how removing the try block fixed this. I believe you just got lucky when you submitted it again. What's happening is the manual reset event is reset at the end of the method call which then returns to the service. That then makes a second call back to the client and the client is being closed at the same time as another call is coming in. There's a race condition and you happened to avoid it when the tests got ran again. What you need to do is save the task from channel.DoWork() (which needs renaming to DoWorkAsync as it's an async method) and await it before moving to cleanup. That way you you know both callbacks have completed and there will be no error.
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 was trying to code according to my understanding, but it doesn't seem to work as expected, here is the code snippet:
channel = factory.CreateChannel();
Task task = channel.DoWorkAsync();
Assert.True(imp.MyManualResetEvent.WaitOne(20000));
Assert.Equal(1, imp.Counter);
await task;
the await statement didn't wait for completion of the two tasks defined in DoWorkAsync which is awaiting Task.WhenAll(t1,t2). Should it work like 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.
The call to DoWorkAsync kicks off two async callbacks on the service side. In the service code you save each of the tasks and then await Task.WhenAll which shouldn't complete until both of the callback tasks have completed. One the await Task.WhenAll completes, the service side DoWorkAsync completes and sends the result (which just says it finished without an exception) to the client. On the client side the await on channel.DoWorkAsync task will complete when the server returns that completed message to the client. At that point there should be no requests in progress initiated by either the server or the client. The await task
statement should wait for completion of the two tasks defined in DoWorkAsync.
I'll pull down your changes and see if I can work out what's going on.
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 server side, the line below doesn't await but return immediately:
await Task.WhenAll(t1, t2);
I got some problem attaching the repro app here, it's attached in a new comment here.
ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)channel, factory); | ||
} | ||
// *** VALIDATE *** \\ | ||
Assert.True(CallbackHandler_ConcurrencyMode_Single.s_manualResetEvent.WaitOne(20000)); |
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.
Make s_manualResetEvent an instance field or property. When you create an instance of the class CallbackHandler_ConcurrencyMode_Single and pass it to the InstanceContext constructor, keep a reference to it and then you can access the manual reset event via the instance reference rather than statically.
src/System.Private.ServiceModel/src/System/ServiceModel/CallbackBehaviorAttribute.cs
Show resolved
Hide resolved
@imcarolwang, that repro app was really useful. I missed an important detail in your PR. You set the operations to
I verified the Single mode had a count of 1 and the Multiple mode had a count of 2. |
…ior with ConcurrencyMode.Multiple
@mconnew Thank you for the explanation! I understand now and I tried the fix on the repro app, it works as expected! |
For issue #1959.
@mconnew I've added the public property, it seems the feature should work as it's applied in the attribute implementation:
Regarding test scenario, I need your guidance on how to test it, what kind of combination for instance mode and concurrency mode would you suggest to use to test the feature? Thank you.