resque_jobs_tree 0.3.4 → 0.4.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.
@@ -1,6 +1,10 @@
1
1
  require "resque_jobs_tree/version"
2
2
  require 'resque'
3
3
 
4
+ require 'resque_jobs_tree/storage'
5
+ require 'resque_jobs_tree/storage/tree'
6
+ require 'resque_jobs_tree/storage/node'
7
+
4
8
  require 'resque_jobs_tree/factory'
5
9
  require 'resque_jobs_tree/tree'
6
10
  require 'resque_jobs_tree/node'
@@ -8,8 +12,26 @@ require 'resque_jobs_tree/job'
8
12
  require 'resque_jobs_tree/resources_serializer'
9
13
  require 'resque_jobs_tree/storage'
10
14
 
15
+ require 'resque_jobs_tree/definitions'
16
+ require 'resque_jobs_tree/definitions/tree'
17
+ require 'resque_jobs_tree/definitions/node'
18
+
11
19
  module ResqueJobsTree
12
- class TreeInvalid < Exception ; end
13
- class NodeInvalid < Exception ; end
14
- class JobNotUniq < Exception ; end
20
+ extend self
21
+ class TreeDefinitionInvalid < Exception ; end
22
+ class NodeDefinitionInvalid < Exception ; end
23
+ class JobNotUniq < Exception ; end
24
+
25
+ def find name
26
+ Factory.find name.to_s
27
+ end
28
+
29
+ def launch name, *resources
30
+ tree_definition = find name
31
+ tree_definition ? tree_definition.spawn(resources).launch : raise("Can't find tree `#{name}`")
32
+ end
33
+
34
+ def create *resources
35
+ Factory.create *resources
36
+ end
15
37
  end
@@ -0,0 +1,15 @@
1
+ class ResqueJobsTree::Definitions
2
+
3
+ def on_failure &block
4
+ @on_failure ||= block
5
+ end
6
+
7
+ def before_perform &block
8
+ @before_perform ||= block
9
+ end
10
+
11
+ def after_perform &block
12
+ @before_perform ||= block
13
+ end
14
+
15
+ end
@@ -0,0 +1,77 @@
1
+ class ResqueJobsTree::Definitions::Node < ResqueJobsTree::Definitions
2
+
3
+ attr_accessor :tree, :parent, :name, :node_childs, :options
4
+
5
+ def initialize name, tree, parent=nil
6
+ @tree = tree
7
+ @name = name.to_s
8
+ @parent = parent
9
+ @node_childs = []
10
+ @options = {}
11
+ end
12
+
13
+ def node name, options={}, &block
14
+ ResqueJobsTree::Definitions::Node.new(name, tree, self).tap do |node|
15
+ node.options = options
16
+ @node_childs << node
17
+ node.instance_eval(&block) if block_given?
18
+ end
19
+ end
20
+
21
+ def spawn resources, parent=nil
22
+ ResqueJobsTree::Node.new self, resources, parent
23
+ end
24
+
25
+ def childs &block
26
+ @childs ||= block
27
+ end
28
+
29
+ def perform &block
30
+ @perform ||= block
31
+ end
32
+
33
+ def leaf?
34
+ @node_childs.empty?
35
+ end
36
+
37
+ def root?
38
+ parent.nil?
39
+ end
40
+
41
+ def siblings
42
+ root? ? [] : (parent.node_childs - [self])
43
+ end
44
+
45
+ def find _name
46
+ if name == _name.to_s
47
+ self
48
+ else
49
+ node_childs.inject(nil){|result,node| result ||= node.find _name }
50
+ end
51
+ end
52
+
53
+ def validate!
54
+ if (childs.kind_of?(Proc) && node_childs.empty?) || (childs.nil? && !node_childs.empty?)
55
+ raise ResqueJobsTree::NodeDefinitionInvalid,
56
+ "node `#{name}` from tree `#{tree.name}` should defines childs and child nodes"
57
+ end
58
+ unless perform.kind_of? Proc
59
+ raise ResqueJobsTree::NodeDefinitionInvalid,
60
+ "node `#{name}` from tree `#{tree.name}` has no perform block"
61
+ end
62
+ if (tree.nodes - [self]).map(&:name).include? name
63
+ raise ResqueJobsTree::NodeDefinitionInvalid,
64
+ "node name `#{name}` is already taken in tree `#{tree.name}`"
65
+ end
66
+ node_childs.each &:validate!
67
+ end
68
+
69
+ def nodes
70
+ node_childs+node_childs.map(&:nodes)
71
+ end
72
+
73
+ def inspect
74
+ "<ResqueJobsTree::Node @name=#{name}>"
75
+ end
76
+
77
+ end
@@ -0,0 +1,39 @@
1
+ class ResqueJobsTree::Definitions::Tree < ResqueJobsTree::Definitions
2
+
3
+ attr_accessor :name, :root
4
+
5
+ def initialize name
6
+ @name = name
7
+ end
8
+
9
+ def spawn resources
10
+ ResqueJobsTree::Tree.new self, resources
11
+ end
12
+
13
+ def root name=nil, &block
14
+ @root ||= Node.new(name, self).tap do |root|
15
+ root.instance_eval &block
16
+ end
17
+ end
18
+
19
+ def find name
20
+ root.find name.to_s
21
+ end
22
+
23
+ def validate!
24
+ if @root
25
+ root.validate!
26
+ else
27
+ raise ResqueJobsTree::TreeDefinitionInvalid, "`#{name}` has no root node"
28
+ end
29
+ end
30
+
31
+ def nodes
32
+ [root, root.nodes].flatten
33
+ end
34
+
35
+ def inspect
36
+ "<ResqueJobsTree::Definitions::Tree @name=#{name}>"
37
+ end
38
+
39
+ end
@@ -3,21 +3,21 @@ module ResqueJobsTree::Factory
3
3
  extend self
4
4
 
5
5
  def create name, &block
6
- @trees ||= []
7
- @trees.delete_if{|tree| tree.name == name.to_s}
8
- ResqueJobsTree::Tree.new(name).tap do |tree|
9
- @trees << tree
6
+ name = name.to_s
7
+ @trees ||= {}
8
+ ResqueJobsTree::Definitions::Tree.new(name).tap do |tree|
10
9
  tree.instance_eval &block
11
10
  tree.validate!
11
+ @trees[name] = tree
12
12
  end
13
13
  end
14
14
 
15
15
  def trees
16
- @trees
16
+ @trees ||= {}
17
17
  end
18
18
 
19
- def find_tree_by_name name
20
- @trees.detect{ |tree| tree.name == name.to_s }
19
+ def find name
20
+ trees[name.to_s]
21
21
  end
22
22
 
23
23
  end
@@ -3,45 +3,27 @@ class ResqueJobsTree::Job
3
3
  class << self
4
4
 
5
5
  def perform *args
6
- node, resources = node_and_resources(args)
7
- node.perform.call resources
6
+ node(*args).perform
8
7
  end
9
8
 
10
9
  protected
11
10
 
12
- def after_perform_enqueue_parent *args
13
- node, resources = node_and_resources(args)
14
- if node.root?
15
- ResqueJobsTree::Storage.release_launch node.tree, resources
16
- else
17
- ResqueJobsTree::Storage.remove(node, resources) do
18
- parent_job_args = ResqueJobsTree::Storage.parent_job_args node, resources
19
- Resque.enqueue_to node.tree.name, ResqueJobsTree::Job, *parent_job_args
20
- end
21
- end
11
+ def before_perform_run_callback *args
12
+ node(*args).before_perform
22
13
  end
23
14
 
24
- def on_failure_cleanup exception, *args
25
- node, resources = node_and_resources args
26
- if node.options[:continue_on_failure]
27
- begin
28
- after_perform_enqueue_parent *args
29
- ensure
30
- ResqueJobsTree::Storage.failure_cleanup node, resources
31
- end
32
- else
33
- ResqueJobsTree::Storage.failure_cleanup node, resources, global: true
34
- node.tree.on_failure.call(resources) if node.tree.on_failure.kind_of?(Proc)
35
- raise exception
36
- end
15
+ def after_perform_run_callback *args
16
+ node(*args).after_perform
37
17
  end
38
18
 
39
- def node_and_resources args
40
- tree_name , job_name = args[0..1]
41
- tree = ResqueJobsTree::Factory.find_tree_by_name tree_name
42
- node = tree.find_node_by_name job_name
43
- resources = ResqueJobsTree::ResourcesSerializer.to_resources args[2..-1]
44
- [node, resources]
19
+ def on_failure_run_callback exception, *args
20
+ node(*args).on_failure
21
+ end
22
+
23
+ def node tree_name, job_name, *resources_arguments
24
+ node_definition = ResqueJobsTree.find(tree_name).find job_name
25
+ resources = ResqueJobsTree::ResourcesSerializer.instancize resources_arguments
26
+ node_definition.spawn resources
45
27
  end
46
28
 
47
29
  end
@@ -1,92 +1,96 @@
1
1
  class ResqueJobsTree::Node
2
2
 
3
- attr_accessor :tree, :parent, :name, :node_childs, :options
4
-
5
- def initialize name, tree, parent=nil
6
- @tree = tree
7
- @name = name.to_s
8
- @parent = parent
9
- @node_childs = []
10
- @options = {}
3
+ include ResqueJobsTree::Storage::Node
4
+
5
+ attr_reader :resources, :definition, :tree
6
+
7
+ def initialize definition, resources, parent=nil, tree=nil
8
+ @childs = []
9
+ @definition = definition
10
+ @resources = resources
11
+ @parent = parent
12
+ @tree = tree
13
+ end
14
+
15
+ def enqueue
16
+ Resque.enqueue_to definition.tree.name, ResqueJobsTree::Job, *argumentize
11
17
  end
12
18
 
13
- def resources &block
14
- @resources_block ||= block
19
+ def perform
20
+ definition.perform.call *resources
15
21
  end
16
22
 
17
- def perform &block
18
- @perform_block ||= block
23
+ def before_perform
24
+ run_callback :before_perform
19
25
  end
20
26
 
21
- def childs &block
22
- @childs ||= block
27
+ def after_perform
28
+ run_callback :after_perform
29
+ if root?
30
+ tree.finish
31
+ else
32
+ lock do
33
+ parent.enqueue if only_stored_child?
34
+ unstore
35
+ end
36
+ end
23
37
  end
24
38
 
25
- # Defines a child node.
26
- def node name, options={}, &block
27
- ResqueJobsTree::Node.new(name, tree, self).tap do |node|
28
- node.options = options
29
- @node_childs << node
30
- node.instance_eval(&block) if block_given?
39
+ def on_failure
40
+ if definition.options[:continue_on_failure]
41
+ after_perform
42
+ else
43
+ root.tree.on_failure
44
+ root.cleanup
31
45
  end
32
46
  end
33
47
 
34
- def leaf? resources
35
- childs.kind_of?(Proc) ? childs.call(resources).empty? : true
48
+ def tree
49
+ @tree ||= root? ? definition.tree.spawn(resources) : @parent.tree
50
+ end
51
+
52
+ def name
53
+ definition.name
54
+ end
55
+
56
+ def leaf?
57
+ childs.empty?
36
58
  end
37
59
 
38
60
  def root?
39
- parent == nil
61
+ definition.root?
40
62
  end
41
63
 
42
- def siblings
43
- return [] unless parent
44
- parent.node_childs - [self]
64
+ def root
65
+ @root ||= root? ? self : parent.root
45
66
  end
46
67
 
47
- def launch resources, parent_resources=nil
48
- unless root?
49
- ResqueJobsTree::Storage.store self, resources, parent, parent_resources
50
- end
51
- if node_childs.empty?
52
- @tree.enqueue(name, *resources) unless options[:async]
53
- else
54
- childs.call(resources).each do |name, *child_resources|
55
- find_node_by_name(name).launch child_resources, resources
56
- end
57
- end
68
+ def childs
69
+ return @childs unless @childs.empty?
70
+ @childs = definition.leaf? ? [] : definition.childs.call(*resources)
58
71
  end
59
72
 
60
- def find_node_by_name _name
61
- if name == _name.to_s
62
- self
73
+ def launch
74
+ store unless root?
75
+ if leaf?
76
+ tree.register_a_leaf self
63
77
  else
64
- node_childs.inject(nil){|result,node| result ||= node.find_node_by_name _name }
78
+ childs.each do |node_name, *resources|
79
+ node = definition.find(node_name).spawn resources, self
80
+ node.launch
81
+ end
65
82
  end
66
83
  end
67
84
 
68
- def validate!
69
- if (childs.kind_of?(Proc) && node_childs.empty?) || (childs.nil? && !node_childs.empty?)
70
- raise ResqueJobsTree::NodeInvalid,
71
- "node `#{name}` from tree `#{tree.name}` should defines childs and child nodes"
72
- end
73
- unless perform.kind_of? Proc
74
- raise ResqueJobsTree::NodeInvalid,
75
- "node `#{name}` from tree `#{tree.name}` has no perform block"
76
- end
77
- if (tree.nodes - [self]).map(&:name).include? name
78
- raise ResqueJobsTree::NodeInvalid,
79
- "node name `#{name}` is already taken in tree `#{tree.name}`"
80
- end
81
- node_childs.each &:validate!
85
+ def inspect
86
+ "<ResqueJobsTree::Node @name=#{name} @resources=#{resources}>"
82
87
  end
83
88
 
84
- def nodes
85
- node_childs+node_childs.map(&:nodes)
86
- end
89
+ private
87
90
 
88
- def inspect
89
- "<ResqueJobsTree::Node @name=#{name}>"
91
+ def run_callback callback
92
+ callback_block = definition.send callback
93
+ callback_block.call(*resources) if callback_block.kind_of? Proc
90
94
  end
91
95
 
92
96
  end
@@ -1,10 +1,9 @@
1
1
  module ResqueJobsTree::ResourcesSerializer
2
-
3
2
  extend self
4
3
 
5
4
  # in: [<Localisation id=1>, :pdf]
6
5
  # out: [[Localisation, 1], :pdf]
7
- def to_args resources
6
+ def argumentize resources
8
7
  resources.to_a.map do |resource|
9
8
  resource.respond_to?(:id) ? [resource.class.name, resource.id] : resource
10
9
  end
@@ -12,7 +11,7 @@ module ResqueJobsTree::ResourcesSerializer
12
11
 
13
12
  # in: [['Localisation', 1], :pdf]
14
13
  # out: [<Localisation id=1>, :pdf]
15
- def to_resources args
14
+ def instancize args
16
15
  args.to_a.map do |arg|
17
16
  if arg.kind_of? Array
18
17
  eval(arg[0]).find(arg[1]) rescue arg
@@ -1,127 +1,20 @@
1
- # put expire on every key
2
1
  module ResqueJobsTree::Storage
3
- extend self
4
2
 
5
3
  PARENTS_KEY = "ResqueJobsTree:Node:Parents"
6
4
  LAUNCHED_TREES = "ResqueJobsTree:Tree:Launched"
7
5
 
8
- def store node, resources, parent, parent_resources
9
- node_key = key node, resources
10
- parent_key = key parent, parent_resources
11
- redis.hset PARENTS_KEY, node_key, parent_key
12
- childs_key = childs_key parent, parent_resources
13
- unless redis.sadd childs_key, node_key
14
- raise ResqueJobsTree::JobNotUniq,
15
- "Job #{parent.name} already has the child #{node.name} with resources: #{resources}"
16
- end
17
- end
18
-
19
- def remove node, resources
20
- lock node, resources do
21
- siblings_key = siblings_key node, resources
22
- node_key = key(node, resources)
23
- redis.srem siblings_key, node_key
24
- yield if redis.scard(siblings_key) == 0 && block_given?
25
- redis.hdel PARENTS_KEY, node_key
26
- end
27
- end
28
-
29
- def failure_cleanup node, resources, options={}
30
- cleanup_childs node, resources
31
- if node.root?
32
- release_launch node.tree, resources
33
- else
34
- remove_from_siblings node, resources
35
- options[:global] ? cleanup_parent(node, resources, options) : remove_parent_key(node, resources)
36
- end
37
- end
38
-
39
- def parent_job_args node, resources
40
- args_from_key parent_key(node, resources)
41
- end
42
-
43
- def track_launch *tree_info
44
- yield if redis.sadd LAUNCHED_TREES, tree_reference(*tree_info)
45
- end
46
-
47
- def release_launch *tree_info
48
- redis.srem LAUNCHED_TREES, tree_reference(*tree_info)
49
- end
50
-
51
6
  private
52
7
 
53
- def key node, resources
54
- job_args = ResqueJobsTree::ResourcesSerializer.to_args(resources)
55
- job_args.unshift node.name
56
- job_args.unshift node.tree.name
57
- "ResqueJobsTree:Node:#{job_args.to_json}"
58
- end
59
-
60
- def childs_key node, resources
61
- "#{key node, resources}:childs"
62
- end
63
-
64
- def parent_key node, resources
65
- node_key = key node, resources
66
- redis.hget PARENTS_KEY, node_key
67
- end
68
-
69
- def siblings_key node, resources
70
- "#{parent_key node, resources}:childs"
8
+ def serialize
9
+ argumentize.to_json
71
10
  end
72
11
 
73
- def parent_lock_key node, resources
74
- "#{parent_key node, resources}:lock"
75
- end
76
-
77
- def lock *node_info
78
- key = parent_lock_key *node_info
79
- while !redis.setnx(key, 'locked')
80
- sleep 0.05 # 50 ms
81
- end
82
- yield
83
- ensure
84
- redis.del key
85
- end
86
-
87
- def cleanup_childs *node_info
88
- key = childs_key *node_info
89
- redis.smembers(key).each do |child_key|
90
- failure_cleanup *node_info_from_key(child_key)
91
- end
92
- redis.del key
93
- end
94
-
95
- def cleanup_parent node, resources, options
96
- parent, parent_resources = node_info_from_key(parent_key(node, resources))
97
- remove_parent_key node, resources
98
- failure_cleanup parent, parent_resources, options
99
- end
100
-
101
- def remove_parent_key *node_info
102
- redis.hdel PARENTS_KEY, key(*node_info)
103
- end
104
-
105
- def remove_from_siblings *node_info
106
- redis.srem siblings_key(*node_info), key(*node_info)
107
- end
108
-
109
- def args_from_key key
110
- JSON.load key.gsub(/ResqueJobsTree:Node:/, '')
111
- end
112
-
113
- def node_info_from_key key
114
- tree_name, node_name, *resources = args_from_key(key)
115
- node = ResqueJobsTree::Factory.find_tree_by_name(tree_name).find_node_by_name(node_name)
116
- [node, resources]
12
+ def argumentize
13
+ main_arguments + ResqueJobsTree::ResourcesSerializer.argumentize(resources)
117
14
  end
118
15
 
119
16
  def redis
120
17
  Resque.redis
121
18
  end
122
-
123
- def tree_reference tree, resources
124
- [tree.name, ResqueJobsTree::ResourcesSerializer.to_args(resources)].to_json
125
- end
126
-
19
+
127
20
  end