flor 0.16.1 → 0.16.2

Sign up to get free protection for your applications and to get access to all the features.
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