Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
82ab8ed
Add the Block (non-lossy) EventPipe buffering mode
mdh1418 Jun 15, 2026
f73319e
Park EventPipe producers on full buffers in Block mode
mdh1418 Jun 15, 2026
d3e2726
Dispatch provider callbacks from ep_start_streaming
mdh1418 Jun 15, 2026
f15cb28
Detach deferred provider callbacks under the EventPipe lock
mdh1418 Jun 16, 2026
0e40f68
Harden EventPipe Block-mode teardown and deferred callback dispatch
mdh1418 Jun 17, 2026
d4c7780
Add unit test for Block-mode buffer manager abort and disable
mdh1418 Jun 18, 2026
24f66b5
Address review feedback on Block-mode buffer manager
mdh1418 Jun 22, 2026
4f02569
Add an explicit FIFO wait queue for parked Block-mode producers
mdh1418 Jun 22, 2026
899214f
Replace deferred provider-callback queue with session_init/enable split
mdh1418 Jun 25, 2026
ab24be4
Start EventPipe session drain threads eagerly as native threads
mdh1418 Jun 25, 2026
968d9d9
Exclude Block buffering mode from single-threaded (PERFTRACING_DISABL…
mdh1418 Jun 25, 2026
f91b5e2
Restrict Block buffering mode to streaming session types
mdh1418 Jun 25, 2026
554b6e7
Add CollectTracing6 IPC command to opt into Block buffering mode
mdh1418 Jun 16, 2026
ddf2af6
Add DOTNET_EventPipeBufferingMode env-var opt-in for the startup session
mdh1418 Jun 25, 2026
53905b2
Rename ep_start_streaming to ep_start_session
mdh1418 Jun 25, 2026
852922f
Merge remote-tracking branch 'upstream/main' into eventpipe-nonlossy-…
mdh1418 Jun 25, 2026
cbf3585
Address Copilot review feedback
mdh1418 Jun 25, 2026
5e64d8a
Attach EventPipe session drain thread at the minimum level on Mono an…
mdh1418 Jul 6, 2026
0e6f069
Address PR feedback: drop Block-mode alloc fallbacks and cleanup
mdh1418 Jul 8, 2026
8eeafd6
Rename ep_enable/_2/_3 to ep_init_session/_2/_3
mdh1418 Jul 8, 2026
0357c8c
Address review feedback: reject invalid buffering-mode configs; accou…
mdh1418 Jul 8, 2026
5999d08
Only park Block-mode writers on buffer-capacity exhaustion
mdh1418 Jul 9, 2026
f6ddfad
Address feedback
mdh1418 Jul 10, 2026
bf529eb
Address feedback
mdh1418 Jul 13, 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
5 changes: 3 additions & 2 deletions src/mono/mono/eventpipe/test/ep-buffer-manager-tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ buffer_manager_init (
1,
NULL,
NULL,
0);
0,
EP_BUFFERING_MODE_DROP);
EP_LOCK_EXIT (section1)

ep_raise_error_if_nok (*session != NULL);
Expand Down Expand Up @@ -164,7 +165,7 @@ write_events (
for (; i < event_count; ++i) {
EventPipeEventPayload payload;
ep_event_payload_init (&payload, (uint8_t *)TEST_EVENT_DATA, ARRAY_SIZE (TEST_EVENT_DATA));
result = ep_buffer_manager_write_event (buffer_manager, thread, session, ep_event, &payload, NULL, NULL, thread, NULL);
result = ep_buffer_manager_write_event (buffer_manager, thread, session, ep_event, &payload, NULL, NULL, thread, NULL) == EP_WRITE_EVENT_RESULT_WRITTEN;
Comment thread
mdh1418 marked this conversation as resolved.
ep_event_payload_fini (&payload);

if (!result)
Expand Down
20 changes: 10 additions & 10 deletions src/mono/mono/eventpipe/test/ep-tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ test_session_write_event (void)
EventPipeSessionID session_id = 0;
EventPipeProviderConfiguration provider_config;
EventPipeProviderConfiguration *current_provider_config = NULL;
bool write_result = false;
EventPipeWriteEventResult write_result = EP_WRITE_EVENT_RESULT_DROPPED;

current_provider_config = ep_provider_config_init (&provider_config, TEST_PROVIDER_NAME, 1, EP_EVENT_LEVEL_LOGALWAYS, "");
ep_raise_error_if_nok (current_provider_config != NULL);
Expand Down Expand Up @@ -799,7 +799,7 @@ test_session_write_event (void)
write_result = ep_session_write_event ((EventPipeSession *)(uintptr_t)session_id, ep_rt_thread_get_handle (), ep_event, &payload, NULL, NULL, NULL, NULL);
ep_event_payload_fini (&payload);

ep_raise_error_if_nok (write_result == true);
ep_raise_error_if_nok (write_result == EP_WRITE_EVENT_RESULT_WRITTEN);

ep_on_exit:
ep_disable (session_id);
Expand All @@ -823,7 +823,7 @@ test_session_write_event_seq_point (void)
EventPipeSessionID session_id = 0;
EventPipeProviderConfiguration provider_config;
EventPipeProviderConfiguration *current_provider_config = NULL;
bool write_result = false;
EventPipeWriteEventResult write_result = EP_WRITE_EVENT_RESULT_DROPPED;

current_provider_config = ep_provider_config_init (&provider_config, TEST_PROVIDER_NAME, 1, EP_EVENT_LEVEL_LOGALWAYS, "");
ep_raise_error_if_nok (current_provider_config != NULL);
Expand Down Expand Up @@ -852,7 +852,7 @@ test_session_write_event_seq_point (void)
write_result = ep_session_write_event ((EventPipeSession *)(uintptr_t)session_id, ep_rt_thread_get_handle (), ep_event, &payload, NULL, NULL, NULL, NULL);
ep_event_payload_fini (&payload);

ep_raise_error_if_nok (write_result == true);
ep_raise_error_if_nok (write_result == EP_WRITE_EVENT_RESULT_WRITTEN);

test_location = 5;

Expand Down Expand Up @@ -880,7 +880,7 @@ test_session_write_wait_get_next_event (void)
EventPipeSessionID session_id = 0;
EventPipeProviderConfiguration provider_config;
EventPipeProviderConfiguration *current_provider_config = NULL;
bool write_result = false;
EventPipeWriteEventResult write_result = EP_WRITE_EVENT_RESULT_DROPPED;

current_provider_config = ep_provider_config_init (&provider_config, TEST_PROVIDER_NAME, 1, EP_EVENT_LEVEL_LOGALWAYS, "");
ep_raise_error_if_nok (current_provider_config != NULL);
Expand Down Expand Up @@ -909,7 +909,7 @@ test_session_write_wait_get_next_event (void)
write_result = ep_session_write_event ((EventPipeSession *)(uintptr_t)session_id, ep_rt_thread_get_handle (), ep_event, &payload, NULL, NULL, NULL, NULL);
ep_event_payload_fini (&payload);

ep_raise_error_if_nok (write_result == true);
ep_raise_error_if_nok (write_result == EP_WRITE_EVENT_RESULT_WRITTEN);

test_location = 5;

Expand Down Expand Up @@ -945,7 +945,7 @@ test_session_write_get_next_event (void)
EventPipeSessionID session_id = 0;
EventPipeProviderConfiguration provider_config;
EventPipeProviderConfiguration *current_provider_config = NULL;
bool write_result = false;
EventPipeWriteEventResult write_result = EP_WRITE_EVENT_RESULT_DROPPED;

current_provider_config = ep_provider_config_init (&provider_config, TEST_PROVIDER_NAME, 1, EP_EVENT_LEVEL_LOGALWAYS, "");
ep_raise_error_if_nok (current_provider_config != NULL);
Expand Down Expand Up @@ -981,7 +981,7 @@ test_session_write_get_next_event (void)
write_result = ep_session_write_event ((EventPipeSession *)(uintptr_t)session_id, ep_rt_thread_get_handle (), ep_event, &payload, NULL, NULL, NULL, NULL);
ep_event_payload_fini (&payload);

ep_raise_error_if_nok (write_result == true);
ep_raise_error_if_nok (write_result == EP_WRITE_EVENT_RESULT_WRITTEN);

test_location = 6;

Expand Down Expand Up @@ -1022,7 +1022,7 @@ test_session_write_suspend_event (void)
EventPipeSessionID session_id = 0;
EventPipeProviderConfiguration provider_config;
EventPipeProviderConfiguration *current_provider_config = NULL;
bool write_result = false;
EventPipeWriteEventResult write_result = EP_WRITE_EVENT_RESULT_DROPPED;

current_provider_config = ep_provider_config_init (&provider_config, TEST_PROVIDER_NAME, 1, EP_EVENT_LEVEL_LOGALWAYS, "");
ep_raise_error_if_nok (current_provider_config != NULL);
Expand Down Expand Up @@ -1051,7 +1051,7 @@ test_session_write_suspend_event (void)
write_result = ep_session_write_event ((EventPipeSession *)(uintptr_t)session_id, ep_rt_thread_get_handle (), ep_event, &payload, NULL, NULL, NULL, NULL);
ep_event_payload_fini (&payload);

ep_raise_error_if_nok (write_result == true);
ep_raise_error_if_nok (write_result == EP_WRITE_EVENT_RESULT_WRITTEN);

test_location = 5;

Expand Down
89 changes: 76 additions & 13 deletions src/native/eventpipe/ep-buffer-manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ buffer_manager_release_buffer (
ep_rt_thread_sleep (0); // yield the thread to the scheduler in case we're in high contention
}
} while (new_size_of_all_buffers >= 0 && ep_rt_atomic_compare_exchange_size_t (&buffer_manager->size_of_all_buffers, old_size_of_all_buffers, new_size_of_all_buffers) != old_size_of_all_buffers);

if (buffer_manager->buffering_mode == EP_BUFFERING_MODE_BLOCK)
Comment thread
mdh1418 marked this conversation as resolved.
Outdated
ep_rt_wait_event_set (&buffer_manager->buffer_available_event);
}

#ifdef EP_CHECKED_BUILD
Expand Down Expand Up @@ -816,7 +819,9 @@ buffer_manager_convert_buffer_to_read_only (
// Both session_use_in_progress and write_buffer are accessed via atomic operations. By setting the write_buffer to NULL above while holding
// the buffer manager lock, the wait is correct only because the writer thread sets session_use_in_progress before caching the write_buffer
// and resets it after it's done using the cached write_buffer.
EP_YIELD_WHILE (ep_thread_get_session_use_in_progress (thread) == index &&
// Match on the WRITE_BUFFER_IN_USE bit, not just the index: a parked Block-mode producer holds
// the index with the bit cleared, which is exactly when we are free to drain its buffer.
EP_YIELD_WHILE (ep_thread_get_session_use_in_progress (thread) == (index | EP_SESSION_USE_WRITE_BUFFER_IN_USE) &&
ep_thread_session_state_get_volatile_write_buffer (thread_session_state) == NULL);
}

Expand All @@ -839,7 +844,8 @@ EventPipeBufferManager *
ep_buffer_manager_alloc (
EventPipeSession *session,
size_t max_size_of_all_buffers,
size_t sequence_point_allocation_budget)
size_t sequence_point_allocation_budget,
EventPipeBufferingMode buffering_mode)
{
EventPipeBufferManager *instance = ep_rt_object_alloc (EventPipeBufferManager);
ep_raise_error_if_nok (instance != NULL);
Expand All @@ -856,6 +862,14 @@ ep_buffer_manager_alloc (
ep_rt_wait_event_alloc (&instance->rt_wait_event, false, true);
ep_raise_error_if_nok (ep_rt_wait_event_is_valid (&instance->rt_wait_event));

instance->buffering_mode = buffering_mode;
instance->aborting = 0;

if (buffering_mode == EP_BUFFERING_MODE_BLOCK) {
ep_rt_wait_event_alloc (&instance->buffer_available_event, false, false);
ep_raise_error_if_nok (ep_rt_wait_event_is_valid (&instance->buffer_available_event));
}

instance->thread_session_state_list_snapshot = dn_list_alloc ();
ep_raise_error_if_nok (instance->thread_session_state_list_snapshot != NULL);
instance->snapshot_timestamp = 0;
Expand Down Expand Up @@ -914,11 +928,49 @@ ep_buffer_manager_free (EventPipeBufferManager * buffer_manager)

ep_rt_wait_event_free (&buffer_manager->rt_wait_event);

ep_rt_wait_event_free (&buffer_manager->buffer_available_event);

ep_rt_spin_lock_free (&buffer_manager->rt_lock);

ep_rt_object_free (buffer_manager);
}

void
ep_buffer_manager_writer_wait_for_capacity (EventPipeBufferManager *buffer_manager)
{
EP_ASSERT (buffer_manager != NULL);
Comment thread
mdh1418 marked this conversation as resolved.
Outdated
ep_rt_wait_event_wait (&buffer_manager->buffer_available_event, EP_INFINITE_WAIT, false);
}

bool
ep_buffer_manager_is_aborting (const EventPipeBufferManager *buffer_manager)
{
EP_ASSERT (buffer_manager != NULL);
return ep_rt_volatile_load_uint32_t (&buffer_manager->aborting) != 0;
}

void
ep_buffer_manager_signal_capacity (EventPipeBufferManager *buffer_manager)
{
EP_ASSERT (buffer_manager != NULL);
Comment thread
mdh1418 marked this conversation as resolved.
ep_rt_wait_event_set (&buffer_manager->buffer_available_event);
Comment thread
mdh1418 marked this conversation as resolved.
Outdated
}

void
ep_buffer_manager_abort_blocked_writers (EventPipeBufferManager *buffer_manager)
{
EP_ASSERT (buffer_manager != NULL);

if (buffer_manager->buffering_mode != EP_BUFFERING_MODE_BLOCK)
return;

// Raise the abort flag (parked producers give up when woken; none newly park). The wake is
// driven by ep_session_wait_for_inflight_thread_ops, which signals buffer_available_event while
// it waits out the threads still holding this session's index - so no parked-writer count is kept.
ep_rt_volatile_store_uint32_t (&buffer_manager->aborting, 1);
ep_rt_wait_event_set (&buffer_manager->buffer_available_event);
Comment thread
mdh1418 marked this conversation as resolved.
Outdated
}

#ifdef EP_CHECKED_BUILD
void
ep_buffer_manager_requires_lock_held (const EventPipeBufferManager *buffer_manager)
Expand Down Expand Up @@ -955,7 +1007,7 @@ ep_buffer_manager_init_sequence_point_thread_list (
ep_exit_error_handler ();
}

bool
EventPipeWriteEventResult
ep_buffer_manager_write_event (
EventPipeBufferManager *buffer_manager,
ep_rt_thread_handle_t thread,
Expand All @@ -967,7 +1019,7 @@ ep_buffer_manager_write_event (
ep_rt_thread_handle_t event_thread,
EventPipeStackContents *stack)
{
bool result = false;
EventPipeWriteEventResult result = EP_WRITE_EVENT_RESULT_DROPPED;
bool alloc_new_buffer = false;
EventPipeBuffer *buffer = NULL;
EventPipeThreadSessionState *session_state = NULL;
Expand All @@ -981,7 +1033,8 @@ ep_buffer_manager_write_event (
EP_ASSERT (thread == ep_rt_thread_get_handle ());

// Before we pick a buffer, make sure the event is enabled.
ep_return_false_if_nok (ep_event_is_enabled (ep_event));
if (!ep_event_is_enabled (ep_event))
return EP_WRITE_EVENT_RESULT_DROPPED;
Comment thread
mdh1418 marked this conversation as resolved.
Outdated

// Check to see if an event thread was specified. If not, then use the current thread.
if (event_thread == NULL)
Expand All @@ -992,7 +1045,7 @@ ep_buffer_manager_write_event (
ep_raise_error_if_nok (current_thread != NULL);

// session_state won't be freed if use_in_progress is set.
EP_ASSERT (ep_thread_get_session_use_in_progress (current_thread) == ep_session_get_index (session));
EP_ASSERT ((ep_thread_get_session_use_in_progress (current_thread) & ~EP_SESSION_USE_WRITE_BUFFER_IN_USE) == ep_session_get_index (session));
session_state = ep_thread_get_volatile_session_state (current_thread, session);
if (session_state == NULL) {
// slow path should only happen once per thread per session
Expand All @@ -1010,7 +1063,7 @@ ep_buffer_manager_write_event (
{
ep_rt_atomic_inc_int64_t (&buffer_manager->num_oversized_events_dropped);
ep_thread_session_state_increment_sequence_number (session_state);
return false;
return EP_WRITE_EVENT_RESULT_DROPPED;
}

current_stack_contents = ep_stack_contents_init (&stack_contents);
Expand All @@ -1020,7 +1073,7 @@ ep_buffer_manager_write_event (
}

// buffer won't be converted to read-only if use_in_progress is set
EP_ASSERT (ep_thread_get_session_use_in_progress (current_thread) == ep_session_get_index(session));
EP_ASSERT ((ep_thread_get_session_use_in_progress (current_thread) & ~EP_SESSION_USE_WRITE_BUFFER_IN_USE) == ep_session_get_index (session));
buffer = ep_thread_session_state_get_volatile_write_buffer (session_state);
if (!buffer) {
alloc_new_buffer = true;
Expand All @@ -1046,7 +1099,16 @@ ep_buffer_manager_write_event (
// We treat this as the write_event call occurring after this session stopped listening for events, effectively the
// same as if ep_event_is_enabled test above returned false.
ep_raise_error_if_nok (!write_suspended);
ep_thread_session_state_increment_sequence_number (session_state);

// In block mode, notify the caller that they should park and retry, unless the session is closing,
// or rundown is enabled, in which case we cannot block without deadlocking.
Comment thread
mdh1418 marked this conversation as resolved.
Outdated
if (buffer_manager->buffering_mode == EP_BUFFERING_MODE_BLOCK &&
!ep_rt_volatile_load_uint32_t (&buffer_manager->aborting) &&
!ep_thread_is_rundown_thread (current_thread)) {
result = EP_WRITE_EVENT_RESULT_BLOCKED;
} else {
ep_thread_session_state_increment_sequence_number (session_state);
}
} else {
current_thread = ep_thread_get ();
EP_ASSERT (current_thread != NULL);
Expand All @@ -1064,15 +1126,16 @@ ep_buffer_manager_write_event (
// Indicate that there is new data to be read
ep_rt_wait_event_set (&buffer_manager->rt_wait_event);

#ifdef EP_CHECKED_BUILD
if (!alloc_new_buffer)
result = EP_WRITE_EVENT_RESULT_WRITTEN;

#ifdef EP_CHECKED_BUILD
if (result == EP_WRITE_EVENT_RESULT_WRITTEN)
ep_rt_atomic_inc_int64_t (&buffer_manager->num_events_stored);
else
else if (result == EP_WRITE_EVENT_RESULT_DROPPED)
ep_rt_atomic_inc_int64_t (&buffer_manager->num_events_dropped);
#endif

result = !alloc_new_buffer;

ep_on_exit:
ep_stack_contents_fini (current_stack_contents);
return result;
Expand Down
27 changes: 25 additions & 2 deletions src/native/eventpipe/ep-buffer-manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ struct _EventPipeBufferManager_Internal {
// large to fit in the 64KB size limit
volatile int64_t num_oversized_events_dropped;

EventPipeBufferingMode buffering_mode;
// Block mode: auto-reset event the reader (and teardown) signals to wake a parked producer.
ep_rt_wait_event_handle_t buffer_available_event;
// Block mode: set once at teardown so parked producers give up and none newly park.
volatile uint32_t aborting;

#ifdef EP_CHECKED_BUILD
volatile int64_t num_events_stored;
volatile int64_t num_events_dropped;
Expand All @@ -144,7 +150,8 @@ EventPipeBufferManager *
ep_buffer_manager_alloc (
EventPipeSession *session,
size_t max_size_of_all_buffers,
size_t sequence_point_allocation_budget);
size_t sequence_point_allocation_budget,
EventPipeBufferingMode buffering_mode);

void
ep_buffer_manager_free (EventPipeBufferManager *buffer_manager);
Expand All @@ -171,7 +178,7 @@ ep_buffer_manager_init_sequence_point_thread_list (
// This is because the thread that writes the events is not the same as the "event thread".
// An optional stack trace can be provided for sample profiler events.
// Otherwise, if a stack trace is needed, one will be automatically collected.
bool
EventPipeWriteEventResult
ep_buffer_manager_write_event (
EventPipeBufferManager *buffer_manager,
ep_rt_thread_handle_t thread,
Expand All @@ -183,6 +190,22 @@ ep_buffer_manager_write_event (
ep_rt_thread_handle_t event_thread,
EventPipeStackContents *stack);

// Park the calling producer until the reader frees capacity or teardown wakes it.
void
Comment thread
mdh1418 marked this conversation as resolved.
ep_buffer_manager_writer_wait_for_capacity (EventPipeBufferManager *buffer_manager);

// True once the session is tearing down: a parked producer must give up and drop.
bool
ep_buffer_manager_is_aborting (const EventPipeBufferManager *buffer_manager);

// Wake one parked producer (called by the reader after freeing capacity, and by teardown).
void
ep_buffer_manager_signal_capacity (EventPipeBufferManager *buffer_manager);

// Teardown: make parked producers give up and stop new parks; call before freeing the buffers.
void
ep_buffer_manager_abort_blocked_writers (EventPipeBufferManager *buffer_manager);

// Write the contents of the managed buffers to the specified file.
// The stop_timeStamp is used to determine when tracing was stopped to ensure that we
// skip any events that might be partially written due to races when tracing is stopped.
Expand Down
Loading
Loading