Note: If you're using old Errbit version (0.2.0, 0.4.0) and an old Airbrake version (v4) please refer to this manual to make it work with self-signed certificates.
Having an error catcher like Errbit behind SSL is generally a good idea. Especially when Errbit is hosted on a different server than you application (for example when you manage multiple apps with one Errbit instance). In many cases you will have a self-signed certificate (why would you pay for a cert for internal tool). If you try to use it with Airbrake, you will see following error:
Unfortunately, global SSL certificates verification disabling hack (solution that used to work with Airbrake notifier v4) won't work:
# No longer working!
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
Luckily, Airbrake notifier is written pretty well, so hacking it (and disabling per request SSL certificate verification) is not hard at all. Here's a full code you need to place in config/initializers/errbit.rb to make it work:
module Patches
module Airbrake
module SyncSender
def build_https(uri)
super.tap do |req|
req.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
end
end
end
end
Airbrake::SyncSender.prepend(::Patches::Airbrake::SyncSender)
After that (and configuring Airbrake notifier), you can test it out like this:
If your exceptions aren't exceptions but expectations, you're doing it wrong. Here's an example what programmers tend to do:
def validate_status(user)
case user.status
when 'active' then return user
when 'inactive' then fail InactiveUserError
when 'invalid' then fail InvalidUserError
when 'deleted' then fail DeletedUserError
else
fail UnknownUserStatusError
end
end
begin
validate_status(user)
rescue InactiveUserError
# do something
rescue InvalidUserError
# do something else
rescue DeletedUserError
# do something else 2
rescue UnknownUserStatusError
# do something else 3
end
I've seen also few cases, when exceptions parameters were used to pass objects that the programmer was later on working with!
As you can see, the whole flow of this piece of code is handled with exceptions. In this post I will focus on a performance reason why it is bad, (but if you're interested in how to refactor code like this, at the end of this post you will find some external links about that. That's why I've prepared a simple benchmark
require 'benchmark'
elements = [0, 1]
big_ar = (1..10000).to_a
TIMES = 100000
Benchmark.bmbm do |x|
x.report('break') do
TIMES.times do
elements.each do |i|
break
end
end
end
x.report('catch throw') do
TIMES.times do
catch(:benchmarking) do
elements.each do |i|
throw(:benchmarking)
end
end
end
end
x.report('catch throw heavy') do
TIMES.times do
catch(:benchmarking) do
elements.each do |i|
throw(:benchmarking, big_ar)
end
end
end
end
x.report('fail') do
TIMES.times do
begin
elements.each do |i|
fail StandardError
end
rescue
end
end
end
x.report('fail heavy') do
TIMES.times do
begin
elements.each do |i|
fail StandardError, big_ar, {}
end
rescue
end
end
end
x.report('raise') do
TIMES.times do
begin
elements.each do |i|
raise StandardError
end
rescue
end
end
end
x.report('raise heavy') do
TIMES.times do
begin
elements.each do |i|
raise StandardError, big_ar, {}
end
rescue
end
end
end
end
ruby benchmark.rb
user system total real
break 0.040000 0.000000 0.040000 ( 0.040243)
catch throw 0.080000 0.000000 0.080000 ( 0.082100)
catch throw heavy 0.080000 0.000000 0.080000 ( 0.082422)
fail 0.300000 0.000000 0.300000 ( 0.298863)
fail heavy 0.470000 0.000000 0.470000 ( 0.476829)
raise 0.300000 0.000000 0.300000 ( 0.305635)
raise heavy 0.480000 0.000000 0.480000 ( 0.475377)
And this is how it looks on a chart:
Based on this benchmark we can see following things:
Catch/throw performance is not influenced by the size of passed attribute - it doesn't matter if we pass a huge structure or a simple object
Performance of fail and raise is almost equal, for both normal and heavy case
Fail/raise can be up to 12 times slower than break
Fail/raise can be up to 6 times slower than catch/throw
So, from the performance point of view, handling flow with exceptions can be much more expensive than in other ways. Exceptions are heavy because they are exceptions. They aren't suppose to happen all the time, that's why the implementers of compilers nor the designers of the language focus on their performance.
Refactoring
If you've noticed code like this in your apps, here are some great blog posts on how to fix that: