Description
In .NET 9+, CORECLR_NOTIFICATION_PROFILERS silently does nothing when the value contains a single entry with no trailing semicolon - the most common real-world usage. This is a regression from .NET 8.
I noticed this when adding integration tests for notification profiling for 9 and 10: grafana/pyroscope-dotnet#252
Root cause
AttemptLoadProfilerList was rewritten in .NET 9 from wcstok_s-based parsing to SString::Find-based iteration in #98008. The new loop uses Find(sectionEnd, W(';')) as its condition:
// .NET 9+ (broken) — profilinghelper.cpp line 797
for (SString::Iterator sectionStart = profilerList.Begin(), sectionEnd = profilerList.Begin();
profilerList.Find(sectionEnd, W(';')); // ← never true if no ';' present
sectionStart = ++sectionEnd)
{
// load profiler from [sectionStart, sectionEnd)
}
If the string contains no ;, Find returns false immediately and the loop body never
executes. The last (or only) entry — which has no trailing semicolon — is silently dropped.
The .NET 8 implementation handled this correctly using wcstok_s, which returns all tokens
including the final one regardless of whether a trailing delimiter is present:
// .NET 8 (correct) — profilinghelper.cpp line 817
currentSection = wcstok_s(wszProfilerList, W(";"), &pOuter);
for (; currentSection != NULL; currentSection = wcstok_s(NULL, W(";"), &pOuter))
{
// processes all tokens including the last one without trailing ';'
}
Steps to reproduce
Set the following environment variables and launch a .NET 9+ process with a profiler that supports
the notification profiler role (i.e. LoadAsNotificationOnly returns TRUE):
CORECLR_ENABLE_PROFILING=1
CORECLR_PROFILER={main-profiler-guid}
CORECLR_PROFILER_PATH=/path/to/main-profiler.so
CORECLR_ENABLE_NOTIFICATION_PROFILERS=1
CORECLR_NOTIFICATION_PROFILERS=/path/to/notification-profiler.so={notification-guid}
Expected: notification profiler loads and its Initialize is called.
Actual: notification profiler is never loaded; no error or warning is emitted.
Workaround
Append a trailing semicolon to the value:
CORECLR_NOTIFICATION_PROFILERS=/path/to/notification-profiler.so={notification-guid};
Impact
Any tool using CORECLR_NOTIFICATION_PROFILERS with a single entry silently stops working when upgrading from .NET 8 to .NET 9+. The failure is invisible: no error is logged, the app starts normally, and profiling data is simply absent.
Confirmed affected: .NET 9.0.14, .NET 10.0.105 (SDK 10.0.201), main as of 2026-03-27.
Suggested fix
Either handle the trailing entry after the loop, or normalize input before parsing:
// Option A: process the final entry after the loop
if (sectionStart != profilerList.End())
{
// process [sectionStart, End())
}
// Option B: normalize input by appending ';' before parsing
profilerList.Append(W(';'));
Source references
Description
In .NET 9+,
CORECLR_NOTIFICATION_PROFILERSsilently does nothing when the value contains a single entry with no trailing semicolon - the most common real-world usage. This is a regression from .NET 8.I noticed this when adding integration tests for notification profiling for 9 and 10: grafana/pyroscope-dotnet#252
Root cause
AttemptLoadProfilerListwas rewritten in .NET 9 fromwcstok_s-based parsing toSString::Find-based iteration in #98008. The new loop usesFind(sectionEnd, W(';'))as its condition:If the string contains no
;,Findreturnsfalseimmediately and the loop body neverexecutes. The last (or only) entry — which has no trailing semicolon — is silently dropped.
The .NET 8 implementation handled this correctly using
wcstok_s, which returns all tokensincluding the final one regardless of whether a trailing delimiter is present:
Steps to reproduce
Set the following environment variables and launch a .NET 9+ process with a profiler that supports
the notification profiler role (i.e.
LoadAsNotificationOnlyreturnsTRUE):Expected: notification profiler loads and its
Initializeis called.Actual: notification profiler is never loaded; no error or warning is emitted.
Workaround
Append a trailing semicolon to the value:
Impact
Any tool using
CORECLR_NOTIFICATION_PROFILERSwith a single entry silently stops working when upgrading from .NET 8 to .NET 9+. The failure is invisible: no error is logged, the app starts normally, and profiling data is simply absent.Confirmed affected: .NET 9.0.14, .NET 10.0.105 (SDK 10.0.201),
mainas of 2026-03-27.Suggested fix
Either handle the trailing entry after the loop, or normalize input before parsing:
Source references
.NET 9—profilinghelper.cppline 757–838 (brokenSString::Findloop).NET 9— broken loop condition at line 797.NET 10v10.0.105 — same bug at line 797mainas of 2026-03-27 — same bug at line 796.NET 8—profilinghelper.cppline 773–840 (correctwcstok_simplementation).NET 8—wcstok_sat line 817