uninterruptible 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|