Bug: Do not serialize gigantic objects
johnnyshields opened this issue · 7 comments
Describe the bug
If you make an object with a large instance variable, then raise an error, Bugsnag will attempt to serialize the large instance variable with no regard to memory. This caused OOM errors on my machine. There should be some timeout or reasonable limit here.
Steps to reproduce
# assuming Bugsnag installed...
class BigFoo
def initialize
@big_data = "a" * 10_000_000_000
end
def raise_an_error
raise RuntimeError
end
end
BigFoo.new.raise_an_error
Environment
- Ruby version: 3.0
- Bugsnag version: 6.23.0
Hi @johnnyshields - I think you originally opened this in bugsnag-api-ruby
by mistake so I transferred the issue to bugsnag-ruby
instead.
I don't think there's an issue with Bugsnag here, however. I think the OOM will be occurring when the instance is created (i.e. on BigFoo.new
), which has nothing to do with Bugsnag, nor will Bugsnag be trying to serialize anything at this point. In fact - Bugsnag shouldn't be serializing this variable at all, unless you're adding it as metadata somewhere else in your code?
I haven't investigated in detail, however, in our app we had a report job object that contains large instance variables. The app was completely fine until a Bugsnag occured (i.e. after instance variables were populated with data), at which point we got OOM presumably because Bugsnag was reporting the instance variables.
@bomberby can you provide more info here?
A closer code representation will be something along these line
class DataObject
# Heavy object with many child classes
def inspect
'a' * 100
end
end
class BigFoo
def initialize
@big_data = []
10_000_000.times do
@big_data << DataObject.new
end
end
def raise_an_error
bad_method_exception
end
end
BigFoo.new.raise_an_error
The class that raise the exception has thousands of activerecord models, so when exception is reported, all child objects are added to the report.
Hi @bomberby
We're struggling to reproduce the issue you're seeing, even with your latest code snippets. Are you saying Bugsnag is automatically capturing this data with a default configuration? I think we need to understand exactly what you're doing that causes these large instance variables to be added to the report.
Can you share a more complete reproduction example?
When exception is raised, when resolving the creator class, it will call inspect
to all embedded classes individually.
In this case DataObject
will receive an inspect
message, if that object's inspect
is time consuming(such as having it's own embedded object that is also heavy and take a while to serialize), this can easily get out of hand if the exception caller had tens of thousands of objects, and each object is a full database model with ability and data, and perhaps even embedded models.
@bomberby, if you removed Bugsnag from the equation, the call to BigFoo.new.raise_an_error
would still call initialize
, and therefore iterate 1M times over DataObject
. The only way this would get added to your Bugsnag error report and serialized is if you told Bugsnag to do so, e.g. by adding the instance variable @big_data
to your reports.
If you've a code snippet that suggest otherwise, please do let us know. If you run:
# configure Bugsnag here...
class DataObject
# Heavy object with many child classes
def inspect
'a' * 100
end
end
class BigFoo
def initialize
@big_data = []
10.times do
@big_data.append(DataObject.new)
end
end
def raise_an_error
bad_method_exception
end
end
b = BigFoo.new
puts b.instance_variable_get(:@big_data)
### Optionally add @big_data to a Bugsnag report
# Bugsnag.add_metadata(:big_data, {
# big_data: b.instance_variable_get(:@big_data).inspect
# })
b.raise_an_error
You'll see the output that @big_data
exists...
> $ bundle exec ruby big-objects.rb ⬡ 14.15.4 [±master ●●]
#<DataObject:0x00007f96ff20f498>
#<DataObject:0x00007f96ff20f420>
#<DataObject:0x00007f96ff20f3f8>
#<DataObject:0x00007f96ff20f3d0>
#<DataObject:0x00007f96ff20f3a8>
#<DataObject:0x00007f96ff20f380>
#<DataObject:0x00007f96ff20f358>
#<DataObject:0x00007f96ff20f308>
#<DataObject:0x00007f96ff20f2e0>
#<DataObject:0x00007f96ff20f2b8>
** [Bugsnag] 2022-01-17 15:02:05 +0000: Notifying https://notify.bugsnag.com of NameError
** [Bugsnag] 2022-01-17 15:02:05 +0000: Waiting for 1 outstanding request(s)
Traceback (most recent call last):
1: from big-objects.rb:31:in `<main>'
big-objects.rb:25:in `raise_an_error': undefined local variable or method `bad_method_exception' for #<BigFoo:0x00007f96ff20f4e8> (NameError)
... but, it's not part of the Bugsnag payload
We're closing this now as we haven't been able to reproduce the problem, but if you can share an example we'd be happy to reopen and investigate further.