Skip to content

Use module initializer for WSAStartup in System.Net.Sockets on Windows#43268

Closed
stephentoub wants to merge 1 commit intodotnet:masterfrom
stephentoub:socketsmodinit
Closed

Use module initializer for WSAStartup in System.Net.Sockets on Windows#43268
stephentoub wants to merge 1 commit intodotnet:masterfrom
stephentoub:socketsmodinit

Conversation

@stephentoub
Copy link
Member

@jkotas, this is what I mentioned to you. Do you have concerns about this usage? It seems to make sense, given that there's no reason to use System.Net.Sockets if you're not going to use one of the the things that requires this initialization, so from a trimming perspective it shouldn't matter. And it helps to simplify the usage and make it less likely code paths would be added that need this initialization but don't get it (which would be easy to sneak through testing).

cc: @geoffkizer

@ghost
Copy link

ghost commented Oct 11, 2020

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

@jkotas
Copy link
Member

jkotas commented Oct 11, 2020

  • Module initializers are at odds with IL linking. They will be never IL-linked away if the module is used. I think this specific case is ok given the current factoring of System.Net.Sockets. However, it is something we need to keep in mind if we add new types to System.Net.Sockets or merge assemblies together.

  • The timing of the module initializers (how early they are run) is unpredictable. It depends on when the module is loaded that can vary based on e.g. JIT inlining heuristics or the AOT precompilation mode. Are we ok with console applications always initializing Sockets on Windows before Main method starts, even if they use networking rarely or on unreachable path?

@marek-safar Do you have thoughts on use of module initializers? Are there any Mono-specific concerns with using them?

@jkotas
Copy link
Member

jkotas commented Oct 11, 2020

make it less likely code paths would be added that need this initialization but don't get it

I believe that this risk is pretty small given the current System.Net.Sockets factoring. The number of places that need this initialization is very small and they are easy to see.

I do agree that there is opportunity to simplify this initialization: It needs to be in Windows-specific PAL only, it does not need lock or any cache (can depend on cache in Dns.GetHostName).

@marek-safar
Copy link
Contributor

Do you have thoughts on use of module initializers? Are there any Mono-specific concerns with using them?

I don't have any Mono specific concerns but I'm not a fan of any global initialization. As you correctly pointed out will hurt us on trimming and I'll also need to check if we can optimize the initialization under AOT at all. Could we try to move the initialization to Windows PAL layer instead?

@stephentoub
Copy link
Member Author

Could we try to move the initialization to Windows PAL layer instead?

It's only built in on windows.

will hurt us on trimming

Can you give a real example where this will result in less stuff being trimmed? If you were using sockets on windows, this initialization was being kept; now it's still being kept, but with much less code to do so.

If you're saying the presence of a module initializer will prevent anything in the assembly from being trimmed, that sounds like a bug in the linker.

@MichalStrehovsky
Copy link
Member

If you're saying the presence of a module initializer will prevent anything in the assembly from being trimmed, that sounds like a bug in the linker.

If anything from the module is being referenced, the module initializer will be kept intact by the linker. So e.g. if the only thing the app uses from the entire assembly is the ProtocolType enum, linker will still keep the initializer and everything referenced from it.

@stephentoub
Copy link
Member Author

stephentoub commented Oct 11, 2020

Right, that's what I expected. What is a real world example where that would happen and be meaningful? Where a Windows app would be referencing System.Net.Sockets just to use ProtocolType and not using pretty much anything else in the networking stack and where the extra kept methods mattered.

@jkotas
Copy link
Member

jkotas commented Oct 11, 2020

Could we try to move the initialization to Windows PAL layer instead?

Having slept on this, this is my opinion as well.

@stephentoub
Copy link
Member Author

What does that look like in your opinion?

@stephentoub
Copy link
Member Author

stephentoub commented Oct 11, 2020

My main concern here is, to me, this looks like a about as ideal a use case for module initializers you could find (some init code needs to be run before anything meaningful in the library can be used, and there are multiple entry points into the library that all need to be protected). If we're unwilling to use one here, out of concerns around linking and load order and startup costs and whatnot, I think we're basically saying the feature shouldn't be used at all, not only in all of dotnet/ repositories, but discouraging devs in general. Maybe that's the right thing, and we can establish that precedent here, but it's unfortunate for a feature we're about to ship a new in C#.

@marek-safar
Copy link
Contributor

I think we're basically saying the feature shouldn't be used at all, not only in all of dotnet/ repositories, but discouraging devs in general.

I see this feature as similarly to explicit type initializers. They are not discouraged in general but we try to avoid them in core or optimized libraries.

@jkotas
Copy link
Member

jkotas commented Oct 11, 2020

What does that look like in your opinion?

Something like this: #43284

@stephentoub
Copy link
Member Author

I see this feature as similarly to explicit type initializers. They are not discouraged in general but we try to avoid them in core or optimized libraries.

Sure, but we don't entirely avoid them. For example, here:


and

we could have similarly instrumented all of the relevant methods with a lazy-init check that invokes this, but we instead used a static cctor. Is the suggestion then to go "fix" that?

Similarly here in GdiPlus:

static Gdip()
{
Debug.Assert(s_initToken == IntPtr.Zero, "GdiplusInitialization: Initialize should not be called more than once in the same domain!");
PlatformInitialize();
StartupInput input = StartupInput.GetDefault();
// GDI+ ref counts multiple calls to Startup in the same process, so calls from multiple
// domains are ok, just make sure to pair each w/GdiplusShutdown
int status = GdiplusStartup(out s_initToken, ref input, out StartupOutput output);
CheckStatus(status);
}

Or here in OidLookup.cs:

static OidLookup()
{
InitializeLookupDictionaries();

we could have restructured the code to make it easier to do this initialization in a field initializer, but it was left using a static cctor. Is that a mistake?

The point being, there are times where we've continued using static cctors even though they have some potential perf impact, because the alternatives were seen to be worse. I'm not seeing how that's different from this case. Here we've got some code that needs to be executed before anything meaningful in the library is run, and with multiple entry points that require it, which seems like what module initializers are made for. If we're not comfortable using or recommending module initializers for this case, then I don't know when we'd ever recommend using them. If there's a concern that it negatively impacts linking and the size of a consuming app, well, that's going to impact any relevant app that pulls in that library from nuget. If there's a concern that it negatively impacts the startup time of a consuming app because the runtime might end up running the initializer as part of startup even if the library is rarely used, that's going to similarly impact any relevant app that pulls in that library from nuget.

Which is why I'm pushing on this. Again, those might be legitimate concerns, but if they are, to the point where we choose not to use the functionality in this case, then I think that's equivalent to saying "we don't believe this feature should be used outside of toys; you should instead instrument all code paths that need the logic with a lazy-initialized open-coded version". And I'd expect us to add a new analyzer that warns when someone adds a module initializer, ala CA1810 for static cctors (https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1810). Which makes me a bit sad, for a feature we've literally not shipped yet and are already saying don't use it.

@jkotas
Copy link
Member

jkotas commented Oct 12, 2020

Yes, we use explicit type initializers where appropriate. I think explicit type initializers should be preffered over module initializers in the core libraries. I think the bit of extra overhead is worth paying for the predictable behavior and easier to reason about codebase.

C++ static global constructors are equivalent of module initializers. There was a lot written about how you can use C++ static global constructors to shoot yourself in the foot. The classic one is https://www.bing.com/search?q=Initialization+Order+Fiasco . One of recomended ways to fix SIOF is to use local static constructors that are equivalent of our type initializers.

I'd expect us to add a new analyzer that warns when someone adds a module initializer, ala CA1810 for static cctors

Yep, some C++ compilers have a warning for global constructors too: https://releases.llvm.org/10.0.0/tools/clang/docs/DiagnosticsReference.html#wglobal-constructors . And large C++ infrastructure-type projects often have rules against using global constructors, for example: https://llvm.org/docs/CodingStandards.html#do-not-use-static-constructors .

we don't believe this feature should be used outside of toys

I think it is fine to use global constructors for registrations in the application layer. For example, I expect that we will see source generators that are using module initializers to register their generated code. Again, this does not work well with IL linking. It should be ok application layer, especially if the source generator is optimization of a reflection-heavy algorithm that would have prevented anything in the application layer to be tree-shaked anyway. It would not be a good pattern to use inside linker-friendly libraries though.

@stephentoub
Copy link
Member Author

I opened #43328 for an analyzer.

@stephentoub stephentoub deleted the socketsmodinit branch November 21, 2020 01:10
@ghost ghost locked as resolved and limited conversation to collaborators Dec 21, 2020
@karelz karelz added this to the 6.0.0 milestone Jan 26, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants