Class: PcaprLocal::Xtractr::Instance

Inherits:
Object
  • Object
show all
Defined in:
lib/pcapr_local/xtractr/instance.rb

Defined Under Namespace

Classes: XtractrStartupException

Constant Summary collapse

MAX_START_TIME =

Start xtractr

30
RE_STARTED =
/starting on http:/i

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(index_dir, xtractr_path) ⇒ Instance

Returns a new instance of Instance.



14
15
16
17
18
19
20
21
# File 'lib/pcapr_local/xtractr/instance.rb', line 14

def initialize index_dir, xtractr_path
    @index_dir = index_dir
    @xtractr_path = xtractr_path
    @lock = Mutex.new
    @last_use = Time.new.to_f # for idle timeouts
    @pid = nil
    @port = nil
end

Instance Attribute Details

#last_useObject (readonly)

Returns the value of attribute last_use.



12
13
14
# File 'lib/pcapr_local/xtractr/instance.rb', line 12

def last_use
  @last_use
end

#lockObject (readonly)

Returns the value of attribute lock.



12
13
14
# File 'lib/pcapr_local/xtractr/instance.rb', line 12

def lock
  @lock
end

#pidObject (readonly)

Returns the value of attribute pid.



12
13
14
# File 'lib/pcapr_local/xtractr/instance.rb', line 12

def pid
  @pid
end

#portObject (readonly)

Returns the value of attribute port.



12
13
14
# File 'lib/pcapr_local/xtractr/instance.rb', line 12

def port
  @port
end

Class Method Details

.free_local_portObject

Pick a random port over 10000 and verify that it can be bound to (on localhost)



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/pcapr_local/xtractr/instance.rb', line 151

def self.free_local_port
    while true
        port = rand(0xffff-10000) + 10000
        socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
        socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
        addr = Socket.pack_sockaddr_in(port, '127.0.0.1')
        begin
            socket.bind addr
            return port
        rescue Errno::EADDRINUSE
            next # port in use
        ensure
            socket.close
        end
    end
end

Instance Method Details

#do_startObject



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
# File 'lib/pcapr_local/xtractr/instance.rb', line 81

def do_start 
    port = Instance.free_local_port
    command = [@xtractr_path, 'browse', @index_dir, '--port', port.to_s]
    Logger.debug "running: #{command.inspect}"
    xtractr_out = Tempfile.new "xtractr_out"
    pid = fork do
        # Xtractr forks a child process. Set process group so we
        # can send signal to all processes in a group.
        Process.setpgid $$, $$
        STDOUT.reopen xtractr_out
        STDERR.reopen xtractr_out
        Dir.chdir @index_dir
        exec *command 
    end

    begin
        Timeout.timeout MAX_START_TIME do 
            # Wait for "starting" line.
            while xtractr_out.grep(/starting on http:\/\/127\.0\.0\.1:#{port}/i).empty?
                xtractr_out.rewind
                sleep 0.01
            end
            # Sanity check that server is up.
            Net::HTTP.start('127.0.0.1', port) do |http|
                http.options("/")
            end
        end
    rescue Timeout::Error, SystemCallError
        Logger.error "Xtractr failed to start on port #{port}"
        xtractr_out.rewind
        Logger.error "Xtractr output: #{xtractr_out.read.inspect}"
        kind_kill -pid
        raise XtractrStartupException, "Timeout waiting for xtractr to startup"
    end
    
    @last_use = Time.new.to_f
    @pid = pid
    @port = port
end

#get(path_and_params) ⇒ Object

Does GET request and returns response headers and body.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/pcapr_local/xtractr/instance.rb', line 24

def get path_and_params
    start if not @pid
    @lock.synchronize do
        @last_use = Time.new.to_f

        # Make request to xtractr
        uri = URI.parse("http://127.0.0.1:#{@port}/#{path_and_params}")
        response = Net::HTTP.get_response(uri)

        # Copy headers from response
        headers = {}
        response.each_header {|name,val| headers[name] = val}

        return response.code.to_i, headers, response.body
    end
end

#kind_kill(pid, wait = 1) ⇒ Object

Sends SIGTERM and waits for process to exit. If process does not exit within wait period, sends SIGKILL. Use a negative pid to kill all members of a process group.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/pcapr_local/xtractr/instance.rb', line 132

def kind_kill pid, wait=1
    begin
        Process.kill 15, pid
        # Wait for process (or all group members) to
        # exit.
        Timeout.timeout wait do
            loop { Process.wait(pid) }
        end
    rescue Errno::ECHILD, Errno::ESRCH
        # Processes is dead or group has no members.
        return 
    rescue Timeout::Error, StandardError
        # Process did not shutdown in time (or there
        # was an unexpected error).
        Process.kill(9, pid) rescue nil
    end
end

#post(path_and_params, post_body) ⇒ Object

Does POST request and returns response headers and body.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/pcapr_local/xtractr/instance.rb', line 42

def post path_and_params, post_body
    start if not @pid
    @lock.synchronize do
        @last_use = Time.new.to_f

        # Make request to xtractr
        Net::HTTP.start('localhost', @port) do |http|
            http.request_post "/#{path_and_params}", post_body do |response|
                headers = {}
                response.each_header {|name,val| headers[name] = val}
                return response.code.to_i, headers, response.body
            end
        end
    end
end

#startObject

Starts underlying process.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/pcapr_local/xtractr/instance.rb', line 59

def start
    @lock.synchronize do 
        return if @pid
        err = nil
        # There is a remote possibility that the random port we pick will be
        # in use at the moment we try to bind to it. Thus the retries.
        3.times do  |n|
            begin
                do_start
                return
            rescue => err
            end
        end
        raise err
    end
end

#stopObject

Kills underlying xtractr process.



122
123
124
125
126
127
# File 'lib/pcapr_local/xtractr/instance.rb', line 122

def stop
    if @pid
        kind_kill -@pid
        @pid = nil
    end
end