rethinkdb 1.2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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