swift 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +20 -0
- data/VERSION +1 -1
- data/lib/swift.rb +22 -11
- data/lib/swift/adapter.rb +1 -2
- data/lib/swift/eventmachine.rb +6 -3
- data/lib/swift/fiber_connection_pool.rb +94 -0
- data/lib/swift/synchrony.rb +4 -1
- data/swift.gemspec +4 -2
- data/test/test_synchrony.rb +58 -0
- metadata +14 -10
data/README.md
CHANGED
@@ -303,6 +303,26 @@ or use the `em-synchrony` api for `swift`
|
|
303
303
|
end
|
304
304
|
```
|
305
305
|
|
306
|
+
### Fibers and Connection Pools
|
307
|
+
|
308
|
+
If you intend to use `Swift::Record` with `em-synchrony` you will need to use a fiber aware connection pool.
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
require 'swift/fiber_connection_pool'
|
312
|
+
|
313
|
+
EM.run do
|
314
|
+
Swift.setup(:default) do
|
315
|
+
Swift::FiberConnectionPool.new(size: 5) {Swift::Adapter::Postgres.new(db: 'swift')}
|
316
|
+
end
|
317
|
+
|
318
|
+
5.times do
|
319
|
+
EM.synchrony do
|
320
|
+
p User.execute("select * from users").entries
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
```
|
325
|
+
|
306
326
|
## Performance
|
307
327
|
|
308
328
|
Swift prefers performance when it doesn't compromise the Ruby-ish interface. It's unfair to compare Swift to DataMapper
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
data/lib/swift.rb
CHANGED
@@ -67,11 +67,15 @@ module Swift
|
|
67
67
|
# @return [Swift::Adapter]
|
68
68
|
#
|
69
69
|
# @see Swift::Adapter
|
70
|
-
def setup name, type, options = {}
|
71
|
-
|
72
|
-
|
70
|
+
def setup name, type = nil, options = {}
|
71
|
+
if block_given?
|
72
|
+
repositories[name] = yield
|
73
|
+
else
|
74
|
+
unless type.kind_of?(Class) && type < Swift::Adapter
|
75
|
+
raise TypeError, "Expected +type+ Swift::Adapter subclass but got #{type.inspect}"
|
76
|
+
end
|
77
|
+
repositories[name] = type.new(options)
|
73
78
|
end
|
74
|
-
(@repositories ||= {})[name] = type.new(options)
|
75
79
|
end
|
76
80
|
|
77
81
|
# Fetch or scope a block to a specific DB by name.
|
@@ -90,14 +94,9 @@ module Swift
|
|
90
94
|
# @param [Symbol] name Adapter name.
|
91
95
|
# @param [Proc] block Scope this block to the named adapter instead of <tt>:default</tt>.
|
92
96
|
# @return [Swift::Adapter]
|
93
|
-
#--
|
94
|
-
# I pilfered the logic from DM but I don't really understand what is/isn't thread safe.
|
95
97
|
def db name = nil, &block
|
96
|
-
repository =
|
97
|
-
|
98
|
-
else
|
99
|
-
scopes.last
|
100
|
-
end
|
98
|
+
repository = name || scopes.size < 1 ? repositories[name ||= :default] : scopes.last
|
99
|
+
repository or raise "Unknown db '#{name}', did you forget to #setup ?"
|
101
100
|
|
102
101
|
if block_given?
|
103
102
|
begin
|
@@ -119,10 +118,22 @@ module Swift
|
|
119
118
|
@schema ||= []
|
120
119
|
end
|
121
120
|
|
121
|
+
# Trace the command execution in currently scoped adapter
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# Swift.trace { Swift.db.execute("select * from users") }
|
125
|
+
#
|
126
|
+
# @see Swift::Adapter#trace
|
122
127
|
def trace io = $stdout, &block
|
123
128
|
Swift.db.trace(io, &block)
|
124
129
|
end
|
125
130
|
|
131
|
+
# @private
|
132
|
+
def repositories
|
133
|
+
@repositories ||= {}
|
134
|
+
end
|
135
|
+
|
136
|
+
# @private
|
126
137
|
def scopes
|
127
138
|
Thread.current[:swift_db] ||= []
|
128
139
|
end
|
data/lib/swift/adapter.rb
CHANGED
@@ -219,8 +219,7 @@ module Swift
|
|
219
219
|
log_command(start, command, bind) if @trace
|
220
220
|
end
|
221
221
|
|
222
|
-
|
223
|
-
|
222
|
+
# :nodoc:
|
224
223
|
def log_command start, command, bind
|
225
224
|
@trace.print Time.now.strftime('%F %T.%N'), ' - %.9f' % (Time.now - start).to_f, ' - ', command
|
226
225
|
@trace.print ' ', bind if bind && bind.size > 0
|
data/lib/swift/eventmachine.rb
CHANGED
@@ -10,6 +10,7 @@ module Swift
|
|
10
10
|
|
11
11
|
class EMHandler < EM::Connection
|
12
12
|
def initialize adapter, record, defer
|
13
|
+
@started = Time.now
|
13
14
|
@adapter = adapter
|
14
15
|
@record = record
|
15
16
|
@defer = defer
|
@@ -17,7 +18,9 @@ module Swift
|
|
17
18
|
|
18
19
|
def notify_readable
|
19
20
|
detach
|
20
|
-
@adapter.pending.shift
|
21
|
+
start, command, bind = @adapter.pending.shift
|
22
|
+
@adapter.log_command(start, command, bind) if @adapter.trace?
|
23
|
+
|
21
24
|
begin
|
22
25
|
@defer.succeed(@record ? Result.new(@record, @adapter.result) : @adapter.result)
|
23
26
|
rescue Exception => e
|
@@ -41,9 +44,9 @@ module Swift
|
|
41
44
|
def execute command, *bind
|
42
45
|
raise RuntimeError, 'Command already in progress' unless pending.empty?
|
43
46
|
|
44
|
-
|
45
|
-
start = Time.now
|
47
|
+
start = Time.now
|
46
48
|
record, command = command, bind.shift if command.kind_of?(Class) && command < Record
|
49
|
+
pending << [start, command, bind]
|
47
50
|
query(command, *bind)
|
48
51
|
|
49
52
|
EM::DefaultDeferrable.new.tap do |defer|
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Based on EM::Synchrony::ConnectionPool
|
2
|
+
|
3
|
+
require 'swift/synchrony'
|
4
|
+
|
5
|
+
module Swift
|
6
|
+
class FiberConnectionPool
|
7
|
+
|
8
|
+
def initialize opts, &block
|
9
|
+
@reserved = {} # map of in-progress connections
|
10
|
+
@available = [] # pool of free connections
|
11
|
+
@pending = [] # pending reservations (FIFO)
|
12
|
+
@trace = nil
|
13
|
+
|
14
|
+
opts[:size].times do
|
15
|
+
@available.push(block.call)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def trace io = $stdout
|
20
|
+
if block_given?
|
21
|
+
begin
|
22
|
+
_io, @trace = @trace, io
|
23
|
+
yield
|
24
|
+
ensure
|
25
|
+
@trace = _io
|
26
|
+
end
|
27
|
+
else
|
28
|
+
@trace = io
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
# Choose first available connection and pass it to the supplied
|
34
|
+
# block. This will block indefinitely until there is an available
|
35
|
+
# connection to service the request.
|
36
|
+
def __reserve__
|
37
|
+
id = "#{Fiber.current.object_id}:#{rand}"
|
38
|
+
fiber = Fiber.current
|
39
|
+
begin
|
40
|
+
yield acquire(id, fiber)
|
41
|
+
ensure
|
42
|
+
release(id)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Acquire a lock on a connection and assign it to executing fiber
|
47
|
+
# - if connection is available, pass it back to the calling block
|
48
|
+
# - if pool is full, yield the current fiber until connection is available
|
49
|
+
def acquire id, fiber
|
50
|
+
if conn = @available.pop
|
51
|
+
@reserved[id] = conn
|
52
|
+
else
|
53
|
+
Fiber.yield @pending.push(fiber)
|
54
|
+
acquire(id, fiber)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Release connection assigned to the supplied fiber and
|
59
|
+
# resume any other pending connections (which will
|
60
|
+
# immediately try to run acquire on the pool)
|
61
|
+
def release(id)
|
62
|
+
@available.push(@reserved.delete(id))
|
63
|
+
if pending = @pending.shift
|
64
|
+
pending.resume
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Allow the pool to behave as the underlying connection
|
69
|
+
def method_missing method, *args, &blk
|
70
|
+
__reserve__ do |conn|
|
71
|
+
if @trace
|
72
|
+
conn.trace(@trace) {conn.__send__(method, *args, &blk)}
|
73
|
+
else
|
74
|
+
conn.__send__(method, *args, &blk)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end # FiberConnectionPool
|
79
|
+
|
80
|
+
class Adapter::Sql
|
81
|
+
def serialized_transaction &block
|
82
|
+
Swift.scopes.push(self)
|
83
|
+
execute('begin')
|
84
|
+
res = yield(self)
|
85
|
+
execute('commit')
|
86
|
+
res
|
87
|
+
rescue => e
|
88
|
+
execute('rollback')
|
89
|
+
raise e
|
90
|
+
ensure
|
91
|
+
Swift.scopes.pop
|
92
|
+
end
|
93
|
+
end # Adapter::Sql
|
94
|
+
end # Swift
|
data/lib/swift/synchrony.rb
CHANGED
@@ -27,7 +27,10 @@ module Swift
|
|
27
27
|
# @see [Swift::Adapter]
|
28
28
|
def execute *args
|
29
29
|
res = EM::Synchrony.sync aexecute(*args)
|
30
|
-
|
30
|
+
if res.kind_of?(Error)
|
31
|
+
res.set_backtrace caller.reject {|subject| subject =~ %r{swift/fiber_connection_pool}}
|
32
|
+
raise res
|
33
|
+
end
|
31
34
|
yield res if block_given?
|
32
35
|
res
|
33
36
|
end
|
data/swift.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "swift"
|
8
|
-
s.version = "1.0
|
8
|
+
s.version = "1.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Shane Hanna", "Bharanee 'Barney' Rathna"]
|
12
|
-
s.date = "
|
12
|
+
s.date = "2013-02-18"
|
13
13
|
s.description = "A rational rudimentary database abstraction."
|
14
14
|
s.email = ["shane.hanna@gmail.com", "deepfryed@gmail.com"]
|
15
15
|
s.extra_rdoc_files = [
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
|
|
30
30
|
"lib/swift/adapter/sqlite3.rb",
|
31
31
|
"lib/swift/attribute.rb",
|
32
32
|
"lib/swift/eventmachine.rb",
|
33
|
+
"lib/swift/fiber_connection_pool.rb",
|
33
34
|
"lib/swift/header.rb",
|
34
35
|
"lib/swift/identity_map.rb",
|
35
36
|
"lib/swift/migrations.rb",
|
@@ -52,6 +53,7 @@ Gem::Specification.new do |s|
|
|
52
53
|
"test/test_io.rb",
|
53
54
|
"test/test_record.rb",
|
54
55
|
"test/test_swift.rb",
|
56
|
+
"test/test_synchrony.rb",
|
55
57
|
"test/test_timestamps.rb",
|
56
58
|
"test/test_transactions.rb",
|
57
59
|
"test/test_types.rb",
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe 'fiber connection pool' do
|
4
|
+
before do
|
5
|
+
skip 'swift/synchrony re-defines Adapter#execute' unless ENV['TEST_SWIFT_SYNCHRONY']
|
6
|
+
|
7
|
+
require 'swift/fiber_connection_pool'
|
8
|
+
EM.synchrony do
|
9
|
+
Swift.setup(:default, Swift::Adapter::Postgres, db: 'swift_test')
|
10
|
+
Swift.db.execute('drop table if exists users')
|
11
|
+
Swift.db.execute('create table users(id serial primary key, name text)')
|
12
|
+
|
13
|
+
@user = Class.new(Swift::Record) do
|
14
|
+
store :users
|
15
|
+
attribute :id, Swift::Type::Integer, key: true, serial: true
|
16
|
+
attribute :name, Swift::Type::String
|
17
|
+
end
|
18
|
+
|
19
|
+
10.times { @user.create(name: 'test') }
|
20
|
+
EM.stop
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can synchronize queries across fibers' do
|
25
|
+
EM.run do
|
26
|
+
Swift.setup(:default) { Swift::FiberConnectionPool.new(size: 2) {Swift::Adapter::Postgres.new(db: 'swift_test')}}
|
27
|
+
|
28
|
+
@counts = []
|
29
|
+
5.times do
|
30
|
+
EM.synchrony do
|
31
|
+
@counts << @user.execute('select * from users').selected_rows
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
EM.add_timer(0.2) { EM.stop }
|
36
|
+
end
|
37
|
+
|
38
|
+
assert_equal 5, @counts.size
|
39
|
+
assert_equal [10], @counts.uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'sets appropriate backtrace for errors' do
|
43
|
+
EM.synchrony do
|
44
|
+
error = nil
|
45
|
+
Swift.setup(:default) { Swift::FiberConnectionPool.new(size: 2) {Swift::Adapter::Postgres.new(db: 'swift_test')}}
|
46
|
+
|
47
|
+
begin
|
48
|
+
Swift.db.execute 'foo bar baz'
|
49
|
+
rescue => e
|
50
|
+
error = e
|
51
|
+
end
|
52
|
+
|
53
|
+
assert error
|
54
|
+
assert_match %r{test/test_synchrony.rb:48}, error.backtrace.first
|
55
|
+
EM.stop
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: swift
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,24 +10,24 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2013-02-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name: minitest
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
18
|
- - ! '>='
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: 1.7.0
|
23
|
-
type: :development
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
21
|
none: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
23
|
requirements:
|
28
24
|
- - ! '>='
|
29
25
|
- !ruby/object:Gem::Version
|
30
26
|
version: 1.7.0
|
27
|
+
none: false
|
28
|
+
name: minitest
|
29
|
+
prerelease: false
|
30
|
+
type: :development
|
31
31
|
description: A rational rudimentary database abstraction.
|
32
32
|
email:
|
33
33
|
- shane.hanna@gmail.com
|
@@ -51,6 +51,7 @@ files:
|
|
51
51
|
- lib/swift/adapter/sqlite3.rb
|
52
52
|
- lib/swift/attribute.rb
|
53
53
|
- lib/swift/eventmachine.rb
|
54
|
+
- lib/swift/fiber_connection_pool.rb
|
54
55
|
- lib/swift/header.rb
|
55
56
|
- lib/swift/identity_map.rb
|
56
57
|
- lib/swift/migrations.rb
|
@@ -73,6 +74,7 @@ files:
|
|
73
74
|
- test/test_io.rb
|
74
75
|
- test/test_record.rb
|
75
76
|
- test/test_swift.rb
|
77
|
+
- test/test_synchrony.rb
|
76
78
|
- test/test_timestamps.rb
|
77
79
|
- test/test_transactions.rb
|
78
80
|
- test/test_types.rb
|
@@ -84,17 +86,20 @@ rdoc_options: []
|
|
84
86
|
require_paths:
|
85
87
|
- lib
|
86
88
|
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
-
none: false
|
88
89
|
requirements:
|
89
90
|
- - ! '>='
|
90
91
|
- !ruby/object:Gem::Version
|
92
|
+
hash: -3874151873791212661
|
91
93
|
version: '0'
|
92
|
-
|
94
|
+
segments:
|
95
|
+
- 0
|
93
96
|
none: false
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
98
|
requirements:
|
95
99
|
- - ! '>='
|
96
100
|
- !ruby/object:Gem::Version
|
97
101
|
version: '0'
|
102
|
+
none: false
|
98
103
|
requirements: []
|
99
104
|
rubyforge_project:
|
100
105
|
rubygems_version: 1.8.24
|
@@ -102,4 +107,3 @@ signing_key:
|
|
102
107
|
specification_version: 3
|
103
108
|
summary: A rational rudimentary database abstraction.
|
104
109
|
test_files: []
|
105
|
-
has_rdoc:
|