fraggle 0.1.1 → 0.2.0
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.
- data/README.md +32 -16
- data/lib/fraggle.rb +6 -316
- data/lib/fraggle/client.rb +325 -0
- data/lib/fraggle/emitter.rb +26 -0
- data/lib/fraggle/logger.rb +26 -0
- data/lib/fraggle/meta.rb +9 -0
- data/lib/fraggle/{proto.rb → msg.rb} +10 -2
- data/lib/fraggle/protocol.rb +47 -0
- data/lib/fraggle/request.rb +15 -0
- data/lib/fraggle/response.rb +33 -0
- data/lib/fraggle/snap.rb +43 -0
- data/lib/fraggle/test.rb +72 -0
- data/test/fraggle_client_test.rb +261 -0
- data/test/fraggle_protocol_test.rb +100 -0
- data/test/fraggle_snap_test.rb +67 -0
- metadata +20 -11
- data/test/core_test.rb +0 -215
- data/test/live_test.rb +0 -197
- data/test/reconnect_test.rb +0 -175
data/README.md
CHANGED
@@ -16,15 +16,18 @@
|
|
16
16
|
# connected. In the event of a lost connection, fraggle will attempt
|
17
17
|
# other doozers until one accepts or it runs out of options; An
|
18
18
|
# AssemlyError will be raised if that later happens.
|
19
|
-
c = Fraggle.connect "127.0.0.1:8046"
|
20
|
-
|
21
|
-
c.get "/foo" do |e|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
c = Fraggle.connect "doozerd://127.0.0.1:8046"
|
20
|
+
|
21
|
+
req = c.get "/foo" do |e|
|
22
|
+
e.value # => "bar"
|
23
|
+
e.cas # => "123"
|
24
|
+
e.dir? # => false
|
25
|
+
e.notdir? # => true
|
26
|
+
end
|
27
|
+
|
28
|
+
req.error do |e|
|
29
|
+
e.err_code # => nil
|
30
|
+
e.err_detail # => nil
|
28
31
|
end
|
29
32
|
|
30
33
|
watch = c.watch "/foo" do |e|
|
@@ -44,20 +47,15 @@
|
|
44
47
|
|
45
48
|
# Phoney check for example
|
46
49
|
if can_stop_watching?(path)
|
47
|
-
|
50
|
+
watch.cancel
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
51
54
|
## Setting a key (this will trigger the watch above)
|
52
|
-
c.set "/foo", "zomg!", :missing do |e|
|
55
|
+
req = c.set "/foo", "zomg!", :missing do |e|
|
53
56
|
case true
|
54
57
|
when e.mismatch? # CAS mis-match
|
55
58
|
# retry if we must
|
56
|
-
c.set "/foo", "zomg!", e.cas do |e|
|
57
|
-
if ! e.ok?
|
58
|
-
# we give up
|
59
|
-
end
|
60
|
-
end
|
61
59
|
when e.ok?
|
62
60
|
e.cas # => "123"
|
63
61
|
else
|
@@ -65,6 +63,24 @@
|
|
65
63
|
end
|
66
64
|
end
|
67
65
|
|
66
|
+
req.error do |e|
|
67
|
+
# This is the default behavior for fraggle.
|
68
|
+
# I'm showing this to bring attention to the use of the
|
69
|
+
# error callback.
|
70
|
+
raise e.err_detail
|
71
|
+
end
|
72
|
+
|
73
|
+
# Knowning when a command is done is useful in some cases.
|
74
|
+
# Use the `done` callback for those situations.
|
75
|
+
ents = []
|
76
|
+
req = c.getdir("/test") do |e|
|
77
|
+
ents << e
|
78
|
+
end
|
79
|
+
|
80
|
+
req.done do
|
81
|
+
p ents
|
82
|
+
end
|
83
|
+
|
68
84
|
end
|
69
85
|
|
70
86
|
|
data/lib/fraggle.rb
CHANGED
@@ -1,322 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require 'fraggle/proto'
|
1
|
+
require 'fraggle/snap'
|
2
|
+
require 'uri'
|
4
3
|
|
5
4
|
module Fraggle
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# Response extensions
|
12
|
-
class Response
|
13
|
-
module Flag
|
14
|
-
VALID = 1
|
15
|
-
DONE = 2
|
16
|
-
end
|
17
|
-
|
18
|
-
def valid?
|
19
|
-
(flags & Flag::VALID) > 0
|
20
|
-
end
|
21
|
-
|
22
|
-
def done?
|
23
|
-
(flags & Flag::DONE) > 0
|
24
|
-
end
|
25
|
-
|
26
|
-
# Err sugar
|
27
|
-
def ok? ; err_code == nil ; end
|
28
|
-
def other? ; err_code == Err::OTHER ; end
|
29
|
-
def tag_in_use? ; err_code == Err::TAG_IN_USE ; end
|
30
|
-
def unknown_verb? ; err_code == Err::UNKNOWN_VERB ; end
|
31
|
-
def redirect? ; err_code == Err::REDIRECT ; end
|
32
|
-
def invalid_snap? ; err_code == Err::INVALID_SNAP ; end
|
33
|
-
def mismatch? ; err_code == Err::CAS_MISMATCH ; end
|
34
|
-
def notdir? ; err_code == Err::NOTDIR ; end
|
35
|
-
def dir? ; err_code == Err::ISDIR ; end
|
36
|
-
|
37
|
-
# CAS sugar
|
38
|
-
def missing? ; cas == 0 ; end
|
39
|
-
def clobber? ; cas == -1 ; end
|
40
|
-
def dir? ; cas == -2 ; end
|
41
|
-
def dummy? ; cas == -3 ; end
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
class AssemblyError < StandardError
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
def self.connect(addr="127.0.0.1:8046", opts={})
|
50
|
-
# TODO: take a magnet link instead
|
51
|
-
host, port = addr.split(":")
|
52
|
-
EM.connect(host, port, self, addr, opts)
|
53
|
-
end
|
54
|
-
|
55
|
-
attr_reader :doozers, :addr, :opts
|
56
|
-
|
57
|
-
def initialize(addr, opts)
|
58
|
-
opts[:assemble] = opts.fetch(:assemble, true)
|
59
|
-
|
60
|
-
# TODO: take a magnet link and load into @doozers
|
61
|
-
@addr = addr
|
62
|
-
@opts = opts
|
63
|
-
@doozers = {}
|
64
|
-
end
|
65
|
-
|
66
|
-
##
|
67
|
-
# Collect all cluster information for the event of a disconnect from the
|
68
|
-
# server; At which point we will want to attempt a connecting to them one by
|
69
|
-
# one until we have a connection or run out of options.
|
70
|
-
def assemble
|
71
|
-
return if ! opts[:assemble]
|
72
|
-
|
73
|
-
blk = Proc.new do |we|
|
74
|
-
if ! we.ok?
|
75
|
-
raise AssemblyError, we.err_detail
|
76
|
-
end
|
77
|
-
|
78
|
-
if we.value == ""
|
79
|
-
doozers.delete(we.path)
|
80
|
-
else
|
81
|
-
get "/doozer/info/#{we.value}/public-addr" do |e|
|
82
|
-
next if e.value == addr
|
83
|
-
doozers[we.path] = e.value
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
watch "/doozer/slot/*", &blk
|
89
|
-
walk "/doozer/slot/*", &blk
|
90
|
-
end
|
91
|
-
|
92
|
-
##
|
93
|
-
# Attempts to connect to another doozer when a connection is lost
|
94
|
-
def unbind
|
95
|
-
return if ! opts[:assemble]
|
96
|
-
|
97
|
-
_, @addr = doozers.shift
|
98
|
-
if ! @addr
|
99
|
-
raise AssemblyError, "All known doozers are down"
|
100
|
-
end
|
101
|
-
|
102
|
-
host, port = @addr.split(":")
|
103
|
-
reconnect(host, port)
|
104
|
-
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
##
|
110
|
-
# Session generation
|
111
|
-
def gen_key(name, size=16)
|
112
|
-
nibbles = "0123456789abcdef"
|
113
|
-
"#{name}." + (0...size).map { nibbles[rand(nibbles.length)].chr }.join
|
114
|
-
end
|
115
|
-
|
116
|
-
def session(name="fraggle", &blk)
|
117
|
-
raise ArgumentError, "no block given" if ! blk
|
118
|
-
|
119
|
-
id = gen_key(name)
|
120
|
-
|
121
|
-
fun = lambda do |e|
|
122
|
-
raise e.err_detail if ! e.ok?
|
123
|
-
checkin(e.cas, id, &fun)
|
124
|
-
end
|
125
|
-
|
126
|
-
established = lambda do |e|
|
127
|
-
case true
|
128
|
-
when e.mismatch?
|
129
|
-
id = gen_key(name)
|
130
|
-
checkin(0, id, &established)
|
131
|
-
when ! e.ok?
|
132
|
-
raise e.err_detail
|
133
|
-
else
|
134
|
-
blk.call
|
135
|
-
checkin(e.cas, id, &fun)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
checkin(0, id, &established)
|
140
|
-
end
|
141
|
-
|
142
|
-
def checkin(cas, id, &blk)
|
143
|
-
call(
|
144
|
-
Request::Verb::CHECKIN,
|
145
|
-
:cas => casify(cas),
|
146
|
-
:path => id.to_s,
|
147
|
-
&blk
|
148
|
-
)
|
149
|
-
end
|
150
|
-
|
151
|
-
def post_init
|
152
|
-
@buf = ""
|
153
|
-
@tag = 0
|
154
|
-
@cbx = {}
|
155
|
-
@len = nil
|
156
|
-
|
157
|
-
assemble
|
158
|
-
end
|
159
|
-
|
160
|
-
def receive_data(data)
|
161
|
-
@buf << data
|
162
|
-
|
163
|
-
got = true
|
164
|
-
while got
|
165
|
-
got = false
|
166
|
-
|
167
|
-
if @len.nil? && @buf.length >= 4
|
168
|
-
@len = @buf.slice!(0, 4).unpack("N").first
|
169
|
-
end
|
170
|
-
|
171
|
-
if @len && @buf.length >= @len
|
172
|
-
bytes = @buf.slice!(0, @len)
|
173
|
-
res = Response.decode(bytes)
|
174
|
-
receive_response(res)
|
175
|
-
@len = nil
|
176
|
-
got = true
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
def receive_response(res)
|
182
|
-
blk = @cbx[res.tag]
|
183
|
-
|
184
|
-
if blk && res.valid?
|
185
|
-
if blk.arity == 2
|
186
|
-
blk.call(res, false)
|
187
|
-
else
|
188
|
-
blk.call(res)
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
if res.done?
|
193
|
-
if blk && blk.arity == 2
|
194
|
-
blk.call(nil, true)
|
195
|
-
end
|
196
|
-
@cbx.delete(res.tag)
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def call(verb, attrs={}, &blk)
|
201
|
-
if @tag == MaxInt32
|
202
|
-
@tag = MinInt32
|
203
|
-
end
|
204
|
-
|
205
|
-
while true
|
206
|
-
break if ! @cbx.has_key?(@tag)
|
207
|
-
@tag += 1
|
208
|
-
end
|
209
|
-
|
210
|
-
attrs[:verb] = verb
|
211
|
-
attrs[:tag] = @tag
|
212
|
-
@cbx[@tag] = blk
|
213
|
-
|
214
|
-
send_request(Request.new(attrs))
|
215
|
-
|
216
|
-
@tag
|
217
|
-
end
|
218
|
-
|
219
|
-
def send_request(req)
|
220
|
-
buf = req.encode
|
221
|
-
|
222
|
-
send_data([buf.length].pack("N"))
|
223
|
-
send_data(buf)
|
224
|
-
end
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
##
|
229
|
-
# Sugar
|
230
|
-
def get(path, sid=0, &blk)
|
231
|
-
call(
|
232
|
-
Request::Verb::GET,
|
233
|
-
:path => path,
|
234
|
-
:id => sid,
|
235
|
-
&blk
|
236
|
-
)
|
237
|
-
end
|
238
|
-
|
239
|
-
def set(path, body, cas, &blk)
|
240
|
-
call(
|
241
|
-
Request::Verb::SET,
|
242
|
-
:path => path,
|
243
|
-
:value => body,
|
244
|
-
:cas => casify(cas),
|
245
|
-
&blk
|
246
|
-
)
|
247
|
-
end
|
248
|
-
|
249
|
-
def del(path, cas, &blk)
|
250
|
-
call(
|
251
|
-
Request::Verb::DEL,
|
252
|
-
:path => path,
|
253
|
-
:cas => casify(cas),
|
254
|
-
&blk
|
255
|
-
)
|
256
|
-
end
|
257
|
-
|
258
|
-
def watch(glob, &blk)
|
259
|
-
call(
|
260
|
-
Request::Verb::WATCH,
|
261
|
-
:path => glob,
|
262
|
-
&blk
|
263
|
-
)
|
264
|
-
end
|
265
|
-
|
266
|
-
def walk(glob, &blk)
|
267
|
-
call(
|
268
|
-
Request::Verb::WALK,
|
269
|
-
:path => glob,
|
270
|
-
&blk
|
271
|
-
)
|
272
|
-
end
|
273
|
-
|
274
|
-
def snap(&blk)
|
275
|
-
call(
|
276
|
-
Request::Verb::SNAP,
|
277
|
-
&blk
|
278
|
-
)
|
279
|
-
end
|
280
|
-
|
281
|
-
def delsnap(id, &blk)
|
282
|
-
call(
|
283
|
-
Request::Verb::DELSNAP,
|
284
|
-
:id => id,
|
285
|
-
&blk
|
286
|
-
)
|
287
|
-
end
|
288
|
-
|
289
|
-
def noop(&blk)
|
290
|
-
call(
|
291
|
-
Request::Verb::NOOP,
|
292
|
-
&blk
|
293
|
-
)
|
294
|
-
end
|
295
|
-
|
296
|
-
def cancel(tag)
|
297
|
-
blk = lambda do |e|
|
298
|
-
if e.ok?
|
299
|
-
if blk = @cbx.delete(tag)
|
300
|
-
blk.call(nil, true)
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
call(
|
306
|
-
Request::Verb::CANCEL,
|
307
|
-
:id => tag,
|
308
|
-
&blk
|
309
|
-
)
|
310
|
-
end
|
311
|
-
|
312
|
-
private
|
313
|
-
|
314
|
-
def casify(cas)
|
315
|
-
case cas
|
316
|
-
when :missing then 0
|
317
|
-
when :clobber then -1
|
318
|
-
else cas
|
319
|
-
end
|
6
|
+
def self.connect(uri, *args)
|
7
|
+
uri = URI(uri)
|
8
|
+
c = EM.connect(uri.host, uri.port, Client, uri, *args)
|
9
|
+
Snap.new(0, c)
|
320
10
|
end
|
321
11
|
|
322
12
|
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require 'fraggle/logger'
|
2
|
+
require 'fraggle/meta'
|
3
|
+
require 'fraggle/protocol'
|
4
|
+
require 'fraggle/request'
|
5
|
+
require 'fraggle/response'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module Fraggle
|
9
|
+
|
10
|
+
module Client
|
11
|
+
include Protocol
|
12
|
+
include Logger
|
13
|
+
|
14
|
+
class Error < StandardError ; end
|
15
|
+
|
16
|
+
|
17
|
+
MinTag = 0
|
18
|
+
MaxTag = (1<<32)
|
19
|
+
|
20
|
+
|
21
|
+
def initialize(uri)
|
22
|
+
# Simplied for now. Later we'll take a real uri
|
23
|
+
# and disect it to init the addrs list
|
24
|
+
uri = URI(uri.to_s)
|
25
|
+
|
26
|
+
@addr = [uri.host, uri.port] * ":"
|
27
|
+
@addrs = {}
|
28
|
+
@shun = {}
|
29
|
+
@cbx = {}
|
30
|
+
|
31
|
+
# Logging
|
32
|
+
@level = ERROR
|
33
|
+
@writer = $stderr
|
34
|
+
end
|
35
|
+
|
36
|
+
def receive_response(res)
|
37
|
+
debug "received response: #{res.inspect}"
|
38
|
+
|
39
|
+
if res.err_code
|
40
|
+
if req = @cbx.delete(res.tag)
|
41
|
+
req.emit(:error, res)
|
42
|
+
return
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if (res.flags & Response::Flag::VALID) > 0
|
47
|
+
if req = @cbx[res.tag]
|
48
|
+
req.emit(:valid, res)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if (res.flags & Response::Flag::DONE) > 0
|
53
|
+
if req = @cbx.delete(res.tag)
|
54
|
+
req.emit(:done)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def checkin(path, cas, &blk)
|
60
|
+
req = Request.new
|
61
|
+
req.verb = Request::Verb::CHECKIN
|
62
|
+
req.path = path
|
63
|
+
req.cas = casify(cas)
|
64
|
+
|
65
|
+
send(req, &blk)
|
66
|
+
end
|
67
|
+
|
68
|
+
def session(prefix=nil, &blk)
|
69
|
+
nibbles = "0123456789abcdef"
|
70
|
+
postfix = (0...16).map { nibbles[rand(nibbles.length)].chr }.join
|
71
|
+
name = prefix ? prefix+"."+postfix : postfix
|
72
|
+
estab = false
|
73
|
+
|
74
|
+
f = Proc.new do |e|
|
75
|
+
# If this is the first response from the server, it's go-time.
|
76
|
+
if ! estab
|
77
|
+
blk.call
|
78
|
+
end
|
79
|
+
|
80
|
+
# We've successfully established a session. Say so.
|
81
|
+
estab = true
|
82
|
+
|
83
|
+
# Get back to the server ASAP
|
84
|
+
checkin(name, e.cas, &f)
|
85
|
+
end
|
86
|
+
|
87
|
+
checkin(name, 0, &f)
|
88
|
+
end
|
89
|
+
|
90
|
+
def get(sid, path, &blk)
|
91
|
+
req = Request.new
|
92
|
+
req.verb = Request::Verb::GET
|
93
|
+
req.id = sid if sid != 0 # wire optimization
|
94
|
+
req.path = path
|
95
|
+
|
96
|
+
send(req, &blk)
|
97
|
+
end
|
98
|
+
|
99
|
+
def stat(sid, path, &blk)
|
100
|
+
req = Request.new
|
101
|
+
req.verb = Request::Verb::STAT
|
102
|
+
req.id = sid if sid != 0 # wire optimization
|
103
|
+
req.path = path
|
104
|
+
|
105
|
+
send(req, &blk)
|
106
|
+
end
|
107
|
+
|
108
|
+
def getdir(sid, path, offset, limit, &blk)
|
109
|
+
req = Request.new
|
110
|
+
req.verb = Request::Verb::GETDIR
|
111
|
+
req.id = sid if sid != 0
|
112
|
+
req.offset = offset if offset != 0
|
113
|
+
req.limit = limit if limit != 0
|
114
|
+
req.path = path
|
115
|
+
|
116
|
+
send(req, &blk)
|
117
|
+
end
|
118
|
+
|
119
|
+
def set(path, value, cas, &blk)
|
120
|
+
req = Request.new
|
121
|
+
req.verb = Request::Verb::SET
|
122
|
+
req.path = path
|
123
|
+
req.value = value
|
124
|
+
req.cas = casify(cas)
|
125
|
+
|
126
|
+
send(req, &blk)
|
127
|
+
end
|
128
|
+
|
129
|
+
def del(path, cas, &blk)
|
130
|
+
req = Request.new
|
131
|
+
req.verb = Request::Verb::DEL
|
132
|
+
req.path = path
|
133
|
+
req.cas = casify(cas)
|
134
|
+
|
135
|
+
send(req, &blk)
|
136
|
+
end
|
137
|
+
|
138
|
+
def walk(sid, glob, &blk)
|
139
|
+
req = Request.new
|
140
|
+
req.verb = Request::Verb::WALK
|
141
|
+
req.id = sid if sid != 0 # wire optimization
|
142
|
+
req.path = glob
|
143
|
+
|
144
|
+
cancelable(send(req, &blk))
|
145
|
+
end
|
146
|
+
|
147
|
+
def watch(glob, &blk)
|
148
|
+
req = Request.new
|
149
|
+
req.verb = Request::Verb::WATCH
|
150
|
+
req.path = glob
|
151
|
+
|
152
|
+
cancelable(send(req, &blk))
|
153
|
+
end
|
154
|
+
|
155
|
+
def snap(&blk)
|
156
|
+
req = Request.new
|
157
|
+
req.verb = Request::Verb::SNAP
|
158
|
+
|
159
|
+
send(req, &blk)
|
160
|
+
end
|
161
|
+
|
162
|
+
def delsnap(sid, &blk)
|
163
|
+
req = Request.new
|
164
|
+
req.verb = Request::Verb::DELSNAP
|
165
|
+
req.id = sid
|
166
|
+
|
167
|
+
send(req, &blk)
|
168
|
+
end
|
169
|
+
|
170
|
+
def noop(&blk)
|
171
|
+
req = Request.new
|
172
|
+
req.verb = Request::Verb::NOOP
|
173
|
+
|
174
|
+
send(req, &blk)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Be careful with this. It is recommended you use #cancel on the Request
|
178
|
+
# returned to ensure you don't run into a race-condition where you cancel an
|
179
|
+
# operation you may have thought was something else.
|
180
|
+
def __cancel__(what, &blk)
|
181
|
+
req = Request.new
|
182
|
+
req.verb = Request::Verb::CANCEL
|
183
|
+
req.id = what.tag
|
184
|
+
|
185
|
+
# Hold on to the tag as unavaiable for reuse until the cancel succeeds.
|
186
|
+
@cbx[what.tag] = nil
|
187
|
+
|
188
|
+
send(req) do |res|
|
189
|
+
# Do not send any more responses from the server to this request.
|
190
|
+
@cbx.delete(what.tag)
|
191
|
+
blk.call(res) if blk
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def send(req, &blk)
|
196
|
+
tag = MinTag
|
197
|
+
|
198
|
+
while @cbx.has_key?(tag)
|
199
|
+
tag += 1
|
200
|
+
if tag > MaxTag
|
201
|
+
tag = MinTag
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
req.tag = tag
|
206
|
+
|
207
|
+
if blk
|
208
|
+
req.valid(&blk)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Setup a default error handler that gives useful information
|
212
|
+
req.error do |e|
|
213
|
+
raise Error.new("'error (%d) (%s)' for: %s" % [
|
214
|
+
e.err_code,
|
215
|
+
e.err_detail.inspect,
|
216
|
+
req.inspect
|
217
|
+
])
|
218
|
+
end
|
219
|
+
|
220
|
+
@cbx[req.tag] = req
|
221
|
+
|
222
|
+
debug "sending request: #{req.inspect}"
|
223
|
+
send_request(req)
|
224
|
+
|
225
|
+
req
|
226
|
+
end
|
227
|
+
|
228
|
+
def cancelable(req)
|
229
|
+
c = self
|
230
|
+
can = true
|
231
|
+
|
232
|
+
req.metadef :cancel do
|
233
|
+
if can
|
234
|
+
can = false
|
235
|
+
c.__cancel__(self)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
req.metadef :canceled? do
|
240
|
+
!can
|
241
|
+
end
|
242
|
+
|
243
|
+
req
|
244
|
+
end
|
245
|
+
|
246
|
+
def post_init
|
247
|
+
info "successfully connected to #{@addr}"
|
248
|
+
|
249
|
+
@last_received = Time.now
|
250
|
+
|
251
|
+
EM.add_periodic_timer(2) do
|
252
|
+
if (n = Time.now - last_received) >= 3
|
253
|
+
error("timeout talking to #{@addr}")
|
254
|
+
close_connection
|
255
|
+
else
|
256
|
+
debug("ping")
|
257
|
+
get(0, "/ping") { debug("pong") }
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
waw = Proc.new do |e|
|
262
|
+
if e.value == ""
|
263
|
+
addr = @addrs.delete(e.path)
|
264
|
+
if addr
|
265
|
+
error "noticed #{addr} is gone; removing"
|
266
|
+
end
|
267
|
+
else
|
268
|
+
get 0, "/doozer/info/#{e.value}/public-addr" do |a|
|
269
|
+
if @shun.has_key?(a.value)
|
270
|
+
if (n = Time.now - @shun[a.value]) > 3
|
271
|
+
info "pardoning #{a.value} after #{n} secs"
|
272
|
+
@shun.delete(a.value)
|
273
|
+
else
|
274
|
+
info "ignoring shunned addr #{a.value}"
|
275
|
+
next
|
276
|
+
end
|
277
|
+
end
|
278
|
+
# TODO: Be defensive and check the addr value is valid
|
279
|
+
@addrs[e.path] = a.value
|
280
|
+
info("added #{e.path} addr #{a.value}")
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
watch "/doozer/slot/*", &waw
|
286
|
+
walk 0, "/doozer/slot/*", &waw
|
287
|
+
end
|
288
|
+
|
289
|
+
# What happens when a connection is closed for any reason.
|
290
|
+
def unbind
|
291
|
+
error "disconnected from #{@addr}"
|
292
|
+
|
293
|
+
# Shun the address we were currently attempting/connected to.
|
294
|
+
@shun[@addr] = Time.now
|
295
|
+
@addrs.delete_if {|_, v| v == @addr }
|
296
|
+
|
297
|
+
# We don't want the timer to race us while
|
298
|
+
# we're trying to reconnect. Once the reconnect
|
299
|
+
# has been complete, we'll start the timer again.
|
300
|
+
EM.cancel_timer(@timer)
|
301
|
+
|
302
|
+
_, @addr = @addrs.shift rescue nil
|
303
|
+
|
304
|
+
if ! @addr
|
305
|
+
# We are all out of addresses to try
|
306
|
+
raise "No more doozers!"
|
307
|
+
end
|
308
|
+
|
309
|
+
host, port = @addr.split(":")
|
310
|
+
info "attempting reconnect to #{host}:#{port}"
|
311
|
+
reconnect(host, port.to_i)
|
312
|
+
post_init
|
313
|
+
end
|
314
|
+
|
315
|
+
def casify(cas)
|
316
|
+
case cas
|
317
|
+
when :missing then Response::Missing
|
318
|
+
when :clobber then Response::Clobber
|
319
|
+
else cas
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|