Skip to content

Commit

Permalink
issue #487: add listen/thread and spec
Browse files Browse the repository at this point in the history
  • Loading branch information
ColinDKelley committed Oct 30, 2020
1 parent 388bcab commit 12e0d07
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 0 deletions.
43 changes: 43 additions & 0 deletions lib/listen/thread.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'thread'

require_relative 'logger'

module Listen
module Thread
class << self
# Creates a new thread with the given name.
# Any exceptions raised by the thread will be logged with the thread name and complete backtrace.
def new(name)
thread_name = "listen-#{name}"

caller_stack = caller
::Thread.new do
yield
rescue Exception => ex
_log_exception(ex, thread_name, caller_stack)
nil
end.tap do |thread|
thread.name = thread_name
end
end

private

def _log_exception(ex, thread_name, caller_stack)
complete_backtrace = [*ex.backtrace, "--- Thread.new ---", *caller_stack]
message = "Exception rescued in #{thread_name}:\n#{_exception_with_causes(ex)}\n#{complete_backtrace * "\n"}"
Listen::Logger.error(message)
end

def _exception_with_causes(ex)
result = +"#{ex.class}: #{ex}"
if ex.cause
result << "\n"
result << "--- Caused by: ---\n"
result << _exception_with_causes(ex.cause)
end
result
end
end
end
end
54 changes: 54 additions & 0 deletions spec/lib/listen/thread_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'listen/thread'

RSpec.describe Listen::Thread do
let(:name) { "worker_thread" }
let(:block) { -> { } }
subject { described_class.new(name, &block) }

it "calls Thread.new" do
expect(Thread).to receive(:new) do
thread = instance_double(Thread, "thread")
expect(thread).to receive(:name=).with("listen-#{name}")
thread
end
subject
end

context "when exception raised" do
let(:block) do
-> { raise ArgumentError, 'boom!' }
end

it "rescues and logs exceptions" do
expect(Listen::Logger).to receive(:error)
.with(/Exception rescued in listen-worker_thread:\nArgumentError: boom!\n.*\/listen\/thread_spec\.rb/)
subject.join
end

it "rescues and logs backtrace + exception backtrace" do
expect(Listen::Logger).to receive(:error)
.with(/Exception rescued in listen-worker_thread:\nArgumentError: boom!\n.*\/listen\/thread\.rb.*--- Thread.new ---.*\/listen\/thread_spec\.rb/m)
subject.join
end
end

context "when nested exceptions raised" do
let(:block) do
-> do
begin
raise ArgumentError, 'boom!'
rescue
raise 'nested inner'
end
rescue
raise 'nested outer'
end
end

it "details exception causes" do
expect(Listen::Logger).to receive(:error)
.with(/RuntimeError: nested outer\n--- Caused by: ---\nRuntimeError: nested inner\n--- Caused by: ---\nArgumentError: boom!/)
subject.join
end
end
end

0 comments on commit 12e0d07

Please sign in to comment.