Use module initializer for WSAStartup in System.Net.Sockets on Windows#43268
Use module initializer for WSAStartup in System.Net.Sockets on Windows#43268stephentoub wants to merge 1 commit intodotnet:masterfrom
Conversation
|
Tagging subscribers to this area: @dotnet/ncl |
@marek-safar Do you have thoughts on use of module initializers? Are there any Mono-specific concerns with using them? |
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 |
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? |
It's only built in on windows.
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. |
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 |
|
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. |
Having slept on this, this is my opinion as well. |
|
What does that look like in your opinion? |
|
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#. |
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. |
Something like this: #43284 |
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: Or here in OidLookup.cs: 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. |
|
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.
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 .
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. |
|
I opened #43328 for an analyzer. |
@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