Skip to content

Commit 9ef36f0

Browse files
authored
Merge pull request #220 from samlown/thread-safe-db
Upgrading to Couchrest 2.0.1 and simplifying connection multithreading
2 parents 40fcf9d + b46ce31 commit 9ef36f0

File tree

10 files changed

+153
-37
lines changed

10 files changed

+153
-37
lines changed

couchrest_model.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ Gem::Specification.new do |s|
2222
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
2323
s.require_paths = ["lib"]
2424

25-
s.add_dependency("couchrest", "2.0.0")
25+
s.add_dependency("couchrest", "2.0.1")
2626
s.add_dependency("activemodel", ">= 4.0.2")
2727
s.add_dependency("tzinfo", ">= 0.3.22")
2828
s.add_dependency("hashdiff", "~> 0.3")
2929
s.add_development_dependency("rspec", "~> 3.5.0")
3030
s.add_development_dependency("rack-test", ">= 0.5.7")
31-
s.add_development_dependency("rake", ">= 0.8.0")
31+
s.add_development_dependency("rake", ">= 0.8.0", "< 11.0")
3232
s.add_development_dependency("test-unit")
3333
s.add_development_dependency("minitest", "> 4.1") #, "< 5.0") # For Kaminari and activesupport, pending removal
3434
s.add_development_dependency("kaminari", ">= 0.14.1", "< 0.16.0")

history.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
* Migration improvements and tests ([PR](https://github.com/couchrest/couchrest_model/pull/215) @ellneal)
99
* Add some guards to prevent view errors when using custom emit values ([PR](https://github.com/couchrest/couchrest_model/pull/214) @ellneal)
1010
* Making running the tests a bit easier using Docker ([PR](https://github.com/couchrest/couchrest_model/pull/213) @ellneal)
11+
* Upgraded to Couchrest 2.0.1 ([PR](https://github.com/couchrest/couchrest_model/pull/220) @samlown)
12+
* Removed :persistent and simplifying connection handling with aim to reduce number of connections and improve multithreading ([PR](https://github.com/couchrest/couchrest_model/pull/220) @samlown)
1113

1214
## 2.2.0.beta1 - 2016-08-20
1315

lib/couchrest/model/connection.rb

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,42 @@ def server
99

1010
module ClassMethods
1111

12-
# Overwrite the normal use_database method so that a database
12+
# Overwrite the CouchRest::Document.use_database method so that a database
1313
# name can be provided instead of a full connection.
14-
# The actual database will be validated when it is requested for use.
15-
# Note that this should not be used with proxied models!
14+
# We prepare the database immediatly, so ensure any connection details
15+
# are provided in advance.
16+
# Note that this will not work correctly with proxied models.
1617
def use_database(db)
17-
@_use_database = db
18+
@database = prepare_database(db)
1819
end
1920

2021
# Overwrite the default database method so that it always
2122
# provides something from the configuration.
2223
# It will try to inherit the database from an ancester
23-
# unless the use_database method has been used, in which
24-
# case a new connection will be started.
24+
# unless the use_database method has been used.
2525
def database
2626
@database ||= prepare_database(super)
2727
end
2828

2929
def server
30-
@server ||= CouchRest::Server.new(prepare_server_uri, :persistent => connection_configuration[:persistent])
30+
@server ||= ServerPool.instance[prepare_server_uri]
3131
end
3232

33-
def prepare_database(db = nil, override_use_database = false)
34-
db = @_use_database unless override_use_database || @_use_database.nil?
33+
def prepare_database(db = nil)
3534
if db.nil? || db.is_a?(String) || db.is_a?(Symbol)
36-
conf = connection_configuration
37-
db = [conf[:prefix], db.to_s, conf[:suffix]].reject{|s| s.to_s.empty?}.join(conf[:join])
38-
self.server.database!(db)
35+
self.server.database!(prepare_database_name(db))
3936
else
4037
db
4138
end
4239
end
4340

4441
protected
4542

43+
def prepare_database_name(base)
44+
conf = connection_configuration
45+
[conf[:prefix], base.to_s, conf[:suffix]].reject{|s| s.to_s.empty?}.join(conf[:join])
46+
end
47+
4648
def prepare_server_uri
4749
conf = connection_configuration
4850
userinfo = [conf[:username], conf[:password]].compact.join(':')
@@ -52,21 +54,14 @@ def prepare_server_uri
5254

5355
def connection_configuration
5456
@connection_configuration ||=
55-
self.connection.update(
57+
self.connection.merge(
5658
(load_connection_config_file[environment.to_sym] || {}).symbolize_keys
5759
)
5860
end
5961

6062
def load_connection_config_file
6163
file = connection_config_file
62-
connection_config_cache[file] ||=
63-
(File.exists?(file) ?
64-
YAML::load(ERB.new(IO.read(file)).result) :
65-
{ }).symbolize_keys
66-
end
67-
68-
def connection_config_cache
69-
Thread.current[:connection_config_cache] ||= {}
64+
ConnectionConfig.instance[file]
7065
end
7166

7267
end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module CouchRest
2+
module Model
3+
4+
# Thead safe caching of connection configuration files.
5+
class ConnectionConfig
6+
include Singleton
7+
8+
def initialize
9+
@config_files = {}
10+
@mutex = Mutex.new
11+
end
12+
13+
def [](file)
14+
@mutex.synchronize do
15+
@config_files[file] ||= load_config(file)
16+
end
17+
end
18+
19+
private
20+
21+
def load_config(file)
22+
if File.exists?(file)
23+
YAML::load(ERB.new(IO.read(file)).result).symbolize_keys
24+
else
25+
{ }
26+
end
27+
end
28+
29+
end
30+
31+
end
32+
end

lib/couchrest/model/proxyable.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ def proxy_database(assoc_name)
88
raise StandardError, "Please set the #proxy_database_method" if self.class.proxy_database_method.nil?
99
db_name = self.send(self.class.proxy_database_method)
1010
db_suffix = self.class.proxy_database_suffixes[assoc_name.to_sym]
11-
@proxy_databases ||= {}
12-
@proxy_databases[assoc_name.to_sym] ||= self.class.prepare_database([db_name, db_suffix].compact.reject(&:blank?).join(self.class.connection[:join]), true)
11+
@_proxy_databases ||= {}
12+
@_proxy_databases[assoc_name.to_sym] ||= begin
13+
self.class.prepare_database([db_name, db_suffix].compact.reject(&:blank?).join(self.class.connection[:join]))
14+
end
1315
end
1416

1517
module ClassMethods

lib/couchrest/model/server_pool.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module CouchRest
2+
module Model
3+
4+
# Simple Server Pool with thread safety so that a single server
5+
# instance can be shared with multiple classes.
6+
class ServerPool
7+
include Singleton
8+
9+
def initialize
10+
@servers = {}
11+
@mutex = Mutex.new
12+
end
13+
14+
def [](url)
15+
@mutex.synchronize do
16+
@servers[url] ||= CouchRest::Server.new(url)
17+
end
18+
end
19+
20+
end
21+
end
22+
end

lib/couchrest_model.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
require "couchrest/model"
2828
require "couchrest/model/errors"
29+
require "couchrest/model/server_pool"
30+
require "couchrest/model/connection_config"
2931
require "couchrest/model/configuration"
3032
require "couchrest/model/translation"
3133
require "couchrest/model/persistence"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
require "spec_helper"
3+
4+
describe CouchRest::Model::ConnectionConfig do
5+
6+
subject { CouchRest::Model::ConnectionConfig }
7+
8+
describe ".instance" do
9+
10+
it "should provide a singleton" do
11+
expect(subject.instance).to be_a(CouchRest::Model::ConnectionConfig)
12+
end
13+
14+
end
15+
16+
describe "#[file]" do
17+
18+
let :file do
19+
File.join(FIXTURE_PATH, "config", "couchdb.yml")
20+
end
21+
22+
it "should provide a config file hash" do
23+
conf = subject.instance[file]
24+
expect(conf).to be_a(Hash)
25+
end
26+
27+
it "should provide a config file hash with symbolized keys" do
28+
conf = subject.instance[file]
29+
expect(conf[:development]).to be_a(Hash)
30+
expect(conf[:development]['host']).to be_a(String)
31+
end
32+
33+
it "should always provide same hash" do
34+
f1 = subject.instance[file]
35+
f2 = subject.instance[file]
36+
expect(f1.object_id).to eql(f2.object_id)
37+
end
38+
39+
end
40+
41+
end

spec/unit/connection_spec.rb

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,6 @@
5858
@class.use_database(db)
5959
expect(@class.database).to eql(db)
6060
end
61-
it "should never prepare the database before it is needed" do
62-
db = @class.server.database('test')
63-
expect(@class).not_to receive(:prepare_database)
64-
@class.use_database('test')
65-
@class.use_database(db)
66-
end
6761
it "should use the database specified" do
6862
@class.use_database(:test)
6963
expect(@class.database.name).to eql('couchrest_test')
@@ -131,15 +125,10 @@
131125

132126
it "should use the .use_database value" do
133127
@class.use_database('testing')
134-
db = @class.prepare_database
128+
db = @class.database
135129
expect(db.name).to eql('couchrest_testing')
136130
end
137131

138-
it "should ignore the .use_database value when overrride" do
139-
@class.use_database('testing')
140-
db = @class.prepare_database('test', true)
141-
expect(db.name).to eql('couchrest_test')
142-
end
143132
end
144133

145134
describe "protected methods" do

spec/unit/server_pool_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
require "spec_helper"
3+
4+
describe CouchRest::Model::ServerPool do
5+
6+
subject { CouchRest::Model::ServerPool }
7+
8+
describe ".instance" do
9+
10+
it "should provide a singleton" do
11+
expect(subject.instance).to be_a(CouchRest::Model::ServerPool)
12+
end
13+
14+
end
15+
16+
describe "#[url]" do
17+
18+
it "should provide a server object" do
19+
srv = subject.instance[COUCHHOST]
20+
expect(srv).to be_a(CouchRest::Server)
21+
end
22+
23+
it "should always provide same object" do
24+
srv = subject.instance[COUCHHOST]
25+
srv2 = subject.instance[COUCHHOST]
26+
expect(srv.object_id).to eql(srv2.object_id)
27+
end
28+
29+
end
30+
31+
end

0 commit comments

Comments
 (0)