uninterruptible 1.1.1 → 2.0.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: 946193e9e88ddc165ab4f49e026e9666c6ccb517
4
- data.tar.gz: e8caf941d6da770c6b0f5ffcda039ac111de879e
3
+ metadata.gz: 7f8d400309d6f9bf1354f04b9002aad29d077d2e
4
+ data.tar.gz: 3cb256b49089b46fdf7820594a6919c71e9d4d9f
5
5
  SHA512:
6
- metadata.gz: 3515b11f919831fb45515313d9a46435ce810e84d97f9430dbee0206a32bb326b666f3c62421dc83a2560fc5feee4824c9b1598830a63875541b7559004cff8d
7
- data.tar.gz: 8a90dba066c52a6840e7efb0a20106e8656b1526f84dc0273830742b733f35500d20eb04cc9be4d33a45a50b9555ca1b84001e83ad15c7857c101a5004a3b6e7
6
+ metadata.gz: 4649cdbf7b3eff9fff3709f343513cf36a21814e03c1092c0d09d4a6ff4905bfdfb32ae61866a41caf513867623689429c44dfe5a8df57e824c2173699c52c63
7
+ data.tar.gz: d05d7c375ab9bd546691516abbbc7205a4221cfa60ffa8ddd3ea072fa9028fb12e51dc1951f83053605e7dd37ef9793dbf9c06e29631d7d00425128495ff82bb
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ # 2.0.0
4
+ * Use an internal pipe for delivering signals to the main thread.
5
+ * `accept_connections` retired in favour of a select loop and `accept_client_connection` being called for each waiting connection
6
+ * Logging when shutting down or restarting
data/README.md CHANGED
@@ -90,18 +90,20 @@ completed.
90
90
  By default, Uninterruptible operates on a very simple one thread per connection concurrency model. If you'd like to use
91
91
  something more advanced such as a threadpool or an event driven pattern you can define this in your server class.
92
92
 
93
- By overriding `accept_connections` you can change how connections are accepted and handled. It is recommended that you
94
- call `process_request` from this method and still implement `handle_request` to do the bulk of the work since
93
+ By overriding `accept_client_connection` you can change how connections are accepted and handled. It is recommended
94
+ that you call `process_request` from this method and still implement `handle_request` to do the bulk of the work since
95
95
  `process_request` tracks the number of active connections to the server.
96
96
 
97
+ `accept_client_connection` is called whenever a connection is waiting to be accepted on the socket server.
98
+
97
99
  If you wanted to implement a threadpool to process your requests you could do the following:
98
100
 
99
101
  ```ruby
100
102
  class EchoServer
101
103
  # ...
102
104
 
103
- def accept_connections
104
- threads = 4.times.map do
105
+ def accept_client_connection
106
+ @worker_threads ||= 4.times.map do
105
107
  Thread.new { worker_loop }
106
108
  end
107
109
 
@@ -30,7 +30,7 @@ module Uninterruptible
30
30
  module Server
31
31
  def self.included(base)
32
32
  base.class_eval do
33
- attr_reader :active_connections, :socket_server, :mutex
33
+ attr_reader :active_connections, :socket_server, :signal_pipe_r, :signal_pipe_w, :mutex
34
34
  end
35
35
  end
36
36
 
@@ -55,7 +55,7 @@ module Uninterruptible
55
55
  establish_socket_server
56
56
  write_pidfile
57
57
  setup_signal_traps
58
- accept_connections
58
+ select_loop
59
59
  end
60
60
 
61
61
  # @abstract Override this method to process incoming requests. Each request is handled in it's own thread.
@@ -68,17 +68,32 @@ module Uninterruptible
68
68
 
69
69
  private
70
70
 
71
- # Start a blocking loop which accepts new connections and hands them off to #process_request. Override this to
72
- # use a different concurrency pattern, a thread per connection is the default.
73
- def accept_connections
71
+ # Start a blocking loop which awaits new connections before calling #accept_client_connection. Also monitors
72
+ # signal_pipe_r for processing any signals sent to the process.
73
+ def select_loop
74
74
  loop do
75
- Thread.start(socket_server.accept) do |client_socket|
76
- logger.debug "Accepted connection from #{client_socket.peeraddr.last}"
77
- process_request(client_socket)
75
+ readable, = IO.select([socket_server, signal_pipe_r])
76
+ readable.each do |reader|
77
+ if reader == socket_server
78
+ accept_client_connection
79
+ elsif reader == signal_pipe_r
80
+ signal = reader.gets.chomp
81
+ process_signal(signal)
82
+ end
78
83
  end
79
84
  end
80
85
  end
81
86
 
87
+ # Accept a waiting connection. Should only be called when it is known a connection is waiting, from an IO.select
88
+ # loop for example. By default this creates one thread per connection. Override this method to provide a new
89
+ # concurrency model.
90
+ def accept_client_connection
91
+ Thread.start(socket_server.accept_nonblock) do |client_socket|
92
+ logger.debug "Accepted connection from #{client_socket.peeraddr.last}"
93
+ process_request(client_socket)
94
+ end
95
+ end
96
+
82
97
  # Keeps a track of the number of active connections and passes the client connection to #handle_request for the
83
98
  # user to do with as they wish. Automatically closes a connection once #handle_request has completed.
84
99
  #
@@ -121,18 +136,35 @@ module Uninterruptible
121
136
  File.write(server_configuration.pidfile_path, Process.pid.to_s)
122
137
  end
123
138
 
124
- # Catch TERM and USR1 signals which control the lifecycle of the server.
139
+ # Catch TERM and USR1 signals which control the lifecycle of the server. These get written to an internal pipe
140
+ # which will be picked up by the main accept_connection loop and passed to #process_signal
125
141
  def setup_signal_traps
126
- # On TERM begin a graceful shutdown, if a second TERM is received shutdown immediately with an exit code of 1
127
- Signal.trap('TERM') do
128
- Process.exit(1) if $shutdown
142
+ @signal_pipe_r, @signal_pipe_w = IO.pipe
129
143
 
130
- $shutdown = true
131
- graceful_shutdown
144
+ %w(TERM USR1).each do |signal_name|
145
+ trap(signal_name) do
146
+ @signal_pipe_w.puts(signal_name)
147
+ end
132
148
  end
149
+ end
133
150
 
134
- # On USR1 begin a hot restart
135
- Signal.trap('USR1') do
151
+ # When a signal has been caught, it should be passed here for the appropriate action to be taken
152
+ # On TERM begin a graceful shutdown, if a second TERM is received shutdown immediately with an exit code of 1
153
+ # On USR1 begin a hot restart which will bring up a new copy of the server and then shut down the old one
154
+ #
155
+ # @param [String] signal_name Signal to process
156
+ def process_signal(signal_name)
157
+ if signal_name == 'TERM'
158
+ if $shutdown
159
+ logger.info "TERM received again, exiting immediately"
160
+ Process.exit(1) if $shutdown
161
+ else
162
+ logger.info "TERM received, starting graceful shutdown"
163
+ $shutdown = true
164
+ graceful_shutdown
165
+ end
166
+ elsif signal_name == 'USR1'
167
+ logger.info "USR1 received, hot restart in progress"
136
168
  hot_restart
137
169
  end
138
170
  end
@@ -142,9 +174,12 @@ module Uninterruptible
142
174
  socket_server.close unless socket_server.closed?
143
175
 
144
176
  until active_connections.zero?
177
+ logger.debug "#{active_connections} connections still active"
145
178
  sleep 0.5
146
179
  end
147
180
 
181
+ logger.debug "No more active connections. Exiting'"
182
+
148
183
  Process.exit(0)
149
184
  end
150
185
 
@@ -1,3 +1,3 @@
1
1
  module Uninterruptible
2
- VERSION = "1.1.1"
2
+ VERSION = "2.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uninterruptible
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Wentworth
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-16 00:00:00.000000000 Z
11
+ date: 2017-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -65,6 +65,7 @@ files:
65
65
  - ".gitignore"
66
66
  - ".rspec"
67
67
  - ".travis.yml"
68
+ - CHANGELOG.md
68
69
  - Gemfile
69
70
  - LICENSE.txt
70
71
  - README.md