em-synchrony 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ # EM-Synchrony
2
+
3
+ Collection of convenience classes and patches to common EventMachine clients to
4
+ make them Fiber aware and friendly. Word of warning: even though fibers have been
5
+ backported to Ruby 1.8.x, these classes assume Ruby 1.9 (if you're using fibers
6
+ in production, you should be on 1.9 anyway)
7
+
8
+ Features:
9
+
10
+ * Fiber aware connection pool with sync/async query support
11
+ * Multi request interface which accepts any callback enabled client
12
+ * em-http-request: .get, etc are synchronous, while .aget, etc are async
13
+ * em-mysqlplus: .query is synchronous, while .aquery is async
14
+ * remcached: .get, etc, and .multi_* methods are synchronous
15
+
16
+ ## Example with async em-http client:
17
+
18
+ EventMachine.run do
19
+ Fiber.new {
20
+ res = EventMachine::HttpRequest.new("http://www.postrank.com").get
21
+
22
+ p "Look ma, no callbacks!"
23
+ p res
24
+
25
+ EventMachine.stop
26
+ }.resume
27
+ end
28
+
29
+ ## Example with multi-request async em-http client:
30
+
31
+ EventMachine.run do
32
+ Fiber.new {
33
+
34
+ multi = EventMachine::Synchrony::Multi.new
35
+ multi.add :a, EventMachine::HttpRequest.new("http://www.postrank.com").aget
36
+ multi.add :b, EventMachine::HttpRequest.new("http://www.postrank.com").apost
37
+ res = multi.perform
38
+
39
+ p "Look ma, no callbacks, and parallel requests!"
40
+ p res
41
+
42
+ EventMachine.stop
43
+ }.resume
44
+ end
45
+
46
+ ## Example connection pool shared by a fiber:
47
+
48
+ EventMachine.run do
49
+
50
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
51
+ EventMachine::MySQL.new(host: "localhost")
52
+ end
53
+
54
+ Fiber.new {
55
+ start = now
56
+
57
+ multi = EventMachine::Synchrony::Multi.new
58
+ multi.add :a, db.aquery("select sleep(1)")
59
+ multi.add :b, db.aquery("select sleep(1)")
60
+ res = multi.perform
61
+
62
+ p "Look ma, no callbacks, and parallel requests!"
63
+ p res
64
+
65
+ EventMachine.stop
66
+ }.resume
67
+ end
68
+
69
+ # License
70
+
71
+ (The MIT License)
72
+
73
+ Copyright (c) 2010 Ilya Grigorik
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining
76
+ a copy of this software and associated documentation files (the
77
+ 'Software'), to deal in the Software without restriction, including
78
+ without limitation the rights to use, copy, modify, merge, publish,
79
+ distribute, sublicense, and/or sell copies of the Software, and to
80
+ permit persons to whom the Software is furnished to do so, subject to
81
+ the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be
84
+ included in all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,20 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "em-synchrony"
7
+ gemspec.summary = "Fiber aware EventMachine libraries"
8
+ gemspec.description = gemspec.summary
9
+ gemspec.email = "ilya@igvita.com"
10
+ gemspec.homepage = "http://github.com/igrigorik/em-synchrony"
11
+ gemspec.authors = ["Ilya Grigorik"]
12
+ gemspec.required_ruby_version = ">= 1.9"
13
+ gemspec.add_dependency('eventmachine', '>= 0.12.9')
14
+ gemspec.rubyforge_project = "em-synchrony"
15
+ end
16
+
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,13 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require "rubygems"
4
+ require "eventmachine"
5
+ require "fiber"
6
+
7
+ require "em-synchrony/em-multi"
8
+ require "em-synchrony/em-http"
9
+ require "em-synchrony/em-mysql"
10
+ # require "em-synchrony/em-jack"
11
+ require "em-synchrony/em-remcached"
12
+
13
+ require "em-synchrony/connection_pool"
@@ -0,0 +1,83 @@
1
+ module EventMachine
2
+ module Synchrony
3
+
4
+ class ConnectionPool
5
+ def initialize(opts, &block)
6
+ @reserved = {} # map of in-progress connections
7
+ @available = [] # pool of free connections
8
+ @pending = [] # pending reservations (FIFO)
9
+
10
+ opts[:size].times do
11
+ @available.push(block.call) if block_given?
12
+ end
13
+ end
14
+
15
+ # Choose first available connection and pass it to the supplied
16
+ # block. This will block indefinitely until there is an available
17
+ # connection to service the request.
18
+ def execute(async)
19
+ f = Fiber.current
20
+
21
+ begin
22
+ conn = acquire(f)
23
+ yield conn
24
+ ensure
25
+ release(f) if not async
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # Acquire a lock on a connection and assign it to executing fiber
32
+ # - if connection is available, pass it back to the calling block
33
+ # - if pool is full, yield the current fiber until connection is available
34
+ def acquire(fiber)
35
+
36
+ if conn = @available.pop
37
+ @reserved[fiber.object_id] = conn
38
+ conn
39
+ else
40
+ Fiber.yield @pending.push fiber
41
+ acquire(fiber)
42
+ end
43
+ end
44
+
45
+ # Release connection assigned to the supplied fiber and
46
+ # resume any other pending connections (which will
47
+ # immediately try to run acquire on the pool)
48
+ def release(fiber)
49
+ @available.push(@reserved.delete(fiber.object_id))
50
+
51
+ if pending = @pending.pop
52
+ pending.resume
53
+ end
54
+ end
55
+
56
+ # Allow the pool to behave as the underlying connection
57
+ #
58
+ # If the requesting method begins with "a" prefix, then
59
+ # hijack the callbacks and errbacks to fire a connection
60
+ # pool release whenever the request is complete. Otherwise
61
+ # yield the connection within execute method and release
62
+ # once it is complete (assumption: fiber will yield until
63
+ # data is available, or request is complete)
64
+ #
65
+ def method_missing(method, *args)
66
+ async = (method[0,1] == "a")
67
+
68
+ execute(async) do |conn|
69
+ df = conn.send(method, *args)
70
+
71
+ if async
72
+ fiber = Fiber.current
73
+ df.callback { release(fiber) }
74
+ df.errback { release(fiber) }
75
+ end
76
+
77
+ df
78
+ end
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,18 @@
1
+ module EventMachine
2
+ class HttpRequest
3
+ %w[get head post delete put].each do |type|
4
+ class_eval %[
5
+ alias :a#{type} :#{type}
6
+ def #{type}(options = {}, &blk)
7
+ f = Fiber.current
8
+
9
+ conn = setup_request(:#{type}, options, &blk)
10
+ conn.callback { f.resume(conn) }
11
+ conn.errback { f.resume(conn) }
12
+
13
+ Fiber.yield
14
+ end
15
+ ]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ require 'em-jack'
2
+
3
+ # WANT: namespaced under EventMachine.. would be nice :-)
4
+ # NOTE: no need for "pooling" since Beanstalk supports pipelining
5
+ module EMJack
6
+ class Connection
7
+
8
+ alias :ause :use
9
+ def use(tube, &blk)
10
+ return if @used_tube == tube
11
+
12
+ f = Fiber.current
13
+
14
+ # WANT: per command errbacks, would be nice, instead of one global
15
+ # errback = Proc.new {|r| f.resume(r) }
16
+
17
+ on_error {|r| f.resume(r)}
18
+
19
+ @used_tube = tube
20
+ @conn.send(:use, tube)
21
+
22
+ # WANT: Add conditional on add_deferrable to either accept two procs, or a single block
23
+ # .. two procs = callback, errback
24
+ add_deferrable { |r| f.resume(r) }
25
+
26
+ Fiber.yield
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ module EventMachine
2
+ module Synchrony
3
+ class Multi
4
+ include EventMachine::Deferrable
5
+
6
+ attr_reader :requests, :responses
7
+
8
+ def initialize
9
+ @requests = []
10
+ @responses = {:callback => {}, :errback => {}}
11
+ end
12
+
13
+ def add(name, conn)
14
+ fiber = Fiber.current
15
+ conn.callback { @responses[:callback][name] = conn; check_progress(fiber) }
16
+ conn.errback { @responses[:errback][name] = conn; check_progress(fiber) }
17
+
18
+ @requests.push(conn)
19
+ end
20
+
21
+ def perform
22
+ Fiber.yield
23
+ end
24
+
25
+ protected
26
+
27
+ def check_progress(fiber)
28
+ if (@responses[:callback].size + @responses[:errback].size) == @requests.size
29
+ succeed
30
+
31
+ # continue processing
32
+ fiber.resume(self)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ module EventMachine
2
+ class MySQL
3
+
4
+ alias :aquery :query
5
+ def query(sql, &blk)
6
+ f = Fiber.current
7
+
8
+ # TODO: blk case does not work. Hmm?
9
+ cb = Proc.new { |r| f.resume(r) }
10
+ eb = Proc.new { |r| f.resume(r) }
11
+
12
+ @connection.execute(sql, cb, eb)
13
+
14
+ Fiber.yield
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,67 @@
1
+ require "remcached"
2
+
3
+ module Memcached
4
+ class << self
5
+
6
+ def connect(servers)
7
+ Memcached.servers = servers
8
+
9
+ f = Fiber.current
10
+ @t = EM::PeriodicTimer.new(0.01) do
11
+ if Memcached.usable?
12
+ @t.cancel
13
+ f.resume(self)
14
+ end
15
+ end
16
+
17
+ Fiber.yield
18
+ end
19
+
20
+ %w[add get set delete].each do |type|
21
+ class_eval %[
22
+ def a#{type}(contents, &callback)
23
+ df = EventMachine::DefaultDeferrable.new
24
+ df.callback &callback
25
+
26
+ cb = Proc.new { |res| df.succeed(res) }
27
+ operation Request::#{type.capitalize}, contents, &cb
28
+
29
+ df
30
+ end
31
+
32
+ def #{type}(contents, &callback)
33
+ fiber = Fiber.current
34
+
35
+ df = a#{type}(contents, &Proc.new { |res| fiber.resume(res) })
36
+ df.callback &callback
37
+
38
+ Fiber.yield
39
+ end
40
+ ]
41
+ end
42
+
43
+ %w[add get set delete].each do |type|
44
+ class_eval %[
45
+ def amulti_#{type}(contents, &callback)
46
+ df = EventMachine::DefaultDeferrable.new
47
+ df.callback &callback
48
+
49
+ cb = Proc.new { |res| df.succeed(res) }
50
+ multi_operation Request::#{type.capitalize}, contents, &cb
51
+
52
+ df
53
+ end
54
+
55
+ def multi_#{type}(contents, &callback)
56
+ fiber = Fiber.current
57
+
58
+ df = amulti_#{type}(contents, &Proc.new { |res| fiber.resume(res) })
59
+ df.callback &callback
60
+
61
+ Fiber.yield
62
+ end
63
+ ]
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec/helper'
2
+
3
+ DELAY = 0.25
4
+
5
+ describe EMJack do
6
+
7
+ it "should fire sequential Beanstalk requests" do
8
+ EventMachine.run do
9
+
10
+ Fiber.new {
11
+ jack = EMJack::Connection.new
12
+
13
+ r = jack.use('mytube')
14
+ r.should == 'mytube'
15
+
16
+ EventMachine.stop
17
+ }.resume
18
+ end
19
+ end
20
+
21
+ it "should fire multiple requests in parallel" do
22
+ EventMachine.run do
23
+
24
+ Fiber.new {
25
+ jack = EMJack::Connection.new
26
+
27
+ multi = EventMachine::Multi.new
28
+ multi.add jack.ause('mytube-1')
29
+ multi.add jack.ause('mytube-2')
30
+ res = multi.perform
31
+
32
+ res.responses.size.should == 2
33
+ p [:multi, res.responses]
34
+
35
+ EventMachine.stop
36
+ }.resume
37
+
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,129 @@
1
+ require "spec/helper/all"
2
+
3
+ DELAY = 0.25
4
+ QUERY = "select sleep(#{DELAY})"
5
+
6
+ describe EventMachine::Synchrony::ConnectionPool do
7
+
8
+ it "should queue requests if pool size is exceeded" do
9
+ EventMachine.run do
10
+
11
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 1) do
12
+ EventMachine::MySQL.new(host: "localhost")
13
+ end
14
+
15
+ Fiber.new {
16
+ start = now
17
+
18
+ multi = EventMachine::Synchrony::Multi.new
19
+ multi.add :a, db.aquery(QUERY)
20
+ multi.add :b, db.aquery(QUERY)
21
+ res = multi.perform
22
+
23
+ (now - start.to_f).should be_within(DELAY * 2 * 0.15).of(DELAY * 2)
24
+ res.responses[:callback].size.should == 2
25
+ res.responses[:errback].size.should == 0
26
+
27
+ EventMachine.stop
28
+ }.resume
29
+ end
30
+ end
31
+
32
+ it "should execute multiple async pool requests within same fiber" do
33
+ EventMachine.run do
34
+
35
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
36
+ EventMachine::MySQL.new(host: "localhost")
37
+ end
38
+
39
+ Fiber.new {
40
+ start = now
41
+
42
+ multi = EventMachine::Synchrony::Multi.new
43
+ multi.add :a, db.aquery(QUERY)
44
+ multi.add :b, db.aquery(QUERY)
45
+ res = multi.perform
46
+
47
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
48
+ res.responses[:callback].size.should == 2
49
+ res.responses[:errback].size.should == 0
50
+
51
+ EventMachine.stop
52
+ }.resume
53
+ end
54
+ end
55
+
56
+ it "should share connection pool between different fibers" do
57
+ EventMachine.run do
58
+
59
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
60
+ EventMachine::MySQL.new(host: "localhost")
61
+ end
62
+
63
+ Fiber.new {
64
+ start = now
65
+ results = []
66
+
67
+ fiber = Fiber.current
68
+ 2.times do |n|
69
+ Fiber.new {
70
+ results.push db.query(QUERY)
71
+ fiber.transfer if results.size == 2
72
+ }.resume
73
+ end
74
+
75
+ # wait for workers
76
+ Fiber.yield
77
+
78
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
79
+ results.size.should == 2
80
+
81
+ EventMachine.stop
82
+ }.resume
83
+
84
+ end
85
+ end
86
+
87
+ it "should share connection pool between different fibers & async requests" do
88
+ EventMachine.run do
89
+
90
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 5) do
91
+ EventMachine::MySQL.new(host: "localhost")
92
+ end
93
+
94
+ Fiber.new {
95
+ start = now
96
+ results = []
97
+
98
+ fiber = Fiber.current
99
+ 2.times do |n|
100
+ Fiber.new {
101
+
102
+ multi = EventMachine::Synchrony::Multi.new
103
+ multi.add :a, db.aquery(QUERY)
104
+ multi.add :b, db.aquery(QUERY)
105
+ results.push multi.perform
106
+
107
+ fiber.transfer if results.size == 3
108
+ }.resume
109
+ end
110
+
111
+ Fiber.new {
112
+ # execute a synchronous requests
113
+ results.push db.query(QUERY)
114
+ fiber.transfer if results.size == 3
115
+ }.resume
116
+
117
+ # wait for workers
118
+ Fiber.yield
119
+
120
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
121
+ results.size.should == 3
122
+
123
+ EventMachine.stop
124
+ }.resume
125
+
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'pp'
4
+
5
+ require 'em-http'
6
+ require 'em-mysqlplus'
7
+
8
+ require 'lib/em-synchrony'
9
+ require 'helper/tolerance_matcher'
10
+ require 'helper/stub-http-server'
11
+
12
+ def now(); Time.now.to_f; end
13
+
14
+ Spec::Runner.configure do |config|
15
+ config.include(Sander6::CustomMatchers)
16
+ end
@@ -0,0 +1,22 @@
1
+ class StubServer
2
+ module Server
3
+ attr_accessor :response, :delay
4
+ def receive_data(data)
5
+ EM.add_timer(@delay) {
6
+ send_data @response
7
+ close_connection_after_writing
8
+ }
9
+ end
10
+ end
11
+
12
+ def initialize(response, delay = 0, port=8081)
13
+ @sig = EventMachine::start_server("127.0.0.1", port, Server) { |s|
14
+ s.response = response
15
+ s.delay = delay
16
+ }
17
+ end
18
+
19
+ def stop
20
+ EventMachine.stop_server @sig
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # courtesy of sander 6
3
+ # - http://github.com/sander6/custom_matchers/blob/master/lib/matchers/tolerance_matchers.rb
4
+ #
5
+ module Sander6
6
+ module CustomMatchers
7
+
8
+ class ToleranceMatcher
9
+ def initialize(tolerance)
10
+ @tolerance = tolerance
11
+ @target = 0
12
+ end
13
+
14
+ def of(target)
15
+ @target = target
16
+ self
17
+ end
18
+
19
+ def matches?(value)
20
+ @value = value
21
+ ((@target - @tolerance)..(@target + @tolerance)).include?(@value)
22
+ end
23
+
24
+ def failure_message
25
+ "Expected #{@value.inspect} to be within #{@tolerance.inspect} of #{@target.inspect}, but it wasn't.\n" +
26
+ "Difference: #{@value - @target}"
27
+ end
28
+
29
+ def negative_failure_message
30
+ "Expected #{@value.inspect} not to be within #{@tolerance.inspect} of #{@target.inspect}, but it was.\n" +
31
+ "Difference: #{@value - @target}"
32
+ end
33
+ end
34
+
35
+ def be_within(value)
36
+ Sander6::CustomMatchers::ToleranceMatcher.new(value)
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,51 @@
1
+ require "spec/helper/all"
2
+
3
+ URL = "http://localhost:8081/"
4
+ DELAY = 0.25
5
+
6
+ describe EventMachine::HttpRequest do
7
+ it "should fire sequential requests" do
8
+ EventMachine.run do
9
+ @s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", DELAY)
10
+
11
+ Fiber.new {
12
+ start = now
13
+ order = []
14
+ order.push :get if EventMachine::HttpRequest.new(URL).get
15
+ order.push :post if EventMachine::HttpRequest.new(URL).post
16
+ order.push :head if EventMachine::HttpRequest.new(URL).head
17
+ order.push :post if EventMachine::HttpRequest.new(URL).delete
18
+ order.push :put if EventMachine::HttpRequest.new(URL).put
19
+
20
+ (now - start.to_f).should be_within(DELAY * order.size * 0.15).of(DELAY * order.size)
21
+ order.should == [:get, :post, :head, :post, :put]
22
+
23
+ EventMachine.stop
24
+ }.resume
25
+ end
26
+ end
27
+
28
+ it "should fire simultaneous requests via Multi interface" do
29
+ EventMachine.run do
30
+ @s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", DELAY)
31
+
32
+ Fiber.new {
33
+ start = now
34
+
35
+ multi = EventMachine::Synchrony::Multi.new
36
+ multi.add :a, EventMachine::HttpRequest.new(URL).aget
37
+ multi.add :b, EventMachine::HttpRequest.new(URL).apost
38
+ multi.add :c, EventMachine::HttpRequest.new(URL).ahead
39
+ multi.add :d, EventMachine::HttpRequest.new(URL).adelete
40
+ multi.add :e, EventMachine::HttpRequest.new(URL).aput
41
+ res = multi.perform
42
+
43
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
44
+ res.responses[:callback].size.should == 5
45
+ res.responses[:errback].size.should == 0
46
+
47
+ EventMachine.stop
48
+ }.resume
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,93 @@
1
+ require "spec/helper/all"
2
+ require "em-mysqlplus"
3
+
4
+ DELAY = 0.25
5
+ QUERY = "select sleep(#{DELAY})"
6
+
7
+ describe EventMachine::MySQL do
8
+
9
+ it "should fire sequential, synchronous requests" do
10
+ EventMachine.run do
11
+ Fiber.new {
12
+ db = EventMachine::MySQL.new(host: "localhost")
13
+ start = now
14
+ res = []
15
+
16
+ res.push db.query(QUERY)
17
+ res.push db.query(QUERY)
18
+ (now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size)
19
+
20
+ EventMachine.stop
21
+ }.resume
22
+ end
23
+ end
24
+
25
+ it "should have accept a callback, errback on async queries" do
26
+ EventMachine.run do
27
+ db = EventMachine::MySQL.new(host: "localhost")
28
+
29
+ res = db.aquery(QUERY)
30
+ res.errback {|r| fail }
31
+ res.callback {|r|
32
+ r.all_hashes.size.should == 1
33
+ EventMachine.stop
34
+ }
35
+ end
36
+ end
37
+
38
+ it "should fire simultaneous requests via Multi interface" do
39
+ EventMachine.run do
40
+
41
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
42
+ EventMachine::MySQL.new(host: "localhost")
43
+ end
44
+
45
+ Fiber.new {
46
+ start = now
47
+
48
+ multi = EventMachine::Synchrony::Multi.new
49
+ multi.add :a, db.aquery(QUERY)
50
+ multi.add :b, db.aquery(QUERY)
51
+ res = multi.perform
52
+
53
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
54
+ res.responses[:callback].size.should == 2
55
+ res.responses[:errback].size.should == 0
56
+
57
+ EventMachine.stop
58
+ }.resume
59
+ end
60
+ end
61
+
62
+ it "should fire sequential and simultaneous MySQL requests" do
63
+ EventMachine.run do
64
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 3) do
65
+ EventMachine::MySQL.new(host: "localhost")
66
+ end
67
+
68
+ Fiber.new {
69
+ start = now
70
+ res = []
71
+
72
+ res.push db.query(QUERY)
73
+ res.push db.query(QUERY)
74
+ (now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size)
75
+
76
+ start = now
77
+
78
+ multi = EventMachine::Synchrony::Multi.new
79
+ multi.add :a, db.aquery(QUERY)
80
+ multi.add :b, db.aquery(QUERY)
81
+ multi.add :c, db.aquery(QUERY)
82
+ res = multi.perform
83
+
84
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
85
+ res.responses[:callback].size.should == 3
86
+ res.responses[:errback].size.should == 0
87
+
88
+ EventMachine.stop
89
+ }.resume
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,51 @@
1
+ require "spec/helper/all"
2
+ require "remcached"
3
+
4
+ describe Memcached do
5
+
6
+ it "should yield until connection is ready" do
7
+ EventMachine.run do
8
+ Fiber.new {
9
+ Memcached.connect %w(localhost)
10
+ Memcached.usable?.should be_true
11
+ EventMachine.stop
12
+ }.resume
13
+ end
14
+ end
15
+
16
+ it "should fire sequential memcached requests" do
17
+ EventMachine.run do
18
+ Fiber.new {
19
+
20
+ Memcached.connect %w(localhost)
21
+ Memcached.get(key: 'hai') do |res|
22
+ res[:value].should match('Not found')
23
+ end
24
+
25
+ Memcached.set(key: 'hai', value: 'win')
26
+ Memcached.add(key: 'count')
27
+ Memcached.delete(key: 'hai')
28
+
29
+ EventMachine.stop
30
+ }.resume
31
+ end
32
+ end
33
+
34
+ it "should fire multi memcached requests" do
35
+ pending "remcached borked? opened a ticket"
36
+
37
+ EventMachine.run do
38
+ Fiber.new {
39
+
40
+ Memcached.connect %w(localhost)
41
+
42
+ Memcached.multi_get([{:key => 'foo'},{:key => 'bar'}, {:key => 'test'}]) do |res|
43
+ p res
44
+ end
45
+
46
+ EventMachine.stop
47
+ }.resume
48
+ end
49
+ end
50
+
51
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-synchrony
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - Ilya Grigorik
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-18 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: eventmachine
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 12
30
+ - 9
31
+ version: 0.12.9
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: Fiber aware EventMachine libraries
35
+ email: ilya@igvita.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - README.md
42
+ files:
43
+ - README.md
44
+ - Rakefile
45
+ - VERSION
46
+ - lib/em-synchrony.rb
47
+ - lib/em-synchrony/connection_pool.rb
48
+ - lib/em-synchrony/em-http.rb
49
+ - lib/em-synchrony/em-jack.rb
50
+ - lib/em-synchrony/em-multi.rb
51
+ - lib/em-synchrony/em-mysql.rb
52
+ - lib/em-synchrony/em-remcached.rb
53
+ - spec/beanstalk_spec.rb
54
+ - spec/connection_pool_spec.rb
55
+ - spec/helper/all.rb
56
+ - spec/helper/stub-http-server.rb
57
+ - spec/helper/tolerance_matcher.rb
58
+ - spec/http_spec.rb
59
+ - spec/mysql_spec.rb
60
+ - spec/remcached_spec.rb
61
+ has_rdoc: true
62
+ homepage: http://github.com/igrigorik/em-synchrony
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --charset=UTF-8
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ segments:
75
+ - 1
76
+ - 9
77
+ version: "1.9"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements: []
86
+
87
+ rubyforge_project: em-synchrony
88
+ rubygems_version: 1.3.6
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Fiber aware EventMachine libraries
92
+ test_files:
93
+ - spec/beanstalk_spec.rb
94
+ - spec/connection_pool_spec.rb
95
+ - spec/helper/all.rb
96
+ - spec/helper/stub-http-server.rb
97
+ - spec/helper/tolerance_matcher.rb
98
+ - spec/http_spec.rb
99
+ - spec/mysql_spec.rb
100
+ - spec/remcached_spec.rb