em-synchrony-1.0.5/0000755000004100000410000000000012706127034014202 5ustar www-datawww-dataem-synchrony-1.0.5/Rakefile0000644000004100000410000000025012706127034015644 0ustar www-datawww-datarequire 'bundler/gem_tasks' require 'rspec/core/rake_task' task :default => [:spec] task :test => [:spec] desc "Run all RSpec tests" RSpec::Core::RakeTask.new(:spec) em-synchrony-1.0.5/Gemfile0000644000004100000410000000103612706127034015475 0ustar www-datawww-datasource 'https://rubygems.org' gem 'eventmachine', :git => 'git://github.com/eventmachine/eventmachine.git' group :development do gem 'rake' gem 'rspec', '~> 3.2' gem 'em-http-request', :git => 'git://github.com/igrigorik/em-http-request' gem 'remcached' # gem 'em-mongo', :git => 'https://github.com/bcg/em-mongo.git' gem 'activerecord', "= #{ENV['activerecord'] || '4.1.8'}" gem 'em-mongo' gem 'bson_ext' gem 'mysql2', '~> 0.3.18' gem 'em-redis', '~> 0.3.0' gem 'em-hiredis' gem 'mongo' gem 'amqp', '= 1.0.0' end em-synchrony-1.0.5/examples/0000755000004100000410000000000012706127034016020 5ustar www-datawww-dataem-synchrony-1.0.5/examples/go/0000755000004100000410000000000012706127034016425 5ustar www-datawww-dataem-synchrony-1.0.5/examples/go/consumer-publisher.go0000644000004100000410000000073212706127034022604 0ustar www-datawww-datapackage main import ( "fmt" "runtime" ) func producer(c chan int, N int, s chan bool) { for i := 0; i < N; i++ { fmt.Printf("producer: %d\n", i) c <- i } s <- true } func consumer(c chan int, N int, s chan bool) { for i := 0; i < N; i++ { fmt.Printf("consumer got: %d\n", <- c) } s <- true } func main() { runtime.GOMAXPROCS(2) c := make(chan int) s := make(chan bool) go producer(c, 10, s) go consumer(c, 10, s) <- s <- s } em-synchrony-1.0.5/examples/go/consumer-publisher.rb0000644000004100000410000000206112706127034022577 0ustar www-datawww-datarequire 'go' EM.synchrony do def producer(c, n, s) n.times do |i| puts "producer: #{i}" c << i end s << "producer finished" end consumer = ->(c, n, s) do n.times do |i| puts "consumer 1 got: #{c.pop}" end s << "consumer finished" end c = Channel.new(size: 2) s = Channel.new n = 10 # mix the syntax, just for fun... go c,n,s, &method(:producer) go c,n-1,s, &consumer go c,s do |c, s| puts "consumer 2 got: #{c.pop}" s << "consumer 2 finished" end 3.times { puts s.pop } EM.stop end # (M=6c0863) igrigorik /git/em-synchrony/examples/go> ruby consumer-publisher.rb # producer: 0 # producer: 1 # consumer 1 got: [0] # producer: 2 # consumer 2 got: [1] # producer: 3 # consumer 1 got: [2] # producer: 4 # consumer 2 finished # consumer 1 got: [3] # producer: 5 # consumer 1 got: [4] # producer: 6 # consumer 1 got: [5] # producer: 7 # consumer 1 got: [6] # producer: 8 # consumer 1 got: [7] # producer: 9 # consumer 1 got: [8] # consumer 1 got: [9] # producer finished # consumer finished em-synchrony-1.0.5/examples/go/go.rb0000644000004100000410000000150012706127034017353 0ustar www-datawww-datarequire 'em-synchrony' module Kernel def go(*args, &blk) EM.next_tick do Fiber.new { blk.call(*args) }.resume end end end class Channel < EM::Queue def initialize(opts = {}) @limit = opts[:size] @prodq = [] @size = 0 super() end def size; @size; end def empty?; size == 0; end def pop f = Fiber.current clb = Proc.new do |*args| @size -= 1 f.resume(args) @prodq.shift.call if !@prodq.empty? end super(&clb) Fiber.yield end def push(*items) f = Fiber.current @size += 1 EM.next_tick { super(*items) } # if the queue is bounded, then suspend the producer # until someone consumes a pending message if @limit && size >= @limit @prodq.push -> { f.resume } Fiber.yield end end alias :<< :push end em-synchrony-1.0.5/examples/go/channel.rb0000644000004100000410000000020212706127034020354 0ustar www-datawww-datarequire 'go' EM.synchrony do c = Channel.new go { sleep(1) c << 'go go go sleep 1!' } puts c.pop EM.stop endem-synchrony-1.0.5/examples/go/channel.go0000644000004100000410000000032612706127034020365 0ustar www-datawww-datapackage main import ( "fmt" "time" ) func main() { c := make(chan string) go func() { time.Sleep(1) c <- "go go go sleep 1!" }() fmt.Printf("%v\n", <-c) // Wait for goroutine to finish } em-synchrony-1.0.5/examples/go/README.md0000644000004100000410000000275112706127034017711 0ustar www-datawww-data# CSP Experiments with Ruby Partly an exercise to help myself wrap my head around Go's concurrency, partly an experiment to see how much of the syntax & behavior of Go's CSP model can be modelled in Ruby... As it turns out, it's not hard to almost replicate the look and feel. Note: none of the Ruby examples actually give you the parallelism of Go. ## Notes * Instead of explicitly using locks to mediate access to shared data, Go encourages the use of channels to pass references to data between goroutines. * Channels combine communication — the exchange of a value—with synchronization — guaranteeing that two calculations (goroutines) are in a known state. go.rb implements an (un)bounded Channel interface, and with some help from Fibers & Ruby 1.9, we can also implement the goroutine look and feel pretty easily. In fact, with CSP semantics, its not hard to imagine a MVM (multi-VM) Ruby where each VM still has a GIL, but where data sharing is done via communication of references between VM's. ## Simple channel example in Go package main import ( "fmt" "time" ) func main() { c := make(chan string) go func() { time.Sleep(1) c <- "go go go sleep 1!" }() fmt.Printf("%v\n", <-c) // Wait for goroutine to finish } ## Equivalent in Ruby require 'go' EM.synchrony do c = Channel.new go { sleep(1) c << 'go go go sleep 1!' } puts c.pop EM.stop end em-synchrony-1.0.5/examples/all.rb0000644000004100000410000000206112706127034017114 0ustar www-datawww-datarequire "lib/em-synchrony" EM.synchrony do # open 4 concurrent MySQL connections db = EventMachine::Synchrony::ConnectionPool.new(size: 4) do EventMachine::MySQL.new(host: "localhost") end # perform 4 http requests in parallel, and collect responses multi = EventMachine::Synchrony::Multi.new multi.add :page1, EventMachine::HttpRequest.new("http://service.com/page1").aget multi.add :page2, EventMachine::HttpRequest.new("http://service.com/page2").aget multi.add :page3, EventMachine::HttpRequest.new("http://service.com/page3").aget multi.add :page4, EventMachine::HttpRequest.new("http://service.com/page4").aget data = multi.perform.responses[:callback].values # insert fetched HTTP data into a mysql database, using at most 2 connections at a time # - note that we're writing async code within the callback! EM::Synchrony::Iterator.new(data, 2).each do |page, iter| db.aquery("INSERT INTO table (data) VALUES(#{page});") db.callback { iter.return(http) } end puts "All done! Stopping event loop." EventMachine.stop endem-synchrony-1.0.5/examples/nethttp.rb0000644000004100000410000000041712706127034020035 0ustar www-datawww-datarequire "lib/em-synchrony" require "net/http" $VERBOSE = nil EM.synchrony do # monkey patch default Socket code to use EventMachine Sockets instead TCPSocket = EventMachine::Synchrony::TCPSocket Net::HTTP.get_print 'www.google.com', '/index.html' EM.stop end em-synchrony-1.0.5/.rspec0000644000004100000410000000000412706127034015311 0ustar www-datawww-data-I. em-synchrony-1.0.5/spec/0000755000004100000410000000000012706127034015134 5ustar www-datawww-dataem-synchrony-1.0.5/spec/fiber_iterator_spec.rb0000644000004100000410000000222412706127034021473 0ustar www-datawww-datarequire "spec/helper/all" require "em-synchrony/fiber_iterator" describe EventMachine::Synchrony::FiberIterator do it "should wait until the iterator is done and wrap internal block within a fiber" do EM.synchrony do results = [] i = EM::Synchrony::FiberIterator.new(1..5, 2).each do |num| EM::Synchrony.sleep(0.1) results.push num end results.should == (1..5).to_a results.size.should == 5 EventMachine.stop end end it "works even with nested arrays and iterator" do EM.synchrony do results = [] list = [[1, 2], [3, 4]] EM::Synchrony::FiberIterator.new(list, 2).each { |sublist, iter| results.push(sublist) } expect(results).to eq(list) EM.stop end end # # it "should sum values within the iterator" do # EM.synchrony do # data = (1..5).to_a # res = EM::Synchrony::FiberIterator.new(data, 2).inject(0) do |total, num, iter| # EM::Synchrony.sleep(0.1) # # p [:sync, total, num] # iter.return(total += num) # end # # res.should == data.inject(:+) # EventMachine.stop # end # end end em-synchrony-1.0.5/spec/timer_spec.rb0000644000004100000410000000161712706127034017620 0ustar www-datawww-datarequire "spec/helper/all" describe EventMachine::Synchrony do it "should execute one-shot timer in Fiber" do EM.synchrony do start = Time.now.to_f EM::Synchrony.add_timer(0.1) do EM::Synchrony.sleep(0.1) (Time.now.to_f - start).should > 0.2 EventMachine.stop end end end it "should execute period timers in Fibers" do EM.synchrony do start = Time.now.to_f num = 0 EM::Synchrony.add_periodic_timer(0.1) do EM::Synchrony.sleep(0.1) num += 1 if num > 1 (Time.now.to_f - start).should > 0.3 EventMachine.stop end end end end it 'should return instance of EventMachine::Timer from add_timer method' do EM.synchrony do timer = EM::Synchrony.add_timer(0.1){} timer.should be_instance_of(EventMachine::Timer) EventMachine.stop end end end em-synchrony-1.0.5/spec/amqp_spec.rb0000644000004100000410000001007112706127034017430 0ustar www-datawww-datarequire "spec/helper/all" describe EM::Synchrony::AMQP do it "should yield until connection is ready" do EM.synchrony do connection = EM::Synchrony::AMQP.connect connection.connected?.should eq(true) EM.stop end end it "should yield until disconnection is complete" do EM.synchrony do connection = EM::Synchrony::AMQP.connect connection.disconnect connection.connected?.should eq(false) EM.stop end end it "should yield until the channel is created" do EM.synchrony do connection = EM::Synchrony::AMQP.connect channel = EM::Synchrony::AMQP::Channel.new(connection) channel.should be_kind_of(EM::Synchrony::AMQP::Channel) EM.stop end end it "should yield until the queue is created" do EM.synchrony do connection = EM::Synchrony::AMQP.connect channel = EM::Synchrony::AMQP::Channel.new(connection) queue = EM::Synchrony::AMQP::Queue.new(channel, "test.em-synchrony.queue1", :auto_delete => true) EM.stop end end it "should yield when a queue is created from a channel" do EM.synchrony do connection = EM::Synchrony::AMQP.connect channel = EM::Synchrony::AMQP::Channel.new(connection) queue = channel.queue("test.em-synchrony.queue1", :auto_delete => true) EM.stop end end it "should yield until the exchange is created" do EM.synchrony do connection = EM::Synchrony::AMQP.connect channel = EM::Synchrony::AMQP::Channel.new(connection) exchange = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.exchange") exchange.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange) [:direct, :fanout, :topic, :headers].each do |type| # Exercise cached exchange code path 2.times.map { channel.send(type, "test.em-synchrony.#{type}") }.each do |ex| ex.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange) end end EM.stop end end it "should publish and receive messages" do nb_msg = 10 EM.synchrony do connection = EM::Synchrony::AMQP.connect channel = EM::Synchrony::AMQP::Channel.new(connection) ex = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.fanout") q1 = channel.queue("test.em-synchrony.queues.1", :auto_delete => true) q2 = channel.queue("test.em-synchrony.queues.2", :auto_delete => true) q1.bind(ex) q2.bind(ex) nb_q1, nb_q2 = 0, 0 stop_cb = proc { EM.stop if nb_q1 + nb_q2 == 2 * nb_msg } q1.subscribe(:ack => false) do |meta, msg| msg.should match(/^Bonjour [0-9]+/) nb_q1 += 1 stop_cb.call end q2.subscribe do |meta, msg| msg.should match(/^Bonjour [0-9]+/) nb_q2 += 1 stop_cb.call end Fiber.new do nb_msg.times do |n| ex.publish("Bonjour #{n}") EM::Synchrony.sleep(0.1) end end.resume end end it "should handle several consumers" do nb_msg = 10 EM.synchrony do connection = EM::Synchrony::AMQP.connect channel = EM::Synchrony::AMQP::Channel.new(connection) exchange = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.consumers.fanout") queue = channel.queue("test.em-synchrony.consumers.queue", :auto_delete => true) queue.bind(exchange) cons1 = EM::Synchrony::AMQP::Consumer.new(channel, queue) cons2 = EM::Synchrony::AMQP::Consumer.new(channel, queue) nb_cons1, nb_cons2 = 0, 0 stop_cb = Proc.new do if nb_cons1 + nb_cons2 == nb_msg nb_cons1.should eq(nb_cons2) EM.stop end end cons1.on_delivery do |meta, msg| msg.should match(/^Bonjour [0-9]+/) nb_cons1 += 1 stop_cb.call end.consume cons2.on_delivery do |meta, msg| msg.should match(/^Bonjour [0-9]+/) nb_cons2 += 1 stop_cb.call end.consume 10.times do |n| exchange.publish("Bonjour #{n}") EM::Synchrony.sleep(0.1) end end end end em-synchrony-1.0.5/spec/hiredis_spec.rb0000644000004100000410000000244712706127034020131 0ustar www-datawww-datarequire "spec/helper/all" describe EM::Hiredis do it "should connect on demand" do EventMachine.synchrony do connection = EM::Hiredis::Client.connect connection.should_not be_connected connection.ping connection.should be_connected EventMachine.stop end end it "should work with compact connect syntax" do EventMachine.synchrony do redis = EM::Hiredis.connect redis.set('a', 'bar') redis.get('a').should == 'bar' EM.stop end end it "should work with manual db select" do EventMachine.synchrony do redis = EM::Hiredis.connect 'redis://127.0.0.1:6379' redis.select(0) redis.set('a', 'baz') redis.get('a').should == 'baz' EM.stop end end it "should get/set records synchronously" do EventMachine.synchrony do redis = EM::Hiredis::Client.connect redis.set('a', 'foo') redis.get('a').should == 'foo' redis.get('c').should == nil EM.stop end end it "should incr/decr key synchronously" do EventMachine.synchrony do redis = EM::Hiredis::Client.connect redis.del('key') redis.incr('key') redis.get('key').to_i.should == 1 redis.decr('key') redis.get('key').to_i.should == 0 EM.stop end end end em-synchrony-1.0.5/spec/tcpsocket_spec.rb0000644000004100000410000003464512706127034020506 0ustar www-datawww-datarequire "spec/helper/core" module SendAndClose def post_init send_data "1234" close_connection_after_writing end end module SendAndTimedClose def post_init send_data "1234" EM.add_timer(0.05) { self.close_connection_after_writing } end end module SendAndKeepOpen def post_init send_data "1234" end end def tcp_test(server_type, ops={}, &block) EventMachine.synchrony do ops = {:stop => true}.merge ops EM::start_server('localhost', 12345, server_type) @socket = EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 @socket.close if ops[:close] block.call EM.stop if ops[:stop] end end describe EventMachine::Synchrony::TCPSocket do context '.new' do context 'to an open TCP port on an resolvable host' do it 'succeeds' do EventMachine.synchrony do EM::start_server('localhost', 12345) @socket = EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 @socket.should_not be_error EM.stop end end end context 'to an unresolvable host' do it 'raises SocketError' do EventMachine.synchrony do proc { EventMachine::Synchrony::TCPSocket.new 'xxxyyyzzz', 12345 }.should raise_error(SocketError) EM.stop end end end context 'to a closed TCP port' do it 'raises Errno::ECONNREFUSED' do EventMachine.synchrony do proc { EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 }.should raise_error(Errno::ECONNREFUSED) EM.stop end end end end context '#closed?' do context 'after calling #close' do it 'returns true' do tcp_test(SendAndKeepOpen, :close => true) do @socket.should be_closed end end end context 'after the peer has closed the connection' do context 'when we\'ve not yet read EOF' do it 'returns false' do tcp_test(SendAndClose) do @socket.read(2).size.should eq 2 @socket.should_not be_closed end end end context 'when we\'ve read EOF' do it 'returns false' do tcp_test(SendAndClose) do @socket.read(10).size.should < 10 @socket.read(10).should be_nil @socket.should_not be_closed end end end end end context '#read' do context 'with a length argument' do context 'with a possitive length argument' do context 'when the connection is open' do context 'with greater or equal than the requested data buffered' do it 'returns the requested data and no more' do tcp_test(SendAndKeepOpen) do @socket.read(2).size.should eq 2 @socket.read(1).size.should eq 1 end end end context 'with less than the requested data buffered' do it 'blocks' do tcp_test(SendAndKeepOpen, :stop => false) do @blocked = true EM.next_tick { @blocked.should eq true; EM.next_tick { EM.stop } } @socket.read(10) @blocked = false end end end end context 'when the peer has closed the connection' do context 'with no data buffered' do it 'returns nil' do tcp_test(SendAndClose) do @socket.read(4).size.should eq 4 @socket.read(1).should be_nil end end end context 'with less than the requested data buffered' do it 'returns the buffered data' do tcp_test(SendAndClose) do @socket.read(50).size.should eq 4 end end end context 'with greater or equal than the requested data buffered' do it 'returns the requested data and no more' do tcp_test(SendAndClose) do @socket = EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 @socket.read(2).size.should eq 2 end end end end context 'when we closed the connection' do it 'raises IOError' do tcp_test(SendAndKeepOpen, :close => true) do proc { @socket.read(4) }.should raise_error(IOError) end end end end context 'with a negative length argument' do it 'raises ArgumentError' do tcp_test(SendAndKeepOpen) do proc { @socket.read(-10) }.should raise_error(ArgumentError) end end end context 'with a zero length argument' do context 'when the connection is open' do it 'returns an empty string' do tcp_test(SendAndKeepOpen) do @socket.read(0).should eq "" end end end context 'when the peer has closed the connection' do it 'returns an empty string' do tcp_test(SendAndClose) do @socket.read(0).should eq "" end end end context 'when we closed the connection' do it 'raises IOError' do tcp_test(SendAndKeepOpen, :close => true) do proc { @socket.read(0) }.should raise_error(IOError) end end end end end context 'without a length argument' do context 'when the connection is open' do it 'blocks until the peer closes the connection and returns all data sent' do tcp_test(SendAndTimedClose) do @blocked = true EM.next_tick { @blocked.should eq true } @socket.read(10).should eq '1234' @blocked = false end end end context 'when the peer has closed the connection' do context 'with no data buffered' do it 'returns an empty string' do tcp_test(SendAndClose) do @socket.read() @socket.read().should eq "" end end end context 'with data buffered' do it 'returns the buffered data' do tcp_test(SendAndClose) do @socket.read().should eq "1234" end end end end context 'when we closed the connection' do it 'raises IOError' do tcp_test(SendAndKeepOpen, :close => true) do proc { @socket.read() }.should raise_error(IOError) end end end end end context '#read_nonblock' do context 'with a positive length argument' do context 'when the connection is open' do context 'with greater or equal than the requested data buffered' do it 'returns the requested data and no more' do tcp_test(SendAndKeepOpen) do @socket.read_nonblock(2).size.should eq 2 @socket.read_nonblock(1).size.should eq 1 end end end context 'with less than the requested data buffered' do it 'returns the available data' do tcp_test(SendAndKeepOpen) do @socket.read_nonblock(10).size.should eq 4 end end end end context 'when the peer has closed the connection' do context 'with no data buffered' do it 'raises EOFError' do tcp_test(SendAndClose) do @socket.read_nonblock(4).size.should eq 4 lambda { @socket.read_nonblock(1) }.should raise_error(EOFError) end end end context 'with less than the requested data buffered' do it 'returns the buffered data' do tcp_test(SendAndClose) do @socket.read_nonblock(50).size.should eq 4 end end end context 'with greater or equal than the requested data buffered' do it 'returns the requested data and no more' do tcp_test(SendAndClose) do @socket = EventMachine::Synchrony::TCPSocket.new 'localhost', 12345 @socket.read_nonblock(2).size.should eq 2 end end end end context 'when we closed the connection' do it 'raises IOError' do tcp_test(SendAndKeepOpen, :close => true) do proc { @socket.read_nonblock(4) }.should raise_error(IOError) end end end end context 'with a negative length argument' do it 'raises ArgumentError' do tcp_test(SendAndKeepOpen) do proc { @socket.read_nonblock(-10) }.should raise_error(ArgumentError) end end end context 'with a zero length argument' do it 'raises ArgumentError' do tcp_test(SendAndKeepOpen) do proc { @socket.read_nonblock(0) }.should raise_error(ArgumentError) end end end end context '#recv' do context 'with a length argument' do context 'with a possitive length argument' do context 'when the connection is open' do context 'with greater or equal than the requested data buffered' do it 'returns the requested data and no more' do tcp_test(SendAndKeepOpen) do @socket.recv(2).size.should eq 2 @socket.recv(1).size.should eq 1 end end end context 'with less than the requested data buffered' do it 'return the buffered data' do tcp_test(SendAndKeepOpen) do @socket.recv(50).size.should eq 4 end end end context 'with no buffered data' do it 'blocks' do tcp_test(SendAndKeepOpen, :stop => false) do @socket.recv(10) @blocked = true EM.next_tick { @blocked.should eq true; EM.next_tick { EM.stop } } @socket.recv(10) @blocked = false end end end end context 'when the peer has closed the connection' do context 'with no data buffered' do it 'returns an empty string' do tcp_test(SendAndClose) do @socket.read(4).size.should eq 4 @socket.recv(1).should eq "" end end end context 'with less than the requested data buffered' do it 'returns the buffered data' do tcp_test(SendAndClose) do @socket.recv(50).size.should eq 4 end end end context 'with greater or equal than the requested data buffered' do it 'returns the requested data and no more' do tcp_test(SendAndClose) do @socket.recv(2).size.should eq 2 end end end end context 'when we closed the connection' do it 'raises IOError' do tcp_test(SendAndKeepOpen, :close => true) do proc { @socket.recv(4) }.should raise_error(IOError) end end end end context 'with a negative length argument' do it 'raises ArgumentError' do tcp_test(SendAndKeepOpen) do proc { @socket.recv(-10) }.should raise_error(ArgumentError) end end end context 'with a zero length argument' do context 'when the connection is open' do it 'returns an empty string' do tcp_test(SendAndKeepOpen) do @socket.recv(0).should eq "" end end end context 'when the peer has closed the connection' do it 'returns an empty string' do tcp_test(SendAndClose) do @socket.recv(0).should eq "" end end end context 'when we closed the connection' do it 'raises IOError' do tcp_test(SendAndKeepOpen, :close => true) do proc { @socket.recv(0) }.should raise_error(IOError) end end end end end context 'without a length argument' do it 'raises ArgumentError' do tcp_test(SendAndKeepOpen) do proc { @socket.recv() }.should raise_error(ArgumentError) end end end end context '#write' do context 'when the peer has closed the connection' do it 'raises Errno::EPIPE' do tcp_test(SendAndClose, :stop => false) do EM.add_timer(0.01) do proc { @socket.write("foo") }.should raise_error(Errno::EPIPE) EM.stop end end end end context 'when we closed the connection' do it 'raises IOError' do tcp_test(SendAndKeepOpen, :close => true) do proc { @socket.write("foo") }.should raise_error(IOError) end end end end context '#send' do context 'when the peer has closed the connection' do it 'raises Errno::EPIPE' do tcp_test(SendAndClose, :stop => false) do EM.add_timer(0.01) do proc { @socket.send("foo",0) }.should raise_error(Errno::EPIPE) EM.stop end end end end context 'when we closed the connection' do it 'raises IOError' do tcp_test(SendAndKeepOpen, :close => true) do proc { @socket.send("foo",0) }.should raise_error(IOError) end end end context 'without a flags argument' do it 'raises ArgumentError' do tcp_test(SendAndKeepOpen) do proc { @socket.send('foo') }.should raise_error(ArgumentError) end end end end context 'when wrapped in a connection pool' do it 'accepts "send"' do EventMachine.synchrony do @socket = EventMachine::Synchrony::ConnectionPool.new(size: 1) do EventMachine::Synchrony::TCPSocket.new 'eventmachine.rubyforge.org', 80 end @socket.send("GET / HTTP1.1\r\n\r\n",0).class.should be(Fixnum) EM.stop end end end end em-synchrony-1.0.5/spec/mongo_spec.rb0000644000004100000410000000041112706127034017606 0ustar www-datawww-datarequire 'spec/helper/all' require 'lib/em-synchrony/mongo' require 'mongo' describe Mongo::Connection do it 'connects to DB' do EventMachine.synchrony do conn = Mongo::Connection.new 'localhost', 27017, :connect => true EM.stop end end end em-synchrony-1.0.5/spec/widgets.sql0000644000004100000410000000021712706127034017323 0ustar www-datawww-datacreate database widgets; use widgets; create table widgets ( id INT NOT NULL AUTO_INCREMENT, title varchar(255), PRIMARY KEY (`id`) ); em-synchrony-1.0.5/spec/redis_spec.rb0000644000004100000410000000452312706127034017605 0ustar www-datawww-datarequire "spec/helper/all" describe EM::Protocols::Redis do it "should yield until connection is ready" do EventMachine.synchrony do connection = EM::Protocols::Redis.connect connection.connected.should eq(true) EventMachine.stop end end it "should get/set records synchronously" do EventMachine.synchrony do redis = EM::Protocols::Redis.connect redis.set('a', 'foo') redis.get('a').should == 'foo' redis.get('c').should == nil EM.stop end end it "should mapped_mget synchronously" do EventMachine.synchrony do redis = EM::Protocols::Redis.connect redis.set('mmget1', 'value1') redis.set('mmget3', 'value3') redis.mapped_mget('mmget1', 'mmget2', 'mmget3').should == { 'mmget1' => 'value1', 'mmget3' => 'value3' } EM.stop end end it "should incr/decr key synchronously" do EventMachine.synchrony do redis = EM::Protocols::Redis.connect redis.delete('key') redis.incr('key') redis.get('key').to_i.should == 1 redis.decr('key') redis.get('key').to_i.should == 0 EM.stop end end it "should execute async commands" do EventMachine.synchrony do redis = EM::Protocols::Redis.connect redis.set('a', 'foobar') redis.aget('a') do |response| response.should == 'foobar' EM.stop end end end it "should execute async set add" do EventMachine.synchrony do redis = EM::Protocols::Redis.connect redis.asadd('test', 'hai') do redis.asadd('test', 'bai') do redis.aset_count('test') do |resp| resp.to_i.should == 2 EM.stop end end end end end it "should execute async mapped_mget" do EventMachine.synchrony do redis = EM::Protocols::Redis.connect redis.aset('some_key', 'some_value') do redis.amapped_mget('some_key', 'some_other_key') do |values| values.should == { 'some_key' => 'some_value' } EM.stop end end end end it "should execute sync add and auth", ci_skip: true do EventMachine.synchrony do redis = EM::Protocols::Redis.connect redis.auth('abc') redis.delete('key') redis.add('key', 'value') redis.scard('key').should == 1 EM.stop end end end em-synchrony-1.0.5/spec/connection_pool_spec.rb0000644000004100000410000001074612706127034021673 0ustar www-datawww-datarequire "spec/helper/all" DELAY = 0.25 QUERY = "select sleep(#{DELAY})" describe EventMachine::Synchrony::ConnectionPool do it "should queue requests if pool size is exceeded" do EventMachine.run do db = EventMachine::Synchrony::ConnectionPool.new(size: 1) do Mysql2::EM::Client.new end Fiber.new { start = now multi = EventMachine::Synchrony::Multi.new multi.add :a, db.aquery(QUERY) multi.add :b, db.aquery(QUERY) res = multi.perform (now - start.to_f).should be_within(DELAY * 2 * 0.15).of(DELAY * 2) res.responses[:callback].size.should == 2 res.responses[:errback].size.should == 0 EventMachine.stop }.resume end end it "should execute multiple async pool requests within same fiber" do EventMachine.run do db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do Mysql2::EM::Client.new end Fiber.new { start = now multi = EventMachine::Synchrony::Multi.new multi.add :a, db.aquery(QUERY) multi.add :b, db.aquery(QUERY) res = multi.perform (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY) res.responses[:callback].size.should == 2 res.responses[:errback].size.should == 0 EventMachine.stop }.resume end end it "should share connection pool between different fibers" do EventMachine.run do db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do Mysql2::EM::Client.new end Fiber.new { start = now results = [] fiber = Fiber.current 2.times do |n| Fiber.new { results.push db.query(QUERY) fiber.transfer if results.size == 2 }.resume end # wait for workers Fiber.yield (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY) results.size.should == 2 EventMachine.stop }.resume end end it "should share connection pool between different fibers & async requests" do EventMachine.run do db = EventMachine::Synchrony::ConnectionPool.new(size: 5) do Mysql2::EM::Client.new end Fiber.new { start = now results = [] fiber = Fiber.current 2.times do |n| Fiber.new { multi = EventMachine::Synchrony::Multi.new multi.add :a, db.aquery(QUERY) multi.add :b, db.aquery(QUERY) results.push multi.perform fiber.transfer if results.size == 3 }.resume end Fiber.new { # execute a synchronous requests results.push db.query(QUERY) fiber.transfer if results.size == 3 }.resume # wait for workers Fiber.yield (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY) results.size.should == 3 EventMachine.stop }.resume end end describe '#pool_status' do it 'should return right initial size' do (1..10).each do |count| pool = EventMachine::Synchrony::ConnectionPool.new(size: count) { } status = pool.pool_status expect(status).to include available: count expect(status).to include reserved: 0 expect(status).to include pending: 0 end end it 'should return up-to-date statusrmation' do sleep = 0.5 count = 5 EM.run do pool = EM::Synchrony::ConnectionPool.new(size: count) do -> { EM::Synchrony.sleep(sleep) } end (1..count).each do |used| Fiber.new { pool.call }.resume status = pool.pool_status expect(status).to include available: count - used expect(status).to include reserved: used expect(status).to include pending: 0 end (1..count).each do |used| Fiber.new { pool.call }.resume status = pool.pool_status expect(status).to include available: 0 expect(status).to include reserved: count expect(status).to include pending: used end Fiber.new { EM::Synchrony.sleep(sleep + 0.1) expect(pool.pool_status).to include pending: 0 EM::Synchrony.sleep(sleep + 0.1) status = pool.pool_status expect(status).to include available: count expect(status).to include reserved: 0 expect(status).to include pending: 0 EM.stop }.resume end end end end em-synchrony-1.0.5/spec/memcache_spec.rb0000644000004100000410000000136612706127034020243 0ustar www-datawww-datarequire "spec/helper/all" describe EM::P::Memcache do it "should fire sequential memcached requests" do EventMachine.synchrony do conn = EM::P::Memcache.connect key = 'key' value = 'value for key' fake_key = 'nonexistent key' # without a corresponding value conn.delete(key) conn.set(key, value) conn.get(key).should == value conn.delete(key) conn.get(key).should be_nil conn.set(key, value) conn.get(key).should == value conn.del(key) conn.get(key).should be_nil conn.set(key, value) conn.get(key, fake_key).should == [value, nil] conn.get_hash(key, fake_key).should == { key => value, fake_key => nil } EventMachine.stop end end end em-synchrony-1.0.5/spec/inlinesync_spec.rb0000644000004100000410000000143112706127034020645 0ustar www-datawww-datarequire "spec/helper/all" require "em-synchrony/iterator" describe EventMachine::Synchrony do URL = "http://localhost:8081/" DELAY = 0.01 it "should allow inline callbacks for Deferrable object" do EM.synchrony do s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", DELAY) result = EM::Synchrony.sync EventMachine::HttpRequest.new(URL).aget result.response.should match(/Foo/) EM.stop end end it "should inline errback/callback cases" do EM.synchrony do class E include EventMachine::Deferrable def run EM.add_timer(0.01) {fail("uh oh!")} self end end result = EM::Synchrony.sync E.new.run result.should match(/uh oh!/) EM.stop end end end em-synchrony-1.0.5/spec/iterator_spec.rb0000644000004100000410000000323712706127034020331 0ustar www-datawww-datarequire "spec/helper/all" require "em-synchrony/iterator" describe EventMachine::Synchrony::Iterator do it "should wait until the iterator is done" do EM.synchrony do results = [] i = EM::Synchrony::Iterator.new(1..50, 10).each do |num, iter| results.push num iter.next end results.should == (1..50).to_a results.size.should == 50 EventMachine.stop end end it "should map values within the iterator" do EM.synchrony do results = EM::Synchrony::Iterator.new(1..50, 10).map do |num, iter| iter.return(num + 1) end results.should == (2..51).to_a results.size.should == 50 EventMachine.stop end end it "should sum values within the iterator" do EM.synchrony do data = (1..50).to_a res = EM::Synchrony::Iterator.new(data, 10).inject(0) do |total, num, iter| total += num iter.return(total) end res.should == data.inject(:+) EventMachine.stop end end it "should fire async http requests in blocks of 2" do EM.synchrony do num_urls = 4 concurrency = 2 delay = 0.25 s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", delay) urls = ['http://localhost:8081/'] * num_urls start = now results = EM::Synchrony::Iterator.new(urls, concurrency).map do |url, iter| http = EventMachine::HttpRequest.new(url).aget http.callback { iter.return(http) } end results.size.should == 4 (now - start.to_f).should be_within(delay * 0.15).of(delay * (num_urls / concurrency)) EventMachine.stop end end end em-synchrony-1.0.5/spec/defer_spec.rb0000644000004100000410000000115712706127034017564 0ustar www-datawww-datarequire "spec/helper/all" describe EventMachine::Synchrony do it "defer: simple" do EM.synchrony do x = 1 result = EM::Synchrony.defer do x = 2 sleep 0.1 3 end result.should == 3 x.should == 2 EM.stop end end it "defer: with lambda" do EM.synchrony do x = 1 op = lambda do sleep 0.1 x += 1 3 end EM::S.defer(op).should == 3 x.should == 2 EM.stop end end end em-synchrony-1.0.5/spec/kernel_override_spec.rb0000644000004100000410000000374312706127034021661 0ustar www-datawww-data# encoding: UTF-8 require 'spec/helper/all' describe EventMachine::Synchrony do before do EM::Synchrony.on_sleep end after do EM::Synchrony.on_sleep end describe '#sleep' do context 'outside synchrony' do it 'does not call hook' do EM::Synchrony.on_sleep { fail 'should not happen' } expect { sleep(0.01) }.not_to raise_error end context 'with synchrony in another thread'do before do @thread = Thread.new do EM.run do sleep(0.5) EM.stop end end sleep(0.1) end after do @thread.join end it 'does not call hook' do EM::Synchrony.on_sleep { fail 'should not happen' } expect { sleep(0.01) }.not_to raise_error end end end context 'within synchrony' do around do |example| EM.synchrony do example.run EM.next_tick { EM.stop } end end context 'with no hook defined' do it 'calls Kernel.sleep' do expect(self).to receive(:sleep) sleep(1) end end context 'with hook defined' do it 'executes the hook' do called = 0 EM::Synchrony.on_sleep { called += 1 } (1..10).each do |count| sleep(1) expect(called).to be count end end it 'propagates exceptions' do msg = 'expected exception' EM::Synchrony.on_sleep { fail msg } expect { sleep(1) }.to raise_error(RuntimeError, msg) end context "when calling 'sleep' in the hook" do it 'calls the original sleep' do sleep_time = 1.213234123412341454134512345 expect(self).to receive(:orig_sleep).with(sleep_time) EM::Synchrony.on_sleep do |*args| sleep(*args) end sleep(sleep_time) end end end end end end em-synchrony-1.0.5/spec/thread_spec.rb0000644000004100000410000001031712706127034017744 0ustar www-datawww-datarequire "spec/helper/all" describe EventMachine::Synchrony::Thread::Mutex do let(:m) { EM::Synchrony::Thread::Mutex.new } it "should synchronize" do EM.synchrony do i = 0 f1 = Fiber.new do m.synchronize do f = Fiber.current EM.next_tick { f.resume } Fiber.yield i += 1 end end.resume f1 = Fiber.new do m.synchronize do i.should eql(1) EM.stop end end.resume end end describe "lock" do describe "when mutex already locked" do it "should raise ThreadError" do f = Fiber.new do m.lock Fiber.yield m.lock end f.resume proc { f.resume }.should raise_error(FiberError) end end end describe "sleep" do describe "without timeout" do it "should sleep until resume" do EM.synchrony do m.lock i = 0 f = Fiber.current EM.next_tick { i += 1; f.resume } res = m.sleep i.should eql(1) EM.stop end end it "should release lock" do EM.synchrony do i = 0 Fiber.new do m.lock f = Fiber.current EM.next_tick { f.resume } Fiber.yield i += 1 m.sleep end.resume Fiber.new do m.lock i.should eql(1) EM.stop end.resume end end it "should wait unlock after resume" do EM.synchrony do i = 0 f1 = Fiber.new do m.lock m.sleep i.should eql(1) EM.stop end f2 = Fiber.new do m.lock f1.resume i += 1 m.unlock end f1.resume f2.resume end end describe "with timeout" do it "should sleep for timeout" do EM.synchrony do m.lock i = 0 EM.next_tick { i += 1 } m.sleep(0.05) i.should eql(1) EM.stop end end describe "and resume before timeout" do it "should not raise any execptions" do EM.synchrony do m.lock f = Fiber.current EM.next_tick { f.resume } m.sleep(0.05) EM.add_timer(0.1) { EM.stop } end end it "should resume in nested Fiber" do EM.synchrony do f = Fiber.new do m.synchronize do t = m.sleep(0.05) t.should >= 0.05 end EM.stop end f.resume end end end end end end describe EventMachine::Synchrony::Thread::ConditionVariable do let(:c){ EM::Synchrony::Thread::ConditionVariable.new } it "should wakeup waiter" do i = '' EM.synchrony do f1 = Fiber.new do m.synchronize do i << 'a' c.wait(m) i << 'c' end EM.stop end.resume f2 = Fiber.new do i << 'b' c.signal end.resume end i.should == 'abc' end it 'should allow to play ping-pong' do i = '' EM.synchrony do f1 = Fiber.new do m.synchronize do i << 'pi' c.wait(m) i << '-po' c.signal end end.resume f2 = Fiber.new do m.synchronize do i << 'ng' c.signal c.wait(m) i << 'ng' end EM.stop end.resume end i.should == 'ping-pong' end it 'should not raise, when timer wakes up fiber between `signal` and `next_tick`' do proc { EM.synchrony do f = Fiber.new do m.synchronize do c.wait(m, 0.0001) end EM.add_timer(0.001){ EM.stop } end i = 0 f.resume EM.next_tick{ c.signal } end }.should_not raise_error end end end em-synchrony-1.0.5/spec/beanstalk_spec.rb0000644000004100000410000000137212706127034020442 0ustar www-datawww-datarequire 'spec/helper/all' DELAY = 0.25 __END__ describe EMJack do it "should fire sequential Beanstalk requests" do pending EventMachine.run do Fiber.new { jack = EMJack::Connection.new r = jack.use('mytube') r.should == 'mytube' EventMachine.stop }.resume end end it "should fire multiple requests in parallel" do pending EventMachine.run do Fiber.new { jack = EMJack::Connection.new multi = EventMachine::Multi.new multi.add jack.ause('mytube-1') multi.add jack.ause('mytube-2') res = multi.perform res.responses.size.should == 2 p [:multi, res.responses] EventMachine.stop }.resume end end end em-synchrony-1.0.5/spec/http_spec.rb0000644000004100000410000000552212706127034017456 0ustar www-datawww-datarequire "spec/helper/all" URL = "http://localhost:8081/" CONNECTION_ERROR_URL = "http://random-domain-blah.com/" DELAY = 0.25 describe EventMachine::HttpRequest do it "should perform a synchronous fetch" do EM.synchrony do s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", DELAY) r = EventMachine::HttpRequest.new(URL).get r.response.should == 'Foo' s.stop EventMachine.stop end end it "should fire sequential requests" do EventMachine.synchrony do s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", DELAY) start = now order = [] order.push :get if EventMachine::HttpRequest.new(URL).get order.push :post if EventMachine::HttpRequest.new(URL).post order.push :head if EventMachine::HttpRequest.new(URL).head order.push :post if EventMachine::HttpRequest.new(URL).delete order.push :put if EventMachine::HttpRequest.new(URL).put order.push :options if EventMachine::HttpRequest.new(URL).options order.push :patch if EventMachine::HttpRequest.new(URL).patch (now - start.to_f).should be_within(DELAY * order.size * 0.15).of(DELAY * order.size) order.should == [:get, :post, :head, :post, :put, :options, :patch] s.stop EventMachine.stop end end it "should fire simultaneous requests via Multi interface" do EventMachine.synchrony do s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", DELAY) start = now multi = EventMachine::Synchrony::Multi.new multi.add :a, EventMachine::HttpRequest.new(URL).aget multi.add :b, EventMachine::HttpRequest.new(URL).apost multi.add :c, EventMachine::HttpRequest.new(URL).ahead multi.add :d, EventMachine::HttpRequest.new(URL).adelete multi.add :e, EventMachine::HttpRequest.new(URL).aput multi.add :f, EventMachine::HttpRequest.new(URL).aoptions multi.add :g, EventMachine::HttpRequest.new(URL).apatch res = multi.perform (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY) res.responses[:callback].size.should == 7 res.responses[:errback].size.should == 0 s.stop EventMachine.stop end end it "should terminate immediately in case of connection errors" do EventMachine.synchrony do response = EventMachine::HttpRequest.new(CONNECTION_ERROR_URL, :connection_timeout => 0.1).get response.error.should_not be_nil EventMachine.stop end end it "should process inactivity timeout correctly" do EventMachine.synchrony do s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", 5) start = now r = EventMachine::HttpRequest.new(URL, :inactivity_timeout => 0.1).get (now - start.to_f).should be_within(0.2).of(0.1) s.stop EventMachine.stop end end end em-synchrony-1.0.5/spec/keyboard_spec.rb0000644000004100000410000000233412706127034020275 0ustar www-datawww-datarequire "spec/helper/all" require "tempfile" DELAY = 0.1 describe EventMachine::Synchrony do before(:each) { @temp_file = Tempfile.new("stdout") } after(:each) { @temp_file.unlink } def with_input(string = "", &block) string = "#{string}\n" @temp_file.write string @temp_file.flush EM::Synchrony.add_timer(DELAY) do original_stdin = STDIN STDIN.reopen(@temp_file.path) block.call if block_given? STDIN.reopen(original_stdin) end end it "waits for input" do EM.synchrony do start = now with_input do EM::Synchrony.gets (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY) end EM.add_timer(DELAY * 2) { EM.stop } end end it "trails input with a newline to emulate gets" do EM.synchrony do with_input("Hello") do EM::Synchrony.gets.should == "Hello\n" end EM.add_timer(DELAY * 2) { EM.stop } end end it "should stop after the first line" do EM.synchrony do with_input("Hello\nWorld!") do EM::Synchrony.gets.should == "Hello\n" end EM.add_timer(DELAY * 2) { EM.stop } end end end em-synchrony-1.0.5/spec/multi_spec.rb0000644000004100000410000000133512706127034017627 0ustar www-datawww-datarequire "helper/all" describe EM::Synchrony do describe "Multi" do it "should require unique keys for each deferrable" do lambda do m = EM::Synchrony::Multi.new m.add :df1, EM::DefaultDeferrable.new m.add :df1, EM::DefaultDeferrable.new end.should raise_error("Duplicate Multi key") end context "when defferable succeeded before adding" do it "does not succeed twice" do multi = EM::Synchrony::Multi.new multi.should_receive(:succeed).once slow = EM::DefaultDeferrable.new multi.add :slow, slow quick = EM::DefaultDeferrable.new quick.succeed multi.add :quick, quick slow.succeed end end end end em-synchrony-1.0.5/spec/synchrony_spec.rb0000644000004100000410000000152712706127034020534 0ustar www-datawww-datarequire "helper/all" describe EM::Synchrony do describe "#sync" do it "returns immediately if the syncee already succeeded" do args = stub("args") Fiber.new { df = EM::DefaultDeferrable.new df.succeed args EM::Synchrony.sync(df).should == args df = EM::DefaultDeferrable.new df.succeed nil EM::Synchrony.sync(df).should == nil }.resume end end describe "#next_tick" do it "should wrap next_tick into a Fiber context" do EM.synchrony { begin fired = false f = Fiber.current EM::Synchrony.next_tick do fired = true Fiber.current.should_not eq(f) end EM::Synchrony.interrupt fired.should eq(true) ensure EM.stop end } end end end em-synchrony-1.0.5/spec/helper/0000755000004100000410000000000012706127034016413 5ustar www-datawww-dataem-synchrony-1.0.5/spec/helper/stub-http-server.rb0000644000004100000410000000070512706127034022200 0ustar www-datawww-dataclass StubServer module Server attr_accessor :response, :delay def receive_data(data) EM.add_timer(@delay) { send_data @response close_connection_after_writing } end end def initialize(response, delay = 0, port=8081) @sig = EventMachine::start_server("127.0.0.1", port, Server) { |s| s.response = response s.delay = delay } end def stop EventMachine.stop_server @sig end end em-synchrony-1.0.5/spec/helper/all.rb0000644000004100000410000000100412706127034017503 0ustar www-datawww-datarequire 'spec/helper/core' require 'lib/em-synchrony/mysql2' require 'lib/em-synchrony/em-remcached' require 'lib/em-synchrony/em-memcache' require 'lib/em-synchrony/em-mongo' require 'lib/em-synchrony/em-redis' require 'lib/em-synchrony/em-hiredis' require 'lib/em-synchrony/amqp' require 'helper/tolerance_matcher' require 'helper/stub-http-server' def now(); Time.now.to_f; end RSpec.configure do |config| config.include(Sander6::CustomMatchers) config.filter_run_excluding ci_skip: true if ENV['CI'] end em-synchrony-1.0.5/spec/helper/core.rb0000644000004100000410000000015712706127034017673 0ustar www-datawww-datarequire 'rubygems' require 'rspec' require 'pp' require 'lib/em-synchrony' require 'lib/em-synchrony/em-http' em-synchrony-1.0.5/spec/helper/tolerance_matcher.rb0000644000004100000410000000176012706127034022423 0ustar www-datawww-data# # courtesy of sander 6 # - http://github.com/sander6/custom_matchers/blob/master/lib/matchers/tolerance_matchers.rb # module Sander6 module CustomMatchers class ToleranceMatcher def initialize(tolerance) @tolerance = tolerance @target = 0 end def of(target) @target = target self end def matches?(value) @value = value ((@target - @tolerance)..(@target + @tolerance)).include?(@value) end def failure_message "Expected #{@value.inspect} to be within #{@tolerance.inspect} of #{@target.inspect}, but it wasn't.\n" + "Difference: #{@value - @target}" end def negative_failure_message "Expected #{@value.inspect} not to be within #{@tolerance.inspect} of #{@target.inspect}, but it was.\n" + "Difference: #{@value - @target}" end end def be_within(value) Sander6::CustomMatchers::ToleranceMatcher.new(value) end end end em-synchrony-1.0.5/spec/system_spec.rb0000644000004100000410000000037512706127034020024 0ustar www-datawww-datarequire "spec/helper/all" describe EventMachine::Synchrony do it "system: simple" do EM.synchrony do out, status = EM::Synchrony.system("echo 'some'") status.should == 0 out.should == "some\n" EM.stop end end end em-synchrony-1.0.5/spec/mysql2_spec.rb0000644000004100000410000000576312706127034017735 0ustar www-datawww-datarequire "spec/helper/all" require "em-synchrony/mysql2" describe Mysql2::EM::Client do DELAY = 0.25 QUERY = "SELECT sleep(#{DELAY}) as mysql2_query" it "should support queries" do res = [] EventMachine.synchrony do db = Mysql2::EM::Client.new res = db.query QUERY EventMachine.stop end res.first.keys.should include("mysql2_query") end it "should fire sequential, synchronous requests" do EventMachine.synchrony do db = Mysql2::EM::Client.new start = now res = [] res.push db.query QUERY res.push db.query QUERY (now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size) EventMachine.stop end end it "should have accept a callback, errback on async queries" do EventMachine.synchrony do db = Mysql2::EM::Client.new res = db.aquery(QUERY) res.errback {|r| fail } res.callback {|r| r.size.should == 1 EventMachine.stop } end end it "should fire simultaneous requests via Multi interface" do EventMachine.synchrony do db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do Mysql2::EM::Client.new end start = now multi = EventMachine::Synchrony::Multi.new multi.add :a, db.aquery(QUERY) multi.add :b, db.aquery(QUERY) res = multi.perform (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY) res.responses[:callback].size.should == 2 res.responses[:errback].size.should == 0 EventMachine.stop end end it "should fire sequential and simultaneous MySQL requests" do EventMachine.synchrony do db = EventMachine::Synchrony::ConnectionPool.new(size: 3) do Mysql2::EM::Client.new end start = now res = [] res.push db.query(QUERY) res.push db.query(QUERY) (now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size) start = now multi = EventMachine::Synchrony::Multi.new multi.add :a, db.aquery(QUERY) multi.add :b, db.aquery(QUERY) multi.add :c, db.aquery(QUERY) res = multi.perform (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY) res.responses[:callback].size.should == 3 res.responses[:errback].size.should == 0 EventMachine.stop end end it "should raise Mysql::Error in case of error" do EventMachine.synchrony do db = Mysql2::EM::Client.new proc { db.query("SELECT * FROM i_hope_this_table_does_not_exist;") }.should raise_error(Mysql2::Error) EventMachine.stop end end it "errback should not catch exception thrown from callback" do class ErrbackShouldNotCatchThis < Exception; end proc { EM.synchrony do db = Mysql2::EM::Client.new res = db.query QUERY raise ErrbackShouldNotCatchThis.new("errback should not catch this") EventMachine.stop end }.should raise_error(ErrbackShouldNotCatchThis) end end em-synchrony-1.0.5/spec/activerecord_spec.rb0000644000004100000410000000541512706127034021152 0ustar www-datawww-datarequire "spec/helper/all" require "em-synchrony/activerecord" require "em-synchrony/fiber_iterator" # mysql < spec/widgets.sql class Widget < ActiveRecord::Base; end; describe "Fiberized ActiveRecord driver for mysql2" do DELAY = 0.25 QUERY = "SELECT sleep(#{DELAY})" LOGGER = Logger.new(STDOUT).tap do |logger| logger.formatter = proc do |_severity, datetime, _progname, msg| "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')} ##{Fiber.current.object_id}] -- : #{msg}\n" end end before(:all) do ActiveRecord::Base.logger = LOGGER if ENV['LOGGER'] end def establish_connection ActiveRecord::Base.establish_connection( :adapter => 'em_mysql2', :database => 'widgets', :username => 'root', :pool => 10 ) Widget.delete_all end it "should establish AR connection" do EventMachine.synchrony do establish_connection result = Widget.find_by_sql(QUERY) result.size.should eql(1) EventMachine.stop end end it "should fire sequential, synchronous requests within single fiber" do EventMachine.synchrony do establish_connection start = now res = [] res.push Widget.find_by_sql(QUERY) res.push Widget.find_by_sql(QUERY) (now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size) res.size.should eql(2) EventMachine.stop end end it "should fire 100 requests in fibers" do EM.synchrony do establish_connection EM::Synchrony::FiberIterator.new(1..100, 40).each do |i| widget = Widget.create title: 'hi' widget.update_attributes title: 'hello' end EM.stop end end it "should create widget" do EM.synchrony do establish_connection Widget.create Widget.create Widget.count.should eql(2) EM.stop end end it "should update widget" do EM.synchrony do establish_connection ActiveRecord::Base.connection.execute("TRUNCATE TABLE widgets;") widget = Widget.create title: 'hi' widget.update_attributes title: 'hello' Widget.find(widget.id).title.should eql('hello') EM.stop end end describe "transactions" do it "should work properly" do EM.synchrony do establish_connection EM::Synchrony::FiberIterator.new(1..50, 30).each do |i| widget = Widget.create title: "hi#{i}" ActiveRecord::Base.transaction do widget.update_attributes title: "hello" end ActiveRecord::Base.transaction do widget.update_attributes(title: 'hey') raise ActiveRecord::Rollback end end Widget.all.each do |widget| widget.title.should eq('hello') end EM.stop end end end end em-synchrony-1.0.5/spec/remcached_spec.rb0000644000004100000410000000165412706127034020414 0ustar www-datawww-datarequire "spec/helper/all" require "remcached" describe Memcached do it "should yield until connection is ready" do EventMachine.synchrony do Memcached.connect %w(localhost) Memcached.usable?.should eq(true) EventMachine.stop end end it "should fire sequential memcached requests" do EventMachine.synchrony do Memcached.connect %w(localhost) Memcached.get(key: 'hai') do |res| res[:value].should match('Not found') end Memcached.set(key: 'hai', value: 'win') Memcached.add(key: 'count') Memcached.delete(key: 'hai') EventMachine.stop end end it "should fire multi memcached requests" do skip "patch mult-get" EventMachine.synchrony do Memcached.connect %w(localhost) Memcached.multi_get([{:key => 'foo'},{:key => 'bar'}, {:key => 'test'}]) do |res| # TODO EventMachine.stop end end end end em-synchrony-1.0.5/spec/em-mongo_spec.rb0000644000004100000410000002034412706127034020214 0ustar www-datawww-datarequire "spec/helper/all" describe EM::Mongo do it "should yield until connection is ready" do EventMachine.synchrony do connection = EM::Mongo::Connection.new connection.connected?.should eq(true) db = connection.db('db') db.is_a?(EventMachine::Mongo::Database).should eq(true) EventMachine.stop end end describe 'Synchronously (find & first)' do it "should insert a record into db" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection obj = collection.insert('hello' => 'world') obj.should be_a(BSON::ObjectId) obj = collection.find obj.size.should == 1 obj.first['hello'].should == 'world' EventMachine.stop end end it "should insert a record into db and be able to find it" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection obj = collection.insert('hello' => 'world') obj = collection.insert('hello2' => 'world2') obj = collection.find({}) obj.size.should == 2 obj2 = collection.find({}, {:limit => 1}) obj2.size.should == 1 obj3 = collection.first obj3.is_a?(Hash).should eq(true) EventMachine.stop end end it "should be able to order results" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection collection.insert(:name => 'one', :position => 0) collection.insert(:name => 'three', :position => 2) collection.insert(:name => 'two', :position => 1) res = collection.find({}, {:order => 'position'}) res[0]["name"].should == 'one' res[1]["name"].should == 'two' res[2]["name"].should == 'three' res1 = collection.find({}, {:order => [:position, :desc]}) res1[0]["name"].should == 'three' res1[1]["name"].should == 'two' res1[2]["name"].should == 'one' EventMachine.stop end end end # # em-mongo version > 0.3.6 # if defined?(EM::Mongo::Cursor) describe '*A*synchronously (afind & afirst) [Mongo > 0.3.6, using cursor]' do it "should insert a record into db" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection obj = collection.insert('hello' => 'world') obj.should be_a(BSON::ObjectId) cursor = collection.afind cursor.should be_a(EM::Mongo::Cursor) cursor.to_a.callback do |obj| obj.size.should == 1 obj.first['hello'].should == 'world' EM.next_tick{ EventMachine.stop } end end end it "should insert a record into db and be able to find it" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection obj = collection.insert('hello' => 'world') obj = collection.insert('hello2' => 'world2') collection.afind({}).to_a.callback do |obj| obj.size.should == 2 end collection.afind({}, {:limit => 1}).to_a.callback do |obj2| obj2.size.should == 1 end collection.afirst.callback do |obj3| obj3.is_a?(Hash).should eq(true) obj3['hello'].should == 'world' EM.next_tick{ EventMachine.stop } end end end it "should be able to order results" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection collection.insert(:name => 'one', :position => 0) collection.insert(:name => 'three', :position => 2) collection.insert(:name => 'two', :position => 1) collection.afind({}, {:order => 'position'}).to_a.callback do |res| res[0]["name"].should == 'one' res[1]["name"].should == 'two' res[2]["name"].should == 'three' end collection.afind({}, {:order => [:position, :desc]}).to_a.callback do |res1| res1[0]["name"].should == 'three' res1[1]["name"].should == 'two' res1[2]["name"].should == 'one' EM.next_tick{ EventMachine.stop } end end end end else describe '*A*synchronously (afind & afirst) [Mongo <= 0.3.6, using blocks]' do it "should insert a record into db" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection obj = collection.insert('hello' => 'world') obj.should be_a(BSON::ObjectId) ret_val = collection.afind do |obj| obj.size.should == 1 obj.first['hello'].should == 'world' EM.next_tick{ EventMachine.stop } end ret_val.should be_a(Integer) end end it "should insert a record into db and be able to find it" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection obj = collection.insert('hello' => 'world') obj = collection.insert('hello2' => 'world2') collection.afind({}) do |obj| obj.size.should == 2 end collection.afind({}, {:limit => 1}) do |obj2| obj2.size.should == 1 end collection.afirst do |obj3| obj3.is_a?(Hash).should eq(true) obj3['hello'].should == 'world' EM.next_tick{ EventMachine.stop } end end end it "should be able to order results" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection collection.insert(:name => 'one', :position => 0) collection.insert(:name => 'three', :position => 2) collection.insert(:name => 'two', :position => 1) collection.afind({}, {:order => 'position'}) do |res| res[0]["name"].should == 'one' res[1]["name"].should == 'two' res[2]["name"].should == 'three' end collection.afind({}, {:order => [:position, :desc]}) do |res1| res1[0]["name"].should == 'three' res1[1]["name"].should == 'two' res1[2]["name"].should == 'one' EM.next_tick{ EventMachine.stop } end end end end end it "should update records in db" do EventMachine.synchrony do collection = EM::Mongo::Connection.new.db('db').collection('test') collection.remove({}) # nuke all keys in collection obj_id = collection.insert('hello' => 'world') collection.update({'hello' => 'world'}, {'hello' => 'newworld'}) new_obj = collection.first({'_id' => obj_id}) new_obj['hello'].should == 'newworld' EventMachine.stop end end context "authentication" do # these specs only get asserted if you run mongod with the --auth flag it "successfully authenticates", ci_skip: true do # For this spec you will need to add this user to the database # # From the Mongo shell: # > use db # > db.addUser('test', 'test') EventMachine.synchrony do database = EM::Mongo::Connection.new.db('db') database.authenticate('test', 'test').should == true EventMachine.stop end end it "raises an AuthenticationError if it cannot authenticate" do EventMachine.synchrony do database = EM::Mongo::Connection.new.db('db') proc { database.authenticate('test', 'wrong_password') }.should raise_error(EventMachine::Mongo::AuthenticationError, "auth fails") EventMachine.stop end end end end em-synchrony-1.0.5/.travis.yml0000644000004100000410000000104112706127034016307 0ustar www-datawww-datalanguage: ruby rvm: - 1.9.3 sudo: false services: - mysql - rabbitmq - mongodb - memcached - redis-server before_script: - mysql < spec/widgets.sql matrix: include: - env: "activerecord=3.2.21" script: "bundle exec rspec spec/activerecord_spec.rb" - env: "activerecord=4.0.9" script: "bundle exec rspec spec/activerecord_spec.rb" - env: "activerecord=4.2.0" script: "bundle exec rspec spec/activerecord_spec.rb" allow_failures: - env: "activerecord=3.2.21" - env: "activerecord=4.2.0"em-synchrony-1.0.5/lib/0000755000004100000410000000000012706127034014750 5ustar www-datawww-dataem-synchrony-1.0.5/lib/active_record/0000755000004100000410000000000012706127034017561 5ustar www-datawww-dataem-synchrony-1.0.5/lib/active_record/connection_adapters/0000755000004100000410000000000012706127034023603 5ustar www-datawww-dataem-synchrony-1.0.5/lib/active_record/connection_adapters/em_mysql2_adapter.rb0000644000004100000410000000373012706127034027543 0ustar www-datawww-data# encoding: utf-8 # AR adapter for using a fibered mysql2 connection with EM # This adapter should be used within Thin or Unicorn with the rack-fiber_pool middleware. # Just update your database.yml's adapter to be 'em_mysql2' # to real connection pool size. require 'em-synchrony/mysql2' require 'em-synchrony/activerecord' require 'active_record/connection_adapters/mysql2_adapter' module ActiveRecord class Base def self.em_mysql2_connection(config) client = EM::Synchrony::ActiveRecord::ConnectionPool.new(size: config[:pool]) do conn = ActiveRecord::ConnectionAdapters::EMMysql2Adapter::Client.new(config.symbolize_keys) # From Mysql2Adapter#configure_connection conn.query_options.merge!(:as => :array) # By default, MySQL 'where id is null' selects the last inserted id. # Turn this off. http://dev.rubyonrails.org/ticket/6778 variable_assignments = ['SQL_AUTO_IS_NULL=0'] encoding = config[:encoding] variable_assignments << "NAMES '#{encoding}'" if encoding wait_timeout = config[:wait_timeout] wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum) variable_assignments << "@@wait_timeout = #{wait_timeout}" conn.query("SET #{variable_assignments.join(', ')}") conn end options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0] ActiveRecord::ConnectionAdapters::EMMysql2Adapter.new(client, logger, options, config) end end module ConnectionAdapters class EMMysql2Adapter < ::ActiveRecord::ConnectionAdapters::Mysql2Adapter class Client < Mysql2::EM::Client include EM::Synchrony::ActiveRecord::Client end if ::ActiveRecord.version >= Gem::Version.new('4.2') require 'em-synchrony/activerecord_4_2' include EM::Synchrony::ActiveRecord::Adapter_4_2 else include EM::Synchrony::ActiveRecord::Adapter end end end end em-synchrony-1.0.5/lib/em-synchrony.rb0000644000004100000410000001041712706127034017733 0ustar www-datawww-data$:.unshift(File.dirname(__FILE__) + '/../lib') require "eventmachine" begin require "fiber" rescue LoadError => error raise error unless defined? Fiber end require "em-synchrony/core_ext" require "em-synchrony/thread" require "em-synchrony/em-multi" require "em-synchrony/tcpsocket" require "em-synchrony/connection_pool" require "em-synchrony/keyboard" require "em-synchrony/iterator" if EventMachine::VERSION > '0.12.10' require "em-synchrony/kernel" module EventMachine # A convenience method for wrapping a given block within # a Ruby Fiber such that async operations can be transparently # paused and resumed based on IO scheduling. # It detects whether EM is running or not. def self.synchrony(blk=nil, tail=nil) # EM already running. if reactor_running? if block_given? Fiber.new { yield }.resume else Fiber.new { blk.call }.resume end tail && add_shutdown_hook(tail) # EM not running. else if block_given? run(nil, tail) { Fiber.new { yield }.resume } else run(Proc.new { Fiber.new { blk.call }.resume }, tail) end end end module Synchrony # sync is a close relative to inlineCallbacks from Twisted (Python) # # Synchrony.sync allows you to write sequential code while using asynchronous # or callback-based methods under the hood. Example: # # result = EM::Synchrony.sync EventMachine::HttpRequest.new(URL).get # p result.response # # As long as the asynchronous function returns a Deferrable object, which # has a "callback" and an "errback", the sync methond will automatically # yield and automatically resume your code (via Fibers) when the call # either succeeds or fails. You do not need to patch or modify the # Deferrable object, simply pass it to EM::Synchrony.sync # def self.sync(df) f = Fiber.current xback = proc do |*args| if f == Fiber.current return args.size == 1 ? args.first : args else f.resume(*args) end end df.callback(&xback) df.errback(&xback) Fiber.yield end # Fiber-aware sleep function using an EM timer # # Execution is stopped for specified amount of seconds # and then automatically resumed (just like regular sleep) # except without locking the reactor thread # def self.sleep(secs) fiber = Fiber.current EM::Timer.new(secs) { fiber.resume } Fiber.yield end # Fiber-aware EventMachine timer: wraps the passed in # block within a new fiber context such that you can # continue using synchrony methods # def self.add_timer(interval, &blk) EM::Timer.new(interval) do Fiber.new { blk.call }.resume end end # Fiber-aware EventMachine timer: wraps the passed in # block within a new fiber (new fiber on every invocation) # to allow you to continue using synchrony methods # def self.add_periodic_timer(interval, &blk) EM.add_periodic_timer(interval) do Fiber.new { blk.call }.resume end end # Fiber-aware EM.next_tick convenience function # def self.next_tick(&blk) EM.next_tick { Fiber.new { blk.call }.resume } end # Fiber-aware EM.defer # def self.defer op = nil, &blk fiber = Fiber.current EM.defer(op || blk, lambda{ |result| fiber.resume(result) }) Fiber.yield end # Fiber-aware EM.system # def self.system cmd, *args fiber = Fiber.current EM.system(cmd, *args){ |out, status| fiber.resume( [out, status] ) } Fiber.yield end # Overrides behavior of kernel.sleep # Allows to add custom behavior, e.g. logging or redirection to # EM::Synchrony.sleep . # Calling 'sleep' in this function calls the actual kernel method. # def self.on_sleep(&blk) Kernel.em_synchrony_sleep_hook = blk end # Routes to EM::Synchrony::Keyboard # def self.gets EventMachine::Synchrony::Keyboard.new.gets end # Interrupt current fiber to give chance to other fibers # def self.interrupt fiber = Fiber.current EM.next_tick { fiber.resume } Fiber.yield end end end # Alias for {EventMachine::Synchrony} EM::S = EventMachine::Synchronyem-synchrony-1.0.5/lib/em-synchrony/0000755000004100000410000000000012706127034017403 5ustar www-datawww-dataem-synchrony-1.0.5/lib/em-synchrony/activerecord_4_2.rb0000644000004100000410000000266612706127034023060 0ustar www-datawww-datarequire 'active_record' module EM::Synchrony module ActiveRecord module Adapter_4_2 def configure_connection nil end def transaction(*args) @connection.execute(false) do |conn| super end end def reset_transaction #:nodoc: @transaction_manager = TransactionManager.new(self) end end class TransactionManager < ::ActiveRecord::ConnectionAdapters::TransactionManager def initialize(*args) super @stack = Hash.new { |h, k| h[k] = [] } end def current_transaction #:nodoc: _current_stack.last || NULL_TRANSACTION end def open_transactions _current_stack.size end def begin_transaction(options = {}) #:nodoc: transaction = if _current_stack.empty? ::ActiveRecord::ConnectionAdapters::RealTransaction.new(@connection, options) else ::ActiveRecord::ConnectionAdapters::SavepointTransaction.new(@connection, "active_record_#{Fiber.current.object_id}_#{open_transactions}", options) end _current_stack.push(transaction) transaction end def commit_transaction #:nodoc: _current_stack.pop.commit end def rollback_transaction #:nodoc: _current_stack.pop.rollback end private def _current_stack @stack[Fiber.current.object_id] end end end end em-synchrony-1.0.5/lib/em-synchrony/core_ext.rb0000644000004100000410000000031012706127034021532 0ustar www-datawww-datamodule Kernel if !self.methods.include?(:silence_warnings) def silence_warnings old_verbose, $VERBOSE = $VERBOSE, nil yield ensure $VERBOSE = old_verbose end end end em-synchrony-1.0.5/lib/em-synchrony/mysql2.rb0000644000004100000410000000175712706127034021171 0ustar www-datawww-databegin require 'mysql2/em' rescue LoadError => error raise 'Missing EM-Synchrony dependency: gem install mysql2' end module Mysql2 module EM class Client module Watcher def notify_readable detach begin result = @client.async_result rescue Exception => e @deferable.fail(e) else @deferable.succeed(result) end end end alias :aquery :query def query(sql, opts={}) deferable = aquery(sql, opts) # if EM is not running, we just get the sql result directly # if we get a deferable, then let's do the deferable thing. return deferable unless deferable.kind_of? ::EM::DefaultDeferrable f = Fiber.current deferable.callback { |res| f.resume(res) } deferable.errback { |err| f.resume(err) } Fiber.yield.tap do |result| raise result if result.is_a?(::Exception) end end end end end em-synchrony-1.0.5/lib/em-synchrony/tcpsocket.rb0000644000004100000410000002462112706127034021734 0ustar www-datawww-datamodule EventMachine module Synchrony class TCPSocket < Connection class << self alias_method :_old_new, :new def new(*args) if args.size == 1 _old_new(*args) else # In TCPSocket, new against an unknown hostname raises SocketError with # a message "getaddrinfo: nodename nor servname provided, or not known". # In EM, connect against an unknown hostname raises EM::ConnectionError # with a message of "unable to resolve server address" begin socket = EventMachine::connect(*args[0..1], self) rescue EventMachine::ConnectionError => e raise SocketError, e.message end # In TCPSocket, new against a closed port raises Errno::ECONNREFUSED. # In EM, connect against a closed port result in a call to unbind with # a reason param of Errno::ECONNREFUSED as a class, not an instance. unless socket.sync(:in) # wait for connection raise socket.unbind_reason.new if socket.unbind_reason.is_a? Class raise SocketError end socket end end alias :open :new end def post_init @in_buff, @out_buff = '', '' @in_req = @out_req = @unbind_reason = @read_type = nil @opening = true @closed = @remote_closed = false end def closed? # In TCPSocket, # closed? on a remotely closed socket, when we've not yet read EOF, returns false # closed? on a remotely closed socket, when we've read EOF, returns false # closed? on a socket after #close, returns true # Therefore, we set @close to true when #close is called, but not when unbind is. @closed end # direction must be one of :in or :out def sync(direction) req = self.instance_variable_set "@#{direction.to_s}_req", EventMachine::DefaultDeferrable.new EventMachine::Synchrony.sync req ensure self.instance_variable_set "@#{direction.to_s}_req", nil end # TCPSocket interface def setsockopt(level, name, value); end def send(msg, flags) raise "Unknown flags in send(): #{flags}" if flags.nonzero? # write(X) on a socket after #close, raises IOError with message "closed stream" # send(X,0) on a socket after #close, raises IOError with message "closed stream" raise IOError, "closed stream" if @closed # the first write(X) on a remotely closed socket, <= than some buffer size, generates no error # the first write(X) on a remotely closed socket, > than some buffer size, generates no error # (on my box this buffer appears to be 80KB) # further write(X) on a remotely closed socket, raises Errno::EPIPE # the first send(X,0) on a remotely closed socket, <= than some buffer size, generates no error # the first send(X,0) on a remotely closed socket, > than some buffer size, generates no error # (on my box this buffer appears to be 80KB) # further send(X,0) on a remotely closed socket, raises Errno::EPIPE raise Errno::EPIPE if @remote_closed len = msg.bytesize # write(X) on an open socket, where the remote end closes during the write, raises Errno::EPIPE # send(X,0) on an open socket, where the remote end closes during the write, raises Errno::EPIPE write_data(msg) or sync(:out) or raise(Errno::EPIPE) len end def write(msg) send(msg,0) end def read(num_bytes = nil, dest = nil) handle_read(:read, num_bytes, dest) end def read_nonblock(maxlen, dest = nil) raise ArgumentError, "maxlen must be > 0" if !maxlen || maxlen <= 0 read_bytes = handle_read(:read_nonblock, maxlen, dest) raise EOFError if read_bytes.nil? read_bytes end def recv(num_bytes, flags = 0) raise "Unknown flags in recv(): #{flags}" if flags.nonzero? handle_read(:recv, num_bytes) end def close # close on a closed socket raises IOError with a message of "closed stream" raise IOError, "closed stream" if @closed @closed = true close_connection true @in_req = @out_req = nil # close on an open socket returns nil nil end # EventMachine interface def connection_completed @opening = false @in_req.succeed self end attr_reader :unbind_reason # Can't set a default value for reason (e.g. reason=nil), as in that case # EM fails to pass in the reason argument and you'll always get the default # value. def unbind(reason) @unbind_reason = reason @remote_closed = true unless @closed if @opening @in_req.fail nil if @in_req else @in_req.succeed read_data if @in_req end @out_req.fail nil if @out_req @in_req = @out_req = nil end def receive_data(data) @in_buff << data if @in_req && (data = read_data) @in_req.succeed data unless data == :block end end protected def handle_read(type, num_bytes, dest=nil) # read(-n) always raises ArgumentError # recv(-n) always raises ArgumentError raise ArgumentError, "negative length #{num_bytes} given" if num_bytes != nil and num_bytes < 0 # read(n) on a socket after #close, raises IOError with message "closed stream" # read(0) on a socket after #close, raises IOError with message "closed stream" # read() on a socket after #close, raises IOError with message "closed stream" # recv(n) on a socket after #close, raises IOError with message "closed stream" # recv(0) on a socket after #close, raises IOError with message "closed stream" raise IOError, "closed stream" if @closed # read(0) on an open socket, return "" # read(0) on a remotely closed socket, with buffered data, returns "" # read(0) on a remotely closed socket, with no buffered data, returns "" # recv(0) on an open socket, return "" # recv(0) on a remotely closed socket, with buffered data, returns "" # recv(0) on a remotely closed socket, with no buffered data, returns "" return "" if num_bytes == 0 @read_type = type @read_bytes = num_bytes @read_dest = dest if dest (data = read_data) != :block ? data : sync(:in) end def try_read_data if @read_type == :read || @read_type == :read_nonblock nonblocking = @read_type == :read_nonblock unless @remote_closed if @read_bytes # read(n) on an open socket, with >= than n buffered data, returns n data if (@in_buff.size >= @read_bytes || (nonblocking && @in_buff.size > 0)) then @in_buff.slice!(0, @read_bytes) # read(n) on an open socket, with < than n buffered data, blocks else :block end else # read() on an open socket, blocks until a remote close and returns all the data sent :block end else if @read_bytes # read(n) on a remotely closed socket, with no buffered data, returns nil if @in_buff.empty? then nil # read(n) on a remotely closed socket, with buffered data, returns the buffered data up to n else @in_buff.slice!(0, @read_bytes) end else # read() on a remotely closed socket, with no buffered data, returns "" if @in_buff.empty? then "" # read() on a remotely closed socket, with buffered data, returns the buffered data else @in_buff.slice!(0, @in_buff.size) end end end else #recv unless @remote_closed # recv(n) on an open socket, with no buffered data, blocks if @in_buff.empty? then :block # recv(n) on an open socket, with < than n buffered data, return the buffered data # recv(n) on an open socket, with >= than n buffered data, returns n data else @in_buff.slice!(0, @read_bytes) end else # recv(n) on a remotely closed socket, with no buffered data, returns "" if @in_buff.empty? then "" # recv(n) on a remotely closed socket, with < than n buffered data, return the buffered data # recv(n) on a remotely closed socket, with >= than n buffered data, returns n data else @in_buff.slice!(0, @read_bytes) end end end end def read_data data = try_read_data unless data == :block @read_bytes = 0 # read(n,buffer) returns the buffer when it does not return nil or raise an exception data = @read_dest.replace(data) if @read_dest and not data.nil? @read_dest = nil end data end def write_data(data = nil) @out_buff += data if data loop do if @out_buff.empty? @out_req.succeed true if @out_req return true end if self.get_outbound_data_size > EventMachine::FileStreamer::BackpressureLevel # write(X) on an open socket, where the remote end is not reading, > than some buffer size, blocks # send(X,0) on an open socket, where the remote end is not reading, > than some buffer size, blocks # where that buffer size is EventMachine::FileStreamer::BackpressureLevel, returning false will # cause write/send to block EventMachine::next_tick { write_data } return false else # write(X) on an open socket, where the remote end is not reading, <= than some buffer size, sends and returns # send(X,0) on an open socket, where the remote end is not reading, <= than some buffer size, sends returns len = [@out_buff.bytesize, EventMachine::FileStreamer::ChunkSize].min self.send_data @out_buff.slice!( 0, len ) end end end end end end em-synchrony-1.0.5/lib/em-synchrony/em-memcache.rb0000644000004100000410000000067412706127034022100 0ustar www-datawww-datamodule EventMachine::Protocols::Memcache %w[delete get set].each do |type| module_eval %[ alias :a#{type} :#{type} def #{type}(*params, &blk) f = Fiber.current self.a#{type}(*params) { |*cb_params| f.resume(*cb_params) } Fiber.yield end ] end alias :aget_hash :get_hash def get_hash(*keys) index = 0 get(*keys).inject({}) { |h,v| h[keys[index]] = v; index += 1; h } end end em-synchrony-1.0.5/lib/em-synchrony/amqp.rb0000644000004100000410000001372712706127034020700 0ustar www-datawww-databegin require "amqp" require "amq/protocol" rescue LoadError raise "Missing EM-Synchrony dependency: gem install amqp" end module EventMachine module Synchrony module AMQP class Error < RuntimeError; end class << self def sync &blk fiber = Fiber.current blk.call(fiber) Fiber.yield end def sync_cb fiber lambda do |*args| if fiber == Fiber.current return *args else fiber.resume(*args) end end end %w[connect start run].each do |type| line = __LINE__ + 2 code = <<-EOF def #{type}(*params) sync { |f| ::AMQP.#{type}(*params, &sync_cb(f)) } end EOF module_eval(code, __FILE__, line) end end class Channel < ::AMQP::Channel def initialize(*params, &block) f = Fiber.current super(*params, &EM::Synchrony::AMQP.sync_cb(f)) channel, open_ok = Fiber.yield raise Error.new unless open_ok.is_a?(::AMQ::Protocol::Channel::OpenOk) channel end %w[direct fanout topic headers].each do |type| line = __LINE__ + 2 code = <<-EOF alias :a#{type} :#{type} def #{type}(name = 'amq.#{type}', opts = {}) if exchange = find_exchange(name) extended_opts = Exchange.add_default_options(:#{type}, name, opts, nil) validate_parameters_match!(exchange, extended_opts, :exchange) exchange else register_exchange(Exchange.new(self, :#{type}, name, opts)) end end EOF module_eval(code, __FILE__, line) end alias :aqueue! :queue! def queue!(name, opts = {}) queue = Queue.new(self, name, opts) register_queue(queue) end %w[flow recover tx_select tx_commit tx_rollback reset] .each do |type| line = __LINE__ + 2 code = <<-EOF alias :a#{type} :#{type} def #{type}(*params) EM::Synchrony::AMQP.sync { |f| self.a#{type}(*params, &EM::Synchrony::AMQP.sync_cb(f)) } end EOF module_eval(code, __FILE__, line) end end class Consumer < ::AMQP::Consumer alias :aon_delivery :on_delivery def on_delivery(&block) Fiber.new do aon_delivery(&EM::Synchrony::AMQP.sync_cb(Fiber.current)) loop { block.call(Fiber.yield) } end.resume self end alias :aconsume :consume def consume(nowait = false) ret = EM::Synchrony::AMQP.sync { |f| self.aconsume(nowait, &EM::Synchrony::AMQP.sync_cb(f)) } raise Error.new(ret.to_s) unless ret.is_a?(::AMQ::Protocol::Basic::ConsumeOk) self end alias :aresubscribe :resubscribe def resubscribe EM::Synchrony::AMQP.sync { |f| self.aconsume(&EM::Synchrony::AMQP.sync_cb(f)) } self end alias :acancel :cancel def cancel(nowait = false) EM::Synchrony::AMQP.sync { |f| self.aconsume(nowait, &EM::Synchrony::AMQP.sync_cb(f)) } self end end class Exchange < ::AMQP::Exchange def initialize(channel, type, name, opts = {}, &block) f = Fiber.current # AMQP Exchange constructor handles certain special exchanges differently. # The callback passed in isn't called when the response comes back # but is called immediately on the original calling fiber. That means that # when the sync_cb callback yields the fiber when called, it will hang and never # be resumed. So handle these exchanges without yielding if name == "amq.#{type}" or name.empty? or opts[:no_declare] exchange = nil super(channel, type, name, opts) { |ex| exchange = ex } else super(channel, type, name, opts, &EM::Synchrony::AMQP.sync_cb(f)) exchange, declare_ok = Fiber.yield raise Error.new(declare_ok.to_s) unless declare_ok.is_a?(::AMQ::Protocol::Exchange::DeclareOk) end exchange end alias :apublish :publish def publish payload, options = {} apublish(payload, options) end alias :adelete :delete def delete(opts = {}) EM::Synchrony::AMQP.sync { |f| adelete(opts, &EM::Synchrony::AMQP.sync_cb(f)) } end end class Queue < ::AMQP::Queue def initialize(*params) f = Fiber.current super(*params, &EM::Synchrony::AMQP.sync_cb(f)) queue, declare_ok = Fiber.yield raise Error.new unless declare_ok.is_a?(::AMQ::Protocol::Queue::DeclareOk) queue end alias :asubscribe :subscribe def subscribe(opts = {}, &block) Fiber.new do asubscribe(opts, &EM::Synchrony::AMQP.sync_cb(Fiber.current)) loop { block.call(Fiber.yield) } end.resume end %w[bind rebind unbind delete purge pop unsubscribe status].each do |type| line = __LINE__ + 2 code = <<-EOF alias :a#{type} :#{type} def #{type}(*params) EM::Synchrony::AMQP.sync { |f| self.a#{type}(*params, &EM::Synchrony::AMQP.sync_cb(f)) } end EOF module_eval(code, __FILE__, line) end end class Session < ::AMQP::Session %w[disconnect].each do |type| line = __LINE__ + 2 code = <<-EOF alias :a#{type} :#{type} def #{type}(*params) EM::Synchrony::AMQP.sync { |f| self.a#{type}(*params, &EM::Synchrony::AMQP.sync_cb(f)) } end EOF module_eval(code, __FILE__, line) end end ::AMQP.client = ::EM::Synchrony::AMQP::Session end end end em-synchrony-1.0.5/lib/em-synchrony/fiber_iterator.rb0000644000004100000410000000065212706127034022733 0ustar www-datawww-datamodule EventMachine module Synchrony class FiberIterator < EM::Synchrony::Iterator # execute each iterator block within its own fiber # and auto-advance the iterator after each call def each(foreach=nil, after=nil, &blk) fe = Proc.new do |obj, iter| Fiber.new { (foreach || blk).call(obj, iter); iter.next }.resume end super(fe, after) end end end end em-synchrony-1.0.5/lib/em-synchrony/em-remcached.rb0000644000004100000410000000403012706127034022237 0ustar www-datawww-databegin require "remcached" rescue LoadError => error raise "Missing EM-Synchrony dependency: gem install remcached" end module Memcached class << self def connect(servers) Memcached.servers = servers f = Fiber.current @w = EM::Timer.new(10.0) { f.resume :error } @t = EM::PeriodicTimer.new(0.01) do if Memcached.usable? @w.cancel @t.cancel f.resume(self) end end r = Fiber.yield (r == :error) ? (raise Exception.new('Cannot connect to memcached server')) : r end %w[add get set delete].each do |type| class_eval %[ def a#{type}(contents, &callback) df = EventMachine::DefaultDeferrable.new df.callback &callback cb = Proc.new { |res| df.succeed(res) } operation Request::#{type.capitalize}, contents, &cb df end def #{type}(contents, &callback) fiber = Fiber.current paused = false cb = Proc.new do |res| if paused fiber.resume(res) else return res end end df = a#{type}(contents, &cb) df.callback &callback paused = true Fiber.yield end ] end %w[add get set delete].each do |type| class_eval %[ def amulti_#{type}(contents, &callback) df = EventMachine::DefaultDeferrable.new df.callback &callback cb = Proc.new { |res| df.succeed(res) } multi_operation Request::#{type.capitalize}, contents, &cb df end def multi_#{type}(contents, &callback) fiber = Fiber.current paused = false cb = Proc.new do |res| if paused fiber.resume(res) else return res end end df = amulti_#{type}(contents, &cb) df.callback &callback paused = true Fiber.yield end ] end end end em-synchrony-1.0.5/lib/em-synchrony/em-multi.rb0000644000004100000410000000174712706127034021472 0ustar www-datawww-datamodule EventMachine module Synchrony class Multi include EventMachine::Deferrable attr_reader :requests, :responses def initialize @requests = {} @responses = {:callback => {}, :errback => {}} end def add(name, conn) raise 'Duplicate Multi key' if @requests.key? name @requests[name] = conn fiber = Fiber.current conn.callback { @responses[:callback][name] = conn; check_progress(fiber) } conn.errback { @responses[:errback][name] = conn; check_progress(fiber) } end def finished? (@responses[:callback].size + @responses[:errback].size) == @requests.size end def perform Fiber.yield unless finished? end protected def check_progress(fiber) if finished? succeed # continue processing fiber.resume(self) if fiber.alive? && fiber != Fiber.current end end end end end em-synchrony-1.0.5/lib/em-synchrony/em-http.rb0000644000004100000410000000122312706127034021304 0ustar www-datawww-databegin require "em-http-request" rescue LoadError => error raise "Missing EM-Synchrony dependency: gem install em-http-request" end module EventMachine module HTTPMethods %w[get head post delete put patch options].each do |type| class_eval %[ alias :a#{type} :#{type} def #{type}(options = {}, &blk) f = Fiber.current conn = setup_request(:#{type}, options, &blk) if conn.error.nil? conn.callback { f.resume(conn) } conn.errback { f.resume(conn) } Fiber.yield else conn end end ] end end end em-synchrony-1.0.5/lib/em-synchrony/em-mongo.rb0000644000004100000410000000726012706127034021453 0ustar www-datawww-databegin require "em-mongo" rescue LoadError => error raise "Missing EM-Synchrony dependency: gem install em-mongo" end module EM module Mongo class Database def authenticate(username, password) auth_result = self.collection(SYSTEM_COMMAND_COLLECTION).first({'getnonce' => 1}) auth = BSON::OrderedHash.new auth['authenticate'] = 1 auth['user'] = username auth['nonce'] = auth_result['nonce'] auth['key'] = EM::Mongo::Support.auth_key(username, password, auth_result['nonce']) auth_result2 = self.collection(SYSTEM_COMMAND_COLLECTION).first(auth) if EM::Mongo::Support.ok?(auth_result2) true else raise AuthenticationError, auth_result2["errmsg"] end end end class Connection def initialize(host = DEFAULT_IP, port = DEFAULT_PORT, timeout = nil, opts = {}) f = Fiber.current @em_connection = EMConnection.connect(host, port, timeout, opts) @db = {} # establish connection before returning EM.next_tick { f.resume } Fiber.yield end end class Collection # # The upcoming versions of EM-Mongo change Collection#find's interface: it # now returns a deferrable cursor YAY. This breaks compatibility with past # versions BOO. We'll just choose based on the presence/absence of # EM::Mongo::Cursor YAY # # # em-mongo version > 0.3.6 # if defined?(EM::Mongo::Cursor) # afind is the old (async) find # afind_one is rewritten to call afind # find is sync, using a callback on the cursor # find_one is sync, by calling find and taking the first element. # first is sync, an alias for find_one alias :afind :find def find(*args) f = Fiber.current cursor = afind(*args) cursor.to_a.callback{ |res| f.resume(res) } Fiber.yield end # need to rewrite afind_one manually, as it calls 'find' (reasonably # expecting it to be what is now known as 'afind') def afind_one(spec_or_object_id=nil, opts={}) spec = case spec_or_object_id when nil {} when BSON::ObjectId {:_id => spec_or_object_id} when Hash spec_or_object_id else raise TypeError, "spec_or_object_id must be an instance of ObjectId or Hash, or nil" end afind(spec, opts.merge(:limit => -1)).next_document end alias :afirst :afind_one def find_one(selector={}, opts={}) opts[:limit] = 1 find(selector, opts).first end alias :first :find_one # # em-mongo version <= 0.3.6 # else alias :afind :find def find(selector={}, opts={}) f = Fiber.current cb = proc { |res| f.resume(res) } skip = opts.delete(:skip) || 0 limit = opts.delete(:limit) || 0 order = opts.delete(:order) @connection.find(@name, skip, limit, order, selector, nil, &cb) Fiber.yield end # need to rewrite afirst manually, as it calls 'find' (reasonably # expecting it to be what is now known as 'afind') def afirst(selector={}, opts={}, &blk) opts[:limit] = 1 afind(selector, opts) do |res| yield res.first end end def first(selector={}, opts={}) opts[:limit] = 1 find(selector, opts).first end end end end end em-synchrony-1.0.5/lib/em-synchrony/thread.rb0000644000004100000410000000542412706127034021204 0ustar www-datawww-datamodule EventMachine module Synchrony module Thread # Fiber-aware drop-in replacements for thread objects class Mutex def initialize @waiters = [] @slept = {} end def lock current = Fiber.current raise FiberError if @waiters.include?(current) @waiters << current Fiber.yield unless @waiters.first == current true end def locked? !@waiters.empty? end def _wakeup(fiber) fiber.resume if @slept.delete(fiber) end def sleep(timeout = nil) unlock beg = Time.now current = Fiber.current @slept[current] = true if timeout timer = EM.add_timer(timeout) do _wakeup(current) end Fiber.yield EM.cancel_timer timer # if we resumes not via timer else Fiber.yield end @slept.delete current yield if block_given? lock Time.now - beg end def try_lock lock unless locked? end def unlock raise FiberError unless @waiters.first == Fiber.current @waiters.shift unless @waiters.empty? EM.next_tick{ @waiters.first.resume } end self end def synchronize lock yield ensure unlock end end class ConditionVariable # # Creates a new ConditionVariable # def initialize @waiters = [] end # # Releases the lock held in +mutex+ and waits; reacquires the lock on wakeup. # # If +timeout+ is given, this method returns after +timeout+ seconds passed, # even if no other thread doesn't signal. # def wait(mutex, timeout=nil) current = Fiber.current pair = [mutex, current] @waiters << pair mutex.sleep timeout do @waiters.delete pair end self end def _wakeup(mutex, fiber) if alive = fiber.alive? EM.next_tick { mutex._wakeup(fiber) } end alive end # # Wakes up the first thread in line waiting for this lock. # def signal while (pair = @waiters.shift) break if _wakeup(*pair) end self end # # Wakes up all threads waiting for this lock. # def broadcast @waiters.each do |mutex, fiber| _wakeup(mutex, fiber) end @waiters.clear self end end end end end em-synchrony-1.0.5/lib/em-synchrony/mechanize.rb0000644000004100000410000000123612706127034021675 0ustar www-datawww-datarequire 'mechanize' module EventMachine module Synchrony class Mechanize < ::Mechanize def initialize(*args, &blk) super @agent.instance_variable_get(:@http).singleton_class.send(:include, DeferedNetHttpPersistentRequest) end module DeferedNetHttpPersistentRequest def self.included(base) base.class_eval do alias :request_without_defer :request alias :request :request_with_defer end end def request_with_defer(*args, &blk) EM::Synchrony.defer do request_without_defer(*args, &blk) end end end end end end em-synchrony-1.0.5/lib/em-synchrony/keyboard.rb0000644000004100000410000000133312706127034021530 0ustar www-datawww-datamodule EventMachine module Synchrony class Keyboard attr_reader :current_fiber, :separator def gets @current_fiber = Fiber.current EM.open_keyboard(EventMachine::Synchrony::KeyboardHandler, self) Fiber.yield end end class KeyboardHandler < EM::Connection include EM::Protocols::LineText2 def initialize(keyboard) @keyboard = keyboard end def receive_line(line) # Simulate gets by adding a trailing line feed @input = "#{line}#{$/}" close_connection end def unbind @keyboard.current_fiber.resume @input end end end end em-synchrony-1.0.5/lib/em-synchrony/connection_pool.rb0000644000004100000410000000525312706127034023125 0ustar www-datawww-datamodule EventMachine module Synchrony class ConnectionPool undef :send def initialize(opts, &block) @reserved = {} # map of in-progress connections @available = [] # pool of free connections @pending = [] # pending reservations (FIFO) opts[:size].times do @available.push(block.call) if block_given? end end # Choose first available connection and pass it to the supplied # block. This will block indefinitely until there is an available # connection to service the request. def execute(async) f = Fiber.current begin conn = acquire(f) yield conn ensure release(f) if not async end end # Returns current pool utilization. # # @return [Hash] Current utilization. def pool_status { available: @available.size, reserved: @reserved.size, pending: @pending.size } end private # Acquire a lock on a connection and assign it to executing fiber # - if connection is available, pass it back to the calling block # - if pool is full, yield the current fiber until connection is available def acquire(fiber) if conn = @available.pop @reserved[fiber.object_id] = conn conn else Fiber.yield @pending.push fiber acquire(fiber) end end # Release connection assigned to the supplied fiber and # resume any other pending connections (which will # immediately try to run acquire on the pool) def release(fiber) @available.push(@reserved.delete(fiber.object_id)) if pending = @pending.shift pending.resume end end # Allow the pool to behave as the underlying connection # # If the requesting method begins with "a" prefix, then # hijack the callbacks and errbacks to fire a connection # pool release whenever the request is complete. Otherwise # yield the connection within execute method and release # once it is complete (assumption: fiber will yield until # data is available, or request is complete) # def method_missing(method, *args, &blk) async = (method[0,1] == "a") execute(async) do |conn| df = conn.__send__(method, *args, &blk) if async fiber = Fiber.current df.callback { release(fiber) } df.errback { release(fiber) } end df end end end end end em-synchrony-1.0.5/lib/em-synchrony/mongoid.rb0000644000004100000410000000042112706127034021361 0ustar www-datawww-datarequire "em-synchrony/mongo" # disable mongoid connection initializer if defined? Rails module Rails module Mongoid class Railtie < Rails::Railtie initializers.delete_if { |i| i.name == 'verify that mongoid is configured' } end end end end em-synchrony-1.0.5/lib/em-synchrony/mongo.rb0000644000004100000410000000177012706127034021054 0ustar www-datawww-databegin require "mongo" rescue LoadError => error raise "Missing EM-Synchrony dependency: gem install mongo" end # monkey-patch Mongo to use em-synchrony's socket and thread classs old_verbose = $VERBOSE begin $VERBOSE = nil class Mongo::Connection TCPSocket = ::EventMachine::Synchrony::TCPSocket Mutex = ::EventMachine::Synchrony::Thread::Mutex ConditionVariable = ::EventMachine::Synchrony::Thread::ConditionVariable end class Mongo::Pool TCPSocket = ::EventMachine::Synchrony::TCPSocket Mutex = ::EventMachine::Synchrony::Thread::Mutex ConditionVariable = ::EventMachine::Synchrony::Thread::ConditionVariable end class EventMachine::Synchrony::MongoTimeoutHandler def self.timeout(op_timeout, ex_class, &block) f = Fiber.current timer = EM::Timer.new(op_timeout) { f.resume(nil) } res = block.call timer.cancel res end end Mongo::TimeoutHandler = EventMachine::Synchrony::MongoTimeoutHandler ensure $VERBOSE = old_verbose end em-synchrony-1.0.5/lib/em-synchrony/em-hiredis.rb0000644000004100000410000000642312706127034021763 0ustar www-datawww-databegin require 'em-hiredis' rescue LoadError => error raise 'Missing EM-Synchrony dependency: gem install em-hiredis' end module EventMachine module Hiredis def self.connect(uri = nil) client = setup(uri) EM::Synchrony.sync client.connect client end class Client def pubsub return @pubsub if @pubsub client = PubsubClient.new(@host, @port, @password, @db) EM::Synchrony.sync client.connect @pubsub = client end end class BaseClient def self.connect(host = 'localhost', port = 6379) conn = new(host, port) EM::Synchrony.sync conn.connect conn end def connect @auto_reconnect = true @connection = EM.connect(@host, @port, Connection, @host, @port) @connection.on(:closed) do if @connected @defs.each { |d| d.fail(Error.new("Redis disconnected")) } @defs = [] @deferred_status = nil @connected = false if @auto_reconnect # Next tick avoids reconnecting after for example EM.stop EM.next_tick { reconnect } end emit(:disconnected) EM::Hiredis.logger.info("#{@connection} Disconnected") else if @auto_reconnect @reconnect_failed_count += 1 @reconnect_timer = EM.add_timer(EM::Hiredis.reconnect_timeout) { @reconnect_timer = nil reconnect } emit(:reconnect_failed, @reconnect_failed_count) EM::Hiredis.logger.info("#{@connection} Reconnect failed") if @reconnect_failed_count >= 4 emit(:failed) self.fail(Error.new("Could not connect after 4 attempts")) end end end end @connection.on(:connected) do Fiber.new do @connected = true @reconnect_failed_count = 0 @failed = false select(@db) unless @db == 0 auth(@password) if @password @command_queue.each do |df, command, args| @connection.send_command(command, args) @defs.push(df) end @command_queue = [] emit(:connected) EM::Hiredis.logger.info("#{@connection} Connected") succeed if @reconnecting @reconnecting = false emit(:reconnected) end end.resume end @connection.on(:message) do |reply| if RuntimeError === reply raise "Replies out of sync: #{reply.inspect}" if @defs.empty? deferred = @defs.shift error = RedisError.new(reply.message) error.redis_error = reply deferred.fail(error) if deferred else handle_reply(reply) end end @connected = false @reconnecting = false return self end alias :old_method_missing :method_missing def method_missing(sym, *args) EM::Synchrony.sync old_method_missing(sym, *args) end end end end em-synchrony-1.0.5/lib/em-synchrony/em-redis.rb0000644000004100000410000000315112706127034021435 0ustar www-datawww-databegin require 'em-redis' rescue LoadError => error raise 'Missing EM-Synchrony dependency: gem install em-redis' end module EventMachine module Protocols module Redis attr_reader :connected class << self alias :aconnect :connect end def self.connect(*args) f = Fiber.current conn = self.aconnect(*args) conn.callback { f.resume(conn) } Fiber.yield end alias :old_call_command :call_command SYNC = ['add', 'auth'] def call_command(argv, &blk) # async commands are 'a' prefixed if (argv.first[0] == 'a') && !SYNC.include?(argv.first.to_s) argv[0] = argv[0].to_s.slice(1,argv[0].size) old_call_command(argv, &blk) else # wrap response blocks into fiber callbacks # to emulate the sync api f = Fiber.current blk = proc { |v| v } if !block_given? clb = proc { |v| f.resume(blk.call(v)) } old_call_command(argv, &clb) Fiber.yield end end # adapted from em-redis' implementation to use # the asynchronous version of mget def amapped_mget(*keys) self.amget(*keys) do |response| result = {} response.each do |value| key = keys.shift result.merge!(key => value) unless value.nil? end yield result if block_given? end end def mapped_mget(*keys) f = Fiber.current self.amapped_mget(*keys) do |values| f.resume(values) end Fiber.yield end end end end em-synchrony-1.0.5/lib/em-synchrony/kernel.rb0000644000004100000410000000114612706127034021212 0ustar www-datawww-data# encoding: UTF-8 require 'em-synchrony' # Monkey-patch module Kernel alias_method :orig_sleep, :sleep class << self attr_accessor :em_synchrony_sleep_hook end # Monkey-patch def sleep(sleep_time) if Kernel.em_synchrony_sleep_hook && EM.reactor_thread? && !Thread.current[:em_synchrony_sleep_hook_called] begin Thread.current[:em_synchrony_sleep_hook_called] = true Kernel.em_synchrony_sleep_hook.call(sleep_time) ensure Thread.current[:em_synchrony_sleep_hook_called] = false end else orig_sleep(sleep_time) end end end em-synchrony-1.0.5/lib/em-synchrony/em-jack.rb0000644000004100000410000000145512706127034021244 0ustar www-datawww-databegin require "em-jack" rescue LoadError => error raise "Missing EM-Synchrony dependency: gem install em-jack" end # WANT: namespaced under EventMachine.. would be nice :-) # NOTE: no need for "pooling" since Beanstalk supports pipelining module EMJack class Connection alias :ause :use def use(tube, &blk) return if @used_tube == tube f = Fiber.current # WANT: per command errbacks, would be nice, instead of one global # errback = Proc.new {|r| f.resume(r) } on_error {|r| f.resume(r)} @used_tube = tube @conn.send(:use, tube) # WANT: Add conditional on add_deferrable to either accept two procs, or a single block # .. two procs = callback, errback add_deferrable { |r| f.resume(r) } Fiber.yield end end end em-synchrony-1.0.5/lib/em-synchrony/iterator.rb0000644000004100000410000000220012706127034021553 0ustar www-datawww-datarequire "em/iterator" module EventMachine module Synchrony class Iterator < EM::Iterator # synchronous iterator which will wait until all the # jobs are done before returning. Unfortunately this # means that you loose ability to choose concurrency # on the fly (see iterator documentation in EM) def each(foreach=nil, after=nil, &blk) fiber = Fiber.current fe = (foreach || blk) cb = Proc.new do after.call if after fiber.resume end Fiber.yield super(fe, cb) end def map(&block) fiber = Fiber.current result = nil after = Proc.new {|res| result = res; fiber.resume } super(block, after) Fiber.yield result end def inject(obj, foreach = nil, after = nil, &block) if foreach and after super(obj, foreach, after) else fiber = Fiber.current result = nil after = Proc.new {|res| result = res; fiber.resume} super(obj, block, after) Fiber.yield result end end end end end em-synchrony-1.0.5/lib/em-synchrony/activerecord.rb0000644000004100000410000000666512706127034022417 0ustar www-datawww-datarequire 'em-synchrony' require 'active_record' require 'active_record/connection_adapters/abstract/connection_pool' require 'active_record/connection_adapters/abstract_adapter' require 'em-synchrony/thread' module ActiveRecord module ConnectionAdapters class ConnectionPool def connection _fibered_mutex.synchronize do @reserved_connections[current_connection_id] ||= checkout end end def _fibered_mutex @fibered_mutex ||= EM::Synchrony::Thread::Mutex.new end end end end module EM::Synchrony module ActiveRecord module Client def open_transactions @open_transactions ||= 0 end def open_transactions=(v) @open_transactions = v end def acquired_for_connection_pool @acquired_for_connection_pool ||= 0 end def acquired_for_connection_pool=(v) @acquired_for_connection_pool = v end end module Adapter def configure_connection nil end def transaction(*args, &blk) @connection.execute(false) do |conn| super end end def real_connection @connection.connection end def open_transactions real_connection.open_transactions end def increment_open_transactions real_connection.open_transactions += 1 end def decrement_open_transactions real_connection.open_transactions -= 1 end def current_transaction #:nodoc: @transaction[Fiber.current.object_id] || @closed_transaction end def transaction_open? current_transaction.open? end def begin_transaction(options = {}) #:nodoc: set_current_transaction(current_transaction.begin(options)) end def commit_transaction #:nodoc: set_current_transaction(current_transaction.commit) end def rollback_transaction #:nodoc: set_current_transaction(current_transaction.rollback) end def reset_transaction #:nodoc: @transaction = {} @closed_transaction = ::ActiveRecord::ConnectionAdapters::ClosedTransaction.new(self) end # Register a record with the current transaction so that its after_commit and after_rollback callbacks # can be called. def add_transaction_record(record) current_transaction.add_record(record) end protected def set_current_transaction(t) if t == @closed_transaction @transaction.delete(Fiber.current.object_id) else @transaction[Fiber.current.object_id] = t end end end class ConnectionPool < EM::Synchrony::ConnectionPool # consider connection acquired def execute(async) f = Fiber.current begin conn = acquire(f) conn.acquired_for_connection_pool += 1 yield conn ensure conn.acquired_for_connection_pool -= 1 release(f) if !async && conn.acquired_for_connection_pool == 0 end end def acquire(fiber) return @reserved[fiber.object_id] if @reserved[fiber.object_id] super end def connection acquire(Fiber.current) end # via method_missing affected_rows will be recognized as async method def affected_rows(*args, &blk) execute(false) do |conn| conn.send(:affected_rows, *args, &blk) end end end end end em-synchrony-1.0.5/.gitignore0000644000004100000410000000003612706127034016171 0ustar www-datawww-dataGemfile.lock .bundle misc pkg em-synchrony-1.0.5/LICENSE0000644000004100000410000000204112706127034015204 0ustar www-datawww-dataCopyright (c) 2011 Ilya Grigorik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. em-synchrony-1.0.5/em-synchrony.gemspec0000644000004100000410000000134712706127034020207 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) Gem::Specification.new do |s| s.name = "em-synchrony" s.version = "1.0.5" s.platform = Gem::Platform::RUBY s.authors = ["Ilya Grigorik"] s.email = ["ilya@igvita.com"] s.homepage = "http://github.com/igrigorik/em-synchrony" s.license = "MIT" s.summary = %q{Fiber aware EventMachine libraries} s.description = s.summary s.add_runtime_dependency("eventmachine", ">= 1.0.0.beta.1") s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] end em-synchrony-1.0.5/README.md0000644000004100000410000002041512706127034015463 0ustar www-datawww-data# EM-Synchrony [![Gem Version](https://badge.fury.io/rb/em-synchrony.png)](http://rubygems.org/gems/em-synchrony) [![Analytics](https://ga-beacon.appspot.com/UA-71196-10/em-synchrony/readme)](https://github.com/igrigorik/ga-beacon) [![Build Status](https://travis-ci.org/igrigorik/em-synchrony.svg?branch=master)](https://travis-ci.org/igrigorik/em-synchrony) Collection of convenience classes and primitives to help untangle evented code, plus a number of patched EM clients to make them Fiber aware. To learn more, please see: [Untangling Evented Code with Ruby Fibers](http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers). * Fiber aware ConnectionPool with sync/async query support * Fiber aware Iterator to allow concurrency control & mixing of sync / async * Fiber aware async inline support: turns any async function into sync * Fiber aware Multi-request interface for any callback enabled clients * Fiber aware TCPSocket replacement, powered by EventMachine * Fiber aware Thread, Mutex, ConditionVariable clases * Fiber aware sleep, defer, system Supported clients: * [mysql2](http://github.com/igrigorik/em-synchrony/blob/master/spec/mysql2_spec.rb): .query is synchronous, while .aquery is async (see specs) * [activerecord](http://github.com/igrigorik/em-synchrony/blob/master/spec/activerecord_spec.rb): require synchrony/activerecord, set your AR adapter to em_mysql2 and you should be good to go * [em-http-request](http://github.com/igrigorik/em-synchrony/blob/master/spec/http_spec.rb): .get, etc are synchronous, while .aget, etc are async * [em-memcached](http://github.com/igrigorik/em-synchrony/blob/master/spec/memcache_spec.rb) & [remcached](http://github.com/igrigorik/em-synchrony/blob/master/spec/remcached_spec.rb): .get, etc, and .multi_* methods are synchronous * [em-mongo](http://github.com/igrigorik/em-synchrony/blob/master/spec/em-mongo_spec.rb): .find, .first are synchronous * [mongoid](http://github.com/igrigorik/em-synchrony/blob/master/spec/mongo_spec.rb): all functions synchronous, plus Rails compatibility * em-jack: a[method]'s are async, and all regular jack method's are synchronous * [AMQP](http://github.com/ruby-amqp/amqp): most of functions are synchronous (see specs) Other clients with native Fiber support: * redis: contains [synchrony code](https://github.com/ezmobius/redis-rb/blob/master/test/synchrony_driver.rb) right within the driver * synchrony also supports [em-redis](http://github.com/igrigorik/em-synchrony/blob/master/spec/redis_spec.rb) and em-hiredis (see specs), but unless you specifically need either of those, use the official redis gem ## Fiber-aware Iterator: mixing sync / async code Allows you to perform each, map, inject on a collection of any asynchronous tasks. To advance the iterator, simply call iter.next, or iter.return(result). The iterator will not exit until you advance through the entire collection. Additionally, you can specify the desired concurrency level! Ex: crawling a web-site, but you want to have at most 5 connections open at any one time. ```ruby require "em-synchrony" require "em-synchrony/em-http" EM.synchrony do concurrency = 2 urls = ['http://url.1.com', 'http://url2.com'] # iterator will execute async blocks until completion, .each, .inject also work! results = EM::Synchrony::Iterator.new(urls, concurrency).map do |url, iter| # fire async requests, on completion advance the iterator http = EventMachine::HttpRequest.new(url).aget http.callback { iter.return(http) } http.errback { iter.return(http) } end p results # all completed requests EventMachine.stop end ``` Or, you can use FiberIterator to hide the async nature of em-http: ```ruby require "em-synchrony" require "em-synchrony/em-http" require "em-synchrony/fiber_iterator" EM.synchrony do concurrency = 2 urls = ['http://url.1.com', 'http://url2.com'] results = [] EM::Synchrony::FiberIterator.new(urls, concurrency).each do |url| resp = EventMachine::HttpRequest.new(url).get results.push resp.response end p results # all completed requests EventMachine.stop end ``` ## Fiber-aware ConnectionPool shared by a fiber: Allows you to create a pool of resources which are then shared by one or more fibers. A good example is a collection of long-lived database connections. The pool will automatically block and wake up the fibers as the connections become available. ```ruby require "em-synchrony" require "em-synchrony/mysql2" EventMachine.synchrony do db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do Mysql2::EM::Client.new end multi = EventMachine::Synchrony::Multi.new multi.add :a, db.aquery("select sleep(1)") multi.add :b, db.aquery("select sleep(1)") res = multi.perform p "Look ma, no callbacks, and parallel MySQL requests!" p res EventMachine.stop end ``` ## Fiber-aware Multi interface: parallel HTTP requests Allows you to fire simultaneous requests and wait for all of them to complete (success or error) before advancing. Concurrently fetching many HTTP pages at once is a good example; parallel SQL queries is another. Technically, this functionality can be also achieved by using the Synchrony Iterator shown above. ```ruby require "em-synchrony" require "em-synchrony/em-http" EventMachine.synchrony do multi = EventMachine::Synchrony::Multi.new multi.add :a, EventMachine::HttpRequest.new("http://www.postrank.com").aget multi.add :b, EventMachine::HttpRequest.new("http://www.postrank.com").apost res = multi.perform p "Look ma, no callbacks, and parallel HTTP requests!" p res EventMachine.stop end ``` ## Fiber-aware & EventMachine backed TCPSocket: This is dangerous territory - you've been warned. You can patch your base TCPSocket class to make any/all libraries depending on TCPSocket be actually powered by EventMachine and Fibers under the hood. ```ruby require "em-synchrony" require "lib/em-synchrony" require "net/http" EM.synchrony do # replace default Socket code to use EventMachine Sockets instead TCPSocket = EventMachine::Synchrony::TCPSocket Net::HTTP.get_print 'www.google.com', '/index.html' EM.stop end ``` ## Inline synchronization & Fiber sleep: Allows you to inline/synchronize any callback interface to behave as if it was a blocking call. Simply pass any callback object to Synchrony.sync and it will do the right thing: the fiber will be resumed once the callback/errback fires. Likewise, use Synchrony.sleep to avoid blocking the main thread if you need to put one of your workers to sleep. ```ruby require "em-synchrony" require "em-synchrony/em-http" EM.synchrony do # pass a callback enabled client to sync to automatically resume it when callback fires result = EM::Synchrony.sync EventMachine::HttpRequest.new('http://www.gooogle.com/').aget p result # pause execution for 2 seconds EM::Synchrony.sleep(2) EM.stop end ``` ## Async ActiveRecord: Allows you to use async ActiveRecord within Rails and outside of Rails (see [async-rails](https://github.com/igrigorik/async-rails)). If you need to control the connection pool size, use [rack/fiber_pool](https://github.com/mperham/rack-fiber_pool/). ```ruby require "em-synchrony" require "em-synchrony/mysql2" require "em-synchrony/activerecord" ActiveRecord::Base.establish_connection( :adapter => 'em_mysql2', :database => 'widgets' ) result = Widget.all.to_a ``` ## Hooks em-synchrony already provides fiber-aware calls for sleep, system and defer. When mixing fiber-aware code with other gems, these might use non-fiber-aware versions which result in unexpected behavior: calling `sleep` would pause the whole reactor instead of a single fiber. For that reason, hooks into the Kernel are provided to override the default behavior to e.g. add logging or redirect these calls their fiber-aware versions. ```ruby # Adding logging but still executes the actual sleep require "em-synchrony" log = Logger.new(STDOUT) EM::Synchrony.on_sleep do |*args| log.warn "Kernel.sleep called by:" caller.each { |line| log.warn line } sleep(*args) # Calls the actual sleep end ``` ```ruby # Redirects to EM::Synchrony.sleep require "em-synchrony" log = Logger.new(STDOUT) EM::Synchrony.on_sleep do |*args| EM::Synchrony.sleep(*args) end ``` # License The MIT License - Copyright (c) 2011 Ilya Grigorik