lipp / lua-websockets

Websockets for Lua.
http://lipp.github.com/lua-websockets/
MIT License
396 stars 113 forks source link

sync client and settimeout() #80

Open EnTerr opened 8 years ago

EnTerr commented 8 years ago

I am using the sync client, receiving about 30 msgs/sec. I don't want to get stuck waiting if there is no data ready, so i am trying this:

  ws.sock:settimeout(0)
  local message, opcode, close_was_clean, close_code, close_reason = ws:receive()
  ws.sock:settimeout()

My idea/hope being that if there is no data available, it will fail and return me nil or some such and then i will do my other job and keep polling from time to time till i get something. I get nil - HOWEVER it also seems to close the connection, so i never really receive anything!

From my point of view seems like a bug. What can i do?

EnTerr commented 8 years ago

After some debugging seems that sync.lua's receive()'s clean() is way too aggressive (why oh why?) in closing the connection on any kind of error. I added an if and it seems to work for me now:

  local clean = function(was_clean,code,reason)
    if reason ~= 'timeout' then
      self.state = 'CLOSED'
      self:sock_close()
      if self.on_close then
        self:on_close()
      end
    end
    return nil,nil,was_clean,code,reason or 'closed'
  end

PS. not sure if that won't cause problem if a timeout happens after the first sock_receive, when there is data in encoded and thus it will be lost?

lipp commented 8 years ago

@EnTerr Added this as feature request. It's not that trivial, as you must think of partially received messages/frames.

lipp commented 8 years ago

great. i'll keep this issue open, until ws:receive() accepts an optional timeout

EnTerr commented 8 years ago

Ok, so here is a more complete fix i came up with, most code is round about https://github.com/lipp/lua-websockets/blob/master/src/websocket/sync.lua#L24

  ---
  if self._saved then
    local _ = self._saved
    first_opcode = _.first_opcode
    frames = _.frames
    bytes = _.bytes
    encoded = _.encoded
    self._saved = nil   -- erase the saved state
  end
  ---
  while true do
    local chunk, err, partial = self:sock_receive(bytes)

    if err then
      if err == 'timeout'  then
        if #partial > 0 then
          -- there was some partial data, update
          encoded = encoded .. partial
          bytes = bytes - #partial
        end
        -- save state for next call
        self._saved = {
          first_opcode = first_opcode, 
          frames = frames,
          bytes = bytes,
          encoded = encoded,
        }
      end
      return clean(false,1006,err)
    end

So every time receive() is called, it checks if there is a pending self._saved and if so restores state from it. Then on timeout error it saves partial data for the next time.

In addition, close() and connect() have to do self._saved = nil to ensure old state does not persist for potential next connection. And... that's it!

lipp commented 8 years ago

nice! can you make a PR from this?

EnTerr commented 8 years ago

Oops, afraid i can't. I don't have the code under git - besides i am working on almost a year old lua-websockets version, under svn. I did skim the /master/src/websocket/sync.lua here and there are no substantial/relevant changes - but i am nor really set up to PR. Maybe if one day i get current i will