deadly_serious 0.4.1 → 0.5.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: 775816edf4860d7516908442da4d8ffaa2793aba
4
- data.tar.gz: 0516bbdbccd02bf0102833c1dfcbe2ecc1bbac9f
3
+ metadata.gz: c9050728b34b8dd157c703876d8e5fe9e60a8453
4
+ data.tar.gz: 613441848d930e6429eac0660040f795ce868f94
5
5
  SHA512:
6
- metadata.gz: 9ddb27632bffcbba2bd7c9f008e0662b27c5dcdcd048ae244cffbe577f7afdcfb19c5c7723891dde7f221f9cf81ccd7681d0aec0ebc9e62d6f25c7cf3820ec7a
7
- data.tar.gz: d49e4442d46a296c638d84e6120ae83d25f174d55e353c34e2a63a9741a5116b49f738be2a8642356f84932fa16872884b0a01ec66ca233013a62173adecd609
6
+ metadata.gz: 4294cc634cc2b4b417f4f0dff25f7a8a706d5ef1e262505d0dcf14f8a4834cf9ce22e19c906bd04c5bcf27792a5b8045ccf5e0c18a7e68a77d461af35da99064
7
+ data.tar.gz: d7f9fd19630d96c51b79524b6d0c2995023aad7b1ca0d9df49e96e18953ab856e4f43e961bb913cde0b7cc868bcdcae4016f3f654c45816e3605983e26c33b2d
data/.gitignore CHANGED
@@ -1,18 +1,20 @@
1
- *.gem
2
- *.rbc
3
1
  .bundle
4
2
  .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
3
+ .idea/
9
4
  coverage
10
5
  doc/
6
+ *.gem
7
+ Gemfile.lock
8
+ InstalledFiles
11
9
  lib/bundler/man
10
+ output.data
12
11
  pkg
12
+ *.rbc
13
13
  rdoc
14
14
  spec/reports
15
+ tags
15
16
  test/tmp
16
17
  test/version_tmp
17
18
  tmp
18
- output.data
19
+ _yardoc
20
+ .yardoc
@@ -20,7 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency 'bundler', '~> 1.3'
22
22
  spec.add_development_dependency 'rake'
23
- spec.add_development_dependency 'yard'
24
23
 
25
24
  spec.add_dependency 'json'
26
25
  end
@@ -8,16 +8,27 @@ module DeadlySerious
8
8
  reader.each { |packet| super(packet.chomp) }
9
9
  end
10
10
 
11
+ # Alias to #send
12
+ def emit(packet = nil)
13
+ send(packet)
14
+ end
15
+
16
+ # Send a packet to the next process
11
17
  def send(packet = nil)
12
18
  send_buffered(packet)
13
19
  flush_buffer
14
20
  end
15
21
 
22
+ # Send a packet to the next process,
23
+ # however, accumulate some of them
24
+ # before send to gain a little
25
+ # efficency.
16
26
  def send_buffered(packet = nil)
17
27
  @writer << packet if packet
18
28
  @writer << "\n"
19
29
  end
20
30
 
31
+ # Send all not yet sent packets.
21
32
  def flush_buffer
22
33
  @writer.flush
23
34
  end
@@ -1,19 +1,40 @@
1
1
  require 'socket'
2
+ require 'deadly_serious/engine/lazy_io'
2
3
 
3
4
  module DeadlySerious
4
5
  module Engine
5
6
  # Fake class, it's actually a factory ¬¬
6
7
  module Channel
7
- def self.new(name, data_dir: nil, pipe_dir: nil)
8
+ def self.new(name)
8
9
  matcher = name.match(/^(>)?(.*?)(?:(:)(\d{1,5}))?$/)
9
10
  if matcher[1] == '>'
10
- FileChannel.new(matcher[2], data_dir)
11
+ FileChannel.new(matcher[2], @data_dir)
11
12
  elsif matcher[3] == ':'
12
13
  SocketChannel.new(matcher[2], matcher[4].to_i)
13
14
  else
14
- PipeChannel.new(matcher[2], pipe_dir)
15
+ PipeChannel.new(matcher[2], @pipe_dir)
15
16
  end
16
17
  end
18
+
19
+ def self.config(data_dir, pipe_dir, preserve_pipe_dir)
20
+ @data_dir = data_dir
21
+ @pipe_dir = pipe_dir
22
+ @preserve_pipe_dir = preserve_pipe_dir
23
+ end
24
+
25
+ def self.setup
26
+ FileUtils.mkdir_p(@pipe_dir) unless File.exist?(@pipe_dir)
27
+ end
28
+
29
+ def self.teardown
30
+ if !@preserve_pipe_dir && File.exist?(@pipe_dir)
31
+ FileUtils.rm_r(@pipe_dir, force: true, secure: true)
32
+ end
33
+ end
34
+
35
+ def self.create_pipe(pipe_name)
36
+ new(pipe_name).create
37
+ end
17
38
  end
18
39
 
19
40
  class FileChannel
@@ -35,6 +56,10 @@ module DeadlySerious
35
56
  fail %(File "#{@io_name}" not found) unless File.exist?(@io_name)
36
57
  open(@io_name, 'w')
37
58
  end
59
+
60
+ def io
61
+ LazyIo.new(self)
62
+ end
38
63
  end
39
64
 
40
65
  class PipeChannel
@@ -56,6 +81,10 @@ module DeadlySerious
56
81
  fail %(Pipe "#{@io_name}" not found) unless File.exist?(@io_name)
57
82
  open(@io_name, 'w')
58
83
  end
84
+
85
+ def io
86
+ LazyIo.new(self)
87
+ end
59
88
  end
60
89
 
61
90
  class SocketChannel
@@ -83,6 +112,10 @@ module DeadlySerious
83
112
  server = TCPServer.new(@port)
84
113
  server.accept
85
114
  end
115
+
116
+ def io
117
+ LazyIo.new(self)
118
+ end
86
119
  end
87
120
  end
88
121
  end
@@ -0,0 +1,55 @@
1
+ module DeadlySerious
2
+ module Engine
3
+
4
+ # Restrict IO class that opens ONLY
5
+ # when trying to read something.
6
+ #
7
+ # Also, used to reopend lost connections.
8
+ #
9
+ # By "restrict", I mean it implements
10
+ # just a few IO operations.
11
+ class LazyIo
12
+ def initialize(channel)
13
+ @channel = channel
14
+ end
15
+
16
+ def gets
17
+ open_reader
18
+ @io.gets
19
+ end
20
+
21
+ def each(&block)
22
+ open_reader
23
+ @io.each &block
24
+ end
25
+
26
+ def <<(element)
27
+ open_writer
28
+ @io << element
29
+ end
30
+
31
+ def closed?
32
+ @io.nil? || @io.closed?
33
+ end
34
+
35
+ def close
36
+ @io.close
37
+ @io = nil
38
+ end
39
+
40
+ private
41
+
42
+ def open_reader
43
+ if closed?
44
+ @io = @channel.open_reader
45
+ end
46
+ end
47
+
48
+ def open_writer
49
+ if closed?
50
+ @io = @channel.open_writer
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ require 'fileutils'
2
+
3
+ module DeadlySerious
4
+ module Engine
5
+ module OpenIo
6
+ def run(*args, readers: [], writers:[])
7
+ opened_readers = readers.map { |reader| wrap_io(reader) }
8
+ opened_writers = writers.map { |writer| wrap_io(writer) }
9
+ super(*args, readers: opened_readers, writers: opened_writers)
10
+ ensure
11
+ if opened_writers
12
+ opened_writers.each { |writer| close_io(writer) }
13
+ end
14
+ if opened_readers
15
+ opened_readers.each { |reader| close_io(reader) }
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def close_io(io)
22
+ return unless io
23
+ return if io.closed?
24
+ io.close
25
+ rescue => e
26
+ # Intentionally eat the error
27
+ # because it's being used inside
28
+ # an "ensure" block
29
+ puts e.inspect
30
+ end
31
+
32
+ def wrap_io(pipe_name)
33
+ Channel.new(pipe_name).io
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,55 +1,75 @@
1
- require 'fileutils'
2
1
  require 'deadly_serious/engine/channel'
2
+ require 'deadly_serious/engine/open_io'
3
+ require 'deadly_serious/processes/splitter'
3
4
 
4
5
  module DeadlySerious
5
6
  module Engine
6
7
  class Spawner
7
- def initialize(data_dir: './data', pipe_dir: "/tmp/deadly_serious/#{Process.pid}", preserve_pipe_dir: false)
8
- @data_dir = data_dir
9
- @pipe_dir = pipe_dir
10
- @preserve_pipe_dir = preserve_pipe_dir
8
+ def initialize(data_dir: './data',
9
+ pipe_dir: "/tmp/deadly_serious/#{Process.pid}",
10
+ preserve_pipe_dir: false)
11
11
  @ids = []
12
-
13
- FileUtils.mkdir_p(pipe_dir) unless File.exist?(pipe_dir)
12
+ Channel.config(data_dir, pipe_dir, preserve_pipe_dir)
14
13
  end
15
14
 
16
15
  def run
16
+ Channel.setup
17
17
  run_pipeline
18
18
  wait_children
19
- rescue Exception => e
19
+ rescue => e
20
20
  kill_children
21
21
  raise e
22
22
  ensure
23
- if !@preserve_pipe_dir && File.exist?(@pipe_dir)
24
- FileUtils.rm_r(@pipe_dir, force: true, secure: true)
25
- end
23
+ Channel.teardown
26
24
  end
27
25
 
28
- def spawn_source(a_class, *args, writer: self.class.dasherize(a_class.name))
29
- create_pipe(writer)
26
+ def spawn_process(a_class, *args, process_name: a_class.name, readers: [], writers: [])
27
+ writers.each { |writer| create_pipe(writer) }
30
28
  fork_it do
31
- set_process_name(a_class.name)
32
- write_pipe(writer) do
33
- a_class.new.run(io, *args)
34
- end
29
+ set_process_name(process_name)
30
+ append_open_io_if_needed(a_class)
31
+ a_class.new.run(*args, readers: readers, writers: writers)
35
32
  end
36
33
  end
37
34
 
38
- def spawn_process(a_class, *args, readers: [], writers: [])
39
- writers.each { |writer| create_pipe(writer) }
40
- fork_it do
41
- set_process_name(a_class.name)
42
- open_readers = readers.map { |reader| read_pipe(reader) }
43
- open_writers = writers.map { |writer| write_pipe(writer) }
44
- begin
45
- a_class.new.run(*args, readers: open_readers, writers: open_writers)
46
- ensure
47
- open_writers.each { |writer| writer.close unless writer.closed? }
48
- open_readers.each { |reader| reader.close unless reader.closed? }
49
- end
35
+ def spawn_processes(a_class, *args, process_name: a_class.name, reader_pattern: nil, writers: [])
36
+ number = last_number(reader_pattern)
37
+
38
+ loop do
39
+ this_reader = pattern_replace(reader_pattern, number)
40
+ break unless Channel.exists?(this_reader)
41
+ spawn_process(a_class,
42
+ *args,
43
+ process_name: process_name,
44
+ readers: [this_reader],
45
+ writers: Array(writers))
46
+ number += 1
50
47
  end
51
48
  end
52
49
 
50
+ def spawn_source(a_class, *args, writer: a_class.dasherize(a_class.name))
51
+ spawn_process(a_class, *args, process_name: process_name, readers: [], writers: [writer])
52
+ end
53
+
54
+ def spawn_splitter(process_name: 'Splitter', reader: nil, writer: '>output01.txt', number: 2)
55
+ start = last_number(writer)
56
+ finish = start + number - 1
57
+
58
+ writers = (start..finish).map { |index| pattern_replace(writer, index) }
59
+
60
+ spawn_process(Processes::Splitter,
61
+ process_name: process_name,
62
+ readers: Array(reader),
63
+ writers: writers)
64
+ end
65
+
66
+ def spawn_socket_splitter(process_name: 'SocketSplitter', reader: nil, port: 11000, number: 2)
67
+ spawn_splitter(process_name: process_name,
68
+ reader: reader,
69
+ writer: "localhost:#{port}",
70
+ number: number)
71
+ end
72
+
53
73
  def spawn_command(a_shell_command)
54
74
  command = a_shell_command.dup
55
75
  a_shell_command.scan(/\(\((.*?)\)\)/) do |(pipe_name)|
@@ -61,12 +81,18 @@ module DeadlySerious
61
81
 
62
82
  private
63
83
 
84
+ def append_open_io_if_needed(a_class)
85
+ a_class.send(:prepend, OpenIo) unless a_class.include?(OpenIo)
86
+ end
87
+
88
+ def create_pipe(pipe_name)
89
+ Channel.create_pipe(pipe_name)
90
+ end
91
+
64
92
  # @!group Process Control
65
93
 
66
94
  def fork_it
67
- @ids << fork do
68
- yield
69
- end
95
+ @ids << fork { yield }
70
96
  end
71
97
 
72
98
  def wait_children
@@ -83,34 +109,30 @@ module DeadlySerious
83
109
  end
84
110
 
85
111
  # @!endgroup
86
- # @!group Channel Helpers
112
+ # @!group Minor Helpers
87
113
 
88
- def channel_for(pipe_name)
89
- Channel.new(pipe_name, data_dir: @data_dir, pipe_dir: @pipe_dir)
114
+ def self.dasherize(a_string)
115
+ a_string.gsub(/(.)([A-Z])/, '\1-\2').downcase.gsub(/\W+/, '-')
90
116
  end
91
117
 
92
- def create_pipe(pipe_name)
93
- channel_for(pipe_name).create
94
- end
118
+ def last_number_pattern(a_string)
119
+ last_number_pattern = /(\d+)[^\d]*$/.match(a_string)
120
+ raise %(Writer name "#{writer}" should have a number) if last_number_pattern.nil?
95
121
 
96
- def read_pipe(pipe_name)
97
- channel_for(pipe_name).open_reader
122
+ last_number_pattern[1]
98
123
  end
99
124
 
100
- def write_pipe(pipe_name)
101
- channel = channel_for(pipe_name)
102
- return channel.open_writer unless block_given?
103
-
104
- channel.open_writer do |io|
105
- yield io
106
- end
125
+ def last_number(a_string)
126
+ last_number_pattern(a_string).to_i
107
127
  end
108
128
 
109
- # @!endgroup
110
- # @!group Minor Helpers
129
+ def pattern_replace(a_string, number)
130
+ pattern = last_number_pattern(a_string)
131
+ pattern_length = pattern.size
132
+ find_pattern = /#{pattern}([^\d]*)$/
133
+ replace_pattern = "%0.#{pattern_length}d\\1"
111
134
 
112
- def self.dasherize(a_string)
113
- a_string.gsub(/(.)([A-Z])/, '\1-\2').downcase.gsub(/\W+/, '-')
135
+ a_string.sub(find_pattern, sprintf(replace_pattern, number))
114
136
  end
115
137
  end
116
138
  end
@@ -0,0 +1,34 @@
1
+ module DeadlySerious
2
+ module Processes
3
+ class ResilientSplitter
4
+ def initialize
5
+ @reallocate = false
6
+ Signal.trap('USR1') do
7
+ @reallocate = true
8
+ end
9
+ end
10
+
11
+ def run(readers: [], writers: [])
12
+ reader = readers.first
13
+ outputs = writers.dup
14
+ current = nil
15
+ reader.each do |line|
16
+ begin
17
+ if @reallocate
18
+ @reallocate = false
19
+ outputs = writers.dup
20
+ end
21
+ current = outputs.first
22
+ current << line
23
+ outputs.rotate!
24
+ rescue Errno::EPIPE => e
25
+ puts e.inspect
26
+ outputs.delete(current)
27
+ raise e if outputs.empty?
28
+ redo
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,12 +1,24 @@
1
1
  module DeadlySerious
2
2
  module Processes
3
3
  class Splitter
4
+ def initialize
5
+ Signal.trap('USR1') { @outputs = @writers.dup }
6
+ end
4
7
  def run(readers: [], writers: [])
5
- reader = readers.first
6
- outputs = writers.dup
7
- reader.each do |line|
8
- outputs.first << line
9
- outputs.rotate!
8
+ @writers ||= writers
9
+ reader = readers.first
10
+ @outputs = @writers.dup
11
+ begin
12
+ reader.each do |line|
13
+ @current = @outputs.first
14
+ @current << line
15
+ @outputs.rotate!
16
+ end
17
+ rescue => e
18
+ puts e.inspect
19
+ @outputs.delete(@current)
20
+ raise e if @outputs.empty?
21
+ retry
10
22
  end
11
23
  end
12
24
  end
@@ -1,3 +1,3 @@
1
1
  module DeadlySerious
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -0,0 +1 @@
1
+ ctags -R . --exclude=.git
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deadly_serious
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ronie Uliana
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-12 00:00:00.000000000 Z
11
+ date: 2013-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: yard
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - '>='
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - '>='
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: json
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -84,13 +70,17 @@ files:
84
70
  - lib/deadly_serious/engine/channel.rb
85
71
  - lib/deadly_serious/engine/json_io.rb
86
72
  - lib/deadly_serious/engine/json_process.rb
73
+ - lib/deadly_serious/engine/lazy_io.rb
74
+ - lib/deadly_serious/engine/open_io.rb
87
75
  - lib/deadly_serious/engine/push_pop.rb
88
76
  - lib/deadly_serious/engine/push_pop_send.rb
89
77
  - lib/deadly_serious/engine/spawner.rb
90
78
  - lib/deadly_serious/processes/db_source.rb
91
79
  - lib/deadly_serious/processes/joiner.rb
80
+ - lib/deadly_serious/processes/resilient_splitter.rb
92
81
  - lib/deadly_serious/processes/splitter.rb
93
82
  - lib/deadly_serious/version.rb
83
+ - scripts/update_ctags.sh
94
84
  homepage: https://github.com/ruliana/deadly_serious
95
85
  licenses:
96
86
  - MIT
@@ -119,4 +109,3 @@ summary: Flow Based Programming engine that relies on named pipes and Linux proc
119
109
  with the Operating System, i.e., the S.O. is *part* of the program, it's not something
120
110
  *below* it.
121
111
  test_files: []
122
- has_rdoc: