flor 0.0.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/CHANGELOG.md +13 -0
  2. data/LICENSE.txt +1 -1
  3. data/Makefile +66 -0
  4. data/README.md +57 -0
  5. data/fail.txt +7 -0
  6. data/flor.gemspec +12 -9
  7. data/intercepted.txt +123 -0
  8. data/lib/flor/colours.rb +140 -0
  9. data/lib/flor/conf.rb +88 -0
  10. data/lib/flor/core/executor.rb +473 -0
  11. data/lib/flor/core/node.rb +397 -0
  12. data/lib/flor/core/procedure.rb +600 -0
  13. data/lib/flor/core/texecutor.rb +209 -0
  14. data/lib/flor/core.rb +93 -0
  15. data/lib/flor/dollar.rb +248 -0
  16. data/lib/flor/errors.rb +36 -0
  17. data/lib/flor/flor.rb +556 -0
  18. data/lib/flor/log.rb +336 -0
  19. data/lib/flor/migrations/0001_tables.rb +122 -0
  20. data/lib/flor/parser.rb +414 -0
  21. data/lib/flor/pcore/_arr.rb +49 -0
  22. data/lib/flor/pcore/_atom.rb +43 -0
  23. data/lib/flor/pcore/_att.rb +160 -0
  24. data/lib/flor/pcore/_dump.rb +60 -0
  25. data/lib/flor/pcore/_err.rb +30 -0
  26. data/lib/flor/pcore/_happly.rb +73 -0
  27. data/lib/flor/pcore/_obj.rb +65 -0
  28. data/lib/flor/pcore/_skip.rb +63 -0
  29. data/lib/flor/pcore/apply.rb +60 -0
  30. data/lib/flor/pcore/arith.rb +46 -0
  31. data/lib/flor/pcore/break.rb +71 -0
  32. data/lib/flor/pcore/cmp.rb +72 -0
  33. data/lib/flor/pcore/cond.rb +57 -0
  34. data/lib/flor/pcore/cursor.rb +223 -0
  35. data/lib/flor/pcore/define.rb +96 -0
  36. data/lib/flor/pcore/fail.rb +45 -0
  37. data/lib/flor/pcore/ife.rb +56 -0
  38. data/lib/flor/pcore/loop.rb +53 -0
  39. data/lib/flor/pcore/map.rb +75 -0
  40. data/lib/flor/pcore/match.rb +70 -0
  41. data/lib/flor/pcore/move.rb +65 -0
  42. data/lib/flor/pcore/noeval.rb +46 -0
  43. data/lib/flor/pcore/noret.rb +47 -0
  44. data/lib/flor/pcore/push.rb +69 -0
  45. data/lib/flor/pcore/sequence.rb +39 -0
  46. data/lib/flor/pcore/set.rb +76 -0
  47. data/lib/flor/pcore/stall.rb +35 -0
  48. data/lib/flor/pcore/until.rb +122 -0
  49. data/lib/flor/pcore/val.rb +40 -0
  50. data/lib/flor/punit/cancel.rb +69 -0
  51. data/lib/flor/punit/cmap.rb +76 -0
  52. data/lib/flor/punit/concurrence.rb +149 -0
  53. data/lib/flor/punit/every.rb +46 -0
  54. data/lib/flor/punit/on.rb +81 -0
  55. data/lib/flor/punit/schedule.rb +68 -0
  56. data/lib/flor/punit/signal.rb +47 -0
  57. data/lib/flor/punit/sleep.rb +53 -0
  58. data/lib/flor/punit/task.rb +109 -0
  59. data/lib/flor/punit/trace.rb +51 -0
  60. data/lib/flor/punit/trap.rb +100 -0
  61. data/lib/flor/to_string.rb +81 -0
  62. data/lib/flor/tools/env.rb +103 -0
  63. data/lib/flor/tools/repl.rb +231 -0
  64. data/lib/flor/unit/executor.rb +260 -0
  65. data/lib/flor/unit/hooker.rb +186 -0
  66. data/lib/flor/unit/journal.rb +52 -0
  67. data/lib/flor/unit/loader.rb +181 -0
  68. data/lib/flor/unit/logger.rb +181 -0
  69. data/lib/flor/unit/models/execution.rb +105 -0
  70. data/lib/flor/unit/models/pointer.rb +31 -0
  71. data/lib/flor/unit/models/timer.rb +52 -0
  72. data/lib/flor/unit/models/trace.rb +31 -0
  73. data/lib/flor/unit/models/trap.rb +130 -0
  74. data/lib/flor/unit/models.rb +106 -0
  75. data/lib/flor/unit/scheduler.rb +419 -0
  76. data/lib/flor/unit/storage.rb +633 -0
  77. data/lib/flor/unit/tasker.rb +191 -0
  78. data/lib/flor/unit/waiter.rb +146 -0
  79. data/lib/flor/unit/wlist.rb +77 -0
  80. data/lib/flor/unit.rb +50 -0
  81. data/lib/flor.rb +40 -3
  82. metadata +152 -22
  83. checksums.yaml +0 -7
  84. data/Rakefile +0 -52
@@ -0,0 +1,473 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Flor
27
+
28
+ class Executor
29
+
30
+ attr_reader :unit
31
+ attr_reader :execution
32
+ attr_reader :traps
33
+
34
+ def initialize(unit, traps, execution)
35
+
36
+ @unit = unit
37
+ @execution = execution
38
+ @traps = traps
39
+ end
40
+
41
+ def conf; @unit.conf; end
42
+ def exid; @execution['exid']; end
43
+
44
+ def node(msg_or_nid, node_instance=false)
45
+
46
+ return nil unless msg_or_nid
47
+
48
+ nid = msg_or_nid
49
+ msg = msg_or_nid
50
+ #
51
+ if nid.is_a?(String)
52
+ msg = nil
53
+ else
54
+ nid = msg['nid']
55
+ end
56
+
57
+ n = @execution['nodes'][nid]
58
+
59
+ return nil unless n
60
+ node_instance ? Flor::Node.new(self, n, msg) : n
61
+ end
62
+
63
+ def counter(key)
64
+
65
+ @execution['counters'][key.to_s] || -1
66
+ end
67
+
68
+ def counter_add(key, count)
69
+
70
+ k = key.to_s
71
+
72
+ @execution['counters'][k] ||= 0
73
+ @execution['counters'][k] += count
74
+ end
75
+
76
+ def counter_next(key)
77
+
78
+ counter_add(key, 1)
79
+ end
80
+
81
+ def trigger_hook(hook, message)
82
+
83
+ hook.notify(self, message)
84
+ end
85
+
86
+ def trigger_trap(trap, message)
87
+
88
+ del, msgs = trap.trigger(self, message)
89
+ @traps.delete(trap) if del
90
+
91
+ msgs
92
+ end
93
+
94
+ # Given a nid, returns a copy of all the var the node sees
95
+ #
96
+ def vars(nid, vs={})
97
+
98
+ n = node(nid); return vs unless n
99
+
100
+ (n['vars'] || {})
101
+ .each { |k, v| vs[k] = Flor.dup(v) unless vs.has_key?(k) }
102
+
103
+ if @unit.loader && n['parent'] == nil && n['vdomain'] != false
104
+
105
+ @unit.loader.variables(n['vdomain'] || Flor.domain(@exid))
106
+ .each { |k, v| vs[k] = Flor.dup(v) unless vs.has_key?(k) }
107
+ end
108
+
109
+ if cn = n['cnid']; vars(cn, vs); end
110
+ if pa = n['parent']; vars(pa, vs); end
111
+
112
+ vs
113
+ end
114
+
115
+ protected
116
+
117
+ def make_node(message)
118
+
119
+ nid = message['nid']
120
+
121
+ now = Flor.tstamp
122
+
123
+ node = {
124
+ 'nid' => nid,
125
+ 'parent' => message['from'],
126
+ 'payload' => message['payload'],
127
+ 'status' => [ { 'status' => nil, 'point' => 'execute', 'ctime' => now } ],
128
+ 'ctime' => now,
129
+ 'mtime' => now }
130
+
131
+ %w[ vars vdomain cnid noreply dbg ].each do |k|
132
+ v = message[k]
133
+ node[k] = v if v != nil
134
+ end
135
+ #
136
+ # vars: variables
137
+ # vdomain: variable domain (used in conjuction with the loader)
138
+ # cnid: closure nid
139
+ # noreply: this new node has a parent but shouldn't reply to it
140
+ # dbg: used to debug messages (useful @node['dbg'] when 'receive')
141
+
142
+ @execution['nodes'][nid] = node
143
+ end
144
+
145
+ def determine_heat(message)
146
+
147
+ nid = message['nid']
148
+
149
+ return unless nid
150
+
151
+ node =
152
+ message['point'] == 'execute' ?
153
+ make_node(message) :
154
+ @execution['nodes'][nid]
155
+
156
+ return unless node
157
+
158
+ return if node['heat']
159
+
160
+ n = Flor::Node.new(self, node, message)
161
+
162
+ mt = message['tree']
163
+ mt = [ 'noeval', [ 0 ], mt[2] ] \
164
+ if mt[0] == '_' && Flor.is_array_of_trees?(mt[1])
165
+
166
+ nt = n.lookup_tree(nid)
167
+
168
+ node['tree'] = mt if mt && (mt != nt)
169
+ tree = node['tree'] || nt
170
+
171
+ t0 = tree[0]
172
+ t0 = (t0.is_a?(Array) && t0[0] == '_dqs') ? n.expand(t0[1]) : t0
173
+
174
+ node['heat0'] = tree[0]
175
+ node['heat'] = heat = n.deref(t0)
176
+
177
+ node['heap'] = heap =
178
+ if ! heat.is_a?(Array)
179
+ '_val'
180
+ elsif tree && tree[1] == []
181
+ '_val'
182
+ elsif heat[0] == '_proc'
183
+ heat[1]['proc']
184
+ elsif heat[0] == '_func'
185
+ 'apply'
186
+ elsif heat[0] == '_task'
187
+ 'task'
188
+ elsif Flor.is_tree_head_tree?(tree)
189
+ '_happly'
190
+ else
191
+ '_val'
192
+ end
193
+
194
+ if heap == 'task' && heat[0] == '_task'
195
+ #
196
+ # rewrite `alpha` into `task alpha`
197
+
198
+ l = message['tree'][2]
199
+
200
+ message['otree'] = Flor.dup(message['tree'])
201
+
202
+ message['tree'][0] =
203
+ 'task'
204
+ message['tree'][1].unshift(
205
+ [ '_att', [ [ '_sqs', heat[1]['task'], l ] ], l ])
206
+ end
207
+ end
208
+
209
+ def execute(message)
210
+
211
+ apply(@execution['nodes'][message['nid']], message)
212
+ end
213
+
214
+ def apply(node, message)
215
+
216
+ heap =
217
+ if node['heat']
218
+ node['heap']
219
+ else
220
+ node['failure'] ? '_err' : nil
221
+ end
222
+
223
+ return ([{
224
+ 'point' => 'receive',
225
+ 'nid' => message['from'], 'from' => message['nid'],
226
+ 'exid' => message['exid'],
227
+ 'payload' => Flor.dupm(message['payload'], 'ret' => node['heat0'])
228
+ }]) if heap == nil && message['accept_symbol'] == true
229
+
230
+ return error_reply(
231
+ node, message, "don't know how to apply #{node['heat0'].inspect}"
232
+ ) if heap == nil
233
+
234
+ heac = Flor::Procedure[heap]
235
+ fail NameError.new("unknown procedure #{heap.inspect}") unless heac
236
+
237
+ head = heac.new(self, node, message)
238
+
239
+ #return process(head.rewrite) if head.is_a?(Flor::Macro)
240
+ return [ head.rewrite ] if head.is_a?(Flor::Macro)
241
+
242
+ nid = message['nid']
243
+ pt = message['point']
244
+ pt = "do_#{pt}" if pt == 'receive' || pt == 'cancel'
245
+
246
+ if pt == 'execute'
247
+ head.pre_execute
248
+ pnode = @execution['nodes'][node['parent']]
249
+ cnodes = pnode && (pnode['cnodes'] ||= [])
250
+ cnodes << nid if cnodes && ( ! cnodes.include?(nid))
251
+ end
252
+ head.send(pt)
253
+ end
254
+
255
+ def remove_node(n)
256
+
257
+ return unless n
258
+
259
+ n['removed'] = true # or should I use "status" => "removed" ?
260
+
261
+ @unit.remove_node(exid, n)
262
+ # remove timers/waiters for this node, if any
263
+
264
+ return if (n['closures'] || []).any?
265
+ # don't remove the node if it's a closure for some other nodes
266
+
267
+ nid = n['nid']
268
+
269
+ return if nid == '0'
270
+ # don't remove if it's the "root" node
271
+
272
+ @execution['nodes'].delete(nid)
273
+ end
274
+
275
+ def leave(node, message)
276
+
277
+ ts = node && node['tags']
278
+ return [] unless ts && ts.any?
279
+
280
+ [
281
+ { 'point' => 'left',
282
+ 'tags' => ts,
283
+ 'exid' => exid,
284
+ 'nid' => node['nid'],
285
+ 'payload' => message['payload'] }
286
+ ]
287
+ end
288
+
289
+ # "receive_terminated_or_ceased"
290
+ #
291
+ def receive_toc(message, fnode)
292
+
293
+ msg =
294
+ %w[
295
+ exid nid from payload
296
+ ].inject({}) { |h, k| h[k] = message[k] if message.has_key?(k); h }
297
+
298
+ msg['sm'] = message['m']
299
+
300
+ msg['point'] =
301
+ if message['from'] == '0' || @execution['nodes'].empty? # termination?
302
+ 'terminated'
303
+ else
304
+ 'ceased'
305
+ end
306
+
307
+ [ msg ]
308
+ end
309
+
310
+ def receive(message)
311
+
312
+ from = message['from']
313
+ fnode = @execution['nodes'][from]
314
+
315
+ if fnode && fnode.has_key?('aret')
316
+ message['payload']['ret'] = fnode['aret']
317
+ end
318
+
319
+ remove_node(fnode)
320
+ messages = leave(fnode, message)
321
+
322
+ nid = message['nid']
323
+ nid = nil if fnode && fnode['noreply']
324
+
325
+ return messages + receive_toc(message, fnode) unless nid
326
+
327
+ node = @execution['nodes'][nid]
328
+
329
+ return messages unless node
330
+
331
+ messages + apply(node, message)
332
+ end
333
+
334
+ def error_reply(node, message, err)
335
+
336
+ m = message
337
+ .select { |k, v| %w[ sm exid nid from payload tree ].include?(k) }
338
+
339
+ m['point'] = 'failed'
340
+ m['fpoint'] = message['point']
341
+ m['error'] = Flor.to_error(err)
342
+
343
+ Flor.detail_msg(self, m, flag: true) if @unit.conf['log_err']
344
+
345
+ #if m['error']['msg'].match(/\AToo many open files in system/)
346
+ # puts "=" * 80 + ' ...'
347
+ # system(`lsof #{Process.pid}`)
348
+ # puts "=" * 80 + ' .'
349
+ #end
350
+ #
351
+ # can't seem to provoke that error, so keeping the trap
352
+ # around but commented out...
353
+
354
+ [ m ]
355
+ end
356
+
357
+ def task(message)
358
+
359
+ @execution['tasks'][message['nid']] =
360
+ { 'tasker' => message['tasker'], 'name' => message['taskname'] }
361
+
362
+ @unit.tasker.task(message)
363
+ end
364
+ alias detask task
365
+
366
+ def return(message)
367
+
368
+ @execution['tasks'].delete(message['nid'])
369
+
370
+ [
371
+ { 'point' => 'receive',
372
+ 'exid' => message['exid'],
373
+ 'nid' => message['nid'],
374
+ 'payload' => message['payload'],
375
+ 'tasker' => message['tasker'] }
376
+ ]
377
+ end
378
+
379
+ def cancel(message)
380
+
381
+ if n = @execution['nodes'][message['nid']]
382
+ apply(n, message)
383
+ else
384
+ [] # nothing, node gone
385
+ end
386
+ end
387
+
388
+ def process(message)
389
+
390
+ begin
391
+
392
+ message['m'] = counter_next('msgs') # number messages
393
+ message['pr'] = counter('runs') # "processing run"
394
+
395
+ determine_heat(message)
396
+
397
+ ms = []
398
+ ms += @unit.notify(self, message) # pre
399
+
400
+ ms += self.send(message['point'].to_sym, message)
401
+
402
+ message['payload'] = message.delete('pld') if message.has_key?('pld')
403
+ message['consumed'] = Flor.tstamp
404
+
405
+ ms += @unit.notify(self, message) # post
406
+
407
+ ms.each { |m| m['er'] = counter('runs') } # "emitting run"
408
+
409
+ rescue => e
410
+ error_reply(nil, message, e)
411
+ rescue ScriptError => se
412
+ error_reply(nil, message, se)
413
+ end
414
+ end
415
+
416
+ def trap(message)
417
+
418
+ exid = message['exid']
419
+ nid = message['nid']
420
+ trap = message['trap']
421
+
422
+ nd = node(nid)
423
+ nd['exid'] = exid
424
+
425
+ @traps << @unit.trap(nd, trap)
426
+
427
+ []
428
+ end
429
+
430
+ def entered(message); []; end
431
+ def left(message); []; end
432
+
433
+ def ceased(message); []; end
434
+
435
+ def terminated(message)
436
+
437
+ message['vars'] = @execution['nodes']['0']['vars']
438
+ # especially useful for debugging
439
+
440
+ []
441
+ end
442
+
443
+
444
+ def failed(message)
445
+
446
+ n = node(message['nid'])
447
+
448
+ fail RuntimeError.new(
449
+ "node #{message['nid']} is gone, cannot flag it as failed"
450
+ ) unless n
451
+
452
+ #begin
453
+ n['failure'] = Flor.dup(message)
454
+ #rescue; pp message; exit 0; end
455
+
456
+ oep = lookup_on_error_parent(message)
457
+ return oep.trigger_on_error if oep
458
+
459
+ Flor.detail_msg(self, message) if @unit.conf['log_err']
460
+
461
+ []
462
+ end
463
+
464
+ def signal(message); []; end
465
+
466
+ def lookup_on_error_parent(message)
467
+
468
+ nd = Flor::Node.new(self, nil, message).on_error_parent
469
+ nd ? nd.to_procedure : nil
470
+ end
471
+ end
472
+ end
473
+