em-synchrony 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source :gemcutter
2
+
3
+ gem 'eventmachine', :git => 'git://github.com/eventmachine/eventmachine.git'
4
+
5
+ group :development do
6
+ gem 'rspec', '~> 2.0.0'
7
+ gem 'em-http-request'
8
+ gem 'remcached'
9
+ gem 'em-mongo'
10
+ gem 'bson_ext'
11
+ gem 'mysqlplus'
12
+ gem 'em-mysqlplus'
13
+ gem 'em-redis'
14
+ gem 'mongo'
15
+ end
data/README.md CHANGED
@@ -1,33 +1,28 @@
1
1
  # EM-Synchrony
2
2
 
3
- Blog post: [Untangling Evented Code with Ruby Fibers](http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers)
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). Word of warning: even though Fibers have been backported to Ruby 1.8.x, these classes assume Ruby 1.9.x (if you're using fibers in production, you should be on 1.9.x anyway).
4
4
 
5
- Collection of convenience classes and patches to common EventMachine clients to
6
- make them Fiber aware and friendly. Word of warning: even though fibers have been
7
- backported to Ruby 1.8.x, these classes assume Ruby 1.9 (if you're using fibers
8
- in production, you should be on 1.9 anyway)
5
+ * Fiber aware connection pool 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
9
12
 
10
- Features:
13
+ Supported clients:
11
14
 
12
- * Fiber aware connection pool with sync/async query support
13
- * Multi request interface which accepts any callback enabled client
14
- * Fibered iterator to allow concurrency control & mixing of sync / async
15
15
  * em-http-request: .get, etc are synchronous, while .aget, etc are async
16
+ * em-memcached & remcached: .get, etc, and .multi_* methods are synchronous
17
+ * em-redis: synchronous connect, .a{cmd} are async
16
18
  * em-mysqlplus: .query is synchronous, while .aquery is async
17
- * remcached: .get, etc, and .multi_* methods are synchronous
18
- * bitly: synchronous api calls with EM::HttpRequest.
19
+ * em-mongo: .find, .first are synchronous
20
+ * mongoid: all functions synchronous, plus Rails compatability
21
+ * bitly v2 and v3: synchronous api calls with EM::HttpRequest.
19
22
 
20
- ## Example with async em-http client:
21
- require "em-synchrony/em-http"
22
- EventMachine.synchrony do
23
- res = EventMachine::HttpRequest.new("http://www.postrank.com").get
24
- p "Look ma, no callbacks!"
25
- p res
26
23
 
27
- EventMachine.stop
28
- end
29
-
30
- ## EM Iterator & mixing sync / async code
24
+ ## Fiber-aware Iterator: mixing sync / async code
25
+ 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
26
 
32
27
  require "em-synchrony/em-http"
33
28
  EM.synchrony do
@@ -46,7 +41,8 @@ Features:
46
41
  EventMachine.stop
47
42
  end
48
43
 
49
- ## Example connection pool shared by a fiber:
44
+ ## Fiber-aware ConnectionPool shared by a fiber:
45
+ 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.
50
46
 
51
47
  require "em-synchrony/em-mysqlplus"
52
48
  EventMachine.synchrony do
@@ -65,7 +61,8 @@ Features:
65
61
  EventMachine.stop
66
62
  end
67
63
 
68
- ## Example with multi-request async em-http client:
64
+ ## Fiber-aware Multi inteface: parallel HTTP requests
65
+ 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.
69
66
 
70
67
  require "em-synchrony/em-http"
71
68
  EventMachine.synchrony do
@@ -80,17 +77,44 @@ Features:
80
77
  EventMachine.stop
81
78
  end
82
79
 
83
- ## Example with async Bitly client:
80
+ ## Fiber-aware & EventMachine backed TCPSocket:
81
+ 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.
82
+
83
+ require "lib/em-synchrony"
84
+ require "net/http"
85
+
86
+ EM.synchrony do
87
+ # replace default Socket code to use EventMachine Sockets instead
88
+ TCPSocket = EventMachine::Synchrony::TCPSocket
89
+
90
+ Net::HTTP.get_print 'www.google.com', '/index.html'
91
+ EM.stop
92
+ end
93
+
94
+ ## Inline synchronization & Fiber sleep:
95
+ 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.
84
96
 
85
- require "em-synchrony/em-bitly"
86
97
  EM.synchrony do
87
- bitly = Bitly.new('[INSERT_LOGIN]', '[INSERT_API_KEY]')
88
- url = 'http://github.com/igrigorik/em-synchrony'
89
- short = bitly.shorten(url)
98
+ # pass a callback enabled client to sync to automatically resume it when callback fires
99
+ result = EM::Synchrony.sync EventMachine::HttpRequest.new('http://www.gooogle.com/').aget
100
+ p result
90
101
 
91
- p "Short #{url} => #{short.jmp_url}"
102
+ # pause exection for 2 seconds
103
+ EM::Synchrony.sleep(2)
104
+
105
+ EM.stop
92
106
  end
93
107
 
108
+ ## Patched clients
109
+
110
+ EM-Synchrony ships with a number of patched Eventmachine clients, which allow you to patch your asynchronous drivers on the fly to behave as if they were actually blocking clients:
111
+
112
+ * [em-http-request](http://github.com/igrigorik/em-synchrony/blob/master/spec/http_spec.rb)
113
+ * [em-mysqlplus](http://github.com/igrigorik/em-synchrony/blob/master/spec/mysqlplus_spec.rb)
114
+ * [em-redis](http://github.com/igrigorik/em-synchrony/blob/master/spec/redis_spec.rb)
115
+ * [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)
116
+ * [em-mongo](http://github.com/igrigorik/em-synchrony/blob/master/spec/em-mongo_spec.rb) & [mongoid](http://github.com/igrigorik/em-synchrony/blob/master/spec/mongo_spec.rb)
117
+
94
118
  # License
95
119
 
96
120
  (The MIT License)
@@ -114,4 +138,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
114
138
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
115
139
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
116
140
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
117
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
141
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,20 +1,4 @@
1
- require "rake"
1
+ require 'bundler'
2
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: gem install jeweler"
20
- end
3
+ Bundler.setup
4
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "em-synchrony"
6
+ s.version = "0.2.0"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Ilya Grigorik"]
9
+ s.email = ["ilya@igvita.com"]
10
+ s.homepage = "http://github.com/igrigorik/em-synchrony"
11
+ s.summary = %q{Fiber aware EventMachine libraries}
12
+ s.description = s.summary
13
+
14
+ s.rubyforge_project = "em-synchrony"
15
+
16
+ s.required_ruby_version = Gem::Requirement.new(">= 1.9")
17
+ s.add_runtime_dependency("eventmachine", [">= 0.12.9"])
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
data/examples/bitly.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'lib/em-synchrony'
2
+
3
+ require "em-synchrony/em-bitly"
4
+ EM.synchrony do
5
+ bitly = Bitly.new('[INSERT_LOGIN]', '[INSERT_API_KEY]')
6
+ url = 'http://github.com/igrigorik/em-synchrony'
7
+ short = bitly.shorten(url)
8
+
9
+ p "Short #{url} => #{short.jmp_url}"
10
+ EM.stop
11
+ end
12
+
13
+
14
+ Bitly.use_api_version_3
15
+ EM.synchrony do
16
+ bitly = Bitly.new('[INSERT_LOGIN]', '[INSERT_API_KEY]')
17
+
18
+ url = 'http://github.com/igrigorik/em-synchrony'
19
+ domain='nyti.ms'
20
+
21
+ pro = bitly.bitly_pro_domain(domain)
22
+ p "Domain #{domain} pro=#{pro}"
23
+
24
+ EM.stop
25
+ end
@@ -0,0 +1,50 @@
1
+ # CSP Experiments with Ruby
2
+
3
+ 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.
4
+
5
+ Note: none of the Ruby examples actually give you the parallelism of Go.
6
+
7
+ ## Notes
8
+ * Instead of explicitly using locks to mediate access to shared data, Go encourages the use of channels to pass references to data between goroutines.
9
+
10
+ * Channels combine communication — the exchange of a value—with synchronization — guaranteeing that two calculations (goroutines) are in a known state.
11
+
12
+ 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.
13
+
14
+ ## Simple channel example in Go
15
+
16
+ package main
17
+
18
+ import (
19
+ "fmt"
20
+ "time"
21
+ )
22
+
23
+ func main() {
24
+ c := make(chan string)
25
+
26
+ go func() {
27
+ time.Sleep(1)
28
+ c <- "go go go sleep 1!"
29
+ }()
30
+
31
+ fmt.Printf("%v\n", <-c) // Wait for goroutine to finish
32
+ }
33
+
34
+ ## Equivalent in Ruby
35
+
36
+ require 'go'
37
+
38
+ EM.synchrony do
39
+ c = Channel.new
40
+
41
+ go {
42
+ sleep(1)
43
+ c << 'go go go sleep 1!'
44
+ }
45
+
46
+ puts c.pop
47
+
48
+ EM.stop
49
+ end
50
+
@@ -0,0 +1,17 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "time"
6
+ )
7
+
8
+ func main() {
9
+ c := make(chan string)
10
+
11
+ go func() {
12
+ time.Sleep(1)
13
+ c <- "go go go sleep 1!"
14
+ }()
15
+
16
+ fmt.Printf("%v\n", <-c) // Wait for goroutine to finish
17
+ }
@@ -0,0 +1,14 @@
1
+ require 'go'
2
+
3
+ EM.synchrony do
4
+ c = Channel.new
5
+
6
+ go {
7
+ sleep(1)
8
+ c << 'go go go sleep 1!'
9
+ }
10
+
11
+ puts c.pop
12
+
13
+ EM.stop
14
+ end
@@ -0,0 +1,34 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "runtime"
6
+ )
7
+
8
+ func producer(c chan int, N int, s chan bool) {
9
+ for i := 0; i < N; i++ {
10
+ fmt.Printf("producer: %d\n", i)
11
+ c <- i
12
+ }
13
+ s <- true
14
+ }
15
+
16
+ func consumer(c chan int, N int, s chan bool) {
17
+ for i := 0; i < N; i++ {
18
+ fmt.Printf("consumer got: %d\n", <- c)
19
+ }
20
+ s <- true
21
+ }
22
+
23
+ func main() {
24
+ runtime.GOMAXPROCS(2)
25
+
26
+ c := make(chan int)
27
+ s := make(chan bool)
28
+
29
+ go producer(c, 10, s)
30
+ go consumer(c, 10, s)
31
+
32
+ <- s
33
+ <- s
34
+ }
@@ -0,0 +1,62 @@
1
+ require 'go'
2
+
3
+ EM.synchrony do
4
+ def producer(c, n, s)
5
+ n.times do |i|
6
+ puts "producer: #{i}"
7
+ c << i
8
+ end
9
+
10
+ s << "producer finished"
11
+ end
12
+
13
+ consumer = ->(c, n, s) do
14
+ n.times do |i|
15
+ puts "consumer 1 got: #{c.pop}"
16
+ end
17
+
18
+ s << "consumer finished"
19
+ end
20
+
21
+ c = Channel.new(size: 2)
22
+ s = Channel.new
23
+ n = 10
24
+
25
+ # mix the syntax, just for fun...
26
+ go c,n,s, &method(:producer)
27
+ go c,n-1,s, &consumer
28
+
29
+ go c,s do |c, s|
30
+ puts "consumer 2 got: #{c.pop}"
31
+ s << "consumer 2 finished"
32
+ end
33
+
34
+ 3.times { puts s.pop }
35
+
36
+ EM.stop
37
+ end
38
+
39
+ # (M=6c0863) igrigorik /git/em-synchrony/examples/go> ruby consumer-publisher.rb
40
+ # producer: 0
41
+ # producer: 1
42
+ # consumer 1 got: [0]
43
+ # producer: 2
44
+ # consumer 2 got: [1]
45
+ # producer: 3
46
+ # consumer 1 got: [2]
47
+ # producer: 4
48
+ # consumer 2 finished
49
+ # consumer 1 got: [3]
50
+ # producer: 5
51
+ # consumer 1 got: [4]
52
+ # producer: 6
53
+ # consumer 1 got: [5]
54
+ # producer: 7
55
+ # consumer 1 got: [6]
56
+ # producer: 8
57
+ # consumer 1 got: [7]
58
+ # producer: 9
59
+ # consumer 1 got: [8]
60
+ # consumer 1 got: [9]
61
+ # producer finished
62
+ # consumer finished
data/examples/go/go.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'em-synchrony'
2
+
3
+ module Kernel
4
+ def go(*args, &blk)
5
+ EM.next_tick do
6
+ Fiber.new { blk.call(*args) }.resume
7
+ end
8
+ end
9
+ end
10
+
11
+ class Channel < EM::Queue
12
+ def initialize(opts = {})
13
+ @limit = opts[:size]
14
+ @prodq = []
15
+ @size = 0
16
+
17
+ super()
18
+ end
19
+
20
+ def size; @size; end
21
+ def empty?; size == 0; end
22
+
23
+ def pop
24
+ f = Fiber.current
25
+ clb = Proc.new do |*args|
26
+ @size -= 1
27
+ f.resume(args)
28
+ @prodq.shift.call if !@prodq.empty?
29
+ end
30
+
31
+ super(&clb)
32
+ Fiber.yield
33
+ end
34
+
35
+ def push(*items)
36
+ f = Fiber.current
37
+ @size += 1
38
+
39
+ EM.next_tick { super(*items) }
40
+
41
+ # if the queue is bounded, then suspend the producer
42
+ # until someone consumes a pending message
43
+ if @limit && size >= @limit
44
+ @prodq.push -> { f.resume }
45
+ Fiber.yield
46
+ end
47
+ end
48
+ alias :<< :push
49
+ end