strelka 0.17.0 → 0.18.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
  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