Skip to content

Commit 2d72e93

Browse files
committed
Skip CPU time instrumentation if incompatible rollbar is detected
When I initially worked on #1300 ("Warn on incompatible rollbar version") my understanding of the issue on rollbar/rollbar-gem#1018 was incomplete and I did not realize that old rollbar versions, beyond breaking datadog profiler's CPU time instrumentation, could actually break the customer's application. One of our private beta customers mentioned he had seen both the warning but also had seen a stack trace: ``` [1] ! Unable to load application: SystemStackError: stack level too deep /usr/local/bundle/gems/rollbar-3.1.1/lib/rollbar/plugins/thread.rb:5:in `initialize_with_rollbar': stack level too deep (SystemStackError) from /usr/local/bundle/gems/ddtrace-0.45.0.feature.profiling.109457/lib/ddtrace/profiling/ext/cthread.rb:47:in `initialize' from /usr/local/bundle/gems/rollbar-3.1.1/lib/rollbar/plugins/thread.rb:6:in `initialize_with_rollbar' from /usr/local/bundle/gems/ddtrace-0.45.0.feature.profiling.109457/lib/ddtrace/profiling/ext/cthread.rb:47:in `initialize' ``` This led me to try to reproduce this issue. I discovered that with ```ruby ENV['DD_PROFILING_ENABLED'] = 'true' require 'ddtrace/profiling/preload' require 'rollbar' Rollbar.configure do |c| c.access_token = "nope" end Thread.new { }.join ``` I could trigger the issue, and that the current behavior was that the warning from #1300 would be printed, but then the application would proceed to fail. Having a bit more experience with the codebase now, I realize that the correct place to put this check is in the CPU extension #unsupported_reason method, which will ensure the extension is not loaded at all if an incompatible version of rollbar is around. The resulting behavior is that even with an old rollbar version, profiler will happily load and work; it will just omit the CPU time profiling, similarly to how it behaves on other platforms where for different reasons we don't support CPU time profiling.
1 parent d8b4722 commit 2d72e93

File tree

4 files changed

+39
-79
lines changed

4 files changed

+39
-79
lines changed

lib/ddtrace/profiling/ext/cpu.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ module Ext
55
module CPU
66
FFI_MINIMUM_VERSION = Gem::Version.new('1.0')
77

8+
# We cannot apply our CPU extension if a broken rollbar is around because that can cause customer apps to fail
9+
# with a SystemStackError: stack level too deep.
10+
#
11+
# This occurs whenever our extensions to Thread are applied BEFORE rollbar applies its own. This happens
12+
# because a loop forms: our extension tries to call Thread#initialize, but it's intercepted by rollbar, which
13+
# then tries to call the original Thread#initialize as well, but instead alls our extension, leading to stack
14+
# exhaustion.
15+
#
16+
# See https://github.com/rollbar/rollbar-gem/pull/1018 for more details on the issue
17+
ROLLBAR_INCOMPATIBLE_VERSIONS = Gem::Requirement.new('<= 3.1.1')
18+
819
def self.supported?
920
unsupported_reason.nil?
1021
end
@@ -37,6 +48,10 @@ def self.unsupported_reason
3748
elsif Gem.loaded_specs['ffi'].version < FFI_MINIMUM_VERSION
3849
'Your ffi gem dependency is too old; ensure that you have ffi >= 1.0 by ' \
3950
"adding `gem 'ffi', '~> 1.0'` to your Gemfile or gems.rb file"
51+
elsif Gem::Specification.find_all_by_name('rollbar', ROLLBAR_INCOMPATIBLE_VERSIONS).any?
52+
'You have an incompatible rollbar gem version installed; ensure that you have rollbar >= 3.1.2 by ' \
53+
"adding `gem 'rollbar', '>= 3.1.2'` to your Gemfile or gems.rb file. " \
54+
'See https://github.com/rollbar/rollbar-gem/pull/1018 for details.'
4055
end
4156
end
4257
end

lib/ddtrace/profiling/tasks/setup.rb

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ module Tasks
99
# Takes care of loading our extensions/monkey patches and starting up profiler
1010
class Setup
1111
def run
12-
check_warnings!
1312
activate_main_extensions
1413
autostart_profiler
1514
end
@@ -80,22 +79,6 @@ def autostart_profiler
8079
log "[DDTRACE] Could not autostart profiling. Cause: #{e.message} Location: #{e.backtrace.first}"
8180
end
8281

83-
def check_warnings!
84-
warn_if_incompatible_rollbar_gem_detected
85-
end
86-
87-
# See https://github.com/rollbar/rollbar-gem/pull/1018 for details on the incompatibility
88-
def warn_if_incompatible_rollbar_gem_detected
89-
incompatible_rollbar_versions = Gem::Requirement.new('<= 3.1.1')
90-
91-
if Gem::Specification.find_all_by_name('rollbar', incompatible_rollbar_versions).any?
92-
log "[DDTRACE] Incompatible version of the rollbar gem is installed (#{incompatible_rollbar_versions}). " \
93-
'Loading this version of the rollbar gem will disable ddtrace\'s CPU profiling. ' \
94-
'Please upgrade to the latest rollbar version. ' \
95-
'See https://github.com/rollbar/rollbar-gem/pull/1018 for details.'
96-
end
97-
end
98-
9982
private
10083

10184
def log(message)

spec/ddtrace/profiling/ext/cpu_spec.rb

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,29 @@
7474
include_context 'loaded gems',
7575
ffi: described_class::FFI_MINIMUM_VERSION
7676

77-
it { is_expected.to be nil }
77+
let(:last_version_of_rollbar_affected) { '3.1.1' }
78+
79+
context 'when incompatible rollbar gem is installed' do
80+
before do
81+
expect(Gem::Specification)
82+
.to receive(:find_all_by_name)
83+
.with('rollbar', Gem::Requirement.new("<= #{last_version_of_rollbar_affected}"))
84+
.and_return([instance_double(Gem::Specification), instance_double(Gem::Specification)])
85+
end
86+
87+
it { is_expected.to include 'rollbar >= 3.1.2'}
88+
end
89+
90+
context 'when compatible rollbar gem is installed or no version at all is installed' do
91+
before do
92+
expect(Gem::Specification)
93+
.to receive(:find_all_by_name)
94+
.with('rollbar', Gem::Requirement.new("<= #{last_version_of_rollbar_affected}"))
95+
.and_return([]) # Because we search with a <= requirement, not installed/compatible version installed both lead to empty return here
96+
end
97+
98+
it { is_expected.to be nil }
99+
end
78100
end
79101
end
80102
end
@@ -89,7 +111,7 @@
89111
before { stub_const('Thread', ::Thread.dup) }
90112

91113
context 'when native CPU time is supported' do
92-
before { skip 'CPU profiling not supported' unless described_class.supported? }
114+
before { skip 'CPU profiling not supported on current platform' unless described_class.supported? }
93115

94116
it 'adds Thread extensions' do
95117
apply!

spec/ddtrace/profiling/tasks/setup_spec.rb

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
subject(:run) { task.run }
1313

1414
it do
15-
expect(task).to receive(:check_warnings!).ordered
1615
expect(task).to receive(:activate_main_extensions).ordered
1716
expect(task).to receive(:autostart_profiler).ordered
1817
run
@@ -347,63 +346,4 @@
347346
end
348347
end
349348
end
350-
351-
describe '#check_warnings!' do
352-
subject(:check_warnings!) { task.check_warnings! }
353-
354-
it do
355-
expect(task).to receive(:warn_if_incompatible_rollbar_gem_detected)
356-
357-
check_warnings!
358-
end
359-
end
360-
361-
describe '#warn_if_incompatible_rollbar_gem_detected' do
362-
subject(:warn_if_incompatible_rollbar_gem_detected) { task.warn_if_incompatible_rollbar_gem_detected }
363-
364-
let(:last_version_of_rollbar_affected) { '3.1.1' }
365-
366-
before do
367-
# Simulate the result of the gem apis, so that we can check different combinations of having or not having the
368-
# rollbar gem and affected versions
369-
expect(Gem::Specification)
370-
.to receive(:find_all_by_name)
371-
.with('rollbar', Gem::Requirement.new("<= #{last_version_of_rollbar_affected}"))
372-
.and_return(rollbar_versions_found)
373-
end
374-
375-
context 'when rollbar gem is not installed' do
376-
let(:rollbar_versions_found) { [] }
377-
378-
it 'does not display a warning to STDOUT' do
379-
expect(STDOUT).to_not receive(:puts)
380-
381-
warn_if_incompatible_rollbar_gem_detected
382-
end
383-
end
384-
385-
context 'when compatible version of rollbar gem is installed' do
386-
# same as "no gem installed" because we use a version requirement when
387-
# calling find_all_by_name, so only incompatible versions get returned
388-
let(:rollbar_versions_found) { [] }
389-
390-
it 'does not display a warning to STDOUT' do
391-
expect(STDOUT).to_not receive(:puts)
392-
393-
warn_if_incompatible_rollbar_gem_detected
394-
end
395-
end
396-
397-
context 'when incompatible version of rollbar gem is installed' do
398-
let(:rollbar_versions_found) { [instance_double(Gem::Specification), instance_double(Gem::Specification)] }
399-
400-
it 'displays a warning to STDOUT' do
401-
expect(STDOUT).to receive(:puts) do |message|
402-
expect(message).to include('Incompatible version of the rollbar')
403-
end
404-
405-
warn_if_incompatible_rollbar_gem_detected
406-
end
407-
end
408-
end
409349
end

0 commit comments

Comments
 (0)