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