em-synchrony 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,15 +3,17 @@ source :gemcutter
3
3
  gem 'eventmachine', :git => 'git://github.com/eventmachine/eventmachine.git'
4
4
 
5
5
  group :development do
6
+ gem 'rake'
6
7
  gem 'rspec'
7
8
  gem 'em-http-request', :git => 'git://github.com/igrigorik/em-http-request'
8
9
  gem 'remcached'
9
10
  # gem 'em-mongo', :git => 'https://github.com/bcg/em-mongo.git'
10
11
  gem 'activerecord', '>= 3.1.0.rc6'
11
- gem 'em-mongo', '~> 0.3.6'
12
+ gem 'em-mongo'
12
13
  gem 'bson_ext'
13
14
  gem 'mysql2'
14
15
  gem 'em-redis', '~> 0.3.0'
15
16
  gem 'em-hiredis'
16
17
  gem 'mongo'
18
+ gem 'amqp'
17
19
  end
data/README.md CHANGED
@@ -1,171 +1,174 @@
1
- # EM-Synchrony
2
-
3
- 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).
4
-
5
- * Fiber aware ConnectionPool with sync/async query support
6
- * Fiber aware Iterator to allow concurrency control & mixing of sync / async
7
- * Fiber aware async inline support: turns any async function into sync
8
- * Fiber aware Multi-request interface for any callback enabled clients
9
- * Fiber aware TCPSocket replacement, powered by EventMachine
10
- * Fiber aware Thread, Mutex, ConditionVariable clases
11
- * Fiber aware sleep
12
-
13
- Supported clients:
14
-
15
- * [mysql2](http://github.com/igrigorik/em-synchrony/blob/master/spec/mysql2_spec.rb): .query is synchronous, while .aquery is async (see specs)
16
- * [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
17
- * [em-http-request](http://github.com/igrigorik/em-synchrony/blob/master/spec/http_spec.rb): .get, etc are synchronous, while .aget, etc are async
18
- * [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
19
- * [em-mongo](http://github.com/igrigorik/em-synchrony/blob/master/spec/em-mongo_spec.rb): .find, .first are synchronous
20
- * [mongoid](http://github.com/igrigorik/em-synchrony/blob/master/spec/mongo_spec.rb): all functions synchronous, plus Rails compatibility
21
- * em-jack: a[method]'s are async, and all regular jack method's are synchronous
22
-
23
- Other clients with native Fiber support:
24
-
25
- * redis: contains [synchrony code](https://github.com/ezmobius/redis-rb/blob/master/test/synchrony_driver.rb) right within the driver
26
- * 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
27
-
28
- ## Fiber-aware Iterator: mixing sync / async code
29
-
30
- 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.
31
-
32
- ```ruby
33
- require "em-synchrony"
34
- require "em-synchrony/em-http"
35
-
36
- EM.synchrony do
37
- concurrency = 2
38
- urls = ['http://url.1.com', 'http://url2.com']
39
-
40
- # iterator will execute async blocks until completion, .each, .inject also work!
41
- results = EM::Synchrony::Iterator.new(urls, concurrency).map do |url, iter|
42
-
43
- # fire async requests, on completion advance the iterator
44
- http = EventMachine::HttpRequest.new(url).aget
45
- http.callback { iter.return(http) }
46
- http.errback { iter.return(http) }
47
- end
48
-
49
- p results # all completed requests
50
- EventMachine.stop
51
- end
52
- ```
53
-
54
- Or, you can use FiberIterator to hide the async nature of em-http:
55
-
56
- ```ruby
57
- require "em-synchrony"
58
- require "em-synchrony/em-http"
59
-
60
- EM.synchrony do
61
- concurrency = 2
62
- urls = ['http://url.1.com', 'http://url2.com']
63
- results = []
64
-
65
- EM::Synchrony::FiberIterator.new(urls, concurrency).each do |url|
66
- resp = EventMachine::HttpRequest.new(url).get
67
- results.push resp.response
68
- end
69
-
70
- p results # all completed requests
71
- EventMachine.stop
72
- end
73
- ```
74
-
75
- ## Fiber-aware ConnectionPool shared by a fiber:
76
- 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.
77
-
78
- ```ruby
79
- require "em-synchrony"
80
- require "em-synchrony/mysql2"
81
-
82
- EventMachine.synchrony do
83
- db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
84
- Mysql2::EM::Client.new
85
- end
86
-
87
- multi = EventMachine::Synchrony::Multi.new
88
- multi.add :a, db.aquery("select sleep(1)")
89
- multi.add :b, db.aquery("select sleep(1)")
90
- res = multi.perform
91
-
92
- p "Look ma, no callbacks, and parallel MySQL requests!"
93
- p res
94
-
95
- EventMachine.stop
96
- end
97
- ```
98
-
99
- ## Fiber-aware Multi interface: parallel HTTP requests
100
- 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.
101
-
102
- ```ruby
103
- require "em-synchrony"
104
- require "em-synchrony/em-http"
105
- EventMachine.synchrony do
106
- multi = EventMachine::Synchrony::Multi.new
107
- multi.add :a, EventMachine::HttpRequest.new("http://www.postrank.com").aget
108
- multi.add :b, EventMachine::HttpRequest.new("http://www.postrank.com").apost
109
- res = multi.perform
110
-
111
- p "Look ma, no callbacks, and parallel HTTP requests!"
112
- p res
113
-
114
- EventMachine.stop
115
- end
116
- ```
117
-
118
- ## Fiber-aware & EventMachine backed TCPSocket:
119
- 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.
120
-
121
- ```ruby
122
- require "em-synchrony"
123
- require "lib/em-synchrony"
124
- require "net/http"
125
-
126
- EM.synchrony do
127
- # replace default Socket code to use EventMachine Sockets instead
128
- TCPSocket = EventMachine::Synchrony::TCPSocket
129
-
130
- Net::HTTP.get_print 'www.google.com', '/index.html'
131
- EM.stop
132
- end
133
- ```
134
-
135
- ## Inline synchronization & Fiber sleep:
136
- 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.
137
-
138
- ```ruby
139
- require "em-synchrony"
140
- require "em-synchrony/em-http"
141
- EM.synchrony do
142
- # pass a callback enabled client to sync to automatically resume it when callback fires
143
- result = EM::Synchrony.sync EventMachine::HttpRequest.new('http://www.gooogle.com/').aget
144
- p result
145
-
146
- # pause exection for 2 seconds
147
- EM::Synchrony.sleep(2)
148
-
149
- EM.stop
150
- end
151
- ```
152
-
153
- ## Async ActiveRecord:
154
-
155
- 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/).
156
-
157
- ```ruby
158
- require "em-synchrony"
159
- require "em-synchrony/activerecord"
160
-
161
- ActiveRecord::Base.establish_connection(
162
- :adapter => 'em_mysql2',
163
- :database => 'widgets'
164
- )
165
-
166
- result = Widget.all.to_a
167
- ```
168
-
169
- # License
170
-
1
+ # EM-Synchrony
2
+
3
+ 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).
4
+
5
+ * Fiber aware ConnectionPool with sync/async query support
6
+ * Fiber aware Iterator to allow concurrency control & mixing of sync / async
7
+ * Fiber aware async inline support: turns any async function into sync
8
+ * Fiber aware Multi-request interface for any callback enabled clients
9
+ * Fiber aware TCPSocket replacement, powered by EventMachine
10
+ * Fiber aware Thread, Mutex, ConditionVariable clases
11
+ * Fiber aware sleep
12
+
13
+ Supported clients:
14
+
15
+ * [mysql2](http://github.com/igrigorik/em-synchrony/blob/master/spec/mysql2_spec.rb): .query is synchronous, while .aquery is async (see specs)
16
+ * [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
17
+ * [em-http-request](http://github.com/igrigorik/em-synchrony/blob/master/spec/http_spec.rb): .get, etc are synchronous, while .aget, etc are async
18
+ * [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
19
+ * [em-mongo](http://github.com/igrigorik/em-synchrony/blob/master/spec/em-mongo_spec.rb): .find, .first are synchronous
20
+ * [mongoid](http://github.com/igrigorik/em-synchrony/blob/master/spec/mongo_spec.rb): all functions synchronous, plus Rails compatibility
21
+ * em-jack: a[method]'s are async, and all regular jack method's are synchronous
22
+ * [AMQP](http://github.com/ruby-amqp/amqp): most of functions are synchronous (see specs)
23
+
24
+ Other clients with native Fiber support:
25
+
26
+ * redis: contains [synchrony code](https://github.com/ezmobius/redis-rb/blob/master/test/synchrony_driver.rb) right within the driver
27
+ * 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
28
+
29
+ ## Fiber-aware Iterator: mixing sync / async code
30
+
31
+ 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.
32
+
33
+ ```ruby
34
+ require "em-synchrony"
35
+ require "em-synchrony/em-http"
36
+
37
+ EM.synchrony do
38
+ concurrency = 2
39
+ urls = ['http://url.1.com', 'http://url2.com']
40
+
41
+ # iterator will execute async blocks until completion, .each, .inject also work!
42
+ results = EM::Synchrony::Iterator.new(urls, concurrency).map do |url, iter|
43
+
44
+ # fire async requests, on completion advance the iterator
45
+ http = EventMachine::HttpRequest.new(url).aget
46
+ http.callback { iter.return(http) }
47
+ http.errback { iter.return(http) }
48
+ end
49
+
50
+ p results # all completed requests
51
+ EventMachine.stop
52
+ end
53
+ ```
54
+
55
+ Or, you can use FiberIterator to hide the async nature of em-http:
56
+
57
+ ```ruby
58
+ require "em-synchrony"
59
+ require "em-synchrony/em-http"
60
+ require "em-synchrony/fiber_iterator"
61
+
62
+ EM.synchrony do
63
+ concurrency = 2
64
+ urls = ['http://url.1.com', 'http://url2.com']
65
+ results = []
66
+
67
+ EM::Synchrony::FiberIterator.new(urls, concurrency).each do |url|
68
+ resp = EventMachine::HttpRequest.new(url).get
69
+ results.push resp.response
70
+ end
71
+
72
+ p results # all completed requests
73
+ EventMachine.stop
74
+ end
75
+ ```
76
+
77
+ ## Fiber-aware ConnectionPool shared by a fiber:
78
+ 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.
79
+
80
+ ```ruby
81
+ require "em-synchrony"
82
+ require "em-synchrony/mysql2"
83
+
84
+ EventMachine.synchrony do
85
+ db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
86
+ Mysql2::EM::Client.new
87
+ end
88
+
89
+ multi = EventMachine::Synchrony::Multi.new
90
+ multi.add :a, db.aquery("select sleep(1)")
91
+ multi.add :b, db.aquery("select sleep(1)")
92
+ res = multi.perform
93
+
94
+ p "Look ma, no callbacks, and parallel MySQL requests!"
95
+ p res
96
+
97
+ EventMachine.stop
98
+ end
99
+ ```
100
+
101
+ ## Fiber-aware Multi interface: parallel HTTP requests
102
+ 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.
103
+
104
+ ```ruby
105
+ require "em-synchrony"
106
+ require "em-synchrony/em-http"
107
+ EventMachine.synchrony do
108
+ multi = EventMachine::Synchrony::Multi.new
109
+ multi.add :a, EventMachine::HttpRequest.new("http://www.postrank.com").aget
110
+ multi.add :b, EventMachine::HttpRequest.new("http://www.postrank.com").apost
111
+ res = multi.perform
112
+
113
+ p "Look ma, no callbacks, and parallel HTTP requests!"
114
+ p res
115
+
116
+ EventMachine.stop
117
+ end
118
+ ```
119
+
120
+ ## Fiber-aware & EventMachine backed TCPSocket:
121
+ 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.
122
+
123
+ ```ruby
124
+ require "em-synchrony"
125
+ require "lib/em-synchrony"
126
+ require "net/http"
127
+
128
+ EM.synchrony do
129
+ # replace default Socket code to use EventMachine Sockets instead
130
+ TCPSocket = EventMachine::Synchrony::TCPSocket
131
+
132
+ Net::HTTP.get_print 'www.google.com', '/index.html'
133
+ EM.stop
134
+ end
135
+ ```
136
+
137
+ ## Inline synchronization & Fiber sleep:
138
+ 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.
139
+
140
+ ```ruby
141
+ require "em-synchrony"
142
+ require "em-synchrony/em-http"
143
+ EM.synchrony do
144
+ # pass a callback enabled client to sync to automatically resume it when callback fires
145
+ result = EM::Synchrony.sync EventMachine::HttpRequest.new('http://www.gooogle.com/').aget
146
+ p result
147
+
148
+ # pause execution for 2 seconds
149
+ EM::Synchrony.sleep(2)
150
+
151
+ EM.stop
152
+ end
153
+ ```
154
+
155
+ ## Async ActiveRecord:
156
+
157
+ 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/).
158
+
159
+ ```ruby
160
+ require "em-synchrony"
161
+ require "em-synchrony/mysql2"
162
+ require "em-synchrony/activerecord"
163
+
164
+ ActiveRecord::Base.establish_connection(
165
+ :adapter => 'em_mysql2',
166
+ :database => 'widgets'
167
+ )
168
+
169
+ result = Widget.all.to_a
170
+ ```
171
+
172
+ # License
173
+
171
174
  The MIT License - Copyright (c) 2011 Ilya Grigorik
data/Rakefile CHANGED
@@ -1,10 +1,8 @@
1
- require 'bundler'
1
+ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
- Bundler.setup
5
- Bundler::GemHelper.install_tasks
4
+ task :default => [:spec]
5
+ task :test => [:spec]
6
6
 
7
7
  desc "Run all RSpec tests"
8
8
  RSpec::Core::RakeTask.new(:spec)
9
-
10
- task :default => :spec
data/em-synchrony.gemspec CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "em-synchrony"
6
- s.version = "1.0.0"
6
+ s.version = "1.0.1"
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["Ilya Grigorik"]
9
9
  s.email = ["ilya@igvita.com"]
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ # AR adapter for using a fibered mysql2 connection with EM
4
+ # This adapter should be used within Thin or Unicorn with the rack-fiber_pool middleware.
5
+ # Just update your database.yml's adapter to be 'em_mysql2'
6
+ # to real connection pool size.
7
+
8
+ require 'em-synchrony/mysql2'
9
+ require 'em-synchrony/activerecord'
10
+ require 'active_record/connection_adapters/mysql2_adapter'
11
+
12
+ module ActiveRecord
13
+ class Base
14
+ def self.em_mysql2_connection(config)
15
+ client = EM::Synchrony::ActiveRecord::ConnectionPool.new(size: config[:pool]) do
16
+ conn = ActiveRecord::ConnectionAdapters::EMMysql2Adapter::Client.new(config.symbolize_keys)
17
+ # From Mysql2Adapter#configure_connection
18
+ conn.query_options.merge!(:as => :array)
19
+
20
+ # By default, MySQL 'where id is null' selects the last inserted id.
21
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
22
+ variable_assignments = ['SQL_AUTO_IS_NULL=0']
23
+ encoding = config[:encoding]
24
+ variable_assignments << "NAMES '#{encoding}'" if encoding
25
+
26
+ wait_timeout = config[:wait_timeout]
27
+ wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
28
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
29
+
30
+ conn.query("SET #{variable_assignments.join(', ')}")
31
+ conn
32
+ end
33
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
34
+ ActiveRecord::ConnectionAdapters::EMMysql2Adapter.new(client, logger, options, config)
35
+ end
36
+ end
37
+
38
+ module ConnectionAdapters
39
+ class EMMysql2Adapter < ::ActiveRecord::ConnectionAdapters::Mysql2Adapter
40
+ class Client < Mysql2::EM::Client
41
+ include EM::Synchrony::ActiveRecord::Client
42
+ end
43
+
44
+ include EM::Synchrony::ActiveRecord::Adapter
45
+ end
46
+ end
47
+ end
data/lib/em-synchrony.rb CHANGED
@@ -8,6 +8,7 @@ rescue LoadError => error
8
8
  raise error unless defined? Fiber
9
9
  end
10
10
 
11
+ require "em-synchrony/core_ext"
11
12
  require "em-synchrony/thread"
12
13
  require "em-synchrony/em-multi"
13
14
  require "em-synchrony/tcpsocket"
@@ -29,7 +30,7 @@ module EventMachine
29
30
 
30
31
  module Synchrony
31
32
 
32
- # sync is a close relative to inclineCallbacks from Twisted (Python)
33
+ # sync is a close relative to inlineCallbacks from Twisted (Python)
33
34
  #
34
35
  # Synchrony.sync allows you to write sequential code while using asynchronous
35
36
  # or callback-based methods under the hood. Example:
@@ -45,39 +46,61 @@ module EventMachine
45
46
  #
46
47
  def self.sync(df)
47
48
  f = Fiber.current
48
- xback = proc {|r|
49
+ xback = proc do |*args|
49
50
  if f == Fiber.current
50
- return r
51
+ return args.size == 1 ? args.first : args
51
52
  else
52
- f.resume r
53
+ f.resume(*args)
53
54
  end
54
- }
55
- df.callback &xback
56
- df.errback &xback
55
+ end
56
+
57
+ df.callback(&xback)
58
+ df.errback(&xback)
57
59
 
58
60
  Fiber.yield
59
61
  end
60
62
 
61
- # a Fiber-aware sleep function using an EM timer
63
+
64
+ # Fiber-aware sleep function using an EM timer
65
+ #
66
+ # Execution is stopped for specified amount of seconds
67
+ # and then automatically resumed (just like regular sleep)
68
+ # except without locking the reactor thread
69
+ #
62
70
  def self.sleep(secs)
63
71
  fiber = Fiber.current
64
72
  EM::Timer.new(secs) { fiber.resume }
65
73
  Fiber.yield
66
74
  end
67
75
 
76
+ # Fiber-aware EventMachine timer: wraps the passed in
77
+ # block within a new fiber context such that you can
78
+ # continue using synchrony methods
79
+ #
68
80
  def self.add_timer(interval, &blk)
69
81
  EM.add_timer(interval) do
70
82
  Fiber.new { blk.call }.resume
71
83
  end
72
84
  end
73
85
 
86
+ # Fiber-aware EventMachine timer: wraps the passed in
87
+ # block within a new fiber (new fiber on every invocation)
88
+ # to allow you to continue using synchrony methods
89
+ #
74
90
  def self.add_periodic_timer(interval, &blk)
75
91
  EM.add_periodic_timer(interval) do
76
92
  Fiber.new { blk.call }.resume
77
93
  end
78
94
  end
79
-
80
- # routes to EM::Synchrony::Keyboard
95
+
96
+ # Fiber-aware EM.next_tick convenience function
97
+ #
98
+ def self.next_tick(&blk)
99
+ EM.next_tick { Fiber.new { blk.call }.resume }
100
+ end
101
+
102
+ # Routes to EM::Synchrony::Keyboard
103
+ #
81
104
  def self.gets
82
105
  EventMachine::Synchrony::Keyboard.new.gets
83
106
  end