class OpalWebpackLoader::PipeServer

Constants

BUFFER_SIZE
CONNECTING_STATE
INSTANCES
PIPE_TIMEOUT
READING_STATE
WRITING_STATE

Public Class Methods

new(pipe_name, instances = 4, &block) click to toggle source
# File lib/opal-webpack-loader/pipe_server.rb, line 63
def initialize(pipe_name, instances = 4, &block)
  @run_block = block
  @full_pipe_name = "\\\\.\\pipe\\#{pipe_name}"
  @instances = instances
  @events = []
  @events_pointer = FFI::MemoryPointer.new(:uintptr_t, @instances)
  @pipes = []
end

Public Instance Methods

run() click to toggle source
# File lib/opal-webpack-loader/pipe_server.rb, line 72
def run
  create_instances
  while_loop
end

Private Instance Methods

connect_to_new_client(i) click to toggle source
# File lib/opal-webpack-loader/pipe_server.rb, line 197
def connect_to_new_client(i)
  pending_io = false
  @pipes[i][:request].clear
  @pipes[i][:reply].clear
  connected = ConnectNamedPipe(@pipes[i][:instance], @pipes[i][:overlap].to_ptr)
  last_error = GetLastError()
  raise "ConnectNamedPipe failed with #{last_error} - #{connected}" if connected != 0
  
  case last_error
  when ERROR_IO_PENDING
    pending_io = true
  when ERROR_PIPE_CONNECTED
    SetEvent(@pipes[i][:overlap][:hEvent])
  when ERROR_SUCCESS
    pending_io = true
  else
    raise "ConnectNamedPipe failed with error #{last_error}"
  end

  pending_io
end
create_instances() click to toggle source
# File lib/opal-webpack-loader/pipe_server.rb, line 79
def create_instances
  (0...@instances).each do |i|
    @events[i] = CreateEvent(nil, 1, 1, nil)
    raise "CreateEvent failed with #{GetLastError()}" unless @events[i]

    overlap = Overlapped.new
    overlap[:hEvent] = @events[i]

    @pipes[i] = { overlap: overlap, instance: nil, request: FFI::Buffer.new(1, BUFFER_SIZE), bytes_read: 0, reply: FFI::Buffer.new(1, BUFFER_SIZE), bytes_to_write: 0, state: nil, pending_io: false }
    @pipes[i][:instance] = CreateNamedPipe(@full_pipe_name, 
                                          PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                                          PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                                          @instances,
                                          BUFFER_SIZE,
                                          BUFFER_SIZE,
                                          PIPE_TIMEOUT,
                                          nil)

    raise "CreateNamedPipe failed with #{GetLastError()}" if @pipes[i][:instance] == INVALID_HANDLE_VALUE
    @pipes[i][:pending_io] = connect_to_new_client(i)
    @pipes[i][:state] = @pipes[i][:pending_io] ? CONNECTING_STATE : READING_STATE
  end
  @events_pointer.write_array_of_ulong_long(@events)
  nil
end
disconnect_and_reconnect(i) click to toggle source
# File lib/opal-webpack-loader/pipe_server.rb, line 188
def disconnect_and_reconnect(i)
  FlushFileBuffers(@pipes[i][:instance])
  STDERR.puts("DisconnectNamedPipe failed with #{GetLastError()}") if !DisconnectNamedPipe(@pipes[i][:instance])
  
  @pipes[i][:pending_io] = connect_to_new_client(i)
  
  @pipes[i][:state] = @pipes[i][:pending_io] ? CONNECTING_STATE : READING_STATE
end
while_loop() click to toggle source
# File lib/opal-webpack-loader/pipe_server.rb, line 105
def while_loop
  while true
    i = MsgWaitForMultipleObjects(@instances, @events_pointer, 0, INFINITE, QS_ALLINPUT)
    # Having this STDOUT.putc is essential, otherwise there is a tendency to block within MsgWaitForMultipleObjects ...
    STDOUT.putc "."
    # ... because the ruby interpreter is waiting for objects too on Windows. Thats why we wait for QS_ALLINPUT and
    # with STDOUT.putc give back control to the ruby interpreter that it can handle its things.
    if i < 0 || i > (@instances - 1)
      STDERR.puts "Pipe index out of range. Maybe a error occured."
      next
    end

    if @pipes[i][:pending_io]
      bytes_transferred = FFI::MemoryPointer.new(:ulong)
      success = GetOverlappedResult(@pipes[i][:instance], @pipes[i][:overlap], bytes_transferred, false)

      case @pipes[i][:state]
      when CONNECTING_STATE
        raise "Error #{GetLastError()}" unless success
        @pipes[i][:state] = READING_STATE
      when READING_STATE
        if !success || bytes_transferred.read_ulong == 0
          disconnect_and_reconnect(i)
          next
        else
          @pipes[i][:bytes_read] = bytes_transferred.read_ulong
          @pipes[i][:state] = WRITING_STATE
        end
      when WRITING_STATE
        if !success || bytes_transferred.read_ulong != @pipes[i][:bytes_to_write]
          disconnect_and_reconnect(i)
          next
        else
          @pipes[i][:state] = READING_STATE
        end
      else
        raise "Invalid pipe state."
      end
    end

    case @pipes[i][:state]
    when READING_STATE
      bytes_read = FFI::MemoryPointer.new(:ulong)
      success = ReadFile(@pipes[i][:instance], @pipes[i][:request], BUFFER_SIZE, bytes_read, @pipes[i][:overlap].to_ptr)
      if success && bytes_read.read_ulong != 0
        @pipes[i][:pending_io] = false
        @pipes[i][:state] = WRITING_STATE
        next
      end

      err = GetLastError()
      if !success && err == ERROR_IO_PENDING
        @pipes[i][:pending_io] = true
        next
      end

      disconnect_and_reconnect(i)
    when WRITING_STATE
      @pipes[i][:reply] = @run_block.call(@pipes[i][:request].get_string(0))
      @pipes[i][:bytes_to_write] = @pipes[i][:reply].bytesize
      bytes_written = FFI::MemoryPointer.new(:ulong)
      success = WriteFile(@pipes[i][:instance], @pipes[i][:reply], @pipes[i][:bytes_to_write], bytes_written, @pipes[i][:overlap].to_ptr)

      if success && bytes_written.read_ulong == @pipes[i][:bytes_to_write]
        @pipes[i][:pending_io] = false
        @pipes[i][:state] = READING_STATE
        next
      end

      err = GetLastError()

      if !success && err == ERROR_IO_PENDING
        @pipes[i][:pending_io] = true
        next
      end

      disconnect_and_reconnect(i)
    else
      raise "Invalid pipe state."
    end
  end
end