monga 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +1 -0
  3. data/README.md +59 -3
  4. data/lib/monga/client.rb +51 -6
  5. data/lib/monga/clients/master_slave_client.rb +0 -5
  6. data/lib/monga/clients/replica_set_client.rb +32 -71
  7. data/lib/monga/clients/single_instance_client.rb +53 -0
  8. data/lib/monga/collection.rb +102 -41
  9. data/lib/monga/connection.rb +38 -13
  10. data/lib/monga/connection_pool.rb +6 -17
  11. data/lib/monga/connections/buffer.rb +33 -0
  12. data/lib/monga/connections/em_connection.rb +25 -56
  13. data/lib/monga/connections/em_proxy_connection.rb +80 -0
  14. data/lib/monga/connections/fibered_connection.rb +26 -0
  15. data/lib/monga/connections/fibered_proxy_connection.rb +23 -0
  16. data/lib/monga/connections/proxy_connection.rb +4 -0
  17. data/lib/monga/connections/tcp_connection.rb +57 -0
  18. data/lib/monga/cursor.rb +197 -95
  19. data/lib/monga/database.rb +175 -60
  20. data/lib/monga/{requests → protocol}/delete.rb +1 -2
  21. data/lib/monga/{requests → protocol}/get_more.rb +1 -1
  22. data/lib/monga/{requests → protocol}/insert.rb +1 -2
  23. data/lib/monga/{requests → protocol}/kill_cursors.rb +1 -1
  24. data/lib/monga/{requests → protocol}/query.rb +3 -3
  25. data/lib/monga/{requests → protocol}/update.rb +1 -1
  26. data/lib/monga/request.rb +27 -23
  27. data/lib/monga/utils/constants.rb +5 -0
  28. data/lib/monga/utils/exceptions.rb +11 -0
  29. data/lib/monga.rb +19 -11
  30. data/monga.gemspec +2 -2
  31. data/spec/helpers/mongodb.rb +115 -38
  32. data/spec/monga/block/collection_spec.rb +172 -0
  33. data/spec/monga/block/cursor_spec.rb +160 -0
  34. data/spec/monga/block/database_spec.rb +80 -0
  35. data/spec/monga/block/single_instance_client_spec.rb +31 -0
  36. data/spec/monga/em/collection_spec.rb +308 -0
  37. data/spec/monga/em/cursor_spec.rb +256 -0
  38. data/spec/monga/em/database_spec.rb +140 -0
  39. data/spec/monga/em/replica_set_client_spec.rb +86 -0
  40. data/spec/monga/em/single_instance_client_spec.rb +28 -0
  41. data/spec/monga/sync/collection_spec.rb +247 -0
  42. data/spec/monga/sync/cursor_spec.rb +211 -0
  43. data/spec/monga/sync/database_spec.rb +110 -0
  44. data/spec/monga/sync/replica_set_client_spec.rb +54 -0
  45. data/spec/monga/sync/single_instance_client_spec.rb +25 -0
  46. data/spec/spec_helper.rb +2 -20
  47. metadata +50 -38
  48. data/lib/monga/clients/client.rb +0 -24
  49. data/lib/monga/connections/primary.rb +0 -46
  50. data/lib/monga/connections/secondary.rb +0 -13
  51. data/lib/monga/exceptions.rb +0 -9
  52. data/lib/monga/miner.rb +0 -72
  53. data/lib/monga/response.rb +0 -11
  54. data/spec/helpers/truncate.rb +0 -15
  55. data/spec/monga/collection_spec.rb +0 -448
  56. data/spec/monga/connection_pool_spec.rb +0 -50
  57. data/spec/monga/connection_spec.rb +0 -64
  58. data/spec/monga/cursor_spec.rb +0 -186
  59. data/spec/monga/database_spec.rb +0 -67
  60. data/spec/monga/replica_set_client_spec.rb +0 -46
  61. data/spec/monga/requests/delete_spec.rb +0 -0
  62. data/spec/monga/requests/insert_spec.rb +0 -0
  63. 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(opts = {})
40
- @host = opts[:host]
41
- @port = opts[: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(opts = {})
47
- host = opts[:host] ||= Monga::DEFAULT_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
- EM.schedule { reconnect } unless @reactor_running
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{ |k, cb| cb.call(Monga::Exceptions::LostConnection.new("Mongo has lost connection"))}
117
- @connected = false
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 master?
99
+ def primary?
133
100
  @primary || false
134
101
  end
135
102
 
136
- def is_master?(client)
137
- db = client["admin"]
138
- req = Monga::Requests::Query.new(db, "$cmd", query: {"isMaster" => 1}, limit: 1)
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 === resp
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,4 @@
1
+ module Monga::Connections
2
+ class ProxyConnection
3
+ end
4
+ 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