diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index f039940ec375db..372222dce0ffe7 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -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 diff --git a/src/coreclr/vm/finalizerthread.cpp b/src/coreclr/vm/finalizerthread.cpp index 5fdfe355386b75..38ccdae6713a19 100644 --- a/src/coreclr/vm/finalizerthread.cpp +++ b/src/coreclr/vm/finalizerthread.cpp @@ -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 } @@ -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); @@ -580,6 +649,7 @@ void FinalizerThread::FinalizerThreadCreate() MODE_ANY; } CONTRACTL_END; +#ifndef TARGET_WASM #ifndef TARGET_UNIX MHandles[kLowMemoryNotification] = CreateMemoryResourceNotification(LowMemoryResourceNotification); @@ -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; diff --git a/src/coreclr/vm/finalizerthread.h b/src/coreclr/vm/finalizerthread.h index 4d7f299d37f57c..f3a8a707961720 100644 --- a/src/coreclr/vm/finalizerthread.h +++ b/src/coreclr/vm/finalizerthread.h @@ -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(); diff --git a/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs b/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs index 3a8feb437fefc4..3390ae5c5fa4db 100644 --- a/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs @@ -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() { diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 30c1cd022a9d9c..8bd3953dc82d12 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -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; diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/ExecutionContextFlowTest.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/ExecutionContextFlowTest.cs index bd151c68595a97..58c2e1193ded22 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/ExecutionContextFlowTest.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/ExecutionContextFlowTest.cs @@ -72,6 +72,7 @@ public static IEnumerable TaskCompletionSourceDoesntCaptureExecutionCo yield return new object[] { new Func>(() => new TaskCompletionSource(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> tcsFactory) diff --git a/src/libraries/System.Threading/tests/ThreadLocalTests.cs b/src/libraries/System.Threading/tests/ThreadLocalTests.cs index 1327d768e1babf..4676c8b8a2080b 100644 --- a/src/libraries/System.Threading/tests/ThreadLocalTests.cs +++ b/src/libraries/System.Threading/tests/ThreadLocalTests.cs @@ -315,6 +315,7 @@ private static void RunThreadLocalTest8Helper(ManualResetEventSlim mres) Assert.Throws(() => 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() { diff --git a/src/native/libs/Common/JavaScript/types/ems-ambient.ts b/src/native/libs/Common/JavaScript/types/ems-ambient.ts index e4ff4f2a05a637..b2e2596d20c674 100644 --- a/src/native/libs/Common/JavaScript/types/ems-ambient.ts +++ b/src/native/libs/Common/JavaScript/types/ems-ambient.ts @@ -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; diff --git a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js index a29bb4db959fd7..43fa674788d449 100644 --- a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js +++ b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js @@ -21,7 +21,9 @@ let commonDeps = [ "$BROWSER_UTILS", - "SystemJS_ExecuteTimerCallback", "SystemJS_ExecuteBackgroundJobCallback" + "SystemJS_ExecuteTimerCallback", + "SystemJS_ExecuteBackgroundJobCallback", + "SystemJS_ExecuteFinalizationCallback", ]; const lib = { $DOTNET: { diff --git a/src/native/libs/System.Native.Browser/native/index.ts b/src/native/libs/System.Native.Browser/native/index.ts index fee0bb46e8d6d2..302b6ea425504b 100644 --- a/src/native/libs/System.Native.Browser/native/index.ts +++ b/src/native/libs/System.Native.Browser/native/index.ts @@ -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 { diff --git a/src/native/libs/System.Native.Browser/native/scheduling.ts b/src/native/libs/System.Native.Browser/native/scheduling.ts index bfe56333fef193..f64f9f966e285e 100644 --- a/src/native/libs/System.Native.Browser/native/scheduling.ts +++ b/src/native/libs/System.Native.Browser/native/scheduling.ts @@ -12,8 +12,12 @@ export function SystemJS_ScheduleTimer(shortestDueTimeMs: number): void { _ems_.DOTNET.lastScheduledTimerId = _ems_.safeSetTimeout(SystemJS_ScheduleTimerTick, shortestDueTimeMs); function SystemJS_ScheduleTimerTick(): void { - _ems_.DOTNET.lastScheduledTimerId = undefined; - _ems_._SystemJS_ExecuteTimerCallback(); + try { + _ems_.DOTNET.lastScheduledTimerId = undefined; + _ems_._SystemJS_ExecuteTimerCallback(); + } catch (err) { + _ems_.dotnetApi.exit(1, err); + } } } @@ -26,7 +30,29 @@ export function SystemJS_ScheduleBackgroundJob(): void { _ems_.DOTNET.lastScheduledThreadPoolId = _ems_.safeSetTimeout(SystemJS_ScheduleBackgroundJobTick, 0); function SystemJS_ScheduleBackgroundJobTick(): void { - _ems_.DOTNET.lastScheduledThreadPoolId = undefined; - _ems_._SystemJS_ExecuteBackgroundJobCallback(); + try { + _ems_.DOTNET.lastScheduledThreadPoolId = undefined; + _ems_._SystemJS_ExecuteBackgroundJobCallback(); + } catch (err) { + _ems_.dotnetApi.exit(1, err); + } + } +} + +export function SystemJS_ScheduleFinalization(): void { + if (_ems_.DOTNET.lastScheduledFinalizationId) { + globalThis.clearTimeout(_ems_.DOTNET.lastScheduledFinalizationId); + _ems_.runtimeKeepalivePop(); + _ems_.DOTNET.lastScheduledFinalizationId = undefined; + } + _ems_.DOTNET.lastScheduledFinalizationId = _ems_.safeSetTimeout(SystemJS_ScheduleFinalizationTick, 0); + + function SystemJS_ScheduleFinalizationTick(): void { + try { + _ems_.DOTNET.lastScheduledFinalizationId = undefined; + _ems_._SystemJS_ExecuteFinalizationCallback(); + } catch (err) { + _ems_.dotnetApi.exit(1, err); + } } } diff --git a/src/native/libs/System.Native.Browser/utils/host.ts b/src/native/libs/System.Native.Browser/utils/host.ts index 1a7ab6a19292b1..9d9f9c8806d2b0 100644 --- a/src/native/libs/System.Native.Browser/utils/host.ts +++ b/src/native/libs/System.Native.Browser/utils/host.ts @@ -13,8 +13,13 @@ export function getExitStatus(): new (exitCode: number) => any { } export function runBackgroundTimers(): void { - _ems_._SystemJS_ExecuteTimerCallback(); - _ems_._SystemJS_ExecuteBackgroundJobCallback(); + try { + _ems_._SystemJS_ExecuteTimerCallback(); + _ems_._SystemJS_ExecuteBackgroundJobCallback(); + _ems_._SystemJS_ExecuteFinalizationCallback(); + } catch (err) { + _ems_.dotnetApi.exit(1, err); + } } export function abortBackgroundTimers(): void { @@ -28,6 +33,11 @@ export function abortBackgroundTimers(): void { _ems_.runtimeKeepalivePop(); _ems_.DOTNET.lastScheduledThreadPoolId = undefined; } + if (_ems_.DOTNET.lastScheduledFinalizationId) { + globalThis.clearTimeout(_ems_.DOTNET.lastScheduledFinalizationId); + _ems_.runtimeKeepalivePop(); + _ems_.DOTNET.lastScheduledFinalizationId = undefined; + } } export function abortPosix(exitCode: number): void {