Skip to content

[OpAMP] Refactor OpAMP communication pipe - #4930

Open
RassK wants to merge 23 commits into
open-telemetry:mainfrom
RassK:opamp-com-reworks
Open

[OpAMP] Refactor OpAMP communication pipe#4930
RassK wants to merge 23 commits into
open-telemetry:mainfrom
RassK:opamp-com-reworks

Conversation

@RassK

@RassK RassK commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What

Design discussion issue open-telemetry/opamp-spec#366

This is about ensuring that communication is according to spec.

When the Agent wants to send a message to the Server and the Agent has previously sent a request to the Server that is not yet responded, the Client MUST wait until the response is received before a new request can be made.
src: https://opentelemetry.io/docs/specs/opamp/#plain-http-transport

Since spec is currently not clear that messages can be accepted in sync or async manner. This PR is a basis to support both options via configuration. If it's decided that the client must block the pipe until a full response is constructed, a follow up is needed.

Changes

  • Makes sure the client is waiting for server to respond and blocks the pipe.
  • Uses accumulator message to "queue" client's messages

Breaking changes ❗

  • No need for async send messages, since messages are always accumulated.

Notes

❗ This PR is a preview and a discussion object how to proceed to support corner cases in the spec.
Since there seems to be a consensus with this PR, we can move forward

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 < TODO until the final form is decided
  • Changes in public API reviewed (if applicable)

@RassK
RassK requested a review from a team as a code owner August 4, 2026 11:12
@github-actions
github-actions Bot requested a review from stevejgordon August 4, 2026 11:12
@github-actions github-actions Bot added the comp:opamp.client Things related to OpenTelemetry.OpAmp.Client label Aug 4, 2026
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Aug 4, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on reviewers · refreshed 2026-08-14 16:20 UTC

Review the latest changes.

Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Anything look wrong? Report it with what you expected; it helps us improve the dashboard.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.54902% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.70%. Comparing base (52eef79) to head (b8a31e7).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...c/OpenTelemetry.OpAmp.Client/Internal/OpAmpPipe.cs 95.45% 5 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4930      +/-   ##
==========================================
+ Coverage   77.56%   77.70%   +0.13%     
==========================================
  Files         468      469       +1     
  Lines       19913    19885      -28     
==========================================
+ Hits        15445    15451       +6     
+ Misses       4468     4434      -34     
Flag Coverage Δ
unittests-OpAmp.Client 91.24% <97.54%> (+4.37%) ⬆️

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

Files with missing lines Coverage Δ
...penTelemetry.OpAmp.Client/Internal/FrameBuilder.cs 82.10% <100.00%> (+0.12%) ⬆️
...nTelemetry.OpAmp.Client/Internal/FrameProcessor.cs 87.32% <100.00%> (+1.84%) ⬆️
...etry.OpAmp.Client/Internal/MessageBuilderHelper.cs 100.00% <100.00%> (ø)
...ry.OpAmp.Client/Internal/OpAmpClientEventSource.cs 83.58% <100.00%> (+36.58%) ⬆️
...nt/Internal/Services/Heartbeat/HeartbeatService.cs 91.22% <100.00%> (-5.33%) ⬇️
src/OpenTelemetry.OpAmp.Client/OpAmpClient.cs 96.55% <100.00%> (+3.07%) ⬆️
...ry.OpAmp.Client/Settings/IdentificationSettings.cs 100.00% <ø> (ø)
...c/OpenTelemetry.OpAmp.Client/Internal/OpAmpPipe.cs 95.45% <95.45%> (ø)

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@stevejgordon stevejgordon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The overall direction looks reasonable and the pipe model is the right approach to satisfy the HTTP transport spec requirement.

A few initial code comments to consider. I will do another pass soon.

this.TryFlush();
}

public Task FlushAsync()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this accept a CancellationToken passed through from StopAsync?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, we discovered this as well in the internal overhaul to prevent hangs in the pipe. Using cancellation token here gives the control to user.

0b456cc

{
lock (this.frameLock)
{
this.isBusy = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is correct for the "happy path", but what if the the web socket connection is closed or dropped? I think we'd need a mechanism for WsReciever to notify through when that happens so isBusy can be reset?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, retry / reconnect paths are missing currently intentionally. Seems a larger scope to focus on separately.

public async Task StopAsync(CancellationToken token = default)
{
// Drain queued data.
await this.FlushAsync()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pass the CancellationToken here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this.AppendMessage(MessageBuilderHelper.AppendAgentDisconnect);

// Send disconnect.
await this.FlushAsync()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IFrameBuilder AddCustomMessage(string capability, string type, ReadOnlyMemory<byte> data);

AgentToServer Build();
IFrameBuilder Clear();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this belong on this interface? It's beyond the concern of this abstraction. It seems to only be used from a test and could still existing on the FrameBuilder directly for that call site.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Seems it became a leftover, removed.

.ConfigureAwait(false);
}

this.AppendMessage(MessageBuilderHelper.AppendIdentification);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we flush here to ensure identification is sent before heartbeats (and other services) are started? Otherwise, a lost identification message will cause heartbeats to be sent to a server it had never seen.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TryFlush should send it instantly since the pipe is initially free. I added Flush just in case, so nothing weird should not happen.

internal sealed class FrameProcessor
{
private readonly ConcurrentDictionary<Type, IReadOnlyList<object>> listeners = [];
private readonly ConcurrentBag<Action<ServerToAgent>> internalListeners = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this need to be ConcurrentBag? One one internal listener is accepted in the ctor, can we just store that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The only issue today is that the pipe does not have a control over the processor (it does not construct it). Pipe itself is a user, like any internal services could be.

Comment thread src/OpenTelemetry.OpAmp.Client/Internal/OpAmpPipe.cs Outdated
@Kielek
Kielek requested a review from stevejgordon August 13, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:opamp.client Things related to OpenTelemetry.OpAmp.Client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants