data_objects 0.9.9 → 0.9.10
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/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
|