swift 1.0.3 → 1.1.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/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:
|