From 1863d3a5cfe16aebc1e5ee1ed1db60e410388eef Mon Sep 17 00:00:00 2001 From: Neil Williams Date: Sun, 3 Apr 2016 21:58:06 -0700 Subject: [PATCH 1/7] attempt to brush off this old commit for UDS --- bin/einhorn | 37 +++++++++++++--- lib/einhorn.rb | 51 ++++++++++++++++------ lib/einhorn/bind.rb | 97 ++++++++++++++++++++++++++++++++++++++++++ lib/einhorn/command.rb | 1 + 4 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 lib/einhorn/bind.rb diff --git a/bin/einhorn b/bin/einhorn index 2db7e7a..9a02fe0 100755 --- a/bin/einhorn +++ b/bin/einhorn @@ -53,7 +53,11 @@ arguments: Each address is specified as an ip/port pair, possibly accompanied by options: - ADDR := (IP:PORT)[<,OPT>...] + IP_ADDR := (IP:PORT)[<,OPT>...] + +or as a path to a UNIX domain socket, also possibly accompanied by options: + + UNIX_ADDR := /path/to/socket[<,OPT>...] In the worker process, the opened file descriptors will be represented as file descriptor numbers in a series of environment variables named @@ -181,6 +185,20 @@ EOF end end + +BIND_PATTERN = /\A + # we accept two types of socket address + (?: + # ip, host:port + (?:(?[^:]+):(?\d+)) | + # unix socket path + (?[^,:]+) + ) + + # flags are optional, comma delimited, and come at the end + (?(?:,\w+)*) +\Z/x + # Would be nice if this could be loadable rather than always # executing, but when run under gem it's a bit hard to do so. if true # $0 == __FILE__ @@ -190,14 +208,19 @@ if true # $0 == __FILE__ optparse = OptionParser.new do |opts| opts.on('-b ADDR', '--bind ADDR', 'Bind an address and add the corresponding FD via the environment') do |addr| - unless addr =~ /\A([^:]+):(\d+)((?:,\w+)*)\Z/ - raise "Invalid value for #{addr.inspect}: bind address must be of the form address:port[,flags...]" + unless addr =~ BIND_PATTERN + raise "Invalid value for #{addr.inspect}: bind address must be of the form address:port[,flags...] or /path/to/unix/socket[,flags...]" + end + + flags = $~["flags"].split(",").reject(&:empty?).map(&:downcase) + + bind = if $~["path"] + Einhorn::Bind::UnixBind.new($~["path"], flags) + else + Einhorn::Bind::InetBind.new($~["host"], Integer($~["port"]), flags) end - host = $1 - port = Integer($2) - flags = $3.split(',').select {|flag| flag.length > 0}.map {|flag| flag.downcase} - Einhorn::State.bind << [host, port, flags] + Einhorn::State.bind << bind end opts.on('-c CMD_NAME', '--command-name CMD_NAME', 'Set the command name in ps to this value') do |cmd_name| diff --git a/lib/einhorn.rb b/lib/einhorn.rb index 44479d6..5e9ca70 100644 --- a/lib/einhorn.rb +++ b/lib/einhorn.rb @@ -139,6 +139,17 @@ def self.update_state(store, store_name, old_state) Einhorn::Event::Timer.open(0) do dead.each { |pid| Einhorn::Command.cleanup(pid) } end + + if updated_state[:bind] + updated_state[:bind].map! do |binding| + # bindings used to just be arrays of [host,port,flags] + if binding.is_a? Array + Bind::InetBind.new(*binding) + else + binding + end + end + end end default = store.default_state @@ -161,19 +172,16 @@ def self.print_state log_info(Einhorn::State.state.pretty_inspect) end - def self.bind(addr, port, flags) - log_info("Binding to #{addr}:#{port} with flags #{flags.inspect}") - sd = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) - Einhorn::Compat.cloexec!(sd, false) + def self.bind(binding) + log_info("Binding to #{binding.address} with flags #{binding.flags.inspect}") - if flags.include?("r") || flags.include?("so_reuseaddr") - sd.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1) - end + sd = binding.make_socket() + Einhorn::Compat.cloexec!(sd, false) - sd.bind(Socket.pack_sockaddr_in(port, addr)) + binding.bind(sd) sd.listen(Einhorn::State.config[:backlog]) - if flags.include?("n") || flags.include?("o_nonblock") + if binding.flags.include?('n') || binding.flags.include?('o_nonblock') fl = sd.fcntl(Fcntl::F_GETFL) sd.fcntl(Fcntl::F_SETFL, fl | Fcntl::O_NONBLOCK) end @@ -342,10 +350,28 @@ def self.renice_self end def self.socketify_env! - Einhorn::State.bind.each do |host, port, flags| - fd, actual_port = bind(host, port, flags) + Einhorn::State.bind.each do |binding| + fd = bind(binding) Einhorn::State.bind_fds << fd - Einhorn::State.bound_ports << actual_port + end + end + + # This duplicates some code from the environment path, but is + # deprecated so that's ok. + def self.socketify!(cmd) + cmd.map! do |arg| + if arg =~ /^(.*=|)srv:([^:]+):(\d+)((?:,\w+)*)$/ + log_error("Using deprecated command-line configuration for Einhorn; should upgrade to the environment variable interface.") + opt = $1 + host = $2 + port = $3 + flags = $4.split(',').select {|flag| flag.length > 0}.map {|flag| flag.downcase} + binding = Bind::InetBind.new(host, port, flags) + fd = (Einhorn::State.sockets[[host, port]] ||= bind(binding)) + "#{opt}#{fd}" + else + arg + end end end @@ -454,6 +480,7 @@ def self.run end end +require "einhorn/bind" require "einhorn/command" require "einhorn/compat" require "einhorn/client" diff --git a/lib/einhorn/bind.rb b/lib/einhorn/bind.rb new file mode 100644 index 0000000..32a68fb --- /dev/null +++ b/lib/einhorn/bind.rb @@ -0,0 +1,97 @@ +require 'socket' + +module Einhorn::Bind + class Bind + attr_reader :flags + + def ==(o) + o.class == self.class && o.state == state + end + end + + class InetBind < Bind + def initialize(host, port, flags) + @host = host + @port = port + @flags = flags + end + + def state + [@host, @port, @flags] + end + + def family + "AF_INET" + end + + def address + "#{@host}:#{@port}" + end + + def make_socket + sd = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + + if @flags.include?('r') || @flags.include?('so_reuseaddr') + sd.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1) + end + + sd + end + + def bind(sock) + sock.bind(Socket.pack_sockaddr_in(@port, @host)) + end + end + + class UnixBind < Bind + def initialize(path, flags) + @path = path + @flags = flags + end + + def state + [@path, @flags] + end + + def family + "AF_UNIX" + end + + def address + "#{@path}" + end + + def make_socket + Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) + end + + def clean_old_unix_socket + begin + sock = UNIXSocket.new(@path) + rescue Errno::ECONNREFUSED + # This happens with non-socket files and when the listening + # end of a socket has exited. + rescue Errno::ENOENT + # Socket doesn't exist + return + else + # Rats, it's still active + sock.close + raise Errno::EADDRINUSE.new("Another process is listening on the UNIX socket at #{@path}. If you'd like to run this Einhorn as well, pass a `-b PATH_TO_SOCKET` to change the socket location.") + end + + stat = File.stat(@path) + unless stat.socket? + raise Errno::EADDRINUSE.new("Non-socket file present at UNIX socket path #{@path}. Either remove that file and restart Einhorn, or pass a different `-b PATH_TO_SOCKET` to change where you are binding.") + end + + Einhorn.log_info("Blowing away old UNIX socket at #{@path}. This likely indicates a previous Einhorn master which exited uncleanly.") + File.unlink(@path) + end + + def bind(sock) + self.clean_old_unix_socket + sock.bind(Socket.pack_sockaddr_un(@path)) + end + end +end diff --git a/lib/einhorn/command.rb b/lib/einhorn/command.rb index b4326c8..81273a3 100644 --- a/lib/einhorn/command.rb +++ b/lib/einhorn/command.rb @@ -349,6 +349,7 @@ def self.prepare_child_environment(index) ENV["EINHORN_FD_COUNT"] = Einhorn::State.bind_fds.length.to_s Einhorn::State.bind_fds.each_with_index { |fd, i| ENV["EINHORN_FD_#{i}"] = fd.to_s } + Einhorn::State.bind.each_with_index {|bind, i| ENV["EINHORN_FD_FAMILY_#{i}"] = bind.family} ENV["EINHORN_CHILD_INDEX"] = index.to_s end From 9ea4930fc3ab2248e6c4e6963a129b0d02f4fe49 Mon Sep 17 00:00:00 2001 From: Chris Lundquist Date: Fri, 26 Aug 2022 14:07:41 -0700 Subject: [PATCH 2/7] update the readme with UDS help output --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 46af2e6..5d68188 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,11 @@ arguments: Each address is specified as an ip/port pair, possibly accompanied by options: - ADDR := (IP:PORT)[<,OPT>...] + IP_ADDR := (IP:PORT)[<,OPT>...] + +or as a path to a UNIX domain socket, also possibly accompanied by options: + + UNIX_ADDR := /path/to/socket[<,OPT>...] In the worker process, the opened file descriptors will be represented as file descriptor numbers in a series of environment variables named From f6894eb8a349294305aa68e13ca96064493c1554 Mon Sep 17 00:00:00 2001 From: Chris Lundquist Date: Fri, 26 Aug 2022 15:28:08 -0700 Subject: [PATCH 3/7] add a basic test, rename class, use the Socket::AF_ constant --- .gitignore | 1 + lib/einhorn.rb | 11 +++++++---- lib/einhorn/bind.rb | 8 ++++---- test/unit/einhorn.rb | 12 ++++++++++++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index d87d4be..d9ee225 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.gem *.rbc +*.swp .bundle .config .yardoc diff --git a/lib/einhorn.rb b/lib/einhorn.rb index 5e9ca70..5a840b9 100644 --- a/lib/einhorn.rb +++ b/lib/einhorn.rb @@ -144,7 +144,7 @@ def self.update_state(store, store_name, old_state) updated_state[:bind].map! do |binding| # bindings used to just be arrays of [host,port,flags] if binding.is_a? Array - Bind::InetBind.new(*binding) + Bind::Inet.new(*binding) else binding end @@ -187,7 +187,9 @@ def self.bind(binding) end Einhorn::TransientState.socket_handles << sd - [sd.fileno, sd.local_address.ip_port] + + sd_port = binding.family == Socket::AF_INET ? sd.local_address.ip_port : nil + [sd.fileno, sd_port] end # Implement these ourselves so it plays nicely with state persistence @@ -351,8 +353,9 @@ def self.renice_self def self.socketify_env! Einhorn::State.bind.each do |binding| - fd = bind(binding) + fd, actual_port = bind(binding) Einhorn::State.bind_fds << fd + Einhorn::State.bound_ports << actual_port if actual_port end end @@ -366,7 +369,7 @@ def self.socketify!(cmd) host = $2 port = $3 flags = $4.split(',').select {|flag| flag.length > 0}.map {|flag| flag.downcase} - binding = Bind::InetBind.new(host, port, flags) + binding = Bind::Inet.new(host, port, flags) fd = (Einhorn::State.sockets[[host, port]] ||= bind(binding)) "#{opt}#{fd}" else diff --git a/lib/einhorn/bind.rb b/lib/einhorn/bind.rb index 32a68fb..f0266fd 100644 --- a/lib/einhorn/bind.rb +++ b/lib/einhorn/bind.rb @@ -9,7 +9,7 @@ def ==(o) end end - class InetBind < Bind + class Inet < Bind def initialize(host, port, flags) @host = host @port = port @@ -21,7 +21,7 @@ def state end def family - "AF_INET" + Socket::AF_INET end def address @@ -43,7 +43,7 @@ def bind(sock) end end - class UnixBind < Bind + class Unix < Bind def initialize(path, flags) @path = path @flags = flags @@ -54,7 +54,7 @@ def state end def family - "AF_UNIX" + Socket::AF_UNIX end def address diff --git a/test/unit/einhorn.rb b/test/unit/einhorn.rb index edd74ec..d62b8a3 100644 --- a/test/unit/einhorn.rb +++ b/test/unit/einhorn.rb @@ -23,6 +23,18 @@ class EinhornTest < EinhornTestCase end end + describe ".bind" do + it 'should work with AF_INET bindings' do + uds = Einhorn::Bind::Unix.new("/tmp/einhorn-test.sock","n") + Einhorn.bind(uds) + end + + it 'should work with AF_UNIX bindings' do + inet = Einhorn::Bind::Inet.new("127.0.0.1", "1313","r") + Einhorn.bind(inet) + end + end + describe ".preload" do before do Einhorn::State.preloaded = false From 82fc619ec6aa39b36dab329769886a902719f800 Mon Sep 17 00:00:00 2001 From: Chris Lundquist Date: Fri, 26 Aug 2022 16:31:59 -0700 Subject: [PATCH 4/7] add pry for debugging, fix reference to old name --- Gemfile | 2 +- bin/einhorn | 4 ++-- einhorn.gemspec | 3 ++- lib/einhorn.rb | 20 +++++++++----------- lib/einhorn/bind.rb | 12 ++++++------ lib/einhorn/command.rb | 2 +- test/integration/upgrading.rb | 5 ++++- test/unit/einhorn.rb | 8 ++++---- 8 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Gemfile b/Gemfile index c2bed45..5a66f0d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source "https://rubygems.org/" # Only needed for examples -#gem "thin-attach_socket" +# gem "thin-attach_socket" # Specify your gem's dependencies in einhorn.gemspec gemspec diff --git a/bin/einhorn b/bin/einhorn index 9a02fe0..76ce599 100755 --- a/bin/einhorn +++ b/bin/einhorn @@ -215,9 +215,9 @@ if true # $0 == __FILE__ flags = $~["flags"].split(",").reject(&:empty?).map(&:downcase) bind = if $~["path"] - Einhorn::Bind::UnixBind.new($~["path"], flags) + Einhorn::Bind::Unix.new($~["path"], flags) else - Einhorn::Bind::InetBind.new($~["host"], Integer($~["port"]), flags) + Einhorn::Bind::Inet.new($~["host"], Integer($~["port"]), flags) end Einhorn::State.bind << bind diff --git a/einhorn.gemspec b/einhorn.gemspec index 6e3a32f..66c09eb 100644 --- a/einhorn.gemspec +++ b/einhorn.gemspec @@ -10,7 +10,6 @@ Gem::Specification.new do |gem| gem.files = ["einhorn.gemspec", "README.md", "Changes.md", "LICENSE.txt"] + `git ls-files bin lib example`.split("\n") gem.executables = %w[einhorn einhornsh] - gem.test_files = [] gem.name = "einhorn" gem.require_paths = ["lib"] gem.required_ruby_version = ">= 2.5.0" @@ -26,4 +25,6 @@ Gem::Specification.new do |gem| gem.add_development_dependency "minitest", "~> 5" gem.add_development_dependency "mocha", "~> 1" gem.add_development_dependency "subprocess", "~> 1" + gem.add_development_dependency "pry" + gem.add_development_dependency "pry-byebug" end diff --git a/lib/einhorn.rb b/lib/einhorn.rb index 5a840b9..4ef14d7 100644 --- a/lib/einhorn.rb +++ b/lib/einhorn.rb @@ -140,14 +140,12 @@ def self.update_state(store, store_name, old_state) dead.each { |pid| Einhorn::Command.cleanup(pid) } end - if updated_state[:bind] - updated_state[:bind].map! do |binding| - # bindings used to just be arrays of [host,port,flags] - if binding.is_a? Array - Bind::Inet.new(*binding) - else - binding - end + updated_state[:bind]&.map! do |binding| + # bindings used to just be arrays of [host,port,flags] + if binding.is_a? Array + Bind::Inet.new(*binding) + else + binding end end end @@ -175,13 +173,13 @@ def self.print_state def self.bind(binding) log_info("Binding to #{binding.address} with flags #{binding.flags.inspect}") - sd = binding.make_socket() + sd = binding.make_socket Einhorn::Compat.cloexec!(sd, false) binding.bind(sd) sd.listen(Einhorn::State.config[:backlog]) - if binding.flags.include?('n') || binding.flags.include?('o_nonblock') + if binding.flags.include?("n") || binding.flags.include?("o_nonblock") fl = sd.fcntl(Fcntl::F_GETFL) sd.fcntl(Fcntl::F_SETFL, fl | Fcntl::O_NONBLOCK) end @@ -368,7 +366,7 @@ def self.socketify!(cmd) opt = $1 host = $2 port = $3 - flags = $4.split(',').select {|flag| flag.length > 0}.map {|flag| flag.downcase} + flags = $4.split(",").select { |flag| flag.length > 0 }.map { |flag| flag.downcase } binding = Bind::Inet.new(host, port, flags) fd = (Einhorn::State.sockets[[host, port]] ||= bind(binding)) "#{opt}#{fd}" diff --git a/lib/einhorn/bind.rb b/lib/einhorn/bind.rb index f0266fd..a5ead06 100644 --- a/lib/einhorn/bind.rb +++ b/lib/einhorn/bind.rb @@ -1,11 +1,11 @@ -require 'socket' +require "socket" module Einhorn::Bind class Bind attr_reader :flags - def ==(o) - o.class == self.class && o.state == state + def ==(other) + other.class == self.class && other.state == state end end @@ -31,7 +31,7 @@ def address def make_socket sd = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) - if @flags.include?('r') || @flags.include?('so_reuseaddr') + if @flags.include?("r") || @flags.include?("so_reuseaddr") sd.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1) end @@ -58,7 +58,7 @@ def family end def address - "#{@path}" + @path.to_s end def make_socket @@ -90,7 +90,7 @@ def clean_old_unix_socket end def bind(sock) - self.clean_old_unix_socket + clean_old_unix_socket sock.bind(Socket.pack_sockaddr_un(@path)) end end diff --git a/lib/einhorn/command.rb b/lib/einhorn/command.rb index 81273a3..a483de2 100644 --- a/lib/einhorn/command.rb +++ b/lib/einhorn/command.rb @@ -349,7 +349,7 @@ def self.prepare_child_environment(index) ENV["EINHORN_FD_COUNT"] = Einhorn::State.bind_fds.length.to_s Einhorn::State.bind_fds.each_with_index { |fd, i| ENV["EINHORN_FD_#{i}"] = fd.to_s } - Einhorn::State.bind.each_with_index {|bind, i| ENV["EINHORN_FD_FAMILY_#{i}"] = bind.family} + Einhorn::State.bind.each_with_index { |bind, i| ENV["EINHORN_FD_FAMILY_#{i}"] = bind.family } ENV["EINHORN_CHILD_INDEX"] = index.to_s end diff --git a/test/integration/upgrading.rb b/test/integration/upgrading.rb index 88856dc..5588ed3 100644 --- a/test/integration/upgrading.rb +++ b/test/integration/upgrading.rb @@ -90,7 +90,10 @@ class UpgradeTests < EinhornIntegrationTestCase # exec the new einhorn with the same environment: reexec_cmdline = "env VAR=b OINK=b bundle exec --keep-file-descriptors einhorn" - with_running_einhorn(%W[einhorn -m manual -b 127.0.0.1:#{@port} --reexec-as=#{reexec_cmdline} -d #{@socket_path} -- ruby #{@server_program} VAR], + + cmd = %W[einhorn -m manual -b 127.0.0.1:#{@port} --reexec-as=#{reexec_cmdline} -d #{@socket_path} -- ruby #{@server_program} VAR] + + with_running_einhorn(cmd, env: ENV.to_hash.merge({"VAR" => "a"})) do |process| wait_for_open_port einhornsh(%W[-d #{@socket_path} -e upgrade]) diff --git a/test/unit/einhorn.rb b/test/unit/einhorn.rb index d62b8a3..f4e7d9c 100644 --- a/test/unit/einhorn.rb +++ b/test/unit/einhorn.rb @@ -24,13 +24,13 @@ class EinhornTest < EinhornTestCase end describe ".bind" do - it 'should work with AF_INET bindings' do - uds = Einhorn::Bind::Unix.new("/tmp/einhorn-test.sock","n") + it "should work with AF_INET bindings" do + uds = Einhorn::Bind::Unix.new("/tmp/einhorn-test.sock", "n") Einhorn.bind(uds) end - it 'should work with AF_UNIX bindings' do - inet = Einhorn::Bind::Inet.new("127.0.0.1", "1313","r") + it "should work with AF_UNIX bindings" do + inet = Einhorn::Bind::Inet.new("127.0.0.1", "1313", "r") Einhorn.bind(inet) end end From e7b49f0c84c23ba25d78dead2e34639075a113c0 Mon Sep 17 00:00:00 2001 From: Chris Lundquist Date: Fri, 26 Aug 2022 17:09:17 -0700 Subject: [PATCH 5/7] go back to strings :( --- lib/einhorn.rb | 2 +- lib/einhorn/bind.rb | 4 ++-- lib/einhorn/command.rb | 2 +- test/integration/upgrading.rb | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/einhorn.rb b/lib/einhorn.rb index 4ef14d7..9a13274 100644 --- a/lib/einhorn.rb +++ b/lib/einhorn.rb @@ -186,7 +186,7 @@ def self.bind(binding) Einhorn::TransientState.socket_handles << sd - sd_port = binding.family == Socket::AF_INET ? sd.local_address.ip_port : nil + sd_port = binding.family == "AF_INET" ? sd.local_address.ip_port : nil [sd.fileno, sd_port] end diff --git a/lib/einhorn/bind.rb b/lib/einhorn/bind.rb index a5ead06..c37451e 100644 --- a/lib/einhorn/bind.rb +++ b/lib/einhorn/bind.rb @@ -21,7 +21,7 @@ def state end def family - Socket::AF_INET + "AF_INET" end def address @@ -54,7 +54,7 @@ def state end def family - Socket::AF_UNIX + "AF_UNIX" end def address diff --git a/lib/einhorn/command.rb b/lib/einhorn/command.rb index a483de2..5474ce2 100644 --- a/lib/einhorn/command.rb +++ b/lib/einhorn/command.rb @@ -349,7 +349,7 @@ def self.prepare_child_environment(index) ENV["EINHORN_FD_COUNT"] = Einhorn::State.bind_fds.length.to_s Einhorn::State.bind_fds.each_with_index { |fd, i| ENV["EINHORN_FD_#{i}"] = fd.to_s } - Einhorn::State.bind.each_with_index { |bind, i| ENV["EINHORN_FD_FAMILY_#{i}"] = bind.family } + Einhorn::State.bind.each_with_index { |bind, i| ENV["EINHORN_FD_FAMILY_#{i}"] = bind.family.to_s } ENV["EINHORN_CHILD_INDEX"] = index.to_s end diff --git a/test/integration/upgrading.rb b/test/integration/upgrading.rb index 5588ed3..2ba3859 100644 --- a/test/integration/upgrading.rb +++ b/test/integration/upgrading.rb @@ -90,7 +90,6 @@ class UpgradeTests < EinhornIntegrationTestCase # exec the new einhorn with the same environment: reexec_cmdline = "env VAR=b OINK=b bundle exec --keep-file-descriptors einhorn" - cmd = %W[einhorn -m manual -b 127.0.0.1:#{@port} --reexec-as=#{reexec_cmdline} -d #{@socket_path} -- ruby #{@server_program} VAR] with_running_einhorn(cmd, From 9e8351e466005df72f5bc2196d48c90e8ed3b2c7 Mon Sep 17 00:00:00 2001 From: Chris Lundquist Date: Tue, 30 Aug 2022 16:08:50 -0700 Subject: [PATCH 6/7] well...here we are --- lib/einhorn/safe_yaml.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/einhorn/safe_yaml.rb b/lib/einhorn/safe_yaml.rb index 0e380e6..259e4da 100644 --- a/lib/einhorn/safe_yaml.rb +++ b/lib/einhorn/safe_yaml.rb @@ -6,11 +6,11 @@ module SafeYAML YAML.safe_load("---", permitted_classes: []) rescue ArgumentError def self.load(payload) - YAML.safe_load(payload, [Set, Symbol, Time], [], true) + YAML.safe_load(payload, [Set, Symbol, Time, Einhorn::Bind::Inet, Einhorn::Bind::Unix], [], true) end else def self.load(payload) # rubocop:disable Lint/DuplicateMethods - YAML.safe_load(payload, permitted_classes: [Set, Symbol, Time], aliases: true) + YAML.safe_load(payload, permitted_classes: [Set, Symbol, Time, Einhorn::Bind::Inet, Einhorn::Bind::Unix], aliases: true) end end end From 5c7b10eb90c0240639bfb5f1c365d0cc5dfb43ed Mon Sep 17 00:00:00 2001 From: Chris Lundquist Date: Tue, 30 Aug 2022 17:52:39 -0700 Subject: [PATCH 7/7] a test passes --- .../_lib/helpers/einhorn_helpers.rb | 29 +++++++++++++++++-- test/integration/upgrading.rb | 19 ++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/test/integration/_lib/helpers/einhorn_helpers.rb b/test/integration/_lib/helpers/einhorn_helpers.rb index bd728ab..cf14618 100644 --- a/test/integration/_lib/helpers/einhorn_helpers.rb +++ b/test/integration/_lib/helpers/einhorn_helpers.rb @@ -95,6 +95,7 @@ def prepare_fixture_directory(name) def cleanup_fixtured_directories (@fixtured_dirs || []).each { |dir| FileUtils.rm_rf(dir) } + FileUtils.rm_rf(@unix_listener_socket_path) end def find_free_port(host = "127.0.0.1") @@ -109,11 +110,12 @@ def get_state(client) Einhorn::SafeYAML.load(client.receive_message["message"])[:state] end - def wait_for_open_port + + def mash_retry(&block) max_retries = 50 begin - read_from_port - rescue Errno::ECONNREFUSED + yield + rescue Errno::ECONNREFUSED, Errno::ENOENT max_retries -= 1 if max_retries <= 0 raise @@ -124,6 +126,27 @@ def wait_for_open_port end end + def wait_for_open_socket + mash_retry do + read_from_socket + end + end + + def wait_for_open_port + mash_retry do + read_from_port + end + end + + def read_from_socket + begin + socket = UNIXSocket.new(@unix_listener_socket_path) + socket.read.chomp + ensure + socket&.close + end + end + def read_from_port ewouldblock = RUBY_VERSION >= "1.9.0" ? IO::WaitWritable : Errno::EINPROGRESS socket = Socket.new(Socket::PF_INET, Socket::SOCK_STREAM, 0) diff --git a/test/integration/upgrading.rb b/test/integration/upgrading.rb index 2ba3859..7476eff 100644 --- a/test/integration/upgrading.rb +++ b/test/integration/upgrading.rb @@ -35,9 +35,28 @@ class UpgradeTests < EinhornIntegrationTestCase @port = find_free_port @server_program = File.join(@dir, "env_printer.rb") @socket_path = File.join(@dir, "einhorn.sock") + + mangler = ('a'..'z').to_a.shuffle[0,8].join + @unix_listener_socket_path = "unix-listener-einhorn-#{mangler}.sock" end after { cleanup_fixtured_directories } + describe "when running as a unix domain socket listener" do + it "preserves environment variables across restarts" do + # exec the new einhorn with the same environment: + reexec_cmdline = "env VAR=a bundle exec --keep-file-descriptors einhorn" + + with_running_einhorn(%W[einhorn -m manual -b #{@unix_listener_socket_path} --reexec-as=#{reexec_cmdline} -d #{@socket_path} -- ruby #{@server_program} VAR], + env: ENV.to_hash.merge({"VAR" => "a"})) do |process| + wait_for_open_socket + einhornsh(%W[-d #{@socket_path} -e upgrade]) + assert_equal("a", read_from_socket, "Should report the upgraded version") + + process.terminate + end + end + end + describe "when running with --reexec-as" do it "preserves environment variables across restarts" do # exec the new einhorn with the same environment: