uninterruptible 1.0.0 → 1.1.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
- data/README.md +12 -4
- data/lib/uninterruptible.rb +3 -0
- data/lib/uninterruptible/binder.rb +69 -0
- data/lib/uninterruptible/configuration.rb +8 -3
- data/lib/uninterruptible/server.rb +14 -20
- data/lib/uninterruptible/version.rb +1 -1
- data/uninterruptible-1.0.0.gem +0 -0
- data/uninterruptible.gemspec +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1947e00fb25907205555f9a1cbef1d5875008513
|
4
|
+
data.tar.gz: a6865bb7d65675c31aad797c4cd88634285419ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33f6c792fa320d873e3d47d90f2500f7ec2fc589192ae18081df04a88bcda5518a198bffb0933005bb4af0ffe2636ee1c3542619ebb9604aa1fc2be2a913ae37
|
7
|
+
data.tar.gz: dd6b8c1f399b544a4977043176734aa50ea3857a1aa55bc1f8aeb0e9b3201e712785a510c8c23a0eb8613b89696aa0d1495ede000c19080e8e44eed07d416bd0
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Uninterruptible
|
2
2
|
|
3
|
-
Uninterruptible gives you zero downtime restarts for your
|
3
|
+
Uninterruptible gives you zero downtime restarts for your socket servers with nearly zero effort. Sounds good? Read on.
|
4
4
|
|
5
5
|
Small socket servers are great, sometimes you need a quick and efficient way of moving data between servers (or even
|
6
6
|
processes on the same machine). Restarting these processes can be a bit hairy though, you either need to build your
|
@@ -60,14 +60,22 @@ finished processing all of it's existing connections. To kill the server (allowi
|
|
60
60
|
```ruby
|
61
61
|
echo_server.configure do |config|
|
62
62
|
config.start_command = 'ruby echo_server.rb' # *Required* Command to execute to start a new server process
|
63
|
-
config.
|
64
|
-
config.bind_address = '::' # Address to listen on
|
63
|
+
config.bind = "tcp://0.0.0.0:12345" # *Required* Interface to listen on, falls back to 0.0.0.0 on ENV['PORT']
|
65
64
|
config.pidfile_path = 'tmp/pids/echoserver.pid' # Location to write a pidfile, falls back to ENV['PID_FILE']
|
66
65
|
config.log_path = 'log/echoserver.log' # Location to write logfile, defaults to STDOUT
|
67
66
|
config.log_level = Logger::INFO # Log writing severity, defaults to Logger::INFO
|
68
67
|
end
|
69
68
|
```
|
70
69
|
|
70
|
+
Uninterruptible supports both TCP and UNIX sockets. To connect to a unix socket simply pass the path in the bind
|
71
|
+
configuration parameter:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
echo_server.configure do |config|
|
75
|
+
config.bind = "unix:///tmp/echo_server.sock"
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
71
79
|
## The Magic
|
72
80
|
|
73
81
|
Upon receiving `USR1`, your server will spawn a new copy of itself and pass the file descriptor of the open socket to
|
@@ -102,7 +110,7 @@ class EchoServer
|
|
102
110
|
|
103
111
|
def worker_loop
|
104
112
|
loop do
|
105
|
-
client_socket =
|
113
|
+
client_socket = socket_server.accept
|
106
114
|
process_request(client_socket)
|
107
115
|
end
|
108
116
|
end
|
data/lib/uninterruptible.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require "uninterruptible/version"
|
2
2
|
require 'uninterruptible/configuration'
|
3
|
+
require 'uninterruptible/binder'
|
3
4
|
require 'uninterruptible/server'
|
4
5
|
|
5
6
|
# All of the interesting stuff is in Uninterruptible::Server
|
6
7
|
module Uninterruptible
|
7
8
|
class ConfigurationError < StandardError; end
|
9
|
+
|
10
|
+
SERVER_FD_VAR = 'UNINTERRUPTIBLE_SERVER_FD'.freeze
|
8
11
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Uninterruptible
|
4
|
+
class Binder
|
5
|
+
attr_reader :bind_uri
|
6
|
+
|
7
|
+
# @param [String] bind_address The config for a server we're returning the socket for
|
8
|
+
# @example
|
9
|
+
# "unix:///tmp/server.sock"
|
10
|
+
# "tcp://127.0.0.1:8080"
|
11
|
+
def initialize(bind_address)
|
12
|
+
@bind_uri = parse_bind_address(bind_address)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Bind to the TCP or UNIX socket defined in the #bind_uri
|
16
|
+
#
|
17
|
+
# @return [TCPServer, UNIXServer] Successfully bound server
|
18
|
+
#
|
19
|
+
# @raise [Uninterruptible::ConfigurationError] Raised when the URI indicates a non-tcp or unix scheme
|
20
|
+
def bind_to_socket
|
21
|
+
case bind_uri.scheme
|
22
|
+
when 'tcp'
|
23
|
+
bind_to_tcp_socket
|
24
|
+
when 'unix'
|
25
|
+
bind_to_unix_socket
|
26
|
+
else
|
27
|
+
raise Uninterruptible::ConfigurationError, "Can only bind to TCP and UNIX sockets"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Connect (or reconnect if the FD is set) to a TCP server
|
34
|
+
#
|
35
|
+
# @return [TCPServer] Socket server for the configured address and port
|
36
|
+
def bind_to_tcp_socket
|
37
|
+
if ENV[SERVER_FD_VAR]
|
38
|
+
TCPServer.for_fd(ENV[SERVER_FD_VAR].to_i)
|
39
|
+
else
|
40
|
+
TCPServer.new(bind_uri.host, bind_uri.port)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Connect (or reconnect if FD is set) to a UNIX socket. Will delete existing socket at path if required.
|
45
|
+
#
|
46
|
+
# @return [UNIXServer] Socket server for the configured path
|
47
|
+
def bind_to_unix_socket
|
48
|
+
if ENV[SERVER_FD_VAR]
|
49
|
+
UNIXServer.for_fd(ENV[SERVER_FD_VAR].to_i)
|
50
|
+
else
|
51
|
+
File.delete(bind_uri.path) if File.exist?(bind_uri.path)
|
52
|
+
UNIXServer.new(bind_uri.path)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Parse the bind address in the configuration
|
57
|
+
#
|
58
|
+
# @param [String] bind_address The config for a server we're returning the socket for
|
59
|
+
#
|
60
|
+
# @return [URI::Generic] Parsed version of the bind_address
|
61
|
+
#
|
62
|
+
# @raise [Uninterruptible::ConfigurationError] Raised if the bind_address could not be parsed
|
63
|
+
def parse_bind_address(bind_address)
|
64
|
+
URI.parse(bind_address)
|
65
|
+
rescue URI::Error
|
66
|
+
raise Uninterruptible::ConfigurationError, "Couldn't parse the bind address: \"#{bind_address}\""
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -3,7 +3,7 @@ module Uninterruptible
|
|
3
3
|
#
|
4
4
|
# See {Server#configure} for usage instructions.
|
5
5
|
class Configuration
|
6
|
-
attr_writer :bind_port, :bind_address, :pidfile_path, :start_command, :log_path, :log_level
|
6
|
+
attr_writer :bind, :bind_port, :bind_address, :pidfile_path, :start_command, :log_path, :log_level
|
7
7
|
|
8
8
|
# Available TCP Port for the server to bind to (required). Falls back to environment variable PORT if set.
|
9
9
|
#
|
@@ -14,9 +14,14 @@ module Uninterruptible
|
|
14
14
|
port.to_i
|
15
15
|
end
|
16
16
|
|
17
|
-
# Address to bind the server to (defaults to
|
17
|
+
# Address to bind the server to (defaults to +0.0.0.0+).
|
18
18
|
def bind_address
|
19
|
-
@bind_address || "
|
19
|
+
@bind_address || "0.0.0.0"
|
20
|
+
end
|
21
|
+
|
22
|
+
# URI to bind to, falls back to tcp://bind_address:bind_port if unset
|
23
|
+
def bind
|
24
|
+
@bind || "tcp://#{bind_address}:#{bind_port}"
|
20
25
|
end
|
21
26
|
|
22
27
|
# Location to write the pid of the current server to. If blank pidfile will not be written. Falls back to
|
@@ -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, :
|
33
|
+
attr_reader :active_connections, :socket_server, :mutex
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -52,7 +52,7 @@ module Uninterruptible
|
|
52
52
|
|
53
53
|
logger.info "Starting server on #{server_configuration.bind_address}:#{server_configuration.bind_port}"
|
54
54
|
|
55
|
-
|
55
|
+
establish_socket_server
|
56
56
|
write_pidfile
|
57
57
|
setup_signal_traps
|
58
58
|
accept_connections
|
@@ -72,7 +72,7 @@ module Uninterruptible
|
|
72
72
|
# use a different concurrency pattern, a thread per connection is the default.
|
73
73
|
def accept_connections
|
74
74
|
loop do
|
75
|
-
Thread.start(
|
75
|
+
Thread.start(socket_server.accept) do |client_socket|
|
76
76
|
logger.debug "Accepted connection from #{client_socket.peeraddr.last}"
|
77
77
|
process_request(client_socket)
|
78
78
|
end
|
@@ -93,23 +93,17 @@ module Uninterruptible
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
-
# Listen (or reconnect) to the bind address and port specified in the config. If
|
97
|
-
# reconnect to that file descriptor. Once @
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
@tcp_server = TCPServer.for_fd(ENV['TCP_SERVER_FD'].to_i)
|
103
|
-
kill_parent
|
104
|
-
else
|
105
|
-
logger.debug "Opening new socket..."
|
106
|
-
@tcp_server = TCPServer.open(server_configuration.bind_address, server_configuration.bind_port)
|
107
|
-
end
|
96
|
+
# Listen (or reconnect) to the bind address and port specified in the config. If socket_server_FD is set in the env,
|
97
|
+
# reconnect to that file descriptor. Once @socket_server is set, write the file descriptor ID to the env.
|
98
|
+
def establish_socket_server
|
99
|
+
@socket_server = Uninterruptible::Binder.new(server_configuration.bind).bind_to_socket
|
100
|
+
# If there's a file descriptor present, take over from a previous instance of this server and kill it off
|
101
|
+
kill_parent if ENV[SERVER_FD_VAR]
|
108
102
|
|
109
|
-
@
|
110
|
-
@
|
103
|
+
@socket_server.autoclose = false
|
104
|
+
@socket_server.close_on_exec = false
|
111
105
|
|
112
|
-
ENV[
|
106
|
+
ENV[SERVER_FD_VAR] = @socket_server.to_i.to_s
|
113
107
|
end
|
114
108
|
|
115
109
|
# Send a TERM signal to the parent process. This will be called by a newly spawned server if it has been started
|
@@ -143,9 +137,9 @@ module Uninterruptible
|
|
143
137
|
end
|
144
138
|
end
|
145
139
|
|
146
|
-
# Stop listening on
|
140
|
+
# Stop listening on socket_server, wait until all active connections have finished processing and exit with 0.
|
147
141
|
def graceful_shutdown
|
148
|
-
|
142
|
+
socket_server.close unless socket_server.closed?
|
149
143
|
|
150
144
|
until active_connections.zero?
|
151
145
|
sleep 0.5
|
Binary file
|
data/uninterruptible.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Dan Wentworth"]
|
10
10
|
spec.email = ["dan@atechmedia.com"]
|
11
11
|
|
12
|
-
spec.summary = "Zero-downtime restarts for your trivial
|
12
|
+
spec.summary = "Zero-downtime restarts for your trivial socket servers"
|
13
13
|
spec.description = "Uninterruptible gives your socket server magic restarting powers. Send your running "\
|
14
14
|
"Uninterruptible server USR1 and it will start a brand new copy of itself which will immediately start handling "\
|
15
15
|
"new requests while the old server stays alive until all of it's active connections are complete."
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uninterruptible
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Wentworth
|
@@ -71,9 +71,11 @@ files:
|
|
71
71
|
- bin/console
|
72
72
|
- bin/setup
|
73
73
|
- lib/uninterruptible.rb
|
74
|
+
- lib/uninterruptible/binder.rb
|
74
75
|
- lib/uninterruptible/configuration.rb
|
75
76
|
- lib/uninterruptible/server.rb
|
76
77
|
- lib/uninterruptible/version.rb
|
78
|
+
- uninterruptible-1.0.0.gem
|
77
79
|
- uninterruptible.gemspec
|
78
80
|
homepage: https://github.com/darkphnx/uninterruptible
|
79
81
|
licenses:
|
@@ -98,5 +100,5 @@ rubyforge_project:
|
|
98
100
|
rubygems_version: 2.5.1
|
99
101
|
signing_key:
|
100
102
|
specification_version: 4
|
101
|
-
summary: Zero-downtime restarts for your trivial
|
103
|
+
summary: Zero-downtime restarts for your trivial socket servers
|
102
104
|
test_files: []
|