sinatra-synchrony 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source :rubygems
2
+ gem 'sinatra'
3
+ gem 'rack-fiber_pool', '0.9.1'
4
+ gem 'em-http-request', '0.3.0'
5
+ gem 'em-synchrony', '0.2.0'
6
+ gem 'em-resolv-replace', '1.1.1'
7
+ gem 'async-rack', '0.5.1'
8
+ gem 'wrong', '0.5.0'
9
+ gem 'rack-test', '0.5.7'
10
+ gem 'freegenie-em-spec', '0.2.3'
11
+ gem 'ruby-debug19'
@@ -0,0 +1,111 @@
1
+ Sinatra::Synchrony
2
+ ===
3
+
4
+ Sinatra + EM-Synchrony - fast, concurrent web applications with no callbacks!
5
+
6
+ Sinatra::Synchrony is a very small extension for Sinatra that dramatically improves the concurrency of your web application. Powered by [EventMachine](https://github.com/eventmachine/eventmachine) and [EM-Synchrony](https://github.com/igrigorik/em-synchrony), it increases the number of clients your application can serve per process when you have a lot of slow IO calls (like HTTP calls to external APIs). Because it uses [Fibers](http://www.ruby-doc.org/core-1.9/classes/Fiber.html) internally to handle concurrency, no callback gymnastics are required! Just develop as if you were writing a normal Sinatra web application, use non-blocking libraries (see below) and you're all set!
7
+
8
+ How it works
9
+ ---
10
+
11
+ * Loads [EventMachine](https://github.com/eventmachine/eventmachine) and [EM-Synchrony](https://github.com/igrigorik/em-synchrony). Requires app server with EventMachine and Ruby 1.9 support (Thin, Rainbows!, Heroku).
12
+ * Inserts the [Rack::FiberPool](https://github.com/mperham/rack-fiber_pool) middleware, which automatically provides a Fiber for each incoming request, allowing EM-Synchrony to work.
13
+ * Inserts [async-rack](https://github.com/rkh/async-rack/tree/master/lib/async_rack), which makes Rack's middleware more async friendly.
14
+ * Adds [em-http-request](https://github.com/igrigorik/em-http-request), which you can use with EM::Synchrony to do concurrent HTTP calls to APIs! Or if you'd rather use a different client:
15
+ * Patches TCPSocket via EM-Synchrony. Any software that uses this (such as an HTTP Client that uses Net::HTTP) can run without blocking IO. [RestClient](https://github.com/archiloque/rest-client) works great with this!
16
+ * Patches Rack::Test so that it runs your tests within an EventMachine. Just test the same way you did before and it should just work.
17
+ * Patches Resolv via [em-resolv-replace](https://github.com/mperham/em-resolv-replace), enabling non-blocking DNS lookups magically, the way David Bowie would want it.
18
+
19
+ What it doesn't do (yet)
20
+ ---
21
+
22
+ Provide non-blocking drivers for everything. Right now the focus was to deal with the biggest concurrency problem for most apps, which is API calls to external websites. You don't have to make _everything_ non-blocking to speed up applications with this approach, which is the important thing to understand. For example, if your database access is under ten milliseconds, it's not as bad as an API call to an external web site that takes a few seconds. There are numerous non-blocking drivers available however, check out the [Protocol Implementations](https://github.com/eventmachine/eventmachine/wiki/Protocol-Implementations) page on the [EventMachine GitHub Wiki](https://github.com/eventmachine/eventmachine/wiki) for a full list. I would personally like to see plug-and-play drivers implemented for the major three ORMs (ActiveRecord, DataMapper, Sequel), because then I could simply drop them into this gem and you'd be non-blocking without requiring any special changes. For most of the web applications I work on, this would be all I need to eliminate my blocking IO problems forever!
23
+
24
+ Installation
25
+ ---
26
+ Install the gem:
27
+
28
+ gem install sinatra-synchrony
29
+
30
+ Register with Sinatra __at the top, before any other middleware or plugins are loaded__:
31
+
32
+ require 'sinatra/synchrony'
33
+ class App < Sinatra::Base
34
+ register Sinatra::Synchrony
35
+ end
36
+
37
+ One important thing: __do not use Sinatra's internal session code__. So no "enable :sessions". Instead, enable the sessions directly via the [Rack::Session::Cookie](http://rack.rubyforge.org/doc/classes/Rack/Session/Cookie.html) middleware (there is no consequence to doing this, Sinatra does the same thing under the hood.. it just does it in the wrong load order):
38
+
39
+ class App < Sinatra::Base
40
+ register Sinatra::Synchrony
41
+ use Rack::Session::Cookie, :secret => 'CHANGE ME TO SOMETHING!'
42
+ end
43
+
44
+ Tests
45
+ ---
46
+
47
+ Just write your tests as usual, my Rack::Test patch fixes the "outside of EventMachine" errors. You must be in the __test__ environment so that Sinatra will not load Rack::FiberPool. My patch admittedly needs some work.
48
+
49
+ Benchmarks
50
+ ---
51
+ It's pretty fast!
52
+
53
+ class App < Sinatra::Base
54
+ register Sinatra::Synchrony
55
+ get '/' do
56
+ 'Hello World!'
57
+ end
58
+ end
59
+
60
+ run with rackup -s thin:
61
+
62
+ $ ab -c 50 -n 2000 http://127.0.0.1:9292/
63
+ ...
64
+ Requests per second: 3102.30 [#/sec] (mean)
65
+ Time per request: 16.117 [ms] (mean)
66
+ Time per request: 0.322 [ms] (mean, across all concurrent requests)
67
+
68
+ Connection Times (ms)
69
+ min mean[+/-sd] median max
70
+ Connect: 0 0 0.1 0 1
71
+ Processing: 5 16 7.7 13 38
72
+ Waiting: 3 13 7.0 10 35
73
+ Total: 6 16 7.7 13 38
74
+
75
+ Let's try a simple blocking IO example to prove it works. 100 hits to google.com:
76
+
77
+ require 'rest-client'
78
+
79
+ class App < Sinatra::Base
80
+ register Sinatra::Synchrony
81
+ get '/' do
82
+ # Using EventMachine::HttpRequest
83
+ # EM::Synchrony.sync(EventMachine::HttpRequest.new('http://google.com').get).response
84
+
85
+ # Using RestClient, which gets concurrency via patched TCPSocket, no changes required!
86
+ RestClient.get 'http://google.com'
87
+ end
88
+ end
89
+
90
+ $ ab -c 100 -n 100 http://127.0.0.1:9292/
91
+ ...
92
+ Time taken for tests: 1.270 seconds
93
+
94
+ For a perspective, this operation takes __33 seconds__ without this extension. That's __26x__ faster!
95
+
96
+ TODO / Thoughts
97
+ ---
98
+ * This is fairly alpha. Please test before deploying to production.
99
+ * Provide better method for patching Rack::Test that's less fragile to version changes. This is a big priority and I intend to improve this. Pull requests here welcome!
100
+ * Research way to run tests with Rack::FiberPool enabled.
101
+
102
+ Author
103
+ ---
104
+ * [Kyle Drake](http://kyledrake.net)
105
+
106
+ Thanks
107
+ ---
108
+ * [Ilya Grigorik](http://www.igvita.com) and [PostRank](http://www.postrank.com) for their amazing work on em-synchrony, em-http-request, and countless articles explaining this.
109
+ * [Mike Perham](http://www.mikeperham.com) and [Carbon Five](http://carbonfive.com). For rack-fiber_pool, em-resolv-replace, and many blog posts and presentations on this.
110
+ * [Konstantin Haase](http://rkh.im/) for async-rack and his many contributions to Sinatra.
111
+ * The many Sinatra developers that liberated me from framework hell, and EventMachine developers that liberated me from blocking IO hell.
@@ -0,0 +1,25 @@
1
+ require 'rack/test'
2
+ # Monkeypatch to provide EM within tests.
3
+ # If you have a better approach, please send a pull request!
4
+
5
+ module Rack
6
+ class MockSession
7
+ def request(uri, env)
8
+ env["HTTP_COOKIE"] ||= cookie_jar.for(uri)
9
+ @last_request = Rack::Request.new(env)
10
+ EM.synchrony do
11
+ status, headers, body = @app.call(@last_request.env)
12
+ @last_response = MockResponse.new(status, headers, body, env["rack.errors"].flush)
13
+ body.close if body.respond_to?(:close)
14
+ cookie_jar.merge(last_response.headers["Set-Cookie"], uri)
15
+ @after_request.each { |hook| hook.call }
16
+ if @last_response.respond_to?(:finish)
17
+ @last_response.finish
18
+ else
19
+ @last_response
20
+ end
21
+ EM.stop
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'sinatra/base'
2
+ require 'rack/fiber_pool'
3
+ require 'async-rack'
4
+ require 'eventmachine'
5
+ require 'em-http-request'
6
+ require 'em-synchrony'
7
+ require 'em-resolv-replace'
8
+ require 'net/http'
9
+
10
+ original_verbosity = $VERBOSE
11
+ $VERBOSE = nil
12
+ TCPSocket = EventMachine::Synchrony::TCPSocket
13
+ $VERBOSE = original_verbosity
14
+
15
+ module Sinatra
16
+ module Synchrony
17
+ def self.registered(app)
18
+ app.use Rack::FiberPool unless app.test?
19
+ end
20
+ end
21
+ register Synchrony
22
+ end
@@ -0,0 +1,29 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+ require File.join(File.join(File.expand_path(File.dirname(__FILE__))), '..', 'lib', 'sinatra', 'synchrony')
3
+ require File.join(File.join(File.expand_path(File.dirname(__FILE__))), '..', 'lib', 'rack', 'test_synchrony')
4
+ require 'minitest/autorun'
5
+ require 'wrong/adapters/minitest'
6
+ Wrong.config.alias_assert :expect
7
+
8
+ def mock_app(base=Sinatra::Base, &block)
9
+ @app = Sinatra.new(base, &block)
10
+ @app.set :environment, :test
11
+ @app.disable :show_exceptions
12
+ @app.register Sinatra::Synchrony
13
+ end
14
+ def app; @app end
15
+
16
+ describe 'A mock app' do
17
+ include Rack::Test::Methods
18
+ it 'successfully completes a sleep call' do
19
+ mock_app {
20
+ get '/?' do
21
+ EM::Synchrony.sleep(0.0001)
22
+ 'ok'
23
+ end
24
+ }
25
+ get '/'
26
+ expect { last_response.ok? }
27
+ expect { last_response.body == 'ok' }
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,198 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-synchrony
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Kyle Drake
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-04 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: sinatra
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 15
29
+ segments:
30
+ - 1
31
+ - 0
32
+ version: "1.0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rack-fiber_pool
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - "="
42
+ - !ruby/object:Gem::Version
43
+ hash: 57
44
+ segments:
45
+ - 0
46
+ - 9
47
+ - 1
48
+ version: 0.9.1
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: async-rack
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - "="
58
+ - !ruby/object:Gem::Version
59
+ hash: 9
60
+ segments:
61
+ - 0
62
+ - 5
63
+ - 1
64
+ version: 0.5.1
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: em-http-request
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - "="
74
+ - !ruby/object:Gem::Version
75
+ hash: 19
76
+ segments:
77
+ - 0
78
+ - 3
79
+ - 0
80
+ version: 0.3.0
81
+ type: :runtime
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: em-synchrony
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - "="
90
+ - !ruby/object:Gem::Version
91
+ hash: 23
92
+ segments:
93
+ - 0
94
+ - 2
95
+ - 0
96
+ version: 0.2.0
97
+ type: :runtime
98
+ version_requirements: *id005
99
+ - !ruby/object:Gem::Dependency
100
+ name: em-resolv-replace
101
+ prerelease: false
102
+ requirement: &id006 !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - "="
106
+ - !ruby/object:Gem::Version
107
+ hash: 17
108
+ segments:
109
+ - 1
110
+ - 1
111
+ - 1
112
+ version: 1.1.1
113
+ type: :runtime
114
+ version_requirements: *id006
115
+ - !ruby/object:Gem::Dependency
116
+ name: rack-test
117
+ prerelease: false
118
+ requirement: &id007 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - "="
122
+ - !ruby/object:Gem::Version
123
+ hash: 5
124
+ segments:
125
+ - 0
126
+ - 5
127
+ - 7
128
+ version: 0.5.7
129
+ type: :development
130
+ version_requirements: *id007
131
+ - !ruby/object:Gem::Dependency
132
+ name: wrong
133
+ prerelease: false
134
+ requirement: &id008 !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - "="
138
+ - !ruby/object:Gem::Version
139
+ hash: 11
140
+ segments:
141
+ - 0
142
+ - 5
143
+ - 0
144
+ version: 0.5.0
145
+ type: :development
146
+ version_requirements: *id008
147
+ description: Bootstraps in code required to take advantage of EventMachine/EM-Synchrony's concurrency enhancements for slow IO. Patches TCPSocket, which makes anything based on it EM-aware (including RestClient). Includes patch for tests. Requires ruby 1.9.
148
+ email:
149
+ - kyledrake@gmail.com
150
+ executables: []
151
+
152
+ extensions: []
153
+
154
+ extra_rdoc_files: []
155
+
156
+ files:
157
+ - lib/sinatra/synchrony.rb
158
+ - lib/rack/test_synchrony.rb
159
+ - spec/synchrony_spec.rb
160
+ - Gemfile
161
+ - README.markdown
162
+ homepage: https://github.com/kyledrake/sinatra-synchrony
163
+ licenses: []
164
+
165
+ post_install_message:
166
+ rdoc_options: []
167
+
168
+ require_paths:
169
+ - lib
170
+ required_ruby_version: !ruby/object:Gem::Requirement
171
+ none: false
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ hash: 3
176
+ segments:
177
+ - 0
178
+ version: "0"
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ none: false
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ hash: 19
185
+ segments:
186
+ - 1
187
+ - 3
188
+ - 4
189
+ version: 1.3.4
190
+ requirements: []
191
+
192
+ rubyforge_project: sinatra-synchrony
193
+ rubygems_version: 1.7.2
194
+ signing_key:
195
+ specification_version: 3
196
+ summary: Bootstraps Sinatra with EM-Synchrony code, make TCPSocket EM-aware, provides support for tests
197
+ test_files: []
198
+