Skip to content

HTTPX instrumentation crashes with NoMethodError: undefined method 'add_request_headers' for nil when start_external_request_segment returns nil #3509

@thebravoman

Description

@thebravoman

Versions:

  • newrelic_rpm 9.23.0 (also present in 10.2.0 — code is identical)
  • httpx 1.7.5
  • Ruby 3.4.4
  • Passenger (multi-threaded)

Description:

nr_start_segment in lib/new_relic/agent/instrumentation/httpx/instrumentation.rb calls start_external_request_segment which can return nil when the internal rescue => exception catches an error. The return value is then used on line 26 without a nil check:

def nr_start_segment(request)
  return unless NewRelic::Agent::Tracer.state.is_execution_traced?

  wrapped_request = NewRelic::Agent::HTTPClients::HTTPXHTTPRequest.new(request)
  segment = NewRelic::Agent::Tracer.start_external_request_segment(
    library: wrapped_request.type,
    uri: wrapped_request.uri,
    procedure: wrapped_request.method
  )
  segment.add_request_headers(wrapped_request)  # <-- crashes when segment is nil
  # ...
end

start_external_request_segment in tracer.rb has:

def start_external_request_segment(library:, uri:, procedure:, start_time: nil, parent: nil)
  segment = Transaction::ExternalRequestSegment.new(library, uri, procedure, start_time)
  start_and_add_segment(segment, parent)
rescue ArgumentError
  raise
rescue => exception
  log_error('start_external_request_segment', exception)
  # implicitly returns nil
end

When any exception occurs during segment creation (e.g. URI parsing edge case, threading issue), the rescue swallows it, logs it, and returns nil. The HTTPX instrumentation doesn't guard against this.

Error:

NoMethodError: undefined method 'add_request_headers' for nil
  newrelic_rpm-9.23.0/lib/new_relic/agent/instrumentation/httpx/instrumentation.rb:26:in 'nr_start_segment'
  newrelic_rpm-9.23.0/lib/new_relic/agent/instrumentation/httpx/instrumentation.rb:13:in 'block in send_requests_with_tracing'
  newrelic_rpm-9.23.0/lib/new_relic/agent/instrumentation/httpx/instrumentation.rb:13:in 'Array#each'
  newrelic_rpm-9.23.0/lib/new_relic/agent/instrumentation/httpx/instrumentation.rb:13:in 'send_requests_with_tracing'
  newrelic_rpm-9.23.0/lib/new_relic/agent/instrumentation/httpx/prepend.rb:11:in 'send_requests'

Context: This occurs in a multi-threaded Passenger environment making concurrent HTTPX requests. The actual HTTP requests succeed (fired pixels are stored), but New Relic's instrumentation crashes during tracing setup.

Suggested fix:

Add a nil guard in nr_start_segment:

def nr_start_segment(request)
  return unless NewRelic::Agent::Tracer.state.is_execution_traced?

  wrapped_request = NewRelic::Agent::HTTPClients::HTTPXHTTPRequest.new(request)
  segment = NewRelic::Agent::Tracer.start_external_request_segment(
    library: wrapped_request.type,
    uri: wrapped_request.uri,
    procedure: wrapped_request.method
  )
  return unless segment  # <-- guard against nil

  segment.add_request_headers(wrapped_request)
  request.on(:response) { nr_finish_segment.call(request, segment) }
end

Workaround: Disable HTTPX instrumentation in newrelic.yml:

instrumentation:
  httpx: disabled

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugcommunityTo tag external issues and PRs submitted by the community

    Type

    No type

    Projects

    Status

    Code Complete/Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions