oflow 0.8.0 → 1.0.0

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