Skip to content

Commit

Permalink
Merge cba5140 into 6db768c
Browse files Browse the repository at this point in the history
  • Loading branch information
chewi committed Mar 21, 2018
2 parents 6db768c + cba5140 commit 960a53c
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 98 deletions.
11 changes: 5 additions & 6 deletions .travis.yml
@@ -1,16 +1,15 @@
language: ruby
rvm:
- 2.2.2
- 2.3.0
- jruby-9.0.4.0
- 2.3.6
- 2.4.3
- jruby-9.1.16.0
- ruby-head
jdk:
- openjdk7 # for jruby
before_script: export JRUBY_OPTS="--server -J-Xss1024k -J-Xmx652m -J-XX:+UseConcMarkSweepGC"
- openjdk8 # for jruby
matrix:
allow_failures:
- rvm: ruby-head
env: ARUBA_TIMEOUT=120 AHN_ENV=development
env: ARUBA_TIMEOUT=120 AHN_ENV=development JRUBY_OPTS="--server -J-Xss1024k -J-Xmx652m -J-XX:+UseConcMarkSweepGC"
sudo: false
notifications:
slack:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,4 +1,8 @@
# [develop](https://github.com/adhearsion/adhearsion)
* Feature: Increases concurrent call routing performance by delegating call routing to Call actors
* Bugfix: Reporting statistics should not deadlock call processing
* Bugfix: Shuts down a call which couldn't be routed with a fake end event. This avoids leaking call actors in cases where the call ended before it was dispatched and we missed the real End event.
* Bugfix: Removes proactive checks on call availability before dispatching events because these are inaccurate; now optimistically dispatches.

# [3.0.0.rc1](https://github.com/adhearsion/adhearsion/compare/v3.0.0.beta2...v3.0.0.rc1) - [2016-01-07](https://rubygems.org/gems/adhearsion/versions/3.0.0.rc1)
* Bugfix: Concurrent access to call collection should not be permitted. See [#589](https://github.com/adhearsion/adhearsion/issues/589)
Expand Down
18 changes: 17 additions & 1 deletion lib/adhearsion/call.rb
Expand Up @@ -324,7 +324,7 @@ def answer(headers = nil)

def reject(reason = :busy, headers = nil)
write_and_await_response Adhearsion::Rayo::Command::Reject.new(:reason => reason, :headers => headers)
Adhearsion::Events.trigger_immediately :call_rejected, call: current_actor, reason: reason
Adhearsion::Events.trigger :call_rejected, call: current_actor, reason: reason
rescue Adhearsion::ProtocolError => e
abort e
end
Expand Down Expand Up @@ -501,6 +501,22 @@ def write_command(command)
client.execute_command command
end

def route
case Adhearsion::Process.state_name
when :booting, :rejecting
logger.info "Declining call because the process is not yet running."
reject :decline
when :running, :stopping
logger.info "Routing call"
Adhearsion.router.handle current_actor
else
reject :error
end
rescue Call::Hangup, Call::ExpiredError
logger.warn "Call routing could not be completed because call was unavailable."
self << Adhearsion::Event::End.new(reason: :error)
end

##
# Sends a message to the caller
#
Expand Down
2 changes: 1 addition & 1 deletion lib/adhearsion/outbound_call.rb
Expand Up @@ -99,7 +99,7 @@ def dial(to, options = {})
Adhearsion.active_calls << current_actor
Adhearsion.active_calls.delete(@id)
end
Adhearsion::Events.trigger_immediately :call_dialed, current_actor
Adhearsion::Events.trigger :call_dialed, current_actor
end
rescue
clear_from_active_calls
Expand Down
13 changes: 2 additions & 11 deletions lib/adhearsion/rayo/initializer.rb
Expand Up @@ -130,21 +130,12 @@ def dispatch_offer(offer)
catching_standard_errors do
call = Call.new(offer)
Adhearsion.active_calls << call
case Adhearsion::Process.state_name
when :booting, :rejecting
logger.info "Declining call because the process is not yet running."
call.reject :decline
when :running, :stopping
Adhearsion.router.handle call
else
call.reject :error
end
call.async.route
end
end

def dispatch_call_event(event)
call = Adhearsion.active_calls[event.target_call_id]
if call && call.alive?
if call = Adhearsion.active_calls[event.target_call_id]
call.async.deliver_message event
else
logger.warn "Event received for inactive call #{event.target_call_id}: #{event.inspect}"
Expand Down
4 changes: 1 addition & 3 deletions lib/adhearsion/router/route.rb
Expand Up @@ -26,7 +26,7 @@ def match?(call)
end

def dispatch(call, callback = nil)
Adhearsion::Events.trigger_immediately :call_routed, call: call, route: self
Adhearsion::Events.trigger :call_routed, call: call, route: self

call_id = call.id # Grab this to use later incase the actor is dead

Expand Down Expand Up @@ -54,8 +54,6 @@ def dispatch(call, callback = nil)
end
callback.call if callback
}
rescue Call::Hangup, Call::ExpiredError
logger.info "Call routing could not be completed because call was unavailable."
end

def evented?
Expand Down
67 changes: 56 additions & 11 deletions spec/adhearsion/call_spec.rb
Expand Up @@ -39,6 +39,16 @@ module Adhearsion
Adhearsion.active_calls.clear
end

def expect_message_waiting_for_response(message = nil, fail = false, &block)
expectation = expect(subject.wrapped_object).to receive(:write_and_await_response, &block).once
expectation = expectation.with message if message
if fail
expectation.and_raise fail
else
expectation.and_return message
end
end

it "should do recursion detection on inspect" do
subject[:foo] = subject
Timeout.timeout(0.2) do
Expand Down Expand Up @@ -869,6 +879,51 @@ module Adhearsion
end
end

describe "routing" do
before do
expect(Adhearsion::Process).to receive(:state_name).once.and_return process_state
end

after { subject.route }

context "when the Adhearsion::Process is :booting" do
let(:process_state) { :booting }

it 'should reject a call with cause :declined' do
expect_message_waiting_for_response Adhearsion::Rayo::Command::Reject.new(reason: :decline)
end
end

[ :running, :stopping ].each do |state|
context "when when Adhearsion::Process is in :#{state}" do
let(:process_state) { state }

it "should dispatch via the router" do
Adhearsion.router do
route 'foobar', Class.new
end
expect(Adhearsion.router).to receive(:handle).once.with subject
end
end
end

context "when when Adhearsion::Process is in :rejecting" do
let(:process_state) { :rejecting }

it 'should reject a call with cause :declined' do
expect_message_waiting_for_response Adhearsion::Rayo::Command::Reject.new(reason: :decline)
end
end

context "when when Adhearsion::Process is not :running, :stopping or :rejecting" do
let(:process_state) { :foobar }

it 'should reject a call with cause :error' do
expect_message_waiting_for_response Adhearsion::Rayo::Command::Reject.new(reason: :error)
end
end
end

describe "#send_message" do
it "should send a message through the Rayo connection using the call ID and domain" do
expect(subject.wrapped_object).to receive(:client).once.and_return mock_client
Expand All @@ -884,16 +939,6 @@ module Adhearsion
end

describe "basic control commands" do
def expect_message_waiting_for_response(message = nil, fail = false, &block)
expectation = expect(subject.wrapped_object).to receive(:write_and_await_response, &block).once
expectation = expectation.with message if message
if fail
expectation.and_raise fail
else
expectation.and_return message
end
end

describe '#accept' do
describe "with no headers" do
it 'should send an Accept message' do
Expand Down Expand Up @@ -992,7 +1037,7 @@ def expect_message_waiting_for_response(message = nil, fail = false, &block)

it "should immediately fire the :call_rejected event giving the call and the reason" do
expect_message_waiting_for_response kind_of(Adhearsion::Rayo::Command::Reject)
expect(Adhearsion::Events).to receive(:trigger_immediately).once.with(:call_rejected, :call => subject, :reason => :decline)
expect(Adhearsion::Events).to receive(:trigger).once.with(:call_rejected, :call => subject, :reason => :decline)
subject.reject :decline
end

Expand Down
2 changes: 1 addition & 1 deletion spec/adhearsion/outbound_call_spec.rb
Expand Up @@ -280,7 +280,7 @@ def expect_message_waiting_for_response(message, uri = call_uri)
end

it "should immediately fire the :call_dialed event giving the call" do
expect(Adhearsion::Events).to receive(:trigger_immediately).once.with(:call_dialed, subject)
expect(Adhearsion::Events).to receive(:trigger).once.with(:call_dialed, subject)
subject.dial to, :from => from
end

Expand Down
58 changes: 5 additions & 53 deletions spec/adhearsion/rayo/initializer_spec.rb
Expand Up @@ -39,7 +39,7 @@ def initialize_rayo(options = {})
Adhearsion.config.core
end

let(:call_id) { rand }
let(:call_id) { rand.to_s }
let(:offer) { Adhearsion::Event::Offer.new :target_call_id => call_id }
let(:mock_call) { Adhearsion::Call.new }
let(:mock_client) { double 'Client' }
Expand Down Expand Up @@ -198,48 +198,13 @@ def initialize_rayo(options = {})
describe "dispatching an offer" do
before do
initialize_rayo
expect(Adhearsion::Process).to receive(:state_name).once.and_return process_state
expect(Adhearsion::Call).to receive(:new).once.and_return mock_call
end

context "when the Adhearsion::Process is :booting" do
let(:process_state) { :booting }

it 'should reject a call with cause :declined' do
expect(mock_call).to receive(:reject).once.with(:decline)
end
end

[ :running, :stopping ].each do |state|
context "when when Adhearsion::Process is in :#{state}" do
let(:process_state) { state }

it "should dispatch via the router" do
Adhearsion.router do
route 'foobar', Class.new
end
expect(Adhearsion.router).to receive(:handle).once.with mock_call
end
end
end

context "when when Adhearsion::Process is in :rejecting" do
let(:process_state) { :rejecting }

it 'should reject a call with cause :declined' do
expect(mock_call).to receive(:reject).once.with(:decline)
end
end

context "when when Adhearsion::Process is not :running, :stopping or :rejecting" do
let(:process_state) { :foobar }

it 'should reject a call with cause :error' do
expect(mock_call).to receive(:reject).once.with(:error)
end
it "should create and route the call" do
expect(Adhearsion::Call).to receive(:new).once.and_return mock_call
expect(mock_call).to receive_message_chain("async.route")
Adhearsion::Rayo::Initializer.dispatch_offer offer
end

after { Adhearsion::Events.trigger_immediately :rayo, offer }
end

describe "dispatching a component event" do
Expand Down Expand Up @@ -280,7 +245,6 @@ def initialize_rayo(options = {})
end

it "should not block on the call handling the event" do

mock_call.register_event_handler do |event|
sleep 5
end
Expand All @@ -302,18 +266,6 @@ def initialize_rayo(options = {})
described_class.dispatch_call_event mock_event
end
end

describe "when the registry contains a dead call" do
before do
mock_call.terminate
Adhearsion.active_calls[mock_call.id] = mock_call
end

it "should log a warning" do
expect(Adhearsion::Logging.get_logger(described_class)).to receive(:warn).once.with("Event received for inactive call #{call_id}: #{mock_event.inspect}")
described_class.dispatch_call_event mock_event
end
end
end

context "rayo configuration" do
Expand Down
14 changes: 3 additions & 11 deletions spec/adhearsion/router/route_spec.rb
Expand Up @@ -138,7 +138,7 @@ def should_not_match_the_call
let(:route) { Route.new 'foobar', controller }

it "should immediately fire the :call_routed event giving the call and route" do
expect(Adhearsion::Events).to receive(:trigger_immediately).once.with(:call_routed, call: call, route: route)
expect(Adhearsion::Events).to receive(:trigger).once.with(:call_routed, call: call, route: route)
expect(call).to receive(:hangup).once
route.dispatch call, lambda { latch.countdown! }
expect(latch.wait(2)).to be true
Expand All @@ -164,16 +164,8 @@ def should_not_match_the_call
context "when the call has already ended before routing can begin" do
before { Celluloid::Actor.kill call }

it "should fall through cleanly" do
expect { route.dispatch call }.not_to raise_error
end
end

context "when the call has already ended before routing can begin" do
before { Celluloid::Actor.kill call }

it "should fall through cleanly" do
expect { route.dispatch call }.not_to raise_error
it "should raise a hangup exception" do
expect { route.dispatch call }.to raise_error(Call::ExpiredError)
end
end

Expand Down

0 comments on commit 960a53c

Please sign in to comment.