Skip to content

Commit b27534e

Browse files
authored
Use implicit MTA for threadpool threads (dotnet#7367)
* Use implicit MTA for threadpool threads It is not ok to leave COM initialized on Win32 threadpool threads. This change skips COM initialization on Win32 threadpool threads completely and takes advantage of implicit MTA that is initialized by the finalizer thread. This fix should provide high compatiblity with .NET Framework/Core, without performance overhead of initialization/uninitializing COM every time; or running dedicated threadpool. Fixes dotnet#7356
1 parent 0617e3c commit b27534e

File tree

9 files changed

+98
-17
lines changed

9 files changed

+98
-17
lines changed

src/Native/Runtime/FinalizerHelpers.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,17 @@ void RhEnableFinalization()
130130
g_FinalizerEvent.Set();
131131
}
132132

133+
EXTERN_C REDHAWK_API void __cdecl RhInitializeFinalizerThread()
134+
{
135+
#ifdef APP_LOCAL_RUNTIME
136+
// We may have failed to create the finalizer thread at startup.
137+
// Try again now.
138+
RhStartFinalizerThread();
139+
#endif
140+
141+
g_FinalizerEvent.Set();
142+
}
143+
133144
EXTERN_C REDHAWK_API void __cdecl RhWaitForPendingFinalizers(UInt32_BOOL allowReentrantWait)
134145
{
135146
// This must be called via p/invoke rather than RuntimeImport since it blocks and could starve the GC if
@@ -145,7 +156,7 @@ EXTERN_C REDHAWK_API void __cdecl RhWaitForPendingFinalizers(UInt32_BOOL allowRe
145156
g_FinalizerEvent.Set();
146157

147158
#ifdef APP_LOCAL_RUNTIME
148-
// We may have failed to create the finalizer thread at startup.
159+
// We may have failed to create the finalizer thread at startup.
149160
// Try again now.
150161
RhStartFinalizerThread();
151162
#endif

src/Runtime.Base/src/System/Runtime/__Finalizer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ internal static class __Finalizer
2222
[NativeCallable(EntryPoint = "ProcessFinalizers", CallingConvention = CallingConvention.Cdecl)]
2323
public static void ProcessFinalizers()
2424
{
25+
#if INPLACE_RUNTIME
26+
System.Runtime.FinalizerInitRunner.DoInitialize();
27+
#endif
28+
2529
while (true)
2630
{
2731
// Wait until there's some work to be done. If true is returned we should finalize objects,

src/System.Private.CoreLib/src/System/Runtime/InitializeFinalizerThread.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static void DoInitialize()
1515
{
1616
// Make sure that the finalizer thread is CoInitialized before any objects are finalized. If this
1717
// fails, it will throw an exception and that will go unhandled, triggering a FailFast.
18-
Thread.InitializeCom();
18+
Thread.InitializeComForFinalizerThread();
1919
}
2020
}
2121
}

src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ internal static void RhWaitForPendingFinalizers(bool allowReentrantWait)
9797
RhWaitForPendingFinalizers(allowReentrantWait ? 1 : 0);
9898
}
9999

100+
#if !PROJECTN
101+
[DllImport(RuntimeLibrary, ExactSpelling = true)]
102+
internal static extern void RhInitializeFinalizerThread();
103+
#endif
104+
100105
// Get maximum GC generation number.
101106
[MethodImplAttribute(MethodImplOptions.InternalCall)]
102107
[RuntimeImport(RuntimeLibrary, "RhGetMaxGcGeneration")]

src/System.Private.CoreLib/src/System/Threading/Thread.CoreRT.Unix.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,16 @@ private void InitializeComOnNewThread()
137137
{
138138
}
139139

140-
internal static void InitializeCom()
140+
internal static void InitializeComForFinalizerThread()
141141
{
142142
}
143143

144144
public void DisableComObjectEagerCleanup() { }
145-
private static void InitializeExistingThreadPoolThread() { }
145+
146+
private static void InitializeExistingThreadPoolThread()
147+
{
148+
ThreadPool.InitializeForThreadPoolThread();
149+
}
146150

147151
public void Interrupt() => WaitSubsystem.Interrupt(this);
148152
internal static void UninterruptibleSleep0() => WaitSubsystem.UninterruptibleSleep0();

src/System.Private.CoreLib/src/System/Threading/Thread.CoreRT.Windows.cs

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ public sealed partial class Thread
2121
private static ApartmentType t_apartmentType;
2222

2323
[ThreadStatic]
24-
private static bool t_comInitializedByUs;
24+
private static ComState t_comState;
2525

2626
private SafeWaitHandle _osHandle;
2727

2828
private ApartmentState _initialApartmentState = ApartmentState.Unknown;
2929

30+
private volatile static bool s_comInitializedOnFinalizerThread;
31+
3032
private void PlatformSpecificInitialize()
3133
{
3234
}
@@ -269,13 +271,16 @@ public bool TrySetApartmentStateUnchecked(ApartmentState state)
269271
}
270272
}
271273

272-
if (state != ApartmentState.Unknown)
273-
{
274-
InitializeCom(state);
275-
}
276-
else
274+
if ((t_comState & ComState.Locked) == 0)
277275
{
278-
UninitializeCom();
276+
if (state != ApartmentState.Unknown)
277+
{
278+
InitializeCom(state);
279+
}
280+
else
281+
{
282+
UninitializeCom();
283+
}
279284
}
280285

281286
// Clear the cache and check whether new state matches the desired state
@@ -288,9 +293,19 @@ private void InitializeComOnNewThread()
288293
InitializeCom(_initialApartmentState);
289294
}
290295

291-
internal static void InitializeCom(ApartmentState state = ApartmentState.MTA)
296+
internal static void InitializeComForFinalizerThread()
292297
{
293-
if (t_comInitializedByUs)
298+
InitializeCom();
299+
300+
// Prevent re-initialization of COM model on finalizer thread
301+
t_comState |= ComState.Locked;
302+
303+
s_comInitializedOnFinalizerThread = true;
304+
}
305+
306+
private static void InitializeCom(ApartmentState state = ApartmentState.MTA)
307+
{
308+
if ((t_comState & ComState.InitializedByUs) != 0)
294309
return;
295310

296311
#if ENABLE_WINRT
@@ -309,7 +324,7 @@ internal static void InitializeCom(ApartmentState state = ApartmentState.MTA)
309324
if (hr < 0)
310325
throw new OutOfMemoryException();
311326

312-
t_comInitializedByUs = true;
327+
t_comState |= ComState.InitializedByUs;
313328

314329
// If the thread has already been CoInitialized to the proper mode, then
315330
// we don't want to leave an outstanding CoInit so we CoUninit.
@@ -319,23 +334,38 @@ internal static void InitializeCom(ApartmentState state = ApartmentState.MTA)
319334

320335
private static void UninitializeCom()
321336
{
322-
if (!t_comInitializedByUs)
337+
if ((t_comState & ComState.InitializedByUs) == 0)
323338
return;
324339

325340
#if ENABLE_WINRT
326341
Interop.WinRT.RoUninitialize();
327342
#else
328343
Interop.Ole32.CoUninitialize();
329344
#endif
330-
t_comInitializedByUs = false;
345+
346+
t_comState &= ~ComState.InitializedByUs;
331347
}
332348

333349
// TODO: https://github.com/dotnet/corefx/issues/20766
334350
public void DisableComObjectEagerCleanup() { }
335351

336352
private static void InitializeExistingThreadPoolThread()
337353
{
354+
#if PROJECTN
338355
InitializeCom();
356+
#else
357+
// Take advantage of implicit MTA initialized by the finalizer thread
358+
SpinWait sw = new SpinWait();
359+
while (!s_comInitializedOnFinalizerThread)
360+
{
361+
RuntimeImports.RhInitializeFinalizerThread();
362+
sw.SpinOnce(0);
363+
}
364+
#endif
365+
366+
// Prevent re-initialization of COM model on threadpool threads
367+
t_comState |= ComState.Locked;
368+
339369
ThreadPool.InitializeForThreadPoolThread();
340370
}
341371

@@ -432,14 +462,21 @@ internal static ApartmentType GetCurrentApartmentType()
432462
return type;
433463
}
434464

435-
internal enum ApartmentType
465+
internal enum ApartmentType : byte
436466
{
437467
Unknown = 0,
438468
None,
439469
STA,
440470
MTA
441471
}
442472

473+
[Flags]
474+
internal enum ComState : byte
475+
{
476+
InitializedByUs = 1,
477+
Locked = 2,
478+
}
479+
443480
private static int ComputeCurrentProcessorId() => (int)Interop.mincore.GetCurrentProcessorNumber();
444481
}
445482
}

src/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ internal void WaitForRemoval()
330330

331331
public static partial class ThreadPool
332332
{
333+
internal static void InitializeForThreadPoolThread() { }
334+
333335
public static bool SetMaxThreads(int workerThreads, int completionPortThreads)
334336
{
335337
if (workerThreads < 0 || completionPortThreads < 0)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Threading;
6+
7+
namespace System.Runtime
8+
{
9+
internal static class FinalizerInitRunner
10+
{
11+
// Here, we are subscribing to a callback from the runtime. This callback is made from the finalizer
12+
// thread before any objects are finalized.
13+
public static void DoInitialize()
14+
{
15+
}
16+
}
17+
}

src/Test.CoreLib/src/Test.CoreLib.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@
235235
<Compile Include="System\Runtime\CompilerServices\StaticClassConstructionContext.cs" />
236236
<Compile Include="System\Runtime\RuntimeImports.cs" />
237237
<Compile Include="System\Runtime\RuntimeHelpers.cs" />
238+
<Compile Include="System\Runtime\InitializeFinalizerThread.cs" />
238239
<Compile Include="System\Threading\Interlocked.cs" />
239240
<Compile Include="System\Array.cs" />
240241
<Compile Include="System\RuntimeExceptionHelpers.cs" />

0 commit comments

Comments
 (0)