conflow 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/lib/conflow.rb +2 -0
- data/lib/conflow/flow.rb +12 -1
- data/lib/conflow/flow/job_handler.rb +4 -2
- data/lib/conflow/redis/complete_job_script.rb +7 -4
- data/lib/conflow/redis/field.rb +0 -21
- data/lib/conflow/redis/model.rb +50 -8
- data/lib/conflow/redis/queue_jobs_script.rb +31 -0
- data/lib/conflow/redis/set_field.rb +43 -0
- data/lib/conflow/redis/sorted_set_field.rb +0 -37
- data/lib/conflow/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b85fd52b7c1a2161135fc389c45b3f1e6754462a17ef30463435f1c919685950
|
4
|
+
data.tar.gz: a5f05aa2c69b8426a72dc1070834ec74120a5053a74f93250098765c461105ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3291b765c557576651568d735b3bad21e7a191919a858e9365bbf7da97d632d09f99f3c7a2f35d3133b03d3e82a9802489dd1b63542bd1343325f89c74c22845
|
7
|
+
data.tar.gz: a6797479cf68b310791680b2709ed54ebb9e372e26e87a1ffb2e685418a33eefa1b621bf5f68aa6ec05024c54d58f427123335ba204049a3fd7bfba4aa6d026e
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/lib/conflow.rb
CHANGED
@@ -12,6 +12,7 @@ require "conflow/redis/value_field"
|
|
12
12
|
require "conflow/redis/hash_field"
|
13
13
|
require "conflow/redis/array_field"
|
14
14
|
require "conflow/redis/sorted_set_field"
|
15
|
+
require "conflow/redis/set_field"
|
15
16
|
require "conflow/redis/field_builder"
|
16
17
|
require "conflow/redis/model"
|
17
18
|
require "conflow/redis/identifier"
|
@@ -20,6 +21,7 @@ require "conflow/redis/findable"
|
|
20
21
|
require "conflow/redis/script"
|
21
22
|
require "conflow/redis/add_job_script"
|
22
23
|
require "conflow/redis/complete_job_script"
|
24
|
+
require "conflow/redis/queue_jobs_script"
|
23
25
|
|
24
26
|
require "conflow/job"
|
25
27
|
require "conflow/flow/job_handler"
|
data/lib/conflow/flow.rb
CHANGED
@@ -16,6 +16,10 @@ module Conflow
|
|
16
16
|
# are fulfilled.
|
17
17
|
# @return [Conflow::Redis::SortedSetField] Set of jobs to be performed
|
18
18
|
#
|
19
|
+
# @!attribute [r] queued_jobs
|
20
|
+
# Set of jobs that are currently queued (and not yet finished).
|
21
|
+
# @return [Conflow::Redis::SetField] Set of queued jobs
|
22
|
+
#
|
19
23
|
# @!method queue(job)
|
20
24
|
# @abstract
|
21
25
|
# Queues job to be performed. Both id of the flow and id of the job must be preserved
|
@@ -35,7 +39,8 @@ module Conflow
|
|
35
39
|
include JobHandler
|
36
40
|
|
37
41
|
has_many :jobs, Conflow::Job
|
38
|
-
field :
|
42
|
+
field :queued_jobs, :set
|
43
|
+
field :indegree, :sorted_set
|
39
44
|
|
40
45
|
# Create new flow with given parameters
|
41
46
|
# @example Simple configurable flow
|
@@ -52,6 +57,12 @@ module Conflow
|
|
52
57
|
new.tap { |flow| flow.configure(*args) }
|
53
58
|
end
|
54
59
|
|
60
|
+
# Returns whether or not the flow is finished (all jobs were processed)
|
61
|
+
# @return [Boolean] true if no pending jobs
|
62
|
+
def finished?
|
63
|
+
queued_jobs.size.zero? && indegree.size.zero?
|
64
|
+
end
|
65
|
+
|
55
66
|
# @abstract
|
56
67
|
# Override this method in order to contain your flow definition inside the class.
|
57
68
|
# This method will be called if flow is created using {.create} method.
|
@@ -18,18 +18,21 @@ module Conflow
|
|
18
18
|
after = prepare_dependencies(after)
|
19
19
|
|
20
20
|
call_script(Conflow::Redis::AddJobScript, job, after: after)
|
21
|
+
queue_available_jobs
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
25
|
def finish(job, result = nil)
|
25
26
|
send(job.hook.to_s, result) unless job.hook.nil?
|
26
27
|
call_script(Conflow::Redis::CompleteJobScript, job)
|
28
|
+
queue_available_jobs
|
29
|
+
destroy! if finished?
|
27
30
|
end
|
28
31
|
|
29
32
|
private
|
30
33
|
|
31
34
|
def queue_available_jobs
|
32
|
-
|
35
|
+
call_script(Conflow::Redis::QueueJobsScript)&.each do |job_id|
|
33
36
|
queue Conflow::Job.new(job_id)
|
34
37
|
end
|
35
38
|
end
|
@@ -44,7 +47,6 @@ module Conflow
|
|
44
47
|
|
45
48
|
def call_script(script, *args)
|
46
49
|
script.call(self, *args)
|
47
|
-
queue_available_jobs
|
48
50
|
end
|
49
51
|
|
50
52
|
def prepare_dependencies(dependencies)
|
@@ -6,15 +6,18 @@ module Conflow
|
|
6
6
|
class CompleteJobScript < Script
|
7
7
|
self.script = <<~LUA
|
8
8
|
local indegree_set = KEYS[1]
|
9
|
-
local
|
9
|
+
local queued_jobs = KEYS[2]
|
10
|
+
local job_key = KEYS[3]
|
11
|
+
local job_id = ARGV[1]
|
10
12
|
|
11
|
-
local successors = redis.call('lrange',
|
13
|
+
local successors = redis.call('lrange', job_key .. ':successor_ids', 0, -1)
|
12
14
|
|
13
15
|
for i=1,#successors do
|
14
16
|
redis.call('zincrby', indegree_set, -1, successors[i])
|
15
17
|
end
|
16
18
|
|
17
|
-
|
19
|
+
redis.call('srem', queued_jobs, job_id)
|
20
|
+
return redis.call('set', job_key .. ':status', '1')
|
18
21
|
LUA
|
19
22
|
|
20
23
|
class << self
|
@@ -24,7 +27,7 @@ module Conflow
|
|
24
27
|
# @param flow [Conflow::Flow] Flow to which job belongs to
|
25
28
|
# @param job [Conflow::Job] Job to be marked as completed
|
26
29
|
def call(flow, job)
|
27
|
-
super([flow.indegree.key, job.key])
|
30
|
+
super([flow.indegree.key, flow.queued_jobs.key, job.key], [job.id])
|
28
31
|
end
|
29
32
|
end
|
30
33
|
end
|
data/lib/conflow/redis/field.rb
CHANGED
@@ -20,27 +20,6 @@ module Conflow
|
|
20
20
|
def command(command, args)
|
21
21
|
redis.with { |conn| conn.send(command, *args) }
|
22
22
|
end
|
23
|
-
|
24
|
-
def transaction(max_retries: 5)
|
25
|
-
result = redis.with do |conn|
|
26
|
-
retryable(max_retries, conn) do
|
27
|
-
conn.watch(key) do
|
28
|
-
conn.multi { |multi| yield(multi) }
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
raise ::Redis::CommandError, "Transaction failed #{max_retries} times" unless result
|
34
|
-
result
|
35
|
-
end
|
36
|
-
|
37
|
-
def retryable(max_retries, *args)
|
38
|
-
loop do
|
39
|
-
result = yield(*args)
|
40
|
-
break result if result || max_retries.zero?
|
41
|
-
max_retries -= 1
|
42
|
-
end
|
43
|
-
end
|
44
23
|
end
|
45
24
|
end
|
46
25
|
end
|
data/lib/conflow/redis/model.rb
CHANGED
@@ -16,16 +16,49 @@ module Conflow
|
|
16
16
|
other.is_a?(Model) ? key == other.key : super
|
17
17
|
end
|
18
18
|
|
19
|
+
# Removes this and all related models.
|
20
|
+
# @param execute [Boolean] if false, only returns keys that would be removed
|
21
|
+
def destroy!(execute: true)
|
22
|
+
keys = self.class.fields.map { |name| send(name).key }
|
23
|
+
related_keys = self.class.relations.map(&method(:collect_relation_keys))
|
24
|
+
|
25
|
+
(keys + related_keys).flatten.tap do |key_list|
|
26
|
+
command :del, [key_list] if execute && key_list.any?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def collect_relation_keys(relation)
|
33
|
+
send(relation).map { |object| object.destroy!(execute: false) }
|
34
|
+
end
|
35
|
+
|
19
36
|
# Methods for defining fields on model
|
20
37
|
module ClassMethods
|
38
|
+
# Maps types (option for {Conflow::Redis::Model::ClassMethods#field}) to specific type field
|
39
|
+
ALLOWED_TYPES = {
|
40
|
+
hash: Conflow::Redis::HashField,
|
41
|
+
array: Conflow::Redis::ArrayField,
|
42
|
+
value: Conflow::Redis::ValueField,
|
43
|
+
sorted_set: Conflow::Redis::SortedSetField,
|
44
|
+
set: Conflow::Redis::SetField
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
# Copies field and relations information
|
48
|
+
def inherited(subclass)
|
49
|
+
subclass.instance_variable_set("@fields", fields.dup)
|
50
|
+
subclass.instance_variable_set("@relations", relations.dup)
|
51
|
+
end
|
52
|
+
|
21
53
|
# Defines Redis field accessors.
|
22
54
|
# @param name [Symbol] name of the field
|
23
|
-
# @param type [:hash, :array, :value, :sorted_set] type of the new field
|
55
|
+
# @param type [:hash, :array, :value, :sorted_set, :set] type of the new field
|
24
56
|
#
|
25
57
|
# @see Conflow::Redis::HashField
|
26
58
|
# @see Conflow::Redis::ArrayField
|
27
59
|
# @see Conflow::Redis::ValueField
|
28
60
|
# @see Conflow::Redis::SortedSetField
|
61
|
+
# @see Conflow::Redis::SetField
|
29
62
|
# @example
|
30
63
|
# model_class.field :data, :hash
|
31
64
|
# instance = model_class.new
|
@@ -33,13 +66,11 @@ module Conflow
|
|
33
66
|
# instance.hash = { something: "else"}
|
34
67
|
# instance.hash["something"] #=> "else"
|
35
68
|
def field(name, type)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
else raise ArgumentError, "Unknown type: #{type}. Should be one of: [:hash, :array]"
|
42
|
-
end
|
69
|
+
type_class = ALLOWED_TYPES[type]
|
70
|
+
raise ArgumentError, "Unknown type: #{type}. Should be one of: #{ALLOWED_TYPES.keys.inspect}" unless type_class
|
71
|
+
|
72
|
+
fields << name
|
73
|
+
FieldBuilder.new(name, type_class).call(self)
|
43
74
|
end
|
44
75
|
|
45
76
|
# Convienience method for defining relation-like accessor.
|
@@ -47,8 +78,19 @@ module Conflow
|
|
47
78
|
# has_many :jobs, Conflow::Job # defines #job_ids and #jobs
|
48
79
|
def has_many(name, klass, field_name: "#{name.to_s.chop}_ids")
|
49
80
|
field(field_name, :array)
|
81
|
+
relations << name
|
50
82
|
define_method(name) { send(field_name).map { |id| klass.new(id) } }
|
51
83
|
end
|
84
|
+
|
85
|
+
# @return [Array<Symbol>] fields defined on this class
|
86
|
+
def fields
|
87
|
+
@fields ||= []
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [Array<Symbol>] relations defined on this class
|
91
|
+
def relations
|
92
|
+
@relations ||= []
|
93
|
+
end
|
52
94
|
end
|
53
95
|
end
|
54
96
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Conflow
|
4
|
+
module Redis
|
5
|
+
# Removes jobs from indegree set, adds them to queued list, and returns IDs of queued jobs.
|
6
|
+
class QueueJobsScript < Script
|
7
|
+
self.script = <<~LUA
|
8
|
+
local indegree_set = KEYS[1]
|
9
|
+
local queued_set = KEYS[2]
|
10
|
+
|
11
|
+
local ids = redis.call('zrangebyscore', indegree_set, 0, 0)
|
12
|
+
redis.call('zremrangebyscore', indegree_set, 0, 0)
|
13
|
+
|
14
|
+
if #ids ~= 0 then
|
15
|
+
redis.call('sadd', queued_set, unpack(ids))
|
16
|
+
end
|
17
|
+
|
18
|
+
return ids
|
19
|
+
LUA
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Call the script.
|
23
|
+
# Script removes jobs which have score 0 in Flow's indegree set and moves them to queued_jobs list.
|
24
|
+
# @param flow [Conflow::Flow] Flow from which jobs should be enqueued
|
25
|
+
def call(flow)
|
26
|
+
super([flow.indegree.key, flow.queued_jobs.key])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Conflow
|
4
|
+
module Redis
|
5
|
+
# Represents Redis list. It's methods mirror most used Set methods.
|
6
|
+
class SetField < Field
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
# Adds one or more elements to the set.
|
10
|
+
# @param values [String] list of values to be added
|
11
|
+
# @return [String] Redis response
|
12
|
+
#
|
13
|
+
# @example Adding multiple fields
|
14
|
+
# field.add(:last, :first, :other, :first)
|
15
|
+
def add(*values)
|
16
|
+
command :sadd, [key, values]
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Integer] Number of elements in the set
|
20
|
+
def size
|
21
|
+
command :scard, [key]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Removes old values from the set and overrides them with new.
|
25
|
+
# @param enumerable [Enumerable] new values of the set
|
26
|
+
# @return [String] Redis response
|
27
|
+
def overwrite(enumerable)
|
28
|
+
redis.with do |conn|
|
29
|
+
conn.pipelined do
|
30
|
+
conn.del(key)
|
31
|
+
conn.sadd(key, enumerable)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns array with set values
|
37
|
+
# @return [Array] values from the set
|
38
|
+
def to_a
|
39
|
+
command :smembers, [key]
|
40
|
+
end; alias to_ary to_a
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -55,36 +55,6 @@ module Conflow
|
|
55
55
|
command :zrem, [key, value]
|
56
56
|
end
|
57
57
|
|
58
|
-
# Return elements with given score
|
59
|
-
# @param score [Numeric, Hash]
|
60
|
-
# - when Numeric, only elements with that exact score will be returned
|
61
|
-
# - when Hash, elements within min..max range will be returned. See {https://redis.io/commands/zrange Redis docs}
|
62
|
-
# @option score [String, Numeric] :min minimal score
|
63
|
-
# @option score [String, Numeric] :max maximal score
|
64
|
-
# @return [Array<String>] Elements with given score
|
65
|
-
#
|
66
|
-
# @example with specific score
|
67
|
-
# field.where(score: 2) #=> ["first", "tie"]
|
68
|
-
# @example with only min set
|
69
|
-
# field.where(score: { min: 3 }) #=> ["last", "second"]
|
70
|
-
# @example with both min and max set
|
71
|
-
# field.where(score: { min: 3, max: "(10" }) #=> ["last"]
|
72
|
-
def where(score:)
|
73
|
-
command :zrangebyscore, [key, *prepare_score_bounds(score)]
|
74
|
-
end
|
75
|
-
|
76
|
-
# Removes elements of the set with given score and returns them.
|
77
|
-
# See {where} for details on how to choose score.
|
78
|
-
# @return [Array<String>] Elements with given score
|
79
|
-
def delete_if(score:)
|
80
|
-
score_bounds = prepare_score_bounds(score)
|
81
|
-
|
82
|
-
transaction do |conn|
|
83
|
-
conn.zrangebyscore key, *score_bounds
|
84
|
-
conn.zremrangebyscore key, *score_bounds
|
85
|
-
end[0]
|
86
|
-
end
|
87
|
-
|
88
58
|
# Returns first *n* elements of the sorted set
|
89
59
|
# @param num [Integer] amount of elements to be returned. Defaults to 1.
|
90
60
|
# @return [String, Array<String>] first *num* elements from the set
|
@@ -129,13 +99,6 @@ module Conflow
|
|
129
99
|
result[index * 2 + 1] = value
|
130
100
|
end
|
131
101
|
end
|
132
|
-
|
133
|
-
def prepare_score_bounds(score)
|
134
|
-
case score
|
135
|
-
when Hash then { min: "-inf", max: "+inf" }.merge(score).values
|
136
|
-
when Numeric then [score, score]
|
137
|
-
end
|
138
|
-
end
|
139
102
|
end
|
140
103
|
end
|
141
104
|
end
|
data/lib/conflow/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: conflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michał Begejowicz
|
@@ -158,7 +158,9 @@ files:
|
|
158
158
|
- lib/conflow/redis/hash_field.rb
|
159
159
|
- lib/conflow/redis/identifier.rb
|
160
160
|
- lib/conflow/redis/model.rb
|
161
|
+
- lib/conflow/redis/queue_jobs_script.rb
|
161
162
|
- lib/conflow/redis/script.rb
|
163
|
+
- lib/conflow/redis/set_field.rb
|
162
164
|
- lib/conflow/redis/sorted_set_field.rb
|
163
165
|
- lib/conflow/redis/value_field.rb
|
164
166
|
- lib/conflow/version.rb
|