Skip to content

[wasi] Enable EventPipe (FEATURE_PERFTRACING) for CoreCLR WASI - #134978

Open
lewing wants to merge 3 commits into
mainfrom
lewing-wasi-enable-eventpipe
Open

lewing wants to merge 3 commits into
mainfrom
lewing-wasi-enable-eventpipe

Conversation

@lewing

@lewing lewing commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

CoreCLR WASI was built with FEATURE_EVENT_TRACE/FEATURE_PERFTRACING off, so anything that goes through EventPipe didn't work. Most visibly, EventSource.CurrentThreadActivityId always returned Guid.Empty on WASI. This enables EventPipe the same way browser CoreCLR does, using threadless EventPipe (FEATURE_PERFTRACING_DISABLE_THREADS).

Changes

  • Enable the feature. Remove the WASI override in src/coreclr/CMakeLists.txt and the FeaturePerfTracing=false override in clr.featuredefines.props, so the native runtime and CoreLib agree again.
  • Diagnostic server PAL. WASI has no AF_UNIX (sockaddr_un has no sun_path), so build the TCP flavor (FEATURE_PERFTRACING_PAL_TCP) and link it into debug-pal. That link was disabled for WASI and tracked by [wasm][wasi] CoreCLR-WASI follow-up TODOs from #130051 (PERFTRACING, exit-code marker, getexepath synth) #130383. All listen and connect ports are disabled (DISABLE_PERFTRACING_LISTEN_PORTS/DEFAULT_LISTEN_PORT/CONNECT_PORTS, the same set Mono WASI uses). So EventPipe works in-process only, and out-of-process tools can't attach yet.
  • Pump EventPipe jobs from the WASI event loop. Browser runs EventPipe jobs (session streaming drain, diagnostic server loop) from a JS setTimeout loop. WASI has no host event loop, so:
    • ep_rt_queue_job puts jobs on a native list (eventpipeinternal.cpp).
    • Before each ThreadPoolWorkQueue.Dispatch(), which can block in wasi:io/poll, WasiEventLoop checks for pending jobs through EventPipeInternal_WasiHasPendingJobs. If there are any, it starts a managed pump (WasiEventPipeJobs.cs) that calls EventPipeInternal_WasiRunJobs and re-polls every 100 ms with Task.Delay. The pump stops once no jobs remain.
    • Jobs only run while the event loop is pumped (async Main or waiting on tasks), as with timers and finalizers on WASI.
  • Finish traces at shutdown. A queued streaming job holds a session reference, and the event loop doesn't run after shutdown. So on WASI, ep_rt_shutdown runs the queued jobs once. The disabled streaming sessions then release their references, and freeing the session writes the trace's end-of-stream tag and closes the file.
  • Streaming job lifetime (shared ep-session.c):
    • If ep_rt_queue_job fails (on WASI, only on allocation failure), ep_session_start_streaming drops the reference it took for the job.
    • After a failed write, streaming_loop_tick keeps the job, so the next tick releases the session reference instead of leaking it. This also changes browser's behavior on that path.
  • Tests. Exclude the EventPipeListener harness on WASI, as on browser. It uses DiagnosticsClient IPC, which needs Process and a diagnostic port.

What happens when nothing drains

A session's buffer manager allocates buffers on demand up to its cap: DOTNET_EventPipeCircularMB (default 1024 MB) for the startup file session, and 10 MB for in-process EventListener sessions. In the default Drop mode, events written past the cap are discarded; buffered data is never overwritten. Block mode needs a drain thread, so single-threaded builds reject it. In-process EventListener sessions were already drained by the managed Task.Delay(100) loop in EventPipeEventDispatcher.Wasm.cs. The pump above covers file and IPC streaming sessions. With a synchronous Main, the pump never runs; events buffer up to the cap and are written when the session is disabled at shutdown.

Validation

Local WASI build on macOS arm64, Debug runtime, Release libraries, wasmtime 45:

  • ./build.sh -s clr+libs -os wasi -arch wasm -c Debug -lc Release /p:TestAssemblies=false: passed
  • ./build.sh -s host+packs+tasks -os wasi -arch wasm -c Debug -lc Release /p:RuntimeFlavor=CoreCLR: passed
  • dotnet build src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj /t:Test /p:TargetOS=wasi /p:TargetArchitecture=wasm /p:RuntimeFlavor=CoreCLR /p:Configuration=Release /p:RuntimeConfiguration=Debug /p:TasksConfiguration=Debug: 39 run, 37 passed, 0 failed, 2 skipped.
    • ActivityTracking.StartStopCreatesActivity, ActivityFlowsAsync, SetCurrentActivityIdBeforeEventFlowsAsync and SetCurrentActivityIdAfterEventDoesNotFlowAsync now pass.
  • Drain and trace-ending check: ran the same suite under wasmtime with DOTNET_EnableEventPipe=1, DOTNET_EventPipeCircularMB=1, and verbose Microsoft-Windows-DotNETRuntime + TplEventSource providers.
    • All tests passed, and the .nettrace was 5.2 MB, more than the 1 MB cap could hold without draining during the run.
    • The trace ends with the NullReference end-of-stream tag (0x01), and so does a single-test run. Before the shutdown fix, traces ended with 0x06.
    • I didn't do a full TraceEvent parse.

Not validated locally:

  • Browser, Mono, and NativeAOT builds of the shared ep-session.c changes.
  • The CoreCLR WASI WasmEnableThreads build. The pump is excluded there via FEATURE_MULTITHREADING, the same condition as its projitems include.

Follow-ups (not in this PR)

  • An IPC transport for the diagnostic server over wasi:sockets TCP. The job pump already handles its server loop once ports are enabled.
  • ETW::GCLog::ForceGC defers through the JS job queue on browser but runs synchronously on WASI. It's only reachable through the GCHeapCollect keyword.
  • ds-portable-rid.c checks TARGET_UNIX before TARGET_WASI, so WASI reports a unix-wasm RID. It's only sent over diagnostic IPC, which is disabled here.

Resolves #134963

Note

This PR description was generated with the help of GitHub Copilot.

Enable FEATURE_EVENT_TRACE/FEATURE_PERFTRACING for CoreCLR WASI using
threadless EventPipe, matching browser CoreCLR, and turn FeaturePerfTracing
back on in CoreLib.

- Build the TCP flavor of the diagnostic server PAL (WASI has no AF_UNIX)
  and disable all listen/connect ports until a WASI transport is validated.
- ep_rt_queue_job returns false instead of hitting EP_UNREACHABLE when the
  host has no job queue; ep_session_start_streaming drops the job's session
  reference in that case. Streaming sessions are flushed on disable.
- Exclude the out-of-process EventPipeListener test harness on WASI, as on
  browser.

Fixes EventSource activity-id tracking on WASI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@lewing lewing added arch-wasm WebAssembly architecture os-wasi Related to WASI variant of arch-wasm labels Sep 30, 2026
@lewing
lewing deployed to copilot-pat-pool September 30, 2026 20:18 — with GitHub Actions Active
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

@pavelsavara

Copy link
Copy Markdown
Member

In browser there is JS setTimeout that is pumping the EP "loop". What is pumping it here ?

Browser runs EventPipe jobs (session streaming, diagnostic server) from a
JS setTimeout loop. WASI has no host event loop, so keep the jobs on a
native list and drain it from managed WasiEventLoop via two QCalls,
re-polling every 100ms with Task.Delay (which blocks in wasi:io/poll
rather than spinning). Jobs only run while the event loop is pumped;
otherwise streaming sessions still flush when they are disabled.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing

lewing commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

@pavelsavara In the first commit, nothing pumped it on WASI. File and IPC streaming sessions just kept buffering up to EventPipeCircularMB (default 1024 MB). Past the cap, new events were dropped (Drop mode), and the rest was flushed when the session was disabled. In-process EventListener sessions were already drained by the managed Task.Delay(100) loop in EventPipeEventDispatcher.Wasm.cs.

5f33b10 adds the WASI counterpart of the setTimeout loop:

  • ep_rt_queue_job puts jobs on a native list.
  • Each WasiEventLoop turn checks it through a QCall, next to the existing finalizer check.
  • If jobs are pending, a managed pump runs them and re-polls every 100 ms with Task.Delay, which blocks in wasi:io/poll.

Jobs only run while the event loop is pumped (async Main or waiting on tasks), as with timers and finalizers.

To verify, I ran the tracing test suite under wasmtime with DOTNET_EnableEventPipe=1, DOTNET_EventPipeCircularMB=1 and verbose runtime + TPL providers. It produced a 4.6 MB .nettrace, so the session was drained while the tests ran, and all tests passed.

Note

This comment was generated with the help of GitHub Copilot.

- Run queued WASI EventPipe jobs once from ep_rt_shutdown. A queued
  streaming job holds a session reference, and the event loop no longer
  runs after shutdown, so the session was never freed and its trace never
  got the end-of-stream tag.
- Start the job pump before ThreadPoolWorkQueue.Dispatch, which can block
  in wasi:io/poll, and skip it in multithreaded builds where the pump is
  not compiled.
- Restore EP_UNREACHABLE for single-threaded CoreCLR hosts other than
  browser and WASI.
- On a streaming write failure, keep the job so the next tick releases
  the session reference instead of leaking it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

This branch was successfully deployed

1 active (outdated) deployment
copilot-pat-pool — fdf9a733 Deployed Sep 30, 2026 by lewing via conclusion #9406
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Tracing-coreclr os-wasi Related to WASI variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[wasi] Enable EventPipe (FEATURE_PERFTRACING) for CoreCLR WASI

2 participants