Search code examples
rubyfedorapumaspawn

Ruby spawned process listening on parent server port


I am running a puma server ruby application on fedora 32. In my server I have certain calls which will spawn new long running processes for various reasons. I came across an issue where my spawned processes were running and listening on the same port as my server. This lead to issues with restarting my server on deploys as the server could not start because of processes listening on the desired port. How could this be possible? From my understanding when I spawn a process it should have completely different memory to the parent process, and share no file descriptors. My spawn command is simply

my_pid = Process.spawn(my_cmd, %i[out err] => log_file)

Ruby version 2.7.0

Edit: something I had overlooked in my deploy process and my original problem description, server restart is not an actual tear down and restart of a new process, but via signalling USR2 to the puma server (as described here)


Solution

  • I believe I have found what is causing this. Seems to be an issue with puma restart process, which I was using. By restarting the server with a USR2 signal, it changes the flags on the open fd for the socket.

    [me@home puma_testing]$ cat /proc/511620/fdinfo/5
    pos:    0
    flags:  02000002
    mnt_id: 10
    [me@home puma_testing]$ kill -s USR2 511620
    [me@home puma_testing]$ cat /proc/511620/fdinfo/5
    pos:    0
    flags:  02
    mnt_id: 10
    

    This was tested on fedora 32 using a very simple puma and sinatra setup like so: puma.rb

    # frozen_string_literal: true
    
    rackup File.join(File.dirname(File.realpath(__FILE__)), './server.ru')
    
    # https://www.rubydoc.info/gems/puma/Puma/DSL#prune_bundler-instance_method
    # This allows us to install new gems with just a phased-restart. Otherwise you
    # need to take the master process down each time.
    prune_bundler
    
    port 11111
    
    environment 'production'
    
    pidfile File.join(File.dirname(File.realpath(__FILE__)), '../', 'server.pid')
    
    tag 'test'
    

    And server.ru like so

    require 'sinatra'
    
    class App < Sinatra::Base
      get "/" do
        "Hello World!"
      end
    
      get "/spawn" do
        spawn "sleep 500"
      end
    end
    
    run App
    

    Ran using bundler bundle exec puma -C puma.rb. Note you can use /spawn get request to test spawning a new process before and after restart to see if it is listening on the socket with lsof -itcp:11111