Skip to content
Draft
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
6 changes: 5 additions & 1 deletion lute/cli/src/climain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,11 @@ bool runBytecode(

lua_pop(GL, 1);

return runtime.runToCompletion();
bool success = runtime.runToCompletion();
if (success)
waitForSpawnedRuntimes();

return success;
}

static bool runFile(Runtime& runtime, const char* name, lua_State* GL, int program_argc, char** program_argv, LuteReporter& reporter)
Expand Down
30 changes: 19 additions & 11 deletions lute/fs/src/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ int open(lua_State* L)
int fs_remove(lua_State* L)
{
uv_fs_t unlink_req;
int err = uv_fs_unlink(uv_default_loop(), &unlink_req, luaL_checkstring(L, 1), nullptr);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably don't want to re-interpret cast here - this should just be a uv_loop_t.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ditto for all the places below.

int err = uv_fs_unlink(loop, &unlink_req, luaL_checkstring(L, 1), nullptr);
uv_fs_req_cleanup(&unlink_req);

if (err)
Expand All @@ -248,7 +249,8 @@ int fs_mkdir(lua_State* L)
int mode = luaL_optinteger(L, 2, 0777);

uv_fs_t req;
int err = uv_fs_mkdir(uv_default_loop(), &req, path, mode, nullptr);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
int err = uv_fs_mkdir(loop, &req, path, mode, nullptr);
uv_fs_req_cleanup(&req);

if (err)
Expand All @@ -262,7 +264,8 @@ int fs_rmdir(lua_State* L)
const char* path = luaL_checkstring(L, 1);

uv_fs_t rmdir_req;
int err = uv_fs_rmdir(uv_default_loop(), &rmdir_req, path, nullptr);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
int err = uv_fs_rmdir(loop, &rmdir_req, path, nullptr);
uv_fs_req_cleanup(&rmdir_req);

if (err)
Expand All @@ -276,7 +279,8 @@ int fs_stat(lua_State* L)
const char* path = luaL_checkstring(L, 1);

uv_fs_t stat_req;
int err = uv_fs_stat(uv_default_loop(), &stat_req, path, nullptr);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
int err = uv_fs_stat(loop, &stat_req, path, nullptr);

if (err)
{
Expand Down Expand Up @@ -353,7 +357,8 @@ int fs_copy(lua_State* L)
auto* req = new uv_fs_t();
req->data = new ResumeToken(getResumeToken(L));

int err = uv_fs_copyfile(uv_default_loop(), req, path, dest, 0, defaultCallback);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
int err = uv_fs_copyfile(loop, req, path, dest, 0, defaultCallback);

if (err)
{
Expand All @@ -373,7 +378,8 @@ int fs_link(lua_State* L)
auto* req = new uv_fs_t();
req->data = new ResumeToken(getResumeToken(L));

int err = uv_fs_link(uv_default_loop(), req, path, dest, defaultCallback);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
int err = uv_fs_link(loop, req, path, dest, defaultCallback);

if (err)
{
Expand Down Expand Up @@ -402,7 +408,8 @@ int fs_symlink(lua_State* L)
req->flags = 0;
}

int err = uv_fs_symlink(uv_default_loop(), req, path, dest, req->flags, defaultCallback);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
int err = uv_fs_symlink(loop, req, path, dest, req->flags, defaultCallback);

if (err)
{
Expand Down Expand Up @@ -474,7 +481,8 @@ int fs_watch(lua_State* L)
event->callbackReference = std::make_shared<Ref>(L, 2);
event->handle.data = event;

int init_err = uv_fs_event_init(uv_default_loop(), &event->handle);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
int init_err = uv_fs_event_init(loop, &event->handle);

if (init_err)
{
Expand Down Expand Up @@ -539,7 +547,7 @@ int fs_exists(lua_State* L)
req->data = new ResumeToken(getResumeToken(L));

int err = uv_fs_access(
uv_default_loop(),
reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop()),
req,
path,
F_OK,
Expand Down Expand Up @@ -578,7 +586,7 @@ int type(lua_State* L)

uv_fs_t req;

int err = uv_fs_stat(uv_default_loop(), &req, path, nullptr);
int err = uv_fs_stat(reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop()), &req, path, nullptr);

if (err)
{
Expand All @@ -601,7 +609,7 @@ int listdir(lua_State* L)
req->data = new ResumeToken(getResumeToken(L));

int err = uv_fs_scandir(
uv_default_loop(),
reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop()),
req,
path,
0,
Expand Down
19 changes: 13 additions & 6 deletions lute/fs/src/fs_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct FSRead : FSRequest
: FSRequest(L)
, file(file)
{
loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
chunk.resize(kChunkIOSize);
iov = uv_buf_init(chunk.data(), chunk.size());
buffer.reserve(kChunkIOSize);
Expand All @@ -28,6 +29,7 @@ struct FSRead : FSRequest
static void readCallback(uv_fs_t* req);

UVFile* file = nullptr;
uv_loop_t* loop = nullptr;
std::vector<char> buffer;
std::vector<char> chunk;
uv_buf_t iov;
Expand All @@ -41,12 +43,14 @@ struct FSWrite : FSRequest
, toWrite(buf, buf + len)
, offset(0)
{
loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
chunk.resize(kChunkIOSize);
}

static void writeCallback(uv_fs_t* req);

UVFile* file = nullptr;
uv_loop_t* loop = nullptr;
std::vector<char> chunk;
uv_buf_t iov;
std::vector<char> toWrite;
Expand All @@ -59,6 +63,7 @@ struct FSClose : FSRequest
: FSRequest(L)
, file(file)
{
loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
}

~FSClose()
Expand All @@ -67,13 +72,15 @@ struct FSClose : FSRequest
}

UVFile* file = nullptr;
uv_loop_t* loop = nullptr;
};

int open_impl(lua_State* L, const char* path, int flags, int mode)
{
uvutils::ScopedUVRequest<FSRequest> req(L);
uv_loop_t* loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
uv_fs_open(
uv_default_loop(),
loop,
&req->req,
path,
flags,
Expand Down Expand Up @@ -134,7 +141,7 @@ void FSRead::readCallback(uv_fs_t* req)
std::fill(r->chunk.begin(), r->chunk.end(), 0);

uvutils::ScopedUVRequest<FSRead> scopedReq{std::move(r)};
uv_fs_read(uv_default_loop(), &scopedReq->req, scopedReq->file->fd.value(), &scopedReq->iov, 1, -1, FSRead::readCallback);
uv_fs_read(scopedReq->loop, &scopedReq->req, scopedReq->file->fd.value(), &scopedReq->iov, 1, -1, FSRead::readCallback);
}

void FSWrite::writeCallback(uv_fs_t* req)
Expand Down Expand Up @@ -167,7 +174,7 @@ void FSWrite::writeCallback(uv_fs_t* req)
w->iov = uv_buf_init(w->chunk.data(), chunkSize);

uvutils::ScopedUVRequest<FSWrite> scopedReq{std::move(w)};
uv_fs_write(uv_default_loop(), &scopedReq->req, scopedReq->file->fd.value(), &scopedReq->iov, 1, -1, FSWrite::writeCallback);
uv_fs_write(scopedReq->loop, &scopedReq->req, scopedReq->file->fd.value(), &scopedReq->iov, 1, -1, FSWrite::writeCallback);
}

int read_impl(lua_State* L, UVFile* handle)
Expand All @@ -178,7 +185,7 @@ int read_impl(lua_State* L, UVFile* handle)
}

uvutils::ScopedUVRequest<FSRead> req{L, handle};
uv_fs_read(uv_default_loop(), &req->req, handle->fd.value(), &req->iov, 1, -1, FSRead::readCallback);
uv_fs_read(req->loop, &req->req, handle->fd.value(), &req->iov, 1, -1, FSRead::readCallback);
// Automatically releases when req goes out of scope
return lua_yield(L, 0);
}
Expand All @@ -197,7 +204,7 @@ int write_impl(lua_State* L, UVFile* handle, const char* toWrite, size_t numByte
std::copy(req->toWrite.begin(), req->toWrite.begin() + chunkSize, req->chunk.begin());
req->iov = uv_buf_init(req->chunk.data(), chunkSize);

uv_fs_write(uv_default_loop(), &req->req, handle->fd.value(), &req->iov, 1, -1, FSWrite::writeCallback);
uv_fs_write(req->loop, &req->req, handle->fd.value(), &req->iov, 1, -1, FSWrite::writeCallback);

return lua_yield(L, 0);
}
Expand All @@ -211,7 +218,7 @@ int close_impl(lua_State* L, UVFile* handle)

uvutils::ScopedUVRequest<FSClose> req{L, handle};
uv_fs_close(
uv_default_loop(),
req->loop,
&req->req,
handle->fd.value(),
[](uv_fs_t* req)
Expand Down
2 changes: 1 addition & 1 deletion lute/io/src/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ static void onTtyRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
int read(lua_State* L)
{
auto handle = std::make_shared<IOHandle>();
handle->loop = uv_default_loop();
handle->loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
handle->resumeToken = getResumeToken(L);
handle->self = handle;

Expand Down
20 changes: 15 additions & 5 deletions lute/net/src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "uv.h"

#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
Expand Down Expand Up @@ -227,6 +228,7 @@ static const int kEmptyServerKey = 0;
static Luau::DenseHashMap<int, uWSApp> serverInstances(kEmptyServerKey);
static Luau::DenseHashMap<int, std::shared_ptr<struct ServerLoopState>> serverStates(kEmptyServerKey);
static int nextServerId = 1;
static std::mutex serverMutex;

struct ServerLoopState
{
Expand Down Expand Up @@ -500,6 +502,8 @@ void setupAppAndListen(auto* app, std::shared_ptr<ServerLoopState> state, bool&

bool closeServer(int serverId)
{
std::scoped_lock lock(serverMutex);

if (!serverInstances.contains(serverId) || !serverStates.contains(serverId))
{
return false;
Expand Down Expand Up @@ -530,8 +534,6 @@ bool closeServer(int serverId)

int lua_serve(lua_State* L)
{
uWS::Loop::get(uv_default_loop());

std::string hostname = "127.0.0.1";
int port = 3000;
bool reusePort = false;
Expand Down Expand Up @@ -618,8 +620,13 @@ int lua_serve(lua_State* L)
}

Runtime* runtime = getRuntime(L);
uWS::Loop::get(runtime->getUvLoop());

int serverId = nextServerId++;
int serverId = 0;
{
std::scoped_lock lock(serverMutex);
serverId = nextServerId++;
}

auto state = std::make_shared<ServerLoopState>();
state->runtime = runtime;
Expand Down Expand Up @@ -655,8 +662,11 @@ int lua_serve(lua_State* L)
return 0;
}

serverInstances[serverId] = std::move(app);
serverStates[serverId] = state;
{
std::scoped_lock lock(serverMutex);
serverInstances[serverId] = std::move(app);
serverStates[serverId] = state;
}

lua_createtable(L, 0, 3);

Expand Down
2 changes: 1 addition & 1 deletion lute/process/src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ const std::string kStdioKindNone = "none";
int executionHelper(lua_State* L, std::vector<std::string> args, ProcessOptions opts)
{
auto handle = std::make_shared<ProcessHandle>();
handle->loop = uv_default_loop();
handle->loop = reinterpret_cast<uv_loop_t*>(getRuntime(L)->getUvLoop());
handle->self = handle;

uv_process_options_t options = {};
Expand Down
13 changes: 13 additions & 0 deletions lute/runtime/include/lute/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <vector>

struct lua_State;
struct uv_loop_s;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an internal libuv type - you'll want to store uv_loop_t


struct ThreadToContinue
{
Expand Down Expand Up @@ -44,6 +45,9 @@ struct Runtime
Runtime();
~Runtime();

bool useDedicatedUvLoop();
uv_loop_s* getUvLoop() const;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uv_loop_s* getUvLoop() const;
uv_loop_t* getUVLoop() const;


bool runToCompletion();
RuntimeStep runOnce();

Expand Down Expand Up @@ -77,6 +81,9 @@ struct Runtime
// Shorthand for global state
lua_State* GL = nullptr;

// Event loop for this runtime; defaults to `uv_default_loop()`, but can be dedicated via `useDedicatedUvLoop`.
uv_loop_s* uvLoop = nullptr;
Comment on lines +84 to +85
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a lot of complexity. Instead of defaulting, just initialize a uv_loop manually for this runtime.


std::mutex dataCopyMutex;
std::unique_ptr<lua_State, void (*)(lua_State*)> dataCopy;

Expand All @@ -92,6 +99,8 @@ struct Runtime
std::thread runLoopThread;

std::atomic<int> activeTokens;

bool ownsUvLoop = false;
Copy link
Collaborator

@Vighnesh-V Vighnesh-V Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do one runtime, one loop - see my comment below, since we might not actually need this?

};

Runtime* getRuntime(lua_State* L);
Expand All @@ -114,3 +123,7 @@ struct ResumeTokenData
ResumeToken getResumeToken(lua_State* L);

lua_State* setupState(Runtime& runtime, std::function<void(lua_State*)> doBeforeSandbox);

// Track child runtimes created via `@lute/vm` so the CLI can stay alive when they have work (e.g. servers).
void registerSpawnedRuntime(const std::shared_ptr<Runtime>& runtime);
void waitForSpawnedRuntimes();
Loading