chore-core 1.9.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0800ed952e9c16ccde3058ff9db8a4b9a72adf44
4
- data.tar.gz: b24b312e93158e7942ae11896d9680f9f8a7714d
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OWVhNTgxZTYxMzNkNDVjODU1NTI5YmJjOGYxOWU4NmUzNDkzMDljYw==
5
+ data.tar.gz: !binary |-
6
+ ZTliZWFkNzRhZDZlZDBiZWZlNjg4MDcxNDEzMTlhNDkwNmJkOWU4NA==
5
7
  SHA512:
6
- metadata.gz: 86f131b78103e360f0219b7b55a53da68338e676791dedcea638130e38e132665a4d33cf2bd0f90e78957184dd8b9d19ea88b7801866097b276d113debb654bd
7
- data.tar.gz: 5f32aa7d1db7aab0579b1bfb43c31497502f76964e71e4d9faf0d6c1230be157901694602cb8a76f82e6c7627d2a74337e82f377a71652f03cbd18f1dfd4b23f
8
+ metadata.gz: !binary |-
9
+ MDJkYmVlYWY4YTljMTA4ZjkxNGFlNGYxMDRiYThhNTJiYTJjNjhkYzVkNWFl
10
+ YTUzNjBlMDIxYjM1MTcxNjE4NzU1YjdkNGVmOWY1Y2IzODg1MTQ5YjIxZGUx
11
+ ZWI5N2U4ZGQwZGRhYTVkZTRiZmMxYzY0NGM1MjQ1YzM5NmJiZTM=
12
+ data.tar.gz: !binary |-
13
+ Mjc2ODk3ZmFjZjc2ZDdiODlkMTA4ODQzM2I1N2JiZDI5M2QwZjU2ODNkOWRl
14
+ NGY4ZDg3NDRiMDAzMTUxNzM0MGQxNTNiNDM4ZmMwMmVhOTQzZTgwOTc4ZTc2
15
+ MTg1YjI4Y2M1ZjE2YTM5ZDJiNTMwY2NkMjNjNTlmMTAyZmM1YTQ=
data/README.md CHANGED
@@ -33,6 +33,7 @@ Other options include:
33
33
  --threads-per-queue 4 # number of threads per queue for consuming from a given queue.
34
34
  --dedupe-servers # if using SQS or similiar queue with at-least once delivery and your memcache is running on something other than localhost
35
35
  --batch-size 50 # how many messages are batched together before handing them to a worker
36
+ --batch-timeout 20 # maximum number of seconds to wait until handing a message over to a worker
36
37
  --queue_prefix prefixy # A prefix to prepend to queue names, mainly for development and qa testing purposes
37
38
  --max-attempts 100 # The maximum number of times a job can be attempted
38
39
  --dupe-on-cache-failure # Determines the deduping behavior when a cache connection error occurs. When set to `false`, the message is assumed not to be a duplicate. Defaults to `false`.
@@ -92,6 +93,7 @@ Chore.configure do |c|
92
93
  c.max_attempts = 100
93
94
  ...
94
95
  c.batch_size = 50
96
+ c.batch_timeout = 20
95
97
  end
96
98
  ```
97
99
 
@@ -34,6 +34,7 @@ module Chore #:nodoc:
34
34
  :fetcher => Fetcher,
35
35
  :consumer_strategy => Strategy::ThreadedConsumerStrategy,
36
36
  :batch_size => 50,
37
+ :batch_timeout => 20,
37
38
  :log_level => Logger::WARN,
38
39
  :log_path => STDOUT,
39
40
  :default_queue_timeout => (12 * 60 * 60), # 12 hours
@@ -186,6 +187,7 @@ module Chore #:nodoc:
186
187
  # Chore.configure do |c|
187
188
  # c.consumer = Chore::Queues::SQS::Consumer
188
189
  # c.batch_size = 50
190
+ # c.batch_timeout = 20
189
191
  # end
190
192
  def self.configure(opts={})
191
193
  @config = (@config ? @config.merge_hash(opts) : Chore::Configuration.new(DEFAULT_OPTIONS.merge(opts)))
@@ -11,27 +11,27 @@ module Chore
11
11
  @size = size
12
12
  @batch = []
13
13
  @mutex = Mutex.new
14
- @last_message = nil
15
14
  @callback = nil
16
15
  @running = true
17
16
  end
18
17
 
19
18
  # The main entry point of the Batcher, <tt>schedule</tt> begins a thread with the provided +batch_timeout+
20
19
  # as the only argument. While the Batcher is running, it will attempt to check if either the batch is full,
21
- # or if the +batch_timeout+ has elapsed since the last batch was executed. If the batch is full, it will be executed.
22
- # If the +batch_timeout+ has elapsed, as soon as the next message enters the batch, it will be executed.
20
+ # or if the +batch_timeout+ has elapsed since the oldest message was added. If either case is true, the
21
+ # items in the batch will be executed.
23
22
  #
24
23
  # Calling <tt>stop</tt> will cause the thread to finish it's current check, and exit
25
- def schedule(batch_timeout=20)
24
+ def schedule(batch_timeout)
26
25
  @thread = Thread.new(batch_timeout) do |timeout|
27
26
  Chore.logger.info "Batching timeout thread starting"
28
27
  while @running do
29
28
  begin
30
- Chore.logger.debug "Last message added to batch: #{@last_message}: #{@batch.size}"
31
- if @last_message && Time.now > (@last_message + timeout)
32
- Chore.logger.debug "Batching timeout reached (#{@last_message + timeout}), current size: #{@batch.size}"
29
+ oldest_item = @batch.first
30
+ timestamp = oldest_item && oldest_item.created_at
31
+ Chore.logger.debug "Oldest message in batch: #{timestamp}, size: #{@batch.size}"
32
+ if timestamp && Time.now > (timestamp + timeout)
33
+ Chore.logger.debug "Batching timeout reached (#{timestamp + timeout}), current size: #{@batch.size}"
33
34
  self.execute(true)
34
- @last_message = nil
35
35
  end
36
36
  sleep(1)
37
37
  rescue => e
@@ -44,7 +44,6 @@ module Chore
44
44
  # Adds the +item+ to the current batch
45
45
  def add(item)
46
46
  @batch << item
47
- @last_message = Time.now
48
47
  execute if ready?
49
48
  end
50
49
 
@@ -5,13 +5,14 @@ module Chore
5
5
  attr_accessor :batcher
6
6
 
7
7
  Chore::CLI.register_option 'batch_size', '--batch-size SIZE', Integer, 'Number of items to collect for a single worker to process'
8
+ Chore::CLI.register_option 'batch_timeout', '--batch-timeout SIZE', Integer, 'Maximum number of seconds to wait until processing a message'
8
9
  Chore::CLI.register_option 'threads_per_queue', '--threads-per-queue NUM_THREADS', Integer, 'Number of threads to create for each named queue'
9
10
 
10
11
  def initialize(fetcher)
11
12
  @fetcher = fetcher
12
13
  @batcher = Batcher.new(Chore.config.batch_size)
13
14
  @batcher.callback = lambda { |batch| @fetcher.manager.assign(batch) }
14
- @batcher.schedule
15
+ @batcher.schedule(Chore.config.batch_timeout)
15
16
  @running = true
16
17
  end
17
18
 
@@ -9,6 +9,14 @@ module Chore
9
9
  # * +:consumer+ The consumer instance used to fetch this message. Most queue implementations won't need access to this, but some (RabbitMQ) will. So we
10
10
  # make sure to pass it along with each message. This instance will be used by the Worker for things like <tt>complete</tt> and </tt>reject</tt>.
11
11
  class UnitOfWork < Struct.new(:id,:queue_name,:queue_timeout,:message,:previous_attempts,:consumer,:decoded_message, :klass)
12
+ # The time at which this unit of work was created
13
+ attr_accessor :created_at
14
+
15
+ def initialize(*) #:nodoc:
16
+ super
17
+ @created_at = Time.now
18
+ end
19
+
12
20
  # The current attempt number for the worker processing this message.
13
21
  def current_attempt
14
22
  previous_attempts + 1
@@ -1,7 +1,7 @@
1
1
  module Chore
2
2
  module Version #:nodoc:
3
3
  MAJOR = 1
4
- MINOR = 9
4
+ MINOR = 10
5
5
  PATCH = 0
6
6
 
7
7
  STRING = [ MAJOR, MINOR, PATCH ].join('.')
@@ -90,4 +90,54 @@ describe Chore::Strategy::Batcher do
90
90
  subject.batch.should == ['test']
91
91
  end
92
92
  end
93
+
94
+ describe 'schedule' do
95
+ let(:timeout) { 5 }
96
+ let(:batch) { [] }
97
+
98
+ before(:each) do
99
+ Thread.stub(:new) do |&block|
100
+ # Stop the batcher on the next iteration
101
+ subject.stub(:sleep) { subject.stop }
102
+
103
+ # Run the scheduling thread
104
+ block.call(timeout)
105
+ end
106
+
107
+ subject.batch = batch.dup
108
+ end
109
+
110
+ context 'with no items' do
111
+ it 'should not invoke the callback' do
112
+ callback.should_not_receive(:call)
113
+ subject.schedule(timeout)
114
+ end
115
+ end
116
+
117
+ context 'with new items' do
118
+ let(:batch) do
119
+ [
120
+ Chore::UnitOfWork.new.tap {|work| work.created_at = Time.now - 2}
121
+ ]
122
+ end
123
+
124
+ it 'should not invoke the callback' do
125
+ callback.should_not_receive(:call).with(batch)
126
+ subject.schedule(timeout)
127
+ end
128
+ end
129
+
130
+ context 'with old items' do
131
+ let(:batch) do
132
+ [
133
+ Chore::UnitOfWork.new.tap {|work| work.created_at = Time.now - 6}
134
+ ]
135
+ end
136
+
137
+ it 'should invoke the callback' do
138
+ callback.should_receive(:call).with(batch)
139
+ subject.schedule(timeout)
140
+ end
141
+ end
142
+ end
93
143
  end
@@ -51,6 +51,7 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
51
51
  work.message.should == "test"
52
52
  work.previous_attempts.should == 0
53
53
  work.current_attempt.should == 1
54
+ work.created_at.should_not be_nil
54
55
  end
55
56
  end
56
57
 
metadata CHANGED
@@ -1,103 +1,103 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chore-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tapjoy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-09 00:00:00.000000000 Z
11
+ date: 2016-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - ! '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: aws-sdk-v1
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.56'
34
- - - ">="
34
+ - - ! '>='
35
35
  - !ruby/object:Gem::Version
36
36
  version: 1.56.0
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
- - - "~>"
41
+ - - ~>
42
42
  - !ruby/object:Gem::Version
43
43
  version: '1.56'
44
- - - ">="
44
+ - - ! '>='
45
45
  - !ruby/object:Gem::Version
46
46
  version: 1.56.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: thread
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ~>
52
52
  - !ruby/object:Gem::Version
53
53
  version: 0.1.3
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ~>
59
59
  - !ruby/object:Gem::Version
60
60
  version: 0.1.3
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rspec
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - "~>"
65
+ - - ~>
66
66
  - !ruby/object:Gem::Version
67
67
  version: 3.3.0
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - "~>"
72
+ - - ~>
73
73
  - !ruby/object:Gem::Version
74
74
  version: 3.3.0
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: rdoc
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - "~>"
79
+ - - ~>
80
80
  - !ruby/object:Gem::Version
81
81
  version: '3.12'
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - "~>"
86
+ - - ~>
87
87
  - !ruby/object:Gem::Version
88
88
  version: '3.12'
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: bundler
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ">="
93
+ - - ! '>='
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - ">="
100
+ - - ! '>='
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
103
  description: Job processing with pluggable backends and strategies
@@ -176,12 +176,12 @@ require_paths:
176
176
  - lib
177
177
  required_ruby_version: !ruby/object:Gem::Requirement
178
178
  requirements:
179
- - - ">="
179
+ - - ! '>='
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  requirements:
184
- - - ">="
184
+ - - ! '>='
185
185
  - !ruby/object:Gem::Version
186
186
  version: '0'
187
187
  requirements: []
@@ -191,3 +191,4 @@ signing_key:
191
191
  specification_version: 4
192
192
  summary: Job processing... for the future!
193
193
  test_files: []
194
+ has_rdoc: