Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
455555e
wip
pavelsavara Jan 26, 2026
4d248aa
GCX_COOP, BEGIN_EXTERNAL_ENTRYPOINT, EX_TRY
pavelsavara Jan 26, 2026
a97615b
Merge branch 'main' into browser_finalizer
pavelsavara Jan 26, 2026
74ded88
fix
pavelsavara Jan 26, 2026
ff1340e
Update src/coreclr/vm/finalizerthread.cpp
pavelsavara Jan 26, 2026
5d1b3fe
Update src/coreclr/vm/finalizerthread.cpp
pavelsavara Jan 26, 2026
8c843c9
Update src/coreclr/vm/ceemain.cpp
pavelsavara Jan 26, 2026
c15760c
Update src/coreclr/vm/finalizerthread.cpp
pavelsavara Jan 26, 2026
9d77348
Update src/coreclr/vm/finalizerthread.cpp
pavelsavara Jan 26, 2026
51d7483
Update src/coreclr/vm/finalizerthread.cpp
pavelsavara Jan 26, 2026
8272ca1
Update src/coreclr/vm/finalizerthread.h
pavelsavara Jan 26, 2026
cdb1fd7
feedback
pavelsavara Jan 26, 2026
c57b7be
feedback
pavelsavara Jan 26, 2026
9abe1be
capture the current (single) thread as the finalizer thread
pavelsavara Jan 26, 2026
943a522
DisablePreemptiveGC() at the end
pavelsavara Jan 26, 2026
045c722
Merge branch 'main' into browser_finalizer
pavelsavara Jan 27, 2026
5b9ce38
feedback
pavelsavara Jan 27, 2026
df7b420
feedback
pavelsavara Jan 27, 2026
0e13fbb
more contracts
pavelsavara Jan 27, 2026
4f0aab4
Update src/coreclr/vm/finalizerthread.cpp
pavelsavara Jan 27, 2026
364d6d2
DoExtraWorkForFinalizer feedback
pavelsavara Jan 27, 2026
3c89e41
Merge branch 'main' into browser_finalizer
pavelsavara Jan 27, 2026
5a73a97
Update src/coreclr/vm/finalizerthread.cpp
pavelsavara Jan 27, 2026
945bad5
feedback
pavelsavara Jan 27, 2026
5904b9d
Merge branch 'main' into browser_finalizer
pavelsavara Jan 28, 2026
901a515
fail faster
pavelsavara Jan 28, 2026
ec59876
fail fast on throttling
pavelsavara Jan 28, 2026
623e43b
TODO 123712
pavelsavara Feb 1, 2026
9e27094
Merge branch 'main' into browser_finalizer
pavelsavara Feb 1, 2026
a1a3840
ActiveIssue https://github.com/dotnet/runtime/issues/123572
pavelsavara Feb 1, 2026
042ec8e
unix only contract
pavelsavara Feb 2, 2026
5354bf4
TARGET_BROWSER feedback
pavelsavara Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/coreclr/vm/ceemain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -921,9 +921,6 @@ void EEStartupHelper()
// On windows the finalizer thread is already partially created and is waiting
// right before doing HasStarted(). We will release it now.
FinalizerThread::EnableFinalization();
#elif defined(TARGET_WASM)
// on wasm we need to run finalizers on main thread as we are single threaded
// active issue: https://github.com/dotnet/runtime/issues/114096
#else
// This isn't done as part of InitializeGarbageCollector() above because
// debugger must be initialized before creating EE thread objects
Expand Down
242 changes: 158 additions & 84 deletions src/coreclr/vm/finalizerthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,43 @@ bool FinalizerThread::IsCurrentThreadFinalizer()
return GetThreadNULLOk() == g_pFinalizerThread;
}

#ifdef TARGET_BROWSER

extern "C" void SystemJS_ScheduleFinalization();

extern "C" void SystemJS_ExecuteFinalizationCallback()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_PREEMPTIVE;
}
CONTRACTL_END;

INSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP;
{
GCX_COOP();
// TODO-WASM https://github.com/dotnet/runtime/issues/123712
// ManagedThreadBase::KickOff(FinalizerThread::FinalizerThreadWorkerIteration, NULL);
}
UNINSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP;
}

#endif // TARGET_BROWSER

void FinalizerThread::EnableFinalization()
{
WRAPPER_NO_CONTRACT;

#ifndef TARGET_WASM
hEventFinalizer->Set();
#else // !TARGET_WASM
#ifdef TARGET_BROWSER
SystemJS_ScheduleFinalization();
#else
// WASI is not implemented yet
#endif // TARGET_BROWSER
#endif // !TARGET_WASM
}

Expand Down Expand Up @@ -377,129 +408,167 @@ void FinalizerThread::WaitForFinalizerEvent (CLREvent *event)
}
}

static BOOL s_FinalizerThreadOK = FALSE;
static BOOL s_InitializedFinalizerThreadForPlatform = FALSE;
static bool s_FinalizerThreadOK = false;
static bool s_InitializedFinalizerThreadForPlatform = false;
static bool s_PriorityBoosted = false;

VOID FinalizerThread::FinalizerThreadWorker(void *args)
{
BOOL bPriorityBoosted = FALSE;
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
}
CONTRACTL_END;

while (!fQuitFinalizer)
{
// Wait for work to do...
FinalizerThread::FinalizerThreadWorkerIteration(nullptr);
}

if (s_InitializedFinalizerThreadForPlatform)
Thread::CleanUpForManagedThreadInNative(GetFinalizerThread());
}

VOID FinalizerThread::FinalizerThreadWorkerIteration(void *args)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
}
CONTRACTL_END;

_ASSERTE(GetFinalizerThread()->PreemptiveGCDisabled());
#ifdef _DEBUG
if (g_pConfig->FastGCStressLevel())
{
GetFinalizerThread()->m_GCOnTransitionsOK = FALSE;
}
if (g_pConfig->FastGCStressLevel())
{
GetFinalizerThread()->m_GCOnTransitionsOK = FALSE;
}
#endif
GetFinalizerThread()->EnablePreemptiveGC();
GetFinalizerThread()->EnablePreemptiveGC();
#ifdef _DEBUG
if (g_pConfig->FastGCStressLevel())
{
GetFinalizerThread()->m_GCOnTransitionsOK = TRUE;
}
if (g_pConfig->FastGCStressLevel())
{
GetFinalizerThread()->m_GCOnTransitionsOK = TRUE;
}
#endif

#ifndef TARGET_WASM
#if 0
// Setting the event here, instead of at the bottom of the loop, could
// cause us to skip draining the Q, if the request is made as soon as
// the app starts running.
SignalFinalizationDone();
// Setting the event here, instead of at the bottom of the loop, could
// cause us to skip draining the Q, if the request is made as soon as
// the app starts running.
SignalFinalizationDone();
#endif //0

WaitForFinalizerEvent (hEventFinalizer);
// Wait for work to do...
WaitForFinalizerEvent (hEventFinalizer);

#endif // !TARGET_WASM

// Process pending finalizer work items from the GC first.
FinalizerWorkItem* pWork = GCHeapUtilities::GetGCHeap()->GetExtraWorkForFinalization();
while (pWork != NULL)
{
FinalizerWorkItem* pNext = pWork->next;
pWork->callback(pWork);
pWork = pNext;
}
// Process pending finalizer work items from the GC first.
FinalizerWorkItem* pWork = GCHeapUtilities::GetGCHeap()->GetExtraWorkForFinalization();
while (pWork != NULL)
{
FinalizerWorkItem* pNext = pWork->next;
pWork->callback(pWork);
pWork = pNext;
}

#ifndef TARGET_WASM
#if defined(__linux__) && defined(FEATURE_EVENT_TRACE)
if (g_TriggerHeapDump && (minipal_lowres_ticks() > (LastHeapDumpTime + LINUX_HEAP_DUMP_TIME_OUT)))
{
s_forcedGCInProgress = true;
GetFinalizerThread()->DisablePreemptiveGC();
GCHeapUtilities::GetGCHeap()->GarbageCollect(2, false, collection_blocking);
GetFinalizerThread()->EnablePreemptiveGC();
s_forcedGCInProgress = false;
if (g_TriggerHeapDump && (minipal_lowres_ticks() > (LastHeapDumpTime + LINUX_HEAP_DUMP_TIME_OUT)))
{
s_forcedGCInProgress = true;
GetFinalizerThread()->DisablePreemptiveGC();
GCHeapUtilities::GetGCHeap()->GarbageCollect(2, false, collection_blocking);
GetFinalizerThread()->EnablePreemptiveGC();
s_forcedGCInProgress = false;

LastHeapDumpTime = minipal_lowres_ticks();
g_TriggerHeapDump = FALSE;
}
LastHeapDumpTime = minipal_lowres_ticks();
g_TriggerHeapDump = FALSE;
}
#endif
if (gcGenAnalysisState == GcGenAnalysisState::Done)
if (gcGenAnalysisState == GcGenAnalysisState::Done)
{
gcGenAnalysisState = GcGenAnalysisState::Disabled;
if (gcGenAnalysisTrace)
{
gcGenAnalysisState = GcGenAnalysisState::Disabled;
if (gcGenAnalysisTrace)
{
#ifdef FEATURE_PERFTRACING
EventPipeAdapter::Disable(gcGenAnalysisEventPipeSessionId);
EventPipeAdapter::Disable(gcGenAnalysisEventPipeSessionId);
#ifdef GEN_ANALYSIS_STRESS
GenAnalysis::EnableGenerationalAwareSession();
GenAnalysis::EnableGenerationalAwareSession();
#endif //GEN_ANALYSIS_STRESS
#endif //FEATURE_PERFTRACING
}

// Writing an empty file to indicate completion
WCHAR outputPath[MAX_PATH];
ReplacePid(GENAWARE_COMPLETION_FILE_NAME, outputPath, MAX_PATH);
FILE* fp = NULL;
if (fopen_lp(&fp, outputPath, W("w+")) == 0)
{
fclose(fp);
}
}

if (!bPriorityBoosted)
{
if (GetFinalizerThread()->SetThreadPriority(THREAD_PRIORITY_HIGHEST))
bPriorityBoosted = TRUE;
}

// The Finalizer thread is started very early in EE startup. We deferred
// some initialization until a point we are sure the EE is up and running. At
// this point we make a single attempt and if it fails won't try again.
if (!s_InitializedFinalizerThreadForPlatform)
// Writing an empty file to indicate completion
WCHAR outputPath[MAX_PATH];
ReplacePid(GENAWARE_COMPLETION_FILE_NAME, outputPath, MAX_PATH);
FILE* fp = NULL;
if (fopen_lp(&fp, outputPath, W("w+")) == 0)
{
s_InitializedFinalizerThreadForPlatform = TRUE;
Thread::InitializationForManagedThreadInNative(GetFinalizerThread());
fclose(fp);
}
}

JitHost::Reclaim();
if (!s_PriorityBoosted)
{
if (GetFinalizerThread()->SetThreadPriority(THREAD_PRIORITY_HIGHEST))
s_PriorityBoosted = true;
}

GetFinalizerThread()->DisablePreemptiveGC();
// The Finalizer thread is started very early in EE startup. We deferred
// some initialization until a point we are sure the EE is up and running. At
// this point we make a single attempt and if it fails won't try again.
if (!s_InitializedFinalizerThreadForPlatform)
{
s_InitializedFinalizerThreadForPlatform = true;
Thread::InitializationForManagedThreadInNative(GetFinalizerThread());
}
#endif // !TARGET_WASM

// we might want to do some extra work on the finalizer thread
// check and do it
if (HaveExtraWorkForFinalizer())
{
DoExtraWorkForFinalizer(GetFinalizerThread());
}
LOG((LF_GC, LL_INFO100, "***** Calling Finalizers\n"));
JitHost::Reclaim();

int observedFullGcCount =
GCHeapUtilities::GetGCHeap()->CollectionCount(GCHeapUtilities::GetGCHeap()->GetMaxGeneration());
FinalizeAllObjects();
GetFinalizerThread()->DisablePreemptiveGC();

// Anyone waiting to drain the Q can now wake up. Note that there is a
// race in that another thread starting a drain, as we leave a drain, may
// consider itself satisfied by the drain that just completed.
// Thus we include the Full GC count that we have certaily observed.
SignalFinalizationDone(observedFullGcCount);
// we might want to do some extra work on the finalizer thread
// check and do it
if (HaveExtraWorkForFinalizer())
{
DoExtraWorkForFinalizer(GetFinalizerThread());
}
LOG((LF_GC, LL_INFO100, "***** Calling Finalizers\n"));

if (s_InitializedFinalizerThreadForPlatform)
Thread::CleanUpForManagedThreadInNative(GetFinalizerThread());
int observedFullGcCount =
GCHeapUtilities::GetGCHeap()->CollectionCount(GCHeapUtilities::GetGCHeap()->GetMaxGeneration());
FinalizeAllObjects();

#ifndef TARGET_WASM
// Anyone waiting to drain the Q can now wake up. Note that there is a
// race in that another thread starting a drain, as we leave a drain, may
// consider itself satisfied by the drain that just completed.
// Thus we include the Full GC count that we have certaily observed.
SignalFinalizationDone(observedFullGcCount);
#endif // !TARGET_WASM
}

DWORD WINAPI FinalizerThread::FinalizerThreadStart(void *args)
{
CONTRACTL
{
#ifdef TARGET_UNIX // because UNINSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP is catching exceptions only on Unix. On Windows OS is handling that.
NOTHROW;
#else // TARGET_UNIX
THROWS;
#endif // TARGET_UNIX
GC_TRIGGERS;
MODE_PREEMPTIVE;
}
CONTRACTL_END;

ClrFlsSetThreadType (ThreadType_Finalizer);

ASSERT(args == 0);
Expand Down Expand Up @@ -580,6 +649,7 @@ void FinalizerThread::FinalizerThreadCreate()
MODE_ANY;
} CONTRACTL_END;

#ifndef TARGET_WASM
#ifndef TARGET_UNIX
MHandles[kLowMemoryNotification] =
CreateMemoryResourceNotification(LowMemoryResourceNotification);
Expand Down Expand Up @@ -617,6 +687,10 @@ void FinalizerThread::FinalizerThreadCreate()
// and the moment we execute the test below.
_ASSERTE(dwRet == 1 || dwRet == 2);
}
#else // !TARGET_WASM
// capture the current (single) thread as the finalizer thread
g_pFinalizerThread = PTR_Thread(GetThread());
#endif // !TARGET_WASM
}

static int g_fullGcCountSeenByFinalization;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/vm/finalizerthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class FinalizerThread
static void SignalFinalizationDone(int observedFullGcCount);

static VOID FinalizerThreadWorker(void *args);

static VOID FinalizerThreadWorkerIteration(void *args);

static DWORD WINAPI FinalizerThreadStart(void *args);

static void FinalizerThreadCreate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ public async Task BrowserHttpHandler_Streaming()
}
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/123572", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsChromium))]
public async Task BrowserHttpHandler_StreamingRequest()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,7 @@ private static bool GetLinqExpressionsBuiltWithIsInterpretingOnly()
// heavily on Reflection.Emit
public static bool IsXmlDsigXsltTransformSupported => !PlatformDetection.IsInAppContainer && IsReflectionEmitSupported;

public static bool IsPreciseGcSupported => !IsMonoRuntime
&& !IsBrowser; // TODO-WASM: https://github.com/dotnet/runtime/issues/114096
public static bool IsPreciseGcSupported => !IsMonoRuntime;

public static bool IsRareEnumsSupported => !IsNativeAot;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public static IEnumerable<object[]> TaskCompletionSourceDoesntCaptureExecutionCo
yield return new object[] { new Func<TaskCompletionSource<int>>(() => new TaskCompletionSource<int>(new object(), TaskCreationOptions.RunContinuationsAsynchronously)) };
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/114096", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))]
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsPreciseGcSupported))]
[MemberData(nameof(TaskCompletionSourceDoesntCaptureExecutionContext_MemberData))]
public static async Task TaskCompletionSourceDoesntCaptureExecutionContext(Func<TaskCompletionSource<int>> tcsFactory)
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Threading/tests/ThreadLocalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ private static void RunThreadLocalTest8Helper(ManualResetEventSlim mres)
Assert.Throws<ObjectDisposedException>(() => values = tl.Values);
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/114096", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPreciseGcSupported))]
public static void RunThreadLocalTest8_Values_NegativeCases()
{
Expand Down
1 change: 1 addition & 0 deletions src/native/libs/Common/JavaScript/types/ems-ambient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & {
_GetDotNetRuntimeContractDescriptor: () => void;
_SystemJS_ExecuteTimerCallback: () => void;
_SystemJS_ExecuteBackgroundJobCallback: () => void;
_SystemJS_ExecuteFinalizationCallback: () => void;
_BrowserHost_CreateHostContract: () => VoidPtr;
_BrowserHost_InitializeCoreCLR: (propertiesCount: number, propertyKeys: CharPtrPtr, propertyValues: CharPtrPtr) => number;
_BrowserHost_ExecuteAssembly: (mainAssemblyNamePtr: number, argsLength: number, argsPtr: number) => number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

let commonDeps = [
"$BROWSER_UTILS",
"SystemJS_ExecuteTimerCallback", "SystemJS_ExecuteBackgroundJobCallback"
"SystemJS_ExecuteTimerCallback",
"SystemJS_ExecuteBackgroundJobCallback",
"SystemJS_ExecuteFinalizationCallback",
];
const lib = {
$DOTNET: {
Expand Down
2 changes: 1 addition & 1 deletion src/native/libs/System.Native.Browser/native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import GitHash from "consts:gitHash";
export { SystemJS_RandomBytes } from "./crypto";
export { SystemJS_GetLocaleInfo } from "./globalization-locale";
export { SystemJS_RejectMainPromise, SystemJS_ResolveMainPromise, SystemJS_ConsoleClear } from "./main";
export { SystemJS_ScheduleTimer, SystemJS_ScheduleBackgroundJob } from "./scheduling";
export { SystemJS_ScheduleTimer, SystemJS_ScheduleBackgroundJob, SystemJS_ScheduleFinalization } from "./scheduling";

export const gitHash = GitHash;
export function dotnetInitializeModule(internals: InternalExchange): void {
Expand Down
Loading
Loading