def eventLoopBody
      # 1. Check timers
      timer, selectTimeout = processTimers

      readset = []
      writeset = []
      outputBufferNotEmptyCount = 0
      ioToRemove = []
      @ioInfo.each do |k,v|
        if k.closed?
          ioToRemove.push k
          next
        end
        readset.push k if v.state != :connecting && ! @stopped && (v.readRateLimit.nil? || v.readRateLimit.avail >= 1.0)
        @logger.debug "eventloop: IO metainfo=#{v.metainfo} added to read set" if @logger
        writeset.push k if (!v.outputBuffer.empty? || v.state == :connecting) && v.state != :listening && (v.writeRateLimit.nil? || v.writeRateLimit.avail >= 1.0)
        @logger.debug "eventloop: IO metainfo=#{v.metainfo} added to write set" if @logger
        outputBufferNotEmptyCount += 1 if !v.outputBuffer.empty?
      end
      readset.push @eventRead

      # Only exit the event loop once we've written all pending data.
      return :halt if @stopped && outputBufferNotEmptyCount == 0

      # 2. Check user events
      @userEvents.each{ |event| @handler.userEvent event } if ! @stopped

      # 3. Call Select. Ignore exception set: apparently this is for OOB data, or terminal things.  
      selectResult = nil
      while true
        begin
          if readset.length > 1024 || writeset.length > 1024
            @logger.error "Too many file descriptors to pass to select! Trimming them. Some fds may starve!" if @logger
            readset = readset.first(1024)
            writeset = writeset.first(1024)
          end
          @logger.debug "eventloop: Calling select" if @logger
          selectResult = IO.select(readset, writeset, nil, selectTimeout)
          @logger.debug "eventloop: select done. result: #{selectResult.inspect}" if @logger
          break
        rescue
          # Exception occurred. Probably EINTR.
          @logger.warn "Select raised exception; will retry. Reason: #{$!}" if @logger
        end
      end

      if timer
        # Calling processTimer in withReadFiber here is not correct. What if at this point the fiber was already paused in a read, and we
        # want to process a timer? In that case we will resume the read and it will possibly finish, but we'll never 
        # call the timer handler. For this reason we must prevent read calls in timerHandlers.
        # Process timer
        @logger.debug "eventloop: processing timer" if @logger
        processTimer(timer)
      end

      if ! selectResult.nil?
        readable, writeable = selectResult
  
        # If we are stopped, then ignore reads; we only care about completing our writes that were pending when we were stopped.
        readable = [] if @stopped

        readable.each do |io|
          # This could be the eventRead pipe, which we use to signal shutdown or to reloop.
          if io == @eventRead
            @logger.debug "Event received on the eventRead pipe." if @logger
            @eventRead.read 1
            @currentEventPipeChars -= 1
            next
          end

          @currentIoInfo = @ioInfo[io]
 
          # The IOInfo associated with this io could have been closed by the timer handler processed above.         
          next if @currentIoInfo.nil?

          if @currentIoInfo.state == :listening
            @logger.debug "eventloop: calling handleAccept for IO metainfo=#{@currentIoInfo.metainfo}" if @logger
            # Replace the currentIoInfo with the accepted socket
            listenerMetainfo = @currentIoInfo.metainfo
            @currentIoInfo, addr, port = handleAccept(@currentIoInfo)
            withReadFiber(@currentIoInfo) do 
              @currentHandlerCallback = :serverinit
              @handler.serverInit(listenerMetainfo, addr, port)
            end
          else
            @logger.debug "eventloop: calling handleRead for IO metainfo=#{@currentIoInfo.metainfo}" if @logger
            #handleRead(@currentIoInfo)
            withReadFiber(@currentIoInfo) do 
              @currentHandlerCallback = :recv_data
              @handler.recvData @currentIoInfo.metainfo
            end
          end
        end

        writeable.each do |io|
          @currentIoInfo = @ioInfo[io]
          # Check if there is still ioInfo for this io. This can happen if this io was also ready for read, and 
          # the read had an error (for example connection failure) and the ioinfo was removed when handling the error.
          next if ! @currentIoInfo 
          if @currentIoInfo.state == :connecting
            @logger.debug "eventloop: calling finishConnection for IO metainfo=#{@currentIoInfo.metainfo}" if @logger
            finishConnection(@currentIoInfo)
          else
            @logger.debug "eventloop: calling writeOutputBuffer for IO metainfo=#{@currentIoInfo.metainfo}" if @logger
            writeOutputBuffer(@currentIoInfo)
          end
        end
      end

      ioToRemove.each do |io|
        ioInfo = @ioInfo.delete io
        @logger.warn "Detected an IO that was closed but still in the list of selectable IO. Metadata = #{ioInfo.metainfo}"
      end

      :continue
    end
def eventLoopBody
      # 1. Check timers
      timer, selectTimeout = processTimers

      readset = []
      writeset = []
      outputBufferNotEmptyCount = 0
      ioToRemove = []
      @ioInfo.each do |k,v|
        if k.closed?
          ioToRemove.push k
          next
        end
        readset.push k if v.state != :connecting && ! @stopped && (v.readRateLimit.nil? || v.readRateLimit.avail >= 1.0)
        @logger.debug "eventloop: IO metainfo=#{v.metainfo} added to read set" if @logger
        writeset.push k if (!v.outputBuffer.empty? || v.state == :connecting) && v.state != :listening && (v.writeRateLimit.nil? || v.writeRateLimit.avail >= 1.0)
        @logger.debug "eventloop: IO metainfo=#{v.metainfo} added to write set" if @logger
        outputBufferNotEmptyCount += 1 if !v.outputBuffer.empty?
      end
      readset.push @eventRead

      # Only exit the event loop once we've written all pending data.
      return :halt if @stopped && outputBufferNotEmptyCount == 0

      # 2. Check user events
      @userEvents.each{ |event| @handler.userEvent event } if ! @stopped

      # 3. Call Select. Ignore exception set: apparently this is for OOB data, or terminal things.  
      selectResult = nil
      while true
        begin
          if readset.length > 1024 || writeset.length > 1024
            @logger.error "Too many file descriptors to pass to select! Trimming them. Some fds may starve!" if @logger
            readset = readset.first(1024)
            writeset = writeset.first(1024)
          end
          @logger.debug "eventloop: Calling select" if @logger
          selectResult = IO.select(readset, writeset, nil, selectTimeout)
          @logger.debug "eventloop: select done. result: #{selectResult.inspect}" if @logger
          break
        rescue
          # Exception occurred. Probably EINTR.
          @logger.warn "Select raised exception; will retry. Reason: #{$!}" if @logger
        end
      end

      if timer
        # Calling processTimer in withReadFiber here is not correct. What if at this point the fiber was already paused in a read, and we
        # want to process a timer? In that case we will resume the read and it will possibly finish, but we'll never 
        # call the timer handler. For this reason we must prevent read calls in timerHandlers.
        # Process timer
        @logger.debug "eventloop: processing timer" if @logger
        processTimer(timer)
      end

      if ! selectResult.nil?
        readable, writeable = selectResult
  
        # If we are stopped, then ignore reads; we only care about completing our writes that were pending when we were stopped.
        readable = [] if @stopped

        readable.each do |io|
          # This could be the eventRead pipe, which we use to signal shutdown or to reloop.
          if io == @eventRead
            @logger.debug "Event received on the eventRead pipe." if @logger
            @eventRead.read 1
            @currentEventPipeChars -= 1
            next
          end

          @currentIoInfo = @ioInfo[io]
 
          # The IOInfo associated with this io could have been closed by the timer handler processed above.         
          next if @currentIoInfo.nil?

          if @currentIoInfo.state == :listening
            @logger.debug "eventloop: calling handleAccept for IO metainfo=#{@currentIoInfo.metainfo}" if @logger
            # Replace the currentIoInfo with the accepted socket
            listenerMetainfo = @currentIoInfo.metainfo
            @currentIoInfo, addr, port = handleAccept(@currentIoInfo)
            withReadFiber(@currentIoInfo) do 
              @currentHandlerCallback = :serverinit
              @handler.serverInit(listenerMetainfo, addr, port)
            end
          else
            @logger.debug "eventloop: calling handleRead for IO metainfo=#{@currentIoInfo.metainfo}" if @logger
            #handleRead(@currentIoInfo)
            withReadFiber(@currentIoInfo) do 
              @currentHandlerCallback = :recv_data
              @handler.recvData @currentIoInfo.metainfo
            end
          end
        end

        writeable.each do |io|
          @currentIoInfo = @ioInfo[io]
          # Check if there is still ioInfo for this io. This can happen if this io was also ready for read, and 
          # the read had an error (for example connection failure) and the ioinfo was removed when handling the error.
          next if ! @currentIoInfo 
          if @currentIoInfo.state == :connecting
            @logger.debug "eventloop: calling finishConnection for IO metainfo=#{@currentIoInfo.metainfo}" if @logger
            finishConnection(@currentIoInfo)
          else
            @logger.debug "eventloop: calling writeOutputBuffer for IO metainfo=#{@currentIoInfo.metainfo}" if @logger
            writeOutputBuffer(@currentIoInfo)
          end
        end
      end

      ioToRemove.each do |io|
        ioInfo = @ioInfo.delete io
        @logger.warn "Detected an IO that was closed but still in the list of selectable IO. Metadata = #{ioInfo.metainfo}"
      end

      :continue
    end
Returns :continue or :halt to the caller, specifying whether to continue the event loop or halt.