Skip to content

feat: Add Task support for BatchExportProcessor and PeriodicExportingMetricReader for single-threaded WebAssembly#6379

Merged
rajkumar-rangaraj merged 28 commits intoopen-telemetry:mainfrom
jeromelaban:dev/jela/tasks
Apr 2, 2026
Merged

feat: Add Task support for BatchExportProcessor and PeriodicExportingMetricReader for single-threaded WebAssembly#6379
rajkumar-rangaraj merged 28 commits intoopen-telemetry:mainfrom
jeromelaban:dev/jela/tasks

Conversation

@jeromelaban
Copy link
Copy Markdown
Contributor

@jeromelaban jeromelaban commented Jul 15, 2025

Fixes #2816
Design discussion issue: Partially based on #5778 and #5838

Changes

The change introduces updates to BatchExportProcessor and PeriodicExportingMetricReader in order to switch between Thread and Task implementations to support .NET's WebAssembly single threaded mode (used by Blazor, Uno Platform and others).

Based on #5838 (review), the threads mode is automatically enabled when multi-threading is available. Tasks are enabled when WebAssembly is detected, based on the ThreadingHelper class.

Note that there is no public API to change between thread and tasks.

Merge requirement checklist

  • CONTRIBUTING guidelines followed (license requirements, nullable enabled, static analysis, etc.)
  • Unit tests added/updated
  • Appropriate CHANGELOG.md files updated for non-trivial changes
  • Changes in public API reviewed (if applicable)

@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla bot commented Jul 15, 2025

CLA Signed

The committers listed above are authorized under a signed CLA.

@github-actions github-actions bot added the pkg:OpenTelemetry Issues related to OpenTelemetry NuGet package label Jul 15, 2025
@codecov
Copy link
Copy Markdown

codecov bot commented Jul 15, 2025

Codecov Report

❌ Patch coverage is 71.82448% with 122 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.39%. Comparing base (ec00c87) to head (1eb4768).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...rc/OpenTelemetry/Internal/BatchExportTaskWorker.cs 37.86% 64 Missing ⚠️
...nternal/PeriodicExportingMetricReaderTaskWorker.cs 65.67% 23 Missing ⚠️
.../OpenTelemetry/Internal/BatchExportThreadWorker.cs 82.92% 14 Missing ⚠️
...ernal/PeriodicExportingMetricReaderThreadWorker.cs 75.47% 13 Missing ⚠️
src/OpenTelemetry/Internal/BatchExportWorker.cs 91.66% 3 Missing ⚠️
src/OpenTelemetry/BatchExportProcessor.cs 93.93% 2 Missing ⚠️
...ry/Internal/PeriodicExportingMetricReaderWorker.cs 88.23% 2 Missing ⚠️
...ry/Metrics/Reader/PeriodicExportingMetricReader.cs 96.29% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #6379      +/-   ##
==========================================
- Coverage   88.90%   88.39%   -0.51%     
==========================================
  Files         263      270       +7     
  Lines       12424    12731     +307     
==========================================
+ Hits        11045    11253     +208     
- Misses       1379     1478      +99     
Flag Coverage Δ
unittests-Project-Experimental 88.30% <71.82%> (-0.49%) ⬇️
unittests-Project-Stable 88.18% <71.82%> (-0.59%) ⬇️
unittests-Solution 88.33% <71.82%> (-0.42%) ⬇️
unittests-UnstableCoreLibraries-Experimental 41.38% <16.62%> (-1.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/OpenTelemetry/Internal/ThreadingHelper.cs 100.00% <100.00%> (ø)
...ry/Trace/Processor/BatchActivityExportProcessor.cs 100.00% <ø> (ø)
...ry/Metrics/Reader/PeriodicExportingMetricReader.cs 95.12% <96.29%> (+10.91%) ⬆️
src/OpenTelemetry/BatchExportProcessor.cs 94.82% <93.93%> (+10.56%) ⬆️
...ry/Internal/PeriodicExportingMetricReaderWorker.cs 88.23% <88.23%> (ø)
src/OpenTelemetry/Internal/BatchExportWorker.cs 91.66% <91.66%> (ø)
...ernal/PeriodicExportingMetricReaderThreadWorker.cs 75.47% <75.47%> (ø)
.../OpenTelemetry/Internal/BatchExportThreadWorker.cs 82.92% <82.92%> (ø)
...nternal/PeriodicExportingMetricReaderTaskWorker.cs 65.67% <65.67%> (ø)
...rc/OpenTelemetry/Internal/BatchExportTaskWorker.cs 37.86% <37.86%> (ø)

... and 1 file with indirect coverage changes

@jeromelaban
Copy link
Copy Markdown
Contributor Author

To whoever approved the workflows run, thank you :)

I've made adjustments to avoid parameters reordering breaking changes which should get the build go further.

@jeromelaban
Copy link
Copy Markdown
Contributor Author

The tests revealed a possible race condition in the disposing/shutdown ordering of the processor resulting in missing exports, for the task implementation.

@jeromelaban
Copy link
Copy Markdown
Contributor Author

Fixed an additional race condition for slower machines where the exporter may not have fully started while being shut down, preventing it to still export what was in queue.

@jeromelaban jeromelaban marked this pull request as ready for review July 17, 2025 01:22
@jeromelaban jeromelaban requested a review from a team as a code owner July 17, 2025 01:22
@jeromelaban
Copy link
Copy Markdown
Contributor Author

Thanks for the run, again!

Looks like there's a reported test failure, but without any failed tests. Feels like a fluke, but it may be something known to this project. Would a rerun help? Thanks!

@jeromelaban
Copy link
Copy Markdown
Contributor Author

Thanks @cijothomas, it helped :)

@cijothomas cijothomas requested a review from Copilot July 17, 2025 02:27

This comment was marked as outdated.

@jeromelaban jeromelaban requested a review from Copilot July 17, 2025 13:01

This comment was marked as outdated.

@jeromelaban jeromelaban requested a review from Copilot July 17, 2025 13:20

This comment was marked as outdated.

@jeromelaban jeromelaban requested a review from Copilot July 22, 2025 03:23
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds optional Task support for BatchExportProcessor and PeriodicExportingMetricReader to enable OpenTelemetry to work in .NET's WebAssembly single-threaded mode (Blazor, Uno Platform, etc.). The implementation maintains backward compatibility by defaulting to the existing Thread-based approach while allowing configuration to use Task-based workers.

Key changes:

  • Added new UseThreads parameter to control whether to use Thread or Task-based implementations
  • Refactored existing code to use abstract worker classes with Thread and Task implementations
  • WebAssembly runtime automatically uses Task-based workers when detected

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated no comments.

Show a summary per file
File Description
BatchExportProcessor.cs Refactored to use configurable worker pattern with Thread/Task implementations
PeriodicExportingMetricReader.cs Added options-based constructor and worker abstraction
BatchExportProcessorOptions.cs Added UseThreads property
PeriodicExportingMetricReaderOptions.cs Added UseThreads property
BatchActivityExportProcessor.cs Added options-based constructor overload
BatchLogRecordExportProcessor.cs Added options-based constructor overload
Multiple worker classes New abstract base classes and Thread/Task implementations
Test files Updated tests to cover both Thread and Task scenarios
Comments suppressed due to low confidence (4)

test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs:16

  • Parameter name 'useThread' should be 'useThreads' to be consistent with the property name 'UseThreads' used throughout the codebase.
    public void StateValuesAndScopeBufferingTest(bool useThread)

test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs:33

  • Variable name 'useThread' should be 'useThreads' to match the property name and maintain consistency.
                UseThreads = useThread,

test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs:165

  • Parameter name 'useThread' should be 'useThreads' for consistency with the property name used throughout the codebase.
    public void DisposeWithoutShutdown(bool useThread)

test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs:182

  • Variable name 'useThread' should be 'useThreads' to match the property name and maintain consistency.
                UseThreads = useThread,

@jeromelaban
Copy link
Copy Markdown
Contributor Author

Thanks for the reviews @martincostello!

this.workerTask = Task.Factory.StartNew(
this.ExporterProcAsync,
this.cancellationTokenSource.Token,
TaskCreationOptions.LongRunning,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option means creating a dedicated thread, defeating the purpose of this implementation. Though, since ExporterProcAsync can yield the execution, the continuation will be schedule on the thread pool and the thread will probably die.

A simple Task.Run would be safer here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TaskCreationOptions.LongRunning hints the scheduler to create a dedicated thread, which is exactly what this PR is trying to avoid on WebAssembly. As @verdie-g pointed out in the PR comments, a simple Task.Run(this.ExporterProcAsync) would be safer. On WASM where threads aren't available, LongRunning may either be ignored or throw.

await Task.WhenAny(
this.dataExportedNotification.Task,
this.shutdownCompletionSource.Task,
Task.Delay(timeout, combinedTokenSource.Token)).ConfigureAwait(false);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the Task.Delay is used as a timeout mechanism, the delay result will be discarded most of the time since the other would already have completed. Yet, we let the timer fire every time here which can cause important contention (see dotnet/runtime#83890 (comment)). The fix would be to cancel the Task.Delay when it did not complete.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While combinedTokenSource exists, it only cancels on overall shutdown/timeout, not when dataExportedNotification completes. Each loop iteration leaks a live timer until it fires. This was also flagged by @verdie-g. The fix: cancel the CancellationTokenSource after WhenAny returns, or restructure to reuse a single timer.

Same issue exists in ExporterProcAsync:

await Task.WhenAny(
    this.exportTrigger.WaitAsync(cancellationToken),
    Task.Delay(this.ScheduledDelayMilliseconds, cancellationToken)).ConfigureAwait(false);

The Task.Delay is only cancelled on full shutdown (cancellationToken), not when the semaphore fires, so timers accumulate.

This was referenced Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

keep-open Prevents issues and pull requests being closed as stale pkg:OpenTelemetry Issues related to OpenTelemetry NuGet package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tracing not working in Blazor WebAssembly application