main_loop 0.1.1.16821

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4995f3729d31c8254173cf965afa34b85e2925f5aef044f8c0f9d5355c01cf83
4
+ data.tar.gz: e0df7e76e16f78ddf26897fd3e2df03272c33768ffb4080b69874d859be32581
5
+ SHA512:
6
+ metadata.gz: 0e3836071670675d4b55809c525c330dd6c9eeb9b75bb0375b30f9e43df425832bd9ebb57aa72cf8c90f549563e33e061bf7239cb10e3cbed7e198ebbc77ca81
7
+ data.tar.gz: d8c65293ee7b8ea5247d40b6d8a371078b1f77b00c0f87cc5890c1b07a6a2742672e94d7e84e2235ca58c88353d3ff0286fdf592b326f11e79cdab9afbc9c204
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014-2019 Рнд Софт
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # MainLoop
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/main_loop.svg)](https://rubygems.org/gems/main_loop)
4
+ [![Gem](https://img.shields.io/gem/dt/main_loop.svg)](https://rubygems.org/gems/main_loop/versions)
5
+ [![YARD](https://badgen.net/badge/YARD/doc/blue)](http://www.rubydoc.info/gems/main_loop)
6
+
7
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/ed48b89a9793a074cd23/test_coverage)](https://codeclimate.com/github/RnD-Soft/main_loop/test_coverage)
8
+ [![Maintainability](https://api.codeclimate.com/v1/badges/ed48b89a9793a074cd23/maintainability)](https://codeclimate.com/github/RnD-Soft/main_loop/maintainability)
9
+ [![Quality](https://lysander.x.rnds.pro/api/v1/badges/main_loop_quality.svg)](https://lysander.x.rnds.pro/api/v1/badges/main_loop_quality.html)
10
+ [![Outdated](https://lysander.x.rnds.pro/api/v1/badges/main_loop_outdated.svg)](https://lysander.x.rnds.pro/api/v1/badges/main_loop_outdated.html)
11
+ [![Vulnerabilities](https://lysander.x.rnds.pro/api/v1/badges/main_loop_vulnerable.svg)](https://lysander.x.rnds.pro/api/v1/badges/main_loop_vulnerable.html)
12
+
13
+ MainLoop is a simple main application implementation to control subprocesses(children) and threads.
14
+
15
+ Features:
16
+ - reaping children
17
+ - handling SIGTERM SIGINT to shutdown children(and threads) gracefully
18
+ - restarting children
19
+ - termination the children
20
+
21
+ # Usage
22
+
23
+ Example usage:
24
+
25
+ ```ruby
26
+ require 'main_loop'
27
+
28
+ logger = Logger.new(STDOUT)
29
+ logger.level = Logger::DEBUG
30
+
31
+ bus = MainLoop::Bus.new
32
+
33
+ dispatcher = MainLoop::Dispatcher.new(bus, logger: logger)
34
+ mainloop = MainLoop::Loop.new(bus, dispatcher, logger: logger)
35
+
36
+ MainLoop::ProcessHandler.new dispatcher, 'test1', retry_count: 3, logger: logger do
37
+ sleep 2
38
+ exit! 0
39
+ end
40
+
41
+ MainLoop::ProcessHandler.new dispatcher, 'test2', retry_count: 2, logger: logger do
42
+ trap 'TERM' do
43
+ exit(0)
44
+ end
45
+ sleep 2
46
+ exit! 1
47
+ end
48
+
49
+ MainLoop::ThreadHandler.new dispatcher, 'thread2', retry_count: 0, logger: logger do
50
+ system('sleep 15;echo ok')
51
+ end
52
+
53
+ mainloop.run
54
+ ```
55
+
56
+
57
+ # Installation
58
+
59
+ It's a gem:
60
+ ```bash
61
+ gem install main_loop
62
+ ```
63
+ There's also the wonders of [the Gemfile](http://bundler.io):
64
+ ```ruby
65
+ gem 'main_loop'
66
+ ```
67
+
68
+
@@ -0,0 +1,67 @@
1
+ require 'monitor'
2
+ require 'timeouter'
3
+
4
+ module MainLoop
5
+ class Bus
6
+
7
+ include MonitorMixin
8
+
9
+ attr_reader :read, :write
10
+
11
+ EOL = "\n".freeze
12
+
13
+ def initialize
14
+ super()
15
+ @read, @write = IO.pipe
16
+ @read.sync = true
17
+ @write.sync = true
18
+ @buffer = ''
19
+ end
20
+
21
+ def empty?(timeout = 0)
22
+ !wait_for_event(timeout)
23
+ end
24
+
25
+ def close
26
+ @write.close rescue nil
27
+ @read.close rescue nil
28
+ end
29
+
30
+ def closed?
31
+ @write.closed? || @read.closed?
32
+ end
33
+
34
+ def puts(str)
35
+ synchronize do
36
+ @write.puts str.to_s
37
+ end
38
+ end
39
+
40
+ def wait_for_event(timeout)
41
+ IO.select([@read], [], [], timeout)
42
+ end
43
+
44
+ def gets(timeout)
45
+ Timeouter.loop(timeout) do |t|
46
+ line = gets_nonblock if wait_for_event(t.left)
47
+ return line if line
48
+ end
49
+ end
50
+
51
+ def gets_nonblock
52
+ while (ch = @read.read_nonblock(1))
53
+ @buffer << ch
54
+ next if ch != MainLoop::Bus::EOL
55
+
56
+ line = @buffer
57
+ @buffer = ''
58
+ return line&.strip
59
+ end
60
+ nil
61
+ rescue IO::WaitReadable
62
+ nil
63
+ end
64
+
65
+ end
66
+ end
67
+
@@ -0,0 +1,105 @@
1
+ require 'monitor'
2
+ require 'logger'
3
+
4
+ module MainLoop
5
+ class Dispatcher
6
+
7
+ include MonitorMixin
8
+
9
+ attr_reader :bus, :handlers, :logger
10
+
11
+ def initialize(bus, timeout: 5, logger: nil)
12
+ super()
13
+ @bus = bus
14
+ @timeout = timeout
15
+ @handlers = []
16
+ @logger = logger || Logger.new(nil)
17
+ end
18
+
19
+ def reap(statuses)
20
+ statuses.each do |(pid, status)|
21
+ reap_by_id(pid, status)
22
+ end
23
+ end
24
+
25
+ def reap_by_id(id, status)
26
+ synchronize do
27
+ if (handler = handlers.find {|h| h.id == id })
28
+ logger.info("Reap handler #{handler.name.inspect}. Status: #{status.inspect}")
29
+ handler.reap(status)
30
+ else
31
+ logger.debug("Reap unknown handler. Status: #{status.inspect}. Skipped")
32
+ end
33
+ end
34
+ end
35
+
36
+ def add_handler(handler)
37
+ synchronize do
38
+ handler.term if terminating?
39
+ handlers << handler
40
+ end
41
+ end
42
+
43
+ def terminating?
44
+ @terminating_at
45
+ end
46
+
47
+ def term
48
+ synchronize do
49
+ if terminating?
50
+ logger.info('Terminate FORCE all handlers')
51
+ handlers.each(&:kill)
52
+ else
53
+ @terminating_at ||= Time.now
54
+ logger.info('Terminate all handlers')
55
+ handlers.each(&:term)
56
+ end
57
+ end
58
+ end
59
+
60
+ def tick
61
+ log_status if logger.debug?
62
+ return unless terminating?
63
+
64
+ try_exit!
65
+
66
+ return if @killed || !need_force_kill?
67
+
68
+ @killed = true
69
+ logger.info('Killing all handlers by timeout')
70
+ handlers.each(&:kill)
71
+ end
72
+
73
+ def need_force_kill?
74
+ @terminating_at && (Time.now - @terminating_at) >= @timeout
75
+ end
76
+
77
+ def pids
78
+ handlers.map{|h| h.pid rescue nil }.compact
79
+ end
80
+
81
+ # :nocov:
82
+ def try_exit!
83
+ synchronize do
84
+ return unless handlers.all?(&:finished?)
85
+
86
+ logger.info('All handlers finished exiting...')
87
+ status = handlers.all?(&:success?) ? 0 : 1
88
+ exit status
89
+ end
90
+ end
91
+ # :nocov:
92
+
93
+ # :nocov:
94
+ def log_status
95
+ total = handlers.size
96
+ running = handlers.count(&:running?)
97
+ finihsed = handlers.count(&:finished?)
98
+ term_text = terminating? ? 'TERM' : ''
99
+ logger.debug("Total:#{total} Running:#{running} Finihsed:#{finihsed}. #{term_text}".strip)
100
+ end
101
+ # :nocov:
102
+
103
+ end
104
+ end
105
+
@@ -0,0 +1,91 @@
1
+ require 'logger'
2
+
3
+ module MainLoop
4
+ class Handler
5
+
6
+ attr_reader :dispatcher, :name, :logger
7
+
8
+ def initialize(dispatcher, name, *_args, retry_count: 0, logger: nil, **_kwargs)
9
+ @dispatcher = dispatcher
10
+ @name = name
11
+ @code = 0
12
+ @retry_count = retry_count
13
+ @logger = logger || Logger.new(nil)
14
+ @handler_type = 'Unknown'
15
+ end
16
+
17
+ # :nocov:
18
+ def id(*_args)
19
+ raise 'not implemented!'
20
+ end
21
+ # :nocov:
22
+
23
+ # :nocov:
24
+ def term(*_args)
25
+ raise 'not implemented!'
26
+ end
27
+ # :nocov:
28
+
29
+ # :nocov:
30
+ def run(*_args)
31
+ raise 'not implemented!'
32
+ end
33
+ # :nocov:
34
+
35
+ # :nocov:
36
+ def kill(*_args)
37
+ raise 'not implemented!'
38
+ end
39
+ # :nocov:
40
+
41
+ # :nocov:
42
+ def reap(*_args)
43
+ raise 'not implemented!'
44
+ end
45
+ # :nocov:
46
+
47
+ # :nocov:
48
+ def publish(event)
49
+ dispatcher.bus.puts(event)
50
+ end
51
+ # :nocov:
52
+
53
+ # :nocov:
54
+ def finished?
55
+ @finished
56
+ end
57
+ # :nocov:
58
+
59
+ # :nocov:
60
+ def success?
61
+ finished? && @success
62
+ end
63
+ # :nocov:
64
+
65
+ # :nocov:
66
+ def running?
67
+ !finished?
68
+ end
69
+ # :nocov:
70
+
71
+ # :nocov:
72
+ def terminating?
73
+ @terminating_at
74
+ end
75
+ # :nocov:
76
+
77
+ def handle_retry
78
+ if @retry_count == :unlimited
79
+ logger.info "#{@handler_type}[#{name}] retry...."
80
+ self.run(&@block)
81
+ elsif @retry_count && (@retry_count -= 1) >= 0
82
+ logger.info "#{@handler_type}[#{name}] retry...."
83
+ self.run(&@block)
84
+ else
85
+ publish(:term)
86
+ end
87
+ end
88
+
89
+ end
90
+ end
91
+
@@ -0,0 +1,122 @@
1
+ require 'logger'
2
+ require 'timeouter'
3
+
4
+ module MainLoop
5
+
6
+ TERM_SIGNALS = %w[INT TERM].freeze
7
+
8
+ class Loop
9
+
10
+ attr_reader :logger
11
+
12
+ def initialize(bus, dispatcher, logger: nil)
13
+ STDOUT.sync = true
14
+ STDERR.sync = true
15
+ @bus = bus
16
+ @dispatcher = dispatcher
17
+ @logger = logger || Logger.new(nil)
18
+ end
19
+
20
+ def run(timeout = 0)
21
+ install_signal_handlers(@bus)
22
+
23
+ start_loop_forever(timeout)
24
+ rescue StandardError => e
25
+ # :nocov:
26
+ logger.fatal("Exception in Main Loop: #{e.inspect}")
27
+ exit!(2)
28
+ # :nocov:
29
+ end
30
+
31
+ def start_loop_forever(timeout = 0)
32
+ wait = [timeout, 10].min
33
+ Timeouter.loop(timeout) do
34
+ event = @bus.gets(wait)
35
+ logger.debug("command:#{event}")
36
+
37
+ case event
38
+ when 'term'
39
+ term(event)
40
+ when /sig:/
41
+ signal(event)
42
+ when /reap:/
43
+ reap(event)
44
+ when nil
45
+ logger.debug('Empty event: reaping...')
46
+ else
47
+ logger.debug("unknown event:#{event}")
48
+ end
49
+
50
+ @dispatcher.reap(reap_children)
51
+ @dispatcher.tick
52
+ end
53
+ end
54
+
55
+ # :nocov:
56
+ def install_signal_handlers(bus)
57
+ TERM_SIGNALS.each do |sig|
58
+ trap(sig) do |*_args|
59
+ Thread.new(bus) {|b| b.puts "sig:#{sig}" }
60
+ end
61
+ end
62
+
63
+ trap 'CLD' do
64
+ Thread.new(bus) {|b| b.puts 'sig:CLD' }
65
+ end
66
+ end
67
+ # :nocov:
68
+
69
+ def signal(command)
70
+ _, sig = command.split(':')
71
+ logger.debug("signal:#{sig}")
72
+
73
+ if TERM_SIGNALS.include?(sig)
74
+ @dispatcher.term
75
+ elsif sig == 'CLD'
76
+ # nothing to do child will reap later
77
+ else
78
+ logger.info("unhandled signal:#{sig}")
79
+ end
80
+ end
81
+
82
+ def term(_command)
83
+ @dispatcher.term unless @dispatcher.terminating?
84
+ end
85
+
86
+ def reap(command)
87
+ _, id, status = command.split(':')
88
+ @dispatcher.reap_by_id(id, status)
89
+ end
90
+
91
+ def reap_children
92
+ results = []
93
+
94
+ @dispatcher.pids.each do |pid|
95
+ if (result = self.wait2(pid))
96
+ results << result
97
+ end
98
+ end
99
+
100
+ Timeouter.loop(2) do
101
+ unless (result = self.wait2(-1))
102
+ break
103
+ end
104
+
105
+ results << result
106
+ end
107
+
108
+ results
109
+ rescue Errno::ECHILD
110
+ results
111
+ end
112
+
113
+ # :nocov:
114
+ def wait2(pid)
115
+ Process.wait2(pid, ::Process::WNOHANG)
116
+ end
117
+ # :nocov:
118
+
119
+ end
120
+
121
+ end
122
+
@@ -0,0 +1,81 @@
1
+ require 'main_loop/handler'
2
+
3
+ module MainLoop
4
+ class ProcessHandler < MainLoop::Handler
5
+
6
+ attr_reader :pid
7
+
8
+ def initialize(dispatcher, name, **kwargs, &block)
9
+ super
10
+ @handler_type = 'Process'
11
+ @pid = nil
12
+ dispatcher.add_handler(self)
13
+
14
+ run(&block) if block_given?
15
+ end
16
+
17
+ def id
18
+ @pid
19
+ end
20
+
21
+ def reap(status)
22
+ logger.info "Process[#{name}] exited: Pid:#{@pid} Status: #{status.exitstatus.inspect} Termsig: #{status.termsig.inspect} Success: #{status.success?}"
23
+ @pid = nil
24
+ @finished = true
25
+ @success = !!status.success?
26
+
27
+ return if terminating?
28
+
29
+ handle_retry
30
+ end
31
+
32
+ def term
33
+ unless @pid
34
+ @terminating_at ||= Time.now
35
+ logger.debug "Process[#{name}] alredy terminated. Skipped."
36
+ return
37
+ end
38
+
39
+ if terminating?
40
+ @success = false
41
+ logger.info "Process[#{name}] send force terminate: KILL Pid:#{@pid}"
42
+ ::Process.kill('KILL', @pid) rescue nil
43
+ else
44
+ @terminating_at ||= Time.now
45
+ logger.info "Process[#{name}] send terminate: Pid:#{@pid}"
46
+ ::Process.kill('TERM', @pid) rescue nil
47
+ end
48
+ end
49
+
50
+ def kill
51
+ unless @pid
52
+ logger.debug "Process[#{name}] alredy Killed. Skipped."
53
+ return
54
+ end
55
+
56
+ @success = false
57
+ logger.info "Process[#{name}] send kill: Pid:#{@pid}"
58
+ ::Process.kill('KILL', @pid) rescue nil
59
+ end
60
+
61
+ def run(&block)
62
+ return if terminating?
63
+
64
+ @block = block
65
+ start_fork(&@block)
66
+ end
67
+
68
+ protected
69
+
70
+ def start_fork
71
+ @pid = Kernel.fork do
72
+ yield
73
+ end
74
+ @finished = false
75
+ logger.info "Process[#{name}] created: Pid:#{@pid}"
76
+ end
77
+
78
+
79
+ end
80
+ end
81
+
@@ -0,0 +1,83 @@
1
+ require 'main_loop/handler'
2
+
3
+ module MainLoop
4
+
5
+ class ThreadHandler < MainLoop::Handler
6
+
7
+ attr_reader :thread
8
+
9
+ def initialize(dispatcher, name, **kwargs, &block)
10
+ super
11
+ @handler_type = 'Thread'
12
+ @thread = nil
13
+ dispatcher.add_handler(self)
14
+
15
+ run(&block) if block_given?
16
+ end
17
+
18
+ def id
19
+ @thread&.object_id.to_s
20
+ end
21
+
22
+ def reap(status)
23
+ logger.info "Thread[#{name}] exited: thread:#{@thread} Status:#{status}"
24
+ @thread = nil
25
+ @finished = true
26
+ @success = false
27
+
28
+ return if terminating?
29
+
30
+ handle_retry
31
+ end
32
+
33
+ def term
34
+ unless @thread
35
+ @terminating_at ||= Time.now
36
+ logger.debug "Thread[#{name}] alredy terminated. Skipped."
37
+ return
38
+ end
39
+
40
+ if terminating?
41
+ @success = false
42
+ logger.info "Thread[#{name}] send force terminate: KILL thread:#{@thread}"
43
+ @thread.kill rescue nil
44
+ else
45
+ @terminating_at ||= Time.now
46
+ logger.info "Thread[#{name}] send terminate: thread:#{@thread}"
47
+ end
48
+ end
49
+
50
+ def kill
51
+ unless @thread
52
+ logger.debug "Thread[#{name}] alredy Killed. Skipped."
53
+ return
54
+ end
55
+
56
+ @success = false
57
+ logger.info "Thread[#{name}] send kill: thread:#{@thread}"
58
+ @thread.kill rescue nil
59
+ end
60
+
61
+ def run(&block)
62
+ return if terminating?
63
+
64
+ @block = block
65
+ start_thread(&@block)
66
+ end
67
+
68
+ protected
69
+
70
+ def start_thread
71
+ @thread = Thread.new do
72
+ yield(self)
73
+ ensure
74
+ publish("reap:#{id}:exited")
75
+ end
76
+ @finished = false
77
+ logger.info "Thread[#{name}] created: thread:#{@thread}"
78
+ end
79
+
80
+
81
+ end
82
+ end
83
+
@@ -0,0 +1,6 @@
1
+ module MainLoop
2
+
3
+ VERSION = '0.1.1'.freeze
4
+
5
+ end
6
+
data/lib/main_loop.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'main_loop/bus'
2
+ require 'main_loop/loop'
3
+ require 'main_loop/dispatcher'
4
+ require 'main_loop/process_handler'
5
+ require 'main_loop/thread_handler'
6
+
7
+
8
+
9
+ module MainLoop
10
+
11
+ class << self
12
+
13
+ end
14
+
15
+ end
16
+
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: main_loop
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1.16821
5
+ platform: ruby
6
+ authors:
7
+ - Samoilenko Yuri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-09-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.1
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec_junit_formatter
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: simplecov
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: simplecov-console
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: timeouter
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ description: Main Loop implementation to control subprocesses and threads
118
+ email:
119
+ - kinnalru@gmail.com
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - LICENSE
125
+ - README.md
126
+ - lib/main_loop.rb
127
+ - lib/main_loop/bus.rb
128
+ - lib/main_loop/dispatcher.rb
129
+ - lib/main_loop/handler.rb
130
+ - lib/main_loop/loop.rb
131
+ - lib/main_loop/process_handler.rb
132
+ - lib/main_loop/thread_handler.rb
133
+ - lib/main_loop/version.rb
134
+ homepage: https://github.com/RnD-Soft/main_loop
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubygems_version: 3.0.3
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Main Loop implementation to control subprocesses and threads
157
+ test_files: []