assistance 0.0.1 → 0.0.2
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/CHANGELOG +4 -0
- data/Rakefile +9 -2
- data/lib/assistance/connection_pool.rb +131 -133
- data/spec/connection_pool_spec.rb +8 -8
- metadata +1 -1
data/CHANGELOG
CHANGED
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ include FileUtils
|
|
9
9
|
# Configuration
|
10
10
|
##############################################################################
|
11
11
|
NAME = "assistance"
|
12
|
-
VERS = "0.0.
|
12
|
+
VERS = "0.0.2"
|
13
13
|
CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
|
14
14
|
RDOC_OPTS = [
|
15
15
|
"--quiet",
|
@@ -33,6 +33,13 @@ Rake::RDocTask.new do |rdoc|
|
|
33
33
|
rdoc.rdoc_files.add ["README", "COPYING", "lib/assistance.rb", "lib/**/*.rb"]
|
34
34
|
end
|
35
35
|
|
36
|
+
task :doc_rforge => [:doc]
|
37
|
+
|
38
|
+
desc "Update docs and upload to rubyforge.org"
|
39
|
+
task :doc_rforge do
|
40
|
+
sh %{scp -r doc/rdoc/* ciconia@rubyforge.org:/var/www/gforge-projects/assistance}
|
41
|
+
end
|
42
|
+
|
36
43
|
##############################################################################
|
37
44
|
# Gem packaging
|
38
45
|
##############################################################################
|
@@ -85,7 +92,7 @@ end
|
|
85
92
|
|
86
93
|
task :tag do
|
87
94
|
cwd = FileUtils.pwd
|
88
|
-
sh %{cd
|
95
|
+
sh %{cd .. && svn copy #{cwd} tags/#{NAME}-#{VERS} && svn commit -m "#{NAME}-#{VERS} tag." tags}
|
89
96
|
end
|
90
97
|
|
91
98
|
##############################################################################
|
@@ -1,152 +1,150 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
attr_reader :mutex
|
9
|
-
|
10
|
-
# The maximum number of connections.
|
11
|
-
attr_reader :max_size
|
12
|
-
|
13
|
-
# The proc used to create a new connection.
|
14
|
-
attr_accessor :connection_proc
|
15
|
-
|
16
|
-
attr_reader :available_connections, :allocated, :created_count
|
3
|
+
# A ConnectionPool manages access to database connections by keeping
|
4
|
+
# multiple connections and giving threads exclusive access to each
|
5
|
+
# connection.
|
6
|
+
class ConnectionPool
|
7
|
+
attr_reader :mutex
|
17
8
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
# pool = ConnectionPool.new(10)
|
27
|
-
# pool.connection_proc = proc {MyConnection.new(opts)}
|
28
|
-
def initialize(max_size = 4, &block)
|
29
|
-
@max_size = max_size
|
30
|
-
@mutex = Mutex.new
|
31
|
-
@connection_proc = block
|
9
|
+
# The maximum number of connections.
|
10
|
+
attr_reader :max_size
|
11
|
+
|
12
|
+
# The proc used to create a new connection.
|
13
|
+
attr_accessor :connection_proc
|
14
|
+
|
15
|
+
attr_reader :available_connections, :allocated, :created_count
|
32
16
|
|
33
|
-
|
34
|
-
|
35
|
-
|
17
|
+
# Constructs a new pool with a maximum size. If a block is supplied, it
|
18
|
+
# is used to create new connections as they are needed.
|
19
|
+
#
|
20
|
+
# pool = ConnectionPool.new(10) {MyConnection.new(opts)}
|
21
|
+
#
|
22
|
+
# The connection creation proc can be changed at any time by assigning a
|
23
|
+
# Proc to pool#connection_proc.
|
24
|
+
#
|
25
|
+
# pool = ConnectionPool.new(10)
|
26
|
+
# pool.connection_proc = proc {MyConnection.new(opts)}
|
27
|
+
def initialize(max_size = 4, &block)
|
28
|
+
@max_size = max_size
|
29
|
+
@mutex = Mutex.new
|
30
|
+
@connection_proc = block
|
31
|
+
|
32
|
+
@available_connections = []
|
33
|
+
@allocated = {}
|
34
|
+
@created_count = 0
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the number of created connections.
|
38
|
+
def size
|
39
|
+
@created_count
|
40
|
+
end
|
41
|
+
|
42
|
+
# Assigns a connection to the current thread, yielding the connection
|
43
|
+
# to the supplied block.
|
44
|
+
#
|
45
|
+
# pool.hold {|conn| conn.execute('DROP TABLE posts')}
|
46
|
+
#
|
47
|
+
# Pool#hold is re-entrant, meaning it can be called recursively in
|
48
|
+
# the same thread without blocking.
|
49
|
+
#
|
50
|
+
# If no connection is available, Pool#hold will block until a connection
|
51
|
+
# is available.
|
52
|
+
def hold
|
53
|
+
t = Thread.current
|
54
|
+
if (conn = owned_connection(t))
|
55
|
+
return yield(conn)
|
36
56
|
end
|
37
|
-
|
38
|
-
|
39
|
-
def size
|
40
|
-
@created_count
|
57
|
+
while !(conn = acquire(t))
|
58
|
+
sleep 0.001
|
41
59
|
end
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
# pool.hold {|conn| conn.execute('DROP TABLE posts')}
|
47
|
-
#
|
48
|
-
# Pool#hold is re-entrant, meaning it can be called recursively in
|
49
|
-
# the same thread without blocking.
|
50
|
-
#
|
51
|
-
# If no connection is available, Pool#hold will block until a connection
|
52
|
-
# is available.
|
53
|
-
def hold
|
54
|
-
t = Thread.current
|
55
|
-
if (conn = owned_connection(t))
|
56
|
-
return yield(conn)
|
57
|
-
end
|
58
|
-
while !(conn = acquire(t))
|
59
|
-
sleep 0.001
|
60
|
-
end
|
61
|
-
begin
|
62
|
-
yield conn
|
63
|
-
ensure
|
64
|
-
release(t)
|
65
|
-
end
|
66
|
-
rescue Exception => e
|
67
|
-
# if the error is not a StandardError it is converted into RuntimeError.
|
68
|
-
raise e.is_a?(StandardError) ? e : e.message
|
60
|
+
begin
|
61
|
+
yield conn
|
62
|
+
ensure
|
63
|
+
release(t)
|
69
64
|
end
|
70
|
-
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
65
|
+
rescue Exception => e
|
66
|
+
# if the error is not a StandardError it is converted into RuntimeError.
|
67
|
+
raise e.is_a?(StandardError) ? e : e.message
|
68
|
+
end
|
69
|
+
|
70
|
+
# Removes all connection currently available, optionally yielding each
|
71
|
+
# connection to the given block. This method has the effect of
|
72
|
+
# disconnecting from the database. Once a connection is requested using
|
73
|
+
# #hold, the connection pool creates new connections to the database.
|
74
|
+
def disconnect(&block)
|
75
|
+
@mutex.synchronize do
|
76
|
+
@available_connections.each {|c| block[c]} if block
|
77
|
+
@available_connections = []
|
78
|
+
@created_count = @allocated.size
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
# Returns the connection owned by the supplied thread, if any.
|
84
|
+
def owned_connection(thread)
|
85
|
+
@mutex.synchronize {@allocated[thread]}
|
81
86
|
end
|
82
87
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# Assigns a connection to the supplied thread, if one is available.
|
90
|
-
def acquire(thread)
|
91
|
-
@mutex.synchronize do
|
92
|
-
if conn = available
|
93
|
-
@allocated[thread] = conn
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# Returns an available connection. If no connection is available,
|
99
|
-
# tries to create a new connection.
|
100
|
-
def available
|
101
|
-
@available_connections.pop || make_new
|
102
|
-
end
|
103
|
-
|
104
|
-
# Creates a new connection if the size of the pool is less than the
|
105
|
-
# maximum size.
|
106
|
-
def make_new
|
107
|
-
if @created_count < @max_size
|
108
|
-
@created_count += 1
|
109
|
-
@connection_proc ? @connection_proc.call : \
|
110
|
-
(raise Error, "No connection proc specified")
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# Releases the connection assigned to the supplied thread.
|
115
|
-
def release(thread)
|
116
|
-
@mutex.synchronize do
|
117
|
-
@available_connections << @allocated[thread]
|
118
|
-
@allocated.delete(thread)
|
88
|
+
# Assigns a connection to the supplied thread, if one is available.
|
89
|
+
def acquire(thread)
|
90
|
+
@mutex.synchronize do
|
91
|
+
if conn = available
|
92
|
+
@allocated[thread] = conn
|
119
93
|
end
|
120
94
|
end
|
121
|
-
|
122
|
-
|
123
|
-
# A SingleThreadedPool acts as a replacement for a ConnectionPool for use
|
124
|
-
# in single-threaded applications. ConnectionPool imposes a substantial
|
125
|
-
# performance penalty, so SingleThreadedPool is used to gain some speed.
|
126
|
-
class SingleThreadedPool
|
127
|
-
attr_reader :conn
|
128
|
-
attr_writer :connection_proc
|
95
|
+
end
|
129
96
|
|
130
|
-
#
|
131
|
-
|
132
|
-
|
97
|
+
# Returns an available connection. If no connection is available,
|
98
|
+
# tries to create a new connection.
|
99
|
+
def available
|
100
|
+
@available_connections.pop || make_new
|
133
101
|
end
|
134
102
|
|
135
|
-
#
|
136
|
-
#
|
137
|
-
def
|
138
|
-
@
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
103
|
+
# Creates a new connection if the size of the pool is less than the
|
104
|
+
# maximum size.
|
105
|
+
def make_new
|
106
|
+
if @created_count < @max_size
|
107
|
+
@created_count += 1
|
108
|
+
@connection_proc ? @connection_proc.call : \
|
109
|
+
(raise Error, "No connection proc specified")
|
110
|
+
end
|
143
111
|
end
|
144
112
|
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
113
|
+
# Releases the connection assigned to the supplied thread.
|
114
|
+
def release(thread)
|
115
|
+
@mutex.synchronize do
|
116
|
+
@available_connections << @allocated[thread]
|
117
|
+
@allocated.delete(thread)
|
118
|
+
end
|
150
119
|
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# A SingleThreadedPool acts as a replacement for a ConnectionPool for use
|
123
|
+
# in single-threaded applications. ConnectionPool imposes a substantial
|
124
|
+
# performance penalty, so SingleThreadedPool is used to gain some speed.
|
125
|
+
class SingleThreadedPool
|
126
|
+
attr_reader :conn
|
127
|
+
attr_writer :connection_proc
|
128
|
+
|
129
|
+
# Initializes the instance with the supplied block as the connection_proc.
|
130
|
+
def initialize(&block)
|
131
|
+
@connection_proc = block
|
132
|
+
end
|
133
|
+
|
134
|
+
# Yields the connection to the supplied block. This method simulates the
|
135
|
+
# ConnectionPool#hold API.
|
136
|
+
def hold
|
137
|
+
@conn ||= @connection_proc.call
|
138
|
+
yield @conn
|
139
|
+
rescue Exception => e
|
140
|
+
# if the error is not a StandardError it is converted into RuntimeError.
|
141
|
+
raise e.is_a?(StandardError) ? e : e.message
|
142
|
+
end
|
143
|
+
|
144
|
+
# Disconnects from the database. Once a connection is requested using
|
145
|
+
# #hold, the connection is reestablished.
|
146
|
+
def disconnect(&block)
|
147
|
+
block[@conn] if block && @conn
|
148
|
+
@conn = nil
|
151
149
|
end
|
152
150
|
end
|
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), 'spec_helper')
|
|
2
2
|
|
3
3
|
context "An empty ConnectionPool" do
|
4
4
|
setup do
|
5
|
-
@cpool =
|
5
|
+
@cpool = ConnectionPool.new
|
6
6
|
end
|
7
7
|
|
8
8
|
specify "should have no available connections" do
|
@@ -21,7 +21,7 @@ end
|
|
21
21
|
context "A connection pool handling connections" do
|
22
22
|
setup do
|
23
23
|
@max_size = 2
|
24
|
-
@cpool =
|
24
|
+
@cpool = ConnectionPool.new(@max_size) {:got_connection}
|
25
25
|
end
|
26
26
|
|
27
27
|
specify "#hold should increment #created_count" do
|
@@ -73,7 +73,7 @@ end
|
|
73
73
|
|
74
74
|
context "ConnectionPool#hold" do
|
75
75
|
setup do
|
76
|
-
@pool =
|
76
|
+
@pool = ConnectionPool.new {DummyConnection.new}
|
77
77
|
end
|
78
78
|
|
79
79
|
specify "should pass the result of the connection maker proc to the supplied block" do
|
@@ -109,7 +109,7 @@ end
|
|
109
109
|
|
110
110
|
context "ConnectionPool#connection_proc" do
|
111
111
|
setup do
|
112
|
-
@pool =
|
112
|
+
@pool = ConnectionPool.new
|
113
113
|
end
|
114
114
|
|
115
115
|
specify "should be nil if no block is supplied to the pool" do
|
@@ -128,7 +128,7 @@ end
|
|
128
128
|
context "A connection pool with a max size of 1" do
|
129
129
|
setup do
|
130
130
|
@invoked_count = 0
|
131
|
-
@pool =
|
131
|
+
@pool = ConnectionPool.new(1) {@invoked_count += 1; 'herro'}
|
132
132
|
end
|
133
133
|
|
134
134
|
specify "should let only one thread access the connection at any time" do
|
@@ -204,7 +204,7 @@ end
|
|
204
204
|
context "A connection pool with a max size of 5" do
|
205
205
|
setup do
|
206
206
|
@invoked_count = 0
|
207
|
-
@pool =
|
207
|
+
@pool = ConnectionPool.new(5) {@invoked_count += 1}
|
208
208
|
end
|
209
209
|
|
210
210
|
specify "should let five threads simultaneously access separate connections" do
|
@@ -274,7 +274,7 @@ end
|
|
274
274
|
context "ConnectionPool#disconnect" do
|
275
275
|
setup do
|
276
276
|
@count = 0
|
277
|
-
@pool =
|
277
|
+
@pool = ConnectionPool.new(5) {{:id => @count += 1}}
|
278
278
|
end
|
279
279
|
|
280
280
|
specify "should invoke the given block for each available connection" do
|
@@ -338,7 +338,7 @@ end
|
|
338
338
|
|
339
339
|
context "SingleThreadedPool" do
|
340
340
|
setup do
|
341
|
-
@pool =
|
341
|
+
@pool = SingleThreadedPool.new {1234}
|
342
342
|
end
|
343
343
|
|
344
344
|
specify "should provide a #hold method" do
|