diff --git a/.ci/install.bat b/.ci/install.bat index c5c3d3b4..bfff1362 100644 --- a/.ci/install.bat +++ b/.ci/install.bat @@ -27,7 +27,7 @@ Setlocal EnableDelayedExpansion EnableExtensions if not defined LUAROCKS_URL set LUAROCKS_URL=https://luarocks.github.io/luarocks/releases if not defined LUAROCKS_REPO set LUAROCKS_REPO=https://luarocks.org -if not defined LUA_URL set LUA_URL=http://www.lua.org/ftp +if not defined LUA_URL set LUA_URL=https://www.lua.org/ftp if defined NOCOMPAT ( set COMPATFLAG=--nocompat ) else ( diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dae55e5..4dc4e631 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 3.10) project (luv C ASM) set(LUV_VERSION_MAJOR 1) -set(LUV_VERSION_MINOR 49) -set(LUV_VERSION_PATCH 2) +set(LUV_VERSION_MINOR 50) +set(LUV_VERSION_PATCH 0) set(LUV_VERSION ${LUV_VERSION_MAJOR}.${LUV_VERSION_MINOR}.${LUV_VERSION_PATCH}) if(NOT ${CMAKE_VERSION} VERSION_LESS "3.5.0") diff --git a/appveyor.yml b/appveyor.yml index 3ef72191..c98657a1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,4 @@ image: - - Visual Studio 2015 - Visual Studio 2022 platform: @@ -23,9 +22,6 @@ matrix: # Skip x86 for LuaRocks tests - platform: x86 LUAROCKS_VER: 3.8.0 - # Only test LuaRocks with latest MSVC - - image: Visual Studio 2015 - LUAROCKS_VER: 3.8.0 cache: - c:\lua -> appveyor.yml diff --git a/deps/libuv b/deps/libuv index e1095c7a..8fb9cb91 160000 --- a/deps/libuv +++ b/deps/libuv @@ -1 +1 @@ -Subproject commit e1095c7a4373ce00cd8874d8e820de5afb25776e +Subproject commit 8fb9cb919489a48880680a56efecff6a7dfb4504 diff --git a/docs.md b/docs.md index ef74a499..e38c38c4 100644 --- a/docs.md +++ b/docs.md @@ -2464,6 +2464,54 @@ completed immediately. **Returns:** `integer` or `fail` +### `uv.udp_try_send2(udp, messages, flags)` + +> method form `udp:try_send2(messages, flags)` + +**Parameters:** +- `udp`: `uv_udp_t userdata` +- `messages`: `table` + - `[1, 2, 3, ..., n]` : `table` + - `data` : `buffer` + - `addr` : `table` + - `ip` : `string` + - `port` : `integer` +- `flags`: `nil` (see below) +- `port`: `integer` + +Like `uv.udp_try_send()`, but can send multiple datagrams. +Lightweight abstraction around `sendmmsg(2)`, with a `sendmsg(2)` fallback loop +for platforms that do not support the former. The `udp` handle must be fully +initialized, either from a `uv.udp_bind` call, another call that will bind +automatically (`udp_send`, `udp_try_send`, etc), or from `uv.udp_connect`. + +`messages` should be an array-like table, where `addr` must be specified +if the `udp` has not been connected via `udp_connect`. Otherwise, `addr` +must be `nil`. + +`flags` is reserved for future extension and must currently be `nil` or `0` or +`{}`. + +Returns the number of messages sent successfully. An error will only be returned +if the first datagram failed to be sent. + +**Returns:** `integer` or `fail` + +```lua +-- If client:connect(...) was not called +local addr = { ip = "127.0.0.1", port = 1234 } +client:try_send2({ + { data = "Message 1", addr = addr }, + { data = "Message 2", addr = addr }, +}) + +-- If client:connect(...) was called +client:try_send2({ + { data = "Message 1" }, + { data = "Message 2" }, +}) +``` + ### `uv.udp_recv_start(udp, callback)` > method form `udp:recv_start(callback)` @@ -3563,6 +3611,41 @@ Waits for the `thread` to finish executing its entry function. **Returns:** `boolean` or `fail` +### `uv.thread_detach(thread)` + +> method form `thread:detach()` + +**Parameters:** +- `thread`: `luv_thread_t userdata` + +Detaches a thread. Detached threads automatically release their resources upon +termination, eliminating the need for the application to call `uv.thread_join`. + +**Returns:** `boolean` or `fail` + +### `uv.thread_setname(name)` + +**Parameters:** +- `name`: `string` + +Sets the name of the current thread. Different platforms define different limits +on the max number of characters a thread name can be: Linux, IBM i (16), macOS +(64), Windows (32767), and NetBSD (32), etc. The name will be truncated +if `name` is larger than the limit of the platform. + +**Returns:** `0` or `fail` + +### `uv.thread_getname(thread)` + +> method form `thread:getname()` + +**Parameters:** +- `thread`: `luv_thread_t userdata` + +Gets the name of the thread specified by `thread`. + +**Returns:** `string` or `fail` + ### `uv.sleep(msec)` **Parameters:** @@ -3674,6 +3757,36 @@ Returns the resource usage. - `nvcsw` : `integer` (voluntary context switches) - `nivcsw` : `integer` (involuntary context switches) +### `uv.getrusage_thread()` + +Gets the resource usage measures for the calling thread. + +**Note** Not supported on all platforms. May return `ENOTSUP`. +On macOS and Windows not all fields are set (the unsupported fields are filled +with zeroes). + +**Returns:** `table` or `fail` +- `utime` : `table` (user CPU time used) + - `sec` : `integer` + - `usec` : `integer` +- `stime` : `table` (system CPU time used) + - `sec` : `integer` + - `usec` : `integer` +- `maxrss` : `integer` (maximum resident set size) +- `ixrss` : `integer` (integral shared memory size) +- `idrss` : `integer` (integral unshared data size) +- `isrss` : `integer` (integral unshared stack size) +- `minflt` : `integer` (page reclaims (soft page faults)) +- `majflt` : `integer` (page faults (hard page faults)) +- `nswap` : `integer` (swaps) +- `inblock` : `integer` (block input operations) +- `oublock` : `integer` (block output operations) +- `msgsnd` : `integer` (IPC messages sent) +- `msgrcv` : `integer` (IPC messages received) +- `nsignals` : `integer` (signals received) +- `nvcsw` : `integer` (voluntary context switches) +- `nivcsw` : `integer` (involuntary context switches) + ### `uv.available_parallelism()` Returns an estimate of the default amount of parallelism a program should use. Always returns a non-zero value. diff --git a/src/luv.c b/src/luv.c index cb9c1c7c..aace3c27 100644 --- a/src/luv.c +++ b/src/luv.c @@ -227,6 +227,9 @@ static const luaL_Reg luv_functions[] = { {"udp_set_ttl", luv_udp_set_ttl}, {"udp_send", luv_udp_send}, {"udp_try_send", luv_udp_try_send}, +#if LUV_UV_VERSION_GEQ(1, 50, 0) + {"udp_try_send2", luv_udp_try_send2}, +#endif {"udp_recv_start", luv_udp_recv_start}, {"udp_recv_stop", luv_udp_recv_stop}, #if LUV_UV_VERSION_GEQ(1, 27, 0) @@ -330,6 +333,9 @@ static const luaL_Reg luv_functions[] = { {"setgid", luv_setgid}, #endif {"getrusage", luv_getrusage}, +#if LUV_UV_VERSION_GEQ(1, 50, 0) + {"getrusage_thread", luv_getrusage_thread}, +#endif {"guess_handle", luv_guess_handle}, {"hrtime", luv_hrtime}, {"interface_addresses", luv_interface_addresses}, @@ -396,6 +402,11 @@ static const luaL_Reg luv_functions[] = { {"thread_getpriority", luv_thread_getpriority}, {"thread_setpriority", luv_thread_setpriority}, #endif +#if LUV_UV_VERSION_GEQ(1, 50, 0) + {"thread_detach", luv_thread_detach}, + {"thread_getname", luv_thread_getname}, + {"thread_setname", luv_thread_setname}, +#endif #if LUV_UV_VERSION_GEQ(1, 49, 0) {"utf16_length_as_wtf8", luv_utf16_length_as_wtf8}, @@ -585,6 +596,9 @@ static const luaL_Reg luv_udp_methods[] = { {"set_ttl", luv_udp_set_ttl}, {"send", luv_udp_send}, {"try_send", luv_udp_try_send}, +#if LUV_UV_VERSION_GEQ(1, 50, 0) + {"try_send2", luv_udp_try_send2}, +#endif {"recv_start", luv_udp_recv_start}, {"recv_stop", luv_udp_recv_stop}, #if LUV_UV_VERSION_GEQ(1, 27, 0) diff --git a/src/luv.h b/src/luv.h index 38f4e7f5..1884fb74 100644 --- a/src/luv.h +++ b/src/luv.h @@ -62,6 +62,10 @@ #define MAX_TITLE_LENGTH (8192) #endif +#ifndef MAX_THREAD_NAME_LENGTH +#define MAX_THREAD_NAME_LENGTH (8192) +#endif + // luv flags to control luv_CFpcall routine #define LUVF_CALLBACK_NOEXIT 0x01 // Don't exit when LUA_ERRMEM #define LUVF_CALLBACK_NOTRACEBACK 0x02 // Don't traceback when error diff --git a/src/misc.c b/src/misc.c index d2e98592..826919ff 100644 --- a/src/misc.c +++ b/src/misc.c @@ -165,61 +165,75 @@ static void luv_push_timeval_table(lua_State* L, const uv_timeval_t* t) { lua_setfield(L, -2, "usec"); } -static int luv_getrusage(lua_State* L) { - uv_rusage_t rusage; - int ret = uv_getrusage(&rusage); - if (ret < 0) return luv_error(L, ret); +static void luv_push_rusage_table(lua_State* L, const uv_rusage_t* rusage) { lua_createtable(L, 0, 16); // user CPU time used - luv_push_timeval_table(L, &rusage.ru_utime); + luv_push_timeval_table(L, &rusage->ru_utime); lua_setfield(L, -2, "utime"); // system CPU time used - luv_push_timeval_table(L, &rusage.ru_stime); + luv_push_timeval_table(L, &rusage->ru_stime); lua_setfield(L, -2, "stime"); // maximum resident set size - lua_pushinteger(L, rusage.ru_maxrss); + lua_pushinteger(L, rusage->ru_maxrss); lua_setfield(L, -2, "maxrss"); // integral shared memory size - lua_pushinteger(L, rusage.ru_ixrss); + lua_pushinteger(L, rusage->ru_ixrss); lua_setfield(L, -2, "ixrss"); // integral unshared data size - lua_pushinteger(L, rusage.ru_idrss); + lua_pushinteger(L, rusage->ru_idrss); lua_setfield(L, -2, "idrss"); // integral unshared stack size - lua_pushinteger(L, rusage.ru_isrss); + lua_pushinteger(L, rusage->ru_isrss); lua_setfield(L, -2, "isrss"); // page reclaims (soft page faults) - lua_pushinteger(L, rusage.ru_minflt); + lua_pushinteger(L, rusage->ru_minflt); lua_setfield(L, -2, "minflt"); // page faults (hard page faults) - lua_pushinteger(L, rusage.ru_majflt); + lua_pushinteger(L, rusage->ru_majflt); lua_setfield(L, -2, "majflt"); // swaps - lua_pushinteger(L, rusage.ru_nswap); + lua_pushinteger(L, rusage->ru_nswap); lua_setfield(L, -2, "nswap"); // block input operations - lua_pushinteger(L, rusage.ru_inblock); + lua_pushinteger(L, rusage->ru_inblock); lua_setfield(L, -2, "inblock"); // block output operations - lua_pushinteger(L, rusage.ru_oublock); + lua_pushinteger(L, rusage->ru_oublock); lua_setfield(L, -2, "oublock"); // IPC messages sent - lua_pushinteger(L, rusage.ru_msgsnd); + lua_pushinteger(L, rusage->ru_msgsnd); lua_setfield(L, -2, "msgsnd"); // IPC messages received - lua_pushinteger(L, rusage.ru_msgrcv); + lua_pushinteger(L, rusage->ru_msgrcv); lua_setfield(L, -2, "msgrcv"); // signals received - lua_pushinteger(L, rusage.ru_nsignals); + lua_pushinteger(L, rusage->ru_nsignals); lua_setfield(L, -2, "nsignals"); // voluntary context switches - lua_pushinteger(L, rusage.ru_nvcsw); + lua_pushinteger(L, rusage->ru_nvcsw); lua_setfield(L, -2, "nvcsw"); // involuntary context switches - lua_pushinteger(L, rusage.ru_nivcsw); + lua_pushinteger(L, rusage->ru_nivcsw); lua_setfield(L, -2, "nivcsw"); +} + +static int luv_getrusage(lua_State* L) { + uv_rusage_t rusage; + int ret = uv_getrusage(&rusage); + if (ret < 0) return luv_error(L, ret); + luv_push_rusage_table(L, &rusage); + return 1; +} + +#if LUV_UV_VERSION_GEQ(1, 50, 0) +static int luv_getrusage_thread(lua_State *L) { + uv_rusage_t rusage; + int ret = uv_getrusage_thread(&rusage); + if (ret < 0) return luv_error(L, ret); + luv_push_rusage_table(L, &rusage); return 1; } +#endif #if LUV_UV_VERSION_GEQ(1, 44, 0) static int luv_available_parallelism(lua_State* L) { diff --git a/src/thread.c b/src/thread.c index 3c21364a..89d7670a 100644 --- a/src/thread.c +++ b/src/thread.c @@ -492,6 +492,32 @@ static int luv_thread_setpriority(lua_State* L) { } #endif +#if LUV_UV_VERSION_GEQ(1, 50, 0) +static int luv_thread_detach(lua_State *L) { + luv_thread_t* tid = luv_check_thread(L, 1); + int ret = uv_thread_detach(&tid->handle); + if (ret < 0) return luv_error(L, ret); + tid->handle = 0; + lua_pushboolean(L, 1); + return 1; +} + +static int luv_thread_getname(lua_State *L) { + luv_thread_t* tid = luv_check_thread(L, 1); + char name[MAX_THREAD_NAME_LENGTH]; + int ret = uv_thread_getname(&tid->handle, name, MAX_THREAD_NAME_LENGTH); + if (ret < 0) return luv_error(L, ret); + lua_pushstring(L, name); + return 1; +} + +static int luv_thread_setname(lua_State *L) { + const char* name = luaL_checkstring(L, 1); + int ret = uv_thread_setname(name); + return luv_result(L, ret); +} +#endif + static int luv_thread_join(lua_State* L) { luv_thread_t* tid = luv_check_thread(L, 1); int ret = uv_thread_join(&tid->handle); @@ -532,6 +558,10 @@ static const luaL_Reg luv_thread_methods[] = { #if LUV_UV_VERSION_GEQ(1, 48, 0) {"getpriority", luv_thread_getpriority}, {"setpriority", luv_thread_setpriority}, +#endif +#if LUV_UV_VERSION_GEQ(1, 50, 0) + {"setname", luv_thread_setname}, + {"detach", luv_thread_detach}, #endif {NULL, NULL} }; diff --git a/src/udp.c b/src/udp.c index d13372ff..bff9bfc4 100644 --- a/src/udp.c +++ b/src/udp.c @@ -324,6 +324,74 @@ static int luv_udp_try_send(lua_State* L) { return 1; } +#if LUV_UV_VERSION_GEQ(1, 50, 0) +static int luv_udp_try_send2(lua_State* L) { + uv_udp_t* handle = luv_check_udp(L, 1); + int err_or_num_datagrams_sent; + unsigned int num_msgs; + // to-be-allocated with the length of num_msgs + struct sockaddr_storage* addrs; + struct sockaddr** addr_ptrs; + unsigned int* counts; + uv_buf_t** bufs; + unsigned int flags = 0; + + luaL_checktype(L, 2, LUA_TTABLE); + num_msgs = lua_rawlen(L, 2); + + // flags param can be nil, an integer, or a table + if (lua_type(L, 3) == LUA_TNUMBER || lua_isnoneornil(L, 3)) { + flags = (unsigned int)luaL_optinteger(L, 3, 0); + } + else if (lua_type(L, 3) == LUA_TTABLE) { + // this is for forwards-compatibility: if flags ever get added, + // we want to be able to take a table + } + else { + return luaL_argerror(L, 3, "expected nil, integer, or table"); + } + + addrs = malloc(sizeof(struct sockaddr_storage) * num_msgs); + addr_ptrs = malloc(sizeof(struct sockaddr_storage*) * num_msgs); + counts = malloc(sizeof(unsigned int) * num_msgs); + bufs = malloc(sizeof(uv_buf_t*) * num_msgs); + for (unsigned int i=0; i UINT_MAX) + return luaL_error(L, "data at index %d contains too many bufs (max is %d)", UINT_MAX); + counts[i] = count; + lua_pop(L, 1); + lua_getfield(L, element_index, "addr"); + int addr_index = lua_gettop(L); + if (!lua_isnoneornil(L, addr_index)) { + lua_getfield(L, addr_index, "ip"); + lua_getfield(L, addr_index, "port"); + addr_ptrs[i] = luv_check_addr(L, &addrs[i], -2, -1); + lua_pop(L, 4); // ip, port, addr, and current array element + } + else { + addr_ptrs[i] = NULL; + lua_pop(L, 2); // addr and current array element + } + } + err_or_num_datagrams_sent = uv_udp_try_send2(handle, num_msgs, bufs, counts, addr_ptrs, flags); + free(addrs); + free(addr_ptrs); + free(counts); + for (unsigned int i=0; idata; lua_State* L = data->ctx->L; diff --git a/tests/test-misc.lua b/tests/test-misc.lua index 890e51ef..6e5b2e60 100644 --- a/tests/test-misc.lua +++ b/tests/test-misc.lua @@ -45,6 +45,11 @@ return require('lib/tap')(function (test) p(rusage) end) + test("uv.getrusage_thread", function (print, p, expect, uv) + local rusage = assert(uv.getrusage_thread()) + p(rusage) + end, "1.50.0") + test("uv.available_parallelism", function (print, p, expect, uv) local available_parallelism = assert(uv.available_parallelism()) p(available_parallelism) diff --git a/tests/test-thread.lua b/tests/test-thread.lua index 3aed8899..a18566f5 100644 --- a/tests/test-thread.lua +++ b/tests/test-thread.lua @@ -182,4 +182,24 @@ return require('lib/tap')(function (test) print('priority after change', priority) thread:join() end, "1.48.0") + + test("getname, setname", function(_, p, _, uv) + local thread = uv.new_thread(function() + local _uv = require('luv') + + local self = _uv.thread_self() + local name = "abc" + assert(_uv.thread_setname(name)) + local new_name = _uv.thread_getname(self) + assert(new_name == name, 'unexpected name: '..tostring(new_name)) + end) + thread:join() + end, "1.50.0") + + test("detach", function(_, p, _, uv) + local thread = uv.new_thread(function(...) + print(table.concat({...}, ' ') .. ' from detached thread') + end, 'hello', 'world') + thread:detach() + end, "1.50.0") end) diff --git a/tests/test-udp.lua b/tests/test-udp.lua index 6a564b38..1f170735 100644 --- a/tests/test-udp.lua +++ b/tests/test-udp.lua @@ -344,4 +344,81 @@ return require('lib/tap')(function (test) assert(sender:try_send("PING", "127.0.0.1", TEST_PORT)) end end, "1.39.0") + + local function udp_try_send2_test(should_connect) + return function(print, p, expect, uv) + -- If udp_connect is called on the sender, then addr cannot be specified in any messages. + -- Otherwise, it *must* be specified in every message. + local send_addr = { ip = "127.0.0.1", port = TEST_PORT } + local msgs_to_send = { + { data = "PING", addr = send_addr }, + { data = "PING", addr = send_addr }, + { data = "PING", addr = send_addr }, + -- Add in a message that goes to a different address to ensure that + -- messages having a different address are allowed (when not connected) + { data = "PING", addr = { ip = "127.0.0.1", port = TEST_PORT + 1 } }, + { data = { "P", "I", "N", "G" }, addr = send_addr }, + } + local expected_msgs = #msgs_to_send + if not should_connect then + expected_msgs = expected_msgs - 1 + end + -- Remove addr from the messages if we are calling udp_connect + if should_connect then + for _,v in ipairs(msgs_to_send) do + v.addr = nil + end + end + + local recver = uv.new_udp() + assert(recver:bind("0.0.0.0", TEST_PORT)) + + local sender = uv.new_udp() + if should_connect then + assert(uv.udp_connect(sender, send_addr.ip, send_addr.port)) + else + -- bind must be called; try_send2 doesn't automatically bind + assert(sender:bind("0.0.0.0", TEST_PORT+2)) + end + + local msgs_recved = 0 + local recv_cb = function(err, data, addr, flags) + assert(not err, err) + p(data, addr) + + -- empty callback can happen, just return early + if data == nil and addr == nil then + return + end + + assert(addr) + assert(data == "PING") + + msgs_recved = msgs_recved + 1 + if msgs_recved == expected_msgs then + sender:close() + recver:close() + end + end + + assert(recver:recv_start(recv_cb)) + while #msgs_to_send > 0 do + local num_sent = assert(sender:try_send2(msgs_to_send)) + print('sent '..num_sent..' out of '..#msgs_to_send..', expecting '..expected_msgs..' to be received') + for _=1,num_sent do + table.remove(msgs_to_send, 1) + end + end + end + end + + test("udp try_send2 not connected", function(print, p, expect, uv) + local testfn = udp_try_send2_test(false) + return testfn(print, p, expect, uv) + end, "1.50.0") + + test("udp try_send2 connected", function(print, p, expect, uv) + local testfn = udp_try_send2_test(true) + return testfn(print, p, expect, uv) + end, "1.50.0") end)