fraggle 1.0.0pre → 1.0.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Fraggle
1
+ # Fraggle (v1.0.0.pre.2 is compatable with Doozer 0.5)
2
2
  **An EventMachine based Doozer client**
3
3
 
4
4
  ## Install
5
5
 
6
- $ gem install fraggle
6
+ $ gem install fraggle --pre
7
7
 
8
8
  ## Use
9
9
 
@@ -14,15 +14,13 @@
14
14
  EM.start do
15
15
  # Fraggle keeps track of this addr plus all others it finds once
16
16
  # connected. In the event of a lost connection, fraggle will attempt
17
- # other doozers until one accepts or it runs out of options; An
18
- # AssemlyError will be raised if that later happens.
17
+ # other doozers until one accepts or it runs out of options; A NoAddrs
18
+ # exception will be raised if that later happens.
19
19
  c = Fraggle.connect "doozerd://127.0.0.1:8046"
20
20
 
21
- req = c.get "/foo" do |e|
21
+ req = c.get("/foo") do |e|
22
22
  e.value # => "bar"
23
- e.cas # => "123"
24
- e.dir? # => false
25
- e.notdir? # => true
23
+ e.rev # => 123
26
24
  end
27
25
 
28
26
  req.error do |e|
@@ -30,18 +28,14 @@
30
28
  e.err_detail # => nil
31
29
  end
32
30
 
33
- watch = c.watch "/foo" do |e|
31
+ watch = c.watch("/foo") do |e|
34
32
  # The event has:
35
33
  # ------------------------
36
34
  e.err_code # => nil
37
35
  e.err_detail # => nil
38
36
  e.path # => "/foo"
39
37
  e.value # => "bar"
40
- e.cas # => "123"
41
- e.dir? # => false
42
- e.notdir? # => true
43
- e.set? # => true
44
- e.del? # => false
38
+ e.rev # => 123
45
39
 
46
40
  do_something_with(e)
47
41
 
@@ -52,43 +46,84 @@
52
46
  end
53
47
 
54
48
  ## Setting a key (this will trigger the watch above)
55
- req = c.set "/foo", "zomg!", :missing do |e|
56
- case true
57
- when e.mismatch? # CAS mis-match
58
- # retry if we must
59
- when e.ok?
60
- e.cas # => "123"
49
+ req = c.set(0, "/foo", "zomg!") do |e|
50
+ # Success!
51
+ end.error do |e|
52
+ if e.mismatch?
53
+ # There was a rev mismatch, handle this.
61
54
  else
62
55
  raise e.err_detail
63
56
  end
64
57
  end
65
58
 
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
59
  # Knowning when a command is done is useful in some cases.
74
60
  # Use the `done` callback for those situations.
75
61
  ents = []
76
62
  req = c.getdir("/test") do |e|
77
63
  ents << e
78
- end
79
-
80
- req.done do
64
+ end.done do
81
65
  p ents
82
66
  end
83
67
 
84
68
  end
85
69
 
70
+ ## Consistency
71
+
72
+ Fraggle read commands take a `rev`. If no rev is given, Doozer will reply with
73
+ the most up-to-date data. If you need to do multiple reads at certain
74
+ point in time for consistency, use the `rev` command.
75
+
76
+ c.rev do |v|
77
+ c.get("/a", v.rev) { ... }
78
+ c.get("/b", v.rev) { ... }
79
+ c.get("/c", v.rev) { ... }
80
+ end
81
+
82
+ This also means you can go back in time or into the future!
83
+
84
+ # This will not yield until the data store is at revision 100,000
85
+ c.get("/a", 100_000) { ... }
86
+
87
+ ## High Availability
88
+
89
+ Fraggle has mechanisms to gracefully deal with connection loss. They are:
90
+
91
+ *Monitoring cluster activity*
92
+
93
+ Fraggle monitors new Doozer nodes that come and go. This enables Fraggle to
94
+ keep an up-to-date list of available nodes it can connect to in the case of
95
+ a connection loss.
96
+
97
+ *Resend / Connection loss*
98
+
99
+ When a connection is lost and Fraggle successfully reconnects to another
100
+ Doozer node, Fraggle will resend most pending requests to the new connection.
101
+ This means you will not miss events; Even events that happened while you were
102
+ disconnected! All read commands will pick up where they left off. This is
103
+ valuable to understand because it means you don't need to code for failure on
104
+ reads; Fraggle gracefully handles it for you. This is really important for
105
+ the `WATCH` command.
106
+
107
+ Write commands will be resent if their `rev` is greater than 0. These are
108
+ idempotent requests. A rev of 0 or less will cause that request's error
109
+ callback to be invoked with a Fraggle::Connection::Disconnected response.
110
+ You will have to handle these yourself because Fraggle cannot know whether or
111
+ not it's safe to retry on your behalf.
112
+
113
+ For commands with multiple responses (i.e. `walk`, `watch`, `getdir`), Fraggle
114
+ will update their offset and limit as each response comes in. This means
115
+ if you disconnect in the middle of the responses, Fraggle will gracefully
116
+ resend the requests making it appear nothing happened and continue giving you
117
+ the remaining responses.
86
118
 
87
119
  ## Dev
88
120
 
89
121
  **Clone**
122
+
90
123
  $ git clone http://github.com/bmizerany/fraggle.git
91
124
 
92
125
  **Test**
126
+
93
127
  $ gem install turn
128
+
94
129
  $ turn
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'fraggle'
4
+ # This is for testing Fraggle's monitoring of addresses in a cluster.
5
+
6
+ EM.run do
7
+ c = Fraggle.connect "doozer:?ca=127.0.0.1:8041"
8
+ c.log.level = Logger::DEBUG
9
+
10
+ EM.add_periodic_timer(1) do
11
+ p [:addrs, c.addr, c.addrs]
12
+ end
13
+ end
@@ -4,21 +4,16 @@ require 'fraggle'
4
4
 
5
5
  EM.run do
6
6
 
7
- EM.error_handler do |e|
8
- $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
- end
10
-
11
7
  c = Fraggle.connect
12
- c.level = Fraggle::Logger::INFO
8
+ c.log.level = Logger::DEBUG
13
9
 
14
10
  ents = []
15
- req = c.getdir "/doozer" do |e|
11
+ c.getdir("/") do |e|
16
12
  ents << e.path
13
+ end.done do
14
+ p [:ents, ents]
15
+ end.error do |e|
16
+ raise StandardError.new("err: "+e.inspect)
17
17
  end
18
18
 
19
- req.error do
20
- puts *ents
21
- end
22
-
23
-
24
19
  end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'fraggle'
3
+
4
+ # This example assumes you have a Doozer with CAL privledges listening on port
5
+ # 8041 and a Doozer sink listening on 8042. This example will connect to the
6
+ # sink and attempt a write operation that will be redirected by the sink.
7
+ # Fraggle will pick the only remaining addr it has which is the CAL Doozer and
8
+ # retry the write operation.
9
+
10
+ EM.run do
11
+ # Connect to a slave (be sure this is a slave)
12
+ c = Fraggle.connect("doozer:?ca=127.0.0.1:8042&ca=127.0.0.1:8041")
13
+ c.log.level = Logger::DEBUG
14
+
15
+ a = c.set("/foo", "bar", -1) do |e|
16
+ # This shouldn't happen
17
+ p [:valid, a, e]
18
+ end.error do |e|
19
+ p [:err, a, e]
20
+ end
21
+
22
+ b = c.set("/foo", "bar", 1) do |e|
23
+ p [:valid, b, e]
24
+ end.error do |e|
25
+ # This shouldn't happen
26
+ p [:err, b, e]
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'fraggle'
3
+
4
+ ###
5
+ # To see this example in action, run 1 doozerd and attach 2
6
+ # more to it before running.
7
+ #
8
+ # NOTE: If you have the doozerd source, you can run ./bin/test-cluster
9
+ #
10
+ EM.run do
11
+ c = Fraggle.connect
12
+
13
+ puts "# To see this work, kill the doozer this is connected to"
14
+ puts "# to see it resend the request to the next connection."
15
+ puts
16
+
17
+ # Wait for rev 100,000
18
+ c.get(100_000, "/foo") do |e|
19
+ p ["This should not be!", e]
20
+ end.error do |e|
21
+ p [:err, e]
22
+ end.done do
23
+ p [:done, "This should not be!"]
24
+ end
25
+ end
@@ -4,15 +4,11 @@ require 'fraggle'
4
4
 
5
5
  EM.run do
6
6
 
7
- EM.error_handler do |e|
8
- $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
- end
10
-
11
7
  c = Fraggle.connect
12
8
 
13
9
  EM.add_periodic_timer(1) do
14
- c.get nil, "/ping" do |e|
15
- p [:e, e]
10
+ c.rev do |e|
11
+ p [:rev, e.rev]
16
12
  end
17
13
  end
18
14
 
@@ -3,19 +3,15 @@ require 'eventmachine'
3
3
  require 'fraggle'
4
4
 
5
5
  EM.run do
6
- EM.error_handler do |e|
7
- $stderr.puts e.message + "\n" + (e.backtrace * "\n")
8
- end
9
-
10
6
  c = Fraggle.connect
11
7
 
12
8
  EM.add_periodic_timer(1) do
13
- c.get "/hello" do |e|
9
+ c.get("/hello") do |e|
14
10
  p [:e, e]
15
11
  end
16
12
  end
17
13
 
18
- c.set '/hello', 'world', :missing do |e|
14
+ c.set('/hello', 'world', 0) do |e|
19
15
  p e
20
16
  end
21
17
  end
@@ -3,21 +3,17 @@ require 'eventmachine'
3
3
  require 'fraggle'
4
4
 
5
5
  EM.run do
6
-
7
- EM.error_handler do |e|
8
- $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
- end
10
-
11
6
  c = Fraggle.connect
12
7
 
13
8
  EM.add_periodic_timer(1) do
14
- c.stat "/example" do |e|
9
+ c.stat("/example") do |e|
15
10
  p e
11
+ end.error do |e|
12
+ p [:err, e]
16
13
  end
17
14
  end
18
15
 
19
16
  EM.add_periodic_timer(0.5) do
20
- c.set "/example/#{rand(10)}", "test", :clobber
17
+ c.set("/example/#{rand(10)}", "test", -1)
21
18
  end
22
-
23
19
  end
@@ -3,21 +3,27 @@ require 'eventmachine'
3
3
  require 'fraggle'
4
4
 
5
5
  EM.run do
6
-
7
- EM.error_handler do |e|
8
- $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
- end
10
-
11
6
  c = Fraggle.connect
12
7
 
13
8
  paths = []
14
- req = c.walk "/**" do |e|
9
+
10
+ req = c.walk("/**", 1)
11
+
12
+ valid = Proc.new do |e|
15
13
  paths << e.path+"="+e.value
16
14
  end
17
15
 
18
- req.done do
16
+ done = Proc.new do
17
+ puts "rev #{req.rev} " + ("-"*25)
19
18
  puts *paths
20
- end
21
19
 
20
+ paths.clear
21
+
22
+ req = c.walk("/**", req.rev+1)
23
+ req.valid(&valid)
24
+ req.done(&done)
25
+ end
22
26
 
27
+ req.valid(&valid)
28
+ req.done(&done)
23
29
  end
@@ -4,19 +4,17 @@ require 'fraggle'
4
4
 
5
5
  EM.run do
6
6
 
7
- EM.error_handler do |e|
8
- $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
- end
10
-
11
7
  c = Fraggle.connect
12
- c.level = Fraggle::Logger::WARN
8
+ c.log.level = Logger::DEBUG
13
9
 
14
- c.watch "/example/*" do |e|
10
+ c.watch("/example/*") do |e|
15
11
  p e
12
+ end.error do |e|
13
+ p [:err, e]
16
14
  end
17
15
 
18
16
  EM.add_periodic_timer(0.5) do
19
- c.set "/example/#{rand(10)}", "test", :clobber
17
+ c.set("/example/#{rand(10)}", "test", -1)
20
18
  end
21
19
 
22
20
  end
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  s.add_dependency "beefcake", "~>0.3"
23
+ s.add_dependency "eventmachine"
23
24
 
24
25
  s.add_development_dependency "turn"
25
26
  end
@@ -2,7 +2,7 @@ require 'fraggle/client'
2
2
 
3
3
  module Fraggle
4
4
 
5
- DEFAULT_URI = "doozerd:?" + [
5
+ DEFAULT_URI = "doozer:?" + [
6
6
  "ca=127.0.0.1:8046",
7
7
  "ca=127.0.0.1:8041",
8
8
  "ca=127.0.0.1:8042",
@@ -26,7 +26,7 @@ module Fraggle
26
26
  end
27
27
 
28
28
  def self.uri(u)
29
- if u =~ /^doozerd:\?(.*)$/
29
+ if u =~ /^doozer:\?(.*)$/
30
30
  parts = $1.split("&")
31
31
  parts.inject([]) do |m, pt|
32
32
  k, v = pt.split("=")
@@ -36,7 +36,7 @@ module Fraggle
36
36
  m
37
37
  end
38
38
  else
39
- raise ArgumentError, "invalid doozerd uri"
39
+ raise ArgumentError, "invalid doozer uri"
40
40
  end
41
41
  end
42
42
 
@@ -1,5 +1,6 @@
1
1
  require 'eventmachine'
2
2
  require 'fraggle/connection'
3
+ require 'logger'
3
4
 
4
5
  module Fraggle
5
6
  class Client
@@ -8,13 +9,21 @@ module Fraggle
8
9
  class NoMoreAddrs < StandardError
9
10
  end
10
11
 
11
- attr_reader :cn
12
+ DefaultLog = Logger.new(STDERR)
13
+ DefaultLog.level = Logger::UNKNOWN
12
14
 
13
- def initialize(cn, addrs)
14
- @cn, @addrs = cn, addrs
15
+ attr_reader :cn, :log, :addrs
16
+
17
+ def initialize(cn, addrs, log=DefaultLog)
18
+ @cn, @addrs, @log = cn, addrs, log
19
+ monitor_addrs
20
+ end
21
+
22
+ def addr
23
+ cn.addr
15
24
  end
16
25
 
17
- def set(rev, path, value, &blk)
26
+ def set(path, value, rev, &blk)
18
27
  req = Request.new
19
28
  req.verb = SET
20
29
  req.rev = rev
@@ -22,61 +31,69 @@ module Fraggle
22
31
  req.value = value
23
32
  req.valid(&blk)
24
33
 
25
- send(req)
34
+ idemp(req)
26
35
  end
27
36
 
28
- def get(rev, path, &blk)
37
+ def get(path, rev=nil, &blk)
29
38
  req = Request.new
30
39
  req.verb = GET
31
40
  req.rev = rev
32
41
  req.path = path
33
42
  req.valid(&blk)
34
43
 
35
- send(req)
44
+ resend(req)
36
45
  end
37
46
 
38
- def del(rev, path, &blk)
47
+ def del(path, rev, &blk)
39
48
  req = Request.new
40
49
  req.verb = DEL
41
50
  req.rev = rev
42
51
  req.path = path
43
52
  req.valid(&blk)
44
53
 
45
- send(req)
54
+ idemp(req)
46
55
  end
47
56
 
48
- def getdir(rev, path, offset=nil, limit=nil, &blk)
57
+ def getdir(path, rev=nil, offset=nil, limit=nil, &blk)
49
58
  req = Request.new
50
59
  req.verb = GETDIR
51
60
  req.rev = rev
52
61
  req.path = path
53
- req.offset = offset
62
+
63
+ # To reliably pick-up where we left off in the event of a disconnect, we
64
+ # must default the offset to zero. This is best done here and not in the
65
+ # param declaration because the user could override it to nil there.
66
+ req.offset = offset || 0
54
67
  req.limit = limit
55
68
  req.valid(&blk)
56
69
 
57
- send(req)
70
+ resend(req)
58
71
  end
59
72
 
60
- def walk(rev, path, offset=nil, limit=nil, &blk)
73
+ def walk(path, rev=nil, offset=nil, limit=nil, &blk)
61
74
  req = Request.new
62
75
  req.verb = WALK
63
76
  req.rev = rev
64
77
  req.path = path
65
- req.offset = offset
78
+
79
+ # To reliably pick-up where we left off in the event of a disconnect, we
80
+ # must default the offset to zero. This is best done here and not in the
81
+ # param declaration because the user could override it to nil there.
82
+ req.offset = offset || 0
66
83
  req.limit = limit
67
84
  req.valid(&blk)
68
85
 
69
- send(req)
86
+ resend(req)
70
87
  end
71
88
 
72
- def watch(rev, path, &blk)
89
+ def watch(path, rev=nil, &blk)
73
90
  req = Request.new
74
91
  req.verb = WATCH
75
92
  req.rev = rev
76
93
  req.path = path
77
94
  req.valid(&blk)
78
95
 
79
- send(req)
96
+ resend(req)
80
97
  end
81
98
 
82
99
  def rev(&blk)
@@ -84,16 +101,65 @@ module Fraggle
84
101
  req.verb = REV
85
102
  req.valid(&blk)
86
103
 
87
- send(req)
104
+ resend(req)
105
+ end
106
+
107
+ def stat(path, rev=nil, &blk)
108
+ req = Request.new
109
+ req.rev = rev
110
+ req.verb = STAT
111
+ req.path = path
112
+ req.valid(&blk)
113
+
114
+ resend(req)
115
+ end
116
+
117
+ def monitor_addrs
118
+ log.debug("monitor addrs")
119
+ rev do |v|
120
+ walk("/ctl/cal/*", v.rev) do |e|
121
+ get("/ctl/node/#{e.value}/addr", v.rev) do |a|
122
+ if a.value != ""
123
+ add_addr(a.value)
124
+ end
125
+ end
126
+ end.done do
127
+ watch("/ctl/cal/*", v.rev+1) do |e|
128
+ if e.value == ""
129
+ ## Look to see what it was before
130
+ get(e.path, e.rev-1) do |b|
131
+ if b.rev > 0
132
+ # The node was cleared. Delete it from the list of addrs.
133
+ log.debug("del addr: #{addr}")
134
+ @addrs.delete(b.value)
135
+ end
136
+ end
137
+ else
138
+ get("/ctl/node/#{e.value}/addr", e.rev) do |b|
139
+ add_addr(b.value)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ def add_addr(s)
148
+ return if s == self.addr
149
+ return if @addrs.include?(s)
150
+ log.debug("add addr: #{s}")
151
+ @addrs << s
88
152
  end
89
153
 
154
+ # Sends a request to the server. Returns the request with a new tag
155
+ # assigned. If `onre` is supplied, it will be invoked when a new connection
156
+ # is established
90
157
  def send(req, &onre)
91
158
  wr = Request.new(req.to_hash)
92
- wr = cn.send_request(wr)
93
-
94
- req.tag = wr.tag
95
159
 
96
160
  wr.valid do |e|
161
+ log.debug("response: #{e.inspect} for #{req.inspect}")
162
+
97
163
  if req.offset
98
164
  req.offset += 1
99
165
  end
@@ -103,36 +169,84 @@ module Fraggle
103
169
  end
104
170
 
105
171
  if (req.rev || 0) < (e.rev || 0)
172
+ log.debug("updating rev: to #{e.rev} - #{req.inspect}")
106
173
  req.rev = e.rev
107
174
  end
108
175
 
109
176
  req.emit(:valid, e)
110
177
  end
111
178
 
112
- wr.done do
179
+ wr.done do
113
180
  req.emit(:done)
114
181
  end
115
182
 
116
183
  wr.error do |e|
117
184
  case true
118
- when cn.err? || e.redirect?
119
- reconnect!
120
- onre.call if onre
185
+ when e.disconnected?
186
+ # If we haven't already reconnected, do so.
187
+ if cn.err?
188
+ log.error("conn err: #{req.inspect}")
189
+ reconnect!
190
+ end
191
+
192
+ if onre
193
+ # Someone else will handle this
194
+ onre.call
195
+ else
196
+ req.emit(:error, e)
197
+ end
198
+ when e.redirect?
199
+
200
+ log.error("redirect: #{req.inspect}")
201
+
202
+ # Closing the connection triggers a reconnect above.
203
+ cn.close_connection
204
+
205
+ if onre
206
+ # Someone else will handle this
207
+ onre.call
208
+ else
209
+ req.emit(:error, Connection::Disconnected)
210
+ end
121
211
  else
212
+ log.error("error: #{e.inspect} for #{req.inspect}")
122
213
  req.emit(:error, e)
123
214
  end
215
+
124
216
  end
125
217
 
218
+ wr = cn.send_request(wr)
219
+ req.tag = wr.tag
220
+ log.debug("sending: #{req.inspect}")
221
+
126
222
  req
127
223
  end
128
224
 
129
225
  def resend(req)
130
226
  send(req) do
131
227
  req.tag = nil
228
+ log.debug("resending: #{req.inspect}")
132
229
  resend(req)
133
230
  end
134
231
  end
135
232
 
233
+ def idemp(req)
234
+ send(req) do
235
+ if req.rev > 0
236
+ # If we're trying to update a value that isn't missing or that we're
237
+ # not trying to clobber, it's safe to retry. We can't idempotently
238
+ # update missing values because there may be a race with another
239
+ # client that sets and/or deletes the key during the time between your
240
+ # read and write.
241
+ req.tag = nil
242
+ idemp(req)
243
+ else
244
+ # We can't safely retry the write. Inform the user.
245
+ req.emit(:error, Connection::Disconnected)
246
+ end
247
+ end
248
+ end
249
+
136
250
  def reconnect!
137
251
  if addr = @addrs.shift
138
252
  reconnect(addr)
@@ -142,8 +256,9 @@ module Fraggle
142
256
  end
143
257
 
144
258
  def reconnect(addr)
259
+ log.warn("reconnecting to #{addr}")
145
260
  host, port = addr.split(":")
146
- @cn = EM.connect(host, port, Fraggle::Connection, @addrs)
261
+ @cn = EM.connect(host, port, Fraggle::Connection, addr)
147
262
  end
148
263
 
149
264
  end
@@ -5,6 +5,9 @@ module Fraggle
5
5
 
6
6
  module Connection
7
7
 
8
+ Disconnected = Response.new
9
+ Disconnected.disconnected = true
10
+
8
11
  # Base class for all Connection errors
9
12
  class Error < StandardError
10
13
  attr_accessor :req
@@ -23,6 +26,7 @@ module Fraggle
23
26
  attr_reader :last_received, :addr
24
27
 
25
28
  def initialize(addr)
29
+ p [:init, addr]
26
30
  @addr, @cb = addr, {}
27
31
  end
28
32
 
@@ -48,6 +52,10 @@ module Fraggle
48
52
 
49
53
  # The default receive_response
50
54
  def receive_response(res)
55
+ if err?
56
+ return
57
+ end
58
+
51
59
  req = @cb[res.tag]
52
60
 
53
61
  if ! req
@@ -60,14 +68,14 @@ module Fraggle
60
68
  return
61
69
  end
62
70
 
71
+ if res.valid?
72
+ req.emit(:valid, res)
73
+ end
74
+
63
75
  if res.done?
64
76
  @cb.delete(req.tag)
65
77
  req.emit(:done)
66
78
  end
67
-
68
- if res.valid?
69
- req.emit(:valid, res)
70
- end
71
79
  end
72
80
 
73
81
  def send_request(req)
@@ -76,7 +84,7 @@ module Fraggle
76
84
  end
77
85
 
78
86
  if err?
79
- next_tick { req.emit(:error, nil) }
87
+ next_tick { req.emit(:error, Disconnected) }
80
88
  return req
81
89
  end
82
90
 
@@ -118,7 +126,7 @@ module Fraggle
118
126
  def unbind
119
127
  @err = true
120
128
  @cb.values.each do |req|
121
- req.emit(:error, nil)
129
+ req.emit(:error, Disconnected)
122
130
  end
123
131
  end
124
132
 
@@ -3,12 +3,14 @@ require 'fraggle/msg.pb'
3
3
  module Fraggle
4
4
  class Request
5
5
 
6
+ DEFAULT_PROC = Proc.new {}
7
+
6
8
  attr_accessor :cn
7
9
  attr_reader :cb
8
10
 
9
11
  def initialize(attrs={})
10
12
  super(attrs)
11
- @cb = Hash.new(Proc.new{})
13
+ @cb = Hash.new
12
14
  end
13
15
 
14
16
  def valid(&blk)
@@ -27,7 +29,7 @@ module Fraggle
27
29
  end
28
30
 
29
31
  def emit(name, *args)
30
- @cb[name].call(*args)
32
+ (@cb[name] || DEFAULT_PROC).call(*args)
31
33
  end
32
34
 
33
35
  def cancel
@@ -6,6 +6,8 @@ module Fraggle
6
6
  VALID = 1
7
7
  DONE = 2
8
8
 
9
+ attr_accessor :disconnected
10
+
9
11
  def valid?
10
12
  return false if !flags
11
13
  (flags & VALID) > 0
@@ -24,5 +26,9 @@ module Fraggle
24
26
  err_code == Err::REDIRECT
25
27
  end
26
28
 
29
+ def disconnected?
30
+ !!@disconnected
31
+ end
32
+
27
33
  end
28
34
  end
@@ -1,3 +1,3 @@
1
1
  module Fraggle
2
- VERSION = "1.0.0pre"
2
+ VERSION = "1.0.0.pre.2"
3
3
  end
@@ -10,11 +10,17 @@ class FraggleClientTest < Test::Unit::TestCase
10
10
  cn = TestConn.new(addr)
11
11
 
12
12
  @addrs = ["1.1.1.1:1", "2.2.2.2:2", "3.3.3.3:3"]
13
- @c = Fraggle::Client.new(cn, @addrs)
13
+ @c = Fraggle::Client.allocate
14
14
 
15
15
  def @c.reconnect(addr)
16
16
  @cn = TestConn.new(addr)
17
17
  end
18
+
19
+ def @c.monitor_addrs
20
+ # do nothing
21
+ end
22
+
23
+ @c.__send__(:initialize, cn, @addrs)
18
24
  end
19
25
 
20
26
  def test_send_valid_done
@@ -29,6 +35,24 @@ class FraggleClientTest < Test::Unit::TestCase
29
35
  assert_equal [], log.error
30
36
  end
31
37
 
38
+ def test_send_valid_called_before_done
39
+ req, _ = request(V::NOP)
40
+ req = c.send(req)
41
+
42
+ log = []
43
+ req.valid do
44
+ log << :valid
45
+ end
46
+ req.done do
47
+ log << :done
48
+ end
49
+
50
+ res = Fraggle::Response.new :tag => req.tag, :value => "ing", :flags => F::VALID|F::DONE
51
+ c.cn.receive_response(res)
52
+
53
+ assert_equal [:valid, :done], log
54
+ end
55
+
32
56
  def test_send_error
33
57
  req, log = request(V::NOP)
34
58
  req = c.send(req)
@@ -57,7 +81,7 @@ class FraggleClientTest < Test::Unit::TestCase
57
81
  assert exp.include?(c.cn.addr), "#{c.cn.addr.inspect} not in #{exp.inspect}"
58
82
 
59
83
  # If the client can handle an error, it should not mention it to the user.
60
- assert_equal [], log.error
84
+ assert_equal [Fraggle::Connection::Disconnected], log.error
61
85
  end
62
86
 
63
87
  def test_reconnect_with_pending_request
@@ -75,11 +99,35 @@ class FraggleClientTest < Test::Unit::TestCase
75
99
 
76
100
  assert exp.include?(c.cn.addr), "#{c.cn.addr.inspect} not in #{exp.inspect}"
77
101
 
102
+ assert_equal [Fraggle::Connection::Disconnected], log.error
103
+ end
104
+
105
+ def test_reconnect_with_multiple_pending_requests
106
+ exp = @addrs.dup
107
+
108
+ # Send a request to invoke reconnect
109
+ req, loga = request(V::NOP)
110
+ req = c.send(req)
111
+
112
+ req, logb = request(V::NOP)
113
+ req = c.send(req)
114
+
115
+ # Disconnect from 127.0.0.1:0
116
+ c.cn.close_connection
117
+
118
+ # Fake reactor turn (only available in tests)
119
+ c.cn.tick!
120
+
121
+ assert exp.include?(c.cn.addr), "#{c.cn.addr.inspect} not in #{exp.inspect}"
122
+
123
+ # Reconnect should only be called once.
124
+ assert_equal exp.length - 1, c.addrs.length
125
+
78
126
  # If the client can handle an error, it should not mention it to the user.
79
- assert_equal [], log.error
127
+ assert_equal [Fraggle::Connection::Disconnected], loga.error
128
+ assert_equal [Fraggle::Connection::Disconnected], logb.error
80
129
  end
81
130
 
82
- # retry
83
131
  def test_resend_pending_requests
84
132
  req, log = request(V::GET, :path => "/foo")
85
133
  req = c.resend(req)
@@ -89,6 +137,24 @@ class FraggleClientTest < Test::Unit::TestCase
89
137
  assert_equal [req], c.cn.sent
90
138
  end
91
139
 
140
+ def test_idemp_pending_requests
141
+ one, olog = request(V::SET, :rev => 1, :path => "/foo", :value => "bar")
142
+ one = c.idemp(one)
143
+
144
+ zero, zlog = request(V::SET, :rev => 0, :path => "/foo", :value => "bar")
145
+ zero = c.idemp(zero)
146
+
147
+ neg, nlog = request(V::SET, :rev => -1, :path => "/foo", :value => "bar")
148
+ zero = c.idemp(neg)
149
+
150
+ c.cn.close_connection
151
+
152
+ assert_equal [one], c.cn.sent
153
+
154
+ assert_equal [Fraggle::Connection::Disconnected], zlog.error
155
+ assert_equal [Fraggle::Connection::Disconnected], nlog.error
156
+ end
157
+
92
158
  def test_manage_offset
93
159
  req, log = request(V::WALK, :path => "/foo/*", :offset => 3)
94
160
  req = c.resend(req)
@@ -148,12 +214,15 @@ class FraggleClientTest < Test::Unit::TestCase
148
214
  assert_equal [exp], c.cn.sent
149
215
  end
150
216
 
151
- def test_redirect
152
- req, log = request(V::SET, :rev => 0, :path => "/foo", :value => "bar")
153
- req = c.send(req)
217
+ def test_redirect_simple
218
+ a, al = request(V::SET, :rev => 0, :path => "/foo")
219
+ a = c.send(a)
220
+
221
+ b, bl = request(V::SET, :rev => 0, :path => "/foo")
222
+ b = c.send(b)
154
223
 
155
224
  res = Fraggle::Response.new(
156
- :tag => req.tag,
225
+ :tag => a.tag,
157
226
  :err_code => E::REDIRECT,
158
227
  :err_detail => "9.9.9.9:9",
159
228
  :flags => F::VALID|F::DONE
@@ -162,8 +231,11 @@ class FraggleClientTest < Test::Unit::TestCase
162
231
  c.cn.receive_response(res)
163
232
 
164
233
  assert_equal "1.1.1.1:1", c.cn.addr
165
- end
234
+ assert_equal ["2.2.2.2:2", "3.3.3.3:3"], c.addrs
166
235
 
236
+ assert_equal [Fraggle::Connection::Disconnected], al.error
237
+ assert_equal [Fraggle::Connection::Disconnected], bl.error
238
+ end
167
239
 
168
240
  ###
169
241
  # Sugar
@@ -191,7 +263,7 @@ class FraggleClientTest < Test::Unit::TestCase
191
263
  :value => "bar"
192
264
  }
193
265
 
194
- assert_verb exp, :set, 0, "/foo", "bar"
266
+ assert_verb exp, :set, "/foo", "bar", 0
195
267
  end
196
268
 
197
269
  def test_get
@@ -201,7 +273,7 @@ class FraggleClientTest < Test::Unit::TestCase
201
273
  :path => "/foo"
202
274
  }
203
275
 
204
- assert_verb exp, :get, 0, "/foo"
276
+ assert_verb exp, :get, "/foo", 0
205
277
  end
206
278
 
207
279
  def test_del
@@ -211,7 +283,7 @@ class FraggleClientTest < Test::Unit::TestCase
211
283
  :path => "/foo"
212
284
  }
213
285
 
214
- assert_verb exp, :del, 0, "/foo"
286
+ assert_verb exp, :del, "/foo", 0
215
287
  end
216
288
 
217
289
  def test_getdir
@@ -223,7 +295,7 @@ class FraggleClientTest < Test::Unit::TestCase
223
295
  :limit => 5
224
296
  }
225
297
 
226
- assert_verb exp, :getdir, 0, "/foo", 0, 5
298
+ assert_verb exp, :getdir, "/foo", 0, 0, 5
227
299
  end
228
300
 
229
301
  def test_rev
@@ -234,6 +306,16 @@ class FraggleClientTest < Test::Unit::TestCase
234
306
  assert_verb exp, :rev
235
307
  end
236
308
 
309
+ def test_stat
310
+ exp = {
311
+ :rev => 0,
312
+ :verb => V::STAT,
313
+ :path => "/foo"
314
+ }
315
+
316
+ assert_verb exp, :stat, "/foo", 0
317
+ end
318
+
237
319
  def test_walk
238
320
  exp = {
239
321
  :verb => V::WALK,
@@ -243,7 +325,18 @@ class FraggleClientTest < Test::Unit::TestCase
243
325
  :limit => 5
244
326
  }
245
327
 
246
- assert_verb exp, :walk, 0, "/foo/*", 0, 5
328
+ assert_verb exp, :walk, "/foo/*", 0, 0, 5
329
+ end
330
+
331
+ def test_walk_no_offset_limit_given
332
+ exp = {
333
+ :verb => V::WALK,
334
+ :rev => 0,
335
+ :path => "/foo/*",
336
+ :offset => 0
337
+ }
338
+
339
+ assert_verb exp, :walk, "/foo/*", 0
247
340
  end
248
341
 
249
342
  def test_watch
@@ -253,7 +346,7 @@ class FraggleClientTest < Test::Unit::TestCase
253
346
  :path => "/foo/*"
254
347
  }
255
348
 
256
- assert_verb exp, :watch, 0, "/foo/*"
349
+ assert_verb exp, :watch, "/foo/*", 0
257
350
  end
258
351
 
259
352
  end
@@ -3,7 +3,7 @@ require 'fraggle'
3
3
  class FraggleTest < Test::Unit::TestCase
4
4
 
5
5
  def test_uri
6
- uri = "doozerd:?ca=1:1&ca=2:2&ca=3:3&ignore=this"
6
+ uri = "doozer:?ca=1:1&ca=2:2&ca=3:3&ignore=this"
7
7
  addrs = Fraggle.uri(uri)
8
8
  assert_equal ["1:1", "2:2", "3:3"], addrs
9
9
  end
@@ -165,13 +165,25 @@ class FraggleTransactionTest < Test::Unit::TestCase
165
165
  cn.unbind
166
166
 
167
167
  assert_equal 1, al.error.length
168
- assert_equal nil, al.error.first
168
+ assert_equal Fraggle::Connection::Disconnected, al.error.first
169
169
 
170
170
  assert_equal 1, bl.error.length
171
- assert_equal nil, bl.error.first
171
+ assert_equal Fraggle::Connection::Disconnected, bl.error.first
172
172
 
173
173
  assert_equal 1, cl.error.length
174
- assert_equal nil, cl.error.first
174
+ assert_equal Fraggle::Connection::Disconnected, cl.error.first
175
+ end
176
+
177
+ def test_ignores_responses_in_err_state
178
+ a, al = nop
179
+ a = cn.send_request(a)
180
+
181
+ cn.unbind
182
+
183
+ res = Fraggle::Response.new(:tag => a.tag, :flags => F::VALID|F::DONE)
184
+ cn.receive_response(res)
185
+
186
+ assert_equal [], al.valid
175
187
  end
176
188
 
177
189
  def test_send_request_in_error_state
@@ -213,6 +225,7 @@ class FraggleTransactionTest < Test::Unit::TestCase
213
225
  cn.receive_response(res)
214
226
  assert ! cn.err?
215
227
 
228
+ res.tag += 1
216
229
  cn.receive_response(res)
217
230
  assert cn.err?
218
231
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fraggle
3
3
  version: !ruby/object:Gem::Version
4
- hash: 961915988
5
- prerelease: 5
4
+ hash: 1923831949
5
+ prerelease: 6
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
9
  - 0
10
10
  - pre
11
- version: 1.0.0pre
11
+ - 2
12
+ version: 1.0.0.pre.2
12
13
  platform: ruby
13
14
  authors:
14
15
  - Blake Mizerany
@@ -16,7 +17,7 @@ autorequire:
16
17
  bindir: bin
17
18
  cert_chain: []
18
19
 
19
- date: 2011-03-28 00:00:00 -07:00
20
+ date: 2011-04-25 00:00:00 -05:00
20
21
  default_executable:
21
22
  dependencies:
22
23
  - !ruby/object:Gem::Dependency
@@ -35,7 +36,7 @@ dependencies:
35
36
  type: :runtime
36
37
  version_requirements: *id001
37
38
  - !ruby/object:Gem::Dependency
38
- name: turn
39
+ name: eventmachine
39
40
  prerelease: false
40
41
  requirement: &id002 !ruby/object:Gem::Requirement
41
42
  none: false
@@ -46,8 +47,22 @@ dependencies:
46
47
  segments:
47
48
  - 0
48
49
  version: "0"
49
- type: :development
50
+ type: :runtime
50
51
  version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: turn
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :development
65
+ version_requirements: *id003
51
66
  description: An EventMachine Client for Doozer
52
67
  email:
53
68
  - blake.mizerany@gmail.com
@@ -63,10 +78,11 @@ files:
63
78
  - LICENSE
64
79
  - README.md
65
80
  - Rakefile
66
- - example/again.rb
81
+ - example/addrs.rb
67
82
  - example/getdir.rb
68
- - example/ping.rb
69
- - example/session.rb
83
+ - example/redirect.rb
84
+ - example/resend.rb
85
+ - example/rev.rb
70
86
  - example/set.rb
71
87
  - example/stat.rb
72
88
  - example/walk.rb
@@ -1,21 +0,0 @@
1
- require 'rubygems'
2
- require 'eventmachine'
3
- require 'fraggle'
4
-
5
- EM.run do
6
-
7
- c = Fraggle.connect
8
- c.level = Fraggle::Logger::INFO
9
-
10
- paths = []
11
-
12
- c.walk("/**") do |e|
13
- paths << e.path+"="+e.value
14
- end.again do |w|
15
- # We've been connected to to a new server, Resend the request
16
- c.send(w)
17
- end.done do
18
- puts "## DONE", *paths
19
- end
20
-
21
- end
@@ -1,16 +0,0 @@
1
- require 'rubygems'
2
- require 'eventmachine'
3
- require 'fraggle'
4
-
5
- EM.run do
6
-
7
- EM.error_handler do |e|
8
- $stderr.puts e.message + "\n" + (e.backtrace * "\n")
9
- end
10
-
11
- c = Fraggle.connect :level => Fraggle::Client::DEBUG
12
-
13
- c.session "example." do |session_id|
14
- c.debug "established session (#{session_id})!"
15
- end
16
- end