Skip to content

Commit

Permalink
Close other client sockets in application forks
Browse files Browse the repository at this point in the history
All client unix sockets are initiated at the spring application
process before forking to serve each client. A reference to the
client socket remains active by the thread in the wait method waiting
for the client's fork to exit.

This is problematic in cases with more than one parallel clients
because the client socket for the first client is also present in
the fork for the second client due to fd inheritance from the spring
application parent process.

With the first client's socket being present in the second client's
fork, the first client cannot exit gracefully because a reference to
its socket remains open in the fork for the second client leading to
rails console hanging for the first client until the second client
gets terminated.

The problem can be reproduced by opening 2 rails consoles on spring
& attempting to exit the first console while the second is still active.
  • Loading branch information
charkost committed Jun 14, 2021
1 parent 577cf01 commit 96cba83
Showing 1 changed file with 7 additions and 0 deletions.
7 changes: 7 additions & 0 deletions lib/spring/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def initialize(manager, original_env, spring_env = Env.new)
@spring_env = spring_env
@mutex = Mutex.new
@waiting = Set.new
@clients = Set.new
@preloaded = false
@state = :initialized
@interrupt = IO.pipe
Expand Down Expand Up @@ -151,6 +152,8 @@ def serve(client)
log "got client"
manager.puts

@clients << client

_stdout, stderr, _stdin = streams = 3.times.map { client.recv_io }
[STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }

Expand All @@ -167,6 +170,10 @@ def serve(client)
end

pid = fork {
# Make sure to close other clients otherwise their graceful termination
# will be impossible due to reference from this fork.
@clients.select { |c| c != client }.each(&:close)

Process.setsid
IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
trap("TERM", "DEFAULT")
Expand Down

0 comments on commit 96cba83

Please sign in to comment.