em-synchrony 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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