hutch-schedule 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb70893978620f96f7701d003b06812d0b1de18c
4
- data.tar.gz: 739355b288a4be2a3e54cc116a3193ec7edddff1
3
+ metadata.gz: 5cdb316a95fe95813079a1b059cd8221d1eabfec
4
+ data.tar.gz: 00057ddbb5ea2c6c810f292edf04a680439125f4
5
5
  SHA512:
6
- metadata.gz: dda680d37cf8846741fa624db5e0b3d656f2dc3e7a2f1d14a347b04185f648bf81dfaf7e57b37df6a22507815c547a837efb1fabaf5e31600d7deb03652844f0
7
- data.tar.gz: 32afd0caa0ff7b1510c4158344b1e164576eb4971e4778b5f3568410dd7d04fdc1bfaad6822229ca43883e5ee59c11a4b6140dcd800abe27e3701dfa471fee67
6
+ metadata.gz: 693efd110acf103a7d6513bfab197460f051676a1266c33a75f12936870b44c31fdaa1b67b20cd63adde014063a9d33eb522e3de26c24dce6628a41fd2c3fa39
7
+ data.tar.gz: 0b4cd7093767aba2b28128ca4fed1d8579354f23082a220c769e4f7ed707a9404e9363aaedf6efa041254a156de3eef3cbfb1e98c9ec5fe328a7223f60d9c76e
data/README.md CHANGED
@@ -23,9 +23,8 @@ Or install it yourself as:
23
23
  Use the code below to initialize the Hutch::Schedule
24
24
 
25
25
  ```ruby
26
- Hutch::Config.setup_procs << -> {
27
- Hutch::Schedule.connect(Hutch.broker)
28
- }
26
+ Hutch.connect
27
+ Hutch::Schedule.connect
29
28
  ```
30
29
 
31
30
  They will do something below:
@@ -34,6 +33,71 @@ They will do something below:
34
33
  2. Declear an queue named `<hutch>_schedule_queue` and with some params:
35
34
  - Set `x-dead-letter-exchange: <hutch>`: let queue republish message to default <hutch> exchange.
36
35
  - Set `x-message-ttl: <30.days>`: to avoid the queue is to large, because there is no consumer with this queue.
36
+ 3. If ActiveJob is loaded. it will use `ActiveJob::Base.descendants` to register all ActiveJob class to one-job-per-consumer to Hutch::Consumer
37
+
38
+
39
+ ### Error Retry
40
+ If you want use error retry, then:
41
+
42
+ 1. Add `Hutch::ErrorHandlers::MaxRetry` to `Hutch::Config.error_handlers` like below
43
+ ```ruby
44
+ Hutch::Config.error_handlers << Hutch::ErrorHandlers::MaxRetry.new
45
+ ```
46
+
47
+ 2. Let `Hutch::Consumer` to include `Hutch::Enqueue` and setup `attempts`
48
+ ```ruby
49
+ class PlanConsumer
50
+ include Hutch::Consumer
51
+ include Hutch::Enqueue
52
+
53
+ attempts 3
54
+ consume 'abc.plan'
55
+ end
56
+ ```
57
+
58
+ Error retry will use ActiveJob `exponentially_longer` algorithm `(executes**4) + 2` seconds
59
+
60
+
61
+ ## Rails
62
+
63
+ ### Work with Hutch it`s self
64
+ Add an `hutch.rb` to `conf/initializers`:
65
+ ```ruby
66
+ # reuse Hutch config.yaml file
67
+ Hutch::Config.load_from_file(Rails.root.join('config', 'config.yaml'))
68
+ # replace error_handlers with Hutch::ErrorHandlers::MaxRetry
69
+ Hutch::Config.error_handlers = [Hutch::ErrorHandlers::MaxRetry.new]
70
+ # Init Hutch
71
+ Hutch.connect
72
+ # Init Hutch::Schedule
73
+ Hutch::Schedule.connect
74
+ ```
75
+
76
+ Then you can enqueue message in Rails console like below:
77
+ ```ruby
78
+ PlanConsumer.enqueue(a: 1)
79
+ # or schedule message
80
+ PlanConsumer.enqueue_in(5.seconds, a: 1)
81
+ ```
82
+
83
+ ### Work with ActiveJob
84
+ ```ruby
85
+ class EmailJob < ApplicationJob
86
+ queue_as :email
87
+
88
+ retry_on StandardError, wait: :exponentially_longer
89
+
90
+ def perform(user_id)
91
+ user = User.find(user_id)
92
+ user.send_email
93
+ end
94
+ end
95
+
96
+ # in rails console, you can
97
+ EmailJob.perform_later(user.id)
98
+ # or
99
+ EmailJob.set(wait: 5.seconds).perform_later(user.id)
100
+ ```
37
101
 
38
102
  ## Development
39
103
 
@@ -6,7 +6,7 @@ require 'hutch/schedule/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "hutch-schedule"
8
8
  spec.version = Hutch::Schedule::VERSION
9
- spec.authors = ["wyatt pan"]
9
+ spec.authors = ["Wyatt pan"]
10
10
  spec.email = ["wppurking@gmail.com"]
11
11
 
12
12
  spec.summary = %q{Add Schedule and Error Retry To Hutch.}
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.require_paths = ["lib"]
23
23
 
24
24
  spec.add_runtime_dependency 'hutch', '~> 0.24'
25
+ spec.add_runtime_dependency "multi_json"
25
26
 
26
27
  spec.add_development_dependency "activejob"
27
28
  spec.add_development_dependency "bundler", "~> 1.14"
@@ -56,6 +56,11 @@ module ActiveJob
56
56
  define_method :process do |job_data|
57
57
  ActiveJob::Base.execute(job_data)
58
58
  end
59
+
60
+ # inspect name
61
+ define_method :inspect do
62
+ "#{job.queue_name}-anonymous-consumer"
63
+ end
59
64
  end
60
65
  end
61
66
  end
data/lib/hutch/enqueue.rb CHANGED
@@ -17,16 +17,16 @@ module Hutch
17
17
  # publish message at a delay times
18
18
  # interval: delay interval
19
19
  # message: publish message
20
- def enqueue_in(interval, message)
21
- props = { expiration: interval.in_milliseconds.to_i }
22
- Hutch::Schedule.publish(enqueue_routing_key, message, props)
20
+ def enqueue_in(interval, message, props = {})
21
+ properties = props.merge(expiration: interval.in_milliseconds.to_i)
22
+ Hutch::Schedule.publish(enqueue_routing_key, message, properties)
23
23
  end
24
24
 
25
25
  # delay at exatly time point
26
- def enqueue_at(time, message)
26
+ def enqueue_at(time, message, props = {})
27
27
  # if time is early then now then just delay 1 second
28
28
  interval = [(time.utc - Time.now.utc), 1.second].max
29
- enqueue_in(interval, message)
29
+ enqueue_in(interval, message, props)
30
30
  end
31
31
 
32
32
  # routing_key: the purpose is to send message to hutch exchange and then routing to the correct queue,
@@ -35,6 +35,14 @@ module Hutch
35
35
  raise "Routing Keys is not set!" if routing_keys.size < 1
36
36
  routing_keys.to_a.last
37
37
  end
38
+
39
+ def attempts(times)
40
+ @max_retries = [times, 0].max
41
+ end
42
+
43
+ def max_attempts
44
+ @max_retries || 0
45
+ end
38
46
  end
39
47
  end
40
48
  end
@@ -1,15 +1,14 @@
1
1
  require 'hutch/logging'
2
+ require 'active_support/core_ext/object/blank'
2
3
 
3
4
  module Hutch
4
5
  module ErrorHandlers
6
+
7
+ # When reach the Max Attempts, republish this message to RabbitMQ,
8
+ # And persisted the properties[:headers] to tell RabbitMQ the `x-dead.count`
5
9
  class MaxRetry
6
10
  include Logging
7
11
 
8
- # TODO: Need to be implement.
9
- # 1. 获取 hutch 本身记录的 x-death 中的错误次数
10
- # 2. 从每一个 consumer 身上寻找 max_retry 的次数, 不超过则进行延迟重试
11
- # 3. 根据错误次数计算类似 active_job 的 exponentially_longer 延迟时间
12
- #
13
12
  # properties.headers example:
14
13
  # {
15
14
  # "x-death": [
@@ -37,7 +36,43 @@ module Hutch
37
36
  # ]
38
37
  # }
39
38
  def handle(properties, payload, consumer, ex)
40
- raise "Not implement"
39
+ unless consumer.ancestors.include?(Hutch::Enqueue)
40
+ logger.warn("Consumer: #{consumer} is not include Hutch::Enqueue can`t use #enqueue_in`")
41
+ return false
42
+ end
43
+
44
+ prop_headers = properties[:headers] || {}
45
+ attempts = failure_count(prop_headers, consumer) + 1
46
+ if attempts <= consumer.max_attempts
47
+ logger.debug("retrying, count=#{attempts}, headers:#{prop_headers}")
48
+ # execute_times = attempts - 1
49
+ consumer.enqueue_in(retry_delay(attempts - 1), MultiJson.decode(payload), { headers: prop_headers })
50
+ else
51
+ logger.debug("failing, retry_count=#{attempts}, ex:#{ex}")
52
+ end
53
+ end
54
+
55
+ def retry_delay(executes)
56
+ (executes**4) + 2
57
+ end
58
+
59
+ def failure_count(headers, consumer)
60
+ if headers.nil? || headers['x-death'].nil?
61
+ 0
62
+ else
63
+ x_death_array = headers['x-death'].select do |x_death|
64
+ # http://ruby-doc.org/stdlib-2.2.3/libdoc/set/rdoc/Set.html#method-i-intersect-3F
65
+ (x_death['routing-keys'].presence || []).to_set.intersect?(consumer.routing_keys)
66
+ end
67
+
68
+ if x_death_array.count > 0 && x_death_array.first['count']
69
+ # Newer versions of RabbitMQ return headers with a count key
70
+ x_death_array.inject(0) { |sum, x_death| sum + x_death['count'] }
71
+ else
72
+ # Older versions return a separate x-death header for each failure
73
+ x_death_array.count
74
+ end
75
+ end
41
76
  end
42
77
  end
43
78
  end
@@ -2,6 +2,7 @@ require 'active_support/core_ext/module/delegation'
2
2
  require 'active_support/core_ext/object/blank'
3
3
  require 'hutch'
4
4
  require 'hutch/enqueue'
5
+ require 'hutch/error_handlers/max_retry'
5
6
  require 'hutch/schedule/core'
6
7
 
7
8
  # If ActiveJob is requried then required the adapter
@@ -16,6 +17,7 @@ module Hutch
16
17
  module Schedule
17
18
 
18
19
  def self.connect(broker = Hutch.broker)
20
+ raise "Please invoke Hutch.connect before Hutch::Schedule.connect, Hutch::Schedule need Hutch.broker" unless Hutch.connected?
19
21
  return if core.present?
20
22
  @core = Hutch::Schedule::Core.new(broker)
21
23
  @core.connect!
@@ -1,5 +1,5 @@
1
1
  module Hutch
2
2
  module Schedule
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hutch-schedule
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
- - wyatt pan
7
+ - Wyatt pan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.24'
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: activejob
29
43
  requirement: !ruby/object:Gem::Requirement