File: run.rb

package info (click to toggle)
ruby-spring 1.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 364 kB
  • ctags: 429
  • sloc: ruby: 2,762; makefile: 6
file content (151 lines) | stat: -rw-r--r-- 3,698 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
142
143
144
145
146
147
148
149
150
151
require "rbconfig"
require "socket"

module Spring
  module Client
    class Run < Command
      FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO) & Signal.list.keys
      TIMEOUT = 1

      def initialize(args)
        super
        @signal_queue = []
      end

      def log(message)
        env.log "[client] #{message}"
      end

      def server
        @server ||= UNIXSocket.open(env.socket_name)
      end

      def call
        boot_server unless env.server_running?
        verify_server_version

        application, client = UNIXSocket.pair

        queue_signals
        connect_to_application(client)
        run_command(client, application)
      rescue Errno::ECONNRESET
        exit 1
      ensure
        server.close if @server
      end

      def boot_server
        env.socket_path.unlink if env.socket_path.exist?

        pid = fork {
          require "spring/server"
          Spring::Server.boot
        }

        until env.socket_path.exist?
          _, status = Process.waitpid2(pid, Process::WNOHANG)
          exit status.exitstatus if status
          sleep 0.1
        end
      end

      def verify_server_version
        server_version = server.gets.chomp
        if server_version != env.version
          $stderr.puts <<-ERROR
There is a version mismatch between the spring client and the server.
You should restart the server and make sure to use the same version.

CLIENT: #{env.version}, SERVER: #{server_version}
ERROR
          exit 1
        end
      end

      def connect_to_application(client)
        server.send_io client
        send_json server, "args" => args, "default_rails_env" => default_rails_env

        if IO.select([server], [], [], TIMEOUT)
          server.gets or raise CommandNotFound
        else
          raise "Error connecting to Spring server"
        end
      end

      def run_command(client, application)
        log "sending command"

        application.send_io STDOUT
        application.send_io STDERR
        application.send_io STDIN

        send_json application, "args" => args, "env" => ENV.to_hash

        pid = server.gets
        pid = pid.chomp if pid

        # We must not close the client socket until we are sure that the application has
        # received the FD. Otherwise the FD can end up getting closed while it's in the server
        # socket buffer on OS X. This doesn't happen on Linux.
        client.close

        if pid && !pid.empty?
          log "got pid: #{pid}"

          forward_signals(pid.to_i)
          status = application.read.to_i

          log "got exit status #{status}"

          exit status
        else
          log "got no pid"
          exit 1
        end
      ensure
        application.close
      end

      def queue_signals
        FORWARDED_SIGNALS.each do |sig|
          trap(sig) { @signal_queue << sig }
        end
      end

      def forward_signals(pid)
        @signal_queue.each { |sig| kill sig, pid }

        FORWARDED_SIGNALS.each do |sig|
          trap(sig) { forward_signal sig, pid }
        end
      rescue Errno::ESRCH
      end

      def forward_signal(sig, pid)
        kill(sig, pid)
      rescue Errno::ESRCH
        # If the application process is gone, then don't block the
        # signal on this process.
        trap(sig, 'DEFAULT')
        Process.kill(sig, Process.pid)
      end

      def kill(sig, pid)
        Process.kill(sig, -Process.getpgid(pid))
      end

      def send_json(socket, data)
        data = JSON.dump(data)

        socket.puts  data.bytesize
        socket.write data
      end

      def default_rails_env
        ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
      end
    end
  end
end