Skip to content

Commit

Permalink
filters/thread_filter: don't serialise IO objects
Browse files Browse the repository at this point in the history
Fixes #204 (Airbrake gem segfaults on Circle CI)

I stumbled upon an edge case while running the thread filter against a
Rails app. The app would stuck because `io_obj.to_json` would never
return. The `io_obj` was open. Closed IO objects raise IOError, which
we already handle.

To make sure we avoid any kind of segfaults, we ignore
`:__recursive_key__`, which holds data for PrettyPrint.
  • Loading branch information
kyrylo committed May 4, 2017
1 parent 6de77d1 commit b7c554f
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Airbrake Ruby Changelog

### master

* Fixed segfault on Ruby 2.1 while using the thread filter
([#206](https://github.com/airbrake/airbrake-ruby/pull/206))

### [v2.2.0][v2.2.0] (May 1, 2017)

* Make `notify/notify_sync` accept a block, which yields an `Airbrake::Notice`
Expand Down
9 changes: 7 additions & 2 deletions lib/airbrake-ruby/filters/thread_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,17 @@ def call(notice)

def thread_variables(th)
th.thread_variables.map.with_object({}) do |var, h|
h[var] = th.thread_variable_get(var).inspect
next if (value = th.thread_variable_get(var)).is_a?(IO)
h[var] = value
end
end

def fiber_variables(th)
th.keys.map.with_object({}) { |key, h| h[key] = th[key].inspect }
th.keys.map.with_object({}) do |key, h|
next if key == :__recursive_key__
next if (value = th[key]).is_a?(IO)
h[key] = value
end
end

def add_thread_info(th, thread_info)
Expand Down
32 changes: 30 additions & 2 deletions spec/filters/thread_filter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
subject.call(notice)
th.thread_variable_set(:bingo, nil)

expect(notice[:params][:thread][:thread_variables][:bingo]).to eq(':bango')
expect(notice[:params][:thread][:thread_variables][:bingo]).to eq(:bango)
end

it "appends fiber variables" do
th[:bingo] = :bango
subject.call(notice)
th[:bingo] = nil

expect(notice[:params][:thread][:fiber_variables][:bingo]).to eq(':bango')
expect(notice[:params][:thread][:fiber_variables][:bingo]).to eq(:bango)
end

it "appends name", skip: !Thread.current.respond_to?(:name) do
Expand Down Expand Up @@ -49,4 +49,32 @@
subject.call(notice)
expect(notice[:params][:thread][:safe_level]).to eq(0)
end

context "when an IO-like object is stored" do
let(:io_obj) do
Class.new(IO) do
def initialize; end
end.new
end

before do
expect(io_obj).to be_is_a(IO)
end

it "doesn't append the IO object to thread variables" do
th.thread_variable_set(:io, io_obj)
subject.call(notice)
th.thread_variable_set(:io, nil)

expect(notice[:params][:thread][:thread_variables][:io]).to be_nil
end

it "doesn't append the IO object to thread variables" do
th[:io] = io_obj
subject.call(notice)
th[:io] = nil

expect(notice[:params][:thread][:fiber_variables][:io]).to be_nil
end
end
end

0 comments on commit b7c554f

Please sign in to comment.