Skip to content

Azure Service Bus: HostName NREs for credential-based connections, taking the whole ServiceCapabilities snapshot with it #3740

Description

@jeremydmiller

From a user error report (CritterWatch monitoring a Wolverine 6.24.0 service on JasperFx 2.36.2):

System.Reflection.TargetInvocationException:
   at JasperFx.Descriptors.OptionsValue.Read (JasperFx/Descriptors/OptionsValue.cs:53)
   at JasperFx.Descriptors.OptionsDescription.readProperties (JasperFx/Descriptors/OptionsDescription.cs:156)
   at Wolverine.Transports.TransportBase`1.TryBuildBrokerUsage (Wolverine/Transports/TransportBase.cs:22)
   at Wolverine.Configuration.Capabilities.ServiceCapabilities.readTransports (ServiceCapabilities.cs:456)
   at Wolverine.Configuration.Capabilities.ServiceCapabilities+<ReadFrom>d__74.MoveNext (ServiceCapabilities.cs:161)
   at Wolverine.CritterWatch.CritterWatchObserver+<processBatch>d__27.MoveNext (CritterWatchObserver.cs:144)
Inner exception System.NullReferenceException handled at System.Reflection.RuntimeMethodInfo.Invoke:
   at Wolverine.AzureServiceBus.AzureServiceBusTransport.get_HostName (AzureServiceBusTransport.cs:280)

Root cause

AzureServiceBusTransport.HostName parses the host out of the connection string via ConnectionString!.Split(';'). For any credential-based connection — FullyQualifiedNamespace plus a TokenCredential / AzureNamedKeyCredential / AzureSasCredential, i.e. the normal managed-identity setup — there is no connection string at all, so the getter throws a NullReferenceException.

HostName is a public property, and BrokerDescription (via JasperFx's OptionsDescription) reads every public property reflectively, so this fires while building the ServiceCapabilities diagnostic snapshot. Two knock-on effects:

  • The public BusClient / ManagementClient properties are also read reflectively, which lazily constructs a live Azure client purely as a side effect of describing configuration — and throws ArgumentNullException when nothing is configured yet.
  • PulsarTransport's this[Uri] indexer has the same problem from a different direction: PropertyInfo.GetValue(subject) on an indexer throws TargetParameterCountException, so applications using Pulsar can't build a snapshot either.

Impact

ServiceCapabilities.ReadFrom is all-or-nothing, so one bad getter loses the entire snapshot. CritterWatch retries on every batch and hits the same wall, so the service heartbeats forever without ever completing a handshake — no endpoints, no message types, no stores, nothing.

Fix

  1. HostName returns the parsed connection-string host when there is one, otherwise falls back to FullyQualifiedNamespace (which is the host name for credential-based connections), and is null only when nothing at all is configured. Also fixes the IMassTransitInteropEndpoint.MassTransitUri() implementations, which interpolate HostName.
  2. [IgnoreDescription] on BusClient / ManagementClient (live runtime objects, not configuration — NATS and Pulsar already do this) and on the Pulsar indexer.
  3. Harden ServiceCapabilities.ReadFrom: each section, plus each individual transport and endpoint, is now best effort — a failure is logged and skipped instead of aborting the snapshot. Cancellation still propagates.

Companion hardening in JasperFx so a throwing getter degrades to a "could not be read" value rather than an exception: JasperFx/jasperfx#590

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions