Skip to content
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

WCF: action is assigned with null if operation name is not defined(#169) #170

Merged

Conversation

tamilaban
Copy link
Contributor

public string DisplayName { get { return _displayName ?? OperationName; } set { _displayName = (value ?? throw new ArgumentNullException("value")); }

        Display name throws Argument null exception if action is null or operation contract name is defined in the WCF operationContract . 

@tamilaban tamilaban requested a review from CodeBlanch as a code owner January 5, 2022 07:43
@tamilaban tamilaban requested a review from a team January 5, 2022 07:43
@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Jan 5, 2022

CLA Signed

The committers are authorized under a signed CLA.

  • ✅ Tamilanban Chinnaiyan (d6575d1)

@CodeBlanch
Copy link
Member

@tamilaban Thanks for the contribution! Would you mind adding a test that covers this case?

@codecov
Copy link

codecov bot commented Jan 5, 2022

Codecov Report

Merging #170 (d7fff85) into main (e039516) will increase coverage by 0.01%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #170      +/-   ##
==========================================
+ Coverage   78.60%   78.62%   +0.01%     
==========================================
  Files          89       89              
  Lines        2323     2325       +2     
==========================================
+ Hits         1826     1828       +2     
  Misses        497      497              
Impacted Files Coverage Δ
...rumentation.Wcf/TelemetryClientMessageInspector.cs 83.11% <100.00%> (+0.45%) ⬆️

@tamilaban
Copy link
Contributor Author

@tamilaban Thanks for the contribution! Would you mind adding a test that covers this case?

HI @CodeBlanch , I could not make the action as null, I have assigned the action name to an empty string and updated the unit test in adherence . Can you please have a look ?

@tamilaban
Copy link
Contributor Author

@tamilaban Thanks for the contribution! Would you mind adding a test that covers this case?

HI @CodeBlanch , I could not make the action as null, I have assigned the action name to an empty string and updated the unit test in adherence . Can you please have a look ?

Hi @CodeBlanch , Can you please review this PR ?

@CodeBlanch
Copy link
Member

@mconnew Hey I could use some help on this if you have any free cycles.

So our telemetry for WCF calls does this...

  • Span name: {Action}
  • Tag rpc.service: {ContractName}
  • Tag rpc.method: {OperationName}

How we find ContractName + OperationName is we pre-build a lookup dictionary:

foreach (var clientOperation in clientRuntime.ClientOperations)
{
actionMappings[clientOperation.Action] = new ActionMetadata
{
ContractName = $"{clientRuntime.ContractNamespace}{clientRuntime.ContractName}",
OperationName = clientOperation.Name,
};

When we have an Action to use for the lookup this all seems to work great.

Now the user is reporting a contract without action:

    [ServiceContract(Namespace = "http://opentelemetry.io/", Name = "Service", SessionMode = SessionMode.Allowed)]
    public interface IServiceContract
    {
        [OperationContract]
        Task<ServiceResponse> ExecuteAsync(ServiceRequest request);

        [OperationContract(Action = "")]
        Task<ServiceResponse> ExecuteWithEmptyActionNameAsync(ServiceRequest request);
    }

That sort of accidentally still works because the empty action still ends up on the lookup. But if contract has multiple empty actions...

    [ServiceContract(Namespace = "http://opentelemetry.io/", Name = "Service", SessionMode = SessionMode.Allowed)]
    public interface IServiceContract
    {
        [OperationContract]
        Task<ServiceResponse> ExecuteAsync(ServiceRequest request);

        [OperationContract(Action = "")]
        Task<ServiceResponse> ExecuteWithEmptyActionNameAsync(ServiceRequest request);

        [OperationContract(Action = "")]
        Task<ServiceResponse> SomeOtherOperationWithEmptyActionNameAsync(ServiceRequest request);
    }

...then the telemetry is going to lie and always pick the first empty entry.

What I need is some way in...

public object BeforeSendRequest(ref Message request, IClientChannel channel)

...to get at the operation data.

I don't see anything public to help. It seems like there is private stuff like Message.BodyWriter.OperationFormatter I could possibly use reflectively, but hoping to avoid that.

Any ideas?

@mconnew
Copy link

mconnew commented Jan 21, 2022

@CodeBlanch, does an empty Action actually work for multiple operations? Here's the default code where WCF looks up which operation is being executed based on the incoming message, and as you can see it uses a dictionary with the action as the key. So two operations with the same action won't work by default. I took a deeper look at the code to see if there should be an exception on service startup, and it looks like there should be as ActionDemuxer is calling HybridDictionary.Add which throws on duplicates. This is only with the default behavior though.

There is another alternative how this might work. If an implementation of IDispatchOperationSelector has been provided in the application, then the operation name is resolved by calling IDispatchOperationSelector.SelectOperation(ref request). In this case it's the applications implementation of the SelectOperation method which works out which operation is being used. You can extend the current implementation to check if an instance of IDispatchOperationSelector has been provided, and if so use that to get the operation name from the Message.

The only way I am aware of where that you can have two operations with the same action without it throwing when you start the service is to use a custom IDispatchOperationSelector. If I'm wrong about this, let me know and I'll do some deeper investigation to work out what's happening.

@CodeBlanch CodeBlanch changed the title action is assigned with null if operation name is not defined(#169) WCF: action is assigned with null if operation name is not defined(#169) Feb 5, 2022
@CodeBlanch
Copy link
Member

@mconnew Thanks for the great info! I did a bit of testing. With two operations defined with Action = '' starting a server, I get an exception. Starting a client seems to work fine. I think in that case the logic is good and should cover the common case. I'll implement IDispatchOperationSelector as a separate PR.

@CodeBlanch CodeBlanch merged commit 667fc49 into open-telemetry:main Feb 5, 2022
@mconnew
Copy link

mconnew commented Feb 5, 2022

It's allowed on the client as you can have a Task based and synchronous version of the same operation in a contract. They must be equivalent though. I think it might even modify the name so they both have the same operation name. There's no ambiguity about what to do there. It's only on the server side that you can't have the ambiguity.

@tamilaban
Copy link
Contributor Author

Thanks much @CodeBlanch and @mconnew .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants