moped 1.5.3 → 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of moped might be problematic. Click here for more details.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -5
  3. data/README.md +1 -1
  4. data/lib/moped.rb +10 -13
  5. data/lib/moped/address.rb +56 -0
  6. data/lib/moped/authenticatable.rb +89 -0
  7. data/lib/moped/cluster.rb +169 -136
  8. data/lib/moped/collection.rb +53 -19
  9. data/lib/moped/connection.rb +69 -10
  10. data/lib/moped/connection/manager.rb +49 -0
  11. data/lib/moped/connection/pool.rb +198 -0
  12. data/lib/moped/connection/queue.rb +93 -0
  13. data/lib/moped/connection/reaper.rb +52 -0
  14. data/lib/moped/connection/socket.rb +4 -0
  15. data/lib/moped/connection/socket/connectable.rb +169 -0
  16. data/lib/moped/connection/socket/ssl.rb +52 -0
  17. data/lib/moped/connection/socket/tcp.rb +25 -0
  18. data/lib/moped/connection/sockets.rb +4 -0
  19. data/lib/moped/cursor.rb +3 -5
  20. data/lib/moped/database.rb +18 -24
  21. data/lib/moped/errors.rb +35 -6
  22. data/lib/moped/executable.rb +96 -0
  23. data/lib/moped/failover.rb +41 -0
  24. data/lib/moped/failover/disconnect.rb +31 -0
  25. data/lib/moped/failover/ignore.rb +29 -0
  26. data/lib/moped/failover/reconfigure.rb +34 -0
  27. data/lib/moped/failover/retry.rb +37 -0
  28. data/lib/moped/indexes.rb +4 -1
  29. data/lib/moped/instrumentable.rb +39 -0
  30. data/lib/moped/instrumentable/log.rb +43 -0
  31. data/lib/moped/instrumentable/noop.rb +31 -0
  32. data/lib/moped/loggable.rb +110 -0
  33. data/lib/moped/node.rb +316 -297
  34. data/lib/moped/operation.rb +3 -0
  35. data/lib/moped/operation/read.rb +62 -0
  36. data/lib/moped/operation/write.rb +57 -0
  37. data/lib/moped/protocol/command.rb +65 -4
  38. data/lib/moped/protocol/commands/authenticate.rb +1 -2
  39. data/lib/moped/protocol/delete.rb +16 -0
  40. data/lib/moped/protocol/get_more.rb +102 -31
  41. data/lib/moped/protocol/insert.rb +17 -0
  42. data/lib/moped/protocol/message.rb +44 -46
  43. data/lib/moped/protocol/query.rb +175 -92
  44. data/lib/moped/protocol/reply.rb +19 -8
  45. data/lib/moped/protocol/update.rb +18 -0
  46. data/lib/moped/query.rb +43 -17
  47. data/lib/moped/read_preference.rb +49 -0
  48. data/lib/moped/read_preference/nearest.rb +55 -0
  49. data/lib/moped/read_preference/primary.rb +60 -0
  50. data/lib/moped/read_preference/primary_preferred.rb +55 -0
  51. data/lib/moped/read_preference/secondary.rb +50 -0
  52. data/lib/moped/read_preference/secondary_preferred.rb +53 -0
  53. data/lib/moped/read_preference/selectable.rb +79 -0
  54. data/lib/moped/readable.rb +55 -0
  55. data/lib/moped/session.rb +122 -70
  56. data/lib/moped/{mongo_uri.rb → uri.rb} +75 -31
  57. data/lib/moped/version.rb +1 -1
  58. data/lib/moped/write_concern.rb +33 -0
  59. data/lib/moped/write_concern/propagate.rb +38 -0
  60. data/lib/moped/write_concern/unverified.rb +28 -0
  61. metadata +79 -44
  62. data/lib/moped/bson.rb +0 -45
  63. data/lib/moped/bson/binary.rb +0 -137
  64. data/lib/moped/bson/code.rb +0 -112
  65. data/lib/moped/bson/document.rb +0 -41
  66. data/lib/moped/bson/extensions.rb +0 -91
  67. data/lib/moped/bson/extensions/array.rb +0 -37
  68. data/lib/moped/bson/extensions/boolean.rb +0 -16
  69. data/lib/moped/bson/extensions/false_class.rb +0 -19
  70. data/lib/moped/bson/extensions/float.rb +0 -22
  71. data/lib/moped/bson/extensions/hash.rb +0 -39
  72. data/lib/moped/bson/extensions/integer.rb +0 -36
  73. data/lib/moped/bson/extensions/nil_class.rb +0 -19
  74. data/lib/moped/bson/extensions/object.rb +0 -11
  75. data/lib/moped/bson/extensions/regexp.rb +0 -38
  76. data/lib/moped/bson/extensions/string.rb +0 -45
  77. data/lib/moped/bson/extensions/symbol.rb +0 -33
  78. data/lib/moped/bson/extensions/time.rb +0 -23
  79. data/lib/moped/bson/extensions/true_class.rb +0 -19
  80. data/lib/moped/bson/max_key.rb +0 -51
  81. data/lib/moped/bson/min_key.rb +0 -51
  82. data/lib/moped/bson/object_id.rb +0 -301
  83. data/lib/moped/bson/timestamp.rb +0 -38
  84. data/lib/moped/bson/types.rb +0 -67
  85. data/lib/moped/logging.rb +0 -58
  86. data/lib/moped/session/context.rb +0 -115
  87. data/lib/moped/sockets/connectable.rb +0 -167
  88. data/lib/moped/sockets/ssl.rb +0 -50
  89. data/lib/moped/sockets/tcp.rb +0 -23
  90. data/lib/moped/threaded.rb +0 -69
@@ -1,16 +1,18 @@
1
+ # encoding: utf-8
2
+ require "moped/query"
3
+
1
4
  module Moped
2
5
 
3
6
  # The class for interacting with a MongoDB collection.
4
7
  #
5
- # @example
6
- # users = session[:users] # => <Moped::Collection ...>
7
- # users.drop
8
- # users.insert(name: "John")
9
- # users.find.to_a # => [{ name: "John" }]
8
+ # @since 1.0.0
10
9
  class Collection
10
+ include Readable
11
11
 
12
- # @attribute [r] database The collection's database.
13
- # @attribute [r] name The collection name.
12
+ # @!attribute database
13
+ # @return [ Database ] The database for the collection.
14
+ # @!attribute name
15
+ # @return [ String ] The name of the collection.
14
16
  attr_reader :database, :name
15
17
 
16
18
  # Return whether or not this collection is a capped collection.
@@ -35,15 +37,31 @@ module Moped
35
37
  # @since 1.0.0
36
38
  def drop
37
39
  begin
38
- database.session.with(consistency: :strong) do |session|
39
- session.context.command(database.name, drop: name)
40
- end
40
+ session.with(read: :primary).command(drop: name)
41
41
  rescue Moped::Errors::OperationFailure => e
42
- raise e unless e.details["errmsg"] == "ns not found"
42
+ raise e unless e.ns_not_found?
43
43
  false
44
44
  end
45
45
  end
46
46
 
47
+ # Rename the collection
48
+ #
49
+ # @example Rename the collection to 'foo'
50
+ # collection.rename('foo')
51
+ #
52
+ # @return [ Hash ] The command information.
53
+ #
54
+ # @since 2.0.0
55
+ def rename(to_name)
56
+ begin
57
+ session.
58
+ with(database: "admin", read: :primary).
59
+ command(renameCollection: "#{database.name}.#{name}", to: "#{database.name}.#{to_name}")
60
+ rescue Moped::Errors::OperationFailure => e
61
+ raise e unless e.ns_not_exists?
62
+ false
63
+ end
64
+ end
47
65
  # Build a query for this collection.
48
66
  #
49
67
  # @example Build a query based on the provided selector.
@@ -81,7 +99,8 @@ module Moped
81
99
  #
82
100
  # @since 1.0.0
83
101
  def initialize(database, name)
84
- @database, @name = database, name.to_s
102
+ @database = database
103
+ @name = name.to_s
85
104
  end
86
105
 
87
106
  # Insert one or more documents into the collection.
@@ -101,9 +120,9 @@ module Moped
101
120
  #
102
121
  # @since 1.0.0
103
122
  def insert(documents, flags = nil)
104
- documents = [documents] unless documents.is_a?(Array)
105
- database.session.with(consistency: :strong) do |session|
106
- session.context.insert(database.name, name, documents, flags: flags || [])
123
+ docs = documents.is_a?(Array) ? documents : [ documents ]
124
+ cluster.with_primary do |node|
125
+ node.insert(database.name, name, docs, write_concern, flags: flags || [])
107
126
  end
108
127
  end
109
128
 
@@ -117,15 +136,30 @@ module Moped
117
136
  # }
118
137
  # })
119
138
  #
120
- # @param [ Hash, Array<Hash> ] documents representing the aggregate function to execute
139
+ # @param [ Hash, Array<Hash> ] documents representing the aggregate
140
+ # function to execute
121
141
  #
122
142
  # @return [ Hash ] containing the result of aggregation
123
143
  #
124
144
  # @since 1.3.0
125
145
  def aggregate(*pipeline)
126
- pipeline.flatten!
127
- command = { aggregate: name.to_s, pipeline: pipeline }
128
- database.session.command(command)["result"]
146
+ session.command(aggregate: name, pipeline: pipeline.flatten)["result"]
147
+ end
148
+
149
+ # Get the session for the collection.
150
+ #
151
+ # @example Get the session for the collection.
152
+ # collection.session
153
+ #
154
+ # @return [ Session ] The session for the collection.
155
+ #
156
+ # @since 2.0.0
157
+ def session
158
+ database.session
159
+ end
160
+
161
+ def write_concern
162
+ session.write_concern
129
163
  end
130
164
  end
131
165
  end
@@ -1,16 +1,33 @@
1
- require "timeout"
2
- require "moped/sockets/connectable"
3
- require "moped/sockets/tcp"
4
- require "moped/sockets/ssl"
1
+ # encoding: utf-8
2
+ require "moped/connection/manager"
3
+ require "moped/connection/pool"
4
+ require "moped/connection/queue"
5
+ require "moped/connection/reaper"
6
+ require "moped/connection/sockets"
5
7
 
6
8
  module Moped
7
9
 
8
10
  # This class contains behaviour of database socket connections.
9
11
  #
10
- # @api private
12
+ # @since 2.0.0
11
13
  class Connection
12
14
 
13
- attr_reader :host, :port, :timeout, :options
15
+ # The default connection timeout, in seconds.
16
+ #
17
+ # @since 2.0.0
18
+ TIMEOUT = 5
19
+
20
+ # @!attribute host
21
+ # @return [ String ] The ip address of the host.
22
+ # @!attribute options
23
+ # @return [ Hash ] The connection options.
24
+ # @!attribute port
25
+ # @return [ String ] The port the connection connects on.
26
+ # @!attribute timeout
27
+ # @return [ Integer ] The timeout in seconds.
28
+ # @!attribute last_use
29
+ # @return [ Time ] The time the connection was last checked out.
30
+ attr_reader :host, :options, :port, :timeout, :last_use
14
31
 
15
32
  # Is the connection alive?
16
33
  #
@@ -34,9 +51,9 @@ module Moped
34
51
  # @since 1.0.0
35
52
  def connect
36
53
  @sock = if !!options[:ssl]
37
- Sockets::SSL.connect(host, port, timeout)
54
+ Socket::SSL.connect(host, port, timeout)
38
55
  else
39
- Sockets::TCP.connect(host, port, timeout)
56
+ Socket::TCP.connect(host, port, timeout)
40
57
  end
41
58
  end
42
59
 
@@ -80,9 +97,49 @@ module Moped
80
97
  # @option options [ Boolean ] :ssl Connect using SSL
81
98
  # @since 1.0.0
82
99
  def initialize(host, port, timeout, options = {})
100
+ @host = host
101
+ @port = port
102
+ @timeout = timeout
103
+ @options = options
83
104
  @sock = nil
105
+ @last_use = nil
84
106
  @request_id = 0
85
- @host, @port, @timeout, @options = host, port, timeout, options
107
+ end
108
+
109
+ # Expiring a connection means returning it to the connection pool.
110
+ #
111
+ # @example Expire the connection.
112
+ # connection.expire
113
+ #
114
+ # @return [ nil ] nil.
115
+ #
116
+ # @since 2.0.0
117
+ def expire
118
+ @last_use = nil
119
+ end
120
+
121
+ # An expired connection is not currently being used.
122
+ #
123
+ # @example Is the connection expired?
124
+ # connection.expired?
125
+ #
126
+ # @return [ true, false ] If the connection is expired.
127
+ #
128
+ # @since 2.0.0
129
+ def expired?
130
+ @last_use.nil?
131
+ end
132
+
133
+ # A leased connection is currently checkout out from the connection pool.
134
+ #
135
+ # @example Lease the connection.
136
+ # connection.lease
137
+ #
138
+ # @return [ Time ] The current time of leasing.
139
+ #
140
+ # @since 2.0.0
141
+ def lease
142
+ @last_use = Time.now
86
143
  end
87
144
 
88
145
  # Read from the connection.
@@ -113,7 +170,7 @@ module Moped
113
170
  sock_read = read_data(socket, reply.length - 36)
114
171
  buffer = StringIO.new(sock_read)
115
172
  reply.documents = reply.count.times.map do
116
- BSON::Document.deserialize(buffer)
173
+ BSON::Document.from_bson(buffer)
117
174
  end
118
175
  end
119
176
  reply
@@ -189,6 +246,8 @@ module Moped
189
246
  # Yields a connected socket to the calling back. It will attempt to reconnect
190
247
  # the socket if it is not connected.
191
248
  #
249
+ # @api private
250
+ #
192
251
  # @example Write to the connection.
193
252
  # with_connection do |socket|
194
253
  # socket.write(buf)
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+ module Moped
3
+ class Connection
4
+
5
+ # This class contains behaviour of connection pools for specific addresses.
6
+ #
7
+ # @since 2.0.0
8
+ module Manager
9
+ extend self
10
+
11
+ # Used for synchronization of pools access.
12
+ MUTEX = Mutex.new
13
+
14
+ # Get a connection pool for the provided node.
15
+ #
16
+ # @example Get a connection pool for the node.
17
+ # Manager.pool(node)
18
+ #
19
+ # @param [ Node ] The node.
20
+ #
21
+ # @return [ Pool ] The connection pool for the Node.
22
+ #
23
+ # @since 2.0.0
24
+ def pool(node)
25
+ MUTEX.synchronize do
26
+ pools[node.address.resolved] ||=
27
+ Pool.new(node.address.ip, node.address.port, node.options)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Get all the connection pools. This is a cache that stores each pool
34
+ # with lookup by it's resolved address.
35
+ #
36
+ # @api private
37
+ #
38
+ # @example Get the pools.
39
+ # Manager.pools
40
+ #
41
+ # @return [ Hash ] The cache of pools.
42
+ #
43
+ # @since 2.0.0
44
+ def pools
45
+ @pools ||= {}
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,198 @@
1
+ # encoding: utf-8
2
+ module Moped
3
+ class Connection
4
+
5
+ # This class contains behaviour of connection pools for specific addresses.
6
+ #
7
+ # @since 2.0.0
8
+ class Pool
9
+
10
+ # The default max size for the connection pool.
11
+ POOL_SIZE = 5
12
+
13
+ # The default timeout for getting connections from the queue.
14
+ TIMEOUT = 0.25
15
+
16
+ # @!attribute host
17
+ # @return [ String ] The host the pool is for.
18
+ # @!attribute port
19
+ # @return [ Integer ] The port on the host.
20
+ # @!attribute options
21
+ # @return [ Hash ] The connection pool options.
22
+ # @!attribute reaper
23
+ # @return [ Reaper ] The connection pool reaper.
24
+ attr_reader :host, :port, :options, :reaper
25
+
26
+ # Checkout a connection from the connection pool. If there exists a
27
+ # connection pinned to the current thread, then we return that first. If
28
+ # no connection is pinned, we will take an unpinned connection or create
29
+ # a new one if no unpinned exist and the pool is not saturated.
30
+ #
31
+ # @example Checkout a connection.
32
+ # pool.checkout
33
+ #
34
+ # @return [ Connection ] A connection.
35
+ #
36
+ # @since 2.0.0
37
+ def checkout
38
+ mutex.synchronize do
39
+ connection = pinned[thread_id]
40
+ if connection
41
+ unless connection.expired?
42
+ raise Errors::ConnectionInUse, "The connection on thread: #{thread_id} is in use."
43
+ else
44
+ lease(connection)
45
+ end
46
+ else
47
+ connection = pinned[thread_id] = next_connection
48
+ lease(connection)
49
+ end
50
+ end
51
+ end
52
+
53
+ # Checkin the connection, indicating that it is finished being used. The
54
+ # connection will stay pinned to the current thread.
55
+ #
56
+ # @example Checkin the connection.
57
+ # pool.checkin(connection)
58
+ #
59
+ # @param [ Connection ] connection The connection to checkin.
60
+ #
61
+ # @since 2.0.0
62
+ def checkin(connection)
63
+ mutex.synchronize do
64
+ expire(connection)
65
+ end
66
+ end
67
+
68
+ # Initialize the connection pool.
69
+ #
70
+ # @example Instantiate the connection pool.
71
+ # Pool.new(max_size: 4)
72
+ #
73
+ # @param [ Hash ] options The connection pool options.
74
+ #
75
+ # @since 2.0.0
76
+ def initialize(host, port, options = {})
77
+ @host = host
78
+ @port = port
79
+ @options = options
80
+ @reaper = Reaper.new(options[:reap_interval] || Reaper::INTERVAL, self)
81
+ @mutex = Mutex.new
82
+ @resource = ConditionVariable.new
83
+ @pinned = {}
84
+ @unpinned = Queue.new(max_size) do
85
+ Connection.new(host, port, options[:timeout] || Connection::TIMEOUT, options)
86
+ end
87
+ reaper.start
88
+ end
89
+
90
+ # Get the max size for the connection pool.
91
+ #
92
+ # @example Get the max size.
93
+ # pool.max_size
94
+ #
95
+ # @return [ Integer ] The max size of the pool.
96
+ #
97
+ # @since 2.0.0
98
+ def max_size
99
+ @max_size ||= (options[:pool_size] || POOL_SIZE)
100
+ end
101
+
102
+ # Reap all connections that are active and associated with dead threads.
103
+ #
104
+ # @example Reap the connections.
105
+ # pool.reap([ 12351122313 ])
106
+ #
107
+ # @param [ Array<Integer> ] ids The ids of the current active threads.
108
+ #
109
+ # @return [ Pool ] The connection pool.
110
+ #
111
+ # @since 2.0.0
112
+ def reap(ids = active_threads)
113
+ pinned.each do |id, conn|
114
+ unless ids.include?(id)
115
+ conn.expire
116
+ unpinned.push(pinned.delete(id))
117
+ end
118
+ end and self
119
+ end
120
+
121
+ # Get the current size of the connection pool. Is the total of pinned
122
+ # plus unpinned connections.
123
+ #
124
+ # @example Get the pool's current size.
125
+ # pool.size
126
+ #
127
+ # @return [ Integer ] The current size of the pool.
128
+ #
129
+ # @since 2.0.0
130
+ def size
131
+ unpinned.size + pinned.size
132
+ end
133
+
134
+ # Get the timeout when attempting to check out items from the pool.
135
+ #
136
+ # @example Get the checkout timeout.
137
+ # pool.timeout
138
+ #
139
+ # @return [ Float ] The pool timeout.
140
+ #
141
+ # @since 2.0.0
142
+ def timeout
143
+ @timeout ||= (options[:pool_timeout] || TIMEOUT)
144
+ end
145
+
146
+ # Execute the block with a connection, ensuring that the checkin/checkout
147
+ # workflow is properly executed.
148
+ #
149
+ # @example Execute the block with a connection.
150
+ # pool.with_connection do |conn|
151
+ # conn.connect
152
+ # end
153
+ #
154
+ # @return [ Object ] The result of the yield.
155
+ #
156
+ # @since 2.0.0
157
+ def with_connection
158
+ connection = checkout
159
+ begin
160
+ yield(connection)
161
+ ensure
162
+ checkin(connection)
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ attr_reader :mutex, :resource, :pinned, :unpinned
169
+
170
+ def expire(connection)
171
+ connection.expire
172
+ pinned[thread_id] = connection
173
+ end
174
+
175
+ def lease(connection)
176
+ connection.lease
177
+ connection
178
+ end
179
+
180
+ def next_connection
181
+ reap if saturated?
182
+ unpinned.pop(timeout)
183
+ end
184
+
185
+ def saturated?
186
+ size == max_size
187
+ end
188
+
189
+ def thread_id
190
+ Thread.current.object_id
191
+ end
192
+
193
+ def active_threads
194
+ Thread.list.select{ |thread| thread.alive? }.map{ |thread| thread.object_id }
195
+ end
196
+ end
197
+ end
198
+ end