monga 0.0.3 → 0.0.4

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