File: application_manager.rb

package info (click to toggle)
ruby-spring 2.1.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 428 kB
  • sloc: ruby: 3,373; sh: 9; makefile: 7
file content (141 lines) | stat: -rw-r--r-- 3,398 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
module Spring
  class ApplicationManager
    attr_reader :pid, :child, :app_env, :spring_env, :status

    def initialize(app_env, spring_env)
      @app_env    = app_env
      @spring_env = spring_env
      @mutex      = Mutex.new
      @state      = :running
      @pid        = nil
    end

    def log(message)
      spring_env.log "[application_manager:#{app_env}] #{message}"
    end

    # We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
    # line which messes with backtraces in e.g. rspec
    def synchronize
      @mutex.lock
      yield
    ensure
      @mutex.unlock
    end

    def start
      start_child
    end

    def restart
      return if @state == :stopping
      start_child(true)
    end

    def alive?
      @pid
    end

    def with_child
      synchronize do
        if alive?
          begin
            yield
          rescue Errno::ECONNRESET, Errno::EPIPE
            # The child has died but has not been collected by the wait thread yet,
            # so start a new child and try again.
            log "child dead; starting"
            start
            yield
          end
        else
          log "child not running; starting"
          start
          yield
        end
      end
    end

    # Returns the pid of the process running the command, or nil if the application process died.
    def run(client)
      with_child do
        child.send_io client
        child.gets or raise Errno::EPIPE
      end

      pid = child.gets.to_i

      unless pid.zero?
        log "got worker pid #{pid}"
        pid
      end
    rescue Errno::ECONNRESET, Errno::EPIPE => e
      log "#{e} while reading from child; returning no pid"
      nil
    ensure
      client.close
    end

    def stop
      log "stopping"
      @state = :stopping

      if pid
        Process.kill('TERM', pid)
        Process.wait(pid)
      end
    rescue Errno::ESRCH, Errno::ECHILD
      # Don't care
    end

    private

    def start_child(preload = false)
      @child, child_socket = UNIXSocket.pair

      Bundler.with_original_env do
        bundler_dir = File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first)
        @pid = Process.spawn(
          {
            "RAILS_ENV"           => app_env,
            "RACK_ENV"            => app_env,
            "SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV),
            "SPRING_PRELOAD"      => preload ? "1" : "0"
          },
          "ruby",
          *(bundler_dir != RbConfig::CONFIG["rubylibdir"] ? ["-I", bundler_dir] : []),
          "-I", File.expand_path("../..", __FILE__),
          "-e", "require 'spring/application/boot'",
          3 => child_socket,
          4 => spring_env.log_file,
        )
      end

      start_wait_thread(pid, child) if child.gets
      child_socket.close
    end

    def start_wait_thread(pid, child)
      Process.detach(pid)

      Spring.failsafe_thread {
        # The recv can raise an ECONNRESET, killing the thread, but that's ok
        # as if it does we're no longer interested in the child
        loop do
          IO.select([child])
          break if child.recv(1, Socket::MSG_PEEK).empty?
          sleep 0.01
        end

        log "child #{pid} shutdown"

        synchronize {
          if @pid == pid
            @pid = nil
            restart
          end
        }
      }
    end
  end
end