oflow 0.8.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 41a62977484dfde369899d13d67f34f41e88d643
4
- data.tar.gz: 80f9dc4d7fb42859bfd4b292393b53857ac418b8
3
+ metadata.gz: 2a972ca61c61251a79b699cce7cab0bd3a3810e9
4
+ data.tar.gz: 8a995d9f63fcd512f802520cf5097491d6edfbff
5
5
  SHA512:
6
- metadata.gz: a32921aa1cfae8f6053f571d5b9fad0bf473f2365f46b36f12ff5866ea913e8836d6f5463b46f7fe05cdc839dd93455f985aabaccc2ae1ecf85455f84353beb0
7
- data.tar.gz: 04ad4e9db6a86bb6ea02ab1f31f405f8abcb34c7af516e3598c0549414a5fbb704224eb91935a635884daad0837e1e3a8eb99805c0f074a54946fc31b818052e
6
+ metadata.gz: 5a31cb845ae3322e83fdcb51e8dead9f12991d6f4b4fdf3daa6f9c60ae04618fde0e220339c4a047370c482a2026d23c4901986ce5c0b4fffc3908c019655245
7
+ data.tar.gz: e8bca27d81e86ed6c390146fcc80a2071451f7fb2b0a73269d3a9443267910b382050aad46595560c7b1092684e769a5355d72ccef6b64f8334d559dfdd62cc3
data/README.md CHANGED
@@ -25,13 +25,21 @@ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announceme
25
25
 
26
26
  ## Release Notes
27
27
 
28
- ### Next Release 0.8
28
+ ### Current Release 1.0
29
+
30
+ - Add an Actors/Task that starts a application and exchanges data using stdout
31
+ and stdin.
32
+
33
+ - Add an Actors/Task that callout to a remote application and exchange data
34
+ using JSON over stdin and stdout.
35
+
36
+ ### Release 0.8
29
37
 
30
38
  - A somewhat non trivial example. GemChart collects statistics on my gems and
31
39
  stores those statistics. It also provides a web interface to view the
32
40
  statistics as a graph.
33
41
 
34
- ### Current Release 0.7
42
+ ### Release 0.7
35
43
 
36
44
  - Simplified the APIs and structure.
37
45
 
@@ -139,22 +147,16 @@ Hello World!
139
147
 
140
148
  ## Future Features
141
149
 
142
- - .svg file input for configuration.
143
-
144
- - Visio file input for configuration.
145
-
146
- - CallOut Actor that uses pipes and fork to use a non-Ruby actor.
147
-
148
- - Cross linking Tasks and Flows.
149
-
150
- - Dynamic links to Tasks and Flows.
151
-
152
150
  - High performance C version. Proof of concept puts the performance range at
153
151
  around 10M operations per second where an operation is one task execution per
154
152
  thread.
155
153
 
156
154
  - HTTP/Websockets based inpector.
157
155
 
156
+ - .svg file input for configuration.
157
+
158
+ - Visio file input for configuration.
159
+
158
160
  # Links
159
161
 
160
162
  ## Links of Interest
@@ -51,6 +51,10 @@ module OFlow
51
51
  {}
52
52
  end
53
53
 
54
+ def busy?()
55
+ false
56
+ end
57
+
54
58
  class Spec
55
59
  attr_reader :op
56
60
  attr_reader :type
@@ -15,3 +15,5 @@ require 'oflow/actors/merger'
15
15
  require 'oflow/actors/persister'
16
16
  require 'oflow/actors/timer'
17
17
  require 'oflow/actors/httpserver'
18
+ require 'oflow/actors/shellone'
19
+ require 'oflow/actors/shellrepeat'
@@ -0,0 +1,79 @@
1
+
2
+ require 'open3'
3
+
4
+ module OFlow
5
+ module Actors
6
+
7
+ #
8
+ class ShellOne < Actor
9
+
10
+ attr_reader :dir
11
+ attr_reader :cmd
12
+ attr_reader :timeout
13
+
14
+ def initialize(task, options)
15
+ super
16
+ @dir = options[:dir]
17
+ @dir = '.' if @dir.nil?
18
+ @dir = File.expand_path(@dir.strip)
19
+
20
+ @cmd = options[:cmd]
21
+ @timeout = options.fetch(:timeout, 1.0).to_f
22
+ @timeout = 0.001 if 0.001 > @timeout
23
+ end
24
+
25
+ def perform(op, box)
26
+ input = Oj.dump(box.contents, mode: :compat, indent: 0)
27
+ i, o, e, _ = Open3.popen3(@cmd, chdir: @dir)
28
+ i.write(input)
29
+ i.close
30
+ giveup = Time.now + @timeout
31
+ ra = [e, o]
32
+
33
+ out = ''
34
+ err = ''
35
+ ec = false # stderr closed flag
36
+ oc = false # stdout closed flag
37
+ while true
38
+ rem = giveup - Time.now
39
+ raise Exception.new("Timed out waiting for output.") if 0.0 > rem
40
+ rs, _, es = select(ra, nil, ra, rem)
41
+ unless es.nil?
42
+ es.each do |io|
43
+ ec |= io == e
44
+ oc |= io == o
45
+ end
46
+ end
47
+ break if ec && oc
48
+ unless rs.nil?
49
+ rs.each do |io|
50
+ if io == e && !ec
51
+ if io.closed? || io.eof?
52
+ ec = true
53
+ next
54
+ end
55
+ err += io.read_nonblock(1000)
56
+ elsif io == o && !oc
57
+ if io.closed? || io.eof?
58
+ oc = true
59
+ next
60
+ end
61
+ out += io.read_nonblock(1000)
62
+ end
63
+ end
64
+ end
65
+ break if ec && oc
66
+ end
67
+ if 0 < err.length
68
+ raise Exception.new(err)
69
+ end
70
+ output = Oj.load(out, mode: :compat)
71
+ o.close
72
+ e.close
73
+
74
+ task.ship(nil, Box.new(output, box.tracker))
75
+ end
76
+
77
+ end # ShellOne
78
+ end # Actors
79
+ end # OFlow
@@ -0,0 +1,110 @@
1
+
2
+ require 'open3'
3
+ require 'thread'
4
+
5
+ module OFlow
6
+ module Actors
7
+
8
+ #
9
+ class ShellRepeat < Actor
10
+
11
+ attr_reader :dir
12
+ attr_reader :cmd
13
+ attr_reader :timeout
14
+ attr_reader :out
15
+
16
+ def initialize(task, options)
17
+ super
18
+ @dir = options[:dir]
19
+ @dir = '.' if @dir.nil?
20
+ @dir = File.expand_path(@dir.strip)
21
+
22
+ @cmd = options[:cmd]
23
+ @timeout = options.fetch(:timeout, 1.0).to_f
24
+ @timeout = 0.001 if 0.001 > @timeout
25
+ @in = nil
26
+ @out = nil
27
+ @err = nil
28
+ @pid = nil
29
+ @outThread = nil
30
+ @ctxs = {}
31
+ @ctxCnt = 0
32
+ @killLock = Mutex.new
33
+ end
34
+
35
+ def perform(op, box)
36
+ if :kill == op
37
+ status = kill()
38
+ task.ship(:killed, Box.new(status, box.tracker))
39
+ return
40
+ end
41
+ if @pid.nil?
42
+ @in, @out, @err, wt = Open3.popen3(@cmd, chdir: @dir)
43
+ @pid = wt[:pid]
44
+ @outThread = Thread.start(self) do |me|
45
+ Thread.current[:name] = me.task.full_name() + "-out"
46
+ Oj.load(me.out, mode: :compat) do |o|
47
+ begin
48
+ k = o["ctx"]
49
+ raise Exception.new("missing context in #{cmd} reply") if k.nil?
50
+ raise Exception.new("context not found in #{cmd} reply for #{k}") unless me.hasCtx?(k)
51
+ ctx = me.clearCtx(k)
52
+ me.task.ship(nil, Box.new(o["out"], ctx))
53
+ rescue Exception => e
54
+ me.task.handle_error(e)
55
+ end
56
+ end
57
+ @outThread = nil
58
+ kill()
59
+ end
60
+ end
61
+ if @in.closed?
62
+ kill()
63
+ return
64
+ end
65
+ @ctxCnt += 1
66
+ @ctxs[@ctxCnt] = box.tracker
67
+ wrap = { "ctx" => @ctxCnt, "in" => box.contents }
68
+ input = Oj.dump(wrap, mode: :compat, indent: 0)
69
+ @in.write(input + "\n")
70
+ @in.flush
71
+ end
72
+
73
+ def busy?()
74
+ !@ctxs.empty?
75
+ end
76
+
77
+ def getCtx(ctx)
78
+ @ctxs[ctx]
79
+ end
80
+
81
+ def hasCtx?(ctx)
82
+ @ctxs.has_key?(ctx)
83
+ end
84
+
85
+ def clearCtx(ctx)
86
+ @ctxs.delete(ctx)
87
+ end
88
+
89
+ def kill()
90
+ status = nil
91
+ @killLock.synchronize do
92
+ # kill but don't wait for an exit. Leave it orphaned so a new app can be
93
+ # started.
94
+ status = Process.kill("HUP", @pid) unless @pid.nil?
95
+ @in.close() unless @in.nil?
96
+ @out.close() unless @out.nil?
97
+ @err.close() unless @err.nil?
98
+ Thread.kill(@outThread) unless @outThread.nil?
99
+ @in = nil
100
+ @out = nil
101
+ @err = nil
102
+ @pid = nil
103
+ @outThread = nil
104
+ end
105
+ status
106
+ end
107
+
108
+ end # ShellRepeat
109
+ end # Actors
110
+ end # OFlow
@@ -241,7 +241,7 @@ module OFlow
241
241
  # Returns the true if any requests are queued or a request is being processed.
242
242
  # @return [true|false] true if busy, false otherwise
243
243
  def busy?()
244
- !@current_req.nil? || !@queue.empty?
244
+ !@current_req.nil? || !@queue.empty? || @actor.busy?
245
245
  end
246
246
 
247
247
  # Returns the default timeout for the time to wait for the Task to be
@@ -52,12 +52,25 @@ module OFlow
52
52
  begin
53
53
  @actor.perform(op, box)
54
54
  rescue Exception => e
55
- ship(:error, Box.new([e, full_name()]))
55
+ handle_error(e)
56
56
  end
57
57
  end
58
58
  nil
59
59
  end
60
60
 
61
+ def handle_error(e)
62
+ #puts "*** #{e.class}: #{e.message}"
63
+ @history << Action.new(:error, Box.new([e, full_name()]))
64
+ end
65
+
66
+ def join(timeout)
67
+ giveup = Time.now + timeout
68
+ while @actor.busy?
69
+ raise Exception.new("Timed out") if giveup < Time.now
70
+ sleep(0.1)
71
+ end
72
+ end
73
+
61
74
  # Task API that adds entry to history.
62
75
  def ship(dest, box)
63
76
  @history << Action.new(dest, box)
@@ -1,5 +1,5 @@
1
1
 
2
2
  module OFlow
3
3
  # Current version of the module.
4
- VERSION = '0.8.0'
4
+ VERSION = '1.0.0'
5
5
  end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # broken code intentionally
5
+
6
+ x = y
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << File.dirname(File.dirname(__FILE__)) unless $:.include? File.dirname(File.dirname(__FILE__))
5
+
6
+ [ File.dirname(__FILE__),
7
+ File.join(File.dirname(__FILE__), "../../lib"),
8
+ File.join(File.dirname(__FILE__), "../../../oj/ext"),
9
+ File.join(File.dirname(__FILE__), "../../../oj/lib"),
10
+ ].each { |path| $: << path unless $:.include?(path) }
11
+
12
+ require 'oj'
13
+
14
+ input = Oj.load(STDIN, mode: :compat)
15
+ input.map! { |v| v * 2 }
16
+ puts Oj.dump(input, mode: :compat, indent: 0)
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << File.dirname(File.dirname(__FILE__)) unless $:.include? File.dirname(File.dirname(__FILE__))
5
+
6
+ require 'helper'
7
+ require 'oflow'
8
+ require 'oflow/test'
9
+
10
+ class ShellOneTest < ::MiniTest::Test
11
+
12
+ def test_shellone_config
13
+ root_dir = File.expand_path(File.dirname(File.dirname(__FILE__)))
14
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::ShellOne, state: ::OFlow::Task::BLOCKED,
15
+ dir: 'somewhere',
16
+ cmd: 'pwd',
17
+ timeout: 0.5)
18
+ assert_equal(File.join(root_dir, 'somewhere'), t.actor.dir, 'dir set from options')
19
+ assert_equal('pwd', t.actor.cmd, 'cmd set from options')
20
+ assert_equal(0.5, t.actor.timeout, 'timeout set from options')
21
+ end
22
+
23
+ def test_shellone_simple
24
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::ShellOne, state: ::OFlow::Task::BLOCKED,
25
+ dir: 'actors',
26
+ cmd: './doubler.rb',
27
+ timeout: 0.5)
28
+ t.receive(nil, ::OFlow::Box.new([1,2,3]))
29
+ assert_equal(1, t.history.size, 'one entry should be in the history')
30
+
31
+ assert_equal([2,4,6], t.history[0].box.contents, 'should have correct contents in shipment')
32
+ end
33
+
34
+ def test_shellone_bad
35
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::ShellOne, state: ::OFlow::Task::BLOCKED,
36
+ dir: 'actors',
37
+ cmd: './bad.rb',
38
+ timeout: 0.5)
39
+ t.receive(nil, ::OFlow::Box.new([1,2,3]))
40
+ assert_equal(1, t.history.size, 'one entry should be in the history')
41
+
42
+ assert_equal("Array", t.history[0].box.contents.class.name, 'should have an Array in shipment')
43
+ assert_equal("Exception", t.history[0].box.contents[0].class.name, 'should have an error in shipment')
44
+ end
45
+
46
+ end
47
+
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << File.dirname(File.dirname(__FILE__)) unless $:.include? File.dirname(File.dirname(__FILE__))
5
+
6
+ require 'helper'
7
+ require 'oflow'
8
+ require 'oflow/test'
9
+
10
+ class ShellRepeatTest < ::MiniTest::Test
11
+
12
+ def test_shellrepeat_config
13
+ root_dir = File.expand_path(File.dirname(File.dirname(__FILE__)))
14
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::ShellRepeat, state: ::OFlow::Task::BLOCKED,
15
+ dir: 'somewhere',
16
+ cmd: 'tripler.rb',
17
+ timeout: 0.5)
18
+ assert_equal(File.join(root_dir, 'somewhere'), t.actor.dir, 'dir set from options')
19
+ assert_equal('tripler.rb', t.actor.cmd, 'cmd set from options')
20
+ assert_equal(0.5, t.actor.timeout, 'timeout set from options')
21
+ end
22
+
23
+ def test_shellrepeat_simple
24
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::ShellRepeat, state: ::OFlow::Task::BLOCKED,
25
+ dir: 'actors',
26
+ cmd: './tripler.rb',
27
+ timeout: 0.5)
28
+ t.receive(nil, ::OFlow::Box.new([1,2,3]))
29
+ t.join(1.0)
30
+ assert_equal(1, t.history.size, 'one entry should be in the history')
31
+
32
+ assert_equal([3,6,9], t.history[0].box.contents, 'should have correct contents in shipment')
33
+ t.receive(nil, ::OFlow::Box.new([3,2,1]))
34
+ t.join(1.0)
35
+ assert_equal(2, t.history.size, 'two entries should be in the history')
36
+ # optional
37
+ t.receive(:kill, ::OFlow::Box.new([1,2,3]))
38
+ t.join(1.0)
39
+ end
40
+
41
+ end
42
+
@@ -66,7 +66,7 @@ class TimerTest < ::MiniTest::Test
66
66
  assert_equal(Time, t.actor.start.class, 'is the start time a Time?')
67
67
  assert(0.1 > (Time.now() + 2 - t.actor.start), 'is the start time now + 2?')
68
68
 
69
- assert_raises(::OFlow::ConfigError) do
69
+ assert_raises(ArgumentError) do
70
70
  ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, start: 'now')
71
71
  end
72
72
  end
@@ -83,7 +83,7 @@ class TimerTest < ::MiniTest::Test
83
83
  assert_equal(Time, t.actor.stop.class, 'is the stop time a Time?')
84
84
  assert(0.1 > (Time.now() + 2 - t.actor.stop), 'is the stop time now + 2?')
85
85
 
86
- assert_raises(::OFlow::ConfigError) do
86
+ assert_raises(ArgumentError) do
87
87
  ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, stop: 'now')
88
88
  end
89
89
  end
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << File.dirname(File.dirname(__FILE__)) unless $:.include? File.dirname(File.dirname(__FILE__))
5
+
6
+ [ File.dirname(__FILE__),
7
+ File.join(File.dirname(__FILE__), "../../lib"),
8
+ File.join(File.dirname(__FILE__), "../../../oj/ext"),
9
+ File.join(File.dirname(__FILE__), "../../../oj/lib"),
10
+ ].each { |path| $: << path unless $:.include?(path) }
11
+
12
+ require 'oj'
13
+
14
+ Oj.load(STDIN, mode: :compat) do |input|
15
+ ctx = input["ctx"]
16
+ a = input["in"]
17
+ a.map! { |v| v * 3 }
18
+ out = { "ctx" => ctx, "out" => a }
19
+ puts Oj.dump(out, mode: :compat, indent: 0)
20
+ STDOUT.flush
21
+ end
@@ -24,3 +24,4 @@ require 'actors/merger_test'
24
24
  require 'actors/persister_test'
25
25
  require 'actors/timer_test'
26
26
  require 'actors/httpserver_test'
27
+ require 'actors/shellone_test'
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << File.dirname(File.dirname(__FILE__)) unless $:.include? File.dirname(File.dirname(__FILE__))
5
+
6
+ [ File.dirname(__FILE__),
7
+ File.join(File.dirname(__FILE__), "../../lib"),
8
+ File.join(File.dirname(__FILE__), "../../../oj/ext"),
9
+ File.join(File.dirname(__FILE__), "../../../oj/lib"),
10
+ ].each { |path| $: << path unless $:.include?(path) }
11
+
12
+ #input = gets()
13
+
14
+ #puts "*** input: #{input}"
15
+ puts "*** #{`pwd`}"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-22 00:00:00.000000000 Z
11
+ date: 2015-02-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Operations Workflow in Ruby. This implements a workflow/process flow
14
14
  using multiple task nodes that each have their own queues and execution thread.
@@ -31,6 +31,8 @@ files:
31
31
  - lib/oflow/actors/merger.rb
32
32
  - lib/oflow/actors/persister.rb
33
33
  - lib/oflow/actors/relay.rb
34
+ - lib/oflow/actors/shellone.rb
35
+ - lib/oflow/actors/shellrepeat.rb
34
36
  - lib/oflow/actors/timer.rb
35
37
  - lib/oflow/actors/trigger.rb
36
38
  - lib/oflow/box.rb
@@ -50,16 +52,22 @@ files:
50
52
  - lib/oflow/test/actorwrap.rb
51
53
  - lib/oflow/tracker.rb
52
54
  - lib/oflow/version.rb
55
+ - test/actors/bad.rb
53
56
  - test/actors/balancer_test.rb
57
+ - test/actors/doubler.rb
54
58
  - test/actors/httpserver_test.rb
55
59
  - test/actors/log_test.rb
56
60
  - test/actors/merger_test.rb
57
61
  - test/actors/persister_test.rb
62
+ - test/actors/shellone_test.rb
63
+ - test/actors/shellrepeat_test.rb
58
64
  - test/actors/timer_test.rb
65
+ - test/actors/tripler.rb
59
66
  - test/actorwrap_test.rb
60
67
  - test/all_tests.rb
61
68
  - test/box_test.rb
62
69
  - test/collector.rb
70
+ - test/d2.rb
63
71
  - test/flow_basic_test.rb
64
72
  - test/flow_cfg_error_test.rb
65
73
  - test/flow_linked_test.rb
@@ -92,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
100
  version: '0'
93
101
  requirements: []
94
102
  rubyforge_project: oflow
95
- rubygems_version: 2.2.2
103
+ rubygems_version: 2.4.5
96
104
  signing_key:
97
105
  specification_version: 4
98
106
  summary: Operations Workflow in Ruby