Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Raven fails when trying to capture an unhashable (mutable) exception #1278

Open
immerrr opened this issue Jul 27, 2018 · 0 comments
Open

Raven fails when trying to capture an unhashable (mutable) exception #1278

immerrr opened this issue Jul 27, 2018 · 0 comments

Comments

@immerrr
Copy link

immerrr commented Jul 27, 2018

I've run into this recently. After a rather short debugging session I've narrowed the culprit down to these lines:

https://github.com/immerrr/raven-python/blob/master/raven/events.py#L51-L52

The problem is that I'm using a custom exception class implemented over attrs that by default sets __hash__ = None as probably all sane mutable classes should do (some background in python-attrs/attrs#136):

In [11]: import attr
    ...: @attr.s
    ...: class MyException(Exception):
    ...:     f = attr.ib()
    ...: set([MyException('foo')])
    ...: 
    ...: 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-11-eb39bb116374> in <module>()
      3 class MyException(Exception):
      4     f = attr.ib()
----> 5 set([MyException('foo')])

TypeError: unhashable type: 'MyException'

I tried fixing this on attrs side by using frozen= which assures the library that the resulting class will be immutable and it's safe to generate a __hash__ function. But then I realized I'm attaching a dictionary to the exception and dictionaries are mutable themselves, so the result is still unhashable:

In [14]: import attr
    ...: @attr.s(frozen=True)
    ...: class MyException(Exception):
    ...:     f = attr.ib()
    ...: set([MyException({'foo': 'bar'})])
    ...: 
    ...: 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-16b4c8ca538e> in <module>()
      3 class MyException(Exception):
      4     f = attr.ib()
----> 5 set([MyException({'foo': 'bar'})])

<attrs generated hash 926d52d2b56cfd446b915894a46e5c7977941b62> in __hash__(self)
      2     return hash((
      3         -172549962914795092,
----> 4         self.f,
      5     ))
TypeError: unhashable type: 'dict'

Interestingly enough, builtin exceptions allow mutable fields:

In [16]: set([Exception({'foo': 'bar'})])
Out[16]: {Exception({'foo': 'bar'})}

But probably only because their hash function depends on id(...) and not on the content.

In [17]: set([Exception({'foo': 'bar'}), Exception({'foo': 'bar'})])
Out[17]: {Exception({'foo': 'bar'}), Exception({'foo': 'bar'})}

In [18]: set([Exception({'foo': 'bar'})]*2)
Out[18]: {Exception({'foo': 'bar'})}

So a really straighforward way to fix this would be to deduplicate exceptions only by their id. A slightly more convoluted one would be to create a EnsureHashable wrapper object that would try calculating the wrapped exception's hash falling back to object.__hash__ implementation. If that's OK I could send in a PR doing either of those two.

Also, it would be nice if raven reported some sort of a generic "Error when capturing exception" error so that sentry could send an alert. Given that my service didn't experience any connectivity problems, I expected raven/sentry duo to notify me about any problems via email. However, in this case it didn't work like that, and I discovered this problem while going through the logs on a completely unrelated matter.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant