flor 0.16.1 → 0.16.2

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/CREDITS.md +1 -0
  4. data/Makefile +1 -1
  5. data/README.md +82 -6
  6. data/lib/flor.rb +1 -1
  7. data/lib/flor/conf.rb +19 -6
  8. data/lib/flor/core/executor.rb +45 -16
  9. data/lib/flor/core/node.rb +4 -4
  10. data/lib/flor/core/procedure.rb +40 -0
  11. data/lib/flor/djan.rb +5 -2
  12. data/lib/flor/flor.rb +92 -7
  13. data/lib/flor/id.rb +19 -0
  14. data/lib/flor/migrations/0001_tables.rb +6 -6
  15. data/lib/flor/migrations/0005_pointer_content.rb +20 -0
  16. data/lib/flor/pcore/_apply.rb +103 -57
  17. data/lib/flor/pcore/_att.rb +15 -1
  18. data/lib/flor/pcore/_ref.rb +2 -1
  19. data/lib/flor/pcore/arith.rb +46 -9
  20. data/lib/flor/pcore/break.rb +1 -1
  21. data/lib/flor/pcore/case.rb +41 -0
  22. data/lib/flor/pcore/collect.rb +1 -1
  23. data/lib/flor/pcore/cursor.rb +1 -1
  24. data/lib/flor/pcore/define.rb +32 -6
  25. data/lib/flor/pcore/iterator.rb +12 -0
  26. data/lib/flor/pcore/on_cancel.rb +1 -1
  27. data/lib/flor/pcore/set.rb +14 -4
  28. data/lib/flor/punit/{ccollect.rb → c_collect.rb} +2 -2
  29. data/lib/flor/punit/c_each.rb +11 -0
  30. data/lib/flor/punit/c_for_each.rb +41 -0
  31. data/lib/flor/punit/c_iterator.rb +160 -0
  32. data/lib/flor/punit/c_map.rb +43 -0
  33. data/lib/flor/punit/concurrence.rb +43 -200
  34. data/lib/flor/punit/graft.rb +3 -2
  35. data/lib/flor/punit/m_ram.rb +281 -0
  36. data/lib/flor/unit.rb +1 -0
  37. data/lib/flor/unit/caller.rb +6 -1
  38. data/lib/flor/unit/executor.rb +17 -4
  39. data/lib/flor/unit/ganger.rb +12 -1
  40. data/lib/flor/unit/hloader.rb +251 -0
  41. data/lib/flor/unit/hook.rb +74 -15
  42. data/lib/flor/unit/hooker.rb +9 -12
  43. data/lib/flor/unit/loader.rb +41 -17
  44. data/lib/flor/unit/models.rb +54 -18
  45. data/lib/flor/unit/models/execution.rb +15 -4
  46. data/lib/flor/unit/models/pointer.rb +11 -0
  47. data/lib/flor/unit/scheduler.rb +126 -30
  48. data/lib/flor/unit/spooler.rb +5 -3
  49. data/lib/flor/unit/storage.rb +40 -13
  50. data/lib/flor/unit/waiter.rb +165 -26
  51. data/lib/flor/unit/wlist.rb +98 -5
  52. metadata +10 -4
  53. data/lib/flor/punit/cmap.rb +0 -112
@@ -43,9 +43,11 @@ module Flor
43
43
 
44
44
  def determine_spool_dir
45
45
 
46
- d = @unit.conf['spo_dir'] || 'var/spool'
47
- d = File.join(@unit.conf['root'], d) if d
48
- d = nil unless File.directory?(d)
46
+ r = @unit.conf['root']
47
+ return nil unless r
48
+
49
+ d = File.join(r, @unit.conf['spo_dir'] || 'var/spool')
50
+ return nil unless File.directory?(d)
49
51
 
50
52
  d
51
53
  end
@@ -80,14 +80,25 @@ module Flor
80
80
  opts[:current] ||= from if from.is_a?(Integer)
81
81
  #
82
82
  # defaults for the migration version table:
83
- #:table => :schema_info
84
- #:column => :version
83
+ # { table: :schema_info,
84
+ # column: :version }
85
+
86
+ skip =
87
+ @unit.conf['db_sparse_migrations'] ||
88
+ @unit.conf['sto_sparse_migrations'] ||
89
+ opts[:sparse_migrations]
90
+ if skip && ! opts.has_key?(:allow_missing_migration_files)
91
+ opts[:allow_missing_migration_files] = true
92
+ end
85
93
 
86
94
  dir =
87
95
  @unit.conf['db_migrations'] ||
88
- File.absolute_path(
89
- File.join(
90
- File.dirname(__FILE__), '..', 'migrations'))
96
+ @unit.conf['db_migration_dir'] ||
97
+ @unit.conf['sto_migrations'] ||
98
+ @unit.conf['sto_migration_dir'] ||
99
+ opts[:migrations] ||
100
+ opts[:migration_dir] ||
101
+ Flor.migration_dir
91
102
 
92
103
  synchronize do
93
104
 
@@ -664,7 +675,9 @@ module Flor
664
675
 
665
676
  def update_pointers(exe, status, now)
666
677
 
667
- # TODO should we archive old pointers?
678
+ # Q Should we archive old pointers?
679
+ # Well, it might be better to only archive the execution and leave
680
+ # in there enough information...
668
681
 
669
682
  exid = exe['exid']
670
683
 
@@ -694,9 +707,12 @@ module Flor
694
707
 
695
708
  vs = nid == '0' ? node['vars'] : nil
696
709
  vs.each { |k, v|
697
- case v; when Integer, String, TrueClass, FalseClass
710
+ case v; when Numeric, String, TrueClass, FalseClass, NilClass
698
711
  a << [ dom, exid, '0', 'var', k, v.to_s, now, u ]
699
- when NilClass
712
+ when Array, Hash
713
+ s = '(array)'; s = '(object)' if v.is_a?(Hash)
714
+ a << [ dom, exid, '0', 'var', k, s, now, u ]
715
+ else
700
716
  a << [ dom, exid, '0', 'var', k, nil, now, u ]
701
717
  end } if vs
702
718
 
@@ -793,19 +809,30 @@ module Flor
793
809
  @db.loggers << @db_logger
794
810
  end
795
811
 
796
- def self.to_blob(h)
812
+ class << self
813
+
814
+ def to_blob(h)
797
815
 
798
- Sequel.blob(Zlib::Deflate.deflate(JSON.dump(h)))
816
+ h ? Sequel.blob(Zlib::Deflate.deflate(JSON.dump(h))) : nil
799
817
  #rescue => e; pp h; raise e
800
- end
818
+ end
801
819
 
802
- def self.from_blob(content)
820
+ def from_blob(content)
803
821
 
804
- JSON.parse(Zlib::Inflate.inflate(content))
822
+ content ? JSON.parse(Zlib::Inflate.inflate(content)) : nil
823
+ end
805
824
  end
806
825
 
807
826
  def to_blob(h); self.class.to_blob(h); end
808
827
  def from_blob(content); self.class.from_blob(content); end
809
828
  end
829
+
830
+ # module Flor
831
+
832
+ class << self
833
+
834
+ def to_blob(h); ::Flor::Storage.to_blob(h); end
835
+ def from_blob(content); ::Flor::Storage.from_blob(content); end
836
+ end
810
837
  end
811
838
 
@@ -5,11 +5,12 @@ module Flor
5
5
 
6
6
  def initialize(exid, opts)
7
7
 
8
- serie, timeout, on_timeout, repeat =
8
+ serie, timeout, on_timeout =
9
9
  expand_args(opts)
10
10
 
11
+ # TODO fail if the serie mixes msg_waiting with row_waiting...
12
+
11
13
  @exid = exid
12
- @original_serie = repeat ? Flor.dup(serie) : nil
13
14
  @serie = serie
14
15
  @timeout = timeout
15
16
  @on_timeout = on_timeout
@@ -17,39 +18,81 @@ module Flor
17
18
  @queue = []
18
19
  @mutex = Mutex.new
19
20
  @var = ConditionVariable.new
21
+
22
+ @executor = nil
23
+ end
24
+
25
+ ROW_PSEUDO_POINTS = %w[ status tag tasker var variable ]
26
+ # "tasker", not "task", since "task" is already a message point
27
+
28
+ def row_waiter?
29
+
30
+ @serie.find { |_, points|
31
+ points.find { |po|
32
+ pos = po.split(':')
33
+ pos.length > 1 && ROW_PSEUDO_POINTS.include?(pos[0]) } }
34
+ end
35
+
36
+ def msg_waiter?
37
+
38
+ @serie.find { |_, points|
39
+ points.find { |po|
40
+ ! ROW_PSEUDO_POINTS.include?(po.split(':').first) } }
20
41
  end
21
42
 
22
43
  def to_s
23
44
 
24
- "#{super[0..-2]}#{
25
- { exid: @exid,
26
- original_serie: @original_serie,
27
- timeout: @timeout }.inspect
28
- }>"
45
+ "#{super[0..-2]}#{{ exid: @exid, timeout: @timeout }.inspect}>"
29
46
  end
30
47
 
31
48
  def notify(executor, message)
32
49
 
50
+ @executor = executor
51
+ # could be handy
52
+
33
53
  @mutex.synchronize do
34
54
 
35
55
  return false unless match?(message)
36
56
 
37
57
  @serie.shift
38
- return false unless @serie.empty?
58
+ return false if @serie.any?
39
59
 
40
60
  @queue << [ executor, message ]
41
61
  @var.signal
42
62
  end
43
63
 
44
- # then...
45
- # returning false: do not remove me, I want to listen/wait further
46
- # returning true: remove me
64
+ true # serie over, remove me
65
+ end
66
+
67
+ def check(unit, rs)
68
+
69
+ @mutex.synchronize do
47
70
 
48
- return true unless @original_serie
71
+ row = nil
49
72
 
50
- @serie = Flor.dup(@original_serie) # reset serie
73
+ loop do
51
74
 
52
- false # do not remove me
75
+ break if @serie.empty?
76
+
77
+ row = row_match?(unit, rs)
78
+ return false unless row
79
+
80
+ @serie.shift
81
+ end
82
+
83
+ @queue << [ unit, row ]
84
+ @var.signal
85
+ end
86
+
87
+ true # serie over, remove me
88
+
89
+ rescue => err
90
+
91
+ #puts "!" * 80; p err
92
+ unit.logger.warn(
93
+ "#{self.class}#check()", err, '(returning true, aka remove me)')
94
+
95
+ true # remove me
53
96
  end
54
97
 
55
98
  def wait
@@ -63,7 +106,8 @@ module Flor
63
106
 
64
107
  if @queue.empty?
65
108
  fail RuntimeError.new(
66
- "timeout for #{self.to_s}"
109
+ "timeout for #{self.to_s}, " +
110
+ "msg_waiter? #{ !! msg_waiter?}, row_waiter? #{ !! row_waiter?}"
67
111
  ) if @on_timeout == 'fail'
68
112
  return { 'exid' => @exid, 'timed_out' => @on_timeout }
69
113
  end
@@ -73,6 +117,37 @@ module Flor
73
117
  end
74
118
  end
75
119
 
120
+ def to_query_hashes
121
+
122
+ @serie
123
+ .inject([ [], [] ]) { |a, (nid, points)|
124
+
125
+ points.each do |point|
126
+
127
+ ss = point.split(':')
128
+
129
+ h = {}
130
+ h[:exid] = @exid if @exid
131
+ h[:nid] = nid if nid
132
+
133
+ case ss[0]
134
+ when 'status'
135
+ h[:status] = ss[1]
136
+ a[0] << h
137
+ when 'tag', 'tasker', 'var', 'variable'
138
+ t = ss[0]; t = 'var' if t == 'variable'
139
+ h[:type] = t
140
+ h[:name] = ss[1]
141
+ h[:value] = ss[2] if ss[2]
142
+ a[1] << h
143
+ else
144
+ fail ArgumentError, "cannot turn to query_hash, #{self.inspect}"
145
+ end
146
+ end
147
+
148
+ a }
149
+ end
150
+
76
151
  protected
77
152
 
78
153
  def match?(message)
@@ -85,15 +160,70 @@ module Flor
85
160
  mnid = message['nid']
86
161
 
87
162
  return false if nid && mnid && nid != mnid
88
- return false if ! points.include?(mpoint)
163
+
164
+ return false unless points.find { |point|
165
+ ps = point.split(':')
166
+ next false if ps[0] != mpoint
167
+ next false if ps[1] && ! message['tags'].include?(ps[1])
168
+ true }
89
169
 
90
170
  true
91
171
  end
92
172
 
173
+ def row_match?(unit, rs)
174
+
175
+ nid, points = @serie.first
176
+
177
+ row = nil
178
+
179
+ points.find { |point|
180
+ ps = point.split(':')
181
+ row = send("row_match_#{ps[0]}?", unit, rs, nid, ps[1..-1]) }
182
+
183
+ row
184
+ end
185
+
186
+ def row_match_status?(unit, rs, _, cdr)
187
+
188
+ rs[0].find { |exe|
189
+ (@exid == nil || exe.exid == @exid) &&
190
+ exe.status == cdr.first }
191
+ end
192
+
193
+ def row_match_tag?(unit, rs, nid, (name, value))
194
+
195
+ rs[1].find { |ptr|
196
+ ptr.type == 'tag' &&
197
+ (@exid == nil || ptr.exid == @exid) &&
198
+ (nid == nil || ptr.nid == nid) &&
199
+ (name == nil || ptr.name == name) &&
200
+ (value == nil || ptr.value == value) }
201
+ end
202
+
203
+ def row_match_var?(unit, rs, nid, (name, value))
204
+
205
+ rs[1].find { |ptr|
206
+ ptr.type == 'var' &&
207
+ (@exid == nil || ptr.exid == @exid) &&
208
+ (nid == nil || ptr.nid == nid) &&
209
+ (name == nil || ptr.name == name) &&
210
+ (value == nil || ptr.value == value.to_s) }
211
+ end
212
+ alias row_match_variable? row_match_var?
213
+
214
+ def row_match_tasker?(unit, rs, nid, (name, value))
215
+
216
+ rs[1].find { |ptr|
217
+ ptr.type == 'tasker' &&
218
+ (@exid == nil || ptr.exid == @exid) &&
219
+ (nid == nil || ptr.nid == nid) &&
220
+ (name == nil || ptr.name == name) &&
221
+ (value == nil || ptr.value == value) }
222
+ end
223
+
93
224
  def expand_args(opts)
94
225
 
95
226
  owait = opts[:wait]
96
- orepeat = opts[:repeat] || false
97
227
  otimeout = opts[:timeout]
98
228
  oontimeout = opts[:on_timeout] || opts[:ontimeout] || 'fail'
99
229
 
@@ -101,33 +231,42 @@ module Flor
101
231
  when nil, true
102
232
  [ [ [ nil, %w[ failed terminated ] ] ], # serie
103
233
  otimeout,
104
- oontimeout,
105
- orepeat ]
234
+ oontimeout ]
106
235
  when Numeric
107
236
  [ [ [ nil, %w[ failed terminated ] ] ], # serie
108
237
  owait, # timeout
109
- oontimeout,
110
- orepeat ]
238
+ oontimeout ]
111
239
  when String, Array
112
240
  [ parse_serie(owait), # serie
113
241
  otimeout,
114
- oontimeout,
115
- orepeat ]
242
+ oontimeout ]
116
243
  else
117
244
  fail ArgumentError.new(
118
245
  "don't know how to deal with #{owait.inspect} (#{owait.class})")
119
246
  end
120
247
  end
121
248
 
249
+ WAIT_REX =
250
+ %r{
251
+ \A
252
+ ([0-9_\-]+)?[ ]*
253
+ (
254
+ [a-z]+(?::[^:|;,\s]+){0,2}
255
+ (?:[|, ][a-z]+(:[^:|;,\s]+){0,2})*
256
+ )\z
257
+ }x
258
+
122
259
  def parse_serie(s)
123
260
 
124
261
  return s if s.is_a?(Array) && s.collect(&:class).uniq == [ Array ]
125
262
 
126
263
  (s.is_a?(String) ? s.split(';') : s)
127
264
  .collect { |ss|
128
- ni, pt = ss.strip.match(/\A([0-9_\-]+)? *([a-z|, ]+)\z/)[1, 2]
129
- [ ni, pt.split(/[|,]/).collect(&:strip) ]
130
- }
265
+ m = ss.strip.match(WAIT_REX)
266
+ fail ArgumentError.new(
267
+ "cannot parse #{ss.strip.inspect} wait directive") unless m
268
+ ni, pt = m[1, 2]
269
+ [ ni, pt.split(/[|,]/).collect(&:strip) ] }
131
270
  end
132
271
  end
133
272
  end
@@ -7,6 +7,9 @@ module Flor
7
7
  #
8
8
  # `wtl_default_timeout`:
9
9
  # when #launch ing or #wait ing, set the default timeout, in seconds
10
+ #
11
+ # `wtl_row_frequency`:
12
+ # sleep time between row waiter checks, defaults to 1
10
13
 
11
14
  DEFAULT_TIMEOUT = Flor.env_i('FLOR_DEFAULT_TIMEOUT')
12
15
 
@@ -16,16 +19,25 @@ module Flor
16
19
  @unit.hooker.add('wlist', self)
17
20
 
18
21
  @mutex = Mutex.new
19
- @waiters = []
22
+ @msg_waiters = []
23
+ @row_waiters = []
20
24
 
21
25
  @unit.instance_eval do
22
26
  def wait(exid, opts=true, more=nil)
23
27
  @hooker['wlist'].wait(exid, opts, more)
24
28
  end
25
29
  end
30
+
31
+ @row_thread = nil
32
+ @row_thread_status = nil
33
+ @row_frequency = @unit.conf['wtl_row_frequency'] || 1
26
34
  end
27
35
 
28
36
  def shutdown
37
+
38
+ @row_thread_status = :shutdown
39
+
40
+ nil
29
41
  end
30
42
 
31
43
  def notify(executor, message)
@@ -34,12 +46,12 @@ module Flor
34
46
 
35
47
  to_remove = []
36
48
 
37
- @waiters.each do |w|
49
+ @msg_waiters.each do |w|
38
50
  remove = w.notify(executor, message)
39
51
  to_remove << w if remove
40
52
  end
41
53
 
42
- @waiters -= to_remove
54
+ @msg_waiters -= to_remove
43
55
 
44
56
  end if message['consumed']
45
57
 
@@ -49,7 +61,7 @@ module Flor
49
61
  def wait(exid, opts, more)
50
62
 
51
63
  exid, opts =
52
- if opts == true && exid == 'idle'
64
+ if opts == true && ! Flor.is_exid?(exid)
53
65
  [ nil, { wait: exid } ]
54
66
  elsif opts == true || opts.is_a?(String)
55
67
  [ exid, { wait: opts } ]
@@ -65,11 +77,92 @@ module Flor
65
77
 
66
78
  @mutex.synchronize do
67
79
 
68
- (@waiters << Waiter.new(exid, opts)).last
80
+ waiter = Waiter.new(exid, opts)
81
+
82
+ fail ArgumentError.new(
83
+ "unit is stopped, it cannot wait for #{[ exid, opts ].inspect}"
84
+ ) if waiter.msg_waiter? && @unit.stopped?
85
+
86
+ waiters = [ @msg_waiters, @row_waiters ]
87
+ ts = [ 'msg', 'row' ]
88
+ if waiter.row_waiter?; waiters.reverse!; ts.reverse!; end
89
+
90
+ fail ArgumentError.new(
91
+ "cannot add a #{ts[0]} waiter, since there are already #{ts[1]} ones"
92
+ ) if waiters[1].any?
93
+
94
+ waiters.first << waiter
95
+
96
+ start_row_thread if @row_waiters.any?
97
+
98
+ waiter
69
99
 
70
100
  end.wait
71
101
  # returns the response message
72
102
  end
103
+
104
+ protected
105
+
106
+ def start_row_thread
107
+
108
+ return if @row_thread_status == :shutdown
109
+
110
+ @row_thread = nil if @row_thread && ! @row_thread.alive?
111
+ @row_thread_status = :running
112
+
113
+ @row_thread ||=
114
+ Thread.new do
115
+ loop do
116
+ sleep(@row_frequency)
117
+ break if [ :stop, :shutdown ].include?(@row_thread_status)
118
+ break if @row_waiters.empty?
119
+ check
120
+ end
121
+ end
122
+ end
123
+
124
+ def check
125
+
126
+ @mutex.synchronize do
127
+
128
+ to_remove = []
129
+
130
+ rs = row_query_all
131
+
132
+ @row_waiters.each do |w|
133
+ remove = w.check(@unit, rs)
134
+ to_remove << w if remove
135
+ end
136
+
137
+ @row_waiters -= to_remove
138
+ end
139
+
140
+ rescue => err
141
+
142
+ @unit.logger.warn("#{self.class}#check()", err)
143
+ end
144
+
145
+ def row_query_all
146
+
147
+ exes, ptrs =
148
+ @row_waiters.inject([ [], [] ]) { |a, w|
149
+ es, ps = w.to_query_hashes
150
+ a[0].concat(es)
151
+ a[1].concat(ps)
152
+ a }
153
+
154
+ [ row_query(:executions, exes), row_query(:pointers, ptrs) ]
155
+ end
156
+
157
+ def row_query(klass, hs)
158
+
159
+ return [] if hs.empty?
160
+
161
+ q = @unit.send(klass).where(hs.shift)
162
+ hs.each { |h| q = q.or(h) }
163
+
164
+ q.all
165
+ end
73
166
  end
74
167
  end
75
168