krakow 0.1.0 → 0.1.2

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/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