strelka 0.17.0 → 0.18.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Manifest.txt +2 -0
- data/lib/strelka.rb +25 -1
- data/lib/strelka/command/start.rb +2 -2
- data/lib/strelka/multirunner.rb +123 -0
- data/lib/strelka/signal_handling.rb +117 -0
- data/spec/strelka_spec.rb +14 -0
- metadata +4 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7dad453a5871872ca4f793e78253510d82e4ed27fbb173c82629501c60962477
|
4
|
+
data.tar.gz: 8bf1feafca4c80397808322ed09c93b9c6ed56cd917c5db12ab0f1e33cf8a59f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a220e3f1d3f6fdaf963173170dc2186ebf580a2b93c522017df7cfd7ba3ef3d45850943552a510e7bacbed11271dca7d0a42f1349b74651c64c5a9a957f852f5
|
7
|
+
data.tar.gz: b8eb408709e9fb4269d0516236d9d203a4ea3e59580b82c9b8d9939d7932c6722d79f0076aa44fd08638ce890dc7c6270e4c772ed9b064d698e043055596078e
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/Manifest.txt
CHANGED
@@ -73,6 +73,7 @@ lib/strelka/httpresponse/negotiation.rb
|
|
73
73
|
lib/strelka/httpresponse/session.rb
|
74
74
|
lib/strelka/mixins.rb
|
75
75
|
lib/strelka/multipartparser.rb
|
76
|
+
lib/strelka/multirunner.rb
|
76
77
|
lib/strelka/paramvalidator.rb
|
77
78
|
lib/strelka/plugins.rb
|
78
79
|
lib/strelka/router.rb
|
@@ -81,6 +82,7 @@ lib/strelka/router/exclusive.rb
|
|
81
82
|
lib/strelka/session.rb
|
82
83
|
lib/strelka/session/db.rb
|
83
84
|
lib/strelka/session/default.rb
|
85
|
+
lib/strelka/signal_handling.rb
|
84
86
|
lib/strelka/testing.rb
|
85
87
|
lib/strelka/websocketserver.rb
|
86
88
|
lib/strelka/websocketserver/heartbeat.rb
|
data/lib/strelka.rb
CHANGED
@@ -22,7 +22,7 @@ module Strelka
|
|
22
22
|
extend Loggability
|
23
23
|
|
24
24
|
# Library version constant
|
25
|
-
VERSION = '0.
|
25
|
+
VERSION = '0.18.0'
|
26
26
|
|
27
27
|
# Version-control revision constant
|
28
28
|
REVISION = %q$Revision$
|
@@ -77,6 +77,11 @@ module Strelka
|
|
77
77
|
singleton_attr_reader :after_configure_hooks
|
78
78
|
@after_configure_hooks = Set.new
|
79
79
|
|
80
|
+
##
|
81
|
+
# An Array of callbacks to be run before forking a handler process
|
82
|
+
singleton_attr_reader :before_fork_hooks
|
83
|
+
@before_fork_hooks = Set.new
|
84
|
+
|
80
85
|
##
|
81
86
|
# True if the after_configure hooks have already (started to) run.
|
82
87
|
singleton_predicate_reader :after_configure_hooks_run
|
@@ -123,6 +128,25 @@ module Strelka
|
|
123
128
|
end
|
124
129
|
|
125
130
|
|
131
|
+
### Call the before-fork hooks.
|
132
|
+
def self::call_before_fork_hooks
|
133
|
+
self.log.debug " calling %d before-fork hooks" % [ self.before_fork_hooks.length ]
|
134
|
+
|
135
|
+
self.before_fork_hooks.to_a.each do |hook|
|
136
|
+
self.log.debug " %s line %s..." % hook.source_location
|
137
|
+
hook.call
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
### Register a callback to be run before forking a Strelka::Handler
|
143
|
+
### subprocess.
|
144
|
+
def self::before_fork( &block )
|
145
|
+
raise LocalJumpError, "no block given" unless block
|
146
|
+
self.before_fork_hooks << block
|
147
|
+
end
|
148
|
+
|
149
|
+
|
126
150
|
### Load the specified +config_file+, install the config in all objects with
|
127
151
|
### Configurability, and call any callbacks registered via #after_configure.
|
128
152
|
def self::load_config( config_file=nil, defaults=nil )
|
@@ -3,6 +3,7 @@
|
|
3
3
|
# frozen-string-literal: true
|
4
4
|
|
5
5
|
require 'strelka/cli' unless defined?( Strelka::CLI )
|
6
|
+
require 'strelka/multirunner'
|
6
7
|
|
7
8
|
|
8
9
|
# Command to start a Strelka application
|
@@ -51,8 +52,7 @@ module Strelka::CLI::Start
|
|
51
52
|
if options.number == 1
|
52
53
|
app.run
|
53
54
|
else
|
54
|
-
options.number
|
55
|
-
Process.waitall
|
55
|
+
Strelka::MultiRunner.new( app, options.number ).run
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# frozen-string-literal: true
|
4
|
+
|
5
|
+
require 'strelka' unless defined?( Strelka )
|
6
|
+
require 'strelka/signal_handling'
|
7
|
+
|
8
|
+
# Load multiple simulatneous Strelka handlers (of a single type) with
|
9
|
+
# proper signal handling.
|
10
|
+
#
|
11
|
+
class Strelka::MultiRunner
|
12
|
+
extend Loggability
|
13
|
+
include Strelka::SignalHandling
|
14
|
+
|
15
|
+
# Loggability API -- set up logging under the 'strelka' log host
|
16
|
+
log_to :strelka
|
17
|
+
|
18
|
+
|
19
|
+
# Signals we understand.
|
20
|
+
QUEUE_SIGS = [ :QUIT, :INT, :TERM, :CHLD ]
|
21
|
+
|
22
|
+
|
23
|
+
### Create a new multirunner instance given a handler +app_class+,
|
24
|
+
### and the +number+ of instances to start.
|
25
|
+
def initialize( app_class, number=2 )
|
26
|
+
@handler_pids = []
|
27
|
+
@running = false
|
28
|
+
@app_class = app_class
|
29
|
+
@number = number
|
30
|
+
|
31
|
+
self.set_up_signal_handling
|
32
|
+
end
|
33
|
+
|
34
|
+
# The child handler pids.
|
35
|
+
attr_reader :handler_pids
|
36
|
+
|
37
|
+
# In this instance currently managing children?
|
38
|
+
attr_reader :running
|
39
|
+
|
40
|
+
# How many handler children to manage.
|
41
|
+
attr_reader :number
|
42
|
+
|
43
|
+
# The class name of the handler.
|
44
|
+
attr_reader :app_class
|
45
|
+
|
46
|
+
|
47
|
+
### Start the child handlers via fork(), block for signals.
|
48
|
+
def run
|
49
|
+
@running = true
|
50
|
+
|
51
|
+
# Set up traps for common signals
|
52
|
+
self.set_signal_traps( *QUEUE_SIGS )
|
53
|
+
|
54
|
+
self.log.debug "Starting multirunner loop..."
|
55
|
+
self.spawn_children
|
56
|
+
self.wait_for_signals while self.running
|
57
|
+
self.log.debug "Ending multirunner."
|
58
|
+
|
59
|
+
# Restore the default signal handlers
|
60
|
+
self.reset_signal_traps( *QUEUE_SIGS )
|
61
|
+
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
#########
|
67
|
+
protected
|
68
|
+
#########
|
69
|
+
|
70
|
+
### Start the handlers using fork().
|
71
|
+
def spawn_children
|
72
|
+
self.number.times do
|
73
|
+
Strelka.call_before_fork_hooks
|
74
|
+
pid = Process.fork do
|
75
|
+
Process.setpgrp
|
76
|
+
self.app_class.run
|
77
|
+
end
|
78
|
+
self.handler_pids << pid
|
79
|
+
Process.setpgid( pid, 0 )
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
### Wait on the child associated with the given +pid+, deleting it from the
|
85
|
+
### running tasks Hash if successful.
|
86
|
+
def reap_children( signal )
|
87
|
+
self.handler_pids.dup.each do |pid|
|
88
|
+
self.log.debug " sending %p to pid %p" % [ signal, pid ]
|
89
|
+
Process.kill( signal, pid )
|
90
|
+
pid, status = Process.waitpid2( pid, Process::WUNTRACED )
|
91
|
+
self.log.debug " waitpid2 returned: [ %p, %p ]" % [ pid, status ]
|
92
|
+
self.handler_pids.delete( pid )
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
### Handle signals.
|
98
|
+
def handle_signal( sig )
|
99
|
+
self.log.debug "Handling signal %s in PID %d" % [ sig, Process.pid ]
|
100
|
+
case sig
|
101
|
+
when :INT, :TERM, :QUIT
|
102
|
+
if @running
|
103
|
+
self.log.warn "%s signal: graceful shutdown" % [ sig ]
|
104
|
+
self.reap_children( sig )
|
105
|
+
@running = false
|
106
|
+
else
|
107
|
+
self.ignore_signals
|
108
|
+
self.log.warn "%s signal: forceful shutdown" % [ sig ]
|
109
|
+
self.kill_children( :KILL )
|
110
|
+
exit!( 255 )
|
111
|
+
end
|
112
|
+
|
113
|
+
when :CHLD
|
114
|
+
self.log.info "Got SIGCHLD."
|
115
|
+
# Just need to wake up, nothing else necessary
|
116
|
+
|
117
|
+
else
|
118
|
+
self.log.warn "Unhandled signal %s" % [ sig ]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end # class Strelka::MultiRunner
|
123
|
+
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'strelka'
|
5
|
+
|
6
|
+
|
7
|
+
# A module containing signal-handling logic for the 'start' command
|
8
|
+
# line.
|
9
|
+
module Strelka::SignalHandling
|
10
|
+
|
11
|
+
### Wrap a block in signal-handling.
|
12
|
+
def with_signal_handler( *signals )
|
13
|
+
self.set_up_signal_handling
|
14
|
+
self.set_signal_traps( *signals )
|
15
|
+
self.start_signal_handler
|
16
|
+
|
17
|
+
return yield
|
18
|
+
|
19
|
+
ensure
|
20
|
+
self.stop_signal_handler
|
21
|
+
self.reset_signal_traps( *signals )
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
### Set up data structures for signal handling.
|
26
|
+
def set_up_signal_handling
|
27
|
+
# Self-pipe for deferred signal-handling (ala djb:
|
28
|
+
# http://cr.yp.to/docs/selfpipe.html)
|
29
|
+
reader, writer = IO.pipe
|
30
|
+
reader.close_on_exec = true
|
31
|
+
writer.close_on_exec = true
|
32
|
+
@selfpipe = { reader: reader, writer: writer }
|
33
|
+
|
34
|
+
# Set up a global signal queue
|
35
|
+
Thread.main[:signal_queue] = []
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
### The body of the signal handler. Wait for at least one signal to arrive and
|
40
|
+
### handle it, or timeout and return if a +timeout+ integer is provided. This
|
41
|
+
### should be called inside a loop, either in its own thread or in another loop
|
42
|
+
### that doesn't block anywhere else. Returns true if a signal was handled, or
|
43
|
+
### false if a timeout occurred.
|
44
|
+
def wait_for_signals( timeout=nil )
|
45
|
+
|
46
|
+
# Wait on the selfpipe for signals
|
47
|
+
# self.log.debug " waiting for the selfpipe"
|
48
|
+
fds = IO.select( [@selfpipe[:reader]], [], [], timeout )
|
49
|
+
begin
|
50
|
+
rval = @selfpipe[:reader].read_nonblock( 11 )
|
51
|
+
self.log.debug " read from the selfpipe: %p" % [ rval ]
|
52
|
+
rescue Errno::EAGAIN, Errno::EINTR => err
|
53
|
+
# ignore
|
54
|
+
end
|
55
|
+
|
56
|
+
# Look for any signals that arrived and handle them
|
57
|
+
while sig = Thread.main[:signal_queue].shift
|
58
|
+
self.log.debug " got a queued signal: %p" % [ sig ]
|
59
|
+
self.handle_signal( sig )
|
60
|
+
end
|
61
|
+
|
62
|
+
return fds ? true : false
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
### Wake the main thread up through the self-pipe.
|
67
|
+
### Note: since this is a signal-handler method, it needs to be re-entrant.
|
68
|
+
def wake_up
|
69
|
+
@selfpipe[:writer].write_nonblock('.')
|
70
|
+
rescue Errno::EAGAIN
|
71
|
+
# Ignore.
|
72
|
+
rescue Errno::EINTR
|
73
|
+
# Repeated signal. :TODO: Does this need a counter?
|
74
|
+
retry
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
### Set up signal handlers for common signals that will shut down, restart, etc.
|
79
|
+
def set_signal_traps( *signals )
|
80
|
+
self.log.debug "Setting up deferred signal handlers."
|
81
|
+
signals.each do |sig|
|
82
|
+
Signal.trap( sig ) do
|
83
|
+
Thread.main[:signal_queue] << sig
|
84
|
+
self.wake_up
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
### Set all signal handlers to ignore.
|
91
|
+
def ignore_signals( *signals )
|
92
|
+
self.log.debug "Ignoring signals."
|
93
|
+
signals.each do |sig|
|
94
|
+
next if sig == :CHLD
|
95
|
+
Signal.trap( sig, :IGNORE )
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
### Set the signal handlers back to their defaults.
|
101
|
+
def reset_signal_traps( *signals )
|
102
|
+
self.log.debug "Restoring default signal handlers."
|
103
|
+
signals.each do |sig|
|
104
|
+
Signal.trap( sig, :DEFAULT )
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
### Simulate the receipt of the specified +signal+ (probably only useful
|
110
|
+
### in testing).
|
111
|
+
def simulate_signal( signal )
|
112
|
+
Thread.main[:signal_queue] << signal.to_sym
|
113
|
+
self.wake_up
|
114
|
+
end
|
115
|
+
|
116
|
+
end # module Strelka::SignalHandling
|
117
|
+
|
data/spec/strelka_spec.rb
CHANGED
@@ -189,5 +189,19 @@ RSpec.describe Strelka do
|
|
189
189
|
end
|
190
190
|
|
191
191
|
|
192
|
+
it "provides a way to register blocks that should run before a fork" do
|
193
|
+
callback_ran = false
|
194
|
+
Strelka.before_fork { callback_ran = true }
|
195
|
+
Strelka.call_before_fork_hooks
|
196
|
+
|
197
|
+
expect( callback_ran ).to be( true )
|
198
|
+
end
|
199
|
+
|
200
|
+
it "raises an exception if .before_fork is called without a block" do
|
201
|
+
expect {
|
202
|
+
Strelka.before_fork
|
203
|
+
}.to raise_error( LocalJumpError, /no block given/i )
|
204
|
+
end
|
205
|
+
|
192
206
|
end
|
193
207
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strelka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mahlon E. Smith
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
g3nSb5geweeDxf7Phf3qyZgglWB4UGR0aUkzOwj6yFj1ugCU2R7CwNhqgmtdkvYm
|
36
36
|
tuLuv1oCfpuEmRh93FiLFsOLV3auiU+c
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2019-09-
|
38
|
+
date: 2019-09-25 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: configurability
|
@@ -408,6 +408,7 @@ files:
|
|
408
408
|
- lib/strelka/httpresponse/session.rb
|
409
409
|
- lib/strelka/mixins.rb
|
410
410
|
- lib/strelka/multipartparser.rb
|
411
|
+
- lib/strelka/multirunner.rb
|
411
412
|
- lib/strelka/paramvalidator.rb
|
412
413
|
- lib/strelka/plugins.rb
|
413
414
|
- lib/strelka/router.rb
|
@@ -416,6 +417,7 @@ files:
|
|
416
417
|
- lib/strelka/session.rb
|
417
418
|
- lib/strelka/session/db.rb
|
418
419
|
- lib/strelka/session/default.rb
|
420
|
+
- lib/strelka/signal_handling.rb
|
419
421
|
- lib/strelka/testing.rb
|
420
422
|
- lib/strelka/websocketserver.rb
|
421
423
|
- lib/strelka/websocketserver/heartbeat.rb
|
metadata.gz.sig
CHANGED
Binary file
|