deadly_serious 0.4.1 → 0.5.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: 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: