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

Isolate scoped settings in default OData services #3071

Merged
merged 5 commits into from
Sep 25, 2024

Conversation

habbes
Copy link
Contributor

@habbes habbes commented Sep 23, 2024

Issues

*This pull request fixes #3070 *

Description

Clones the reader, writer and URI parser settings when registering them as scoped services in AddDefaultODataServices() to ensure that different request scopes get different copies that can be independently modified.

Checklist (Uncheck if it is not completed)

  • Test cases added
  • Build and test with one-click build and test script passed

Additional work necessary

If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.

@@ -55,15 +55,15 @@ public static IServiceCollection AddDefaultODataServices(this IServiceCollection
// Finally, register configurable settings.
var readerSettings = new ODataMessageReaderSettings(odataVersion);
configureReaderAction?.Invoke(readerSettings);
services.AddScoped(sp => readerSettings);
services.AddScoped(sp => readerSettings.Clone());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Would there be an issue if we called configure reader action multiple times e.g. if we used a delegate instead of the cloned instance?

Copy link
Contributor Author

@habbes habbes Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you referring to this approach:

services.AddScope(_ => {
    var readerSettings = new ODataMessageReaderSettings(...);
    configureReaderAction?.Invoke(readerSettings);
    return readerSettings;
});

If so, I considered it (and mentioned it in the attached issue). The reason I opted for cloning instead of that approach is because we can't predict the cost of the delegate since it's user defined, for all we know it could be reading from a file or database. On the other hand, since we control the implementation of the Clone() method, we can make better assumptions about it cost and we can optimize if it becomes a hotspot, so I think it's safer to err on the side of calling Clone() multiple times rather than calling the opaque user-defined delegate multiple times.

Copy link
Member

@marabooy marabooy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


var writerSettings = new ODataMessageWriterSettings(odataVersion);
configureWriterAction?.Invoke(writerSettings);
services.AddScoped(sp => writerSettings);
services.AddScoped(sp => writerSettings.Clone());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an ODataMessageWriterSettings is passed as argument to an ODataMessageWriter, the scoped ODataMessageWriterSettings service is being updated to that value?

Copy link
Contributor Author

@habbes habbes Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@syprieur That's a very good question, apparently it does :( The settings from the ODataMessageWriterSettings are copied to the scoped ODataMessageWriterSettings service:

see:

which calls:

internal static ODataMessageWriterSettings CreateWriterSettings(
    IServiceProvider container,
    ODataMessageWriterSettings other)
{
    ODataMessageWriterSettings writerSettings;
    if (container == null)
    {
        writerSettings = new ODataMessageWriterSettings();
    }
    else
    {
       // we get the scoped settings
        writerSettings = container.GetRequiredService<ODataMessageWriterSettings>();
    }

    if (other != null)
    {
        // then overwrite the scoped service based on the settings passed to the writer
        writerSettings.CopyFrom(other);
    }

    return writerSettings;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That explains the observed bug and confirms the hypothesis: the same settings instance is indeed re-used and updated concurrently.
Your change will fix it.

@habbes habbes merged commit 96cc99a into main Sep 25, 2024
5 checks passed
@habbes habbes deleted the fix/3070-isolate-scoped-settings branch September 25, 2024 07:19
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.

Changes in reader/writer/uri parser settings in one request affect other requests
4 participants