Class: Sandbox::IRBServer

Inherits:
Object
  • Object
show all
Defined in:
lib/sandbox/server.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host, port, num_processors = (2**30-1), timeout = 0) ⇒ IRBServer

Returns a new instance of IRBServer.



12
13
14
15
16
17
18
19
20
21
# File 'lib/sandbox/server.rb', line 12

def initialize(host, port, num_processors=(2**30-1), timeout=0)
  @socket = TCPServer.new(host, port) 
  @host = host
  @port = port
  @workers = ThreadGroup.new
  @timeout = timeout
  @num_processors = num_processors
  @death_time = 60
  @sessions = {}
end

Instance Attribute Details

#acceptorObject (readonly)

Returns the value of attribute acceptor.



5
6
7
# File 'lib/sandbox/server.rb', line 5

def acceptor
  @acceptor
end

#hostObject (readonly)

Returns the value of attribute host.



7
8
9
# File 'lib/sandbox/server.rb', line 7

def host
  @host
end

#num_processorsObject (readonly)

Returns the value of attribute num_processors.



10
11
12
# File 'lib/sandbox/server.rb', line 10

def num_processors
  @num_processors
end

#portObject (readonly)

Returns the value of attribute port.



8
9
10
# File 'lib/sandbox/server.rb', line 8

def port
  @port
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



9
10
11
# File 'lib/sandbox/server.rb', line 9

def timeout
  @timeout
end

#workersObject (readonly)

Returns the value of attribute workers.



6
7
8
# File 'lib/sandbox/server.rb', line 6

def workers
  @workers
end

Instance Method Details

#graceful_shutdownObject

Performs a wait on all the currently running threads and kills any that take too long. Right now it just waits 60 seconds, but will expand this to allow setting. The @timeout setting does extend this waiting period by that much longer.



81
82
83
84
85
86
# File 'lib/sandbox/server.rb', line 81

def graceful_shutdown
  while reap_dead_workers("shutdown") > 0
    STDERR.print "Waiting for #{@workers.list.length} requests to finish, could take #{@death_time + @timeout} seconds."
    sleep @death_time / 10
  end
end

#new_sandboxObject



28
29
30
# File 'lib/sandbox/server.rb', line 28

def new_sandbox
  Sandbox.safe(:timeout => 10)
end

#process_client(client) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/sandbox/server.rb', line 32

def process_client(client)
  begin
    case client.gets
    when /^LOGIN (\w+)/
      sess = $1
    else
      sess = randid
    end

    @sessions[sess] ||= new_sandbox
    client.puts sess
    Sandbox::IRB.new(@sessions[sess]).start(client)
  rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
    # ignored
  rescue Errno::EMFILE
    reap_dead_workers('too many files')
  rescue Object
    STDERR.puts "#{Time.now}: ERROR: #$!"
    STDERR.puts $!.backtrace.join("\n")
  ensure
    client.close unless client.closed?
  end
end

#randidObject



23
24
25
26
# File 'lib/sandbox/server.rb', line 23

def randid
  abc = %{ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz} 
  (1..20).map { abc[rand(abc.size),1] }.join
end

#reap_dead_workers(reason = 'unknown') ⇒ Object

Used internally to kill off any worker threads that have taken too long to complete processing. Only called if there are too many processors currently servicing. It returns the count of workers still active after the reap is done. It only runs if there are workers to reap.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/sandbox/server.rb', line 60

def reap_dead_workers(reason='unknown')
  if @workers.list.length > 0
    STDERR.puts "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'"
    mark = Time.now
    @workers.list.each do |w|
      w[:started_on] = Time.now if not w[:started_on]

      if mark - w[:started_on] > @death_time + @timeout
        STDERR.puts "Thread #{w.inspect} is too old, killing."
        w.raise(TimeoutError.new("Timed out thread."))
      end
    end
  end

  return @workers.list.length
end

#runObject

Runs the thing. It returns the thread used so you can “join” it. You can also access the HttpServer::acceptor attribute to get the thread later.



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
# File 'lib/sandbox/server.rb', line 91

def run
  BasicSocket.do_not_reverse_lookup=true

  @acceptor = Thread.new do
    while true
      begin
        client = @socket.accept
        worker_list = @workers.list

        if worker_list.length >= @num_processors
          STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
          client.close
          reap_dead_workers("max processors")
        else
          thread = Thread.new { process_client(client) }
          thread.abort_on_exception = true
          thread[:started_on] = Time.now
          @workers.add(thread)

          sleep @timeout/100 if @timeout > 0
        end
      rescue StopServer
        @socket.close if not @socket.closed?
        break
      rescue Errno::EMFILE
        reap_dead_workers("too many open files")
        sleep 0.5
      rescue Errno::ECONNABORTED
        # client closed the socket even before accept
        client.close if not client.closed?
      end
    end

    graceful_shutdown
  end

  return @acceptor
end

#stopObject

Stops the acceptor thread and then causes the worker threads to finish off the request queue before finally exiting.



132
133
134
135
136
137
138
# File 'lib/sandbox/server.rb', line 132

def stop
  stopper = Thread.new do 
    exc = StopServer.new
    @acceptor.raise(exc)
  end
  stopper.priority = 10
end