monga 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/README.md +59 -3
- data/lib/monga/client.rb +51 -6
- data/lib/monga/clients/master_slave_client.rb +0 -5
- data/lib/monga/clients/replica_set_client.rb +32 -71
- data/lib/monga/clients/single_instance_client.rb +53 -0
- data/lib/monga/collection.rb +102 -41
- data/lib/monga/connection.rb +38 -13
- data/lib/monga/connection_pool.rb +6 -17
- data/lib/monga/connections/buffer.rb +33 -0
- data/lib/monga/connections/em_connection.rb +25 -56
- data/lib/monga/connections/em_proxy_connection.rb +80 -0
- data/lib/monga/connections/fibered_connection.rb +26 -0
- data/lib/monga/connections/fibered_proxy_connection.rb +23 -0
- data/lib/monga/connections/proxy_connection.rb +4 -0
- data/lib/monga/connections/tcp_connection.rb +57 -0
- data/lib/monga/cursor.rb +197 -95
- data/lib/monga/database.rb +175 -60
- data/lib/monga/{requests → protocol}/delete.rb +1 -2
- data/lib/monga/{requests → protocol}/get_more.rb +1 -1
- data/lib/monga/{requests → protocol}/insert.rb +1 -2
- data/lib/monga/{requests → protocol}/kill_cursors.rb +1 -1
- data/lib/monga/{requests → protocol}/query.rb +3 -3
- data/lib/monga/{requests → protocol}/update.rb +1 -1
- data/lib/monga/request.rb +27 -23
- data/lib/monga/utils/constants.rb +5 -0
- data/lib/monga/utils/exceptions.rb +11 -0
- data/lib/monga.rb +19 -11
- data/monga.gemspec +2 -2
- data/spec/helpers/mongodb.rb +115 -38
- data/spec/monga/block/collection_spec.rb +172 -0
- data/spec/monga/block/cursor_spec.rb +160 -0
- data/spec/monga/block/database_spec.rb +80 -0
- data/spec/monga/block/single_instance_client_spec.rb +31 -0
- data/spec/monga/em/collection_spec.rb +308 -0
- data/spec/monga/em/cursor_spec.rb +256 -0
- data/spec/monga/em/database_spec.rb +140 -0
- data/spec/monga/em/replica_set_client_spec.rb +86 -0
- data/spec/monga/em/single_instance_client_spec.rb +28 -0
- data/spec/monga/sync/collection_spec.rb +247 -0
- data/spec/monga/sync/cursor_spec.rb +211 -0
- data/spec/monga/sync/database_spec.rb +110 -0
- data/spec/monga/sync/replica_set_client_spec.rb +54 -0
- data/spec/monga/sync/single_instance_client_spec.rb +25 -0
- data/spec/spec_helper.rb +2 -20
- metadata +50 -38
- data/lib/monga/clients/client.rb +0 -24
- data/lib/monga/connections/primary.rb +0 -46
- data/lib/monga/connections/secondary.rb +0 -13
- data/lib/monga/exceptions.rb +0 -9
- data/lib/monga/miner.rb +0 -72
- data/lib/monga/response.rb +0 -11
- data/spec/helpers/truncate.rb +0 -15
- data/spec/monga/collection_spec.rb +0 -448
- data/spec/monga/connection_pool_spec.rb +0 -50
- data/spec/monga/connection_spec.rb +0 -64
- data/spec/monga/cursor_spec.rb +0 -186
- data/spec/monga/database_spec.rb +0 -67
- data/spec/monga/replica_set_client_spec.rb +0 -46
- 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 +0 -28
@@ -0,0 +1,33 @@
|
|
1
|
+
module Monga::Connections
|
2
|
+
class Buffer
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :buffer
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@buffer = ""
|
9
|
+
@positon = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def append(data)
|
13
|
+
@buffer += data
|
14
|
+
end
|
15
|
+
|
16
|
+
def each
|
17
|
+
while true
|
18
|
+
size = @buffer.size
|
19
|
+
if size > Monga::HEADER_SIZE
|
20
|
+
msg_length = @buffer[0, 4].unpack("L").first
|
21
|
+
if msg_length && size >= msg_length
|
22
|
+
data = @buffer.slice!(0, msg_length)
|
23
|
+
yield data.unpack("LLLLLQLLa*")
|
24
|
+
else
|
25
|
+
break
|
26
|
+
end
|
27
|
+
else
|
28
|
+
break
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -2,61 +2,29 @@ module Monga::Connections
|
|
2
2
|
class EMConnection < EM::Connection
|
3
3
|
include EM::Deferrable
|
4
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, msg_length)
|
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
5
|
attr_reader :responses, :host, :port
|
38
6
|
|
39
|
-
def initialize(
|
40
|
-
@host =
|
41
|
-
@port =
|
7
|
+
def initialize(host, port, timeout)
|
8
|
+
@host = host
|
9
|
+
@port = port
|
10
|
+
@timeout = timeout
|
42
11
|
@reactor_running = true
|
43
12
|
@responses = {}
|
44
13
|
end
|
45
14
|
|
46
|
-
def self.connect(
|
47
|
-
host
|
48
|
-
port = opts[:port] ||= Monga::DEFAULT_PORT
|
49
|
-
|
50
|
-
EM.connect(host, port, self, opts)
|
15
|
+
def self.connect(host, port, timeout)
|
16
|
+
EM.connect(host, port, self, host, port, timeout)
|
51
17
|
end
|
52
18
|
|
53
19
|
def send_command(msg, request_id=nil, &cb)
|
20
|
+
# Reconnect is a hack for testing.
|
21
|
+
# We are stopping EvenMachine for each test.
|
22
|
+
# This hack reconnects to Mongo on first query
|
54
23
|
reconnect unless @connected
|
55
24
|
|
56
25
|
callback do
|
57
26
|
send_data msg
|
58
27
|
end
|
59
|
-
|
60
28
|
@responses[request_id] = cb if cb
|
61
29
|
end
|
62
30
|
|
@@ -99,22 +67,21 @@ module Monga::Connections
|
|
99
67
|
end
|
100
68
|
end
|
101
69
|
|
102
|
-
def force_reconnect(host, port)
|
103
|
-
@connected = false
|
104
|
-
@host = host
|
105
|
-
@port = port
|
106
|
-
end
|
107
|
-
|
108
70
|
def connected?
|
109
|
-
|
71
|
+
reconnect unless @reactor_running
|
110
72
|
@connected || false
|
111
73
|
end
|
112
74
|
|
113
75
|
def unbind
|
76
|
+
@connected = false
|
114
77
|
Monga.logger.debug("Lost connection #{@host}:#{@port}")
|
115
78
|
|
116
|
-
@responses.each
|
117
|
-
|
79
|
+
@responses.keys.each do |k|
|
80
|
+
cb = @responses.delete k
|
81
|
+
err = Monga::Exceptions::Disconnected.new("Disconnected from #{@host}:#{@port}")
|
82
|
+
cb.call(err)
|
83
|
+
end
|
84
|
+
|
118
85
|
@primary = false
|
119
86
|
@pending_for_reconnect = false
|
120
87
|
set_deferred_status(nil)
|
@@ -129,21 +96,23 @@ module Monga::Connections
|
|
129
96
|
@reactor_running = false
|
130
97
|
end
|
131
98
|
|
132
|
-
def
|
99
|
+
def primary?
|
133
100
|
@primary || false
|
134
101
|
end
|
135
102
|
|
136
|
-
def is_master?
|
137
|
-
|
138
|
-
req = Monga::
|
103
|
+
def is_master?
|
104
|
+
reconnect unless @connected
|
105
|
+
req = Monga::Protocol::Query.new(self, "admin", "$cmd", query: {"isMaster" => 1}, limit: 1)
|
139
106
|
command = req.command
|
140
107
|
request_id = req.request_id
|
141
108
|
@responses[request_id] = proc do |data|
|
142
|
-
resp = req.parse_response(data)
|
143
|
-
if Exception ===
|
109
|
+
err, resp = req.parse_response(data)
|
110
|
+
if Exception === err
|
144
111
|
@primary = false
|
112
|
+
yield nil
|
145
113
|
else
|
146
114
|
@primary = resp.last.first["ismaster"]
|
115
|
+
yield @primary ? :primary : :secondary
|
147
116
|
end
|
148
117
|
end
|
149
118
|
send_data command
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Monga::Connections
|
2
|
+
# ProxyConnection accepts requests when ReplicaSetClient didn't know where to send requests.
|
3
|
+
# I.E. when client is just initialized here is no any established connections,
|
4
|
+
# so client waits for the connection ready to accept requests.
|
5
|
+
# Also, when primary is down it will collect request while nodes are voting.
|
6
|
+
# Importaint to say, that requests will be stored in this object only for `timeout` period.
|
7
|
+
class EMProxyConnection
|
8
|
+
# Pause while searching server in seconds
|
9
|
+
WAIT = 0.1
|
10
|
+
|
11
|
+
def initialize(client)
|
12
|
+
@client = client
|
13
|
+
@timeout = @client.timeout
|
14
|
+
@requests = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# If timeout is defined then collect request and start timout.
|
18
|
+
# If timout is not defined or zero then return exception.
|
19
|
+
def send_command(msg, request_id = nil, &cb)
|
20
|
+
if @timeout && @timeout > 0
|
21
|
+
@requests[request_id] = [msg, cb] if cb
|
22
|
+
set_timeout
|
23
|
+
find_server!
|
24
|
+
else
|
25
|
+
error = Monga::Exceptions::Disconnected.new "Can't find appropriate server (all disconnected)"
|
26
|
+
cb.call(error) if cb
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# If timeout happend send exception to all collected requests.
|
31
|
+
def set_timeout
|
32
|
+
unless @pending_timeout
|
33
|
+
@pending_timeout = true
|
34
|
+
EM.add_timer(@timeout) do
|
35
|
+
@pending_timeout = false
|
36
|
+
@requests.keys.each do |request_id|
|
37
|
+
msg, cb = @requests.delete request_id
|
38
|
+
error = Monga::Exceptions::Disconnected.new "Can't find appropriate server (all disconnected)"
|
39
|
+
cb.call(error)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Find server unless server is found
|
46
|
+
def find_server!
|
47
|
+
if !@pending_server && @pending_timeout
|
48
|
+
@pending_server = true
|
49
|
+
_count = 0
|
50
|
+
@client.clients.each do |client|
|
51
|
+
client.force_status! do |status|
|
52
|
+
if status == :primary && [:primary, :primary_preferred, :secondary_preferred].include?(@client.read_pref)
|
53
|
+
@pending_server = false
|
54
|
+
server_found!
|
55
|
+
elsif status == :secondary && [:secondary, :primary_preferred, :secondary_preferred].include?(@client.read_pref)
|
56
|
+
@pending_server = false
|
57
|
+
server_found!
|
58
|
+
else
|
59
|
+
EM.add_timer(WAIT) do
|
60
|
+
EM.next_tick do
|
61
|
+
@pending_server = false if (_count +=1) == @client.clients.size
|
62
|
+
find_server!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# YEEEHA! Send all collected requests back to client
|
71
|
+
def server_found!
|
72
|
+
@pending_timeout = false
|
73
|
+
@requests.keys.each do |request_id|
|
74
|
+
msg, blk = @requests.delete request_id
|
75
|
+
@client.aquire_connection.send_command(msg, request_id, &blk)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
|
3
|
+
class Fiber
|
4
|
+
alias :call :resume
|
5
|
+
end
|
6
|
+
|
7
|
+
module Monga::Connections
|
8
|
+
class FiberedConnection < EMConnection
|
9
|
+
def send_command(msg, request_id=nil, &cb)
|
10
|
+
fib = Fiber.current
|
11
|
+
reconnect unless @connected
|
12
|
+
|
13
|
+
callback do
|
14
|
+
send_data msg
|
15
|
+
end
|
16
|
+
|
17
|
+
if cb
|
18
|
+
reconnect unless @connected
|
19
|
+
@responses[request_id] = fib
|
20
|
+
res = Fiber.yield
|
21
|
+
raise res if Exception === res
|
22
|
+
cb.call(res)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Monga::Connections
|
2
|
+
class FiberedProxyConnection < EMProxyConnection
|
3
|
+
def send_command(msg, request_id = nil, &cb)
|
4
|
+
if @timeout && @timeout > 0
|
5
|
+
@requests[request_id] = [msg, @fib]
|
6
|
+
@fib = Fiber.current
|
7
|
+
set_timeout
|
8
|
+
find_server!
|
9
|
+
res = Fiber.yield
|
10
|
+
@requests.delete(request_id)
|
11
|
+
raise res if Exception === res
|
12
|
+
@client.aquire_connection.send_command(msg, request_id, &cb)
|
13
|
+
else
|
14
|
+
error = Monga::Exceptions::Disconnected.new "Can't find appropriate server (all disconnected)"
|
15
|
+
cb.call(error) if cb
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def server_found!
|
20
|
+
@fib.resume
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# Currently blocking mode is very poor.
|
4
|
+
# It is working as is.
|
5
|
+
# Going to support reconnecting and timouts later.
|
6
|
+
# Use it for tests and prototyping. Not the best choice for production.
|
7
|
+
|
8
|
+
module Monga::Connections
|
9
|
+
class TCPConnection
|
10
|
+
def self.connect(host, port, timeout)
|
11
|
+
new(host, port, timeout)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(host, port, timeout)
|
15
|
+
@host, @port, @timout = host, port, timeout
|
16
|
+
@connected = true
|
17
|
+
@buffer = Buffer.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def connected?
|
21
|
+
@connected
|
22
|
+
end
|
23
|
+
|
24
|
+
def socket
|
25
|
+
@socket ||= TCPSocket.new(@host, @port)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fake answer, as far as we are blocking
|
29
|
+
def responses
|
30
|
+
0
|
31
|
+
end
|
32
|
+
|
33
|
+
def send_command(msg, request_id=nil, &cb)
|
34
|
+
socket.send msg.to_s, 0
|
35
|
+
if cb
|
36
|
+
length = socket.read(4)
|
37
|
+
raise Errno::ECONNREFUSED, "Socket returns nothing like it would be closed." unless length
|
38
|
+
@buffer.append(length)
|
39
|
+
l = length.unpack("L").first
|
40
|
+
rest = socket.read(l-4)
|
41
|
+
@buffer.append(rest)
|
42
|
+
@buffer.each do |message|
|
43
|
+
rid = message[2]
|
44
|
+
fail "Returned Request Id is not equal to sended one" if rid != request_id
|
45
|
+
cb.call(message)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
rescue Errno::ECONNREFUSED, Errno::EPIPE => e
|
49
|
+
@connected = false
|
50
|
+
@socket = nil
|
51
|
+
if cb
|
52
|
+
err = Monga::Exceptions::Disconnected.new("Disconnected from #{@host}:#{@port}, #{e.message}")
|
53
|
+
cb.call(err)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|