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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a69b39669c90e69f7ea690787d47981776c6a29044febf339d8890c240d6d1aa
4
- data.tar.gz: 0bc16e15b5a1196a3944b99156eae5039eb8d9f7a5e71636974c41b7853f5fd2
3
+ metadata.gz: 7dad453a5871872ca4f793e78253510d82e4ed27fbb173c82629501c60962477
4
+ data.tar.gz: 8bf1feafca4c80397808322ed09c93b9c6ed56cd917c5db12ab0f1e33cf8a59f
5
5
  SHA512:
6
- metadata.gz: bb4c71d572c432b6a13a6b66ab4a1060c45f0d326bff9a4841579a30d2dd55b2782662252a28d22195bf228651aa5a8ea225295a846000f874a6c2cb38ab6c9e
7
- data.tar.gz: 9ae69ea8b0aadd5ae2567084641f7114cbf99089e7394b5e7dcd8438145f8a92570820d2b2b5bb457bb3f859b3334b82f2923eb205dba9d35f19d39c168432bc
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.17.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.times { fork { app.run } }
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.17.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-18 00:00:00.000000000 Z
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