monga 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|