deferrer 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -12
- data/example/client.rb +1 -1
- data/example/common.rb +8 -0
- data/example/runner.rb +0 -3
- data/lib/deferrer.rb +4 -12
- data/lib/deferrer/configuration.rb +4 -1
- data/lib/deferrer/item.rb +32 -0
- data/lib/deferrer/processor.rb +23 -0
- data/lib/deferrer/queue.rb +73 -0
- data/lib/deferrer/runner.rb +4 -83
- data/lib/deferrer/version.rb +1 -1
- data/lib/deferrer/worker.rb +25 -0
- data/spec/deferrer/item_spec.rb +39 -0
- data/spec/deferrer/processor_spec.rb +9 -0
- data/spec/deferrer/queue_spec.rb +90 -0
- data/spec/deferrer/runner_spec.rb +20 -95
- data/spec/deferrer/worker_spec.rb +34 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/helpers.rb +8 -1
- metadata +14 -3
- data/lib/deferrer/json_encoding.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bc1f7b9f8b06d8c607e75b51a267f77b017e493
|
4
|
+
data.tar.gz: b5ace0f90bf10f7ab04e8bfefa63fdd22fbef02c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 424bb4d6787044ae36c91f1e05da72dc7c25700a0a9862e87a0fd493a617593046d4202cc129ba8781f5122e57664ace03e1a1ef829e88839dcc782422a25bdb
|
7
|
+
data.tar.gz: 978dbb2dceed7aa36ffe56a9906bdaba02d998b3cc168874a70fcbfb4dfeb382c6f5f0d39f6050e58ad736c418186294314608c0bdc230036e49f5cd51325f06
|
data/README.md
CHANGED
@@ -35,19 +35,21 @@ Deferrer.logger = Logger.new(STDOUT)
|
|
35
35
|
```
|
36
36
|
|
37
37
|
|
38
|
-
Define
|
38
|
+
Define Deferrer worker (must respond to peform method):
|
39
39
|
|
40
40
|
```ruby
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
class WorkDeferrer
|
42
|
+
include Deferrer::Worker
|
43
|
+
|
44
|
+
def perform(*args)
|
45
|
+
end
|
44
46
|
end
|
45
47
|
```
|
46
48
|
|
47
|
-
Deferrer is usually used in combination with background processing tools like sidekiq and resque
|
49
|
+
Deferrer is usually used in combination with background processing tools like sidekiq and resque where the Deferrer worker is a light-weight worker that just pushed jobs on the queue.
|
48
50
|
|
49
51
|
|
50
|
-
Start
|
52
|
+
Start the runner.
|
51
53
|
|
52
54
|
```ruby
|
53
55
|
Deferrer.run(options = {})
|
@@ -58,20 +60,19 @@ Deferrer.run(options = {})
|
|
58
60
|
# ignore_time - don't wait for time period to expire, useful for testing
|
59
61
|
```
|
60
62
|
|
61
|
-
|
62
63
|
Defer some executions:
|
63
64
|
|
64
65
|
```ruby
|
65
|
-
|
66
|
-
|
67
|
-
|
66
|
+
WorkDeferrer.perform_in(5, 'identifier', 'update 1')
|
67
|
+
WorkDeferrer.perform_in(6, 'identifier', 'update 2')
|
68
|
+
WorkDeferrer.perform_in(9, 'identifier', 'update 3')
|
68
69
|
```
|
69
70
|
|
70
71
|
|
71
|
-
It will stack all defered
|
72
|
+
It will stack all defered work units per identifier until first timeout expires (5 seconds) and then it will only process the last update for the expired identifier, calling the deferrer worker:
|
72
73
|
|
73
74
|
```ruby
|
74
|
-
|
75
|
+
WorkDeferrer.new.perform('update 3')
|
75
76
|
```
|
76
77
|
|
77
78
|
|
data/example/client.rb
CHANGED
data/example/common.rb
CHANGED
data/example/runner.rb
CHANGED
data/lib/deferrer.rb
CHANGED
@@ -3,20 +3,12 @@ require "deferrer/version"
|
|
3
3
|
|
4
4
|
module Deferrer
|
5
5
|
autoload :Configuration, 'deferrer/configuration'
|
6
|
-
autoload :JsonEncoding, 'deferrer/json_encoding'
|
7
6
|
autoload :Runner, 'deferrer/runner'
|
8
|
-
autoload :
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
autoload :Worker, 'deferrer/worker'
|
8
|
+
autoload :Item, 'deferrer/item'
|
9
|
+
autoload :Queue, 'deferrer/queue'
|
10
|
+
autoload :Processor, 'deferrer/processor'
|
12
11
|
|
13
12
|
extend Configuration
|
14
|
-
extend JsonEncoding
|
15
13
|
extend Runner
|
16
|
-
|
17
|
-
class WorkerNotConfigured < NotImplementedError
|
18
|
-
def initialize
|
19
|
-
super("Deferrer worker not configured")
|
20
|
-
end
|
21
|
-
end
|
22
14
|
end
|
@@ -3,12 +3,15 @@ module Deferrer
|
|
3
3
|
|
4
4
|
attr_reader :redis
|
5
5
|
attr_accessor :logger
|
6
|
-
attr_accessor :worker
|
7
6
|
attr_accessor :inline
|
8
7
|
|
9
8
|
# Deferrer.redis_config = { :host => "localhost", :port => 6379 }
|
10
9
|
def redis_config=(config)
|
11
10
|
@redis = Redis.new(config)
|
12
11
|
end
|
12
|
+
|
13
|
+
def log(type, message)
|
14
|
+
logger.send(type, message) if logger
|
15
|
+
end
|
13
16
|
end
|
14
17
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Deferrer
|
4
|
+
class Item
|
5
|
+
|
6
|
+
attr_reader :id, :class_name, :args
|
7
|
+
|
8
|
+
def self.from_json(json)
|
9
|
+
item = MultiJson.load(json)
|
10
|
+
new(item['id'], item['class'], item['args'])
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(id, class_name, args)
|
14
|
+
@id = id
|
15
|
+
@class_name = class_name
|
16
|
+
@args = args
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_json
|
20
|
+
MultiJson.dump(to_hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
{ 'id' => id, 'class' => class_name, 'args' => args }
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(object)
|
28
|
+
object.id == self.id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Deferrer
|
2
|
+
class Processor
|
3
|
+
|
4
|
+
def initialize(item)
|
5
|
+
@item = item
|
6
|
+
end
|
7
|
+
|
8
|
+
def process
|
9
|
+
Deferrer.log(:info, "Processing: #{@item.id}")
|
10
|
+
klass = constantize(@item.class_name)
|
11
|
+
klass.new.send(:perform, *@item.args)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def constantize(klass_string)
|
16
|
+
klass_string.split('::').inject(Object) do |object, name|
|
17
|
+
object = object.const_get(name)
|
18
|
+
object
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Deferrer
|
2
|
+
class Queue
|
3
|
+
LIST_KEY = 'deferred_list'
|
4
|
+
ITEM_KEY_PREFIX = 'deferred'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def push(item, timestamp)
|
8
|
+
key = item_key(item.id)
|
9
|
+
count = redis.rpush(key, item.to_json)
|
10
|
+
|
11
|
+
# set score only on first update
|
12
|
+
if count == 1
|
13
|
+
score = calculate_score(timestamp)
|
14
|
+
redis.zadd(LIST_KEY, score, key)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def pop(ignore_time = false)
|
19
|
+
find(next_key(ignore_time))
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_by_id(id)
|
23
|
+
return nil unless id
|
24
|
+
find(item_key(id))
|
25
|
+
end
|
26
|
+
|
27
|
+
def find(key)
|
28
|
+
return nil unless key
|
29
|
+
|
30
|
+
item = nil
|
31
|
+
|
32
|
+
if json = redis.rpop(key)
|
33
|
+
item = Item.from_json(json)
|
34
|
+
end
|
35
|
+
|
36
|
+
remove(key)
|
37
|
+
|
38
|
+
item
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def next_key(ignore_time)
|
43
|
+
if ignore_time
|
44
|
+
redis.zrange(LIST_KEY, 0, 1).first
|
45
|
+
else
|
46
|
+
score = calculate_score(Time.now)
|
47
|
+
redis.zrangebyscore(LIST_KEY, '-inf', score, :limit => [0, 1]).first
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def remove(key)
|
52
|
+
redis.watch(key)
|
53
|
+
redis.multi do
|
54
|
+
redis.del(key)
|
55
|
+
redis.zrem(LIST_KEY, key)
|
56
|
+
end
|
57
|
+
redis.unwatch
|
58
|
+
end
|
59
|
+
|
60
|
+
def calculate_score(timestamp)
|
61
|
+
timestamp.to_f
|
62
|
+
end
|
63
|
+
|
64
|
+
def item_key(id)
|
65
|
+
"#{ITEM_KEY_PREFIX}:#{id}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def redis
|
69
|
+
Deferrer.redis
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/deferrer/runner.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
module Deferrer
|
2
2
|
module Runner
|
3
|
+
|
3
4
|
def run(options = {})
|
4
5
|
loop_frequency = options.fetch(:loop_frequency, 0.1)
|
5
6
|
single_run = options.fetch(:single_run, false)
|
6
|
-
|
7
|
-
@inline = options.fetch(:inline, false)
|
8
|
-
|
9
|
-
raise WorkerNotConfigured unless worker
|
7
|
+
ignore_time = options.fetch(:ignore_time, false)
|
10
8
|
|
11
9
|
loop do
|
12
10
|
begin
|
13
|
-
while item =
|
14
|
-
|
11
|
+
while item = Deferrer::Queue.pop(ignore_time)
|
12
|
+
Processor.new(item).process
|
15
13
|
end
|
16
14
|
rescue StandardError => e
|
17
15
|
log(:error, "Error: #{e.class}: #{e.message}")
|
@@ -24,82 +22,5 @@ module Deferrer
|
|
24
22
|
sleep loop_frequency
|
25
23
|
end
|
26
24
|
end
|
27
|
-
|
28
|
-
def next_item(key = next_key)
|
29
|
-
return nil unless key
|
30
|
-
|
31
|
-
item = nil
|
32
|
-
decoded_item = nil
|
33
|
-
|
34
|
-
item = redis.rpop(key)
|
35
|
-
if item
|
36
|
-
decoded_item = decode(item)
|
37
|
-
decoded_item['key'] = key
|
38
|
-
end
|
39
|
-
|
40
|
-
remove(key)
|
41
|
-
|
42
|
-
decoded_item
|
43
|
-
end
|
44
|
-
|
45
|
-
def defer_in(number_of_seconds_from_now, identifier, *args)
|
46
|
-
timestamp = Time.now + number_of_seconds_from_now
|
47
|
-
defer_at(timestamp, identifier, *args)
|
48
|
-
end
|
49
|
-
|
50
|
-
def defer_at(timestamp, identifier, *args)
|
51
|
-
key = item_key(identifier)
|
52
|
-
item = { 'args' => args }
|
53
|
-
|
54
|
-
push_item(key, item, timestamp)
|
55
|
-
|
56
|
-
process_item(next_item(key)) if @inline
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
def process_item(item)
|
61
|
-
log(:info, "Processing: #{item['key']}")
|
62
|
-
worker.call(*item['args'])
|
63
|
-
end
|
64
|
-
|
65
|
-
def item_key(identifier)
|
66
|
-
"#{ITEM_KEY_PREFIX}:#{identifier}"
|
67
|
-
end
|
68
|
-
|
69
|
-
def push_item(key, item, timestamp)
|
70
|
-
count = redis.rpush(key, encode(item))
|
71
|
-
|
72
|
-
# set score only on first update
|
73
|
-
if count == 1
|
74
|
-
score = calculate_score(timestamp)
|
75
|
-
redis.zadd(LIST_KEY, score, key)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def next_key
|
80
|
-
if @ignore_time
|
81
|
-
redis.zrange(LIST_KEY, 0, 1).first
|
82
|
-
else
|
83
|
-
score = calculate_score(Time.now)
|
84
|
-
redis.zrangebyscore(LIST_KEY, '-inf', score, :limit => [0, 1]).first
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def calculate_score(timestamp)
|
89
|
-
timestamp.to_f
|
90
|
-
end
|
91
|
-
|
92
|
-
def remove(key)
|
93
|
-
redis.watch(key)
|
94
|
-
redis.multi do
|
95
|
-
redis.del(key)
|
96
|
-
redis.zrem(LIST_KEY, key)
|
97
|
-
end
|
98
|
-
redis.unwatch
|
99
|
-
end
|
100
|
-
|
101
|
-
def log(type, message)
|
102
|
-
logger.send(type, message) if logger
|
103
|
-
end
|
104
25
|
end
|
105
26
|
end
|
data/lib/deferrer/version.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Deferrer
|
2
|
+
module Worker
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def perform_in(number_of_seconds_from_now, id, *args)
|
10
|
+
timestamp = Time.now + number_of_seconds_from_now
|
11
|
+
perform_at(timestamp, id, *args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform_at(timestamp, id, *args)
|
15
|
+
item = Item.new(id, name, args)
|
16
|
+
Deferrer::Queue.push(item, timestamp)
|
17
|
+
|
18
|
+
if Deferrer.inline
|
19
|
+
item = Deferrer::Queue.find_by_id(item.id)
|
20
|
+
Processor.new(item).process
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Deferrer::Item do
|
4
|
+
|
5
|
+
let(:item_hash) { {'id' => 1, 'class' => 'TestWorker', 'args' => [1, 2]} }
|
6
|
+
let(:item_json) { MultiJson.dump(item_hash) }
|
7
|
+
|
8
|
+
it "can create a new" do
|
9
|
+
item = Deferrer::Item.new(1, 'TestWorker', [1, 2])
|
10
|
+
expect(item.id).to eq(1)
|
11
|
+
expect(item.class_name).to eq('TestWorker')
|
12
|
+
expect(item.args).to eq([1, 2])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can create new item from json" do
|
16
|
+
item = Deferrer::Item.from_json(item_json)
|
17
|
+
|
18
|
+
expect(item.id).to eq(1)
|
19
|
+
expect(item.class_name).to eq('TestWorker')
|
20
|
+
expect(item.args).to eq([1, 2])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can test if two objects are equal" do
|
24
|
+
item1 = Deferrer::Item.new(1, 'TestWorker', [1, 2])
|
25
|
+
item2 = Deferrer::Item.from_json(item_json)
|
26
|
+
|
27
|
+
expect(item1).to eq(item2)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can convert to hash" do
|
31
|
+
item = Deferrer::Item.new(1, 'TestWorker', [1, 2])
|
32
|
+
expect(item.to_hash).to eq(item_hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can convert to json" do
|
36
|
+
item = Deferrer::Item.new(1, 'TestWorker', [1, 2])
|
37
|
+
expect(item.to_json).to eq(item_json)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Deferrer::Queue do
|
4
|
+
let(:queue) { Deferrer::Queue }
|
5
|
+
let(:id) { 'some_id' }
|
6
|
+
let(:redis) { Deferrer.redis }
|
7
|
+
let(:list_key) { Deferrer::Queue::LIST_KEY }
|
8
|
+
|
9
|
+
describe ".push" do
|
10
|
+
it "can push items" do
|
11
|
+
item1 = Deferrer::Item.new('1', 'TestWorker', [1])
|
12
|
+
item2 = Deferrer::Item.new('2', 'TestWorker', [2])
|
13
|
+
|
14
|
+
queue.push(item1, Time.now - 1)
|
15
|
+
queue.push(item2, Time.now - 2)
|
16
|
+
|
17
|
+
expect(queue.pop).to eq(item2)
|
18
|
+
expect(queue.pop).to eq(item1)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ".pop" do
|
23
|
+
it "returns the next item" do
|
24
|
+
TestWorker.perform_at(Time.now, id, 'test')
|
25
|
+
|
26
|
+
item = queue.pop
|
27
|
+
|
28
|
+
expect(item.class_name).to eq('TestWorker')
|
29
|
+
expect(item.args).to eq(['test'])
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns last update of an item" do
|
33
|
+
TestWorker.perform_at(Time.now - 3, id, 'update1')
|
34
|
+
TestWorker.perform_at(Time.now - 2, id, 'update2')
|
35
|
+
|
36
|
+
item = queue.pop
|
37
|
+
|
38
|
+
expect(item.class_name).to eq('TestWorker')
|
39
|
+
expect(item.args).to eq(['update2'])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns nil when no next item" do
|
43
|
+
expect(queue.pop).to be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "removes values from redis" do
|
47
|
+
TestWorker.perform_at(Time.now, id, 'test')
|
48
|
+
|
49
|
+
item = queue.pop
|
50
|
+
|
51
|
+
expect(redis.zrangebyscore(list_key, '-inf', Time.now.to_f, :limit => [0, 1]).first).to be_nil
|
52
|
+
expect(redis.exists(item_key(id))).to be_falsey
|
53
|
+
expect(queue.pop).to be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it "doesn't block on empty lists" do
|
57
|
+
TestWorker.perform_in(-1, id, 'test')
|
58
|
+
redis.del(item_key(id))
|
59
|
+
|
60
|
+
expect(queue.pop).to be_nil
|
61
|
+
expect(redis.zrangebyscore(list_key, '-inf', 'inf', :limit => [0, 1]).first).to be_nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ".find_by_id" do
|
66
|
+
it "can find by id" do
|
67
|
+
item1 = Deferrer::Item.new('1', 'TestWorker', [1])
|
68
|
+
item2 = Deferrer::Item.new('2', 'TestWorker', [2])
|
69
|
+
|
70
|
+
queue.push(item1, Time.now)
|
71
|
+
queue.push(item2, Time.now)
|
72
|
+
|
73
|
+
expect(queue.find_by_id(item1.id)).to eq(item1)
|
74
|
+
expect(queue.find_by_id(item2.id)).to eq(item2)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe ".find" do
|
79
|
+
it "can find by key" do
|
80
|
+
item1 = Deferrer::Item.new('1', 'TestWorker', [1])
|
81
|
+
item2 = Deferrer::Item.new('2', 'TestWorker', [2])
|
82
|
+
|
83
|
+
queue.push(item1, Time.now)
|
84
|
+
queue.push(item2, Time.now)
|
85
|
+
|
86
|
+
expect(queue.find(item_key(item1.id))).to eq(item1)
|
87
|
+
expect(queue.find(item_key(item2.id))).to eq(item2)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -2,153 +2,78 @@ require 'spec_helper'
|
|
2
2
|
require 'logger'
|
3
3
|
|
4
4
|
describe Deferrer::Runner do
|
5
|
-
let(:
|
6
|
-
let(:redis) { Deferrer.redis }
|
7
|
-
let(:list_key) { Deferrer::LIST_KEY }
|
5
|
+
let(:id) { 'some_id' }
|
8
6
|
let(:logger) { Logger.new(STDOUT) }
|
9
7
|
|
10
8
|
describe ".run" do
|
11
9
|
it "processes jobs" do
|
12
|
-
|
10
|
+
expect_any_instance_of(TestWorker).to receive(:perform).with('test')
|
13
11
|
|
14
|
-
|
12
|
+
TestWorker.perform_in(-1, id, 'test')
|
15
13
|
Deferrer.run(single_run: true)
|
16
14
|
end
|
17
15
|
|
18
16
|
it "correctly sets arguments and converts symbols to strings for hashes" do
|
19
|
-
|
17
|
+
expect_any_instance_of(TestWorker).to receive(:perform).with(1, 'arg1', { "a" => "b"})
|
20
18
|
|
21
|
-
|
19
|
+
TestWorker.perform_in(-1, id, 1, 'arg1', { a: :b })
|
22
20
|
Deferrer.run(single_run: true)
|
23
21
|
end
|
24
22
|
|
25
23
|
it "rescues standard errors" do
|
26
|
-
|
24
|
+
expect(logger).to receive(:error).with("Error: RuntimeError: error")
|
25
|
+
expect(Deferrer::Queue).to receive(:pop) { raise RuntimeError.new('error') }
|
27
26
|
|
28
|
-
|
27
|
+
TestWorker.perform_in(-1, id, 'test')
|
28
|
+
Deferrer.logger = logger
|
29
29
|
Deferrer.run(single_run: true)
|
30
30
|
end
|
31
31
|
|
32
32
|
it "rescues exceptions and logs error messages" do
|
33
33
|
expect(logger).to receive(:error).with("Error: Exception: error")
|
34
|
-
|
34
|
+
expect(Deferrer::Queue).to receive(:pop) { raise Exception.new('error') }
|
35
35
|
|
36
|
+
TestWorker.perform_in(-1, id, 'test')
|
36
37
|
Deferrer.logger = logger
|
37
|
-
Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
|
38
38
|
expect { Deferrer.run(single_run: true) }.to raise_error(Exception)
|
39
39
|
end
|
40
40
|
|
41
|
-
it "raises error if worker not configured" do
|
42
|
-
Deferrer.worker = nil
|
43
|
-
Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
|
44
|
-
|
45
|
-
expect { Deferrer.run(single_run: true) }.to raise_error(Deferrer::WorkerNotConfigured)
|
46
|
-
end
|
47
|
-
|
48
41
|
it "ignores time to wait and performs jobs" do
|
49
|
-
|
42
|
+
expect_any_instance_of(TestWorker).to receive(:perform).with({ "c" => "d"})
|
50
43
|
|
51
|
-
|
52
|
-
|
44
|
+
TestWorker.perform_in(100, id, { a: :b })
|
45
|
+
TestWorker.perform_in(100, id, { c: :d })
|
53
46
|
|
54
47
|
Deferrer.run(single_run: true, ignore_time: true)
|
55
48
|
end
|
56
49
|
end
|
57
50
|
|
58
|
-
describe ".defer_at" do
|
59
|
-
it "deferrs at given time" do
|
60
|
-
Deferrer.defer_at(Time.now, identifier, 'TestWorker', 'test')
|
61
|
-
|
62
|
-
expect(redis.zrangebyscore(list_key, '-inf', Time.now.to_f, :limit => [0, 1]).first).not_to be_nil
|
63
|
-
expect(redis.exists(item_key(identifier))).to be_truthy
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
describe ".defer_in" do
|
68
|
-
it "defers in given interval" do
|
69
|
-
Deferrer.defer_in(1, identifier, 'TestWorker', 'test')
|
70
|
-
|
71
|
-
expect(redis.zrangebyscore(list_key, '-inf', (Time.now + 1).to_f, :limit => [0, 1]).first).not_to be_nil
|
72
|
-
expect(redis.exists(item_key(identifier))).to be_truthy
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
describe ".next_item" do
|
77
|
-
it "returns the next item" do
|
78
|
-
Deferrer.defer_at(Time.now, identifier, 'TestWorker', 'test')
|
79
|
-
|
80
|
-
item = Deferrer.next_item
|
81
|
-
|
82
|
-
expect(item['args']).to eq(['TestWorker', 'test'])
|
83
|
-
end
|
84
|
-
|
85
|
-
it "returns last update of an item" do
|
86
|
-
Deferrer.defer_at(Time.now - 3, identifier, 'TestWorker', 'update1')
|
87
|
-
Deferrer.defer_at(Time.now - 2, identifier, 'TestWorker', 'update2')
|
88
|
-
|
89
|
-
item = Deferrer.next_item
|
90
|
-
|
91
|
-
expect(item['args']).to eq(['TestWorker', 'update2'])
|
92
|
-
end
|
93
|
-
|
94
|
-
it "returns nil when no next item" do
|
95
|
-
expect(Deferrer.next_item).to be_nil
|
96
|
-
end
|
97
|
-
|
98
|
-
it "removes values from redis" do
|
99
|
-
Deferrer.defer_at(Time.now, identifier, 'TestWorker', 'test')
|
100
|
-
|
101
|
-
item = Deferrer.next_item
|
102
|
-
|
103
|
-
expect(redis.zrangebyscore(list_key, '-inf', Time.now.to_f, :limit => [0, 1]).first).to be_nil
|
104
|
-
expect(redis.exists(item_key(identifier))).to be_falsey
|
105
|
-
expect(Deferrer.next_item).to be_nil
|
106
|
-
end
|
107
|
-
|
108
|
-
it "doesn't block on empty lists" do
|
109
|
-
Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
|
110
|
-
redis.del(item_key(identifier))
|
111
|
-
|
112
|
-
expect(Deferrer.next_item).to be_nil
|
113
|
-
expect(redis.zrangebyscore(list_key, '-inf', 'inf', :limit => [0, 1]).first).to be_nil
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
51
|
describe ".logger" do
|
118
52
|
before :each do
|
119
53
|
Deferrer.logger = logger
|
120
54
|
end
|
121
55
|
|
122
56
|
it "logs info messages" do
|
123
|
-
expect(logger).to receive(:info).with("Processing:
|
57
|
+
expect(logger).to receive(:info).with("Processing: #{id}")
|
124
58
|
|
125
|
-
|
59
|
+
TestWorker.perform_in(-1, id, 'test')
|
126
60
|
Deferrer.run(single_run: true)
|
127
61
|
end
|
128
62
|
|
129
63
|
it "logs error messages" do
|
130
64
|
expect(logger).to receive(:error).with("Error: RuntimeError: error")
|
131
|
-
allow(Deferrer).to receive(:
|
65
|
+
allow(Deferrer::Queue).to receive(:pop) { raise RuntimeError.new('error') }
|
132
66
|
|
133
|
-
|
67
|
+
TestWorker.perform_in(-1, id, 'test')
|
134
68
|
Deferrer.run(single_run: true)
|
135
69
|
end
|
136
70
|
|
137
71
|
it "logs error messages on exceptions" do
|
138
72
|
expect(logger).to receive(:error).with("Error: Exception: error")
|
139
|
-
allow(Deferrer).to receive(:
|
73
|
+
allow(Deferrer::Queue).to receive(:pop) { raise Exception.new('error') }
|
140
74
|
|
141
|
-
|
75
|
+
TestWorker.perform_in(-1, id, 'test')
|
142
76
|
expect { Deferrer.run(single_run: true) }.to raise_error
|
143
77
|
end
|
144
78
|
end
|
145
|
-
|
146
|
-
describe ".inline" do
|
147
|
-
it "ignores time to wait and performs jobs" do
|
148
|
-
Deferrer.inline = true
|
149
|
-
expect(Deferrer.worker).to receive(:call).with('Worker', { "c" => "d"})
|
150
|
-
|
151
|
-
Deferrer.defer_in(100, identifier, 'Worker', { c: :d })
|
152
|
-
end
|
153
|
-
end
|
154
79
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Deferrer::Worker do
|
4
|
+
let(:id) { 'some_id' }
|
5
|
+
let(:redis) { Deferrer.redis }
|
6
|
+
let(:list_key) { Deferrer::Queue::LIST_KEY }
|
7
|
+
|
8
|
+
describe ".perform_at" do
|
9
|
+
it "deferrs at given time" do
|
10
|
+
TestWorker.perform_at(Time.now, id, 'test')
|
11
|
+
|
12
|
+
expect(redis.zrangebyscore(list_key, '-inf', Time.now.to_f, :limit => [0, 1]).first).not_to be_nil
|
13
|
+
expect(redis.exists(item_key(id))).to be_truthy
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".perform_in" do
|
18
|
+
it "defers in given interval" do
|
19
|
+
TestWorker.perform_in(1, id, 'test')
|
20
|
+
|
21
|
+
expect(redis.zrangebyscore(list_key, '-inf', (Time.now + 1).to_f, :limit => [0, 1]).first).not_to be_nil
|
22
|
+
expect(redis.exists(item_key(id))).to be_truthy
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe ".inline" do
|
27
|
+
it "ignores time to wait and performs jobs" do
|
28
|
+
Deferrer.inline = true
|
29
|
+
expect_any_instance_of(TestWorker).to receive(:perform).with({ "c" => "d"})
|
30
|
+
|
31
|
+
TestWorker.perform_in(100, id, { c: :d })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/helpers.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deferrer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dalibor Nasevic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -101,10 +101,17 @@ files:
|
|
101
101
|
- example/runner.rb
|
102
102
|
- lib/deferrer.rb
|
103
103
|
- lib/deferrer/configuration.rb
|
104
|
-
- lib/deferrer/
|
104
|
+
- lib/deferrer/item.rb
|
105
|
+
- lib/deferrer/processor.rb
|
106
|
+
- lib/deferrer/queue.rb
|
105
107
|
- lib/deferrer/runner.rb
|
106
108
|
- lib/deferrer/version.rb
|
109
|
+
- lib/deferrer/worker.rb
|
110
|
+
- spec/deferrer/item_spec.rb
|
111
|
+
- spec/deferrer/processor_spec.rb
|
112
|
+
- spec/deferrer/queue_spec.rb
|
107
113
|
- spec/deferrer/runner_spec.rb
|
114
|
+
- spec/deferrer/worker_spec.rb
|
108
115
|
- spec/spec_helper.rb
|
109
116
|
- spec/support/helpers.rb
|
110
117
|
homepage: ''
|
@@ -132,7 +139,11 @@ signing_key:
|
|
132
139
|
specification_version: 4
|
133
140
|
summary: Defer work units and process only the last work unit when time comes
|
134
141
|
test_files:
|
142
|
+
- spec/deferrer/item_spec.rb
|
143
|
+
- spec/deferrer/processor_spec.rb
|
144
|
+
- spec/deferrer/queue_spec.rb
|
135
145
|
- spec/deferrer/runner_spec.rb
|
146
|
+
- spec/deferrer/worker_spec.rb
|
136
147
|
- spec/spec_helper.rb
|
137
148
|
- spec/support/helpers.rb
|
138
149
|
has_rdoc:
|