monga 0.0.3 → 0.0.4

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 CHANGED
@@ -11,7 +11,6 @@ doc/
11
11
  lib/bundler/man
12
12
  pkg
13
13
  rdoc
14
- benchmarks
15
14
  spec/reports
16
15
  test/tmp
17
16
  test/version_tmp
data/README.md CHANGED
@@ -1,65 +1,107 @@
1
1
  [![Build Status](https://travis-ci.org/fl00r/monga.png?branch=master)](https://travis-ci.org/fl00r/monga)
2
2
 
3
- # Monga
3
+ This client is under development. You can try
4
+
5
+ * [em-mongo](https://github.com/bcg/em-mongo) with Eventmachine inside
6
+ * Official [mongo-ruby-driver](https://github.com/mongodb/mongo-ruby-driver) from 10gen
7
+ * [Moped](http://mongoid.org/en/moped/) from Mongoid guys
4
8
 
5
- [MongoDB](http://www.mongodb.org/) Ruby Client on [EventMachine](https://github.com/eventmachine/eventmachine). Also it supports synchrony mode ([em-synchrony](https://github.com/igrigorik/em-synchrony)).
9
+ # Monga
6
10
 
7
- This client is under development. You can try [em-mongo](https://github.com/bcg/em-mongo).
11
+ Yet another [MongoDB](http://www.mongodb.org/) Ruby Client.
8
12
 
9
- Client supports MongoDB 2.4. Some features won't work in lower versions.
13
+ It supports three kind of interfaces:
10
14
 
11
- # Introduction
15
+ * Asynchronous over [EventMachine](https://github.com/eventmachine/eventmachine)
16
+ * Synchronous (on Fibers)
17
+ * Blocking (over TCPSocket, [kgio](http://bogomips.org/kgio/) actually)
12
18
 
13
- Monga supports asynchronous (over EventMachine), synchronous (Fibers) and blocking (TCPSocket) interfaces.
19
+ ## Introduction
14
20
 
15
- API will be familiar to Node.js developers: it returns `err, response` into callback.
21
+ Asynchronous API will be familiar to Node.js developers. Instead of Deferrable Object you will receive `err, response` into callback.
16
22
 
17
23
  ```ruby
18
- # Async mode
19
24
  EM.run do
20
- connection = Monga::Client.new(host: "127.0.0.1", port: 27017, type: :em)
21
- db = connection["dbTest"]
25
+ client = Monga::Client.new(type: :em)
26
+ db = client["testDb"]
22
27
  collection = db["testCollection"]
23
- collection.safe_insert(title: "Test") do |err, resp|
28
+
29
+ # Fire and forget
30
+ collection.insert(artist: "Madonna", title: "Frozen")
31
+
32
+ # Safe method
33
+ collection.safe_insert(artist: "Madonna", title: "Burning Up") do |err, response|
24
34
  if err
25
- puts "Error happend: #{err.message}"
35
+ puts "Ha, an error! #{err.message}"
26
36
  else
27
- puts "saved!"
28
- collection.find.all do |err, docs|
29
- if err
30
- puts "Error happend: #{err.message}"
37
+ puts "Job is done. Let's do more job"
38
+
39
+ # Cursor
40
+ collection.find.batch_size(100).limit(500).each_doc do |err, doc, iter|
41
+ if iter
42
+ puts "What have we got here: #{doc['title']}"
43
+ iter.next
31
44
  else
32
- puts "Docs fetched: #{docs.size}"
45
+ puts "No more documents in collection"
46
+ EM.stop
33
47
  end
34
- EM.stop
35
48
  end
49
+ # Yes, you should call `iter.next`, welcome to callback world!
36
50
  end
37
51
  end
38
52
  end
53
+ ```
54
+
55
+ Synchronous mode is more simple. It is just like blocking mode, but you can use pool of fibers to make it as fast as lightning.
39
56
 
40
- # Sync mode
57
+ ```ruby
41
58
  EM.synchrony do
42
- connection = Monga::Client.new(host: "127.0.0.1", port: 27017, type: :sync)
43
- db = connection["dbTest"]
59
+ client = Monga::Client.new(type: :sync)
60
+ db = client["testDb"]
44
61
  collection = db["testCollection"]
45
- collection.safe_insert(title: "Test")
46
- puts "saved"
47
- docs = collection.find.all
48
- puts "Docs fetched: #{docs.size}"
62
+
63
+ # Fire and forget
64
+ collection.insert(artist: "Madonna", title: "Frozen")
65
+
66
+ # Safe method
67
+ collection.safe_insert(artist: "Madonna", title: "Burning Up")
68
+ puts "Job is done"
69
+
70
+ # Cursor
71
+ docs = []
72
+ collection.find.batch_size(100).limit(500).each_doc do |doc|
73
+ puts "What have we got here: #{doc['title']}"
74
+ docs << doc
75
+ end
76
+ puts "We have got #{docs.size} documents in this pretty array"
77
+
49
78
  EM.stop
50
79
  end
80
+ ```
81
+
82
+ Blocking mode is as simple as a potato
51
83
 
52
- # Blocking mode
53
- connection = Monga::Client.new(host: "127.0.0.1", port: 27017, type: :block)
54
- db = connection["dbTest"]
84
+ ```ruby
85
+ # client = Monga::Client.new(type: :block)
86
+ client = Monga::Client.new
87
+ db = client["testDb"]
55
88
  collection = db["testCollection"]
56
- collection.safe_insert(title: "Test")
57
- puts "saved"
58
- docs = collection.find.all
59
- puts "Docs fetched: #{docs.size}"
60
- ```
61
89
 
62
- README and WIKI is going to be written.
90
+ # Fire and forget
91
+ collection.insert(artist: "Madonna", title: "Frozen")
92
+
93
+ # Safe method
94
+ collection.safe_insert(artist: "Madonna", title: "Burning Up")
95
+ puts "Job is done"
96
+
97
+ # Cursor
98
+ docs = []
99
+ collection.find.batch_size(100).limit(500).each_doc do |doc|
100
+ puts "What have we got here: #{doc['title']}"
101
+ docs << doc
102
+ end
103
+ puts "We have got #{docs.size} documents in this pretty array"
104
+ ```
63
105
 
64
106
  ## To Do List
65
107
 
@@ -0,0 +1,87 @@
1
+ require 'benchmark'
2
+ require 'mongo'
3
+ require 'moped'
4
+ require File.expand_path('../../lib/monga', __FILE__)
5
+
6
+ include Mongo
7
+
8
+ Benchmark.bm do |x|
9
+ total = 10000
10
+ fetch = 50
11
+ mongo_collection = MongoClient.new.db("dbTest").collection("testCollection")
12
+ monga_collection = Monga::Client.new(type: :block).get_database("dbTest").get_collection("testCollection")
13
+ Monga.logger.level = Logger::ERROR
14
+ moped_session = Moped::Session.new([ "127.0.0.1:27017" ])
15
+ moped_session.use "dbTest"
16
+
17
+ document = {}
18
+ document[:title] = "Some title"
19
+ chars = ('a'..'z').to_a
20
+ document[:body] = 100.times.map{ chars.sample } * ""
21
+
22
+ sleep 0.5
23
+
24
+ GC.start
25
+
26
+ x.report("Inserting with mongo") do
27
+ total.times do |i|
28
+ mongo_collection.insert(document.dup)
29
+ end
30
+ end
31
+
32
+ GC.start
33
+
34
+ x.report("Fetching with mongo") do
35
+ fetch.times do
36
+ mongo_collection.find.to_a
37
+ end
38
+ end
39
+ mongo_collection.drop
40
+
41
+ sleep 0.5
42
+
43
+ GC.start
44
+
45
+ x.report("Inserting with monga") do
46
+ total.times do |i|
47
+ monga_collection.safe_insert(document.dup)
48
+ end
49
+ end
50
+
51
+ GC.start
52
+
53
+ x.report("Fetching with monga") do
54
+ fetch.times do
55
+ monga_collection.find.all
56
+ end
57
+ end
58
+
59
+ monga_collection.drop
60
+
61
+ sleep 0.5
62
+
63
+ GC.start
64
+
65
+ x.report("Inserting with moped") do
66
+ moped_session.with(safe: true) do |safe|
67
+ total.times do |i|
68
+ safe[:testCollection].insert(document)
69
+ end
70
+ end
71
+ end
72
+
73
+ GC.start
74
+
75
+ x.report("Fetching with moped") do
76
+ fetch.times do
77
+ moped_session[:testCollection].find.to_a
78
+ end
79
+ end
80
+
81
+ monga_collection.drop
82
+ end
83
+
84
+
85
+
86
+
87
+
@@ -0,0 +1,26 @@
1
+ require 'ruby-prof'
2
+ require File.expand_path('../../lib/monga', __FILE__)
3
+ require 'mongo'
4
+ include Mongo
5
+
6
+ total = 100
7
+ # monga_collection = Monga::Client.new(type: :block).get_database("dbTest").get_collection("testCollection")
8
+ mongo_collection = MongoClient.new.db("dbTest").collection("testCollection")
9
+
10
+ total.times do |i|
11
+ mongo_collection.insert(title: "Row #{i}")
12
+ end
13
+ RubyProf.start
14
+ mongo_collection.find.to_a
15
+
16
+ result = RubyProf.stop
17
+ mongo_collection.drop
18
+
19
+ # Print a flat profile to text
20
+ printer = RubyProf::FlatPrinter.new(result)
21
+ printer.print(STDOUT)
22
+
23
+
24
+
25
+ mongo_collection = MongoClient.new.db("dbTest").collection("testCollection")
26
+ mongo_collection.find.to_a
@@ -14,7 +14,7 @@ module Monga::Clients
14
14
 
15
15
  servers = opts.delete :servers
16
16
  @clients = servers.map do |server|
17
- case server
17
+ c = case server
18
18
  when Hash
19
19
  Monga::Clients::SingleInstanceClient.new(opts.merge(server))
20
20
  when String
@@ -22,9 +22,11 @@ module Monga::Clients
22
22
  o = { host: h, port: p.to_i }
23
23
  Monga::Clients::SingleInstanceClient.new(opts.merge(o))
24
24
  end
25
+ c.force_status!
26
+ c
25
27
  end
26
28
 
27
- @proxy_connection = Monga::Connection.proxy_connection_class(opts[:type]).new(self)
29
+ @proxy_connection = Monga::Connection.proxy_connection_class(opts[:type], self)
28
30
  end
29
31
 
30
32
  # Aquires connection due to read_pref option
@@ -141,7 +141,7 @@ module Monga
141
141
  if Hash === last
142
142
  [ :j, :w, :fsync, :wtimeout ].each do |k|
143
143
  v = last.delete k
144
- opts[k] = v if v
144
+ opts[k] = v if v != nil
145
145
  end
146
146
  end
147
147
  req = #{meth}(*args)
@@ -9,7 +9,7 @@ module Monga
9
9
  CONNECTIONS = {
10
10
  em: Monga::Connections::EMConnection,
11
11
  sync: Monga::Connections::FiberedConnection,
12
- block: Monga::Connections::TCPConnection,
12
+ block: Monga::Connections::KGIOConnection,
13
13
  }
14
14
  PROXY_CONNECTIONS = {
15
15
  em: Monga::Connections::EMProxyConnection,
@@ -41,8 +41,9 @@ module Monga
41
41
  end
42
42
 
43
43
  # Returns name of proxy_connection class
44
- def self.proxy_connection_class(type)
45
- PROXY_CONNECTIONS[type]
44
+ def self.proxy_connection_class(type, client)
45
+ conn_class = PROXY_CONNECTIONS[type]
46
+ conn_class.new(client) if conn_class
46
47
  end
47
48
  end
48
49
  end
@@ -1,33 +1,80 @@
1
1
  module Monga::Connections
2
2
  class Buffer
3
- include Enumerable
3
+ # include Enumerable
4
4
 
5
- attr_reader :buffer
5
+ attr_reader :buffer, :responses
6
6
 
7
7
  def initialize
8
8
  @buffer = ""
9
- @positon = 0
9
+ @position = 0
10
+ @docs = []
11
+ @responses = []
10
12
  end
11
13
 
12
14
  def append(data)
13
- @buffer += data
15
+ @buffer << data
16
+ @buffer_size = @buffer.bytesize
17
+ job
18
+ @buffer
14
19
  end
15
20
 
16
- def each
21
+ def job
22
+ parse_meta if @position == 0
23
+ parse_doc if @position > 0
24
+ end
25
+
26
+ def parse_meta
27
+ return if @buffer_size < 36
28
+ @response = []
29
+ @response << ::BinUtils.get_int32_le(@buffer, @position)
30
+ @response << ::BinUtils.get_int32_le(@buffer, @position += 4)
31
+ @response << ::BinUtils.get_int32_le(@buffer, @position += 4)
32
+ @response << ::BinUtils.get_int32_le(@buffer, @position += 4)
33
+ @response << ::BinUtils.get_int32_le(@buffer, @position += 4)
34
+ @response << ::BinUtils.get_int64_le(@buffer, @position += 4)
35
+ @response << ::BinUtils.get_int32_le(@buffer, @position += 8)
36
+ @response << (@number_returned = ::BinUtils.get_int32_le(@buffer, @position += 4))
37
+ @response << []
38
+
39
+ @position += 4
40
+ end
41
+
42
+ def parse_doc
17
43
  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
44
+ if @number_returned == 0
45
+ done
46
+ break
47
+ end
48
+ break if @buffer_size < @position + 4
49
+ doc_length = ::BinUtils.get_int32_le(@buffer, @position)
50
+ break if @buffer_size < @position + doc_length
51
+ doc = @buffer[@position, doc_length]
52
+ @response[-1] << CBson.deserialize(doc)
53
+ @position += doc_length
54
+ @number_returned -= 1
55
+ if @number_returned == 0
56
+ done
28
57
  break
29
58
  end
30
59
  end
31
60
  end
61
+
62
+ def each
63
+ while resp = @responses.shift
64
+ yield resp
65
+ end
66
+ end
67
+
68
+ def done
69
+ @responses << @response
70
+ @response = nil
71
+ if @buffer_size == @position
72
+ @buffer.clear
73
+ else
74
+ @buffer = @buffer[@position, @buffer_size-@position]
75
+ end
76
+ @buffer_size = @buffer.bytesize
77
+ @position = 0
78
+ end
32
79
  end
33
80
  end
@@ -66,14 +66,14 @@ module Monga::Connections
66
66
  end
67
67
  end
68
68
  end
69
+ end
69
70
 
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
71
+ # YEEEHA! Send all collected requests back to client
72
+ def server_found!
73
+ @pending_timeout = false
74
+ @requests.keys.each do |request_id|
75
+ msg, blk = @requests.delete request_id
76
+ @client.aquire_connection.send_command(msg, request_id, &blk)
77
77
  end
78
78
  end
79
79
  end
@@ -0,0 +1,93 @@
1
+ require 'kgio'
2
+ require 'io/nonblock'
3
+ module Monga::Connections
4
+ class KGIOConnection
5
+ def self.connect(host, port, timeout)
6
+ new(host, port, timeout)
7
+ end
8
+
9
+ def initialize(host, port, timeout)
10
+ @host, @port, @timout = host, port, timeout
11
+ @connected = true
12
+ @buffer = Buffer.new
13
+ end
14
+
15
+ def connected?
16
+ @connected
17
+ end
18
+
19
+ def socket
20
+ @socket ||= begin
21
+ sock = Kgio::TCPSocket.new(@host, @port)
22
+ sock.kgio_autopush = true
23
+ @connected = true
24
+ sock
25
+ end
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.kgio_write msg.to_s
35
+ if cb
36
+ read_socket
37
+
38
+ message = @buffer.responses.shift
39
+ rid = message[2]
40
+
41
+ fail "Returned Request Id is not equal to sended one (#{rid} != #{request_id}), #{message}" if rid != request_id
42
+
43
+ cb.call(message)
44
+ end
45
+ rescue Errno::ECONNREFUSED, Errno::EPIPE => e
46
+ close
47
+ if cb
48
+ err = Monga::Exceptions::Disconnected.new("Disconnected from #{@host}:#{@port}, #{e.message}")
49
+ cb.call(err)
50
+ end
51
+ end
52
+
53
+ def read_socket
54
+ torecv = 512
55
+ length = nil
56
+ buf = ''.force_encoding('ASCII-8BIT')
57
+ tmp = ''
58
+ while torecv > 0
59
+ resp = socket.kgio_read(torecv, tmp)
60
+ raise Errno::ECONNREFUSED.new "Nil was return. Closing connection" unless resp
61
+ buf << resp
62
+ size = buf.bytesize
63
+ length ||= ::BinUtils.get_int32_le(buf) if size > 4
64
+ torecv = length - size if length
65
+ end
66
+ @buffer.append(buf)
67
+ end
68
+
69
+ def primary?
70
+ @primary || false
71
+ end
72
+
73
+ def is_master?
74
+ req = Monga::Protocol::Query.new(self, "admin", "$cmd", query: {"isMaster" => 1}, limit: 1)
75
+ command = req.command
76
+ request_id = req.request_id
77
+ socket.kgio_write command
78
+ read_socket
79
+ message = @buffer.responses.shift
80
+ @primary = message.last.first["ismaster"]
81
+ yield @primary ? :primary : :secondary
82
+ rescue => e
83
+ close
84
+ yield nil
85
+ end
86
+
87
+ def close
88
+ @socket = nil
89
+ @primary = false
90
+ @connected = false
91
+ end
92
+ end
93
+ end
@@ -1,4 +1,61 @@
1
+ require 'timeout'
2
+
1
3
  module Monga::Connections
2
4
  class ProxyConnection
5
+ # Pause while searching server in seconds
6
+ WAIT = 0.1
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ @timeout = @client.timeout
11
+ @requests = {}
12
+ end
13
+
14
+ # If timeout is defined then collect request and start timout.
15
+ # If timout is not defined or zero then return exception.
16
+ def send_command(msg, request_id = nil, &cb)
17
+ if @timeout && @timeout > 0
18
+ @requests[request_id] = [msg, cb] if cb
19
+ set_timeout
20
+ else
21
+ error = Monga::Exceptions::Disconnected.new "Can't find appropriate server (all disconnected)"
22
+ cb.call(error) if cb
23
+ end
24
+ end
25
+
26
+ # If timeout happend send exception to all collected requests.
27
+ def set_timeout
28
+ @not_found = true
29
+ Timeout::timeout(@timeout) do
30
+ while @not_found
31
+ find_server!
32
+ sleep(WAIT)
33
+ end
34
+ end
35
+ end
36
+
37
+ # Find server unless server is found
38
+ def find_server!
39
+ @client.clients.each do |client|
40
+ client.force_status! do |status|
41
+ if status == :primary && [:primary, :primary_preferred, :secondary_preferred].include?(@client.read_pref)
42
+ @pending_server = false
43
+ server_found!
44
+ elsif status == :secondary && [:secondary, :primary_preferred, :secondary_preferred].include?(@client.read_pref)
45
+ @pending_server = false
46
+ server_found!
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # YEEEHA! Send all collected requests back to client
53
+ def server_found!
54
+ @not_found = false
55
+ @requests.keys.each do |request_id|
56
+ msg, blk = @requests.delete request_id
57
+ @client.aquire_connection.send_command(msg, request_id, &blk)
58
+ end
59
+ end
3
60
  end
4
61
  end
@@ -22,7 +22,9 @@ module Monga::Connections
22
22
  end
23
23
 
24
24
  def socket
25
- @socket ||= TCPSocket.new(@host, @port)
25
+ @socket ||= begin
26
+ TCPSocket.new(@host, @port)
27
+ end
26
28
  end
27
29
 
28
30
  # Fake answer, as far as we are blocking
data/lib/monga/cursor.rb CHANGED
@@ -154,9 +154,16 @@ module Monga
154
154
  end
155
155
 
156
156
  def each_batch(&blk)
157
+ iter_more = true
157
158
  iterator = Proc.new do
158
- next_batch do |err, batch, more|
159
- more ? blk.call(err, batch, iterator) : blk.call(err, batch)
159
+ if iter_more
160
+ next_batch do |err, batch, more|
161
+ iter_more = more
162
+ (more || batch || err) ? blk.call(err, batch, iterator) : blk.call
163
+ end
164
+ else
165
+ # iteration stopped
166
+ blk.call
160
167
  end
161
168
  end
162
169
  class << iterator
@@ -184,9 +191,16 @@ module Monga
184
191
  alias :next_document :next_doc
185
192
 
186
193
  def each_doc(&blk)
194
+ iter_more = true
187
195
  iterator = Proc.new do
188
- next_doc do |err, doc, more|
189
- more ? blk.call(err, doc, iterator) : blk.call(err, doc)
196
+ if iter_more
197
+ next_doc do |err, doc, more|
198
+ iter_more = more
199
+ (more || doc || err) ? blk.call(err, doc, iterator) : blk.call
200
+ end
201
+ else
202
+ # iteration stopped
203
+ blk.call
190
204
  end
191
205
  end
192
206
  class << iterator
@@ -202,8 +216,8 @@ module Monga
202
216
  if err
203
217
  block_given? ? yield(err) : raise(err)
204
218
  else
205
- documents += batch
206
219
  if iter
220
+ documents += batch
207
221
  iter.next
208
222
  else
209
223
  block_given? ? yield(nil, documents) : documents
@@ -231,7 +245,7 @@ module Monga
231
245
  end
232
246
  end
233
247
 
234
- def each_batch(&blk)
248
+ def each_batch
235
249
  begin
236
250
  batch, more = next_batch
237
251
  yield batch
@@ -242,13 +256,11 @@ module Monga
242
256
  if doc = @fetched_docs.shift
243
257
  [doc, more?]
244
258
  else
245
- get_more(get_batch_size) do |err, batch, more|
246
- raise(err) if err
247
- @fetched_docs = batch
248
- doc = @fetched_docs.shift
249
- m = more || @fetched_docs.any?
250
- return [doc, m]
251
- end
259
+ batch, more = next_batch
260
+ @fetched_docs = batch
261
+ doc = @fetched_docs.shift
262
+ m = more || @fetched_docs.any?
263
+ return [doc, m]
252
264
  end
253
265
  end
254
266
  alias :next_document :next_doc
@@ -75,7 +75,12 @@ module Monga
75
75
  cmd = {}
76
76
  cmd[:getLastError] = 1
77
77
  cmd[:connection] = connection
78
- cmd.merge!(opts)
78
+
79
+ cmd[:j] = opts[:j] if opts[:j]
80
+ cmd[:fsync] = opts[:fsync] if opts[:fsync]
81
+ cmd[:w] = opts[:w] if opts[:w]
82
+ cmd[:wtimeout] = opts[:wtimeout] if opts[:wtimeout]
83
+
79
84
  run_cmd(cmd, blk)
80
85
  end
81
86
 
@@ -170,7 +175,9 @@ module Monga
170
175
 
171
176
  Monga::CallbackCursor.new(connection, name, "$cmd", options).first do |err, resp|
172
177
  res = make_response(err, resp, ret_blk, resp_blk)
173
- return res unless ret_blk
178
+ unless ret_blk
179
+ return res
180
+ end
174
181
  end
175
182
  end
176
183
 
@@ -10,12 +10,11 @@ module Monga::Protocol
10
10
  @body ||= begin
11
11
  query = @options[:query]
12
12
 
13
- b = BSON::ByteBuffer.new
14
- b.put_int(0)
15
- BSON::BSON_RUBY.serialize_cstr(b, full_name)
16
- b.put_int(flags)
17
- b.append!(BSON::BSON_C.serialize(query).to_s)
18
- b
13
+ msg = ::BinUtils.append_int32_le!(nil, 0)
14
+ msg << full_name << Monga::NULL_BYTE
15
+ ::BinUtils.append_int32_le!(msg, flags)
16
+ msg << BSON::BSON_C.serialize(query).to_s
17
+ msg
19
18
  end
20
19
  end
21
20
  end
@@ -7,12 +7,11 @@ module Monga::Protocol
7
7
  batch_size = @options[:batch_size] || 0
8
8
  cursor_id = @options[:cursor_id]
9
9
 
10
- b = BSON::ByteBuffer.new
11
- b.put_int(0)
12
- BSON::BSON_RUBY.serialize_cstr(b, full_name)
13
- b.put_int(batch_size)
14
- b.put_long(cursor_id)
15
- b
10
+ msg = ::BinUtils.append_int32_le!(nil, 0)
11
+ msg << full_name << Monga::NULL_BYTE
12
+ ::BinUtils.append_int32_le!(msg, batch_size)
13
+ ::BinUtils.append_int64_le!(msg, cursor_id)
14
+ msg
16
15
  end
17
16
  end
18
17
  end
@@ -10,18 +10,17 @@ module Monga::Protocol
10
10
  @body ||= begin
11
11
  documents = @options[:documents]
12
12
 
13
- b = BSON::ByteBuffer.new
14
- b.put_int(flags)
15
- BSON::BSON_RUBY.serialize_cstr(b, full_name)
13
+ msg = ::BinUtils.append_int32_le!(nil, flags)
14
+ msg << full_name << Monga::NULL_BYTE
16
15
  case documents
17
16
  when Array
18
17
  documents.each do |doc|
19
- b.append!(BSON::BSON_C.serialize(doc).to_s)
18
+ msg << BSON::BSON_C.serialize(doc).to_s
20
19
  end
21
20
  when Hash
22
- b.append!(BSON::BSON_C.serialize(documents).to_s)
21
+ msg << BSON::BSON_C.serialize(documents).to_s
23
22
  end
24
- b
23
+ msg
25
24
  end
26
25
  end
27
26
  end
@@ -12,13 +12,9 @@ module Monga::Protocol
12
12
  @body ||= begin
13
13
  cursor_ids = @options[:cursor_ids]
14
14
 
15
- b = BSON::ByteBuffer.new
16
- b.put_int(0)
17
- b.put_int(cursor_ids.size)
18
- cursor_ids.each do |cursor_id|
19
- b.put_long(cursor_id)
20
- end
21
- b
15
+ msg = ::BinUtils.append_int32_le!(nil, 0, cursor_ids.size)
16
+ ::BinUtils.append_int64_le!(msg, 0, *cursor_ids)
17
+ msg
22
18
  end
23
19
  end
24
20
  end
@@ -23,14 +23,12 @@ module Monga::Protocol
23
23
  query["$orderby"] = @options[:sort] if @options[:sort]
24
24
  query["$explain"] = @options[:explain] if @options[:explain]
25
25
 
26
- b = BSON::ByteBuffer.new
27
- b.put_int(flags)
28
- BSON::BSON_RUBY.serialize_cstr(b, full_name)
29
- b.put_int(skip)
30
- b.put_int(limit)
31
- b.append!(BSON::BSON_C.serialize(query).to_s)
32
- b.append!(BSON::BSON_C.serialize(selector).to_s) if selector.any?
33
- b
26
+ msg = ::BinUtils.append_int32_le!(nil, flags)
27
+ msg << full_name << Monga::NULL_BYTE
28
+ ::BinUtils.append_int32_le!(msg, skip, limit)
29
+ msg << BSON::BSON_C.serialize(query).to_s
30
+ msg << BSON::BSON_C.serialize(selector).to_s if selector.any?
31
+ msg
34
32
  end
35
33
  end
36
34
 
@@ -12,13 +12,12 @@ module Monga::Protocol
12
12
  query = @options[:query]
13
13
  update = @options[:update]
14
14
 
15
- b = BSON::ByteBuffer.new
16
- b.put_int(0)
17
- BSON::BSON_RUBY.serialize_cstr(b, full_name)
18
- b.put_int(flags)
19
- b.append!(BSON::BSON_C.serialize(query).to_s)
20
- b.append!(BSON::BSON_C.serialize(update).to_s)
21
- b
15
+ msg = ::BinUtils.append_int32_le!(nil, 0)
16
+ msg << full_name << Monga::NULL_BYTE
17
+ ::BinUtils.append_int32_le!(msg, flags)
18
+ msg << BSON::BSON_C.serialize(query).to_s
19
+ msg << BSON::BSON_C.serialize(update).to_s
20
+ msg
22
21
  end
23
22
  end
24
23
  end
data/lib/monga/request.rb CHANGED
@@ -24,16 +24,11 @@ module Monga
24
24
  end
25
25
 
26
26
  def command
27
- header.append!(body)
27
+ header + body
28
28
  end
29
29
 
30
30
  def header
31
- headers = BSON::ByteBuffer.new
32
- headers.put_int(command_length)
33
- headers.put_int(@request_id)
34
- headers.put_int(0)
35
- headers.put_int(op_code)
36
- headers
31
+ ::BinUtils.append_int32_le!(nil, command_length, @request_id, 0, op_code)
37
32
  end
38
33
 
39
34
  # Fire and Forget
@@ -59,9 +54,7 @@ module Monga
59
54
  [data, nil]
60
55
  else
61
56
  flags = data[4]
62
- number = data[7]
63
- docs = unpack_docs(data.last, number)
64
- data[-1] = docs
57
+ docs = data.last
65
58
  if flags & 2**0 > 0
66
59
  Monga::Exceptions::CursorNotFound.new(docs.first)
67
60
  elsif flags & 2**1 > 0
@@ -76,14 +69,6 @@ module Monga
76
69
 
77
70
  private
78
71
 
79
- def unpack_docs(data, number)
80
- number.times.map do
81
- size = data.slice(0, 4).unpack("L").first
82
- d = data.slice!(0, size)
83
- BSON.deserialize(d)
84
- end
85
- end
86
-
87
72
  def flags
88
73
  flags = 0
89
74
  self.class::FLAGS.each do |k, byte|
@@ -2,4 +2,5 @@ module Monga
2
2
  DEFAULT_HOST = "127.0.0.1"
3
3
  DEFAULT_PORT = 27017
4
4
  HEADER_SIZE = 16
5
+ NULL_BYTE = "\x00".freeze
5
6
  end
data/lib/monga.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "em-synchrony"
2
2
  require "bson"
3
+ require "bin_utils"
3
4
  require "logger"
4
5
  require "forwardable"
5
6
 
@@ -23,6 +24,7 @@ require File.expand_path("../monga/clients/replica_set_client", __FILE__)
23
24
  require File.expand_path("../monga/connections/em_connection", __FILE__)
24
25
  require File.expand_path("../monga/connections/fibered_connection", __FILE__)
25
26
  require File.expand_path("../monga/connections/tcp_connection", __FILE__)
27
+ require File.expand_path("../monga/connections/kgio_connection", __FILE__)
26
28
  require File.expand_path("../monga/connections/em_proxy_connection", __FILE__)
27
29
  require File.expand_path("../monga/connections/fibered_proxy_connection", __FILE__)
28
30
  require File.expand_path("../monga/connections/proxy_connection", __FILE__)
data/monga.gemspec CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "monga"
7
- spec.version = "0.0.3"
7
+ spec.version = "0.0.4"
8
8
  spec.authors = ["Petr Yanovich"]
9
9
  spec.email = ["fl00r@yandex.ru"]
10
10
  spec.description = %q{MongoDB Ruby Evented Driver on EventMachine}
@@ -19,8 +19,10 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.add_development_dependency "bundler", "~> 1.3"
21
21
  spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "kgio"
22
23
  spec.add_development_dependency "em-synchrony"
23
24
 
24
25
  spec.add_dependency "bson"
25
26
  spec.add_dependency "bson_ext"
27
+ spec.add_dependency "bin_utils"
26
28
  end
@@ -91,8 +91,8 @@ module Fake
91
91
  end
92
92
 
93
93
  def start
94
+ @sign = EM.start_server('127.0.0.1', @port, Fake::Node, self) unless @connected
94
95
  @connected = true
95
- @sign = EM.start_server '127.0.0.1', @port, Fake::Node, self
96
96
  end
97
97
 
98
98
  def stop
@@ -132,20 +132,20 @@ describe Monga::Cursor do
132
132
 
133
133
  describe "tailable cursor" do
134
134
  before do
135
+ @capped = @db["testCapped"]
135
136
  @db.create_collection("testCapped", capped: true, size: 4*1024)
136
- @capped = @db["testCapped"]
137
137
  @capped.safe_insert(title: "Test")
138
138
  end
139
139
 
140
140
  after do
141
- @db["testCapped"].drop
141
+ @capped.drop
142
142
  end
143
143
 
144
144
  it "should be tailable" do
145
145
  tailable_cursor = @capped.find.flag(tailable_cursor: true)
146
146
  docs = []
147
147
  tailable_cursor.each_doc do |doc|
148
- @capped.insert(title: "New!")
148
+ @capped.safe_insert(title: "New!")
149
149
  if doc
150
150
  docs << doc
151
151
  if docs.size == 2
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monga::Clients::ReplicaSetClient do
4
+ before do
5
+ @thread = Thread.new do
6
+ EM.run do
7
+ @replset = Fake::ReplicaSet.new([29000, 29100, 29200])
8
+ @replset.start_all
9
+ end
10
+ end
11
+ sleep 0.1
12
+ @client = Monga::Client.new servers: ['127.0.0.1:29000', '127.0.0.1:29100', '127.0.0.1:29200'], type: :block, timeout: 1
13
+ @collection = @client["dbTest"]["myCollection"]
14
+ end
15
+
16
+ after do
17
+ EM.stop if EM.reactor_running?
18
+ @thread.join
19
+ end
20
+
21
+ it "should fail on disconnect and reconnect when primary is up again" do
22
+ sleep(0.1)
23
+ @replset.start_all
24
+ sleep(0.1)
25
+ @collection.safe_insert(name: "Peter")
26
+ @replset.primary.stop
27
+ proc{ @collection.safe_insert(name: "Peter") }.must_raise Monga::Exceptions::Disconnected
28
+ proc{ @collection.safe_insert(name: "Peter") }.must_raise Timeout::Error
29
+ proc{ @collection.safe_insert(name: "Peter") }.must_raise Timeout::Error
30
+ @replset.primary.start
31
+ sleep(0.1)
32
+ @collection.safe_insert(name: "Madonna")
33
+ @collection.safe_insert(name: "Madonna")
34
+ @collection.safe_insert(name: "Madonna")
35
+ end
36
+
37
+ it "should work even if secondaries down" do
38
+ sleep(0.1)
39
+ @replset.start_all
40
+ @collection.safe_insert(name: "Peter")
41
+ @collection.safe_insert(name: "Peter")
42
+ @replset.secondaries.each(&:stop)
43
+ @collection.safe_insert(name: "Peter")
44
+ @collection.safe_insert(name: "Peter")
45
+ end
46
+
47
+ it "should find new primary if it is down" do
48
+ sleep(0.1)
49
+ @replset.start_all
50
+ @collection.safe_insert(name: "Peter")
51
+ @replset.primary.stop
52
+ proc{ @collection.safe_insert(name: "Peter") }.must_raise Monga::Exceptions::Disconnected
53
+ proc{ @collection.safe_insert(name: "Peter") }.must_raise Timeout::Error
54
+ proc{ @collection.safe_insert(name: "Peter") }.must_raise Timeout::Error
55
+ @replset.vote
56
+ @collection.safe_insert(name: "Madonna")
57
+ end
58
+ end
@@ -2,14 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe Monga::Clients::SingleInstanceClient do
4
4
  before do
5
- EM.synchrony do
6
- @client = Monga::Client.new port: 29000, type: :block
7
- @collection = @client["dbTest"]["myCollection"]
8
- @instance = Fake::SingleInstance.new(29000)
9
- EM.stop
10
- end
11
- @t = Thread.new do
5
+ @client = Monga::Client.new port: 28000, type: :block
6
+ @collection = @client["dbTest"]["myCollection"]
7
+ @thread = Thread.new do
12
8
  EM.run do
9
+ @instance = Fake::SingleInstance.new(28000)
13
10
  @instance.start
14
11
  end
15
12
  end
@@ -17,10 +14,11 @@ describe Monga::Clients::SingleInstanceClient do
17
14
 
18
15
  after do
19
16
  EM.stop
20
- @t.join
17
+ @thread.join
21
18
  end
22
19
 
23
20
  it "should fail on disconnect and reconnect when instance is up again" do
21
+ sleep(0.1) # wait till instance started
24
22
  @collection.safe_insert(name: "Peter")
25
23
  @instance.stop
26
24
  proc{ @collection.safe_insert(name: "Peter") }.must_raise Monga::Exceptions::Disconnected
@@ -119,8 +119,8 @@ describe Monga::Cursor do
119
119
  EM.run do
120
120
  docs = []
121
121
  @collection.find.batch_size(2).limit(3).each_batch do |err, batch, iter|
122
- docs += batch
123
122
  if iter
123
+ docs += batch
124
124
  iter.next
125
125
  else
126
126
  docs.size.must_equal 3
@@ -157,8 +157,8 @@ describe Monga::Cursor do
157
157
  EM.run do
158
158
  docs = []
159
159
  @collection.find.limit(100).skip(15).batch_size(3).each_doc do |err, doc, iter|
160
- docs << doc
161
160
  if iter
161
+ docs << doc
162
162
  iter.next
163
163
  else
164
164
  docs.size.must_equal 5
@@ -172,8 +172,8 @@ describe Monga::Cursor do
172
172
  EM.run do
173
173
  docs = []
174
174
  @collection.find.batch_size(3).each_doc do |err, doc, iter|
175
- docs << doc
176
175
  if iter
176
+ docs << doc
177
177
  iter.next
178
178
  else
179
179
  docs.size.must_equal 20
@@ -206,9 +206,9 @@ describe Monga::Cursor do
206
206
  describe "tailable cursor" do
207
207
  before do
208
208
  EM.run do
209
+ @capped = @db["testCapped"]
209
210
  @db.create_collection("testCapped", capped: true, size: 4*1024) do |err, resp|
210
211
  raise err if err
211
- @capped = @db["testCapped"]
212
212
  @capped.safe_insert(title: "Test") do |err, resp|
213
213
  raise err if err
214
214
  EM.stop
@@ -219,7 +219,6 @@ describe Monga::Cursor do
219
219
 
220
220
  after do
221
221
  EM.run do
222
- @capped = @db["testCapped"]
223
222
  @capped.drop do |err, resp|
224
223
  raise err if err
225
224
  EM.stop
@@ -246,8 +245,6 @@ describe Monga::Cursor do
246
245
  iter.next
247
246
  end
248
247
  end
249
- else
250
- EM.stop
251
248
  end
252
249
  end
253
250
  end
@@ -175,8 +175,8 @@ describe Monga::Cursor do
175
175
  describe "tailable cursor" do
176
176
  before do
177
177
  EM.synchrony do
178
- @db.create_collection("testCapped", capped: true, size: 4*1024)
179
178
  @capped = @db["testCapped"]
179
+ @db.create_collection("testCapped", capped: true, size: 4*1024)
180
180
  @capped.safe_insert(title: "Test")
181
181
  EM.stop
182
182
  end
@@ -184,7 +184,7 @@ describe Monga::Cursor do
184
184
 
185
185
  after do
186
186
  EM.synchrony do
187
- @db["testCapped"].drop
187
+ @capped.drop
188
188
  EM.stop
189
189
  end
190
190
  end
@@ -194,7 +194,7 @@ describe Monga::Cursor do
194
194
  tailable_cursor = @capped.find.flag(tailable_cursor: true)
195
195
  docs = []
196
196
  tailable_cursor.each_doc do |doc|
197
- @capped.insert(title: "New!")
197
+ @capped.safe_insert(title: "New!")
198
198
  if doc
199
199
  docs << doc
200
200
  if docs.size == 2
@@ -17,10 +17,10 @@ describe Monga::Clients::ReplicaSetClient do
17
17
  @replset.primary.stop
18
18
  proc{ @collection.safe_insert(name: "Peter") }.must_raise Monga::Exceptions::Disconnected
19
19
  proc{ @collection.safe_insert(name: "Peter") }.must_raise Monga::Exceptions::Disconnected
20
- proc{ @collection.safe_insert(name: "Peter") }.must_raise Monga::Exceptions::Disconnected
21
- @replset.primary.start
22
- @collection.safe_insert(name: "Madonna")
23
- @collection.safe_insert(name: "Madonna")
20
+ EM.add_timer(0.5) do
21
+ @replset.primary.start
22
+ end
23
+ @collection.safe_insert(name: "Peter")
24
24
  @collection.safe_insert(name: "Madonna")
25
25
  EM.stop
26
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monga
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-22 00:00:00.000000000 Z
12
+ date: 2013-04-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  requirement: !ruby/object:Gem::Requirement
@@ -43,6 +43,22 @@ dependencies:
43
43
  none: false
44
44
  prerelease: false
45
45
  name: rake
46
+ - !ruby/object:Gem::Dependency
47
+ requirement: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ none: false
53
+ type: :development
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ none: false
60
+ prerelease: false
61
+ name: kgio
46
62
  - !ruby/object:Gem::Dependency
47
63
  requirement: !ruby/object:Gem::Requirement
48
64
  requirements:
@@ -91,6 +107,22 @@ dependencies:
91
107
  none: false
92
108
  prerelease: false
93
109
  name: bson_ext
110
+ - !ruby/object:Gem::Dependency
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ none: false
117
+ type: :runtime
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ none: false
124
+ prerelease: false
125
+ name: bin_utils
94
126
  description: MongoDB Ruby Evented Driver on EventMachine
95
127
  email:
96
128
  - fl00r@yandex.ru
@@ -104,6 +136,8 @@ files:
104
136
  - LICENSE.txt
105
137
  - README.md
106
138
  - Rakefile
139
+ - benchmarks/inserts.rb
140
+ - benchmarks/prof.rb
107
141
  - lib/monga.rb
108
142
  - lib/monga/client.rb
109
143
  - lib/monga/clients/master_slave_client.rb
@@ -117,6 +151,7 @@ files:
117
151
  - lib/monga/connections/em_proxy_connection.rb
118
152
  - lib/monga/connections/fibered_connection.rb
119
153
  - lib/monga/connections/fibered_proxy_connection.rb
154
+ - lib/monga/connections/kgio_connection.rb
120
155
  - lib/monga/connections/proxy_connection.rb
121
156
  - lib/monga/connections/tcp_connection.rb
122
157
  - lib/monga/cursor.rb
@@ -135,6 +170,7 @@ files:
135
170
  - spec/monga/block/collection_spec.rb
136
171
  - spec/monga/block/cursor_spec.rb
137
172
  - spec/monga/block/database_spec.rb
173
+ - spec/monga/block/replica_set_client_spec.rb
138
174
  - spec/monga/block/single_instance_client_spec.rb
139
175
  - spec/monga/em/collection_spec.rb
140
176
  - spec/monga/em/cursor_spec.rb
@@ -158,7 +194,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
158
194
  requirements:
159
195
  - - ! '>='
160
196
  - !ruby/object:Gem::Version
161
- hash: -1506820029322651348
197
+ hash: 3881917270092852844
162
198
  version: '0'
163
199
  segments:
164
200
  - 0
@@ -167,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
203
  requirements:
168
204
  - - ! '>='
169
205
  - !ruby/object:Gem::Version
170
- hash: -1506820029322651348
206
+ hash: 3881917270092852844
171
207
  version: '0'
172
208
  segments:
173
209
  - 0
@@ -183,6 +219,7 @@ test_files:
183
219
  - spec/monga/block/collection_spec.rb
184
220
  - spec/monga/block/cursor_spec.rb
185
221
  - spec/monga/block/database_spec.rb
222
+ - spec/monga/block/replica_set_client_spec.rb
186
223
  - spec/monga/block/single_instance_client_spec.rb
187
224
  - spec/monga/em/collection_spec.rb
188
225
  - spec/monga/em/cursor_spec.rb