move process/timer io objects into seperate TUs

This commit is contained in:
Lily Tsuru 2024-02-01 23:06:19 -05:00
parent 833f2054a4
commit 507e0c88a8
7 changed files with 227 additions and 162 deletions

View File

@ -14,9 +14,11 @@ include(CompilerFlags)
add_executable(nanosm add_executable(nanosm
src/main.cpp src/main.cpp
src/WordExp.cpp # glue code
src/EventLoop.cpp src/EventLoop.cpp
src/Timer.cpp
src/WordExp.cpp
src/Process.cpp
) )
nanosm_target(nanosm) nanosm_target(nanosm)

View File

@ -1,3 +1,5 @@
#pragma once
#include <sys/epoll.h> #include <sys/epoll.h>
#include <deque> #include <deque>

94
src/Process.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "Process.hpp"
#include <linux/sched.h>
#include <sched.h>
#include <signal.h>
#include <sys/types.h>
#include "WordExp.hpp"
namespace nanosm {
inline static pid_t Clone3(const clone_args& args) {
return syscall(SYS_clone3, &args, sizeof(clone_args));
}
Process::~Process() {
Kill();
Reset();
}
int Process::GetFD() const {
return pidFd;
}
int Process::InterestedEvents() const {
/// Pidfd only returns EPOLLIN.
return EPOLLIN;
}
void Process::Spawn(const std::string& commandLine) {
commLine = commandLine;
Respawn();
}
void Process::Respawn() {
pid = Clone3({
.flags = CLONE_PIDFD,
.pidfd = std::bit_cast<u64>(&pidFd),
.exit_signal = SIGCHLD,
});
if(pid < 0)
perror("Clone3()");
if(pid == 0) {
// Forked from the parent successfully, execute the given thing
auto exp = nanosm::WordExp::Expand(commLine);
std::vector<char*> argv(exp.words.size());
for(usize i = 0; i < exp.words.size(); ++i)
argv[i] = exp.words[i].data();
execvp(exp.words[0].data(), argv.data());
} else {
// Parent: monitor FD
eventLoop.AddObject(IoObject::Ptr(shared_from_this()));
}
}
void Process::OnReady(int eventMask) {
// In our case, any readiness signaled by the pidfd means the process exited
// so this will never block (or really, wait).
waitid(P_PIDFD, pidFd, &siginfo, WNOHANG);
// Post a callback to call the user's exit callback (if one exists)
eventLoop.Post([self = shared_from_this()]() {
// Prepare for re-attaching, or etc.
self->eventLoop.RemoveObject(self);
self->Reset();
if(self->onProcessExit)
self->onProcessExit(self->siginfo.si_status);
});
}
void Process::Kill() {
if(pid != -1)
kill(pid, SIGTERM);
}
void Process::SetExitCallback(std::function<void(int)> f) {
onProcessExit = f;
}
void Process::Reset() {
if(pidFd != -1) {
close(pidFd);
pidFd = -1;
}
pid = -1;
siginfo = {};
}
} // namespace nanosm

41
src/Process.hpp Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <sys/types.h>
#include <sys/wait.h>
#include "EventLoop.hpp"
namespace nanosm {
struct Process : nanosm::EventLoop::IoObject, std::enable_shared_from_this<Process> {
// bring ctor in
using nanosm::EventLoop::IoObject::IoObject;
virtual ~Process();
int GetFD() const override;
int InterestedEvents() const override;
void Spawn(const std::string& commandLine);
void Respawn();
void OnReady(int eventMask) override;
void Kill();
void SetExitCallback(std::function<void(int)> f);
private:
void Reset();
int pidFd { -1 };
pid_t pid { -1 };
siginfo_t siginfo {};
std::string commLine;
std::function<void(int)> onProcessExit;
};
} // namespace nanosm

55
src/Timer.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "Timer.hpp"
#include <sys/timerfd.h>
namespace nanosm {
Timer::Timer(nanosm::EventLoop& ev)
: nanosm::EventLoop::IoObject(ev) {
timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if(timerFd < 0)
perror("timerfd_create");
}
Timer::~Timer() {
Reset();
}
void Timer::Reset() {
if(timerFd != -1)
close(timerFd);
}
int Timer::GetFD() const {
return timerFd;
}
int Timer::InterestedEvents() const {
/// TimerFD only returns EPOLLIN.
return EPOLLIN;
}
void Timer::OnReady(int eventMask) {
u64 expiryCount {};
// Read the amount of timer expires so it stops screaming.
static_cast<void>(read(timerFd, &expiryCount, sizeof(expiryCount)));
// Post the expiry callback directly into the event loop
if(cb)
eventLoop.Post(cb);
}
void Timer::SetExpiryCallback(std::function<void()> expire) {
cb = expire;
}
void Timer::Arm(u32 durationSeconds) {
itimerspec spec {};
spec.it_value.tv_sec = durationSeconds;
// TODO: validate.
timerfd_settime(timerFd, 0, &spec, nullptr);
}
} // namespace nanosm

25
src/Timer.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "EventLoop.hpp"
namespace nanosm {
struct Timer : nanosm::EventLoop::IoObject, std::enable_shared_from_this<Timer> {
Timer(nanosm::EventLoop& ev);
virtual ~Timer();
void Reset();
int GetFD() const override;
int InterestedEvents() const override;
void OnReady(int eventMask) override;
void SetExpiryCallback(std::function<void()> expire);
void Arm(u32 durationSeconds);
private:
int timerFd { -1 };
std::function<void()> cb;
};
} // namespace nanosm

View File

@ -1,4 +1,4 @@
#include <bits/types/struct_itimerspec.h>
#include <ctime> #include <ctime>
#include <memory> #include <memory>
@ -6,171 +6,17 @@
#include "EventLoop.hpp" #include "EventLoop.hpp"
#include "WordExp.hpp" #include "WordExp.hpp"
#include "Timer.hpp"
#include "Process.hpp"
nanosm::EventLoop ev; nanosm::EventLoop ev;
#include <linux/sched.h>
#include <sched.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
struct TestProcess : nanosm::EventLoop::IoObject, std::enable_shared_from_this<TestProcess> {
// bring ctor in
using nanosm::EventLoop::IoObject::IoObject;
virtual ~TestProcess() {
Kill();
Reset();
}
int GetFD() const override {
return pidfdFd;
}
int InterestedEvents() const override {
/// Pidfd only returns EPOLLIN.
return EPOLLIN;
}
void Spawn(const std::string& commandLine) {
commLine = commandLine;
Respawn();
}
void Respawn() {
pid = Clone3({
.flags = CLONE_PIDFD,
.pidfd = std::bit_cast<u64>(&pidfdFd),
.exit_signal = SIGCHLD,
});
if(pid < 0)
perror("Error cloning");
if(pid == 0) {
// Forked from the parent successfully, execute the given thing
auto exp = nanosm::WordExp::Expand(commLine);
std::vector<char*> argv(exp.words.size());
for(usize i = 0; i < exp.words.size(); ++i)
argv[i] = exp.words[i].data();
execvp(exp.words[0].data(), argv.data());
} else {
// Parent: monitor FD
eventLoop.AddObject(IoObject::Ptr(shared_from_this()));
}
}
void OnReady(int eventMask) override {
// In our case, any readiness signaled by the pidfd means the process exited
// so this will never block (or really, wait).
waitid(P_PIDFD, pidfdFd, &siginfo, WNOHANG);
// Post a callback to call the user's
eventLoop.Post([self = shared_from_this()]() {
// Prepare for re-attaching, or etc.
self->eventLoop.RemoveObject(self);
self->Reset();
if(self->onProcessExit)
self->onProcessExit(self->siginfo.si_status);
});
}
void Kill() {
if(pid != -1)
kill(pid, SIGTERM);
}
void SetExitCallback(std::function<void(int)> f) {
onProcessExit = f;
}
private:
static pid_t Clone3(const clone_args& args) {
return syscall(SYS_clone3, &args, sizeof(clone_args));
}
void Reset() {
if(pidfdFd != -1) {
close(pidfdFd);
pidfdFd = -1;
}
pid = -1;
siginfo = {};
}
int pidfdFd { -1 };
pid_t pid { -1 };
siginfo_t siginfo {};
std::string commLine;
std::function<void(int)> onProcessExit;
};
#include <sys/timerfd.h>
struct TestTimer : nanosm::EventLoop::IoObject, std::enable_shared_from_this<TestTimer> {
TestTimer(nanosm::EventLoop& ev)
: nanosm::EventLoop::IoObject(ev) {
timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if(timerFd < 0)
perror("timerfd_create");
}
virtual ~TestTimer() {
Reset();
}
void Reset() {
if(timerFd != -1)
close(timerFd);
}
int GetFD() const override {
return timerFd;
}
int InterestedEvents() const override {
/// TimerFD only returns EPOLLIN.
return EPOLLIN;
}
void OnReady(int eventMask) override {
u64 expiryCount {};
// Read the amount of timer expires so it stops screaming.
read(timerFd, &expiryCount, sizeof(expiryCount));
// Post the expiry callback directly into the event loop
if(cb)
eventLoop.Post(cb);
}
void SetExpiryCallback(std::function<void()> expire) {
cb = expire;
}
void Arm(u32 durationSeconds) {
itimerspec spec {};
spec.it_value.tv_sec = durationSeconds;
// TODO: validate.
timerfd_settime(timerFd, 0, &spec, nullptr);
}
private:
int timerFd { -1 };
std::function<void()> cb;
};
// tests stuff :) // tests stuff :)
auto timer = std::make_shared<TestTimer>(ev); auto timer = std::make_shared<nanosm::Timer>(ev);
auto process = std::make_shared<nanosm::Process>(ev);
void test() { void test() {
auto process = std::make_shared<TestProcess>(ev);
// Do magic // Do magic
process->SetExitCallback([p = process](int exitCode) { process->SetExitCallback([p = process](int exitCode) {