moonbridge

changeset 66:3d1f23f1dbc6

Work on non-blocking I/O; Improved efficiency of :readuntil(...)
author jbe
date Sat Apr 04 05:10:05 2015 +0200 (2015-04-04)
parents 8090fe97518a
children c488f2ea29aa
files moonbridge.c moonbridge_http.lua reference.txt
line diff
     1.1 --- a/moonbridge.c	Sat Apr 04 03:22:06 2015 +0200
     1.2 +++ b/moonbridge.c	Sat Apr 04 05:10:05 2015 +0200
     1.3 @@ -2202,27 +2202,6 @@
     1.4    return 1;
     1.5  }
     1.6  
     1.7 -static int moonbr_lua_tonatural(lua_State *L, int idx) {
     1.8 -  int isnum;
     1.9 -  lua_Number n;
    1.10 -  n = lua_tonumberx(L, idx, &isnum);
    1.11 -  if (isnum && n>=0 && n<INT_MAX && (lua_Number)(int)n == n) return n;
    1.12 -  else return -1;
    1.13 -}
    1.14 -
    1.15 -static int moonbr_lua_totimeval(lua_State *L, int idx, struct timeval *value) {
    1.16 -  int isnum;
    1.17 -  lua_Number n;
    1.18 -  n = lua_tonumberx(L, idx, &isnum);
    1.19 -  if (isnum && n>=0 && n<=100000000) {
    1.20 -    value->tv_sec = n;
    1.21 -    value->tv_usec = 1e6 * (n - value->tv_sec);
    1.22 -    return 1;
    1.23 -  } else {
    1.24 -    return 0;
    1.25 -  }
    1.26 -}
    1.27 -
    1.28  /* Memory allocator that allows limiting memory consumption */
    1.29  static void *moonbr_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
    1.30    (void)ud;  /* not used */
    1.31 @@ -2257,11 +2236,33 @@
    1.32    return ptr;
    1.33  }
    1.34  
    1.35 +/* Helper function to convert a value at the given stack index to a natural number */
    1.36 +static int moonbr_lua_tonatural(lua_State *L, int idx) {
    1.37 +  int isnum;
    1.38 +  lua_Number n;
    1.39 +  n = lua_tonumberx(L, idx, &isnum);
    1.40 +  if (isnum && n>=0 && n<INT_MAX && (lua_Number)(int)n == n) return n;
    1.41 +  else return -1;
    1.42 +}
    1.43 +
    1.44 +/* Helper function to convert a value at the given stack index to a timeval struct */
    1.45 +static int moonbr_lua_totimeval(lua_State *L, int idx, struct timeval *value) {
    1.46 +  int isnum;
    1.47 +  lua_Number n;
    1.48 +  n = lua_tonumberx(L, idx, &isnum);
    1.49 +  if (isnum && n>=0 && n<=100000000) {
    1.50 +    value->tv_sec = n;
    1.51 +    value->tv_usec = 1e6 * (n - value->tv_sec);
    1.52 +    return 1;
    1.53 +  } else {
    1.54 +    return 0;
    1.55 +  }
    1.56 +}
    1.57 +
    1.58  /* New function io.poll(...) */
    1.59  static int moonbr_io_poll(lua_State *L) {
    1.60 -  int idx_tbl = 1;
    1.61 -  int idx_timeout = 0;
    1.62    int i;
    1.63 +  luaL_Stream *stream;
    1.64    int fd, isnum;
    1.65    int nfds = 0;
    1.66    fd_set readfds, writefds, exceptfds;
    1.67 @@ -2270,82 +2271,73 @@
    1.68    FD_ZERO(&readfds);
    1.69    FD_ZERO(&writefds);
    1.70    FD_ZERO(&exceptfds);
    1.71 -  if (lua_type(L, 1) == LUA_TNUMBER) {
    1.72 -    idx_timeout = 1;
    1.73 -    idx_tbl = 2;
    1.74 -  }
    1.75 -  luaL_checktype(L, idx_tbl, LUA_TTABLE);
    1.76 -  for (i=1; ; i++) {
    1.77 -#if LUA_VERSION_NUM >= 503
    1.78 -    if (lua_geti(L, idx_tbl, i) == LUA_TNIL) break;
    1.79 -#else
    1.80 -    lua_pushinteger(L, i);
    1.81 -    lua_gettable(L, idx_tbl);
    1.82 -    if (lua_isnil(L, -1)) break;
    1.83 -#endif
    1.84 -    fd = lua_tointegerx(L, -1, &isnum);
    1.85 -    if (!isnum) luaL_error(L, "File descriptor is not an integer");
    1.86 -    FD_SET(fd, &readfds);
    1.87 -    if (fd+1 > nfds) nfds = fd+1;
    1.88 -    lua_pop(L, 1);
    1.89 -  }
    1.90 -  if (!idx_timeout && lua_type(L, 2) == LUA_TNUMBER) {
    1.91 -    idx_timeout = 2;
    1.92 -    idx_tbl = 3;
    1.93 -  } else {
    1.94 -    idx_tbl = 2;
    1.95 -  }
    1.96 -  if (!lua_isnoneornil(L, idx_tbl)) {
    1.97 -    luaL_checktype(L, idx_tbl, LUA_TTABLE);
    1.98 +  if (!lua_isnoneornil(L, 1)) {
    1.99 +    luaL_checktype(L, 1, LUA_TTABLE);
   1.100      for (i=1; ; i++) {
   1.101  #if LUA_VERSION_NUM >= 503
   1.102 -      if (lua_geti(L, idx_tbl, i) == LUA_TNIL) break;
   1.103 +      if (lua_geti(L, 1, i) == LUA_TNIL) break;
   1.104  #else
   1.105        lua_pushinteger(L, i);
   1.106 -      lua_gettable(L, idx_tbl);
   1.107 +      lua_gettable(L, 1);
   1.108        if (lua_isnil(L, -1)) break;
   1.109  #endif
   1.110 -      fd = lua_tointegerx(L, -1, &isnum);
   1.111 -      if (!isnum) luaL_error(L, "File descriptor is not an integer");
   1.112 +      stream = luaL_testudata(L, -1, LUA_FILEHANDLE);
   1.113 +      if (stream) {
   1.114 +        fd = fileno(stream->f);
   1.115 +      } else {
   1.116 +        fd = lua_tointegerx(L, -1, &isnum);
   1.117 +        if (!isnum) luaL_error(L, "File descriptor is not an integer");
   1.118 +      }
   1.119 +      FD_SET(fd, &readfds);
   1.120 +      if (fd+1 > nfds) nfds = fd+1;
   1.121 +      lua_pop(L, 1);
   1.122 +    }
   1.123 +  }
   1.124 +  if (!lua_isnoneornil(L, 2)) {
   1.125 +    luaL_checktype(L, 2, LUA_TTABLE);
   1.126 +    for (i=1; ; i++) {
   1.127 +#if LUA_VERSION_NUM >= 503
   1.128 +      if (lua_geti(L, 2, i) == LUA_TNIL) break;
   1.129 +#else
   1.130 +      lua_pushinteger(L, i);
   1.131 +      lua_gettable(L, 2);
   1.132 +      if (lua_isnil(L, -1)) break;
   1.133 +#endif
   1.134 +      stream = luaL_testudata(L, -1, LUA_FILEHANDLE);
   1.135 +      if (stream) {
   1.136 +        fd = fileno(stream->f);
   1.137 +      } else {
   1.138 +        fd = lua_tointegerx(L, -1, &isnum);
   1.139 +        if (!isnum) luaL_error(L, "File descriptor is not an integer");
   1.140 +      }
   1.141        FD_SET(fd, &writefds);
   1.142        if (fd+1 > nfds) nfds = fd+1;
   1.143        lua_pop(L, 1);
   1.144      }
   1.145      lua_pop(L, 2);
   1.146 -    if (!idx_timeout && !lua_isnoneornil(L, 3)) idx_timeout = 3;
   1.147    }
   1.148 -  if (idx_timeout) {
   1.149 -    luaL_argcheck(
   1.150 -      L,
   1.151 -      moonbr_lua_totimeval(L, idx_timeout, &timeout),
   1.152 -      idx_timeout,
   1.153 -      "not a valid timeout"
   1.154 -    );
   1.155 +  if (!lua_isnoneornil(L, 3)) {
   1.156 +    luaL_argcheck(L, moonbr_lua_totimeval(L, 3, &timeout), 3, "not a valid timeout");
   1.157    }
   1.158    status = select(nfds, &readfds, &writefds, &exceptfds, &timeout);
   1.159    if (status == -1) {
   1.160      if (errno == EINTR) {
   1.161        lua_pushboolean(L, 0);
   1.162 +      lua_pushliteral(L, "Signal received while polling file descriptors");
   1.163 +      return 2;
   1.164      } else {
   1.165        char errmsg[MOONBR_MAXSTRERRORLEN];
   1.166        strerror_r(errno, errmsg, MOONBR_MAXSTRERRORLEN);  /* use thread-safe call in case child created threads */
   1.167 -      luaL_error(L, "Unexpected error during \"select\" system call: %s", errmsg);
   1.168 +      return luaL_error(L, "Unexpected error during \"select\" system call: %s", errmsg);
   1.169      }
   1.170    } else if (status == 0) {
   1.171      lua_pushboolean(L, 0);
   1.172 +    lua_pushliteral(L, "Timeout while polling file descriptors");
   1.173 +    return 2;
   1.174    } else {
   1.175      lua_pushboolean(L, 1);
   1.176 +    return 1;
   1.177    }
   1.178 -  return 1;
   1.179 -}
   1.180 -
   1.181 -/* New method for Lua file objects: get file descriptor */
   1.182 -static int moonbr_io_getfd(lua_State *L) {
   1.183 -  luaL_Stream *stream;
   1.184 -  stream = luaL_checkudata(L, 1, LUA_FILEHANDLE);
   1.185 -  if (!stream->closef) luaL_error(L, "attempt to use a closed file");
   1.186 -  lua_pushinteger(L, fileno(stream->f));
   1.187 -  return 1;
   1.188  }
   1.189  
   1.190  /* New method for Lua file objects: check if non-blocking reading is possible */
   1.191 @@ -2389,32 +2381,6 @@
   1.192    return 0;
   1.193  }
   1.194  
   1.195 -/* New method for Lua file objects: set blocking or non-blocking I/O */
   1.196 -static int moonbr_io_setblocking(lua_State *L) {
   1.197 -  luaL_Stream *stream;
   1.198 -  int blocking;
   1.199 -  int fd, flags;
   1.200 -  stream = luaL_checkudata(L, 1, LUA_FILEHANDLE);
   1.201 -  luaL_checktype(L, 2, LUA_TBOOLEAN);
   1.202 -  blocking = lua_toboolean(L, 2);
   1.203 -  if (!stream->closef) luaL_error(L, "attempt to use a closed file");
   1.204 -  fd = fileno_unlocked(stream->f);
   1.205 -  flags = fcntl(fd, F_GETFL, 0);
   1.206 -  if (flags == -1) goto moonbr_io_setblocking_error;
   1.207 -  if (blocking) flags &= ~ O_NONBLOCK;
   1.208 -  else flags |= O_NONBLOCK;
   1.209 -  if (fcntl(fd, F_SETFL, flags) == -1) goto moonbr_io_setblocking_error;
   1.210 -  lua_pushboolean(L, 1);
   1.211 -  return 1;
   1.212 -  moonbr_io_setblocking_error:
   1.213 -  {
   1.214 -    char errmsg[MOONBR_MAXSTRERRORLEN];
   1.215 -    strerror_r(errno, errmsg, MOONBR_MAXSTRERRORLEN);  /* use thread-safe call in case child created threads */
   1.216 -    luaL_error(L, "Unexpected error in method \"setblocking\" of FILE handle: %s", errmsg);
   1.217 -  }
   1.218 -  return 0;
   1.219 -}
   1.220 -
   1.221  /* New method for Lua file objects: read until terminator or length exceeded */
   1.222  static int moonbr_io_readuntil(lua_State *L) {
   1.223    luaL_Stream *stream;
   1.224 @@ -2434,12 +2400,14 @@
   1.225    luaL_buffinit(L, &buf);
   1.226    if (!maxlen) maxlen = -1;
   1.227    terminator = terminatorstr[0];
   1.228 +  flockfile(file);
   1.229    while (maxlen > 0 ? maxlen-- : maxlen) {
   1.230 -    byte = fgetc(file);
   1.231 +    byte = getc_unlocked(file);
   1.232      if (byte == EOF) {
   1.233        if (ferror(file)) {
   1.234          char errmsg[MOONBR_MAXSTRERRORLEN];
   1.235          strerror_r(errno, errmsg, MOONBR_MAXSTRERRORLEN);  /* use thread-safe call in case child created threads */
   1.236 +        funlockfile(file);
   1.237          lua_pushnil(L);
   1.238          lua_pushstring(L, errmsg);
   1.239          return 2;
   1.240 @@ -2450,6 +2418,7 @@
   1.241      luaL_addchar(&buf, byte);
   1.242      if (byte == terminator) break;
   1.243    }
   1.244 +  funlockfile(file);
   1.245    luaL_pushresult(&buf);
   1.246    if (!lua_rawlen(L, -1)) lua_pushnil(L);
   1.247    return 1;
   1.248 @@ -2931,12 +2900,8 @@
   1.249        moonbr_terminate_error();
   1.250      }
   1.251      lua_getfield(L, -1, "__index");
   1.252 -    lua_pushcfunction(L, moonbr_io_getfd);
   1.253 -    lua_setfield(L, -2, "getfd");
   1.254 -    lua_pushcfunction(L, moonbr_io_pending);
   1.255 +    eua_pushcfunction(L, moonbr_io_pending);
   1.256      lua_setfield(L, -2, "pending");
   1.257 -    lua_pushcfunction(L, moonbr_io_setblocking);
   1.258 -    lua_setfield(L, -2, "setblocking");
   1.259      lua_pushcfunction(L, moonbr_io_readuntil);
   1.260      lua_setfield(L, -2, "readuntil");
   1.261      lua_pop(L, 2);
     2.1 --- a/moonbridge_http.lua	Sat Apr 04 03:22:06 2015 +0200
     2.2 +++ b/moonbridge_http.lua	Sat Apr 04 05:10:05 2015 +0200
     2.3 @@ -180,13 +180,16 @@
     2.4    end
     2.5    local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384
     2.6    local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024
     2.7 -  local request_header_timeout, response_timeout
     2.8 +  local request_idle_timeout, request_header_timeout, response_timeout
     2.9 +  if options.request_idle_timeout ~= nil then
    2.10 +    request_idle_timeout = options.request_idle_timeout or 0
    2.11 +  else
    2.12 +    request_idle_timeout = 330
    2.13 +  end
    2.14    if options.request_header_timeout ~= nil then
    2.15 -    request_header_timeout = options.request_header_timeout
    2.16 -  elseif options.timeout ~= nil then
    2.17 -    request_header_timeout = options.timeout or 0
    2.18 +    request_header_timeout = options.request_header_timeout or 0
    2.19    else
    2.20 -    request_header_timeout = 360
    2.21 +    request_header_timeout = 30
    2.22    end
    2.23    if options.timeout ~= nil then
    2.24      response_timeout = options.timeout or 0
    2.25 @@ -197,8 +200,6 @@
    2.26    return function(socket)
    2.27      local survive = true  -- set to false if process shall be terminated later
    2.28      repeat
    2.29 -      -- (re)set timeout:
    2.30 -      timeout(request_header_timeout or 0)
    2.31        -- process named arguments "request_header_size_limit" and "request_body_size_limit":
    2.32        local remaining_header_size_limit = options.request_header_size_limit or 1024*1024
    2.33        local remaining_body_size_limit = options.request_body_size_limit or 64*1024*1024
    2.34 @@ -893,6 +894,14 @@
    2.35            end
    2.36          end
    2.37        })
    2.38 +      -- wait for input:
    2.39 +      if not socket.input:pending() then
    2.40 +        if not io.poll({socket.input}, nil, request_idle_timeout) then
    2.41 +          return request_error(false, "408 Request Timeout")
    2.42 +        end
    2.43 +      end
    2.44 +      -- set timeout for request header processing:
    2.45 +      timeout(request_header_timeout)
    2.46        -- read and parse request line:
    2.47        local line = socket:readuntil("\n", remaining_header_size_limit)
    2.48        if not line then return survive end
    2.49 @@ -991,12 +1000,14 @@
    2.50            request.cookies[decode_uri(rawkey)] = decode_uri(rawvalue)
    2.51          end
    2.52        end
    2.53 -      -- (re)set timeout:
    2.54 +      -- (re)set timeout for handler:
    2.55        timeout(response_timeout or 0)
    2.56        -- call underlying handler and remember boolean result:
    2.57        if handler(request) ~= true then survive = false end
    2.58        -- finish request (unless already done by underlying handler):
    2.59        request:finish()
    2.60 +      -- stop timeout timer:
    2.61 +      timeout(0)
    2.62      until connection_close_responded
    2.63      return survive
    2.64    end
     3.1 --- a/reference.txt	Sat Apr 04 03:22:06 2015 +0200
     3.2 +++ b/reference.txt	Sat Apr 04 05:10:05 2015 +0200
     3.3 @@ -45,6 +45,26 @@
     3.4  
     3.5  
     3.6  
     3.7 +Function io.poll(read_fds, write_fds, timeout)
     3.8 +----------------------------------------------
     3.9 +
    3.10 +This function allows to wait for file descriptors to become ready for reading
    3.11 +or writing. It accepts the following arguments:
    3.12 +
    3.13 +1. Table of file descriptors to wait for reading (optional, may be nil)
    3.14 +2. Table of file descriptors to wait for writing (optional, may be nil)
    3.15 +3. Timeout in seconds (optional, may be nil or zero to disable timeout)
    3.16 +
    3.17 +Alternatively to file descriptors, the tables may contain file handles (in
    3.18 +which case the file descriptor is automatically extracted).
    3.19 +
    3.20 +Please note that support for non-blocking I/O operations is limited if you use
    3.21 +ordinary file handles (as Moonbridge does). It is possible, however, to wait
    3.22 +until the first byte to read is available at a file handle. For more
    3.23 +information, see socket.input:pending().
    3.24 +
    3.25 +
    3.26 +
    3.27  Socket object passed to "connect" handler
    3.28  -----------------------------------------
    3.29  
    3.30 @@ -90,6 +110,38 @@
    3.31  Supports the same methods as io.open()'s return values.
    3.32  
    3.33  
    3.34 +### socket.input:pending()
    3.35 +
    3.36 +Returns true if there is at least one byte to read. In case of I/O errors, true
    3.37 +is returned as well (to avoid lockup and cause error on subsequent read).
    3.38 +
    3.39 +Note: Subsequent calls of socket.input:read(...) or socket.input:readuntil(...)
    3.40 +may still block when attempting to read more than one byte. To avoid hanging
    3.41 +processes, the timeout(...) function may be used as a watchdog that will
    3.42 +terminate the process in case of unexpected delay. Because file handles are
    3.43 +buffered, data may be still pending even if the underlaying file descriptor
    3.44 +does not have any more data to read. Thus, all file handles passed to
    3.45 +io.poll(...) to wait for reading should be tested for pending data first.
    3.46 +
    3.47 +This method is also available as :pending() for any other Lua file handle.
    3.48 +
    3.49 +
    3.50 +### socket.input:readuntil(terminator, maxlen)
    3.51 +
    3.52 +Reads as many bytes until a byte equal to the terminator value occurs. An
    3.53 +optional maximum length may be specified. The terminating byte is included in
    3.54 +the return value (unless the maximum length would be exceeded). On EOF, nil is
    3.55 +returned. In case of an I/O error, nil (as first result value) plus an error
    3.56 +message (as second result value) is returned.
    3.57 +
    3.58 +Note: This function may provide a significant speedup compared to byte-wise
    3.59 +reading using socket.input:read(1) in a loop. However, this function will block
    3.60 +when no data is available. The timeout(...) function may be used as a watchdog
    3.61 +that will terminate the process in case of unexpected delay.
    3.62 +
    3.63 +This method is also available as :readuntil(...) for any other Lua file handle.
    3.64 +
    3.65 +
    3.66  ### socket.interval
    3.67  
    3.68  Set to the name of an interval timer if the "connect" handler was called due to
    3.69 @@ -124,6 +176,17 @@
    3.70  Supports the same methods as io.open()'s return values.
    3.71  
    3.72  
    3.73 +### socket.output:close()
    3.74 +
    3.75 +Performs a half-close (i.e. sends a TCP FIN package in case of a TCP socket).
    3.76 +
    3.77 +Note: In order to shut down a TCP connection properly, it may be necessary to
    3.78 +read any pending data from socket.input before closing the socket completely
    3.79 +(e.g. with socket:close() or by returning from the connect handler). If there
    3.80 +is still incoming data, a TCP RST packet might be sent which can cause loss of
    3.81 +transmitted data.
    3.82 +
    3.83 +
    3.84  ### socket:read(...)
    3.85  
    3.86  Alias for socket.input:read()
    3.87 @@ -131,14 +194,7 @@
    3.88  
    3.89  ### socket:readuntil(terminator, maxlen)
    3.90  
    3.91 -Reads as many bytes until a byte equal to the terminator value occurs. An
    3.92 -optional maximum length may be specified. The terminating byte is included in
    3.93 -the return value (unless the maximum length would be exceeded). On EOF, nil is
    3.94 -returned. In case of an I/O error, nil (as first result value) plus an error
    3.95 -message (as second result value) is returned.
    3.96 -
    3.97 -This method is also available as :readuntil(...) for any other Lua file handle
    3.98 -(including socket.input).
    3.99 +Alias for socket.input:readuntil(terminator, maxlen)
   3.100  
   3.101  
   3.102  ### socket.remote_ip4

Impressum / About Us