em-synchrony 0.1.2 → 0.1.4

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.
data/README.md CHANGED
@@ -1,71 +1,86 @@
1
1
  # EM-Synchrony
2
2
 
3
+ http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers
4
+
3
5
  Collection of convenience classes and patches to common EventMachine clients to
4
6
  make them Fiber aware and friendly. Word of warning: even though fibers have been
5
7
  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)
8
+ in production, you should be on 1.9 anyway)
7
9
 
8
10
  Features:
9
11
 
10
12
  * Fiber aware connection pool with sync/async query support
11
13
  * Multi request interface which accepts any callback enabled client
14
+ * Fibered iterator to allow concurrency control & mixing of sync / async
12
15
  * em-http-request: .get, etc are synchronous, while .aget, etc are async
13
16
  * em-mysqlplus: .query is synchronous, while .aquery is async
14
17
  * remcached: .get, etc, and .multi_* methods are synchronous
15
18
 
16
19
  ## Example with async em-http client:
17
20
 
18
- EventMachine.run do
19
- Fiber.new {
20
- res = EventMachine::HttpRequest.new("http://www.postrank.com").get
21
+ EventMachine.synchrony do
22
+ res = EventMachine::HttpRequest.new("http://www.postrank.com").get
21
23
 
22
- p "Look ma, no callbacks!"
23
- p res
24
+ p "Look ma, no callbacks!"
25
+ p res
24
26
 
25
- EventMachine.stop
26
- }.resume
27
+ EventMachine.stop
27
28
  end
28
29
 
29
- ## Example with multi-request async em-http client:
30
+ ## EM Iterator & mixing sync / async code
30
31
 
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
32
+ EM.synchrony do
33
+ concurrency = 2
34
+ urls = ['http://url.1.com', 'http://url2.com']
35
+
36
+ # iterator will execute async blocks until completion, .each, .inject also work!
37
+ results = EM::Synchrony::Iterator.new(urls, concurrency).map do |url, iter|
38
+
39
+ # fire async requests, on completion advance the iterator
40
+ http = EventMachine::HttpRequest.new(url).aget
41
+ http.callback { iter.return(http) }
42
+ end
41
43
 
42
- EventMachine.stop
43
- }.resume
44
+ p results # all completed requests
45
+
46
+ EventMachine.stop
44
47
  end
45
48
 
46
49
  ## Example connection pool shared by a fiber:
47
50
 
48
- EventMachine.run do
49
-
51
+ EventMachine.synchrony do
50
52
  db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
51
53
  EventMachine::MySQL.new(host: "localhost")
52
54
  end
53
55
 
54
- Fiber.new {
55
- start = now
56
+ start = now
56
57
 
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
58
+ multi = EventMachine::Synchrony::Multi.new
59
+ multi.add :a, db.aquery("select sleep(1)")
60
+ multi.add :b, db.aquery("select sleep(1)")
61
+ res = multi.perform
61
62
 
62
- p "Look ma, no callbacks, and parallel requests!"
63
- p res
63
+ p "Look ma, no callbacks, and parallel MySQL requests!"
64
+ p res
64
65
 
65
- EventMachine.stop
66
- }.resume
66
+ EventMachine.stop
67
67
  end
68
68
 
69
+
70
+ ## Example with multi-request async em-http client:
71
+
72
+ EventMachine.synchrony do
73
+ multi = EventMachine::Synchrony::Multi.new
74
+ multi.add :a, EventMachine::HttpRequest.new("http://www.postrank.com").aget
75
+ multi.add :b, EventMachine::HttpRequest.new("http://www.postrank.com").apost
76
+ res = multi.perform
77
+
78
+ p "Look ma, no callbacks, and parallel HTTP requests!"
79
+ p res
80
+
81
+ EventMachine.stop
82
+ end
83
+
69
84
  # License
70
85
 
71
86
  (The MIT License)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.1.4
data/examples/all.rb ADDED
@@ -0,0 +1,27 @@
1
+ require "lib/em-synchrony"
2
+
3
+ EM.synchrony do
4
+
5
+ # open 4 concurrent MySQL connections
6
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 4) do
7
+ EventMachine::MySQL.new(host: "localhost")
8
+ end
9
+
10
+ # perform 4 http requests in parallel, and collect responses
11
+ multi = EventMachine::Synchrony::Multi.new
12
+ multi.add :page1, EventMachine::HttpRequest.new("http://service.com/page1").aget
13
+ multi.add :page2, EventMachine::HttpRequest.new("http://service.com/page2").aget
14
+ multi.add :page3, EventMachine::HttpRequest.new("http://service.com/page3").aget
15
+ multi.add :page4, EventMachine::HttpRequest.new("http://service.com/page4").aget
16
+ data = multi.perform.responses[:callback].values
17
+
18
+ # insert fetched HTTP data into a mysql database, using at most 2 connections at a time
19
+ # - note that we're writing async code within the callback!
20
+ EM::Synchrony::Iterator.new(data, 2).each do |page, iter|
21
+ db.aquery("INSERT INTO table (data) VALUES(#{page});")
22
+ db.callback { iter.return(http) }
23
+ end
24
+
25
+ puts "All done! Stopping event loop."
26
+ EventMachine.stop
27
+ end
data/lib/em-synchrony.rb CHANGED
@@ -5,9 +5,23 @@ require "eventmachine"
5
5
  require "fiber"
6
6
 
7
7
  require "em-synchrony/em-multi"
8
+ # require "em-synchrony/iterator" # iterators are not release in EM yet
9
+ require "em-synchrony/connection_pool"
10
+
8
11
  require "em-synchrony/em-http"
9
12
  require "em-synchrony/em-mysql"
10
- # require "em-synchrony/em-jack"
11
13
  require "em-synchrony/em-remcached"
12
14
 
13
- require "em-synchrony/connection_pool"
15
+ module EventMachine
16
+
17
+ # A convenience method for wrapping EM.run body within
18
+ # a Ruby Fiber such that async operations can be transparently
19
+ # paused and resumed based on IO scheduling.
20
+ def self.synchrony(blk=nil, tail=nil, &block)
21
+ blk ||= block
22
+ context = Proc.new { Fiber.new { blk.call }.resume }
23
+
24
+ self.run(context, tail)
25
+ end
26
+
27
+ end
@@ -0,0 +1,52 @@
1
+ require "em/iterator"
2
+
3
+ module EventMachine
4
+ module Synchrony
5
+
6
+ class Iterator < EM::Iterator
7
+
8
+ # synchronous iterator which will wait until all the
9
+ # jobs are done before returning. Unfortunately this
10
+ # means that you loose ability to choose concurrency
11
+ # on the fly (see iterator documentation in EM)
12
+ def each(foreach=nil, after=nil, &blk)
13
+ fiber = Fiber.current
14
+
15
+ fe = (foreach || blk)
16
+ cb = Proc.new do
17
+ after.call if after
18
+ fiber.resume
19
+ end
20
+
21
+ Fiber.yield super(fe, cb)
22
+ end
23
+
24
+ def map(&block)
25
+ fiber = Fiber.current
26
+ result = nil
27
+
28
+ after = Proc.new {|res| result = res; fiber.resume }
29
+ super(block, after)
30
+
31
+ Fiber.yield
32
+ result
33
+ end
34
+
35
+ def inject(obj, foreach = nil, after = nil, &block)
36
+ if foreach and after
37
+ super(obj, foreach, after)
38
+ else
39
+ fiber = Fiber.current
40
+ result = nil
41
+
42
+ after = Proc.new {|res| result = res; fiber.resume}
43
+ super(obj, block, after)
44
+
45
+ Fiber.yield
46
+ result
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -1,12 +1,14 @@
1
- require 'spec/helper'
1
+ require 'spec/helper/all'
2
2
 
3
3
  DELAY = 0.25
4
4
 
5
+ __END__
5
6
  describe EMJack do
6
7
 
7
8
  it "should fire sequential Beanstalk requests" do
9
+ pending
10
+
8
11
  EventMachine.run do
9
-
10
12
  Fiber.new {
11
13
  jack = EMJack::Connection.new
12
14
 
@@ -19,8 +21,9 @@ describe EMJack do
19
21
  end
20
22
 
21
23
  it "should fire multiple requests in parallel" do
24
+ pending
25
+
22
26
  EventMachine.run do
23
-
24
27
  Fiber.new {
25
28
  jack = EMJack::Connection.new
26
29
 
data/spec/http_spec.rb CHANGED
@@ -5,47 +5,45 @@ DELAY = 0.25
5
5
 
6
6
  describe EventMachine::HttpRequest do
7
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
8
+ EventMachine.synchrony do
9
+ s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", DELAY)
10
+
11
+ start = now
12
+ order = []
13
+ order.push :get if EventMachine::HttpRequest.new(URL).get
14
+ order.push :post if EventMachine::HttpRequest.new(URL).post
15
+ order.push :head if EventMachine::HttpRequest.new(URL).head
16
+ order.push :post if EventMachine::HttpRequest.new(URL).delete
17
+ order.push :put if EventMachine::HttpRequest.new(URL).put
18
+
19
+ (now - start.to_f).should be_within(DELAY * order.size * 0.15).of(DELAY * order.size)
20
+ order.should == [:get, :post, :head, :post, :put]
21
+
22
+ s.stop
23
+ EventMachine.stop
25
24
  end
26
25
  end
27
26
 
28
27
  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
28
+ EventMachine.synchrony do
29
+ s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", DELAY)
30
+
31
+ start = now
32
+
33
+ multi = EventMachine::Synchrony::Multi.new
34
+ multi.add :a, EventMachine::HttpRequest.new(URL).aget
35
+ multi.add :b, EventMachine::HttpRequest.new(URL).apost
36
+ multi.add :c, EventMachine::HttpRequest.new(URL).ahead
37
+ multi.add :d, EventMachine::HttpRequest.new(URL).adelete
38
+ multi.add :e, EventMachine::HttpRequest.new(URL).aput
39
+ res = multi.perform
40
+
41
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
42
+ res.responses[:callback].size.should == 5
43
+ res.responses[:errback].size.should == 0
44
+
45
+ s.stop
46
+ EventMachine.stop
49
47
  end
50
48
  end
51
49
  end
@@ -0,0 +1,68 @@
1
+ require "spec/helper/all"
2
+ require "em-synchrony/iterator"
3
+
4
+ describe EventMachine::Synchrony::Iterator do
5
+
6
+ it "should wait until the iterator is done" do
7
+ EM.synchrony do
8
+
9
+ results = []
10
+ i = EM::Synchrony::Iterator.new(1..50, 10).each do |num, iter|
11
+ results.push num
12
+ iter.next
13
+ end
14
+
15
+ results.should == (1..50).to_a
16
+ results.size.should == 50
17
+ EventMachine.stop
18
+ end
19
+ end
20
+
21
+ it "should map values within the iterator" do
22
+ EM.synchrony do
23
+ results = EM::Synchrony::Iterator.new(1..50, 10).map do |num, iter|
24
+ iter.return(num + 1)
25
+ end
26
+
27
+ results.should == (2..51).to_a
28
+ results.size.should == 50
29
+ EventMachine.stop
30
+ end
31
+ end
32
+
33
+ it "should sum values within the iterator" do
34
+ EM.synchrony do
35
+ data = (1..50).to_a
36
+ res = EM::Synchrony::Iterator.new(data, 10).inject(0) do |total, num, iter|
37
+ total += num
38
+ iter.return(total)
39
+ end
40
+
41
+ res.should == data.inject(:+)
42
+ EventMachine.stop
43
+ end
44
+ end
45
+
46
+ it "should fire async http requests in blocks of 2" do
47
+ EM.synchrony do
48
+ num_urls = 4
49
+ concurrency = 2
50
+ delay = 0.25
51
+
52
+ s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", delay)
53
+ urls = ['http://localhost:8081/'] * num_urls
54
+
55
+ start = now
56
+ results = EM::Synchrony::Iterator.new(urls, concurrency).map do |url, iter|
57
+ http = EventMachine::HttpRequest.new(url).aget
58
+ http.callback { iter.return(http) }
59
+ end
60
+
61
+ results.size.should == 4
62
+ (now - start.to_f).should be_within(delay * 0.15).of(delay * (num_urls / concurrency))
63
+
64
+ EventMachine.stop
65
+ end
66
+ end
67
+
68
+ end
data/spec/mysql_spec.rb CHANGED
@@ -7,23 +7,21 @@ QUERY = "select sleep(#{DELAY})"
7
7
  describe EventMachine::MySQL do
8
8
 
9
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 = []
10
+ EventMachine.synchrony do
11
+ db = EventMachine::MySQL.new(host: "localhost")
12
+ start = now
13
+ res = []
15
14
 
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)
15
+ res.push db.query(QUERY)
16
+ res.push db.query(QUERY)
17
+ (now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size)
19
18
 
20
- EventMachine.stop
21
- }.resume
19
+ EventMachine.stop
22
20
  end
23
21
  end
24
22
 
25
23
  it "should have accept a callback, errback on async queries" do
26
- EventMachine.run do
24
+ EventMachine.synchrony do
27
25
  db = EventMachine::MySQL.new(host: "localhost")
28
26
 
29
27
  res = db.aquery(QUERY)
@@ -36,58 +34,54 @@ describe EventMachine::MySQL do
36
34
  end
37
35
 
38
36
  it "should fire simultaneous requests via Multi interface" do
39
- EventMachine.run do
37
+ EventMachine.synchrony do
40
38
 
41
39
  db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
42
40
  EventMachine::MySQL.new(host: "localhost")
43
41
  end
44
42
 
45
- Fiber.new {
46
- start = now
43
+ start = now
47
44
 
48
- multi = EventMachine::Synchrony::Multi.new
49
- multi.add :a, db.aquery(QUERY)
50
- multi.add :b, db.aquery(QUERY)
51
- res = multi.perform
45
+ multi = EventMachine::Synchrony::Multi.new
46
+ multi.add :a, db.aquery(QUERY)
47
+ multi.add :b, db.aquery(QUERY)
48
+ res = multi.perform
52
49
 
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
50
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
51
+ res.responses[:callback].size.should == 2
52
+ res.responses[:errback].size.should == 0
56
53
 
57
- EventMachine.stop
58
- }.resume
54
+ EventMachine.stop
59
55
  end
60
56
  end
61
57
 
62
58
  it "should fire sequential and simultaneous MySQL requests" do
63
- EventMachine.run do
59
+ EventMachine.synchrony do
64
60
  db = EventMachine::Synchrony::ConnectionPool.new(size: 3) do
65
61
  EventMachine::MySQL.new(host: "localhost")
66
62
  end
67
63
 
68
- Fiber.new {
69
- start = now
70
- res = []
64
+ start = now
65
+ res = []
71
66
 
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)
67
+ res.push db.query(QUERY)
68
+ res.push db.query(QUERY)
69
+ (now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size)
75
70
 
76
- start = now
71
+ start = now
77
72
 
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
73
+ multi = EventMachine::Synchrony::Multi.new
74
+ multi.add :a, db.aquery(QUERY)
75
+ multi.add :b, db.aquery(QUERY)
76
+ multi.add :c, db.aquery(QUERY)
77
+ res = multi.perform
83
78
 
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
79
+ (now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
80
+ res.responses[:callback].size.should == 3
81
+ res.responses[:errback].size.should == 0
87
82
 
88
- EventMachine.stop
89
- }.resume
83
+ EventMachine.stop
90
84
  end
91
85
  end
92
86
 
93
- end
87
+ end
@@ -4,47 +4,38 @@ require "remcached"
4
4
  describe Memcached do
5
5
 
6
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
7
+ EventMachine.synchrony do
8
+ Memcached.connect %w(localhost)
9
+ Memcached.usable?.should be_true
10
+ EventMachine.stop
13
11
  end
14
12
  end
15
13
 
16
14
  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
15
+ EventMachine.synchrony do
16
+
17
+ Memcached.connect %w(localhost)
18
+ Memcached.get(key: 'hai') do |res|
19
+ res[:value].should match('Not found')
20
+ end
21
+
22
+ Memcached.set(key: 'hai', value: 'win')
23
+ Memcached.add(key: 'count')
24
+ Memcached.delete(key: 'hai')
25
+
26
+ EventMachine.stop
31
27
  end
32
28
  end
33
-
29
+
34
30
  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
-
31
+ EventMachine.synchrony do
32
+ pending "patch mult-get"
33
+
34
+ Memcached.connect %w(localhost)
35
+ Memcached.multi_get([{:key => 'foo'},{:key => 'bar'}, {:key => 'test'}]) do |res|
36
+ # TODO
46
37
  EventMachine.stop
47
- }.resume
38
+ end
48
39
  end
49
40
  end
50
41
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 2
9
- version: 0.1.2
8
+ - 4
9
+ version: 0.1.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ilya Grigorik
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-19 00:00:00 -04:00
17
+ date: 2010-03-22 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -50,12 +50,14 @@ files:
50
50
  - lib/em-synchrony/em-multi.rb
51
51
  - lib/em-synchrony/em-mysql.rb
52
52
  - lib/em-synchrony/em-remcached.rb
53
+ - lib/em-synchrony/iterator.rb
53
54
  - spec/beanstalk_spec.rb
54
55
  - spec/connection_pool_spec.rb
55
56
  - spec/helper/all.rb
56
57
  - spec/helper/stub-http-server.rb
57
58
  - spec/helper/tolerance_matcher.rb
58
59
  - spec/http_spec.rb
60
+ - spec/iterator_spec.rb
59
61
  - spec/mysql_spec.rb
60
62
  - spec/remcached_spec.rb
61
63
  has_rdoc: true
@@ -96,5 +98,7 @@ test_files:
96
98
  - spec/helper/stub-http-server.rb
97
99
  - spec/helper/tolerance_matcher.rb
98
100
  - spec/http_spec.rb
101
+ - spec/iterator_spec.rb
99
102
  - spec/mysql_spec.rb
100
103
  - spec/remcached_spec.rb
104
+ - examples/all.rb