moped 1.0.0.alpha → 1.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.
- data/README.md +160 -0
- data/lib/moped.rb +4 -2
- data/lib/moped/bson/object_id.rb +31 -50
- data/lib/moped/cluster.rb +104 -152
- data/lib/moped/collection.rb +1 -3
- data/lib/moped/connection.rb +95 -0
- data/lib/moped/cursor.rb +38 -19
- data/lib/moped/database.rb +6 -14
- data/lib/moped/errors.rb +26 -11
- data/lib/moped/node.rb +334 -0
- data/lib/moped/protocol/command.rb +3 -2
- data/lib/moped/query.rb +24 -26
- data/lib/moped/session.rb +25 -71
- data/lib/moped/session/context.rb +105 -0
- data/lib/moped/threaded.rb +32 -0
- data/lib/moped/version.rb +1 -1
- metadata +7 -5
- data/lib/moped/server.rb +0 -73
- data/lib/moped/socket.rb +0 -201
data/lib/moped/collection.rb
CHANGED
@@ -56,12 +56,10 @@ module Moped
|
|
56
56
|
# @param [Array<Hash>] documents the documents to insert
|
57
57
|
def insert(documents)
|
58
58
|
documents = [documents] unless documents.is_a? Array
|
59
|
-
insert = Protocol::Insert.new(database.name, name, documents)
|
60
59
|
|
61
60
|
database.session.with(consistency: :strong) do |session|
|
62
|
-
session.
|
61
|
+
session.context.insert(database.name, name, documents)
|
63
62
|
end
|
64
|
-
|
65
63
|
end
|
66
64
|
end
|
67
65
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "timeout"
|
2
|
+
|
3
|
+
module Moped
|
4
|
+
class Connection
|
5
|
+
|
6
|
+
class TCPSocket < ::TCPSocket
|
7
|
+
def self.connect(host, port, timeout)
|
8
|
+
Timeout::timeout(timeout) do
|
9
|
+
new(host, port).tap do |sock|
|
10
|
+
sock.set_encoding 'binary'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def alive?
|
16
|
+
if Kernel::select([self], nil, nil, 0)
|
17
|
+
!eof? rescue false
|
18
|
+
else
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def write(*args)
|
24
|
+
raise Errors::ConnectionFailure, "Socket connection was closed by remote host" unless alive?
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@sock = nil
|
31
|
+
@request_id = 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def connect(host, port, timeout)
|
35
|
+
@sock = TCPSocket.connect host, port, timeout
|
36
|
+
end
|
37
|
+
|
38
|
+
def alive?
|
39
|
+
connected? ? @sock.alive? : false
|
40
|
+
end
|
41
|
+
|
42
|
+
def connected?
|
43
|
+
!!@sock
|
44
|
+
end
|
45
|
+
|
46
|
+
def disconnect
|
47
|
+
@sock.close
|
48
|
+
rescue
|
49
|
+
ensure
|
50
|
+
@sock = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def write(operations)
|
54
|
+
buf = ""
|
55
|
+
|
56
|
+
operations.each do |operation|
|
57
|
+
operation.request_id = (@request_id += 1)
|
58
|
+
operation.serialize(buf)
|
59
|
+
end
|
60
|
+
|
61
|
+
@sock.write buf
|
62
|
+
end
|
63
|
+
|
64
|
+
def receive_replies(operations)
|
65
|
+
operations.map do |operation|
|
66
|
+
read if operation.is_a?(Protocol::Query) || operation.is_a?(Protocol::GetMore)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def read
|
71
|
+
reply = Protocol::Reply.allocate
|
72
|
+
|
73
|
+
reply.length,
|
74
|
+
reply.request_id,
|
75
|
+
reply.response_to,
|
76
|
+
reply.op_code,
|
77
|
+
reply.flags,
|
78
|
+
reply.cursor_id,
|
79
|
+
reply.offset,
|
80
|
+
reply.count = @sock.read(36).unpack('l5<q<l2<')
|
81
|
+
|
82
|
+
if reply.count == 0
|
83
|
+
reply.documents = []
|
84
|
+
else
|
85
|
+
buffer = StringIO.new(@sock.read(reply.length - 36))
|
86
|
+
|
87
|
+
reply.documents = reply.count.times.map do
|
88
|
+
BSON::Document.deserialize(buffer)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
reply
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/moped/cursor.rb
CHANGED
@@ -10,50 +10,69 @@ module Moped
|
|
10
10
|
|
11
11
|
def initialize(session, query_operation)
|
12
12
|
@session = session
|
13
|
-
@query_op = query_operation.dup
|
14
13
|
|
15
|
-
@
|
16
|
-
|
17
|
-
|
18
|
-
0,
|
19
|
-
@query_op.limit
|
20
|
-
)
|
14
|
+
@database = query_operation.database
|
15
|
+
@collection = query_operation.collection
|
16
|
+
@selector = query_operation.selector
|
21
17
|
|
22
|
-
@
|
18
|
+
@cursor_id = 0
|
19
|
+
@limit = query_operation.limit
|
20
|
+
@limited = @limit > 0
|
21
|
+
|
22
|
+
@options = {
|
23
|
+
request_id: query_operation.request_id,
|
24
|
+
flags: query_operation.flags,
|
25
|
+
limit: query_operation.limit,
|
26
|
+
skip: query_operation.skip,
|
27
|
+
fields: query_operation.fields
|
28
|
+
}
|
23
29
|
end
|
24
30
|
|
25
31
|
def each
|
26
|
-
documents =
|
32
|
+
documents = load
|
27
33
|
documents.each { |doc| yield doc }
|
28
34
|
|
29
35
|
while more?
|
30
|
-
return kill if limited? && @
|
36
|
+
return kill if limited? && @limit <= 0
|
31
37
|
|
32
|
-
documents =
|
38
|
+
documents = get_more
|
33
39
|
documents.each { |doc| yield doc }
|
34
40
|
end
|
35
41
|
end
|
36
42
|
|
37
|
-
def
|
38
|
-
|
43
|
+
def load
|
44
|
+
consistency = session.consistency
|
45
|
+
@options[:flags] |= [:slave_ok] if consistency == :eventual
|
46
|
+
|
47
|
+
reply, @node = session.context.with_node do |node|
|
48
|
+
[node.query(@database, @collection, @selector, @options), node]
|
49
|
+
end
|
39
50
|
|
40
|
-
@
|
41
|
-
@
|
42
|
-
@kill_cursor_op.cursor_ids = [reply.cursor_id]
|
51
|
+
@limit -= reply.count if limited?
|
52
|
+
@cursor_id = reply.cursor_id
|
43
53
|
|
44
54
|
reply.documents
|
45
55
|
end
|
46
56
|
|
47
57
|
def limited?
|
48
|
-
@
|
58
|
+
@limited
|
49
59
|
end
|
50
60
|
|
51
61
|
def more?
|
52
|
-
@
|
62
|
+
@cursor_id != 0
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_more
|
66
|
+
reply = @node.get_more @database, @collection, @cursor_id, @limit
|
67
|
+
|
68
|
+
@limit -= reply.count if limited?
|
69
|
+
@cursor_id = reply.cursor_id
|
70
|
+
|
71
|
+
reply.documents
|
53
72
|
end
|
54
73
|
|
55
74
|
def kill
|
56
|
-
|
75
|
+
@node.kill_cursors [@cursor_id]
|
57
76
|
end
|
58
77
|
end
|
59
78
|
|
data/lib/moped/database.rb
CHANGED
@@ -29,7 +29,9 @@ module Moped
|
|
29
29
|
|
30
30
|
# Drop the database.
|
31
31
|
def drop
|
32
|
-
|
32
|
+
session.with(consistency: :strong) do |session|
|
33
|
+
session.context.command name, dropDatabase: 1
|
34
|
+
end
|
33
35
|
end
|
34
36
|
|
35
37
|
# Log in with +username+ and +password+ on the current database.
|
@@ -37,12 +39,12 @@ module Moped
|
|
37
39
|
# @param [String] username the username
|
38
40
|
# @param [String] password the password
|
39
41
|
def login(username, password)
|
40
|
-
session.
|
42
|
+
session.context.login(name, username, password)
|
41
43
|
end
|
42
44
|
|
43
45
|
# Log out from the current database.
|
44
46
|
def logout
|
45
|
-
session.
|
47
|
+
session.context.logout(name)
|
46
48
|
end
|
47
49
|
|
48
50
|
# Run +command+ on the database.
|
@@ -54,17 +56,7 @@ module Moped
|
|
54
56
|
# @param [Hash] command the command to run
|
55
57
|
# @return [Hash] the result of the command
|
56
58
|
def command(command)
|
57
|
-
|
58
|
-
|
59
|
-
result = session.with(consistency: :strong) do |session|
|
60
|
-
session.simple_query(operation)
|
61
|
-
end
|
62
|
-
|
63
|
-
raise Errors::OperationFailure.new(
|
64
|
-
operation, result
|
65
|
-
) unless result["ok"] == 1.0
|
66
|
-
|
67
|
-
result
|
59
|
+
session.context.command name, command
|
68
60
|
end
|
69
61
|
|
70
62
|
# @param [Symbol, String] collection the collection name
|
data/lib/moped/errors.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
module Moped
|
2
|
-
|
3
|
-
# The namespace for all errors generated by Moped.
|
4
2
|
module Errors
|
5
3
|
|
6
4
|
# Mongo's exceptions are sparsely documented, but this is the most accurate
|
@@ -10,14 +8,12 @@ module Moped
|
|
10
8
|
# Generic error class for exceptions related to connection failures.
|
11
9
|
class ConnectionFailure < StandardError; end
|
12
10
|
|
11
|
+
# Tag applied to unhandled exceptions on a node.
|
12
|
+
module SocketError end
|
13
|
+
|
13
14
|
# Generic error class for exceptions generated on the remote MongoDB
|
14
15
|
# server.
|
15
|
-
class MongoError < StandardError
|
16
|
-
|
17
|
-
# Exception class for exceptions generated as a direct result of an
|
18
|
-
# operation, such as a failed insert or an invalid command.
|
19
|
-
class OperationFailure < MongoError
|
20
|
-
|
16
|
+
class MongoError < StandardError
|
21
17
|
# @return the command that generated the error
|
22
18
|
attr_reader :command
|
23
19
|
|
@@ -53,9 +49,28 @@ module Moped
|
|
53
49
|
end
|
54
50
|
end
|
55
51
|
|
56
|
-
#
|
57
|
-
#
|
58
|
-
class
|
52
|
+
# Exception class for exceptions generated as a direct result of an
|
53
|
+
# operation, such as a failed insert or an invalid command.
|
54
|
+
class OperationFailure < MongoError; end
|
55
|
+
|
56
|
+
# Exception raised on invalid queries.
|
57
|
+
class QueryFailure < MongoError; end
|
58
|
+
|
59
|
+
# Exception raised when authentication fails.
|
60
|
+
class AuthenticationFailure < MongoError; end
|
61
|
+
|
62
|
+
# Raised when providing an invalid string from an object id.
|
63
|
+
class InvalidObjectId < StandardError
|
64
|
+
def initialize(string)
|
65
|
+
super("'#{string}' is not a valid object id.")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @api private
|
70
|
+
#
|
71
|
+
# Internal exception raised by Node#ensure_primary and captured by
|
72
|
+
# Cluster#with_primary.
|
73
|
+
class ReplicaSetReconfigured < StandardError; end
|
59
74
|
|
60
75
|
end
|
61
76
|
end
|
data/lib/moped/node.rb
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
module Moped
|
2
|
+
class Node
|
3
|
+
|
4
|
+
attr_reader :address
|
5
|
+
attr_reader :resolved_address
|
6
|
+
attr_reader :ip_address
|
7
|
+
attr_reader :port
|
8
|
+
|
9
|
+
attr_reader :peers
|
10
|
+
attr_reader :timeout
|
11
|
+
|
12
|
+
def initialize(address)
|
13
|
+
@address = address
|
14
|
+
|
15
|
+
host, port = address.split(":")
|
16
|
+
@ip_address = ::Socket.getaddrinfo(host, nil, ::Socket::AF_INET, ::Socket::SOCK_STREAM).first[3]
|
17
|
+
@port = port.to_i
|
18
|
+
@resolved_address = "#{@ip_address}:#{@port}"
|
19
|
+
|
20
|
+
@timeout = 5
|
21
|
+
end
|
22
|
+
|
23
|
+
def command(database, cmd, options = {})
|
24
|
+
operation = Protocol::Command.new(database, cmd, options)
|
25
|
+
|
26
|
+
process(operation) do |reply|
|
27
|
+
result = reply.documents[0]
|
28
|
+
|
29
|
+
raise Errors::OperationFailure.new(
|
30
|
+
operation, result
|
31
|
+
) if result["ok"] != 1 || result["err"] || result["errmsg"]
|
32
|
+
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def kill_cursors(cursor_ids)
|
38
|
+
process Protocol::KillCursors.new(cursor_ids)
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_more(database, collection, cursor_id, limit)
|
42
|
+
process Protocol::GetMore.new(database, collection, cursor_id, limit)
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove(database, collection, selector, options = {})
|
46
|
+
process Protocol::Delete.new(database, collection, selector, options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def update(database, collection, selector, change, options = {})
|
50
|
+
process Protocol::Update.new(database, collection, selector, change, options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def insert(database, collection, documents)
|
54
|
+
process Protocol::Insert.new(database, collection, documents)
|
55
|
+
end
|
56
|
+
|
57
|
+
def query(database, collection, selector, options = {})
|
58
|
+
operation = Protocol::Query.new(database, collection, selector, options)
|
59
|
+
|
60
|
+
process operation do |reply|
|
61
|
+
if reply.flags.include? :query_failure
|
62
|
+
raise Errors::QueryFailure.new(operation, reply.documents.first)
|
63
|
+
end
|
64
|
+
|
65
|
+
reply
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [true/false] whether the node needs to be refreshed.
|
70
|
+
def needs_refresh?(time)
|
71
|
+
!@refreshed_at || @refreshed_at < time
|
72
|
+
end
|
73
|
+
|
74
|
+
def primary?
|
75
|
+
@primary
|
76
|
+
end
|
77
|
+
|
78
|
+
def secondary?
|
79
|
+
@secondary
|
80
|
+
end
|
81
|
+
|
82
|
+
# Refresh information about the node, such as it's status in the replica
|
83
|
+
# set and it's known peers.
|
84
|
+
#
|
85
|
+
# Returns nothing.
|
86
|
+
# Raises Errors::ConnectionFailure if the node cannot be reached
|
87
|
+
# Raises Errors::ReplicaSetReconfigured if the node is no longer a primary node and
|
88
|
+
# refresh was called within an +#ensure_primary+ block.
|
89
|
+
def refresh
|
90
|
+
info = command "admin", ismaster: 1
|
91
|
+
|
92
|
+
@refreshed_at = Time.now
|
93
|
+
primary = true if info["ismaster"]
|
94
|
+
secondary = true if info["secondary"]
|
95
|
+
|
96
|
+
peers = []
|
97
|
+
peers.push info["primary"] if info["primary"]
|
98
|
+
peers.concat info["hosts"] if info["hosts"]
|
99
|
+
peers.concat info["passives"] if info["passives"]
|
100
|
+
peers.concat info["arbiters"] if info["arbiters"]
|
101
|
+
|
102
|
+
@peers = peers.map { |peer| Node.new(peer) }
|
103
|
+
@primary, @secondary = primary, secondary
|
104
|
+
|
105
|
+
if !primary && Threaded.executing?(:ensure_primary)
|
106
|
+
raise Errors::ReplicaSetReconfigured, "#{inspect} is no longer the primary node."
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_reader :down_at
|
111
|
+
|
112
|
+
def down?
|
113
|
+
@down_at
|
114
|
+
end
|
115
|
+
|
116
|
+
# Set a flag on the node for the duration of provided block so that an
|
117
|
+
# exception is raised if the node is no longer the primary node.
|
118
|
+
#
|
119
|
+
# Returns nothing.
|
120
|
+
def ensure_primary
|
121
|
+
Threaded.begin :ensure_primary
|
122
|
+
yield
|
123
|
+
ensure
|
124
|
+
Threaded.end :ensure_primary
|
125
|
+
end
|
126
|
+
|
127
|
+
# Yields the block if a connection can be established, retrying when a
|
128
|
+
# connection error is raised.
|
129
|
+
#
|
130
|
+
# @raises ConnectionFailure when a connection cannot be established.
|
131
|
+
def ensure_connected
|
132
|
+
# Don't run the reconnection login if we're already inside an
|
133
|
+
# +ensure_connected+ block.
|
134
|
+
return yield if Threaded.executing? :connection
|
135
|
+
Threaded.begin :connection
|
136
|
+
|
137
|
+
retry_on_failure = true
|
138
|
+
|
139
|
+
begin
|
140
|
+
connect unless connected?
|
141
|
+
yield
|
142
|
+
rescue Errors::ReplicaSetReconfigured
|
143
|
+
# Someone else wrapped this in an #ensure_primary block, so let the
|
144
|
+
# reconfiguration exception bubble up.
|
145
|
+
raise
|
146
|
+
rescue Errors::OperationFailure, Errors::AuthenticationFailure, Errors::QueryFailure
|
147
|
+
# These exceptions are "expected" in the normal course of events, and
|
148
|
+
# don't necessitate disconnecting.
|
149
|
+
raise
|
150
|
+
rescue Errors::ConnectionFailure
|
151
|
+
disconnect
|
152
|
+
|
153
|
+
if retry_on_failure
|
154
|
+
# Maybe there was a hiccup -- try reconnecting one more time
|
155
|
+
retry_on_failure = false
|
156
|
+
retry
|
157
|
+
else
|
158
|
+
# Nope, we failed to connect twice. Flag the node as down and re-raise
|
159
|
+
# the exception.
|
160
|
+
down!
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
rescue
|
164
|
+
# Looks like we got an unexpected error, so we'll clean up the connection
|
165
|
+
# and re-raise the exception.
|
166
|
+
disconnect
|
167
|
+
raise $!.extend(Errors::SocketError)
|
168
|
+
end
|
169
|
+
ensure
|
170
|
+
Threaded.end :connection
|
171
|
+
end
|
172
|
+
|
173
|
+
def pipeline
|
174
|
+
Threaded.begin :pipeline
|
175
|
+
|
176
|
+
begin
|
177
|
+
yield
|
178
|
+
ensure
|
179
|
+
Threaded.end :pipeline
|
180
|
+
end
|
181
|
+
|
182
|
+
flush unless Threaded.executing? :pipeline
|
183
|
+
end
|
184
|
+
|
185
|
+
def apply_auth(credentials)
|
186
|
+
unless auth == credentials
|
187
|
+
logouts = auth.keys - credentials.keys
|
188
|
+
|
189
|
+
logouts.each do |database|
|
190
|
+
logout database
|
191
|
+
end
|
192
|
+
|
193
|
+
credentials.each do |database, (username, password)|
|
194
|
+
login(database, username, password) unless auth[database] == [username, password]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
def ==(other)
|
202
|
+
resolved_address == other.resolved_address
|
203
|
+
end
|
204
|
+
alias eql? ==
|
205
|
+
|
206
|
+
def hash
|
207
|
+
[ip_address, port].hash
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
def auth
|
213
|
+
@auth ||= {}
|
214
|
+
end
|
215
|
+
|
216
|
+
def login(database, username, password)
|
217
|
+
getnonce = Protocol::Command.new(database, getnonce: 1)
|
218
|
+
connection.write [getnonce]
|
219
|
+
result = connection.read.documents.first
|
220
|
+
raise Errors::OperationFailure.new(getnonce, result) unless result["ok"] == 1
|
221
|
+
|
222
|
+
authenticate = Protocol::Commands::Authenticate.new(database, username, password, result["nonce"])
|
223
|
+
connection.write [authenticate]
|
224
|
+
result = connection.read.documents.first
|
225
|
+
raise Errors::AuthenticationFailure.new(authenticate, result) unless result["ok"] == 1
|
226
|
+
|
227
|
+
auth[database] = [username, password]
|
228
|
+
end
|
229
|
+
|
230
|
+
def logout(database)
|
231
|
+
command = Protocol::Command.new(database, logout: 1)
|
232
|
+
connection.write [command]
|
233
|
+
result = connection.read.documents.first
|
234
|
+
raise Errors::OperationFailure.new(command, result) unless result["ok"] == 1
|
235
|
+
|
236
|
+
auth.delete(database)
|
237
|
+
end
|
238
|
+
|
239
|
+
def initialize_copy(_)
|
240
|
+
@connection = nil
|
241
|
+
end
|
242
|
+
|
243
|
+
def connection
|
244
|
+
@connection ||= Connection.new
|
245
|
+
end
|
246
|
+
|
247
|
+
def disconnect
|
248
|
+
auth.clear
|
249
|
+
connection.disconnect
|
250
|
+
end
|
251
|
+
|
252
|
+
def connected?
|
253
|
+
connection.connected?
|
254
|
+
end
|
255
|
+
|
256
|
+
# Mark the node as down.
|
257
|
+
#
|
258
|
+
# Returns nothing.
|
259
|
+
def down!
|
260
|
+
@down_at = Time.new
|
261
|
+
|
262
|
+
disconnect
|
263
|
+
end
|
264
|
+
|
265
|
+
# Connect to the node.
|
266
|
+
#
|
267
|
+
# Returns nothing.
|
268
|
+
# Raises Moped::ConnectionError if the connection times out.
|
269
|
+
# Raises Moped::ConnectionError if the server is unavailable.
|
270
|
+
def connect
|
271
|
+
connection.connect ip_address, port, timeout
|
272
|
+
@down_at = nil
|
273
|
+
|
274
|
+
refresh
|
275
|
+
rescue Timeout::Error
|
276
|
+
raise Errors::ConnectionFailure, "Timed out connection to Mongo on #{address}"
|
277
|
+
rescue Errno::ECONNREFUSED
|
278
|
+
raise Errors::ConnectionFailure, "Could not connect to Mongo on #{address}"
|
279
|
+
end
|
280
|
+
|
281
|
+
def process(operation, &callback)
|
282
|
+
if Threaded.executing? :pipeline
|
283
|
+
queue.push [operation, callback]
|
284
|
+
else
|
285
|
+
flush([[operation, callback]])
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def queue
|
290
|
+
Threaded.stack(:pipelined_operations)
|
291
|
+
end
|
292
|
+
|
293
|
+
def flush(ops = queue)
|
294
|
+
operations, callbacks = ops.transpose
|
295
|
+
|
296
|
+
logging(operations) do
|
297
|
+
ensure_connected do
|
298
|
+
connection.write operations
|
299
|
+
replies = connection.receive_replies(operations)
|
300
|
+
|
301
|
+
replies.zip(callbacks).map do |reply, callback|
|
302
|
+
callback ? callback[reply] : reply
|
303
|
+
end.last
|
304
|
+
end
|
305
|
+
end
|
306
|
+
ensure
|
307
|
+
ops.clear
|
308
|
+
end
|
309
|
+
|
310
|
+
def logging(operations)
|
311
|
+
instrument_start = (logger = Moped.logger) && logger.debug? && Time.new
|
312
|
+
yield
|
313
|
+
ensure
|
314
|
+
log_operations(logger, operations, Time.new - instrument_start) if instrument_start && !$!
|
315
|
+
end
|
316
|
+
|
317
|
+
def log_operations(logger, ops, duration)
|
318
|
+
prefix = " MOPED: #{address} "
|
319
|
+
indent = " "*prefix.length
|
320
|
+
runtime = (" (%.1fms)" % duration)
|
321
|
+
|
322
|
+
if ops.length == 1
|
323
|
+
logger.debug prefix + ops.first.log_inspect + runtime
|
324
|
+
else
|
325
|
+
first, *middle, last = ops
|
326
|
+
|
327
|
+
logger.debug prefix + first.log_inspect
|
328
|
+
middle.each { |m| logger.debug indent + m.log_inspect }
|
329
|
+
logger.debug indent + last.log_inspect + runtime
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
end
|