Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add/skip remaining 1.45.0 bindings, fix appveyor CI #5

Merged
merged 4 commits into from
May 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .ci/bindcov.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
unbound_functions=0
skipped=()

# waiting on these being documented
# https://github.com/libuv/libuv/issues/4007
skipped+=(uv_os_get_passwd2 uv_os_get_group uv_os_free_group)

# false positives
skipped+=(uv_thread_create uv_thread_create_ex)

Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ for:
- luarocks remove luv
# Test the alternate rockspec
- mkdir build\lib
- cp build.luarocks\deps\libuv\Release\uv_a.lib build\lib\uv.lib
- cp build.luarocks\deps\libuv\Release\libuv.lib build\lib\uv.lib
- cp -a deps\libuv\include build
- ps: luarocks make rockspecs\$(ls rockspecs) LIBUV_DIR=build LUA_COMPAT53_INCDIR=deps/lua-compat-5.3/c-api CFLAGS="/nologo /MT /O2"
- ps: if("$(Get-Location)" -eq $(lua -e "print(require'luv'.cwd())")) { "LuaRocks test OK" } else { "LuaRocks test failed"; exit 1 }
Expand Down
88 changes: 88 additions & 0 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -3286,6 +3286,60 @@ equivalent to the `__eq` metamethod.

**Returns:** `boolean`

### `uv.thread_setaffinity(thread, affinity, [get_old_affinity])`

> method form `thread:setaffinity(affinity, [get_old_affinity])`

**Parameters:**
- `thread`: `luv_thread_t userdata`
- `affinity`: `table`
- `[1, 2, 3, ..., n]` : `boolean`
- `get_old_affinity`: `boolean`

Sets the specified thread's affinity setting. `affinity` must be an array-like
table where each of the keys correspond to a CPU number and the values are
booleans that represent whether the `thread` should be eligible to run on that
CPU. The length of the `affinity` table must be greater than or equal to
`uv.cpumask_size()`. If `get_old_affinity` is `true`, the previous affinity
settings for the `thread` will be returned. Otherwise, `true` is returned after
a successful call.

**Note:** Thread affinity setting is not atomic on Windows. Unsupported on macOS.

**Returns:** `table` or `boolean` or `fail`
- `[1, 2, 3, ..., n]` : `boolean`

### `uv.thread_getaffinity(thread, [mask_size])`

> method form `thread:getaffinity([mask_size])`

**Parameters:**
- `thread`: `luv_thread_t userdata`
- `mask_size`: `integer`

Gets the specified thread's affinity setting.

If `mask_size` is provided, it must be greater than or equal to
`uv.cpumask_size()`. If the `mask_size` parameter is omitted, then the return
of `uv.cpumask_size()` will be used. Returns an array-like table where each of
the keys correspond to a CPU number and the values are booleans that represent
whether the `thread` is eligible to run on that CPU.

**Note:** Thread affinity getting is not atomic on Windows. Unsupported on macOS.

**Returns:** `table` or `fail`
- `[1, 2, 3, ..., n]` : `boolean`

### `uv.thread_getcpu()`

Gets the CPU number on which the calling thread is running.

**Note:** The first CPU will be returned as the number 1, not 0. This allows for
the number to correspond with the table keys used in `uv.thread_getaffinity` and
`uv.thread_setaffinity`.

**Returns:** `integer` or `fail`

### `uv.thread_self()`

Returns the handle for the thread in which this is called.
Expand Down Expand Up @@ -3373,6 +3427,15 @@ greater than the total system memory.

**Returns:** `number`

### `uv.get_available_memory()`

Gets the amount of free memory that is still available to the process (in
bytes). This differs from `uv.get_free_memory()` in that it takes into account
any limits imposed by the OS. If there is no such constraint, or the constraint
is unknown, the amount returned will be identical to `uv.get_free_memory()`.

**Returns:** `number`

### `uv.resident_set_memory()`

Returns the resident set size (RSS) for the current process.
Expand Down Expand Up @@ -3433,6 +3496,13 @@ CPU found.
- `idle` : `number`
- `irq` : `number`

### `uv.cpumask_size()`

Returns the maximum size of the mask used for process/thread affinities, or
`ENOTSUP` if affinities are not supported on the current platform.

**Returns:** `integer` or `fail`

### `uv.getpid()`

**Deprecated:** Please use `uv.os_getpid()` instead.
Expand Down Expand Up @@ -3484,6 +3554,24 @@ time between intervals.

**Returns:** `number`

### `uv.clock_gettime(clock_id)`

**Parameters:**
- `clock_id`: `string`

Obtain the current system time from a high-resolution real-time or monotonic
clock source. `clock_id` can be the string `"monotonic"` or `"realtime"`.

The real-time clock counts from the UNIX epoch (1970-01-01) and is subject
to time adjustments; it can jump back in time.

The monotonic clock counts from an arbitrary point in the past and never
jumps back in time.

**Returns:** `table` or `fail`
- `sec`: `integer`
- `nsec`: `integer`

### `uv.uptime()`

Returns the current system uptime in seconds.
Expand Down
10 changes: 10 additions & 0 deletions src/luv.c
Original file line number Diff line number Diff line change
Expand Up @@ -372,12 +372,22 @@ static const luaL_Reg luv_functions[] = {
{"random", luv_random},
#endif
{"sleep", luv_sleep},
#if LUV_UV_VERSION_GEQ(1, 45, 0)
{"cpumask_size", luv_cpumask_size},
{"get_available_memory", luv_get_available_memory},
{"clock_gettime", luv_clock_gettime},
#endif

// thread.c
{"new_thread", luv_new_thread},
{"thread_equal", luv_thread_equal},
{"thread_self", luv_thread_self},
{"thread_join", luv_thread_join},
#if LUV_UV_VERSION_GEQ(1, 45, 0)
{"thread_getaffinity", luv_thread_getaffinity},
{"thread_setaffinity", luv_thread_setaffinity},
{"thread_getcpu", luv_thread_getcpu},
#endif

// work.c
{"new_work", luv_new_work},
Expand Down
32 changes: 32 additions & 0 deletions src/misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,3 +750,35 @@ static int luv_random(lua_State* L) {
}
}
#endif

#if LUV_UV_VERSION_GEQ(1, 45, 0)
static int luv_cpumask_size(lua_State* L) {
int ret = uv_cpumask_size();
if (ret < 0) return luv_error(L, ret);
lua_pushinteger(L, ret);
return 1;
}

static int luv_get_available_memory(lua_State* L) {
lua_pushinteger(L, uv_get_available_memory());
return 1;
}

// These are the same order as uv_membership which also starts at 0
static const char *const luv_clock_id_opts[] = {
"monotonic", "realtime", NULL
};

static int luv_clock_gettime(lua_State* L) {
uv_clock_id clock_id = (uv_clock_id)luaL_checkoption(L, 1, NULL, luv_clock_id_opts);
uv_timespec64_t timespec;
int ret = uv_clock_gettime(clock_id, &timespec);
if (ret < 0) return luv_error(L, ret);
lua_createtable(L, 0, 2);
lua_pushinteger(L, timespec.tv_sec);
lua_setfield(L, -2, "sec");
lua_pushinteger(L, timespec.tv_nsec);
lua_setfield(L, -2, "nsec");
return 1;
}
#endif
82 changes: 82 additions & 0 deletions src/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,84 @@ static int luv_new_thread(lua_State* L) {
return 1;
}

#if LUV_UV_VERSION_GEQ(1, 45, 0)
static int luv_thread_getaffinity(lua_State* L) {
luv_thread_t* tid = luv_check_thread(L, 1);
int default_mask_size = uv_cpumask_size();
if (default_mask_size < 0) {
return luv_error(L, default_mask_size);
}
int mask_size = luaL_optinteger(L, 2, default_mask_size);
if (mask_size < default_mask_size) {
return luaL_argerror(L, 2, lua_pushfstring(L, "cpumask size must be >= %d (from cpumask_size()), got %d", default_mask_size, mask_size));
}
char* cpumask = malloc(mask_size);
int ret = uv_thread_getaffinity(tid, cpumask, mask_size);
if (ret < 0) {
free(cpumask);
return luv_error(L, ret);
}
lua_newtable(L);
for (int i = 0; i < mask_size; i++) {
lua_pushboolean(L, cpumask[i]);
lua_rawseti(L, -2, i + 1);
}
free(cpumask);
return 1;
}

static int luv_thread_setaffinity(lua_State* L) {
luv_thread_t* tid = luv_check_thread(L, 1);
luaL_checktype(L, 2, LUA_TTABLE);
int get_old_mask = lua_toboolean(L, 3);
int min_mask_size = uv_cpumask_size();
if (min_mask_size < 0) {
return luv_error(L, min_mask_size);
}
int mask_size = lua_rawlen(L, 2);
if (mask_size < min_mask_size) {
return luaL_argerror(L, 2, lua_pushfstring(L, "cpumask size must be >= %d (from cpumask_size()), got %d", min_mask_size, mask_size));
}
char* cpumask = malloc(mask_size);
for (int i = 0; i < mask_size; i++) {
lua_rawgeti(L, 2, i+1);
int val = lua_toboolean(L, -1);
cpumask[i] = val;
lua_pop(L, 1);
}
char* oldmask = get_old_mask ? malloc(mask_size) : NULL;
int ret = uv_thread_setaffinity(tid, cpumask, oldmask, mask_size);
// Done with cpumask at this point
free(cpumask);
if (ret < 0) {
if (get_old_mask) free(oldmask);
return luv_error(L, ret);
}
if (get_old_mask) {
lua_newtable(L);
for (int i = 0; i < mask_size; i++) {
lua_pushboolean(L, oldmask[i]);
lua_rawseti(L, -2, i + 1);
}
free(oldmask);
}
else {
lua_pushboolean(L, 1);
}
return 1;
}

static int luv_thread_getcpu(lua_State* L) {
int ret = uv_thread_getcpu();
if (ret < 0) return luv_error(L, ret);
// Make the returned value start at 1 to match how getaffinity/setaffinity
// masks are implemented (they use array-like tables so the first
// CPU is index 1).
lua_pushinteger(L, ret + 1);
return 1;
}
#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);
Expand Down Expand Up @@ -414,6 +492,10 @@ static int luv_thread_equal(lua_State* L) {
static const luaL_Reg luv_thread_methods[] = {
{"equal", luv_thread_equal},
{"join", luv_thread_join},
#if LUV_UV_VERSION_GEQ(1, 45, 0)
{"getaffinity", luv_thread_getaffinity},
{"setaffinity", luv_thread_setaffinity},
#endif
{NULL, NULL}
};

Expand Down
33 changes: 32 additions & 1 deletion tests/test-misc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ return require('lib/tap')(function (test)
local constrained = nil
if uv.get_constrained_memory then
constrained = uv.get_constrained_memory()
assert(constrained >= 0)
end
local available = nil
if uv.get_available_memory then
available = uv.get_available_memory()
assert(available >= 0)
end
local free = uv.get_free_memory()
p{rss=rss,total=total,free=free, constrained=constrained}
p{rss=rss,total=total,free=free,available=available,constrained=constrained}
assert(rss < total)
end)

Expand Down Expand Up @@ -182,4 +188,29 @@ return require('lib/tap')(function (test)
end
end)

test("uv.cpumask_size", function(print, p, expect, uv)
-- The result can vary per-platform and is only supported on some platforms,
-- so just test that the function exists and behaves coherently.
local size, err = uv.cpumask_size()
p(size, err)
if err then
assert(not size)
else
assert(size >= 0)
end
end, "1.45.0")

test("uv.clock_gettime", function(print, p, expect, uv)
for _, clock_id in ipairs({"monotonic", "realtime"}) do
local timespec, err = uv.clock_gettime("monotonic")
p(clock_id, timespec, err)
if err then
assert(not timespec)
else
assert(timespec.sec >= 0)
assert(timespec.nsec >= 0)
end
end
end, "1.45.0")

end)
43 changes: 43 additions & 0 deletions tests/test-thread.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,47 @@ return require('lib/tap')(function (test)
collectgarbage('collect')
collectgarbage('collect')
end)

test("thread_getcpu", function(print, p, expect, uv)
local cpu, err = uv.thread_getcpu()
if not cpu then
print(err, "skipping")
return
end
-- starts at 1 to match the tables used by getaffinity/setaffinity
assert(cpu >= 1)
end, "1.45.0")

test("getaffinity, setaffinity", function(print, p, expect, uv)
local mask_size, err = uv.cpumask_size()
if not mask_size then
print(err, "skipping")
return
end
uv.new_thread(function(cpumask_size)
local _uv = require('luv')
local thread = _uv.thread_self()
local affinity = assert(thread:getaffinity())
assert(#affinity == cpumask_size)

-- set every cpu's affinity to false except the current cpu
local cur_cpu = _uv.thread_getcpu()
local affinity_to_set = {}
for i=1,cpumask_size do
affinity_to_set[i] = i == cur_cpu
end
local prev_affinity = assert(thread:setaffinity(affinity_to_set, true))
-- the returned affinity should match the original affinity
assert(#prev_affinity == #affinity)
for i=1,#affinity do
assert(prev_affinity[i] == affinity[i])
end

local new_affinity = thread:getaffinity()
assert(#new_affinity == #affinity_to_set)
for i=1,#new_affinity do
assert(new_affinity[i] == affinity_to_set[i])
end
end, mask_size):join()
end, "1.45.0")
end)