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.
@@ -3,15 +3,17 @@ class ResqueJobsTree::Job
3
3
  class << self
4
4
 
5
5
  def perform *args
6
- node, resources = tree_node_and_resources(args)
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 = tree_node_and_resources(args)
14
- unless node.root?
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 = tree_node_and_resources args
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.cleanup node, resources
30
+ ResqueJobsTree::Storage.failure_cleanup node, resources
29
31
  end
30
32
  else
31
- ResqueJobsTree::Storage.cleanup node, resources, global: true
33
+ ResqueJobsTree::Storage.failure_cleanup node, resources, global: true
32
34
  raise exception
33
35
  end
34
36
  end
35
37
 
36
- def tree_node_and_resources args
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
@@ -82,4 +82,8 @@ class ResqueJobsTree::Node
82
82
  node_childs+node_childs.map(&:nodes)
83
83
  end
84
84
 
85
+ def inspect
86
+ "<ResqueJobsTree::Node @name=#{name}>"
87
+ end
88
+
85
89
  end
@@ -2,32 +2,35 @@
2
2
  module ResqueJobsTree::Storage
3
3
  extend self
4
4
 
5
- PARENTS_KEY = "JobsTree:Node:Parents"
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
- Resque.redis.hset PARENTS_KEY, node_key, parent_key
11
+ redis.hset PARENTS_KEY, node_key, parent_key
11
12
  childs_key = childs_key parent, parent_resources
12
- unless Resque.redis.sadd childs_key, node_key
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
- lock_with parent_lock_key(node, resources) do
20
+ lock node, resources do
20
21
  siblings_key = siblings_key node, resources
21
- Resque.redis.srem siblings_key, key(node, resources)
22
- yield if Resque.redis.scard(siblings_key) == 0 && block_given?
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 cleanup node, resources, option={}
27
+ def failure_cleanup node, resources, options={}
27
28
  cleanup_childs node, resources
28
- unless node.root?
29
+ if node.root?
30
+ release_launch node.tree, resources
31
+ else
29
32
  remove_from_siblings node, resources
30
- option[:global] ? cleanup_parent(node, resources) : remove_parent_key(node, resources)
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
- "JobsTree:Node:#{job_args.to_json}"
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
- Resque.redis.hget PARENTS_KEY, node_key
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 lock_with key
65
- while !Resque.redis.setnx(key, 'locked')
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
- Resque.redis.del key
82
+ redis.del key
71
83
  end
72
84
 
73
85
  def cleanup_childs *node_info
74
86
  key = childs_key *node_info
75
- Resque.redis.smembers(key).each do |child_key|
76
- cleanup *node_info_from_key(child_key)
87
+ redis.smembers(key).each do |child_key|
88
+ failure_cleanup *node_info_from_key(child_key)
77
89
  end
78
- Resque.redis.del key
90
+ redis.del key
79
91
  end
80
92
 
81
- def cleanup_parent *node_info
82
- parent, parent_resources = node_info_from_key(parent_key(*node_info))
83
- remove_parent_key *node_info
84
- cleanup parent, parent_resources
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
- Resque.redis.hdel PARENTS_KEY, key(*node_info)
100
+ redis.hdel PARENTS_KEY, key(*node_info)
89
101
  end
90
102
 
91
103
  def remove_from_siblings *node_info
92
- Resque.redis.srem siblings_key(*node_info), key(*node_info)
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(/JobsTree:Node:/, '')
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
- @root.launch resources
19
- enqueue_leaves_jobs
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
@@ -1,3 +1,3 @@
1
1
  module ResqueJobsTree
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
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(:tree_node_and_resources, @args)
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque_jobs_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: