Skip to content

Commit 9d1b163

Browse files
jkotasCopilotAaronRobinsonMSFT
authored
[NativeAOT] Fix resetting apartment state (#119938)
* Fixes #119925 This was latent bug in NativeAOT that got exposed by initializing COM eagerly on finalizer thread (#113194). * Add test for resetting apartment state * Copy over compat quirks from CoreCLR --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Aaron Robinson <[email protected]>
1 parent 6b42053 commit 9d1b163

File tree

2 files changed

+58
-35
lines changed

2 files changed

+58
-35
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ namespace System.Threading
1414
{
1515
public sealed partial class Thread
1616
{
17-
[ThreadStatic]
18-
private static ApartmentType t_apartmentType;
19-
2017
[ThreadStatic]
2118
private static ComState t_comState;
2219

@@ -334,14 +331,15 @@ public ApartmentState GetApartmentState()
334331
return _initialApartmentState;
335332
}
336333

337-
switch (GetCurrentApartmentType())
334+
switch (GetCurrentApartmentState())
338335
{
339-
case ApartmentType.STA:
336+
case ApartmentState.STA:
340337
return ApartmentState.STA;
341-
case ApartmentType.MTA:
338+
case ApartmentState.MTA:
342339
return ApartmentState.MTA;
343340
default:
344-
return ApartmentState.Unknown;
341+
// If COM is uninitialized on the current thread, it is assumed to be implicit MTA.
342+
return ApartmentState.MTA;
345343
}
346344
}
347345

@@ -374,14 +372,29 @@ private bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError)
374372
}
375373
else
376374
{
375+
// Compat: Setting ApartmentState to Unknown uninitializes COM
377376
UninitializeCom();
378377
}
379-
}
380378

381-
// Clear the cache and check whether new state matches the desired state
382-
t_apartmentType = ApartmentType.Unknown;
379+
// Clear the cache and check whether new state matches the desired state
380+
t_comState &= ~(ComState.STA | ComState.MTA);
383381

384-
retState = GetApartmentState();
382+
retState = GetCurrentApartmentState();
383+
}
384+
else
385+
{
386+
Debug.Assert((t_comState & ComState.MTA) != 0);
387+
retState = ApartmentState.MTA;
388+
}
389+
}
390+
391+
// Special case where we pass in Unknown and get back MTA.
392+
// Once we CoUninitialize the thread, the OS will still
393+
// report the thread as implicitly in the MTA if any
394+
// other thread in the process is CoInitialized.
395+
if ((state == ApartmentState.Unknown) && (retState == ApartmentState.MTA))
396+
{
397+
return true;
385398
}
386399

387400
if (retState != state)
@@ -415,7 +428,7 @@ private static void InitializeComForThreadPoolThread()
415428
// Process-wide COM is initialized very early before any managed code can run.
416429
// Assume it is done.
417430
// Prevent re-initialization of COM model on threadpool threads from the default one.
418-
t_comState |= ComState.Locked;
431+
t_comState |= ComState.Locked | ComState.MTA;
419432
}
420433

421434
private static void InitializeCom(ApartmentState state = ApartmentState.MTA)
@@ -527,49 +540,53 @@ internal static void CheckForPendingInterrupt()
527540
}
528541

529542
internal static bool ReentrantWaitsEnabled =>
530-
GetCurrentApartmentType() == ApartmentType.STA;
543+
GetCurrentApartmentState() == ApartmentState.STA;
531544

532-
internal static ApartmentType GetCurrentApartmentType()
545+
// Unlike the public API, this returns ApartmentState.Unknown when COM is uninitialized on the current thread
546+
internal static ApartmentState GetCurrentApartmentState()
533547
{
534-
ApartmentType currentThreadType = t_apartmentType;
535-
if (currentThreadType != ApartmentType.Unknown)
536-
return currentThreadType;
548+
if ((t_comState & (ComState.MTA | ComState.STA)) != 0)
549+
return ((t_comState & ComState.STA) != 0) ? ApartmentState.STA : ApartmentState.MTA;
537550

538551
Interop.APTTYPE aptType;
539552
Interop.APTTYPEQUALIFIER aptTypeQualifier;
540553
int result = Interop.Ole32.CoGetApartmentType(out aptType, out aptTypeQualifier);
541554

542-
ApartmentType type = ApartmentType.Unknown;
555+
ApartmentState state = ApartmentState.Unknown;
543556

544557
switch (result)
545558
{
546559
case HResults.CO_E_NOTINITIALIZED:
547-
type = ApartmentType.None;
560+
Debug.Fail("COM is not initialized");
561+
state = ApartmentState.Unknown;
548562
break;
549563

550564
case HResults.S_OK:
551565
switch (aptType)
552566
{
553567
case Interop.APTTYPE.APTTYPE_STA:
554568
case Interop.APTTYPE.APTTYPE_MAINSTA:
555-
type = ApartmentType.STA;
569+
state = ApartmentState.STA;
556570
break;
557571

558572
case Interop.APTTYPE.APTTYPE_MTA:
559-
type = ApartmentType.MTA;
573+
state = ApartmentState.MTA;
560574
break;
561575

562576
case Interop.APTTYPE.APTTYPE_NA:
563577
switch (aptTypeQualifier)
564578
{
565579
case Interop.APTTYPEQUALIFIER.APTTYPEQUALIFIER_NA_ON_MTA:
580+
state = ApartmentState.MTA;
581+
break;
582+
566583
case Interop.APTTYPEQUALIFIER.APTTYPEQUALIFIER_NA_ON_IMPLICIT_MTA:
567-
type = ApartmentType.MTA;
584+
state = ApartmentState.Unknown;
568585
break;
569586

570587
case Interop.APTTYPEQUALIFIER.APTTYPEQUALIFIER_NA_ON_STA:
571588
case Interop.APTTYPEQUALIFIER.APTTYPEQUALIFIER_NA_ON_MAINSTA:
572-
type = ApartmentType.STA;
589+
state = ApartmentState.STA;
573590
break;
574591

575592
default:
@@ -585,24 +602,18 @@ internal static ApartmentType GetCurrentApartmentType()
585602
break;
586603
}
587604

588-
if (type != ApartmentType.Unknown)
589-
t_apartmentType = type;
590-
return type;
591-
}
592-
593-
internal enum ApartmentType : byte
594-
{
595-
Unknown = 0,
596-
None,
597-
STA,
598-
MTA
605+
if (state != ApartmentState.Unknown)
606+
t_comState |= (state == ApartmentState.STA) ? ComState.STA : ComState.MTA;
607+
return state;
599608
}
600609

601610
[Flags]
602611
internal enum ComState : byte
603612
{
604613
InitializedByUs = 1,
605614
Locked = 2,
615+
MTA = 4,
616+
STA = 8
606617
}
607618
}
608619
}

src/libraries/System.Threading.Thread/tests/ThreadTests.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,20 @@ public static void GetSetApartmentStateTest_ChangeAfterThreadStarted_Windows(
247247
Assert.Equal(ApartmentState.MTA, getApartmentState(t));
248248
Assert.Equal(0, setApartmentState(t, ApartmentState.MTA));
249249
Assert.Equal(ApartmentState.MTA, getApartmentState(t));
250-
Assert.Equal(setType == 0 ? 0 : 2, setApartmentState(t, ApartmentState.STA)); // cannot be changed after thread is started
250+
Assert.Equal(setType == 0 ? 0 : 2, setApartmentState(t, ApartmentState.STA)); // MTA<->STA cannot be changed directly after thread is started
251251
Assert.Equal(ApartmentState.MTA, getApartmentState(t));
252+
253+
if (!PlatformDetection.IsWindowsNanoServer)
254+
{
255+
Assert.Equal(0, setApartmentState(t, ApartmentState.Unknown)); // Compat quirk: MTA<->STA can be changed by going through Unknown
256+
Assert.Equal(ApartmentState.MTA, getApartmentState(t));
257+
Assert.Equal(0, setApartmentState(t, ApartmentState.STA));
258+
Assert.Equal(ApartmentState.STA, getApartmentState(t));
259+
Assert.Equal(0, setApartmentState(t, ApartmentState.Unknown));
260+
Assert.Equal(ApartmentState.MTA, getApartmentState(t));
261+
Assert.Equal(0, setApartmentState(t, ApartmentState.MTA));
262+
Assert.Equal(ApartmentState.MTA, getApartmentState(t));
263+
}
252264
});
253265
}
254266

0 commit comments

Comments
 (0)