resque_jobs_tree 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/resque_jobs_tree/job.rb +9 -7
- data/lib/resque_jobs_tree/node.rb +4 -0
- data/lib/resque_jobs_tree/storage.rb +45 -25
- data/lib/resque_jobs_tree/tree.rb +8 -2
- data/lib/resque_jobs_tree/version.rb +1 -1
- data/test/job_test.rb +1 -1
- data/test/node_test.rb +34 -0
- data/test/storage_test.rb +15 -0
- metadata +1 -1
data/lib/resque_jobs_tree/job.rb
CHANGED
@@ -3,15 +3,17 @@ class ResqueJobsTree::Job
|
|
3
3
|
class << self
|
4
4
|
|
5
5
|
def perform *args
|
6
|
-
node, resources =
|
6
|
+
node, resources = node_and_resources(args)
|
7
7
|
node.perform.call resources
|
8
8
|
end
|
9
9
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def after_perform_enqueue_parent *args
|
13
|
-
node, resources =
|
14
|
-
|
13
|
+
node, resources = node_and_resources(args)
|
14
|
+
if node.root?
|
15
|
+
ResqueJobsTree::Storage.release_launch node.tree, resources
|
16
|
+
else
|
15
17
|
ResqueJobsTree::Storage.remove(node, resources) do
|
16
18
|
parent_job_args = ResqueJobsTree::Storage.parent_job_args node, resources
|
17
19
|
Resque.enqueue_to node.tree.name, ResqueJobsTree::Job, *parent_job_args
|
@@ -20,20 +22,20 @@ class ResqueJobsTree::Job
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def on_failure_cleanup exception, *args
|
23
|
-
node, resources =
|
25
|
+
node, resources = node_and_resources args
|
24
26
|
if node.options[:continue_on_failure]
|
25
27
|
begin
|
26
28
|
after_perform_enqueue_parent *args
|
27
29
|
ensure
|
28
|
-
ResqueJobsTree::Storage.
|
30
|
+
ResqueJobsTree::Storage.failure_cleanup node, resources
|
29
31
|
end
|
30
32
|
else
|
31
|
-
ResqueJobsTree::Storage.
|
33
|
+
ResqueJobsTree::Storage.failure_cleanup node, resources, global: true
|
32
34
|
raise exception
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
def
|
38
|
+
def node_and_resources args
|
37
39
|
tree_name , job_name = args[0..1]
|
38
40
|
tree = ResqueJobsTree::Factory.find_tree_by_name tree_name
|
39
41
|
node = tree.find_node_by_name job_name
|
@@ -2,32 +2,35 @@
|
|
2
2
|
module ResqueJobsTree::Storage
|
3
3
|
extend self
|
4
4
|
|
5
|
-
PARENTS_KEY = "
|
5
|
+
PARENTS_KEY = "ResqueJobsTree:Node:Parents"
|
6
|
+
LAUNCHED_TREES = "ResqueJobsTree:Tree:Launched"
|
6
7
|
|
7
8
|
def store node, resources, parent, parent_resources
|
8
9
|
node_key = key node, resources
|
9
10
|
parent_key = key parent, parent_resources
|
10
|
-
|
11
|
+
redis.hset PARENTS_KEY, node_key, parent_key
|
11
12
|
childs_key = childs_key parent, parent_resources
|
12
|
-
unless
|
13
|
+
unless redis.sadd childs_key, node_key
|
13
14
|
raise ResqueJobsTree::JobNotUniq,
|
14
15
|
"Job #{parent.name} already has the child #{node.name} with resources: #{resources}"
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
19
|
def remove node, resources
|
19
|
-
|
20
|
+
lock node, resources do
|
20
21
|
siblings_key = siblings_key node, resources
|
21
|
-
|
22
|
-
yield if
|
22
|
+
redis.srem siblings_key, key(node, resources)
|
23
|
+
yield if redis.scard(siblings_key) == 0 && block_given?
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
+
def failure_cleanup node, resources, options={}
|
27
28
|
cleanup_childs node, resources
|
28
|
-
|
29
|
+
if node.root?
|
30
|
+
release_launch node.tree, resources
|
31
|
+
else
|
29
32
|
remove_from_siblings node, resources
|
30
|
-
|
33
|
+
options[:global] ? cleanup_parent(node, resources, options) : remove_parent_key(node, resources)
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
@@ -35,13 +38,21 @@ module ResqueJobsTree::Storage
|
|
35
38
|
args_from_key parent_key(node, resources)
|
36
39
|
end
|
37
40
|
|
41
|
+
def track_launch *tree_info
|
42
|
+
yield if redis.sadd LAUNCHED_TREES, tree_reference(*tree_info)
|
43
|
+
end
|
44
|
+
|
45
|
+
def release_launch *tree_info
|
46
|
+
redis.srem LAUNCHED_TREES, tree_reference(*tree_info)
|
47
|
+
end
|
48
|
+
|
38
49
|
private
|
39
50
|
|
40
51
|
def key node, resources
|
41
52
|
job_args = ResqueJobsTree::ResourcesSerializer.to_args(resources)
|
42
53
|
job_args.unshift node.name
|
43
54
|
job_args.unshift node.tree.name
|
44
|
-
"
|
55
|
+
"ResqueJobsTree:Node:#{job_args.to_json}"
|
45
56
|
end
|
46
57
|
|
47
58
|
def childs_key node, resources
|
@@ -50,7 +61,7 @@ module ResqueJobsTree::Storage
|
|
50
61
|
|
51
62
|
def parent_key node, resources
|
52
63
|
node_key = key node, resources
|
53
|
-
|
64
|
+
redis.hget PARENTS_KEY, node_key
|
54
65
|
end
|
55
66
|
|
56
67
|
def siblings_key node, resources
|
@@ -61,45 +72,54 @@ module ResqueJobsTree::Storage
|
|
61
72
|
"#{parent_key node, resources}:lock"
|
62
73
|
end
|
63
74
|
|
64
|
-
def
|
65
|
-
|
75
|
+
def lock *node_info
|
76
|
+
key = parent_lock_key *node_info
|
77
|
+
while !redis.setnx(key, 'locked')
|
66
78
|
sleep 0.05 # 50 ms
|
67
79
|
end
|
68
80
|
yield
|
69
81
|
ensure
|
70
|
-
|
82
|
+
redis.del key
|
71
83
|
end
|
72
84
|
|
73
85
|
def cleanup_childs *node_info
|
74
86
|
key = childs_key *node_info
|
75
|
-
|
76
|
-
|
87
|
+
redis.smembers(key).each do |child_key|
|
88
|
+
failure_cleanup *node_info_from_key(child_key)
|
77
89
|
end
|
78
|
-
|
90
|
+
redis.del key
|
79
91
|
end
|
80
92
|
|
81
|
-
def cleanup_parent
|
82
|
-
parent, parent_resources = node_info_from_key(parent_key(
|
83
|
-
remove_parent_key
|
84
|
-
|
93
|
+
def cleanup_parent node, resources, options
|
94
|
+
parent, parent_resources = node_info_from_key(parent_key(node, resources))
|
95
|
+
remove_parent_key node, resources
|
96
|
+
failure_cleanup parent, parent_resources, options
|
85
97
|
end
|
86
98
|
|
87
99
|
def remove_parent_key *node_info
|
88
|
-
|
100
|
+
redis.hdel PARENTS_KEY, key(*node_info)
|
89
101
|
end
|
90
102
|
|
91
103
|
def remove_from_siblings *node_info
|
92
|
-
|
104
|
+
redis.srem siblings_key(*node_info), key(*node_info)
|
93
105
|
end
|
94
106
|
|
95
107
|
def args_from_key key
|
96
|
-
JSON.load key.gsub(/
|
108
|
+
JSON.load key.gsub(/ResqueJobsTree:Node:/, '')
|
97
109
|
end
|
98
110
|
|
99
111
|
def node_info_from_key key
|
100
|
-
tree_name, node_name, resources = args_from_key(key)
|
112
|
+
tree_name, node_name, *resources = args_from_key(key)
|
101
113
|
node = ResqueJobsTree::Factory.find_tree_by_name(tree_name).find_node_by_name(node_name)
|
102
114
|
[node, resources]
|
103
115
|
end
|
104
116
|
|
117
|
+
def redis
|
118
|
+
Resque.redis
|
119
|
+
end
|
120
|
+
|
121
|
+
def tree_reference tree, resources
|
122
|
+
[tree.name, ResqueJobsTree::ResourcesSerializer.to_args(resources)].to_json
|
123
|
+
end
|
124
|
+
|
105
125
|
end
|
@@ -15,8 +15,10 @@ class ResqueJobsTree::Tree
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def launch *resources
|
18
|
-
|
19
|
-
|
18
|
+
ResqueJobsTree::Storage.track_launch self, resources do
|
19
|
+
@root.launch resources
|
20
|
+
enqueue_leaves_jobs
|
21
|
+
end
|
20
22
|
end
|
21
23
|
|
22
24
|
def enqueue *job_args
|
@@ -37,6 +39,10 @@ class ResqueJobsTree::Tree
|
|
37
39
|
[root, root.nodes].flatten
|
38
40
|
end
|
39
41
|
|
42
|
+
def inspect
|
43
|
+
"<ResqueJobsTree::Tree @name=#{name}>"
|
44
|
+
end
|
45
|
+
|
40
46
|
private
|
41
47
|
|
42
48
|
def enqueue_leaves_jobs
|
data/test/job_test.rb
CHANGED
@@ -9,7 +9,7 @@ class JobTest < MiniTest::Unit::TestCase
|
|
9
9
|
|
10
10
|
def test_tree_node_and_resources
|
11
11
|
result = [@tree.find_node_by_name('job1'), [1, 2, 3]]
|
12
|
-
assert_equal result, ResqueJobsTree::Job.send(:
|
12
|
+
assert_equal result, ResqueJobsTree::Job.send(:node_and_resources, @args)
|
13
13
|
end
|
14
14
|
|
15
15
|
end
|
data/test/node_test.rb
CHANGED
@@ -137,6 +137,40 @@ class NodeTest < MiniTest::Unit::TestCase
|
|
137
137
|
assert_equal [], Resque.keys
|
138
138
|
end
|
139
139
|
|
140
|
+
def test_leaf_failure
|
141
|
+
tree = ResqueJobsTree::Factory.create :tree1 do
|
142
|
+
root :job1 do
|
143
|
+
perform {}
|
144
|
+
childs { [:job2] }
|
145
|
+
node :job2 do
|
146
|
+
perform { raise 'an unexpected failure' }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
resources = [1, 2, 3]
|
151
|
+
assert_raises RuntimeError, 'an unexpected failure' do
|
152
|
+
tree.launch *resources
|
153
|
+
end
|
154
|
+
assert_equal [], Resque.keys
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_root_failure
|
158
|
+
tree = ResqueJobsTree::Factory.create :tree1 do
|
159
|
+
root :job1 do
|
160
|
+
perform { raise 'an unexpected failure' }
|
161
|
+
childs { [:job2] }
|
162
|
+
node :job2 do
|
163
|
+
perform {}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
resources = [1, 2, 3]
|
168
|
+
assert_raises RuntimeError, 'an unexpected failure' do
|
169
|
+
tree.launch resources
|
170
|
+
end
|
171
|
+
assert_equal [], Resque.keys
|
172
|
+
end
|
173
|
+
|
140
174
|
private
|
141
175
|
|
142
176
|
def create_async_tree
|
data/test/storage_test.rb
CHANGED
@@ -50,6 +50,21 @@ class StorageTest < MiniTest::Unit::TestCase
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
+
def test_node_info_from_key
|
54
|
+
key = %Q{ResqueJobsTree:Node:["tree1","job1",1,2,3]}
|
55
|
+
result = [@root, [1, 2, 3]]
|
56
|
+
assert_equal result, ResqueJobsTree::Storage.send(:node_info_from_key, key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_track_launch
|
60
|
+
resources = [1, 2, 3]
|
61
|
+
ResqueJobsTree::Storage.track_launch(@tree, resources) {}
|
62
|
+
key = ResqueJobsTree::Storage::LAUNCHED_TREES
|
63
|
+
assert_equal [[@tree.name, resources].to_json], redis.smembers(key)
|
64
|
+
ResqueJobsTree::Storage.release_launch(@tree, resources)
|
65
|
+
assert_equal [], redis.smembers(key)
|
66
|
+
end
|
67
|
+
|
53
68
|
private
|
54
69
|
|
55
70
|
def store
|