diff --git a/eng/native.wasm.targets b/eng/native.wasm.targets index 734349ba2f1d34..f8593ec4c87e1d 100644 --- a/eng/native.wasm.targets +++ b/eng/native.wasm.targets @@ -82,13 +82,13 @@ - + + + diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt index 7a713a828f5c01..768d8b535f0e36 100644 --- a/src/coreclr/CMakeLists.txt +++ b/src/coreclr/CMakeLists.txt @@ -39,10 +39,6 @@ if(CLR_CMAKE_TARGET_WASI) add_compile_definitions(_WASI_EMULATED_PROCESS_CLOCKS) add_compile_definitions(_WASI_EMULATED_MMAN) add_compile_definitions(_WASI_EMULATED_GETPID) - # EventPipe / diag-server PAL require sockets we have not validated - # for the WASI bring-up; revisit once corerun.wasm runs. - set(FEATURE_EVENT_TRACE 0) - set(FEATURE_PERFTRACING 0) include_directories(BEFORE SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/pal/src/include/pal/wasi) # Cross-compile try_run() answers — wasm32 binaries cannot execute on # the build host, so pre-seed the cache. diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props index 440379ea5e1317..49429013d2ddea 100644 --- a/src/coreclr/clr.featuredefines.props +++ b/src/coreclr/clr.featuredefines.props @@ -32,18 +32,6 @@ false - - - false - - true true diff --git a/src/coreclr/debug/debug-pal/CMakeLists.txt b/src/coreclr/debug/debug-pal/CMakeLists.txt index 59b18e2c91ddb8..d7a233790e7316 100644 --- a/src/coreclr/debug/debug-pal/CMakeLists.txt +++ b/src/coreclr/debug/debug-pal/CMakeLists.txt @@ -27,12 +27,7 @@ if(CLR_CMAKE_HOST_WIN32) endif(CLR_CMAKE_HOST_WIN32) if(CLR_CMAKE_HOST_UNIX) - if (NOT CLR_CMAKE_TARGET_WASI) - # ds-ipc-pal-socket.c needs UNIX/TCP sockets; re-enable once - # WASI PERFTRACING is wired. Tracked by https://github.com/dotnet/runtime/issues/130383 - - set(DEBUG_PAL_REFEREENCE_DIAGNOSTICSERVER ON) - endif() + set(DEBUG_PAL_REFEREENCE_DIAGNOSTICSERVER ON) add_definitions(-DPAL_IMPLEMENTATION) add_definitions(-D_POSIX_C_SOURCE=200809L) diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.cpp b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.cpp index 17859b17988bd4..aaa62cfe2b3995 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.cpp +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.cpp @@ -197,4 +197,63 @@ ep_rt_coreclr_session_stopping (void) #endif // FEATURE_PGO && PERFTRACING_DISABLE_THREADS } +#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS) +// WASI has no host event loop to schedule EventPipe jobs (session streaming, diagnostic server) on, +// so they are kept on this list and run by the managed WasiEventLoop (WasiEventPipeJobs.cs). +// A job returns non-zero when it is done; otherwise it stays queued and runs again on the next pump. +// Single-threaded by construction, so no locking. +struct WasiEventPipeJob +{ + size_t (*Callback)(void *data); + void *Data; + WasiEventPipeJob *Next; +}; + +static WasiEventPipeJob *s_wasiJobsHead = nullptr; +static WasiEventPipeJob *s_wasiJobsTail = nullptr; + +static void WasiAppendJob(WasiEventPipeJob *job) +{ + job->Next = nullptr; + if (s_wasiJobsTail != nullptr) + s_wasiJobsTail->Next = job; + else + s_wasiJobsHead = job; + s_wasiJobsTail = job; +} + +bool ep_rt_coreclr_wasi_queue_job (size_t (*cb)(void *data), void *data) +{ + _ASSERTE (cb != nullptr); + WasiEventPipeJob *job = new (nothrow) WasiEventPipeJob(); + if (job == nullptr) + return false; + + job->Callback = cb; + job->Data = data; + WasiAppendJob (job); + return true; +} + +bool ep_rt_coreclr_wasi_run_jobs (void) +{ + // Detach the current list so jobs queued while running are picked up on the next pump. + WasiEventPipeJob *job = s_wasiJobsHead; + s_wasiJobsHead = nullptr; + s_wasiJobsTail = nullptr; + + while (job != nullptr) + { + WasiEventPipeJob *next = job->Next; + if (job->Callback (job->Data) != 0) + delete job; + else + WasiAppendJob (job); + job = next; + } + + return s_wasiJobsHead != nullptr; +} +#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS + #endif /* ENABLE_PERFTRACING */ diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index c557950df39b68..1ba3a2134d023a 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -314,12 +314,26 @@ ep_rt_init_finish (void) STATIC_CONTRACT_NOTHROW; } +#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS) +// WASI has no host event loop, so jobs are kept on a native list that the managed +// WASI event loop drains (see ep-rt-coreclr.cpp). +bool ep_rt_coreclr_wasi_queue_job (size_t (*cb)(void *data), void *data); + +// Runs the queued jobs once; returns true if any job is still queued. +bool ep_rt_coreclr_wasi_run_jobs (void); +#endif + static inline void ep_rt_shutdown (void) { STATIC_CONTRACT_NOTHROW; +#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS) + // The event loop no longer runs, so run the queued jobs once to let the now-disabled streaming + // sessions release their references; freeing a session ends its trace file. + ep_rt_coreclr_wasi_run_jobs (); +#endif } static @@ -1111,6 +1125,8 @@ ep_rt_queue_job ( if (!cb (params)) SystemJS_DiagnosticServerQueueJob (cb, params); return true; +#elif defined(TARGET_WASI) + return ep_rt_coreclr_wasi_queue_job ((size_t (*)(void *))job_func, params); #else EP_UNREACHABLE ("Not implemented on this platform"); return false; diff --git a/src/coreclr/vm/eventpipeadapter.h b/src/coreclr/vm/eventpipeadapter.h index 7be8fe82827d05..b3bbd4a0cb1569 100644 --- a/src/coreclr/vm/eventpipeadapter.h +++ b/src/coreclr/vm/eventpipeadapter.h @@ -285,6 +285,15 @@ class EventPipeAdapter final return !ep_rt_wait_event_wait (ep_session_get_wait_event (session), (uint32_t)timeoutMs, false) ? true : false; } +#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS) + // Runs the queued EventPipe jobs; returns true if any remain queued. + static inline bool RunWasiJobs() + { + STATIC_CONTRACT_NOTHROW; + return ep_rt_coreclr_wasi_run_jobs (); + } +#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS + static inline FILETIME GetSessionStartTime(EventPipeSession *session) { STATIC_CONTRACT_NOTHROW; diff --git a/src/coreclr/vm/eventpipeinternal.cpp b/src/coreclr/vm/eventpipeinternal.cpp index 5a2dc9933c800c..cdb0e2ecd35bc8 100644 --- a/src/coreclr/vm/eventpipeinternal.cpp +++ b/src/coreclr/vm/eventpipeinternal.cpp @@ -296,4 +296,21 @@ extern "C" BOOL QCALLTYPE EventPipeInternal_WaitForSessionSignal(UINT64 sessionI return result; } +#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS) +// Runs the queued EventPipe jobs and returns whether any are still queued, so the caller knows to run again. +extern "C" CLR_BOOL QCALLTYPE EventPipeInternal_WasiRunJobs(QCallExceptionStatus* qcallError) +{ + QCALL_CONTRACT; + + bool pending = false; + + BEGIN_QCALL; + + pending = EventPipeAdapter::RunWasiJobs(); + + END_QCALL; + return pending ? TRUE : FALSE; +} +#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS + #endif // FEATURE_PERFTRACING diff --git a/src/coreclr/vm/eventpipeinternal.h b/src/coreclr/vm/eventpipeinternal.h index 09cb9acedaca28..2854de3e2013ff 100644 --- a/src/coreclr/vm/eventpipeinternal.h +++ b/src/coreclr/vm/eventpipeinternal.h @@ -104,6 +104,10 @@ extern "C" BOOL QCALLTYPE EventPipeInternal_WaitForSessionSignal( INT32 timeoutMs, QCallExceptionStatus* qcallError); +#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS) +extern "C" CLR_BOOL QCALLTYPE EventPipeInternal_WasiRunJobs(QCallExceptionStatus* qcallError); +#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS + #endif // FEATURE_PERFTRACING #endif // __EVENTPIPEINTERNAL_H__ diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index b35b8e3ffd8053..757daa2d98ddd5 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -492,6 +492,9 @@ static const Entry s_QCall[] = DllImportEntry(EventPipeInternal_GetNextEvent) DllImportEntry(EventPipeInternal_SignalSession) DllImportEntry(EventPipeInternal_WaitForSessionSignal) +#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS) + DllImportEntry(EventPipeInternal_WasiRunJobs) +#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS #endif // FEATURE_PERFTRACING #if defined(TARGET_UNIX) DllImportEntry(FreeEnvironmentStringsW) diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp index c90fd710a16802..e08d4b23c9fdd8 100644 --- a/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp +++ b/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp @@ -316,6 +316,12 @@ namespace *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2)); } + static void CallFunc_I32_I32_I64_I32_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7)); + } + static void CallFunc_I32_I32_I64_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) { int32_t (*fptr)(int32_t, int32_t, int64_t, int64_t, int32_t) = (int32_t (*)(int32_t, int32_t, int64_t, int64_t, int32_t))pcode; @@ -372,6 +378,18 @@ namespace *((int32_t*)pRet) = (*fptr)(ARG_I64(0)); } + static void CallFunc_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int64_t, int32_t) = (int32_t (*)(int64_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_I64(0), ARG_I32(1)); + } + + static void CallFunc_I64_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int64_t, int32_t, int32_t) = (int32_t (*)(int64_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_I64(0), ARG_I32(1), ARG_I32(2)); + } + static void CallFunc_I64_I32_I64_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) { int32_t (*fptr)(int64_t, int32_t, int64_t, int32_t, int32_t) = (int32_t (*)(int64_t, int32_t, int64_t, int32_t, int32_t))pcode; @@ -397,12 +415,24 @@ namespace *((int64_t*)pRet) = (*fptr)(ARG_I32(0)); } + static void CallFunc_I32_I32_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int64_t (*fptr)(int32_t, int32_t) = (int64_t (*)(int32_t, int32_t))pcode; + *((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1)); + } + static void CallFunc_I32_I32_I32_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet) { int64_t (*fptr)(int32_t, int32_t, int32_t) = (int64_t (*)(int32_t, int32_t, int32_t))pcode; *((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2)); } + static void CallFunc_I32_I32_I32_I32_I32_I32_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int64_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (int64_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5)); + } + static void CallFunc_I32_I32_I32_I64_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet) { int64_t (*fptr)(int32_t, int32_t, int32_t, int64_t) = (int64_t (*)(int32_t, int32_t, int32_t, int64_t))pcode; @@ -516,6 +546,24 @@ namespace (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7), ARG_I32(8), ARG_I32(9), ARG_I32(10)); } + static void CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + void (*fptr)(double, double, double, double, double, double, double, double, double, int32_t, int32_t, int32_t) = (void (*)(double, double, double, double, double, double, double, double, double, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_F64(0), ARG_F64(1), ARG_F64(2), ARG_F64(3), ARG_F64(4), ARG_F64(5), ARG_F64(6), ARG_F64(7), ARG_F64(8), ARG_I32(9), ARG_I32(10), ARG_I32(11)); + } + + static void CallFunc_F64_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + void (*fptr)(double, int32_t, int32_t) = (void (*)(double, int32_t, int32_t))pcode; + (*fptr)(ARG_F64(0), ARG_I32(1), ARG_I32(2)); + } + + static void CallFunc_F64_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + void (*fptr)(double, int32_t, int32_t, int32_t, int32_t) = (void (*)(double, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_F64(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); + } + NOINLINE static void CallFunc_F64_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; @@ -560,6 +608,12 @@ namespace (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_IND(3), ARG_I32(4), ARG_I32(5), ARG_I32(6)); } + static void CallFunc_I32_I32_F64_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + void (*fptr)(int32_t, int32_t, double, int32_t) = (void (*)(int32_t, int32_t, double, int32_t))pcode; + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_F64(2), ARG_I32(3)); + } + static void CallFunc_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode; @@ -602,6 +656,12 @@ namespace (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6)); } + static void CallFunc_I32_I32_I32_I32_I64_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int64_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int64_t, int32_t))pcode; + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I64(4), ARG_I32(5)); + } + NOINLINE static void CallFunc_I32_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; @@ -691,6 +751,7 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = { { "Miiiil", (void*)&CallFunc_I32_I32_I32_I64_RetI32 }, { "Miiiip", (void*)&CallFunc_I32_I32_I32_RetI32_PE }, { "Miiil", (void*)&CallFunc_I32_I32_I64_RetI32 }, + { "Miiiliiiii", (void*)&CallFunc_I32_I32_I64_I32_I32_I32_I32_I32_RetI32 }, { "Miiilli", (void*)&CallFunc_I32_I32_I64_I64_I32_RetI32 }, { "Miiip", (void*)&CallFunc_I32_I32_RetI32_PE }, { "Miil", (void*)&CallFunc_I32_I64_RetI32 }, @@ -700,11 +761,15 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = { { "Miilli", (void*)&CallFunc_I32_I64_I64_I32_RetI32 }, { "Miip", (void*)&CallFunc_I32_RetI32_PE }, { "Mil", (void*)&CallFunc_I64_RetI32 }, + { "Mili", (void*)&CallFunc_I64_I32_RetI32 }, + { "Milii", (void*)&CallFunc_I64_I32_I32_RetI32 }, { "Mililii", (void*)&CallFunc_I64_I32_I64_I32_I32_RetI32 }, { "Mip", (void*)&CallFunc_Void_RetI32_PE }, { "Ml", (void*)&CallFunc_Void_RetI64 }, { "Mli", (void*)&CallFunc_I32_RetI64 }, + { "Mlii", (void*)&CallFunc_I32_I32_RetI64 }, { "Mliii", (void*)&CallFunc_I32_I32_I32_RetI64 }, + { "Mliiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetI64 }, { "Mliiil", (void*)&CallFunc_I32_I32_I32_I64_RetI64 }, { "Mlili", (void*)&CallFunc_I32_I64_I32_RetI64 }, { "Mlillp", (void*)&CallFunc_I32_I64_I64_RetI64_PE }, @@ -723,6 +788,9 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = { { "MvS8iiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_RetVoid }, { "MvS8iiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_RetVoid }, { "MvS8iiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid }, + { "Mvdddddddddiii", (void*)&CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_I32_RetVoid }, + { "Mvdii", (void*)&CallFunc_F64_I32_I32_RetVoid }, + { "Mvdiiii", (void*)&CallFunc_F64_I32_I32_I32_I32_RetVoid }, { "Mvdiip", (void*)&CallFunc_F64_I32_I32_RetVoid_PE }, { "Mvfiip", (void*)&CallFunc_F32_I32_I32_RetVoid_PE }, { "Mvi", (void*)&CallFunc_I32_RetVoid }, @@ -730,6 +798,7 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = { { "Mvii", (void*)&CallFunc_I32_I32_RetVoid }, { "MviiS8S8ii", (void*)&CallFunc_I32_I32_S8_S8_I32_I32_RetVoid }, { "MviiS8S8iii", (void*)&CallFunc_I32_I32_S8_S8_I32_I32_I32_RetVoid }, + { "Mviidi", (void*)&CallFunc_I32_I32_F64_I32_RetVoid }, { "Mviii", (void*)&CallFunc_I32_I32_I32_RetVoid }, { "MviiiS8S8i", (void*)&CallFunc_I32_I32_I32_S8_S8_I32_RetVoid }, { "MviiiS8S8ii", (void*)&CallFunc_I32_I32_I32_S8_S8_I32_I32_RetVoid }, @@ -737,6 +806,7 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = { { "Mviiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetVoid }, { "Mviiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid }, { "Mviiiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_I32_RetVoid }, + { "Mviiiili", (void*)&CallFunc_I32_I32_I32_I32_I64_I32_RetVoid }, { "Mviiip", (void*)&CallFunc_I32_I32_I32_RetVoid_PE }, { "Mviip", (void*)&CallFunc_I32_I32_RetVoid_PE }, { "Mvili", (void*)&CallFunc_I32_I64_I32_RetVoid }, diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp index 4d94590f2f4073..39745a7a611c53 100644 --- a/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp +++ b/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp @@ -92,6 +92,19 @@ static void Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeH ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid); } +static MethodDesc* MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid = nullptr; +static void Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid(void * arg0, int32_t arg1, uint32_t arg2, int64_t arg3, int64_t arg4, void * arg5, void * arg6) +{ + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid) + { + LookupUnmanagedCallersOnlyMethodByName("System.Diagnostics.Tracing.EventPipeEventProvider, System.Private.CoreLib", "Callback", &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid); + } + + int64_t args[7] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2, (int64_t)arg3, (int64_t)arg4, (int64_t)arg5, (int64_t)arg6 }; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid); +} + static MethodDesc* MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid = nullptr; static void Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, void * arg2, void * arg3) { @@ -1010,6 +1023,7 @@ const ReverseThunkMapEntry g_ReverseThunks[] = { 3962535319, "CallEntryPoint#5:System.Private.CoreLib:System:Environment", { &MD_System_Private_CoreLib_System_Environment_CallEntryPoint_I32_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Environment_CallEntryPoint_I32_I32_I32_I32_I32_RetVoid } }, { 1821934012, "CallStartupHook#2:System.Private.CoreLib:System:StartupHookProvider", { &MD_System_Private_CoreLib_System_StartupHookProvider_CallStartupHook_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StartupHookProvider_CallStartupHook_I32_I32_RetVoid } }, { 2915047114, "CallToString#3:System.Private.CoreLib:System.Runtime.CompilerServices:RuntimeHelpers", { &MD_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid } }, + { 4077371982, "Callback#7:System.Private.CoreLib:System.Diagnostics.Tracing:EventPipeEventProvider", { &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid } }, { 3358042195, "ClearManaged#4:System.Private.CoreLib:System.StubHelpers:MngdRefCustomMarshaler", { &MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid } }, { 2311968855, "ClearNative#4:System.Private.CoreLib:System.StubHelpers:MngdRefCustomMarshaler", { &MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearNative_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearNative_I32_I32_I32_I32_RetVoid } }, { 3378852959, "ConfigCallback#5:System.Private.CoreLib:System:GC", { &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid } }, diff --git a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs index cb7390b021c58b..d467ed3a7e5180 100644 --- a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs +++ b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs @@ -53,7 +53,6 @@ public void IsSupported() [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/129088", typeof(PlatformDetection), nameof(PlatformDetection.IsAndroid), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134963", TestPlatforms.Wasi)] public void StartStopCreatesActivity() { using ActivityEventListener l = new ActivityEventListener(); @@ -70,7 +69,6 @@ public void StartStopCreatesActivity() [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/129088", typeof(PlatformDetection), nameof(PlatformDetection.IsAndroid), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134963", TestPlatforms.Wasi)] public async Task ActivityFlowsAsync() { using ActivityEventListener l = new ActivityEventListener(); @@ -120,7 +118,6 @@ private async Task YieldTwoActivitiesDeep(ActivityEventSource es) // Compare with SetCurrentActivityIdAfterEventDoesNotFlowAsync below. [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/129088", typeof(PlatformDetection), nameof(PlatformDetection.IsAndroid), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134963", TestPlatforms.Wasi)] public async Task SetCurrentActivityIdBeforeEventFlowsAsync() { using ActivityEventListener l = new ActivityEventListener(); @@ -147,7 +144,6 @@ public async Task SetCurrentActivityIdBeforeEventFlowsAsync() // Compare with SetCurrentActivityIdBeforeEventFlowsAsync above. [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/129088", typeof(PlatformDetection), nameof(PlatformDetection.IsAndroid), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134963", TestPlatforms.Wasi)] public async Task SetCurrentActivityIdAfterEventDoesNotFlowAsync() { using ActivityEventListener l = new ActivityEventListener(); diff --git a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsEventSourceLifetime.cs b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsEventSourceLifetime.cs index 9e86e94b363c26..b4cd37b52a09d4 100644 --- a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsEventSourceLifetime.cs +++ b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsEventSourceLifetime.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.Tracing; using System.Reflection; +using System.Threading.Tasks; using Xunit; namespace BasicEventSourceTests @@ -35,6 +36,65 @@ public void Test_EventSource_Lifetime() TestUtilities.CheckNoEventSourcesRunning("Stop"); } + /// + /// Validates that disposing an EventListener subscribed to the runtime provider releases its native + /// EventPipe session. EventPipe has a fixed number of sessions, so leaking one per listener would + /// eventually stop new subscriptions from receiving events. + /// + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime))] + public async Task RuntimeProviderListener_RepeatedSubscriptions_DoNotLeakSessions() + { + const int Iterations = 80; // more than the 64 native session slots + const EventKeywords GCKeyword = (EventKeywords)0x1; + + for (int i = 0; i < Iterations; i++) + { + using var listener = new RuntimeProviderListener(GCKeyword); + if (i % 2 == 0) + { + await Task.Delay(1); + } + } + + await Task.Delay(500); + + using var finalListener = new RuntimeProviderListener(GCKeyword); + for (int attempt = 0; attempt < 50 && !finalListener.ReceivedEvent; attempt++) + { + GC.Collect(); + await Task.Delay(100); + } + + Assert.True(finalListener.ReceivedEvent, "A new runtime-provider subscription received no events."); + } + + private sealed class RuntimeProviderListener : EventListener + { + private readonly EventKeywords _keywords; + + public volatile bool ReceivedEvent; + + public RuntimeProviderListener(EventKeywords keywords) + { + _keywords = keywords; + // Sources created before the listener are delivered from the base constructor, before _keywords is set. + foreach (EventSource source in EventSource.GetSources()) + { + OnEventSourceCreated(source); + } + } + + protected override void OnEventSourceCreated(EventSource source) + { + if (source.Name == "Microsoft-Windows-DotNETRuntime" && _keywords != 0) + { + EnableEvents(source, EventLevel.Informational, _keywords); + } + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) => ReceivedEvent = true; + } + private void ExerciseEventSource(WeakReference wrProvider, WeakReference wrEventSource) { using (var es = new LifetimeTestEventSource()) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 30dd3fad791e65..2a77d3f4771ef6 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -3005,6 +3005,9 @@ + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Wasm.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Wasm.cs index b9d9acb6db8b51..2222fd983f2784 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Wasm.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Wasm.cs @@ -17,16 +17,19 @@ private void StartDispatchTask(ulong sessionID, DateTime syncTimeUtc, long syncT Debug.Assert(Monitor.IsEntered(m_dispatchControlLock)); Debug.Assert(sessionID != 0); - m_dispatchTaskCancellationSource = new CancellationTokenSource(); + CancellationTokenSource cancellationSource = new CancellationTokenSource(); + m_dispatchTaskCancellationSource = cancellationSource; + CancellationToken token = cancellationSource.Token; Task? previousDispatchTask = m_dispatchTask; if (previousDispatchTask != null) { - m_dispatchTask = previousDispatchTask.ContinueWith(_ => DispatchEventsToEventListeners(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, m_dispatchTaskCancellationSource.Token), - m_dispatchTaskCancellationSource.Token, TaskContinuationOptions.None, TaskScheduler.Default); + // The continuation must always run, even if already cancelled, because it owns disabling the session. + m_dispatchTask = previousDispatchTask.ContinueWith(_ => DispatchEventsToEventListeners(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, token), + CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default).Unwrap(); } else { - m_dispatchTask = DispatchEventsToEventListeners(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, m_dispatchTaskCancellationSource.Token); + m_dispatchTask = DispatchEventsToEventListeners(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, token); } } @@ -34,17 +37,25 @@ private async Task DispatchEventsToEventListeners(ulong sessionID, DateTime sync { Debug.Assert(sessionID != 0); - while (!token.IsCancellationRequested) + try { - DispatchEventsToEventListenersOnce(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, token); - await Task.Delay(DispatcherDelayMs, token).ConfigureAwait(false); + while (!token.IsCancellationRequested) + { + DispatchEventsToEventListenersOnce(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, token); + await Task.Delay(DispatcherDelayMs, token).ConfigureAwait(false); + } } + catch (OperationCanceledException) when (token.IsCancellationRequested) + { + } + finally + { + // Wait for SignalSession() to be called before we call disable, otherwise + // the SignalSession() call could be on a disabled session. + await Task.Yield(); - // Wait for SignalSession() to be called before we call disable, otherwise - // the SignalSession() call could be on a disabled session. - await Task.Yield(); - - EventPipeInternal.Disable(sessionID); + EventPipeInternal.Disable(sessionID); + } } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs index 0dba1ec0041502..91620ed239b385 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs @@ -56,6 +56,10 @@ internal static T PollWasiEventLoopUntilResolved(Task mainTask) s_mainTask = mainTask; while (!mainTask.IsCompleted) { +#if FEATURE_PERFTRACING && CORECLR && !FEATURE_MULTITHREADING + // Before Dispatch, which can block in wasi:io/poll, so the pump's timer is registered first. + WasiEventPipeJobs.Pump(); +#endif ThreadPoolWorkQueue.Dispatch(); WasiFinalizerScheduler.DrainIfPending(); } @@ -78,6 +82,10 @@ internal static void PollWasiEventLoopUntilResolvedVoid(Task mainTask) s_mainTask = mainTask; while (!mainTask.IsCompleted) { +#if FEATURE_PERFTRACING && CORECLR && !FEATURE_MULTITHREADING + // Before Dispatch, which can block in wasi:io/poll, so the pump's timer is registered first. + WasiEventPipeJobs.Pump(); +#endif ThreadPoolWorkQueue.Dispatch(); WasiFinalizerScheduler.DrainIfPending(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventPipeJobs.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventPipeJobs.cs new file mode 100644 index 00000000000000..fbc5fe31444815 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventPipeJobs.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace System.Threading +{ + // Runs the native EventPipe jobs (session streaming, diagnostic server) that browser schedules with + // setTimeout. WASI has no host event loop, so the native side keeps the job queue and WasiEventLoop + // calls Pump on each iteration. Jobs only run while the event loop is being pumped. + internal static partial class WasiEventPipeJobs + { + // Matches the browser re-schedule interval for unfinished jobs. + private const int PumpIntervalMs = 100; + + private static bool s_pumpRunning; + + // Runs the queued jobs and returns true if any remain queued. + [ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)] + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_WasiRunJobs")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool RunJobs(); + + internal static void Pump() + { + if (s_pumpRunning) + { + return; + } + + s_pumpRunning = true; + _ = PumpAsync(); + } + + private static async Task PumpAsync() + { + try + { + while (RunJobs()) + { + await Task.Delay(PumpIntervalMs).ConfigureAwait(false); + } + } + finally + { + s_pumpRunning = false; + } + } + } +} diff --git a/src/native/eventpipe/ep-session.c b/src/native/eventpipe/ep-session.c index 09c6a12b41f104..72420de83d934a 100644 --- a/src/native/eventpipe/ep-session.c +++ b/src/native/eventpipe/ep-session.c @@ -153,6 +153,8 @@ EP_RT_DEFINE_THREAD_FUNC (streaming_thread) static size_t streaming_loop_tick(EventPipeSession *const session) { bool events_written = false; bool ok; + if (!ep_session_has_started (session)) + ep_rt_volatile_store_uint32_t (&session->started, 1); if (!ep_session_get_streaming_enabled (session)){ ep_session_dec_ref (session); return 1; // done @@ -161,8 +163,8 @@ static size_t streaming_loop_tick(EventPipeSession *const session) { ok = ep_session_write_all_buffers_to_file (session, &events_written); EP_GCX_PREEMP_EXIT if (!ok) { + // Keep the job so the next tick sees streaming disabled and releases its reference. ep_disable ((EventPipeSessionID)session); - return 1; // done } return 0; // continue } @@ -189,8 +191,10 @@ session_create_streaming_thread (EventPipeSession *session) EP_UNREACHABLE ("Unable to create streaming thread."); #else ep_session_inc_ref (session); - ep_rt_volatile_store_uint32_t (&session->started, 1); - ep_rt_queue_job ((void *)streaming_loop_tick, (void *)session); + if (!ep_rt_queue_job ((void *)streaming_loop_tick, (void *)session)) { + // The session's own reference keeps it alive, so this only drops the job's reference. + ep_session_dec_ref (session); + } #endif } @@ -400,10 +404,11 @@ ep_session_dec_ref (EventPipeSession *session) { ep_return_void_if_nok (session != NULL); - EP_ASSERT (!ep_session_get_streaming_enabled (session)); - - if (ep_rt_atomic_dec_uint32_t (&session->ref_count) == 0) + if (ep_rt_atomic_dec_uint32_t (&session->ref_count) == 0) { + // Streaming must be stopped before the last reference goes away. + EP_ASSERT (!ep_session_get_streaming_enabled (session)); session_free (session); + } } static