| File: Annotations\OtlpDevTunnelConfigurationAnnotation.cs | Web Access |
| Project: src\src\Aspire.Hosting.Maui\Aspire.Hosting.Maui.csproj (Aspire.Hosting.Maui) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.DevTunnels; using Aspire.Hosting.Maui.Otlp; namespace Aspire.Hosting.Maui.Annotations; /// <summary> /// Annotation that stores the OTLP dev tunnel configuration for a MAUI project. /// This allows sharing a single dev tunnel infrastructure across multiple platform resources. /// </summary> internal sealed class OtlpDevTunnelConfigurationAnnotation : IResourceAnnotation { private readonly object _otlpEndpointLock = new(); private readonly DevTunnelPortOptions _tunnelPortOptions; private int _isOtlpEndpointResolved; /// <summary> /// The OTLP loopback stub resource that acts as the service discovery target. /// </summary> public OtlpLoopbackResource OtlpStub { get; } /// <summary> /// The resource builder for the OTLP stub (used for WithReference calls). /// </summary> public IResourceBuilder<OtlpLoopbackResource> OtlpStubBuilder { get; } /// <summary> /// The dev tunnel resource that tunnels the OTLP endpoint. /// </summary> public IResourceBuilder<DevTunnelResource> DevTunnel { get; } /// <summary> /// The public dev tunnel endpoint used by MAUI platform environment variables. /// </summary> public EndpointReference TunnelEndpoint { get; } /// <summary> /// Gets a value indicating whether the dashboard OTLP listener has been resolved. /// </summary> public bool IsOtlpEndpointResolved => Volatile.Read(ref _isOtlpEndpointResolved) != 0; /// <summary> /// Gets or sets the maximum time to wait for DCP to publish the dashboard's concrete OTLP listener. /// </summary> internal TimeSpan RuntimeSnapshotResolutionTimeout { get; set; } = TimeSpan.FromMinutes(2); public OtlpDevTunnelConfigurationAnnotation( OtlpLoopbackResource otlpStub, IResourceBuilder<OtlpLoopbackResource> otlpStubBuilder, IResourceBuilder<DevTunnelResource> devTunnel, DevTunnelPortOptions tunnelPortOptions, EndpointReference tunnelEndpoint, bool isOtlpEndpointResolved) { OtlpStub = otlpStub; OtlpStubBuilder = otlpStubBuilder; DevTunnel = devTunnel; _tunnelPortOptions = tunnelPortOptions; TunnelEndpoint = tunnelEndpoint; _isOtlpEndpointResolved = isOtlpEndpointResolved ? 1 : 0; } internal bool UpdateOtlpEndpoint(string scheme, int port, string transport) { lock (_otlpEndpointLock) { if (_isOtlpEndpointResolved != 0) { return false; } var endpoint = OtlpStub.OtlpEndpoint; _tunnelPortOptions.Protocol = scheme; endpoint.UriScheme = scheme; endpoint.Port = port; endpoint.TargetPort = port; endpoint.Transport = transport; endpoint.AllocatedEndpoint = new AllocatedEndpoint(endpoint, "localhost", port); Volatile.Write(ref _isOtlpEndpointResolved, 1); return true; } } internal bool TryFailOtlpEndpointResolution(Exception exception) { ArgumentNullException.ThrowIfNull(exception); lock (_otlpEndpointLock) { if (_isOtlpEndpointResolved != 0) { return false; } // DCP can resolve MAUI environment variables while the tunnel startup callback is // still waiting for the dashboard listener. Fault the synthetic target so the // failure-aware endpoint and protocol providers stop waiting. Do not fault the public // DevTunnel endpoint because its lifecycle reads the snapshot before replacing it on retry. #pragma warning disable CS0618 // Type or member is obsolete OtlpStub.OtlpEndpoint.AllocatedEndpointSnapshot.SetException(exception); #pragma warning restore CS0618 // Type or member is obsolete return true; } } }