krakow 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,9 +1,18 @@
1
- ## v0.1.0
2
- * Add logging support
3
- * Include valid responses within commands
4
- * Segregate responses from messages
5
- * Manage connections in consumer (closed/reconnect)
6
- * Add message distribution support
1
+ ## v0.1.2
2
+ * Include backoff support
3
+ * Remove `method_missing` magic
4
+ * Force message redistribution when connection removed
5
+ * Make discovery interval configurable
6
+ * Add support for HTTP producer
7
+ * Include namespace for custom exceptions #1 (thanks @copiousfreetime)
8
+ * Fix timeout method access in req command #1 (thanks @copiousfreetime)
7
9
 
8
- ## v0.0.1
9
- * Initial release
10
+ ## v0.1.0
11
+ * Add logging support
12
+ * Include valid responses within commands
13
+ * Segregate responses from messages
14
+ * Manage connections in consumer (closed/reconnect)
15
+ * Add message distribution support
16
+
17
+ ## v0.0.1
18
+ * Initial release
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- krakow (0.0.1)
4
+ krakow (0.1.1)
5
5
  celluloid-io
6
6
  http
7
7
  multi_json
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ```ruby
8
8
  require 'krakow'
9
9
 
10
- producer = Krakow::Producer(
10
+ producer = Krakow::Producer.new(
11
11
  :host => 'HOST',
12
12
  :port => 'PORT',
13
13
  :topic => 'target'
@@ -20,7 +20,7 @@ producer.write('KRAKOW!', 'KRAKOW!')
20
20
  ```ruby
21
21
  require 'krakow'
22
22
 
23
- consumer = Krakow::Consumer(
23
+ consumer = Krakow::Consumer.new(
24
24
  :nsqlookupd => 'http://HOST:PORT',
25
25
  :topic => 'target',
26
26
  :channel => 'ship'
@@ -36,7 +36,7 @@ end
36
36
 
37
37
  ## What is this?
38
38
 
39
- It's a Ruby library for NSQ[1] using Celluloid[2] under the hood.
39
+ It's a Ruby library for [NSQ][1] using [Celluloid][2] under the hood.
40
40
 
41
41
  ## Information and FAQ that I totally made up
42
42
 
@@ -48,7 +48,7 @@ you want, so adjust it when you create your consumer instance.
48
48
  ```ruby
49
49
  require 'krakow'
50
50
 
51
- consumer = Krakow::Consumer(
51
+ consumer = Krakow::Consumer.new(
52
52
  :nsqlookupd => 'http://HOST:PORT',
53
53
  :topic => 'target',
54
54
  :channel => 'ship',
@@ -56,11 +56,31 @@ consumer = Krakow::Consumer(
56
56
  )
57
57
  ```
58
58
 
59
+ ### Clean up after yourself
60
+
61
+ Since [Celluloid][2] is in use under the hood, and the main interaction points are
62
+ Actors (`Consumer` and `Producer`) you'll need to be sure you clean up. This simply
63
+ means terminating the instance (since falling out of scope will not cause it to be
64
+ garbage collected).
65
+
66
+ ```ruby
67
+ consumer = Krakow::Consumer.new(
68
+ :nsqlookupd => 'http://HOST:PORT',
69
+ :topic => 'target',
70
+ :channel => 'ship',
71
+ :max_in_flight => 30
72
+ )
73
+
74
+ # do stuff
75
+
76
+ consumer.terminate
77
+ ```
78
+
59
79
  ### Please make it shutup!
60
80
 
61
81
  Sure:
62
82
 
63
- ```
83
+ ```ruby
64
84
  Krakow::Utils::Logging.level = :warn # :debug / :info / :warn / :error / :fatal
65
85
  ```
66
86
 
@@ -73,7 +93,7 @@ Because forcing starvation is mean.
73
93
  Fine!
74
94
 
75
95
  ```ruby
76
- consumer = Krakow::Consumer(
96
+ consumer = Krakow::Consumer.new(
77
97
  :host => 'HOST',
78
98
  :port => 'PORT',
79
99
  :topic => 'target',
@@ -83,11 +103,32 @@ consumer = Krakow::Consumer(
83
103
  ```
84
104
  Great for testing, but you really should use the lookup service in the "real world"
85
105
 
106
+ ### Backoff support
107
+
108
+ NSQ has this backoff notion. It's pretty swell. Basically, if messages from a specific
109
+ producer get re-queued (fail), then message consumption from that producer is halted,
110
+ and slowly ramped back up. It gives time for downstream issues to work themselves out,
111
+ if possible, instead of just keeping the firehose of gasoline on. Neat.
112
+
113
+ By default backoff support is disabled. It can be enabled by setting the `:backoff_interval`
114
+ when constructing the `Consumer`. The interval is in seconds (and yes, floats are allowed
115
+ for sub-second intervals):
116
+
117
+ ```ruby
118
+ consumer = Krakow::Consumer.new(
119
+ :nsqlookupd => 'http://HOST:PORT',
120
+ :topic => 'target',
121
+ :channel => 'ship',
122
+ :max_in_flight => 30,
123
+ :backoff_interval => 1
124
+ )
125
+ ```
126
+
86
127
  ### It doesn't work
87
128
 
88
129
  Create an issue on the github repository.
89
130
 
90
- #### It doesn't do `x`
131
+ ### It doesn't do `x`
91
132
 
92
133
  Create an issue, or even better, send a PR. Just base it off the `develop` branch.
93
134
 
@@ -95,5 +136,5 @@ Create an issue, or even better, send a PR. Just base it off the `develop` branc
95
136
  * Repo: https://github.com/chrisroberts/krakow
96
137
  * IRC: Freenode @ spox
97
138
 
98
- [1] https://github.com/bitly/nsq
99
- [2] https://github.com/celluloid/celluloid
139
+ [1]: http://bitly.github.io/nsq/ "NSQ: a realtime distributed messaging platform"
140
+ [2]: http://celluloid.io "Celluloid: Actor-based concurrent object framework for Ruby"
@@ -8,7 +8,7 @@ module Krakow
8
8
  end
9
9
 
10
10
  def to_line
11
- "#{name} #{message_id} #{timeout}\n"
11
+ "#{name} #{message_id} #{self.timeout}\n"
12
12
  end
13
13
 
14
14
  class << self
@@ -14,7 +14,7 @@ module Krakow
14
14
  def initialize(args={})
15
15
  super
16
16
  required! :host, :port
17
- optional :version, :queue, :callback
17
+ optional :version, :queue, :callback, :responses
18
18
  arguments[:queue] ||= Queue.new
19
19
  arguments[:responses] ||= Queue.new
20
20
  arguments[:version] ||= 'v2'
@@ -12,17 +12,19 @@ module Krakow
12
12
  def initialize(args={})
13
13
  super
14
14
  required! :topic, :channel
15
- optional :host, :port, :nslookupd, :max_in_flight
15
+ optional :host, :port, :nslookupd, :max_in_flight, :backoff_interval, :discovery_interval
16
16
  arguments[:max_in_flight] ||= 1
17
+ arguments[:discovery_interval] ||= 30
17
18
  @connections = {}
18
19
  @distribution = Distribution::Default.new(
19
- :max_in_flight => max_in_flight
20
+ :max_in_flight => max_in_flight,
21
+ :backoff_interval => backoff_interval
20
22
  )
21
23
  @queue = Queue.new
22
24
  if(nslookupd)
23
25
  debug "Connections will be established via lookup #{nslookupd.inspect}"
24
26
  @discovery = Discovery.new(:nslookupd => nslookupd)
25
- every(60){ init! }
27
+ every(discovery_interval){ init! }
26
28
  init!
27
29
  elsif(host && port)
28
30
  debug "Connection will be established via direct connection #{host}:#{port}"
@@ -126,10 +128,11 @@ module Krakow
126
128
  connections.delete_if do |key, value|
127
129
  if(value == con)
128
130
  warn "Connection failure detected. Removing connection: #{key}"
129
- discovery.remove_connection(con)
131
+ distribution.remove_connection(con)
130
132
  true
131
133
  end
132
134
  end
135
+ distribution.redistribute!
133
136
  end
134
137
 
135
138
  # message_id:: Message ID
@@ -137,6 +140,7 @@ module Krakow
137
140
  def confirm(message_id)
138
141
  distribution.in_flight_lookup(message_id) do |connection|
139
142
  connection.transmit(Command::Fin.new(:message_id => message_id))
143
+ distribution.success(connection)
140
144
  end
141
145
  connection = distribution.unregister_message(message_id)
142
146
  update_ready!(connection)
@@ -154,8 +158,9 @@ module Krakow
154
158
  :timeout => timeout
155
159
  )
156
160
  )
161
+ distribution.failure(connection)
157
162
  end
158
- connection = distributrion.unregister_message(message_id)
163
+ connection = distribution.unregister_message(message_id)
159
164
  update_ready!(connection)
160
165
  true
161
166
  end
@@ -18,9 +18,8 @@ module Krakow
18
18
  connections.each do |connection|
19
19
  set_ready_for(connection, :force)
20
20
  end
21
- # TODO: Make interval configurable
22
21
  watch_dog.cancel if watch_dog
23
- @watch_dog = every(5) do
22
+ @watch_dog = every(watch_dog_interval) do
24
23
  force_unready
25
24
  end
26
25
  else
@@ -46,10 +45,16 @@ module Krakow
46
45
 
47
46
  # Returns next connection to receive RDY count
48
47
  def less_than_ideal_ready!
49
- if(less_than_ideal_stack.nil? || less_than_ideal_stack.empty?)
50
- @less_than_ideal_stack = waiting_connections
48
+ admit_defeat = false
49
+ connection = nil
50
+ until(connection || (admit_defeat && less_than_ideal_stack.empty?))
51
+ if(less_than_ideal_stack.nil? || less_than_ideal_stack.empty?)
52
+ @less_than_ideal_stack = waiting_connections
53
+ admit_defeat = true
54
+ end
55
+ con = less_than_ideal_stack.pop
56
+ connection = con unless registry_lookup(con)[:backoff_until] > Time.now.to_i
51
57
  end
52
- connection = less_than_ideal_stack.pop
53
58
  if(connection)
54
59
  registry_lookup(connection)[:ready] = 1
55
60
  connection
@@ -61,21 +66,16 @@ module Krakow
61
66
  # Provides customized RDY set when less than ideal to round
62
67
  # robin through connections
63
68
  def set_ready_for(connection, *args)
64
- if(less_than_ideal?)
65
- if(args.include?(:force))
66
- super connection
69
+ super connection
70
+ if(less_than_ideal? && !args.include?(:force))
71
+ debug "RDY set ignored due to less than ideal state (con: #{connection})"
72
+ con = less_than_ideal_ready!
73
+ if(con)
74
+ watch_dog.reset if watch_dog
75
+ super con
67
76
  else
68
- debug "RDY set ignored due to less than ideal state (con: #{connection})"
69
- con = less_than_ideal_ready!
70
- if(con)
71
- watch_dog.reset if watch_dog
72
- super con
73
- else
74
- warn 'Failed to set RDY state while less than ideal. Connection stack is empty!'
75
- end
77
+ warn 'Failed to set RDY state while less than ideal. Connection stack is empty!'
76
78
  end
77
- else
78
- super connection
79
79
  end
80
80
  end
81
81
 
@@ -85,7 +85,14 @@ module Krakow
85
85
  registry_info = registry_lookup(connection)
86
86
  unless(less_than_ideal?)
87
87
  registry_info[:ready] = ideal - registry_info[:in_flight]
88
- registry_info[:ready] = 0 if registry_info[:ready] < 0
88
+ if(registry_info[:ready] < 0 || registry_info[:backoff_until] > Time.now.to_i)
89
+ registry_info[:ready] = 0
90
+ registry_info[:backoff_timer].cancel if registry[:backoff_timer]
91
+ registry_info[:backoff_timer] = after(registry_info[:backoff_until] - Time.now.to_i) do
92
+ calculate_ready!(connection)
93
+ set_ready_for(conection) unless less_than_ideal?
94
+ end
95
+ end
89
96
  registry_info[:ready]
90
97
  else
91
98
  registry_info[:ready] = 0
@@ -95,7 +102,7 @@ module Krakow
95
102
  # Returns all connections without RDY state
96
103
  def waiting_connections
97
104
  registry.find_all do |connection, info|
98
- info[:ready] < 1 && info[:in_flight] < 1
105
+ info[:ready] < 1 && info[:in_flight] < 1 && info[:backoff_until] < Time.now.to_i
99
106
  end.map(&:first).compact
100
107
  end
101
108
 
@@ -12,6 +12,8 @@ module Krakow
12
12
 
13
13
  def initialize(args={})
14
14
  super
15
+ optional :watch_dog_interval, :backoff_interval
16
+ arguments[:watch_dog_interval] ||= 5
15
17
  @max_in_flight = arguments[:max_in_flight] || 1
16
18
  @ideal = 0
17
19
  @flight_record = {}
@@ -35,8 +37,7 @@ module Krakow
35
37
  def unregister_message(message)
36
38
  msg_id = message.respond_to?(:message_id) ? message.message_id : message.to_s
37
39
  connection = flight_record[msg_id]
38
- # TODO: Add lookup error
39
- registry_info = registry[connection_key(connection)]
40
+ registry_info = registry_lookup(connection)
40
41
  flight_record.delete(msg_id)
41
42
  registry_info[:in_flight] -= 1
42
43
  calculate_ready!(connection)
@@ -51,7 +52,7 @@ module Krakow
51
52
 
52
53
  # connection:: Connection
53
54
  # Send RDY for given connection
54
- def set_ready_for(connection)
55
+ def set_ready_for(connection, *_)
55
56
  connection.transmit(
56
57
  Command::Rdy.new(
57
58
  :count => ready_for(connection)
@@ -79,7 +80,9 @@ module Krakow
79
80
  def add_connection(connection)
80
81
  registry[connection_key(connection)] = {
81
82
  :ready => initial_ready,
82
- :in_flight => 0
83
+ :in_flight => 0,
84
+ :failures => 0,
85
+ :backoff_until => 0
83
86
  }
84
87
  true
85
88
  end
@@ -90,7 +93,7 @@ module Krakow
90
93
  # remove connection from registry
91
94
  registry.delete(connection_key(connection))
92
95
  # remove any in flight messages
93
- in_flight.delete_if do |k,v|
96
+ flight_record.delete_if do |k,v|
94
97
  v == connection_key(connection)
95
98
  end
96
99
  true
@@ -99,7 +102,7 @@ module Krakow
99
102
  # connection:: Connection
100
103
  # Return lookup key (actor reference)
101
104
  def connection_key(connection)
102
- connection.current_actor
105
+ connection
103
106
  end
104
107
 
105
108
  # msg_id:: Message ID string
@@ -109,7 +112,7 @@ module Krakow
109
112
  def in_flight_lookup(msg_id)
110
113
  connection = flight_record[msg_id]
111
114
  unless(connection)
112
- abort LookupFailed.new("Failed to locate in flight message (ID: #{msg_id})")
115
+ abort Krakow::Error::LookupFailed.new("Failed to locate in flight message (ID: #{msg_id})")
113
116
  end
114
117
  if(block_given?)
115
118
  yield connection
@@ -122,7 +125,7 @@ module Krakow
122
125
  # Return registry information for given connection
123
126
  def registry_lookup(connection)
124
127
  registry[connection_key(connection)] ||
125
- abort(LookupFailed.new("Failed to locate connection information in registry (#{connection})"))
128
+ abort(Krakow::Error::LookupFailed.new("Failed to locate connection information in registry (#{connection})"))
126
129
  end
127
130
 
128
131
  # Return list of all connections in registry
@@ -130,5 +133,30 @@ module Krakow
130
133
  registry.keys
131
134
  end
132
135
 
136
+ # connection:: Connection
137
+ # Log failure of processed message
138
+ def failure(connection)
139
+ if(backoff_interval)
140
+ registry_info = registry_lookup(connection)
141
+ registry_info[:failures] += 1
142
+ registry_info[:backoff_until] = Time.now.to_i + (registry_info[:failures] * backoff_interval)
143
+ end
144
+ true
145
+ end
146
+
147
+ # connection:: Connection
148
+ # Log success of processed message
149
+ def success(connection)
150
+ if(backoff_interval)
151
+ registry_info = registry_lookup(connection)
152
+ if(registry_info[:failures] > 1)
153
+ registry_info[:failures] -= 1
154
+ registry_info[:backoff_until] = Time.now.to_i + (registry_info[:failures] * backoff_interval)
155
+ else
156
+ registry_info[:failures] = 0
157
+ end
158
+ end
159
+ end
160
+
133
161
  end
134
162
  end
@@ -0,0 +1,115 @@
1
+ require 'http'
2
+ require 'uri'
3
+
4
+ module Krakow
5
+ class Producer
6
+ class Http
7
+
8
+ include Utils::Lazy
9
+
10
+ attr_reader :uri
11
+
12
+ def initialize(args={})
13
+ required! :endpoint, :topic
14
+ @uri = URI.parse(endpoint)
15
+ end
16
+
17
+ def send_message(method, path, args={})
18
+ build = uri.dup
19
+ build.path = "/#{path}"
20
+ HTTP.send(method, build.to_s, args)
21
+ end
22
+
23
+ def write(*payload)
24
+ if(payload.size == 1)
25
+ payload = payload.first
26
+ send_message(:post, :pub,
27
+ :body => payload,
28
+ :params => {:topic => topic}
29
+ )
30
+ else
31
+ send_message(:post, :mpub,
32
+ :body => payload.join("\n"),
33
+ :params => {:topic => topic}
34
+ )
35
+ end
36
+ end
37
+
38
+ def create_topic
39
+ send_message(:post, :create_topic,
40
+ :params => {:topic => topic}
41
+ )
42
+ end
43
+
44
+ def delete_topic
45
+ send_message(:post, :delete_topic,
46
+ :params => {:topic => topic}
47
+ )
48
+ end
49
+
50
+ def create_channel(chan)
51
+ send_message(:post, :create_channel,
52
+ :params => {
53
+ :topic => topic,
54
+ :channel => chan
55
+ }
56
+ )
57
+ end
58
+
59
+ def delete_channel(chan)
60
+ send_message(:post, :delete_channel,
61
+ :params => {
62
+ :topic => topic,
63
+ :channel => chan
64
+ }
65
+ )
66
+ end
67
+
68
+ def empty_topic
69
+ send_message(:post, :empty_topic,
70
+ :params => {:topic => topic}
71
+ )
72
+ end
73
+
74
+ def empty_channel(chan)
75
+ send_message(:post, :empty_channel,
76
+ :params => {
77
+ :topic => topic,
78
+ :channel => chan
79
+ }
80
+ )
81
+ end
82
+
83
+ def pause_channel(chan)
84
+ send_message(:post, :pause_channel,
85
+ :params => {
86
+ :topic => topic,
87
+ :channel => chan
88
+ }
89
+ )
90
+ end
91
+
92
+ def unpause_channel(chan)
93
+ send_message(:post, :unpause_channel,
94
+ :params => {
95
+ :topic => topic,
96
+ :channel => chan
97
+ }
98
+ )
99
+ end
100
+
101
+ def stats(format='json')
102
+ send_message(:get, :stats,
103
+ :params => {
104
+ :format => format
105
+ }
106
+ )
107
+ end
108
+
109
+ def ping
110
+ send_message(:get, :ping)
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -1,6 +1,8 @@
1
1
  module Krakow
2
2
  class Producer
3
3
 
4
+ autoload :Http, 'krakow/producer/http'
5
+
4
6
  include Utils::Lazy
5
7
  include Celluloid
6
8
 
@@ -19,9 +19,13 @@ module Krakow
19
19
  # error if not found
20
20
  def required!(*args)
21
21
  args.each do |key|
22
- unless(arguments.has_key?(key.to_sym))
22
+ key = key.to_sym
23
+ unless(arguments.has_key?(key))
23
24
  raise ArgumentError.new "Missing required option `#{key}`!"
24
25
  end
26
+ define_singleton_method(key) do
27
+ arguments[key]
28
+ end
25
29
  end
26
30
  end
27
31
 
@@ -33,6 +37,9 @@ module Krakow
33
37
  unless(arguments.has_key?(key))
34
38
  arguments[key] = nil
35
39
  end
40
+ define_singleton_method(key) do
41
+ arguments[key]
42
+ end
36
43
  end
37
44
  end
38
45
 
@@ -44,20 +51,6 @@ module Krakow
44
51
  "<#{self.class.name}:#{object_id} [#{arguments.inspect}]>"
45
52
  end
46
53
 
47
- def method_missing(*args)
48
- key = args.first.to_sym
49
- if(arguments.has_key?(key))
50
- arguments[key]
51
- else
52
- super
53
- end
54
- end
55
-
56
- def respond_to_missing?(key, *args)
57
- key = key.to_sym
58
- super || arguments.has_key?(key)
59
- end
60
-
61
54
  end
62
55
  end
63
56
  end
@@ -3,5 +3,5 @@ require 'krakow'
3
3
  module Krakow
4
4
  class Version < Gem::Version
5
5
  end
6
- VERSION = Version.new('0.1.0')
6
+ VERSION = Version.new('0.1.2')
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: krakow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-28 00:00:00.000000000 Z
12
+ date: 2014-02-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: celluloid-io
@@ -74,6 +74,7 @@ files:
74
74
  - lib/krakow/utils/lazy.rb
75
75
  - lib/krakow/utils/logging.rb
76
76
  - lib/krakow/producer.rb
77
+ - lib/krakow/producer/http.rb
77
78
  - lib/krakow/distribution/default.rb
78
79
  - lib/krakow/frame_type/response.rb
79
80
  - lib/krakow/frame_type/error.rb