<commit_before>require 'logger'

module Ganger
  class Proxy
    
    # Receive a connection from a client, ask for a Docker container to service the
    # request, then proxy the connection to the container. When the connection ends,
    # get rid of the container.
    
    def initialize(client_socket)
      @client_socket = client_socket
      @log = Logger.new(STDOUT)
      info "Received connection from #{@client_socket.remote_address.ip_address}:#{@client_socket.remote_address.ip_port}"
      @docker_container = DockerDispatcher.get_docker_container
      info "Obtained a Docker container; service port: #{@docker_container.service_port}"
    end
    
    def main_loop
      begin
        connect_to_service
        proxy
      rescue StandardError => e
        fatal "Exception: #{e.message}"
        fatal e.backtrace.join("\n")
        cleanup
      end
    end
    
    def proxy
      info "Entering proxy loop"
      loop do
        (ready_sockets, dummy, dummy) = IO.select([@client_socket, @service_socket])
        begin
          ready_sockets.each do |socket|
            data = socket.readpartial(4096)
            if socket == @client_socket
              @service_socket.write(data)
              @service_socket.flush
            else
              @client_socket.write(data)
              @client_socket.flush
            end
          end
        rescue EOFError
          info "Closing connection"
          break
        end
      end
      cleanup
    end
    
    def cleanup
      info "Cleaning up"
      @docker_container.dispose
      @client_socket.close
      @service_socket.close
    end
    
    private
    
    def get_service_socket      
      addr = Socket.getaddrinfo(@docker_container.service_host, nil)
      socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
      seconds  = Ganger.configuration.service_timeout
      useconds = 0
      sockopt_value = [seconds, useconds].pack("l_2")
      socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, sockopt_value)
      socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, sockopt_value)
      socket
    end
    
    def connect_to_service
      Ganger.configuration.service_retry.times do |count|
        begin
          socket = get_service_socket
          socket.connect(
            Socket.pack_sockaddr_in(@docker_container.service_port.to_i, @docker_container.service_host)
          )
          @service_socket = socket
          break
        rescue SystemCallError => e
          if count == Ganger.configuration.service_retry
            raise "No connection established with container after #{Ganger.configuration.service_retry} attempts; terminating connection"
          end
          # For timeouts, don't sleep; retry immediately as time has passed
          if e.is_a?(Errno::ETIMEDOUT)
            info "Timeout connecting to service after #{Ganger.configuration.service_timeout} seconds; retrying"<commit_msg>If the container is being killed, we don't need to close the service socket<commit_after>require 'logger'

module Ganger
  class Proxy
    
    # Receive a connection from a client, ask for a Docker container to service the
    # request, then proxy the connection to the container. When the connection ends,
    # get rid of the container.
    
    def initialize(client_socket)
      @client_socket = client_socket
      @log = Logger.new(STDOUT)
      info "Received connection from #{@client_socket.remote_address.ip_address}:#{@client_socket.remote_address.ip_port}"
      @docker_container = DockerDispatcher.get_docker_container
      info "Obtained a Docker container; service port: #{@docker_container.service_port}"
    end
    
    def main_loop
      begin
        connect_to_service
        proxy
      rescue StandardError => e
        fatal "Exception: #{e.message}"
        fatal e.backtrace.join("\n")
        cleanup
      end
    end
    
    def proxy
      info "Entering proxy loop"
      loop do
        (ready_sockets, dummy, dummy) = IO.select([@client_socket, @service_socket])
        begin
          ready_sockets.each do |socket|
            data = socket.readpartial(4096)
            if socket == @client_socket
              @service_socket.write(data)
              @service_socket.flush
            else
              @client_socket.write(data)
              @client_socket.flush
            end
          end
        rescue EOFError
          info "Closing connection"
          break
        end
      end
      cleanup
    end
    
    def cleanup
      info "Cleaning up"
      @docker_container.dispose
      @client_socket.close
    end
    
    private
    
    def get_service_socket      
      addr = Socket.getaddrinfo(@docker_container.service_host, nil)
      socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
      seconds  = Ganger.configuration.service_timeout
      useconds = 0
      sockopt_value = [seconds, useconds].pack("l_2")
      socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, sockopt_value)
      socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, sockopt_value)
      socket
    end
    
    def connect_to_service
      Ganger.configuration.service_retry.times do |count|
        begin
          socket = get_service_socket
          socket.connect(
            Socket.pack_sockaddr_in(@docker_container.service_port.to_i, @docker_container.service_host)
          )
          @service_socket = socket
          break
        rescue SystemCallError => e
          if count == Ganger.configuration.service_retry
            raise "No connection established with container after #{Ganger.configuration.service_retry} attempts; terminating connection"
          end
          # For timeouts, don't sleep; retry immediately as time has passed
          if e.is_a?(Errno::ETIMEDOUT)
            info "Timeout connecting to service after #{Ganger.configuration.service_timeout} seconds; retrying"
{'fraction_non_alphanumeric': 0.07145264577013818, 'fraction_numerical': 0.0023592854735422987, 'mean_word_length': 2.341779279279279, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '467575', 'n_tokens_mistral': 1663, 'n_tokens_neox': 1631, 'n_words': 413}