data_objects 0.9.9 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +1 -2
- data/Rakefile +19 -20
- data/lib/data_objects/connection.rb +26 -6
- data/lib/data_objects/quoting.rb +5 -0
- data/lib/data_objects/uri.rb +6 -2
- data/lib/data_objects/version.rb +1 -1
- data/lib/data_objects.rb +1 -1
- data/spec/connection_spec.rb +16 -2
- data/spec/uri_spec.rb +44 -0
- metadata +3 -4
- data/lib/data_objects/support/pooling.rb +0 -236
- data/spec/support/pooling_spec.rb +0 -374
data/Manifest.txt
CHANGED
@@ -13,7 +13,6 @@ lib/data_objects/logger.rb
|
|
13
13
|
lib/data_objects/quoting.rb
|
14
14
|
lib/data_objects/reader.rb
|
15
15
|
lib/data_objects/result.rb
|
16
|
-
lib/data_objects/support/pooling.rb
|
17
16
|
lib/data_objects/transaction.rb
|
18
17
|
lib/data_objects/uri.rb
|
19
18
|
lib/data_objects/version.rb
|
@@ -25,5 +24,5 @@ spec/reader_spec.rb
|
|
25
24
|
spec/result_spec.rb
|
26
25
|
spec/spec.opts
|
27
26
|
spec/spec_helper.rb
|
28
|
-
spec/support/pooling_spec.rb
|
29
27
|
spec/transaction_spec.rb
|
28
|
+
spec/uri_spec.rb
|
data/Rakefile
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'pathname'
|
2
|
+
require 'rubygems'
|
3
3
|
require 'spec/rake/spectask'
|
4
4
|
require 'lib/data_objects/version'
|
5
5
|
|
6
|
-
ROOT
|
6
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
7
|
+
JRUBY = RUBY_PLATFORM =~ /java/
|
8
|
+
WINDOWS = Gem.win_platform?
|
9
|
+
SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
|
7
10
|
|
8
11
|
AUTHOR = "Yehuda Katz"
|
9
12
|
EMAIL = "wycats@gmail.com"
|
@@ -17,25 +20,29 @@ PROJECT_NAME = "dorb"
|
|
17
20
|
PROJECT_URL = "http://rubyforge.org/projects/dorb"
|
18
21
|
PROJECT_DESCRIPTION = PROJECT_SUMMARY = "The Core DataObjects class"
|
19
22
|
|
20
|
-
|
23
|
+
JAVA_DRIVER = false
|
24
|
+
|
25
|
+
# RCov is run by default, except on the JRuby platform, or if NO_RCOV env is true
|
26
|
+
RUN_RCOV = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
|
21
27
|
|
22
28
|
if (tasks_dir = ROOT.parent + 'tasks').directory?
|
23
29
|
require tasks_dir + 'hoe'
|
24
30
|
end
|
25
31
|
|
26
|
-
|
32
|
+
def sudo_gem(cmd)
|
33
|
+
sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
|
34
|
+
end
|
27
35
|
|
28
|
-
|
29
|
-
SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
|
36
|
+
# Installation
|
30
37
|
|
31
|
-
desc "Install #{GEM_NAME} #{GEM_VERSION}
|
38
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION}"
|
32
39
|
task :install => [ :package ] do
|
33
|
-
|
40
|
+
sudo_gem "install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
|
34
41
|
end
|
35
42
|
|
36
|
-
desc "Uninstall #{GEM_NAME} #{GEM_VERSION}
|
43
|
+
desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
|
37
44
|
task :uninstall => [ :clobber ] do
|
38
|
-
|
45
|
+
sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x"
|
39
46
|
end
|
40
47
|
|
41
48
|
# Specs
|
@@ -46,7 +53,7 @@ Spec::Rake::SpecTask.new(:spec) do |t|
|
|
46
53
|
t.spec_files = Pathname.glob(ENV['FILES'] || 'spec/**/*_spec.rb')
|
47
54
|
|
48
55
|
begin
|
49
|
-
t.rcov =
|
56
|
+
t.rcov = RUN_RCOV
|
50
57
|
t.rcov_opts << '--exclude' << 'spec'
|
51
58
|
t.rcov_opts << '--text-summary'
|
52
59
|
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
@@ -55,14 +62,6 @@ Spec::Rake::SpecTask.new(:spec) do |t|
|
|
55
62
|
end
|
56
63
|
end
|
57
64
|
|
58
|
-
# JRuby
|
59
|
-
namespace :jruby do
|
60
|
-
desc "Install #{GEM_NAME} #{GEM_VERSION} with JRuby"
|
61
|
-
task :install => [ :package ] do
|
62
|
-
sh %{#{SUDO} jruby -S gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}, :verbose => false
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
65
|
namespace :ci do
|
67
66
|
|
68
67
|
task :prepare do
|
@@ -88,4 +87,4 @@ namespace :ci do
|
|
88
87
|
|
89
88
|
end
|
90
89
|
|
91
|
-
task :ci => ["ci:spec"]
|
90
|
+
task :ci => ["ci:spec"]
|
@@ -8,16 +8,36 @@ end
|
|
8
8
|
module DataObjects
|
9
9
|
class Connection
|
10
10
|
|
11
|
-
def self.new(
|
12
|
-
uri = DataObjects::URI::parse(
|
11
|
+
def self.new(uri_s)
|
12
|
+
uri = DataObjects::URI::parse(uri_s)
|
13
|
+
|
14
|
+
case uri.scheme.to_sym
|
15
|
+
when :java
|
16
|
+
warn 'JNDI URLs (connection strings) are only for use with JRuby' unless RUBY_PLATFORM =~ /java/
|
17
|
+
# TODO: handle jndi connection strings
|
18
|
+
when :jdbc
|
19
|
+
warn 'JDBC URLs (connection strings) are only for use with JRuby' unless RUBY_PLATFORM =~ /java/
|
20
|
+
|
21
|
+
driver_name = if uri.path.split(':').first == 'sqlite'
|
22
|
+
'sqlite3'
|
23
|
+
elsif uri.path.split(':').first == 'postgresql'
|
24
|
+
'postgres'
|
25
|
+
else
|
26
|
+
uri.path.split(':').first
|
27
|
+
end
|
13
28
|
|
14
|
-
|
15
|
-
|
29
|
+
conn_uri = uri_s # NOTE: for now, do not reformat this JDBC connection
|
30
|
+
# string -- or, in other words, do not let
|
31
|
+
# DataObjects::URI#to_s be called -- as it is not
|
32
|
+
# correctly handling JDBC URLs, and in doing so, causing
|
33
|
+
# java.sql.DriverManager.getConnection to throw a
|
34
|
+
# 'No suitable driver found for...' exception.
|
16
35
|
else
|
17
|
-
driver_name = uri.scheme
|
36
|
+
driver_name = uri.scheme
|
37
|
+
conn_uri = uri
|
18
38
|
end
|
19
39
|
|
20
|
-
DataObjects.const_get(driver_name.capitalize)::Connection.new(
|
40
|
+
DataObjects.const_get(driver_name.capitalize)::Connection.new(conn_uri)
|
21
41
|
end
|
22
42
|
|
23
43
|
def self.inherited(target)
|
data/lib/data_objects/quoting.rb
CHANGED
@@ -45,6 +45,7 @@ module DataObjects
|
|
45
45
|
when Array then quote_array(value)
|
46
46
|
when Range then quote_range(value)
|
47
47
|
when Symbol then quote_symbol(value)
|
48
|
+
when Regexp then quote_regexp(value)
|
48
49
|
else
|
49
50
|
if value.respond_to?(:to_sql)
|
50
51
|
value.to_sql
|
@@ -93,6 +94,10 @@ module DataObjects
|
|
93
94
|
def quote_range(value)
|
94
95
|
"#{quote_value(value.first)} AND #{quote_value(value.last)}"
|
95
96
|
end
|
97
|
+
|
98
|
+
def quote_regexp(value)
|
99
|
+
quote_string(value.source)
|
100
|
+
end
|
96
101
|
end
|
97
102
|
|
98
103
|
end
|
data/lib/data_objects/uri.rb
CHANGED
@@ -8,7 +8,7 @@ module DataObjects
|
|
8
8
|
def self.parse(uri)
|
9
9
|
return uri if uri.kind_of?(self)
|
10
10
|
uri = Addressable::URI::parse(uri) unless uri.kind_of?(Addressable::URI)
|
11
|
-
self.new(uri.scheme, uri.user, uri.password, uri.host, uri.port, uri.path, uri.
|
11
|
+
self.new(uri.scheme, uri.user, uri.password, uri.host, uri.port, uri.path, uri.query_values, uri.fragment)
|
12
12
|
end
|
13
13
|
|
14
14
|
def to_s
|
@@ -22,7 +22,11 @@ module DataObjects
|
|
22
22
|
string << "#{host}" if host
|
23
23
|
string << ":#{port}" if port
|
24
24
|
string << path.to_s
|
25
|
-
|
25
|
+
if query
|
26
|
+
string << "?" << query.map do |key, value|
|
27
|
+
"#{key}=#{value}"
|
28
|
+
end.join("&")
|
29
|
+
end
|
26
30
|
string << "##{fragment}" if fragment
|
27
31
|
string
|
28
32
|
end
|
data/lib/data_objects/version.rb
CHANGED
data/lib/data_objects.rb
CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
|
|
3
3
|
gem 'extlib', '~>0.9.8'
|
4
4
|
require 'extlib'
|
5
5
|
|
6
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'data_objects', '
|
6
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'data_objects', 'version'))
|
7
7
|
require File.expand_path(File.join(File.dirname(__FILE__), 'data_objects', 'logger'))
|
8
8
|
require File.expand_path(File.join(File.dirname(__FILE__), 'data_objects', 'connection'))
|
9
9
|
require File.expand_path(File.join(File.dirname(__FILE__), 'data_objects', 'uri'))
|
data/spec/connection_spec.rb
CHANGED
@@ -40,8 +40,22 @@ describe DataObjects::Connection do
|
|
40
40
|
c = DataObjects::Connection.new(Addressable::URI.parse('mock://localhost/database'))
|
41
41
|
c.should be_kind_of(DataObjects::Mock::Connection)
|
42
42
|
|
43
|
-
c = DataObjects::Connection.new(Addressable::URI.parse('
|
44
|
-
c.should be_kind_of(DataObjects::Mock::Connection)
|
43
|
+
c = DataObjects::Connection.new(Addressable::URI.parse('mock:jndi://jdbc/database'))
|
44
|
+
#c.should be_kind_of(DataObjects::Mock::Connection)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should return the Connection using username" do
|
48
|
+
c = DataObjects::Connection.new(Addressable::URI.parse('mock://root@localhost/database'))
|
49
|
+
c.instance_variable_get(:@uri).user.should == 'root'
|
50
|
+
c.instance_variable_get(:@uri).password.should be_nil
|
51
|
+
|
52
|
+
c = DataObjects::Connection.new(Addressable::URI.parse('mock://root:@localhost/database'))
|
53
|
+
c.instance_variable_get(:@uri).user.should == 'root'
|
54
|
+
c.instance_variable_get(:@uri).password.should == ''
|
55
|
+
|
56
|
+
c = DataObjects::Connection.new(Addressable::URI.parse('mock://root:pwd@localhost/database'))
|
57
|
+
c.instance_variable_get(:@uri).user.should == 'root'
|
58
|
+
c.instance_variable_get(:@uri).password.should == 'pwd'
|
45
59
|
end
|
46
60
|
end
|
47
61
|
end
|
data/spec/uri_spec.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::URI do
|
4
|
+
before do
|
5
|
+
@uri = DataObjects::URI.parse('mock://username:password@localhost:12345/path?encoding=utf8#fragment')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should parse the scheme part" do
|
9
|
+
@uri.scheme.should == "mock"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should parse the user part" do
|
13
|
+
@uri.user.should == "username"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should parse the password part" do
|
17
|
+
@uri.password.should == "password"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should parse the host part" do
|
21
|
+
@uri.host.should == "localhost"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should parse the port part" do
|
25
|
+
@uri.port.should == 12345
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should parse the path part" do
|
29
|
+
@uri.path.should == "/path"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should parse the query part" do
|
33
|
+
@uri.query.should == { "encoding" => "utf8" }
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should parse the fragment part" do
|
37
|
+
@uri.fragment.should == "fragment"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should provide a correct string representation" do
|
41
|
+
@uri.to_s.should == 'mock://username:password@localhost:12345/path?encoding=utf8#fragment'
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: data_objects
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yehuda Katz
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-01-04 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -69,7 +69,6 @@ files:
|
|
69
69
|
- lib/data_objects/quoting.rb
|
70
70
|
- lib/data_objects/reader.rb
|
71
71
|
- lib/data_objects/result.rb
|
72
|
-
- lib/data_objects/support/pooling.rb
|
73
72
|
- lib/data_objects/transaction.rb
|
74
73
|
- lib/data_objects/uri.rb
|
75
74
|
- lib/data_objects/version.rb
|
@@ -81,8 +80,8 @@ files:
|
|
81
80
|
- spec/result_spec.rb
|
82
81
|
- spec/spec.opts
|
83
82
|
- spec/spec_helper.rb
|
84
|
-
- spec/support/pooling_spec.rb
|
85
83
|
- spec/transaction_spec.rb
|
84
|
+
- spec/uri_spec.rb
|
86
85
|
has_rdoc: true
|
87
86
|
homepage: http://rubyforge.org/projects/dorb
|
88
87
|
post_install_message:
|
@@ -1,236 +0,0 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
|
-
class Object
|
4
|
-
# ==== Notes
|
5
|
-
# Provides pooling support to class it got included in.
|
6
|
-
#
|
7
|
-
# Pooling of objects is a faster way of aquiring instances
|
8
|
-
# of objects compared to regular allocation and initialization
|
9
|
-
# because instances are keeped in memory reused.
|
10
|
-
#
|
11
|
-
# Classes that include Pooling module have re-defined new
|
12
|
-
# method that returns instances acquired from pool.
|
13
|
-
#
|
14
|
-
# Term resource is used for any type of poolable objects
|
15
|
-
# and should NOT be thought as DataMapper Resource or
|
16
|
-
# ActiveResource resource and such.
|
17
|
-
#
|
18
|
-
# In Data Objects connections are pooled so that it is
|
19
|
-
# unnecessary to allocate and initialize connection object
|
20
|
-
# each time connection is needed, like per request in a
|
21
|
-
# web application.
|
22
|
-
#
|
23
|
-
# Pool obviously has to be thread safe because state of
|
24
|
-
# object is reset when it is released.
|
25
|
-
module Pooling
|
26
|
-
def self.included(base)
|
27
|
-
base.send(:extend, ClassMethods)
|
28
|
-
end
|
29
|
-
|
30
|
-
module ClassMethods
|
31
|
-
# ==== Notes
|
32
|
-
# Initializes the pool and returns it.
|
33
|
-
#
|
34
|
-
# ==== Parameters
|
35
|
-
# size_limit<Fixnum>:: maximum size of the pool.
|
36
|
-
#
|
37
|
-
# ==== Returns
|
38
|
-
# <ResourcePool>:: initialized pool
|
39
|
-
def initialize_pool(size_limit, options = {})
|
40
|
-
@__pool.flush! if @__pool
|
41
|
-
|
42
|
-
@__pool = ResourcePool.new(size_limit, self, options)
|
43
|
-
end
|
44
|
-
|
45
|
-
# ==== Notes
|
46
|
-
# Instances of poolable resource are acquired from
|
47
|
-
# pool. This quires a new instance from pool and
|
48
|
-
# returns it.
|
49
|
-
#
|
50
|
-
# ==== Returns
|
51
|
-
# Resource instance acquired from the pool.
|
52
|
-
#
|
53
|
-
# ==== Raises
|
54
|
-
# ArgumentError:: when pool is exhausted and no instance
|
55
|
-
# can be acquired.
|
56
|
-
def new
|
57
|
-
pool.acquire
|
58
|
-
end
|
59
|
-
|
60
|
-
# ==== Notes
|
61
|
-
# Returns pool for this resource class.
|
62
|
-
# Initialization is done when necessary.
|
63
|
-
# Default size limit of the pool is 10.
|
64
|
-
#
|
65
|
-
# ==== Returns
|
66
|
-
# <Object::Pooling::ResourcePool>:: pool for this resource class.
|
67
|
-
def pool
|
68
|
-
@__pool ||= ResourcePool.new(10, self)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# ==== Notes
|
73
|
-
# Pool
|
74
|
-
#
|
75
|
-
class ResourcePool
|
76
|
-
attr_reader :size_limit, :class_of_resources, :expiration_period
|
77
|
-
|
78
|
-
# ==== Notes
|
79
|
-
# Initializes resource pool.
|
80
|
-
#
|
81
|
-
# ==== Parameters
|
82
|
-
# size_limit<Fixnum>:: maximum number of resources in the pool.
|
83
|
-
# class_of_resources<Class>:: class of resource.
|
84
|
-
#
|
85
|
-
# ==== Raises
|
86
|
-
# ArgumentError:: when class of resource does not implement
|
87
|
-
# dispose instance method or is not a Class.
|
88
|
-
def initialize(size_limit, class_of_resources, options)
|
89
|
-
raise ArgumentError.new("Expected class of resources to be instance of Class, got: #{class_of_resources.class}") unless class_of_resources.is_a?(Class)
|
90
|
-
raise ArgumentError.new("Class #{class_of_resources} must implement dispose instance method to be poolable.") unless class_of_resources.instance_methods.include?("dispose")
|
91
|
-
|
92
|
-
@size_limit = size_limit
|
93
|
-
@class_of_resources = class_of_resources
|
94
|
-
|
95
|
-
@reserved = Set.new
|
96
|
-
@available = []
|
97
|
-
@lock = Mutex.new
|
98
|
-
|
99
|
-
initialization_args = options.delete(:initialization_args) || []
|
100
|
-
|
101
|
-
@expiration_period = options.delete(:expiration_period) || 60
|
102
|
-
@initialization_args = [*initialization_args]
|
103
|
-
|
104
|
-
@pool_expiration_thread = Thread.new do
|
105
|
-
while true
|
106
|
-
release_outdated
|
107
|
-
|
108
|
-
sleep (@expiration_period + 1)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# ==== Notes
|
114
|
-
# Current size of pool: number of already reserved
|
115
|
-
# resources.
|
116
|
-
def size
|
117
|
-
@reserved.size
|
118
|
-
end
|
119
|
-
|
120
|
-
# ==== Notes
|
121
|
-
# Indicates if pool has resources to acquire.
|
122
|
-
#
|
123
|
-
# ==== Returns
|
124
|
-
# <Boolean>:: true if pool has resources can be acquired,
|
125
|
-
# false otherwise.
|
126
|
-
def available?
|
127
|
-
@reserved.size < size_limit
|
128
|
-
end
|
129
|
-
|
130
|
-
# ==== Notes
|
131
|
-
# Acquires last used available resource and returns it.
|
132
|
-
# If no resources available, current implementation
|
133
|
-
# throws an exception.
|
134
|
-
def acquire
|
135
|
-
@lock.synchronize do
|
136
|
-
if available?
|
137
|
-
instance = prepair_available_resource
|
138
|
-
@reserved << instance
|
139
|
-
|
140
|
-
instance
|
141
|
-
else
|
142
|
-
raise RuntimeError
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# ==== Notes
|
148
|
-
# Releases previously acquired instance.
|
149
|
-
#
|
150
|
-
# ==== Parameters
|
151
|
-
# instance <Anything>:: previosly acquired instance.
|
152
|
-
#
|
153
|
-
# ==== Raises
|
154
|
-
# RuntimeError:: when given not pooled instance.
|
155
|
-
def release(instance)
|
156
|
-
@lock.synchronize do
|
157
|
-
if @reserved.include?(instance)
|
158
|
-
@reserved.delete(instance)
|
159
|
-
instance.dispose
|
160
|
-
@available << instance
|
161
|
-
else
|
162
|
-
raise RuntimeError
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# ==== Notes
|
168
|
-
# Releases all objects in the pool.
|
169
|
-
#
|
170
|
-
# ==== Returns
|
171
|
-
# nil
|
172
|
-
def flush!
|
173
|
-
@reserved.each do |instance|
|
174
|
-
self.release(instance)
|
175
|
-
end
|
176
|
-
|
177
|
-
nil
|
178
|
-
end
|
179
|
-
|
180
|
-
# ==== Notes
|
181
|
-
# Check if instance has been acquired from the pool.
|
182
|
-
#
|
183
|
-
# ==== Returns
|
184
|
-
# <Boolean>:: true if given resource instance has been acquired from pool,
|
185
|
-
# false otherwise.
|
186
|
-
def acquired?(instance)
|
187
|
-
@reserved.include?(instance)
|
188
|
-
end
|
189
|
-
|
190
|
-
# ==== Notes
|
191
|
-
# Releases instances that haven't been in use and
|
192
|
-
# hit the expiration period.
|
193
|
-
#
|
194
|
-
# ==== Returns
|
195
|
-
# nil
|
196
|
-
def release_outdated
|
197
|
-
@reserved.each do |instance|
|
198
|
-
release(instance) if time_to_release?(instance)
|
199
|
-
end
|
200
|
-
|
201
|
-
nil
|
202
|
-
end
|
203
|
-
|
204
|
-
# ==== Notes
|
205
|
-
# Checks if pooled resource instance is outdated and
|
206
|
-
# should be released.
|
207
|
-
#
|
208
|
-
# ==== Returns
|
209
|
-
# <Boolean>:: true if instance should be released, false otherwise.
|
210
|
-
def time_to_release?(instance)
|
211
|
-
(Time.now - instance.instance_variable_get("@__pool_acquire_timestamp")) > @expiration_period
|
212
|
-
end
|
213
|
-
|
214
|
-
protected
|
215
|
-
|
216
|
-
# ==== Notes
|
217
|
-
# Either allocates new resource,
|
218
|
-
# or takes last used available resource from
|
219
|
-
# the pool.
|
220
|
-
def prepair_available_resource
|
221
|
-
if @available.size > 0
|
222
|
-
res = @available.pop
|
223
|
-
res.instance_variable_set("@__pool_acquire_timestamp", Time.now)
|
224
|
-
|
225
|
-
res
|
226
|
-
else
|
227
|
-
res = @class_of_resources.allocate
|
228
|
-
res.send(:initialize, *@initialization_args)
|
229
|
-
res.instance_variable_set("@__pool_acquire_timestamp", Time.now)
|
230
|
-
|
231
|
-
res
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end # ResourcePool
|
235
|
-
end
|
236
|
-
end
|
@@ -1,374 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
-
require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'data_objects', 'support', 'pooling')
|
3
|
-
require 'timeout'
|
4
|
-
|
5
|
-
# This implements dispose
|
6
|
-
# and works perfectly with
|
7
|
-
# pooling.
|
8
|
-
class DisposableResource
|
9
|
-
include Object::Pooling
|
10
|
-
attr_reader :name
|
11
|
-
|
12
|
-
def initialize(name = "")
|
13
|
-
@name = name
|
14
|
-
end
|
15
|
-
|
16
|
-
def dispose
|
17
|
-
@name = nil
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# This baby causes exceptions
|
22
|
-
# to be raised when you use
|
23
|
-
# it with pooling.
|
24
|
-
class UndisposableResource
|
25
|
-
end
|
26
|
-
|
27
|
-
describe Object::Pooling::ResourcePool do
|
28
|
-
before :each do
|
29
|
-
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, :expiration_period => 50)
|
30
|
-
end
|
31
|
-
|
32
|
-
it "responds to flush!" do
|
33
|
-
@pool.should respond_to(:flush!)
|
34
|
-
end
|
35
|
-
|
36
|
-
it "responds to acquire" do
|
37
|
-
@pool.should respond_to(:acquire)
|
38
|
-
end
|
39
|
-
|
40
|
-
it "responds to release" do
|
41
|
-
@pool.should respond_to(:release)
|
42
|
-
end
|
43
|
-
|
44
|
-
it "responds to :available?" do
|
45
|
-
@pool.should respond_to(:available?)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "has a size limit" do
|
49
|
-
@pool.size_limit.should == 7
|
50
|
-
end
|
51
|
-
|
52
|
-
it "has initial size of zero" do
|
53
|
-
@pool.size.should == 0
|
54
|
-
end
|
55
|
-
|
56
|
-
it "has a set of reserved resources" do
|
57
|
-
@pool.instance_variable_get("@reserved").should be_empty
|
58
|
-
end
|
59
|
-
|
60
|
-
it "has a set of available resources" do
|
61
|
-
@pool.instance_variable_get("@available").should be_empty
|
62
|
-
end
|
63
|
-
|
64
|
-
it "knows class of resources (objects) it works with" do
|
65
|
-
@pool.class_of_resources.should == DisposableResource
|
66
|
-
end
|
67
|
-
|
68
|
-
it "raises exception when given anything but class for resources class" do
|
69
|
-
lambda {
|
70
|
-
@pool = Object::Pooling::ResourcePool.new(7, "Hooray!", {})
|
71
|
-
}.should raise_error(ArgumentError, /class/)
|
72
|
-
end
|
73
|
-
|
74
|
-
it "requires class of resources (objects) it works with to have a dispose instance method" do
|
75
|
-
lambda {
|
76
|
-
@pool = Object::Pooling::ResourcePool.new(3, UndisposableResource, {})
|
77
|
-
}.should raise_error(ArgumentError, /dispose/)
|
78
|
-
end
|
79
|
-
|
80
|
-
it "may take initialization arguments" do
|
81
|
-
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, { :initialization_args => ["paper"] })
|
82
|
-
@pool.instance_variable_get("@initialization_args").should == ["paper"]
|
83
|
-
end
|
84
|
-
|
85
|
-
it "may take expiration period option" do
|
86
|
-
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, { :expiration_period => 100 })
|
87
|
-
@pool.expiration_period.should == 100
|
88
|
-
end
|
89
|
-
|
90
|
-
it "has default expiration period of one minute" do
|
91
|
-
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, {})
|
92
|
-
@pool.expiration_period.should == 60
|
93
|
-
end
|
94
|
-
|
95
|
-
it "spawns a thread to dispose objects haven't been used for a while" do
|
96
|
-
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, {})
|
97
|
-
@pool.instance_variable_get("@pool_expiration_thread").should be_an_instance_of(Thread)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
describe "Acquire from constant size pool" do
|
104
|
-
before :each do
|
105
|
-
DisposableResource.initialize_pool(2)
|
106
|
-
end
|
107
|
-
|
108
|
-
after :each do
|
109
|
-
DisposableResource.instance_variable_set("@__pool", nil)
|
110
|
-
end
|
111
|
-
|
112
|
-
it "increased size of the pool" do
|
113
|
-
@time = DisposableResource.pool.acquire
|
114
|
-
DisposableResource.pool.size.should == 1
|
115
|
-
end
|
116
|
-
|
117
|
-
it "places initialized instance in the reserved set" do
|
118
|
-
@time = DisposableResource.pool.acquire
|
119
|
-
DisposableResource.pool.instance_variable_get("@reserved").size.should == 1
|
120
|
-
end
|
121
|
-
|
122
|
-
it "raises an exception when pool size limit is hit" do
|
123
|
-
@t1 = DisposableResource.pool.acquire
|
124
|
-
@t2 = DisposableResource.pool.acquire
|
125
|
-
|
126
|
-
lambda { DisposableResource.pool.acquire }.should raise_error(RuntimeError)
|
127
|
-
end
|
128
|
-
|
129
|
-
it "returns last released resource" do
|
130
|
-
@t1 = DisposableResource.pool.acquire
|
131
|
-
@t2 = DisposableResource.pool.acquire
|
132
|
-
DisposableResource.pool.release(@t1)
|
133
|
-
|
134
|
-
DisposableResource.pool.acquire.should == @t1
|
135
|
-
end
|
136
|
-
|
137
|
-
it "really truly returns last released resource" do
|
138
|
-
@t1 = DisposableResource.pool.acquire
|
139
|
-
DisposableResource.pool.release(@t1)
|
140
|
-
|
141
|
-
@t2 = DisposableResource.pool.acquire
|
142
|
-
DisposableResource.pool.release(@t2)
|
143
|
-
|
144
|
-
@t3 = DisposableResource.pool.acquire
|
145
|
-
DisposableResource.pool.release(@t3)
|
146
|
-
|
147
|
-
DisposableResource.pool.acquire.should == @t1
|
148
|
-
@t1.should == @t3
|
149
|
-
end
|
150
|
-
|
151
|
-
it "sets allocation timestamp on resource instance" do
|
152
|
-
@t1 = DisposableResource.new
|
153
|
-
@t1.instance_variable_get("@__pool_acquire_timestamp").should be_close(Time.now, 2)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
describe "Releasing from constant size pool" do
|
160
|
-
before :each do
|
161
|
-
DisposableResource.initialize_pool(2)
|
162
|
-
end
|
163
|
-
|
164
|
-
after :each do
|
165
|
-
DisposableResource.instance_variable_set("@__pool", nil)
|
166
|
-
end
|
167
|
-
|
168
|
-
it "decreases size of the pool" do
|
169
|
-
@t1 = DisposableResource.pool.acquire
|
170
|
-
@t2 = DisposableResource.pool.acquire
|
171
|
-
DisposableResource.pool.release(@t1)
|
172
|
-
|
173
|
-
DisposableResource.pool.size.should == 1
|
174
|
-
end
|
175
|
-
|
176
|
-
it "raises an exception on attempt to releases object not in pool" do
|
177
|
-
@t1 = DisposableResource.new
|
178
|
-
@t2 = Set.new
|
179
|
-
|
180
|
-
DisposableResource.pool.release(@t1)
|
181
|
-
lambda { DisposableResource.pool.release(@t2) }.should raise_error(RuntimeError)
|
182
|
-
end
|
183
|
-
|
184
|
-
it "disposes released object" do
|
185
|
-
@t1 = DisposableResource.pool.acquire
|
186
|
-
|
187
|
-
@t1.should_receive(:dispose)
|
188
|
-
DisposableResource.pool.release(@t1)
|
189
|
-
end
|
190
|
-
|
191
|
-
it "removes released object from reserved set" do
|
192
|
-
@t1 = DisposableResource.pool.acquire
|
193
|
-
|
194
|
-
lambda {
|
195
|
-
DisposableResource.pool.release(@t1)
|
196
|
-
}.should change(DisposableResource.pool.instance_variable_get("@reserved"), :size).by(-1)
|
197
|
-
end
|
198
|
-
|
199
|
-
it "returns released object back to available set" do
|
200
|
-
@t1 = DisposableResource.pool.acquire
|
201
|
-
|
202
|
-
lambda {
|
203
|
-
DisposableResource.pool.release(@t1)
|
204
|
-
}.should change(DisposableResource.pool.instance_variable_get("@available"), :size).by(1)
|
205
|
-
end
|
206
|
-
|
207
|
-
it "updates acquire timestamp on already allocated resource instance" do
|
208
|
-
# acquire it once
|
209
|
-
@t1 = DisposableResource.new
|
210
|
-
# wait a bit
|
211
|
-
sleep 3
|
212
|
-
|
213
|
-
# check old timestamp
|
214
|
-
@t1.instance_variable_get("@__pool_acquire_timestamp").should be_close(Time.now, 4)
|
215
|
-
|
216
|
-
# re-acquire
|
217
|
-
DisposableResource.pool.release(@t1)
|
218
|
-
@t1 = DisposableResource.new
|
219
|
-
# see timestamp is updated
|
220
|
-
@t1.instance_variable_get("@__pool_acquire_timestamp").should be_close(Time.now, 2)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
describe Object::Pooling::ResourcePool, "#available?" do
|
227
|
-
before :each do
|
228
|
-
DisposableResource.initialize_pool(2)
|
229
|
-
DisposableResource.new
|
230
|
-
end
|
231
|
-
|
232
|
-
after :each do
|
233
|
-
DisposableResource.instance_variable_set("@__pool", nil)
|
234
|
-
end
|
235
|
-
|
236
|
-
it "returns true when pool has available instances" do
|
237
|
-
DisposableResource.pool.should be_available
|
238
|
-
end
|
239
|
-
|
240
|
-
it "returns false when pool is exhausted" do
|
241
|
-
# acquires the last available resource
|
242
|
-
DisposableResource.new
|
243
|
-
DisposableResource.pool.should_not be_available
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
describe "Flushing of constant size pool" do
|
250
|
-
before :each do
|
251
|
-
DisposableResource.initialize_pool(2)
|
252
|
-
|
253
|
-
@t1 = DisposableResource.new
|
254
|
-
@t2 = DisposableResource.new
|
255
|
-
|
256
|
-
# sanity check
|
257
|
-
DisposableResource.pool.instance_variable_get("@reserved").should_not be_empty
|
258
|
-
end
|
259
|
-
|
260
|
-
after :each do
|
261
|
-
DisposableResource.instance_variable_set("@__pool", nil)
|
262
|
-
end
|
263
|
-
|
264
|
-
it "disposes all pooled objects" do
|
265
|
-
[@t1, @t2].each { |instance| instance.should_receive(:dispose) }
|
266
|
-
|
267
|
-
DisposableResource.pool.flush!
|
268
|
-
end
|
269
|
-
|
270
|
-
it "empties reserved set" do
|
271
|
-
DisposableResource.pool.flush!
|
272
|
-
|
273
|
-
DisposableResource.pool.instance_variable_get("@reserved").should be_empty
|
274
|
-
end
|
275
|
-
|
276
|
-
it "returns all instances to available set" do
|
277
|
-
DisposableResource.pool.flush!
|
278
|
-
|
279
|
-
DisposableResource.pool.instance_variable_get("@available").size.should == 2
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
describe "Poolable resource class" do
|
286
|
-
before :each do
|
287
|
-
DisposableResource.initialize_pool(3, :initialization_args => ["paper"])
|
288
|
-
end
|
289
|
-
|
290
|
-
after :each do
|
291
|
-
DisposableResource.instance_variable_set("@__pool", nil)
|
292
|
-
end
|
293
|
-
|
294
|
-
it "acquires new instances from pool" do
|
295
|
-
@instance_one = DisposableResource.new
|
296
|
-
|
297
|
-
DisposableResource.pool.acquired?(@instance_one).should be(true)
|
298
|
-
end
|
299
|
-
|
300
|
-
it "flushed existing pool on re-initialization" do
|
301
|
-
DisposableResource.pool.should_receive(:flush!)
|
302
|
-
DisposableResource.initialize_pool(5)
|
303
|
-
end
|
304
|
-
|
305
|
-
it "replaces pool on re-initialization" do
|
306
|
-
DisposableResource.initialize_pool(5)
|
307
|
-
DisposableResource.pool.size_limit.should == 5
|
308
|
-
end
|
309
|
-
|
310
|
-
it "passes initialization parameters to newly created resource instances" do
|
311
|
-
DisposableResource.new.name.should == "paper"
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
describe "Pooled object", "on initialization" do
|
318
|
-
after :each do
|
319
|
-
DisposableResource.instance_variable_set("@__pool", nil)
|
320
|
-
end
|
321
|
-
|
322
|
-
it "does not flush pool" do
|
323
|
-
# using pool here initializes the pool first
|
324
|
-
# so we use instance variable directly
|
325
|
-
DisposableResource.instance_variable_get("@__pool").should_not_receive(:flush!)
|
326
|
-
DisposableResource.initialize_pool(23)
|
327
|
-
end
|
328
|
-
|
329
|
-
it "flushes pool first when re-initialized" do
|
330
|
-
DisposableResource.initialize_pool(5)
|
331
|
-
DisposableResource.pool.should_receive(:flush!)
|
332
|
-
DisposableResource.initialize_pool(23)
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
describe Object::Pooling::ResourcePool, "#time_to_dispose?" do
|
339
|
-
before :each do
|
340
|
-
DisposableResource.initialize_pool(7, :expiration_period => 2)
|
341
|
-
end
|
342
|
-
|
343
|
-
after :each do
|
344
|
-
DisposableResource.instance_variable_set("@__pool", nil)
|
345
|
-
end
|
346
|
-
|
347
|
-
it "returns true when object's last acquisition time is greater than limit" do
|
348
|
-
@t1 = DisposableResource.new
|
349
|
-
DisposableResource.pool.time_to_release?(@t1).should be(false)
|
350
|
-
|
351
|
-
sleep 3
|
352
|
-
DisposableResource.pool.time_to_release?(@t1).should be(true)
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
describe Object::Pooling::ResourcePool, "#dispose_outdated" do
|
359
|
-
before :each do
|
360
|
-
DisposableResource.initialize_pool(7, :expiration_period => 2)
|
361
|
-
end
|
362
|
-
|
363
|
-
after :each do
|
364
|
-
DisposableResource.instance_variable_set("@__pool", nil)
|
365
|
-
end
|
366
|
-
|
367
|
-
it "releases and thus disposes outdated instances" do
|
368
|
-
@t1 = DisposableResource.new
|
369
|
-
DisposableResource.pool.should_receive(:time_to_release?).with(@t1).and_return(true)
|
370
|
-
DisposableResource.pool.should_receive(:release).with(@t1)
|
371
|
-
|
372
|
-
DisposableResource.pool.release_outdated
|
373
|
-
end
|
374
|
-
end
|