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

Commit

Permalink
spec the continuation synchronisation
Browse files Browse the repository at this point in the history
  • Loading branch information
ConradIrwin committed Apr 16, 2011
1 parent 440c794 commit 7d0c2b1
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 19 deletions.
1 change: 1 addition & 0 deletions lib/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require 'deferrable_gratification'

$:.unshift File.dirname( __FILE__ )
require 'imap/continuation_synchronisation'
require 'imap/command_sender'
require 'imap/response_parser'
require 'imap/connection'
Expand Down
20 changes: 9 additions & 11 deletions lib/imap/command_sender.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,16 @@ def send_command_object(command)

# See Net::IMAP#authenticate
def send_authentication_data(auth_handler, command)
when_not_awaiting_continuation do
waiter = await_continuations do |response|
begin
data = auth_handler.process(response.data.text.unpack("m")[0])
s = [data].pack("m").gsub(/\n/, "")
send_data(s + CRLF)
rescue => e
command.fail e
end
waiter = await_continuations do |response|
begin
data = auth_handler.process(response.data.text.unpack("m")[0])
s = [data].pack("m").gsub(/\n/, "")
send_data(s + CRLF)
rescue => e
command.fail e
end
command.bothback{ |*args| waiter.succeed }
end
command.bothback{ |*args| waiter.succeed }
end

def send_string(str, command)
Expand Down Expand Up @@ -98,7 +96,7 @@ def send_line_buffered(str)
end
end
include Imap::CommandSender::LineBuffer
include Imap::CommandSender::Formatter
include Imap::ContinuationSynchronisation
end
end
end
19 changes: 11 additions & 8 deletions lib/imap/continuation_synchronisation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,22 @@ module Imap
# to block the outbound link while waiting for the continuation
# responses that it is expecting.
#
# TODO: This synchronisation mehcanism ought to be available standalone...
module ContinuationSynchronisation

def post_init
super
@awaiting_continuation = nil
end

# Pass all continuation responses to the block until further notice.
def awaiting_continuation?
!!@awaiting_continuation
end

# Pass all continuation responses to the block.
#
# Returns a deferrable which you should succeed or fail when you have
# received all the continuations you need.
# Returns a deferrable which you should succeed when you have
# received all the necessary continuations.
def await_continuations(&block)
ContinuationWaiter.new(block).tap do |waiter|
when_not_awaiting_continuation do
Expand All @@ -58,18 +63,16 @@ def receive_continuation(response)
# If possible, the block will be executed immediately; if not it will
# be added to a queue and executed whenever the queue has been emptied.
#
# Any previous items in the queue that wait on the connection will
# Note that if a queued callback retakes the synchronisation lock then
# all the later callbacks will be tranferred to the new queue.
#
def when_not_awaiting_continuation(&block)
if awaiting_continuation?
@awaiting_continuation.bothback{ when_not_awaiting_continuation(&block) }
else
yield
end
end

def awaiting_continuation?
!!@awaiting_continuation
end
end
end
end
88 changes: 88 additions & 0 deletions spec/continuation_synchronisation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require 'spec_helper'

describe EM::Imap::ContinuationSynchronisation do
before :each do
@connection = Class.new(EMStub) do
include EM::Imap::Connection
end.new
end

it "should allow things to happen when nothing is waiting" do
a = false
@connection.when_not_awaiting_continuation do
a = true
end
a.should be_true
end

it "should defer blocks until the waiter is done" do
a = false
waiter = @connection.await_continuations{ }
@connection.when_not_awaiting_continuation{ a = true }
a.should be_false
waiter.succeed
a.should be_true
end

it "should defer blocks multiple times if necessary" do
a = false
waiter1 = @connection.await_continuations{ }
waiter2 = @connection.await_continuations{ }
@connection.when_not_awaiting_continuation{ a = true }
waiter1.succeed
a.should be_false
waiter2.succeed
a.should be_true
end

it "should defer blocks when previously queued blocks want to synchronise" do
a = false
waiter1 = @connection.await_continuations{ }
waiter2 = nil

@connection.when_not_awaiting_continuation do
waiter2 = @connection.await_continuations{ }
end

@connection.when_not_awaiting_continuation{ a = true }
waiter1.succeed
a.should be_false
waiter2.succeed
a.should be_true
end

it "should forward continuation responses onto those waiting for it" do
a = nil
waiter = @connection.await_continuations{ |response| a = response }

response = Net::IMAP::ContinuationRequest.new("hi")
@connection.receive_response response
a.should == response
end

it "should forward many continuations if necessary" do
a = []
waiter = @connection.await_continuations{ |response| a << response }

response1 = Net::IMAP::ContinuationRequest.new("hi")
response2 = Net::IMAP::ContinuationRequest.new("hi")
@connection.receive_response response1
@connection.receive_response response2
a.should == [response1, response2]
end

it "should not forward any continuations after the waiter has stopped waiting" do
a = []
waiter1 = @connection.await_continuations do |response|
a << response
waiter1.succeed
end
waiter2 = @connection.await_continuations{ }

response1 = Net::IMAP::ContinuationRequest.new("hi")
response2 = Net::IMAP::ContinuationRequest.new("hi")
@connection.receive_response response1
@connection.receive_response response2
a.should == [response1]
end
end

0 comments on commit 7d0c2b1

Please sign in to comment.