monga 0.0.1
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/.gitignore +17 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +110 -0
- data/Rakefile +13 -0
- data/lib/monga/client.rb +8 -0
- data/lib/monga/clients/client.rb +24 -0
- data/lib/monga/clients/master_slave_client.rb +5 -0
- data/lib/monga/clients/replica_set_client.rb +101 -0
- data/lib/monga/collection.rb +108 -0
- data/lib/monga/connection.rb +23 -0
- data/lib/monga/connection_pool.rb +38 -0
- data/lib/monga/connections/em_connection.rb +152 -0
- data/lib/monga/connections/primary.rb +46 -0
- data/lib/monga/connections/secondary.rb +13 -0
- data/lib/monga/cursor.rb +175 -0
- data/lib/monga/database.rb +97 -0
- data/lib/monga/exceptions.rb +9 -0
- data/lib/monga/miner.rb +72 -0
- data/lib/monga/request.rb +122 -0
- data/lib/monga/requests/delete.rb +23 -0
- data/lib/monga/requests/get_more.rb +19 -0
- data/lib/monga/requests/insert.rb +29 -0
- data/lib/monga/requests/kill_cursors.rb +25 -0
- data/lib/monga/requests/query.rb +49 -0
- data/lib/monga/requests/update.rb +25 -0
- data/lib/monga/response.rb +11 -0
- data/lib/monga.rb +30 -0
- data/monga.gemspec +26 -0
- data/spec/helpers/mongodb.rb +59 -0
- data/spec/helpers/truncate.rb +15 -0
- data/spec/monga/collection_spec.rb +448 -0
- data/spec/monga/connection_pool_spec.rb +50 -0
- data/spec/monga/connection_spec.rb +64 -0
- data/spec/monga/cursor_spec.rb +186 -0
- data/spec/monga/database_spec.rb +67 -0
- data/spec/monga/replica_set_client_spec.rb +46 -0
- data/spec/monga/requests/delete_spec.rb +0 -0
- data/spec/monga/requests/insert_spec.rb +0 -0
- data/spec/monga/requests/query_spec.rb +28 -0
- data/spec/spec_helper.rb +29 -0
- metadata +185 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Petr Yanovich
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Monga
|
2
|
+
|
3
|
+
[MongoDB](http://www.mongodb.org/) Ruby Client on [EventMachine](https://github.com/eventmachine/eventmachine).
|
4
|
+
|
5
|
+
This client is under development. You can try [em-mongo](https://github.com/bcg/em-mongo).
|
6
|
+
|
7
|
+
Client supports MongoDB 2.4. Some features won't work in lower versions.
|
8
|
+
|
9
|
+
## To Do List
|
10
|
+
|
11
|
+
* [ ] Write a Wiki
|
12
|
+
* [ ] Write comments
|
13
|
+
* [ ] Grammar improvement ;)
|
14
|
+
|
15
|
+
### Clients
|
16
|
+
* [x] Client (Single instance connection)
|
17
|
+
* [IN PROCESS] ReplicaSetClient
|
18
|
+
* [ ] MasterSlaveClient
|
19
|
+
* [ ] ReadPref
|
20
|
+
* [ ] Sharding Support
|
21
|
+
|
22
|
+
### Connection
|
23
|
+
* [x] Connection
|
24
|
+
* [x] Autoreconnect
|
25
|
+
* [x] Connection Pool
|
26
|
+
|
27
|
+
### Protocol
|
28
|
+
* [x] OP_QUERY
|
29
|
+
* [x] OP_GET_MORE
|
30
|
+
* [x] OP_KILL_CURSORS
|
31
|
+
* [x] OP_INSERT
|
32
|
+
* [x] OP_UPDATE
|
33
|
+
* [x] OP_DELETE
|
34
|
+
* [x] OP_REPLY
|
35
|
+
|
36
|
+
### Database
|
37
|
+
* [x] create_collection
|
38
|
+
* [x] drop_collection
|
39
|
+
* [x] get_last_error
|
40
|
+
* [x] drop_indexes
|
41
|
+
* [x] get_indexes
|
42
|
+
* Authentication
|
43
|
+
* [ ] login
|
44
|
+
* [ ] logout
|
45
|
+
* [ ] add_user
|
46
|
+
* [ ] check maxBsonSize / validate
|
47
|
+
* [x] cmd
|
48
|
+
* [x] eval
|
49
|
+
* [ ] where
|
50
|
+
|
51
|
+
### Collection
|
52
|
+
* QUERY_OP
|
53
|
+
* [x] find
|
54
|
+
* [x] find_one (first)
|
55
|
+
* [ ] sorting
|
56
|
+
* INSERT_OP
|
57
|
+
* [x] insert (single)
|
58
|
+
* [x] insert (batch)
|
59
|
+
* [x] safe_insert
|
60
|
+
* FLAGS
|
61
|
+
* [x] continue_on_error
|
62
|
+
* UPDATE_OP
|
63
|
+
* [x] update
|
64
|
+
* [x] safe_update
|
65
|
+
* FLAGS
|
66
|
+
* [x] upsert
|
67
|
+
* [x] multi_update
|
68
|
+
* DELETE_OP
|
69
|
+
* [x] delete
|
70
|
+
* [x] safe_delete
|
71
|
+
* FLAGS
|
72
|
+
* [x] single_remove
|
73
|
+
* INDEXES
|
74
|
+
* [x] ensure_index
|
75
|
+
* [x] drop_index
|
76
|
+
* [x] drop_indexes
|
77
|
+
* [x] get_indexes
|
78
|
+
* [x] count
|
79
|
+
* [x] all
|
80
|
+
* [x] cursor
|
81
|
+
* [ ] DBRef
|
82
|
+
|
83
|
+
### Cursor
|
84
|
+
* [x] limit
|
85
|
+
* [x] skip
|
86
|
+
* [x] batch_size
|
87
|
+
* [x] get_more
|
88
|
+
* [x] next_document
|
89
|
+
* [x] next_batch
|
90
|
+
* [x] each_doc
|
91
|
+
* [x] kill
|
92
|
+
* [x] mark_to_kill
|
93
|
+
* [x] batch_kill
|
94
|
+
* [x] explain
|
95
|
+
* [x] hint
|
96
|
+
* Flags
|
97
|
+
* [x] tailable_cursor
|
98
|
+
* [x] slave_ok
|
99
|
+
* [x] no_cursor_timeout
|
100
|
+
* [x] await_data
|
101
|
+
* [x] exhaust
|
102
|
+
* [x] partial
|
103
|
+
|
104
|
+
# ISSUES handled with
|
105
|
+
|
106
|
+
Some commands, such as `db.getLastError`, `db.count` and other `db.commands` requires `numberToReturn` in OP_QUERY to be setted as `-1`. Also this commands should return a response. If nothing returned it should be interpreted as an Exception. Also, in contrast to the other queries it could return `errmsg` which should be interpreted as an Exception too. Other query methods could return `err` response.
|
107
|
+
|
108
|
+
To create index you can't call any `db.ensureIndex` command but you should insert a document into `sytem.indexes` collection manually. To get list of indexes you should fetch all documents from this collection. But to drop index you should call specific `db.dropIndexes` command.
|
109
|
+
|
110
|
+
`multi_update` flag works only with `$` commands (i.e. `$set: { title: "blahblah" }`)
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
Rake::TestTask.new(:spec) do |t|
|
6
|
+
t.libs << 'spec'
|
7
|
+
if spec = ENV['spec']
|
8
|
+
t.pattern = "spec/**/#{spec}*_spec.rb"
|
9
|
+
else
|
10
|
+
t.pattern = 'spec/**/*_spec.rb'
|
11
|
+
end
|
12
|
+
t.verbose = false
|
13
|
+
end
|
data/lib/monga/client.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require File.expand_path("../clients/client", __FILE__)
|
2
|
+
require File.expand_path("../clients/replica_set_client", __FILE__)
|
3
|
+
require File.expand_path("../clients/master_slave_client", __FILE__)
|
4
|
+
module Monga
|
5
|
+
Client = Monga::Clients::Client
|
6
|
+
ReplicaSetClient = Monga::Clients::ReplicaSetClient
|
7
|
+
MasterSlaveClient = Monga::Clients::MasterSlaveClient
|
8
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Monga::Clients
|
2
|
+
class Client
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :@connection_pool, :aquire_connection, :send_command, :primary?, :connected?
|
6
|
+
|
7
|
+
attr_reader :connection_pool
|
8
|
+
|
9
|
+
def initialize(opts={})
|
10
|
+
opts[:pool_size] ||= 1
|
11
|
+
@connection_pool = Monga::ConnectionPool.new(opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](db_name)
|
15
|
+
Monga::Database.new(self, db_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_primary!
|
19
|
+
@connection_pool.connections.each do |conn|
|
20
|
+
conn.is_master?(self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# How it works
|
2
|
+
# Replica Set tries to establish connections to all passed servers.
|
3
|
+
# Till no connection established it queues al queries inside it's
|
4
|
+
|
5
|
+
module Monga::Clients
|
6
|
+
class ReplicaSetClient
|
7
|
+
class ProxyConnection
|
8
|
+
include EM::Deferrable
|
9
|
+
def initialize(client)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_command(msg, request_id=nil, &cb)
|
14
|
+
callback do
|
15
|
+
connection = @client.aquire_connection
|
16
|
+
connection.send_command(msg, request_id, &cb)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
include EM::Deferrable
|
22
|
+
|
23
|
+
attr_reader :servers, :clients
|
24
|
+
|
25
|
+
def initialize(opts = {})
|
26
|
+
@read_pref = opts.delete(:read_pref) || :primary
|
27
|
+
@servers = opts.delete(:servers)
|
28
|
+
raise ArgumentError, "servers option is not passed or empty" if @servers.empty?
|
29
|
+
|
30
|
+
@clients = @servers.map do |server|
|
31
|
+
Monga::Client.new(server.merge(opts))
|
32
|
+
end
|
33
|
+
|
34
|
+
@proxy_connection = ProxyConnection.new(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](db_name)
|
38
|
+
Monga::Database.new(self, db_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def aquire_connection
|
42
|
+
server ||= case @read_pref
|
43
|
+
when :primary
|
44
|
+
primary
|
45
|
+
when :secondary
|
46
|
+
secondary
|
47
|
+
when :primary_preferred
|
48
|
+
primary || secondary
|
49
|
+
when :secondary_preferred
|
50
|
+
secondary || primary
|
51
|
+
when :nearest
|
52
|
+
fail "unimplemented read_pref mode"
|
53
|
+
else
|
54
|
+
fail "read_pref is undefined"
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
if server
|
59
|
+
if @deferred_status != :succeeded
|
60
|
+
set_deferred_status :succeeded
|
61
|
+
@proxy_connection.set_deferred_status :succeeded
|
62
|
+
end
|
63
|
+
else
|
64
|
+
if @deferred_status == :succeeded
|
65
|
+
set_deferred_status nil
|
66
|
+
@proxy_connection.set_deferred_status nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
server || @proxy_connection
|
71
|
+
end
|
72
|
+
|
73
|
+
def primary
|
74
|
+
prim = @clients.detect{ |c| c.primary? && c.connected? }
|
75
|
+
unless prim
|
76
|
+
find_primary!
|
77
|
+
end
|
78
|
+
prim
|
79
|
+
end
|
80
|
+
|
81
|
+
def secondary
|
82
|
+
@clients.select{ |c| !c.primary? && c.connected? }.sample
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_primary!
|
86
|
+
unless @pending_primary
|
87
|
+
@pending_primary = true
|
88
|
+
@clients.each{ |c| c.find_primary! }
|
89
|
+
EM.add_timer(0.1) do
|
90
|
+
if primary
|
91
|
+
aquire_connection
|
92
|
+
@pending_primary = false
|
93
|
+
else
|
94
|
+
@pending_primary = false
|
95
|
+
find_primary!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Monga
|
2
|
+
class Collection
|
3
|
+
attr_reader :name, :db
|
4
|
+
|
5
|
+
def initialize(db, name)
|
6
|
+
@db = db
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def query(query = {}, fields = {}, opts = {})
|
11
|
+
options = {}
|
12
|
+
options[:query] = query
|
13
|
+
options[:fields] = fields
|
14
|
+
options.merge! opts
|
15
|
+
Monga::Miner.new(db, name, options)
|
16
|
+
end
|
17
|
+
alias :find :query
|
18
|
+
|
19
|
+
def find_one(query = {}, fields = {}, opts = {})
|
20
|
+
options = {}
|
21
|
+
options[:query] = query
|
22
|
+
options[:fields] = fields
|
23
|
+
options.merge! opts
|
24
|
+
|
25
|
+
Monga::Response.surround do |resp|
|
26
|
+
req = Monga::Miner.new(db, name, options).limit(1)
|
27
|
+
req.callback{ |data| resp.succeed data.first }
|
28
|
+
req.errback{ |err| resp.fail err }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
alias :first :find_one
|
32
|
+
|
33
|
+
def insert(documents, opts = {})
|
34
|
+
options = {}
|
35
|
+
options[:documents] = documents
|
36
|
+
options.merge!(opts)
|
37
|
+
Monga::Requests::Insert.new(@db, @name, options).perform
|
38
|
+
end
|
39
|
+
|
40
|
+
def update(query = {}, update = {}, flags = {})
|
41
|
+
options = {}
|
42
|
+
options[:query] = query
|
43
|
+
options[:update] = update
|
44
|
+
options.merge!(flags)
|
45
|
+
Monga::Requests::Update.new(@db, @name, options).perform
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete(query = {}, opts = {})
|
49
|
+
options = {}
|
50
|
+
options[:query] = query
|
51
|
+
options.merge!(opts)
|
52
|
+
Monga::Requests::Delete.new(@db, @name, options).perform
|
53
|
+
end
|
54
|
+
alias :remove :delete
|
55
|
+
|
56
|
+
def ensure_index(keys, opts={})
|
57
|
+
doc = {}
|
58
|
+
doc.merge!(opts)
|
59
|
+
# Read docs about naming
|
60
|
+
doc[:name] ||= keys.to_a.flatten * "_"
|
61
|
+
doc[:key] = keys
|
62
|
+
doc[:ns] = "#{db.name}.#{name}"
|
63
|
+
Monga::Requests::Insert.new(@db, "system.indexes", {documents: doc}).perform
|
64
|
+
end
|
65
|
+
|
66
|
+
def drop_index(indexes)
|
67
|
+
@db.drop_indexes(@name, indexes)
|
68
|
+
end
|
69
|
+
|
70
|
+
def drop_indexes
|
71
|
+
@db.drop_indexes(@name, "*")
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_indexes
|
75
|
+
Monga::Miner.new(@db, "system.indexes")
|
76
|
+
end
|
77
|
+
|
78
|
+
def drop
|
79
|
+
@db.drop_collection(@name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def count
|
83
|
+
@db.count(@name)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Safe methods
|
87
|
+
[:update, :insert, :delete, :remove, :ensure_index].each do |meth|
|
88
|
+
class_eval <<-EOS
|
89
|
+
def safe_#{meth}(*args)
|
90
|
+
safe do
|
91
|
+
#{meth}(*args)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
EOS
|
95
|
+
end
|
96
|
+
|
97
|
+
def safe
|
98
|
+
response = Monga::Response.new
|
99
|
+
request_id = yield
|
100
|
+
req = @db.get_last_error
|
101
|
+
req.callback do |data|
|
102
|
+
response.succeed(request_id)
|
103
|
+
end
|
104
|
+
req.errback{ |err| response.fail(err) }
|
105
|
+
response
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path("../connections/em_connection", __FILE__)
|
2
|
+
require File.expand_path("../connections/primary", __FILE__)
|
3
|
+
require File.expand_path("../connections/secondary", __FILE__)
|
4
|
+
|
5
|
+
module Monga
|
6
|
+
class Connection
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@connection, :connected?, :reconnect, :responses, :send_command, :master?, :is_master?, :host, :port
|
10
|
+
|
11
|
+
CONNECTION_TYPES = {
|
12
|
+
default: Monga::Connections::EMConnection,
|
13
|
+
primary: Monga::Connections::Primary,
|
14
|
+
secondary: Monga::Connections::Secondary,
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(opts={})
|
18
|
+
conn_type = opts.delete(:connection_type) || :default
|
19
|
+
conn_class = CONNECTION_TYPES[conn_type]
|
20
|
+
@connection = conn_class.connect(opts)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Monga
|
2
|
+
class ConnectionPool < Monga::Connection
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :aquire_connection, :send_command
|
6
|
+
|
7
|
+
attr_reader :connections
|
8
|
+
|
9
|
+
def initialize(opts={})
|
10
|
+
@connections = []
|
11
|
+
pool_size = opts.delete :pool_size
|
12
|
+
|
13
|
+
pool_size.times do
|
14
|
+
@connections << Monga::Connection.new(opts)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def aquire_connection
|
19
|
+
connected = @connections.select(&:connected?)
|
20
|
+
if connected.any?
|
21
|
+
min = connected.min_by{ |c| c.responses.size }.responses.size
|
22
|
+
conns = connected.select{ |c| c.responses.size == min }
|
23
|
+
conns.sample
|
24
|
+
else
|
25
|
+
@connections.sample
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def primary?
|
30
|
+
conn = aquire_connection
|
31
|
+
conn ? conn.master? : false
|
32
|
+
end
|
33
|
+
|
34
|
+
def connected?
|
35
|
+
@connections.any?(&:connected?)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Monga::Connections
|
2
|
+
class EMConnection < EM::Connection
|
3
|
+
include EM::Deferrable
|
4
|
+
|
5
|
+
class Buffer
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :buffer
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@buffer = ""
|
12
|
+
@positon = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def append(data)
|
16
|
+
@buffer += data
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
while true
|
21
|
+
size = @buffer.size
|
22
|
+
if size > Monga::HEADER_SIZE
|
23
|
+
msg_length = @buffer[0, 4].unpack("L").first
|
24
|
+
if msg_length && size >= msg_length
|
25
|
+
data = @buffer.slice!(0, size)
|
26
|
+
yield data.unpack("LLLLLQLLa*")
|
27
|
+
else
|
28
|
+
break
|
29
|
+
end
|
30
|
+
else
|
31
|
+
break
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :responses, :host, :port
|
38
|
+
|
39
|
+
def initialize(opts = {})
|
40
|
+
@host = opts[:host]
|
41
|
+
@port = opts[:port]
|
42
|
+
@reactor_running = true
|
43
|
+
@responses = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.connect(opts = {})
|
47
|
+
host = opts[:host] ||= Monga::DEFAULT_HOST
|
48
|
+
port = opts[:port] ||= Monga::DEFAULT_PORT
|
49
|
+
|
50
|
+
EM.connect(host, port, self, opts)
|
51
|
+
end
|
52
|
+
|
53
|
+
def send_command(msg, request_id=nil, &cb)
|
54
|
+
reconnect unless @connected
|
55
|
+
|
56
|
+
callback do
|
57
|
+
send_data msg
|
58
|
+
end
|
59
|
+
|
60
|
+
@responses[request_id] = cb if cb
|
61
|
+
end
|
62
|
+
|
63
|
+
def receive_data(data)
|
64
|
+
@buffer.append(data)
|
65
|
+
@buffer.each do |message|
|
66
|
+
request_id = message[2]
|
67
|
+
cb = @responses.delete request_id
|
68
|
+
cb.call(message) if cb
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def connection_completed
|
73
|
+
Monga.logger.debug("Connection is established #{@host}:#{@port}")
|
74
|
+
|
75
|
+
EM.add_shutdown_hook do
|
76
|
+
close
|
77
|
+
end
|
78
|
+
|
79
|
+
unless @reactor_running
|
80
|
+
EM.add_periodic_timer(Monga::Cursor::CLOSE_TIMEOUT){ Monga::Cursor.batch_kill(self) }
|
81
|
+
end
|
82
|
+
|
83
|
+
@connected = true
|
84
|
+
@pending_for_reconnect = false
|
85
|
+
@buffer = Buffer.new
|
86
|
+
@reactor_running = true
|
87
|
+
|
88
|
+
succeed
|
89
|
+
end
|
90
|
+
|
91
|
+
def reconnect
|
92
|
+
unless @connected && @pending_for_reconnect
|
93
|
+
if @reactor_running
|
94
|
+
super(@host, @port)
|
95
|
+
else
|
96
|
+
EM.schedule{ super(@host, @port) }
|
97
|
+
end
|
98
|
+
@pending_for_reconnect = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def force_reconnect(host, port)
|
103
|
+
@connected = false
|
104
|
+
@host = host
|
105
|
+
@port = port
|
106
|
+
end
|
107
|
+
|
108
|
+
def connected?
|
109
|
+
EM.schedule { reconnect } unless @reactor_running
|
110
|
+
@connected || false
|
111
|
+
end
|
112
|
+
|
113
|
+
def unbind
|
114
|
+
Monga.logger.debug("Lost connection #{@host}:#{@port}")
|
115
|
+
|
116
|
+
@responses.each{ |k, cb| cb.call(Monga::Exceptions::LostConnection.new("Mongo has lost connection"))}
|
117
|
+
@connected = false
|
118
|
+
@primary = false
|
119
|
+
@pending_for_reconnect = false
|
120
|
+
set_deferred_status(nil)
|
121
|
+
|
122
|
+
if @reactor_running
|
123
|
+
EM.add_timer(0.1){ reconnect }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def close
|
128
|
+
Monga.logger.debug("EventMachine is stopped, closing connection")
|
129
|
+
@reactor_running = false
|
130
|
+
end
|
131
|
+
|
132
|
+
def master?
|
133
|
+
@primary || false
|
134
|
+
end
|
135
|
+
|
136
|
+
def is_master?(client)
|
137
|
+
db = client["admin"]
|
138
|
+
req = Monga::Requests::Query.new(db, "$cmd", query: {"isMaster" => 1}, limit: 1)
|
139
|
+
command = req.command
|
140
|
+
request_id = req.request_id
|
141
|
+
@responses[request_id] = proc do |data|
|
142
|
+
resp = req.parse_response(data)
|
143
|
+
if Exception === resp
|
144
|
+
@primary = false
|
145
|
+
else
|
146
|
+
@primary = resp.last.first["ismaster"]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
send_data command
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Monga::Connections
|
2
|
+
class Primary < Monga::Connections::EMConnection
|
3
|
+
def initialize(opts)
|
4
|
+
@client = opts.delete :client
|
5
|
+
@ser
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def connection_completed
|
10
|
+
check_master do |master|
|
11
|
+
if master
|
12
|
+
super
|
13
|
+
else
|
14
|
+
reconnect
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_master
|
20
|
+
db = @client["admin"]
|
21
|
+
request = Monga::Request.new(db, "$cmd", query: { "isMaster" => 1 })
|
22
|
+
request_id = request.request_id
|
23
|
+
@responses[request_id] = proc do |data|
|
24
|
+
request.parse_response(data)
|
25
|
+
if Exception === data
|
26
|
+
Monga.logger.debug("Error on connecting Primary to #{@host}:#{@port}, #{data.class}, #{data.message}")
|
27
|
+
@host, @port = @client.next_addr(@host, @port)
|
28
|
+
yield false
|
29
|
+
else
|
30
|
+
doc = data.last.first
|
31
|
+
if doc["ismaster"]
|
32
|
+
Monga.logger.debug("Primary has connected to #{@host}:#{@port}")
|
33
|
+
@client.inform(:primary, @host, @port)
|
34
|
+
yield true
|
35
|
+
else
|
36
|
+
Monga.logger.debug("#{@host}:#{@port} is not a primary")
|
37
|
+
@host, @port = @client.select_addr(doc)
|
38
|
+
yield false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
command = request.command
|
43
|
+
send_data(command)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|