rethinkdb 1.2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ # Copyright 2010-2012 RethinkDB, all rights reserved.
2
+ #TODO: toplevel doc
3
+ module RethinkDB
4
+ class RQL_Query; end
5
+ class Read_Query < RQL_Query # :nodoc:
6
+ end
7
+ class Write_Query < RQL_Query # :nodoc:
8
+ end
9
+ class Meta_Query < RQL_Query # :nodoc:
10
+ end
11
+
12
+ module Sequence; end
13
+ class JSON_Expression < Read_Query; include Sequence; end
14
+ class Single_Row_Selection < JSON_Expression; end
15
+ class Stream_Expression < Read_Query; include Sequence; end
16
+ class Multi_Row_Selection < Stream_Expression; end
17
+
18
+ class Database; end
19
+ class Table; end
20
+
21
+ class Connection; end
22
+ class Query_Results; include Enumerable; end
23
+
24
+ module RQL; end
25
+
26
+ # Shortcuts to easily build RQL queries.
27
+ module Shortcuts
28
+ # The only shortcut right now. May be used as a wrapper to create
29
+ # RQL values or as a shortcut to access function in the RQL
30
+ # namespace. For example, the following are equivalent:
31
+ # r.add(1, 2)
32
+ # RethinkDB::RQL.add(1, 2)
33
+ # r(1) + r(2)
34
+ # r(3)
35
+ def r(*args)
36
+ (args == [] ) ? RethinkDB::RQL : RQL.expr(*args)
37
+ end
38
+ end
39
+ end
data/lib/bt.rb ADDED
@@ -0,0 +1,148 @@
1
+ # Copyright 2010-2012 RethinkDB, all rights reserved.
2
+ module RethinkDB
3
+ module BT_Mixin
4
+ attr_accessor :highlight, :line
5
+
6
+ def sanitize_context(context)
7
+ if __FILE__ =~ /^(.*\/)[^\/]+.rb$/
8
+ prefix = $1;
9
+ context.reject{|x| x =~ /^#{prefix}/}
10
+ else
11
+ context
12
+ end
13
+ end
14
+
15
+ def format_highlights(str)
16
+ str.chars.map{|x| x == "\000" ? "^" : " "}.join.rstrip
17
+ end
18
+
19
+ def force_raise(query, debug=false)
20
+ obj = query.run
21
+ #maybe_reformat_err(query, obj);
22
+ PP.pp obj if debug
23
+ if obj.class == Query_Results
24
+ obj = obj.to_a
25
+ elsif obj.class == Hash
26
+ raise RuntimeError,obj['first_error'] if obj['first_error']
27
+ end
28
+ obj
29
+ end
30
+
31
+ def maybe_reformat_err(query, obj)
32
+ if obj.class == Hash && (err = obj['first_error'])
33
+ obj['first_error'] = format_err(query, err)
34
+ end
35
+ end
36
+
37
+ def format_err(query, err)
38
+ parts = err.split("\nBacktrace:\n")
39
+ errline = parts[0] || ""
40
+ bt = (parts[1] && parts[1].split("\n")) || []
41
+ errline+"\n"+query.print_backtrace(bt)
42
+ end
43
+
44
+ def set(sym,val)
45
+ @map = {} if not @map
46
+ res, @map[sym] = @map[sym], val
47
+ return res
48
+ end
49
+ def consume sym
50
+ @map = {} if not @map
51
+ res, @map[sym] = @map[sym], nil
52
+ return res.nil? ? 0 : res
53
+ end
54
+ def expand arg
55
+ case arg
56
+ when 'attr' then []
57
+ when 'attrname' then []
58
+ when 'base' then [1+consume(:reduce)]
59
+ when 'body' then [4+consume(:reduce)]
60
+ when 'datacenter' then []
61
+ when 'db_name' then []
62
+ when 'expr' then [2]
63
+ when 'false' then [3]
64
+ when 'group_mapping' then [1, 1, 1]
65
+ when 'key' then [3]
66
+ when 'keyname' then []
67
+ when 'lowerbound' then [1, 2]
68
+ when 'mapping' then [1, 2]
69
+ when 'modify_map' then [2, 1]
70
+ when 'order_by' then []
71
+ when 'point_map' then [4, 1]
72
+ when 'predicate' then [1, 2]
73
+ when 'reduce' then [1]
74
+ when 'reduction' then set(:reduce, -1); [1, 3]
75
+ when 'stream' then [1]
76
+ when 'table_name' then []
77
+ when 'table_ref' then []
78
+ when 'test' then [1]
79
+ when 'true' then [2]
80
+ when 'upperbound' then [1, 3]
81
+ when 'value_mapping' then [1, 2, 1]
82
+ when 'view' then [1]
83
+ when /arg:([0-9]+)/ then [2, $1.to_i]
84
+ when /attrs:([0-9]+)/ then []
85
+ when /elem:([0-9]+)/ then [$1.to_i+1]
86
+ when /bind:(.+)$/ then [1, $1]
87
+ when /key:(.+)$/ then [1..-1, $1]
88
+ when /query:([0-9]+)/ then [3, $1.to_i]
89
+ when /term:([0-9]+)/ then [2, $1.to_i]
90
+ else [:error]
91
+ end
92
+ end
93
+
94
+ def recur(arr, lst, val)
95
+ if lst[0].class == String
96
+ entry = arr[0..-1].find{|x| x[0].to_s == lst[0]}
97
+ raise RuntimeError if not entry
98
+ mark(entry[1], lst[1..-1], val)
99
+ elsif lst[0].class == Fixnum || lst[0].class == Range
100
+ mark(arr[lst[0]], lst[1..-1], val) if lst != []
101
+ else
102
+ raise RuntimeError
103
+ end
104
+ end
105
+ def mark(query, lst, val)
106
+ #PP.pp [lst, query.class, query]
107
+ if query.class == Array
108
+ recur(query, lst, val) if lst != []
109
+ elsif query.kind_of? RQL_Query
110
+ if lst == []
111
+ @line = sanitize_context(query.context)[0]
112
+ val,query.marked = query.marked, val
113
+ val
114
+ else
115
+ recur(query.body, lst, val)
116
+ end
117
+ else raise RuntimeError
118
+ end
119
+ end
120
+
121
+ def with_marked_error(query, bt)
122
+ @map = {}
123
+ bt = bt.map{|x| expand x}.flatten
124
+ raise RuntimeError if bt.any?{|x| x == :error}
125
+ old = mark(query, bt, :error)
126
+ res = yield
127
+ mark(query, bt, old)
128
+ return res
129
+ end
130
+
131
+ def with_highlight
132
+ @highlight = true
133
+ str = yield
134
+ @highlight = false
135
+ str.chars.map{|x| x == "\000" ? "^" : " "}.join.rstrip
136
+ end
137
+
138
+ def alt_inspect(query, &block)
139
+ class << query
140
+ attr_accessor :str_block
141
+ def inspect; real_inspect({:str => @str_block.call(self)}); end
142
+ end
143
+ query.str_block = block
144
+ query
145
+ end
146
+ end
147
+ module BT; extend BT_Mixin; end
148
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright 2010-2012 RethinkDB, all rights reserved.
2
+ module RethinkDB
3
+ # Data collectors are ways of aggregating data (for use with
4
+ # Sequence#groupby). You can define your own; all they are is a hash
5
+ # containing a <b>+mapping+</b> to be applied to row, a
6
+ # <b>+base+</b>/<b>+reduction+</b> to reduce over the mappings, and an
7
+ # optional <b>+finalizer+</b> to call on the output of the reduction. You can
8
+ # expand the builtin data collectors below for examples.
9
+ #
10
+ # All of the builtin collectors can also be accessed using the <b>+r+</b>
11
+ # shortcut (like <b>+r.sum+</b>).
12
+ module Data_Collectors
13
+ # Count the number of rows in each group.
14
+ def self.count
15
+ { :mapping => lambda{|row| 1},
16
+ :base => 0,
17
+ :reduction => lambda{|acc, val| acc + val} }
18
+ end
19
+
20
+ # Sum a particular attribute of the rows in each group.
21
+ def self.sum(attr)
22
+ { :mapping => lambda{|row| row[attr]},
23
+ :base => 0,
24
+ :reduction => lambda{|acc, val| acc + val} }
25
+ end
26
+
27
+ # Average a particular attribute of the rows in each group.
28
+ def self.avg(attr)
29
+ { :mapping => lambda{|row| [row[attr], 1]},
30
+ :base => [0, 0],
31
+ :reduction => lambda{|acc, val| [acc[0] + val[0], acc[1] + val[1]]},
32
+ :finalizer => lambda{|res| res[0] / res[1]} }
33
+ end
34
+ end
35
+ end
data/lib/jsons.rb ADDED
@@ -0,0 +1,136 @@
1
+ # Copyright 2010-2012 RethinkDB, all rights reserved.
2
+ module RethinkDB
3
+ # A query returning a JSON expression. Most of the functions that you
4
+ # can run on a JSON object are found in RethinkDB::RQL and accessed
5
+ # with the <b>+r+</b> shortcut. For convenience, may of the
6
+ # functions in Rethinkdb::RQL can be run as if they were instance
7
+ # methods of JSON_Expression. For example, the following are
8
+ # equivalent:
9
+ # r.add(1, 2)
10
+ # r(1).add(2)
11
+ # r(3)
12
+ #
13
+ # Running a JSON_Expression query will return a literal Ruby
14
+ # representation of the resulting JSON, rather than a stream or a
15
+ # string. For example, the following are equivalent:
16
+ # r.add(1,2).run
17
+ # 3
18
+ # As are:
19
+ # r({:a => 1, :b => 2}).pick(:a).run
20
+ # {'a' => 1}
21
+ # (Note that the symbol keys were coerced into string keys in the
22
+ # object. JSON doesn't distinguish between symbols and strings.)
23
+ class JSON_Expression
24
+ # If <b>+ind+</b> is a symbol or a string, gets the corresponding
25
+ # attribute of an object. For example, the following are equivalent:
26
+ # r({:id => 1})[:id]
27
+ # r({:id => 1})['id']
28
+ # r(1)
29
+ # Otherwise, if <b>+ind+</b> is a number or a range, invokes RethinkDB::Sequence#[]
30
+ def [](ind)
31
+ if ind.class == Symbol || ind.class == String
32
+ BT.alt_inspect(JSON_Expression.new [:call, [:getattr, ind], [self]]) {
33
+ "#{self.inspect}[#{ind.inspect}]"
34
+ }
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ # Append a single element to an array. The following are equivalent:
41
+ # r([1,2,3,4])
42
+ # r([1,2,3]).append(4)
43
+ def append(el)
44
+ JSON_Expression.new [:call, [:arrayappend], [self, S.r(el)]]
45
+ end
46
+
47
+ # Get an attribute of a JSON object (the same as
48
+ # JSON_Expression#[]). The following are equivalent.
49
+ # r({:id => 1}).getattr(:id)
50
+ # r({:id => 1})[:id]
51
+ # r(1)
52
+ def getattr(attrname)
53
+ JSON_Expression.new [:call, [:getattr, attrname], [self]]
54
+ end
55
+
56
+ # Check whether a JSON object has a particular attribute. The
57
+ # following are equivalent:
58
+ # r({:id => 1}).contains(:id)
59
+ # r(true)
60
+ def contains(attrname)
61
+ JSON_Expression.new [:call, [:contains, attrname], [self]]
62
+ end
63
+
64
+ # Construct a JSON object that has a subset of the attributes of
65
+ # another JSON object by specifying which to keep. The following are equivalent:
66
+ # r({:a => 1, :b => 2, :c => 3}).pick(:a, :c)
67
+ # r({:a => 1, :c => 3})
68
+ def pick(*attrnames)
69
+ JSON_Expression.new [:call, [:pick, *attrnames], [self]]
70
+ end
71
+
72
+ # Construct a JSON object that has a subset of the attributes of
73
+ # another JSON object by specifying which to drop. The following are equivalent:
74
+ # r({:a => 1, :b => 2, :c => 3}).without(:a, :c)
75
+ # r({:b => 2})
76
+ def unpick(*attrnames)
77
+ JSON_Expression.new [:call, [:unpick, *attrnames], [self]]
78
+ end
79
+
80
+ # Convert from an array to a stream. While most sequence functions are polymorphic
81
+ # and handle both arrays and streams, when arrays or streams need to be
82
+ # combined (e.g. via <b>+union+</b>) you need to explicitly convert between
83
+ # the types. This is mostly for safety, but also because which type you're
84
+ # working with affects error handling. The following are equivalent:
85
+ # r([1,2,3]).arraytostream
86
+ # r([1,2,3]).to_stream
87
+ def array_to_stream(); Stream_Expression.new [:call, [:array_to_stream], [self]]; end
88
+
89
+ # Prefix numeric -. The following are equivalent:
90
+ # -r(1)
91
+ # r(-1)
92
+ def -@; JSON_Expression.new [:call, [:subtract], [self]]; end
93
+ # Prefix numeric +. The following are equivalent:
94
+ # +r(-1)
95
+ # r(-1)
96
+ def +@; JSON_Expression.new [:call, [:add], [self]]; end
97
+ end
98
+
99
+ # A query representing a variable. Produced by e.g. RQL::letvar. This
100
+ # is its own class because it needs to behave correctly when spliced
101
+ # into a string (see RQL::js).
102
+ class Var_Expression < JSON_Expression
103
+ # Convert from an RQL query representing a variable to the name of that
104
+ # variable. Used e.g. in constructing javascript functions.
105
+ def to_s
106
+ if @body.class == Array and @body[0] == :var
107
+ then @body[1]
108
+ else raise TypeError, 'Can only call to_s on RQL_Queries representing variables.'
109
+ end
110
+ end
111
+ end
112
+
113
+ # Like Multi_Row_Selection, but for a single row. E.g.:
114
+ # table.get(0)
115
+ # yields a Single_Row_Selection
116
+ class Single_Row_Selection < JSON_Expression
117
+ # Analagous to Multi_Row_Selection#update
118
+ def update(variant=nil)
119
+ S.with_var {|vname,v|
120
+ Write_Query.new [:pointupdate, *(@body[1..-1] + [[vname, S.r(yield(v))]])]
121
+ }.apply_variant(variant)
122
+ end
123
+
124
+ # Analagous to Multi_Row_Selection#mutate
125
+ def replace(variant=nil)
126
+ S.with_var {|vname,v|
127
+ Write_Query.new [:pointreplace, *(@body[1..-1] + [[vname, S.r(yield(v))]])]
128
+ }.apply_variant(variant)
129
+ end
130
+
131
+ # Analagous to Multi_Row_Selection#delete
132
+ def delete
133
+ Write_Query.new [:pointdelete, *(@body[1..-1])]
134
+ end
135
+ end
136
+ end
data/lib/net.rb ADDED
@@ -0,0 +1,275 @@
1
+ # Copyright 2010-2012 RethinkDB, all rights reserved.
2
+ require 'socket'
3
+ require 'thread'
4
+ require 'json'
5
+
6
+ module RethinkDB
7
+ module Faux_Abort # :nodoc:
8
+ class Abort # :nodoc:
9
+ end
10
+ end
11
+ # The result of a calling Connection#run on a query that returns a stream.
12
+ # This class is <b>+Enumerable+</b>. You can find documentation on Enumerable
13
+ # classes at http://ruby-doc.org/core-1.9.3/Enumerable.html .
14
+ #
15
+ # <b>NOTE:</b> unlike most enumerable objects, you can only iterate over Query
16
+ # results once. The results are fetched lazily from the server in chunks
17
+ # of 1000. If you need to access values multiple times, use the <b>+to_a+</b>
18
+ # instance method to get an Array, and then work with that.
19
+ class Query_Results
20
+ def out_of_date # :nodoc:
21
+ @conn.conn_id != @conn_id
22
+ end
23
+
24
+ def inspect # :nodoc:
25
+ state = @run ? "(exhausted)" : "(enumerable)"
26
+ extra = out_of_date ? " (Connection #{@conn.inspect} reset!)" : ""
27
+ "#<RethinkDB::Query_Results:#{self.object_id} #{state}#{extra}: #{@query.inspect}>"
28
+ end
29
+
30
+ def initialize(query, connection, token) # :nodoc:
31
+ @query = query
32
+ @run = false
33
+ @conn_id = connection.conn_id
34
+ @conn = connection
35
+ @token = token
36
+ end
37
+
38
+ def each (&block) # :nodoc:
39
+ raise RuntimeError, "Can only iterate over Query_Results once!" if @run
40
+ raise RuntimeError, "Connection has been reset!" if out_of_date
41
+ @conn.token_iter(@query, @token, &block)
42
+ @run = true
43
+ return self
44
+ end
45
+ end
46
+
47
+ # TODO: Make sure tokens don't overflow.
48
+
49
+ # A connection to the RethinkDB
50
+ # cluster. You need to create at least one connection before you can run
51
+ # queries. After creating a connection <b>+conn+</b> and constructing a
52
+ # query <b>+q+</b>, the following are equivalent:
53
+ # conn.run(q)
54
+ # q.run
55
+ # (This is because by default, invoking the <b>+run+</b> instance method on a
56
+ # query runs it on the most-recently-opened connection.)
57
+ #
58
+ # ==== Attributes
59
+ # * +default_db+ - The current default database (can be set with Connection#use). Defaults to 'test'.
60
+ # * +conn_id+ - You probably don't care about this. Used in some advanced situations where you care about whether a connection object has reconnected.
61
+ class Connection
62
+ def inspect # :nodoc:
63
+ properties = "(#{@host}:#{@port}) (Default Database: '#{@default_db}')"
64
+ state = @listener ? "(listening)" : "(closed)"
65
+ "#<RethinkDB::Connection:#{self.object_id} #{properties} #{state}>"
66
+ end
67
+
68
+ @@last = nil
69
+ @@magic_number = 0xaf61ba35
70
+
71
+ def debug_socket # :nodoc:
72
+ @socket;
73
+ end
74
+
75
+ # Reconnect to the server. This will interrupt all queries on the
76
+ # server and invalidate all outstanding enumerables on the client.
77
+ def reconnect
78
+ @socket.close if @socket
79
+ @socket = TCPSocket.open(@host, @port)
80
+ @waiters = {}
81
+ @data = {}
82
+ @mutex = Mutex.new
83
+ @conn_id += 1
84
+ start_listener
85
+ self
86
+ end
87
+
88
+ def dispatch msg # :nodoc:
89
+ PP.pp msg if $DEBUG
90
+ payload = msg.serialize_to_string
91
+ #File.open('sexp_payloads.txt', 'a') {|f| f.write(payload.inspect+"\n")}
92
+ packet = [payload.length].pack('L<') + payload
93
+ @socket.send(packet, 0)
94
+ return msg.token
95
+ end
96
+
97
+ def wait token # :nodoc:
98
+ res = nil
99
+ raise RuntimeError, "Connection closed by server!" if not @listener
100
+ @mutex.synchronize do
101
+ (@waiters[token] = ConditionVariable.new).wait(@mutex) if not @data[token]
102
+ res = @data.delete token if @data[token]
103
+ end
104
+ raise RuntimeError, "Connection closed by server!" if !@listener or !res
105
+ return res
106
+ end
107
+
108
+ def continue token # :nodoc:
109
+ msg = Query.new
110
+ msg.type = Query::QueryType::CONTINUE
111
+ msg.token = token
112
+ dispatch msg
113
+ end
114
+
115
+ def error(query,protob,err=RuntimeError) # :nodoc:
116
+ bt = protob.backtrace ? protob.backtrace.frame : []
117
+ #PP.pp bt
118
+ msg = "RQL: #{protob.error_message}\n" + query.print_backtrace(bt)
119
+ raise err,msg
120
+ end
121
+
122
+ def token_iter(query, token) # :nodoc:
123
+ done = false
124
+ data = []
125
+ loop do
126
+ if (data == [])
127
+ break if done
128
+
129
+ begin
130
+ protob = wait token
131
+ rescue @abort_module::Abort => e
132
+ print "\nAborting query and reconnecting...\n"
133
+ self.reconnect()
134
+ raise e
135
+ end
136
+
137
+ case protob.status_code
138
+ when Response::StatusCode::SUCCESS_JSON then
139
+ yield JSON.parse('['+protob.response[0]+']')[0]
140
+ return false
141
+ when Response::StatusCode::SUCCESS_PARTIAL then
142
+ continue token
143
+ data.push *protob.response
144
+ when Response::StatusCode::SUCCESS_STREAM then
145
+ data.push *protob.response
146
+ done = true
147
+ when Response::StatusCode::SUCCESS_EMPTY then
148
+ return false
149
+ when Response::StatusCode::BAD_QUERY then error query,protob,ArgumentError
150
+ when Response::StatusCode::RUNTIME_ERROR then error query,protob,RuntimeError
151
+ else error query,protob
152
+ end
153
+ end
154
+ #yield JSON.parse("["+data.shift+"]")[0] if data != []
155
+ yield JSON.parse('['+data.shift+']')[0] if data != []
156
+ #yield data.shift if data != []
157
+ end
158
+ return true
159
+ end
160
+
161
+ # Create a new connection to <b>+host+</b> on port <b>+port+</b>.
162
+ # You may also optionally provide a default database to use when
163
+ # running queries over that connection. Example:
164
+ # c = Connection.new('localhost', 28015, 'default_db')
165
+ def initialize(host='localhost', port=28015, default_db='test')
166
+ begin
167
+ @abort_module = ::IRB
168
+ rescue NameError => e
169
+ @abort_module = Faux_Abort
170
+ end
171
+ @@last = self
172
+ @host = host
173
+ @port = port
174
+ @default_db = default_db
175
+ @conn_id = 0
176
+ reconnect
177
+ end
178
+ attr_reader :default_db, :conn_id
179
+
180
+ # Change the default database of a connection.
181
+ def use(new_default_db)
182
+ @default_db = new_default_db
183
+ end
184
+
185
+ # Run a query over the connection. If you run a query that returns a JSON
186
+ # expression (e.g. a reduce), you get back that JSON expression. If you run
187
+ # a query that returns a stream of results (e.g. filtering a table), you get
188
+ # back an enumerable object of class RethinkDB::Query_Results.
189
+ #
190
+ # You may also provide some options as an optional second
191
+ # argument. The only useful option right now is
192
+ # <b>+:use_outdated+</b>, which specifies whether tables can use
193
+ # outdated information. (If you specified this on a per-table
194
+ # basis in your query, specifying it again here won't override the
195
+ # original choice.)
196
+ #
197
+ # <b>NOTE:</b> unlike most enumerable objects, you can only iterate over the
198
+ # result once. See RethinkDB::Query_Results for more details.
199
+ def run (query, opts={:use_outdated => false})
200
+ #File.open('sexp.txt', 'a') {|f| f.write(query.sexp.inspect+"\n")}
201
+ is_atomic = (query.kind_of?(JSON_Expression) ||
202
+ query.kind_of?(Meta_Query) ||
203
+ query.kind_of?(Write_Query))
204
+ map = {}
205
+ map[:default_db] = @default_db if @default_db
206
+ S.check_opts(opts, [:use_outdated])
207
+ map[S.conn_outdated] = !!opts[:use_outdated]
208
+ protob = query.query(map)
209
+ if is_atomic
210
+ a = []
211
+ singular = token_iter(query, dispatch(protob)){|row| a.push row}
212
+ a.each{|o| BT.maybe_reformat_err(query, o)}
213
+ singular ? a : a[0]
214
+ else
215
+ return Query_Results.new(query, self, dispatch(protob))
216
+ end
217
+ end
218
+
219
+ # Close the connection.
220
+ def close
221
+ @listener.terminate! if @listener
222
+ @listener = nil
223
+ @socket.close
224
+ @socket = nil
225
+ end
226
+
227
+ # Return the last opened connection, or throw if there is no such
228
+ # connection. Used by e.g. RQL_Query#run.
229
+ def self.last_connection
230
+ return @@last if @@last
231
+ raise RuntimeError, "No last connection. Use RethinkDB::Connection.new."
232
+ end
233
+
234
+ def start_listener # :nodoc:
235
+ class << @socket
236
+ def read_exn(len) # :nodoc:
237
+ buf = read len
238
+ raise RuntimeError,"Connection closed by server." if !buf or buf.length != len
239
+ return buf
240
+ end
241
+ end
242
+ @socket.send([@@magic_number].pack('L<'), 0)
243
+ @listener.terminate! if @listener
244
+ @listener = Thread.new do
245
+ loop do
246
+ begin
247
+ response_length = @socket.read_exn(4).unpack('L<')[0]
248
+ response = @socket.read_exn(response_length)
249
+ rescue RuntimeError => e
250
+ @mutex.synchronize do
251
+ @listener = nil
252
+ @waiters.each {|kv| kv[1].signal}
253
+ end
254
+ Thread.current.terminate!
255
+ abort("unreachable")
256
+ end
257
+ #TODO: Recovery
258
+ begin
259
+ protob = Response.new.parse_from_string(response)
260
+ rescue
261
+ p response
262
+ abort("Bad Protobuf.")
263
+ end
264
+ @mutex.synchronize do
265
+ @data[protob.token] = protob
266
+ if (@waiters[protob.token])
267
+ cond = @waiters.delete protob.token
268
+ cond.signal
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end