Skip to content

Resque::Job instrumentation never flushing to OTLP when using BatchSpanProcessor #942

Description

@tlubz

Description of the bug

Resque::Job instrumentation never pushes spans to the Exporter when using BatchSpanProcessor.
The same setup with SimpleSpanProcessor does work.

Share details about your runtime

Operating system details: Macos Sonoma 14.3.1 (23D60)
RUBY_ENGINE: "ruby"
RUBY_VERSION: "2.7.6"
RUBY_DESCRIPTION: "ruby 2.7.6p219 (2022-04-12 revision c9c2245c0a) [x86_64-darwin22]"

Share a simplified reproduction if possible

# example.rb
require 'bundler/inline'
require 'bundler'

gemfile(true) do
  source 'https://rubygems.org'

  gem 'opentelemetry-api'
  gem 'opentelemetry-sdk'

  gem 'resque', '2.0.0'
  gem 'redis', '4.6.0'
  gem 'opentelemetry-instrumentation-resque', '0.3.1'
end

require 'opentelemetry-api'
require 'opentelemetry-sdk'

class SummarizingExporter < OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter
  def export(spans, timeout: nil)
    return OpenTelemetry::SDK::Trace::Export::FAILURE if @stopped

    Array(spans).each { |s| summarize_span(s) }

    OpenTelemetry::SDK::Trace::Export::SUCCESS
  end

  def summarize_span(span)
    puts "Span(#{span.name}, #{span.attributes.inspect})"
  end
end

exporter = SummarizingExporter.new

use_batch = ENV['PROCESSOR_TYPE'] != 'simple' # default to batched

OpenTelemetry::SDK.configure do |c|
  c.use('OpenTelemetry::Instrumentation::Resque', span_naming: :job_class)

  if use_batch
    c.add_span_processor(
      OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
        exporter,
        schedule_delay: 500 #ms
      )
    )
  else 
    c.add_span_processor(
      OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
        exporter
      )
    )
  end
end

MainTracer = OpenTelemetry.tracer_provider.tracer('example')

class ExampleJob
  @queue = 'example'

  def self.perform(arg)
    MainTracer.in_span('foo') { puts "performing ExampleJob with #{arg}" }
  end
end

require 'redis'
require 'resque'

Redis.silence_deprecations = true

Resque.enqueue(ExampleJob, 12345)

Resque::Worker.new('example').work_one_job

sleep(1) # wait for more than batch processor interval

OpenTelemetry.tracer_provider.shutdown

puts "Finished."

expected behavior:

→ PROCESSOR_TYPE=simple ruby example.rb 
...
I, [2024-04-23T11:45:27.080842 #88592]  INFO -- : Instrumentation: OpenTelemetry::Instrumentation::Resque was successfully installed with the following options {:span_naming=>:job_class, :propagation_style=>:link}
Span(ExampleJob send, {"messaging.system"=>"resque", "messaging.destination"=>"example", "messaging.destination_kind"=>"queue", "messaging.resque.job_class"=>"ExampleJob"})
performing ExampleJob with 12345
Span(foo, {})
Span(ExampleJob process, {"messaging.system"=>"resque", "messaging.destination"=>"example", "messaging.destination_kind"=>"queue", "messaging.resque.job_class"=>"ExampleJob"})
Finished.

note 3 spans are exported, including foo and ExampleJob process

actual behavior:

→ PROCESSOR_TYPE=batch ruby example.rb 
...
I, [2024-04-23T11:45:27.080842 #88592]  INFO -- : Instrumentation: OpenTelemetry::Instrumentation::Resque was successfully installed with the following options {:span_naming=>:job_class, :propagation_style=>:link}
performing ExampleJob with 12345
Span(ExampleJob send, {"messaging.system"=>"resque", "messaging.destination"=>"example", "messaging.destination_kind"=>"queue", "messaging.resque.job_class"=>"ExampleJob"})
Finished.

note, only the ExampleJob send span is exported

Notes

I also saw some similar behavior when using SimpleSpanProcessor with InMemorySpanExporter which also uses mutexes to lock resources 🤔 ... perhaps this bug related to thread/multiprocess safety. resque does some strange things with concurrency in its workers. at least in the version above, the default is to fork for every job.

setting FORK_PER_JOB=false in the env actually gives the correct result, but that's not generally desirable, as forking gives you better job isolation, protecting against memory space pollution.

The same behavior happens in the latest version of resque as of this writing (v2.6.0).

I wasn't able to test on ruby 3 due to my local environment being a little borked, but we are stuck on ruby 2.7.6 for now anyway.

Would be nice for this to Just Work out of the box, but in the mean time, if there is a workaround to allow this to export correctly even with FORK_PER_JOB enabled, that would be appreciated.

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions