em-synchrony 0.3.0.beta.1 → 1.0.0
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/.gitignore +1 -0
- data/.rspec +1 -0
- data/Gemfile +7 -5
- data/README.md +121 -91
- data/Rakefile +7 -1
- data/em-synchrony.gemspec +2 -2
- data/lib/em-synchrony.rb +29 -5
- data/lib/em-synchrony/active_record/connection_adapters/em_mysql2_adapter.rb +18 -0
- data/lib/em-synchrony/active_record/patches.rb +132 -0
- data/lib/em-synchrony/activerecord.rb +5 -0
- data/lib/em-synchrony/connection_pool.rb +3 -3
- data/lib/em-synchrony/em-hiredis.rb +24 -0
- data/lib/em-synchrony/em-http.rb +10 -6
- data/lib/em-synchrony/em-mongo.rb +80 -13
- data/lib/em-synchrony/em-redis.rb +29 -6
- data/lib/em-synchrony/em-remcached.rb +43 -19
- data/lib/em-synchrony/fiber_iterator.rb +18 -0
- data/lib/em-synchrony/keyboard.rb +33 -0
- data/lib/em-synchrony/mongo.rb +19 -1
- data/lib/em-synchrony/mysql2.rb +25 -0
- data/spec/activerecord_spec.rb +50 -0
- data/spec/em-mongo_spec.rb +192 -28
- data/spec/fiber_iterator_spec.rb +39 -0
- data/spec/helper/all.rb +2 -1
- data/spec/hiredis_spec.rb +40 -0
- data/spec/http_spec.rb +23 -0
- data/spec/keyboard_spec.rb +59 -0
- data/spec/{mysqlplus_spec.rb → mysql2_spec.rb} +26 -14
- data/spec/redis_spec.rb +39 -0
- data/spec/synchrony_spec.rb +14 -0
- data/spec/timer_spec.rb +35 -0
- metadata +52 -60
- data/examples/bitly.rb +0 -25
- data/lib/em-synchrony/em-bitly.rb +0 -58
- data/lib/em-synchrony/em-mysqlplus.rb +0 -27
data/.gitignore
CHANGED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-I.
|
data/Gemfile
CHANGED
@@ -3,13 +3,15 @@ source :gemcutter
|
|
3
3
|
gem 'eventmachine', :git => 'git://github.com/eventmachine/eventmachine.git'
|
4
4
|
|
5
5
|
group :development do
|
6
|
-
gem 'rspec'
|
6
|
+
gem 'rspec'
|
7
7
|
gem 'em-http-request', :git => 'git://github.com/igrigorik/em-http-request'
|
8
8
|
gem 'remcached'
|
9
|
-
gem 'em-mongo'
|
9
|
+
# gem 'em-mongo', :git => 'https://github.com/bcg/em-mongo.git'
|
10
|
+
gem 'activerecord', '>= 3.1.0.rc6'
|
11
|
+
gem 'em-mongo', '~> 0.3.6'
|
10
12
|
gem 'bson_ext'
|
11
|
-
gem '
|
12
|
-
gem 'em-mysqlplus'
|
13
|
+
gem 'mysql2'
|
13
14
|
gem 'em-redis', '~> 0.3.0'
|
15
|
+
gem 'em-hiredis'
|
14
16
|
gem 'mongo'
|
15
|
-
end
|
17
|
+
end
|
data/README.md
CHANGED
@@ -1,141 +1,171 @@
|
|
1
1
|
# EM-Synchrony
|
2
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).
|
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
4
|
|
5
|
-
* Fiber aware
|
6
|
-
* Fiber aware
|
5
|
+
* Fiber aware ConnectionPool with sync/async query support
|
6
|
+
* Fiber aware Iterator to allow concurrency control & mixing of sync / async
|
7
7
|
* Fiber aware async inline support: turns any async function into sync
|
8
|
-
* Fiber aware
|
8
|
+
* Fiber aware Multi-request interface for any callback enabled clients
|
9
9
|
* Fiber aware TCPSocket replacement, powered by EventMachine
|
10
10
|
* Fiber aware Thread, Mutex, ConditionVariable clases
|
11
11
|
* Fiber aware sleep
|
12
12
|
|
13
13
|
Supported clients:
|
14
14
|
|
15
|
-
* em-
|
16
|
-
* em-
|
17
|
-
* em-
|
18
|
-
* em-
|
19
|
-
* em-mongo: .find, .first are synchronous
|
20
|
-
* mongoid: all functions synchronous, plus Rails
|
21
|
-
*
|
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
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
|
23
27
|
|
24
28
|
## Fiber-aware Iterator: mixing sync / async code
|
29
|
+
|
25
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.
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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:
|
31
55
|
|
32
|
-
|
33
|
-
|
56
|
+
```ruby
|
57
|
+
require "em-synchrony"
|
58
|
+
require "em-synchrony/em-http"
|
34
59
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
60
|
+
EM.synchrony do
|
61
|
+
concurrency = 2
|
62
|
+
urls = ['http://url.1.com', 'http://url2.com']
|
63
|
+
results = []
|
39
64
|
|
40
|
-
|
41
|
-
EventMachine.
|
65
|
+
EM::Synchrony::FiberIterator.new(urls, concurrency).each do |url|
|
66
|
+
resp = EventMachine::HttpRequest.new(url).get
|
67
|
+
results.push resp.response
|
42
68
|
end
|
43
69
|
|
70
|
+
p results # all completed requests
|
71
|
+
EventMachine.stop
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
44
75
|
## Fiber-aware ConnectionPool shared by a fiber:
|
45
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.
|
46
77
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
EventMachine::MySQL.new(host: "localhost")
|
51
|
-
end
|
78
|
+
```ruby
|
79
|
+
require "em-synchrony"
|
80
|
+
require "em-synchrony/mysql2"
|
52
81
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
82
|
+
EventMachine.synchrony do
|
83
|
+
db = EventMachine::Synchrony::ConnectionPool.new(size: 2) do
|
84
|
+
Mysql2::EM::Client.new
|
85
|
+
end
|
57
86
|
|
58
|
-
|
59
|
-
|
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
|
60
91
|
|
61
|
-
|
62
|
-
|
92
|
+
p "Look ma, no callbacks, and parallel MySQL requests!"
|
93
|
+
p res
|
94
|
+
|
95
|
+
EventMachine.stop
|
96
|
+
end
|
97
|
+
```
|
63
98
|
|
64
|
-
## Fiber-aware Multi
|
99
|
+
## Fiber-aware Multi interface: parallel HTTP requests
|
65
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.
|
66
101
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
73
110
|
|
74
|
-
|
75
|
-
|
111
|
+
p "Look ma, no callbacks, and parallel HTTP requests!"
|
112
|
+
p res
|
76
113
|
|
77
|
-
|
78
|
-
|
114
|
+
EventMachine.stop
|
115
|
+
end
|
116
|
+
```
|
79
117
|
|
80
118
|
## Fiber-aware & EventMachine backed TCPSocket:
|
81
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.
|
82
120
|
|
83
|
-
|
84
|
-
|
121
|
+
```ruby
|
122
|
+
require "em-synchrony"
|
123
|
+
require "lib/em-synchrony"
|
124
|
+
require "net/http"
|
85
125
|
|
86
|
-
|
87
|
-
|
88
|
-
|
126
|
+
EM.synchrony do
|
127
|
+
# replace default Socket code to use EventMachine Sockets instead
|
128
|
+
TCPSocket = EventMachine::Synchrony::TCPSocket
|
89
129
|
|
90
|
-
|
91
|
-
|
92
|
-
|
130
|
+
Net::HTTP.get_print 'www.google.com', '/index.html'
|
131
|
+
EM.stop
|
132
|
+
end
|
133
|
+
```
|
93
134
|
|
94
135
|
## Inline synchronization & Fiber sleep:
|
95
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.
|
96
137
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
101
145
|
|
102
|
-
|
103
|
-
|
146
|
+
# pause exection for 2 seconds
|
147
|
+
EM::Synchrony.sleep(2)
|
104
148
|
|
105
|
-
|
106
|
-
|
149
|
+
EM.stop
|
150
|
+
end
|
151
|
+
```
|
107
152
|
|
108
|
-
##
|
153
|
+
## Async ActiveRecord:
|
109
154
|
|
110
|
-
|
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/).
|
111
156
|
|
112
|
-
|
113
|
-
|
114
|
-
|
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)
|
157
|
+
```ruby
|
158
|
+
require "em-synchrony"
|
159
|
+
require "em-synchrony/activerecord"
|
117
160
|
|
118
|
-
|
119
|
-
|
120
|
-
|
161
|
+
ActiveRecord::Base.establish_connection(
|
162
|
+
:adapter => 'em_mysql2',
|
163
|
+
:database => 'widgets'
|
164
|
+
)
|
121
165
|
|
122
|
-
|
166
|
+
result = Widget.all.to_a
|
167
|
+
```
|
123
168
|
|
124
|
-
|
125
|
-
a copy of this software and associated documentation files (the
|
126
|
-
'Software'), to deal in the Software without restriction, including
|
127
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
128
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
129
|
-
permit persons to whom the Software is furnished to do so, subject to
|
130
|
-
the following conditions:
|
131
|
-
|
132
|
-
The above copyright notice and this permission notice shall be
|
133
|
-
included in all copies or substantial portions of the Software.
|
169
|
+
# License
|
134
170
|
|
135
|
-
|
136
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
137
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
138
|
-
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
139
|
-
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
140
|
-
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
141
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
171
|
+
The MIT License - Copyright (c) 2011 Ilya Grigorik
|
data/Rakefile
CHANGED
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 = "0.
|
6
|
+
s.version = "1.0.0"
|
7
7
|
s.platform = Gem::Platform::RUBY
|
8
8
|
s.authors = ["Ilya Grigorik"]
|
9
9
|
s.email = ["ilya@igvita.com"]
|
@@ -19,4 +19,4 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
21
|
s.require_paths = ["lib"]
|
22
|
-
end
|
22
|
+
end
|
data/lib/em-synchrony.rb
CHANGED
@@ -12,6 +12,7 @@ require "em-synchrony/thread"
|
|
12
12
|
require "em-synchrony/em-multi"
|
13
13
|
require "em-synchrony/tcpsocket"
|
14
14
|
require "em-synchrony/connection_pool"
|
15
|
+
require "em-synchrony/keyboard"
|
15
16
|
require "em-synchrony/iterator" if EventMachine::VERSION > '0.12.10'
|
16
17
|
|
17
18
|
module EventMachine
|
@@ -44,18 +45,41 @@ module EventMachine
|
|
44
45
|
#
|
45
46
|
def self.sync(df)
|
46
47
|
f = Fiber.current
|
47
|
-
|
48
|
-
|
48
|
+
xback = proc {|r|
|
49
|
+
if f == Fiber.current
|
50
|
+
return r
|
51
|
+
else
|
52
|
+
f.resume r
|
53
|
+
end
|
54
|
+
}
|
55
|
+
df.callback &xback
|
56
|
+
df.errback &xback
|
49
57
|
|
50
58
|
Fiber.yield
|
51
59
|
end
|
52
60
|
|
53
61
|
# a Fiber-aware sleep function using an EM timer
|
54
|
-
def self.sleep(
|
62
|
+
def self.sleep(secs)
|
55
63
|
fiber = Fiber.current
|
56
|
-
EM::Timer.new(secs) {
|
64
|
+
EM::Timer.new(secs) { fiber.resume }
|
57
65
|
Fiber.yield
|
58
66
|
end
|
59
|
-
end
|
60
67
|
|
68
|
+
def self.add_timer(interval, &blk)
|
69
|
+
EM.add_timer(interval) do
|
70
|
+
Fiber.new { blk.call }.resume
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.add_periodic_timer(interval, &blk)
|
75
|
+
EM.add_periodic_timer(interval) do
|
76
|
+
Fiber.new { blk.call }.resume
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# routes to EM::Synchrony::Keyboard
|
81
|
+
def self.gets
|
82
|
+
EventMachine::Synchrony::Keyboard.new.gets
|
83
|
+
end
|
84
|
+
end
|
61
85
|
end
|
@@ -0,0 +1,18 @@
|
|
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
|
+
|
7
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
8
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
9
|
+
|
10
|
+
module ActiveRecord
|
11
|
+
class Base
|
12
|
+
def self.em_mysql2_connection(config)
|
13
|
+
client = Mysql2::EM::Client.new(config.symbolize_keys)
|
14
|
+
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
|
15
|
+
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# Necessary monkeypatching to make AR fiber-friendly.
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
|
6
|
+
def self.fiber_pools
|
7
|
+
@fiber_pools ||= []
|
8
|
+
end
|
9
|
+
def self.register_fiber_pool(fp)
|
10
|
+
fiber_pools << fp
|
11
|
+
end
|
12
|
+
|
13
|
+
class FiberedMonitor
|
14
|
+
class Queue
|
15
|
+
def initialize
|
16
|
+
@queue = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def wait(timeout)
|
20
|
+
t = timeout || 5
|
21
|
+
fiber = Fiber.current
|
22
|
+
x = EM::Timer.new(t) do
|
23
|
+
@queue.delete(fiber)
|
24
|
+
fiber.resume(false)
|
25
|
+
end
|
26
|
+
@queue << fiber
|
27
|
+
Fiber.yield.tap do
|
28
|
+
x.cancel
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def signal
|
33
|
+
fiber = @queue.pop
|
34
|
+
fiber.resume(true) if fiber
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def synchronize
|
39
|
+
yield
|
40
|
+
end
|
41
|
+
|
42
|
+
def new_cond
|
43
|
+
Queue.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# ActiveRecord's connection pool is based on threads. Since we are working
|
48
|
+
# with EM and a single thread, multiple fiber design, we need to provide
|
49
|
+
# our own connection pool that keys off of Fiber.current so that different
|
50
|
+
# fibers running in the same thread don't try to use the same connection.
|
51
|
+
class ConnectionPool
|
52
|
+
def initialize(spec)
|
53
|
+
@spec = spec
|
54
|
+
|
55
|
+
# The cache of reserved connections mapped to threads
|
56
|
+
@reserved_connections = {}
|
57
|
+
|
58
|
+
# The mutex used to synchronize pool access
|
59
|
+
@connection_mutex = FiberedMonitor.new
|
60
|
+
@queue = @connection_mutex.new_cond
|
61
|
+
|
62
|
+
# default 5 second timeout unless on ruby 1.9
|
63
|
+
@timeout = spec.config[:wait_timeout] || 5
|
64
|
+
|
65
|
+
# default max pool size to 5
|
66
|
+
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
67
|
+
|
68
|
+
@connections = []
|
69
|
+
@checked_out = []
|
70
|
+
@automatic_reconnect = true
|
71
|
+
@tables = {}
|
72
|
+
|
73
|
+
@columns = Hash.new do |h, table_name|
|
74
|
+
h[table_name] = with_connection do |conn|
|
75
|
+
|
76
|
+
# Fetch a list of columns
|
77
|
+
conn.columns(table_name, "#{table_name} Columns").tap do |columns|
|
78
|
+
|
79
|
+
# set primary key information
|
80
|
+
columns.each do |column|
|
81
|
+
column.primary = column.name == primary_keys[table_name]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
@columns_hash = Hash.new do |h, table_name|
|
88
|
+
h[table_name] = Hash[columns[table_name].map { |col|
|
89
|
+
[col.name, col]
|
90
|
+
}]
|
91
|
+
end
|
92
|
+
|
93
|
+
@primary_keys = Hash.new do |h, table_name|
|
94
|
+
h[table_name] = with_connection do |conn|
|
95
|
+
table_exists?(table_name) ? conn.primary_key(table_name) : 'id'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def clear_stale_cached_connections!
|
101
|
+
cache = @reserved_connections
|
102
|
+
keys = Set.new(cache.keys)
|
103
|
+
|
104
|
+
ActiveRecord::ConnectionAdapters.fiber_pools.each do |pool|
|
105
|
+
pool.busy_fibers.each_pair do |object_id, fiber|
|
106
|
+
keys.delete(object_id)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
keys.each do |key|
|
111
|
+
next unless cache.has_key?(key)
|
112
|
+
checkin cache[key]
|
113
|
+
cache.delete(key)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def current_connection_id #:nodoc:
|
120
|
+
Fiber.current.object_id
|
121
|
+
end
|
122
|
+
|
123
|
+
def checkout_and_verify(c)
|
124
|
+
@checked_out << c
|
125
|
+
c.run_callbacks :checkout
|
126
|
+
c.verify!
|
127
|
+
c
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|