Actually implement event loop & process stuff
Pretty easy all things considered.
This commit is contained in:
parent
dd0280a1e5
commit
833f2054a4
|
@ -21,6 +21,7 @@ BinPackParameters: true
|
|||
BreakConstructorInitializers: BeforeColon
|
||||
BreakStringLiterals: false
|
||||
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: false
|
||||
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
project(nanosm
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||
|
||||
include(Policies)
|
||||
include(ProjectFuncs)
|
||||
include(CompilerFlags)
|
||||
|
||||
|
||||
add_executable(nanosm
|
||||
src/main.cpp
|
||||
|
||||
src/WordExp.cpp
|
||||
|
||||
src/EventLoop.cpp
|
||||
)
|
||||
|
||||
nanosm_target(nanosm)
|
||||
|
||||
# todo: cmake install sex
|
|
@ -0,0 +1,67 @@
|
|||
# TODO: This currently assumes libstdc++, later on we should *probably* set this with some detection
|
||||
# also TODO: Use a list so that this isn't one giant line (list JOIN should help.)
|
||||
set(NANOSM_CORE_COMPILE_ARGS "-Wall -Wformat=2 -Wimplicit-fallthrough -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -fstrict-flex-arrays=3 -fstack-clash-protection -fstack-protector-strong")
|
||||
set(NANOSM_CORE_LINKER_ARGS "-fuse-ld=${NANOSM_LINKER}")
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release") # OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo"
|
||||
# If on Release use link-time optimizations.
|
||||
# On clang we use ThinLTO for even better build performance.
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
set(NANOSM_CORE_COMPILE_ARGS "${NANOSM_CORE_COMPILE_ARGS} -flto=thin")
|
||||
set(NANOSM_CORE_LINKER_ARGS "${NANOSM_CORE_LINKER_ARGS} -flto=thin")
|
||||
else()
|
||||
set(NANOSM_CORE_COMPILE_ARGS "${NANOSM_CORE_COMPILE_ARGS} -flto")
|
||||
set(NANOSM_CORE_LINKER_ARGS "${NANOSM_CORE_LINKER_ARGS} -flto")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(_NANOSM_CORE_WANTED_SANITIZERS "")
|
||||
|
||||
if("asan" IN_LIST NANOSM_SANITIZERS)
|
||||
# Error if someone's trying to mix asan and tsan together since they aren't compatible.
|
||||
if("tsan" IN_LIST NANOSM_SANITIZERS)
|
||||
message(FATAL_ERROR "ASAN and TSAN cannot be used together.")
|
||||
endif()
|
||||
|
||||
message(STATUS "Enabling ASAN because it was in NANOSM_SANITIZERS")
|
||||
list(APPEND _NANOSM_CORE_WANTED_SANITIZERS "address")
|
||||
endif()
|
||||
|
||||
if("tsan" IN_LIST NANOSM_SANITIZERS)
|
||||
if("asan" IN_LIST NANOSM_SANITIZERS)
|
||||
message(FATAL_ERROR "ASAN and TSAN cannot be used together.")
|
||||
endif()
|
||||
|
||||
message(STATUS "Enabling TSAN because it was in NANOSM_SANITIZERS")
|
||||
list(APPEND _NANOSM_CORE_WANTED_SANITIZERS "thread")
|
||||
endif()
|
||||
|
||||
if("ubsan" IN_LIST NANOSM_SANITIZERS)
|
||||
message(STATUS "Enabling UBSAN because it was in NANOSM_SANITIZERS")
|
||||
list(APPEND _NANOSM_CORE_WANTED_SANITIZERS "undefined")
|
||||
endif()
|
||||
|
||||
list(LENGTH _NANOSM_CORE_WANTED_SANITIZERS _NANOSM_CORE_WANTED_SANITIZERS_LENGTH)
|
||||
if(NOT _NANOSM_CORE_WANTED_SANITIZERS_LENGTH EQUAL 0)
|
||||
list(JOIN _NANOSM_CORE_WANTED_SANITIZERS "," _NANOSM_CORE_WANTED_SANITIZERS_ARG)
|
||||
message(STATUS "Enabled sanitizers: ${_NANOSM_CORE_WANTED_SANITIZERS_ARG}")
|
||||
set(NANOSM_CORE_COMPILE_ARGS "${NANOSM_CORE_COMPILE_ARGS} -fsanitize=${_NANOSM_CORE_WANTED_SANITIZERS_ARG}")
|
||||
set(NANOSM_CORE_LINKER_ARGS "${NANOSM_CORE_LINKER_ARGS} -fsanitize=${_NANOSM_CORE_WANTED_SANITIZERS_ARG}")
|
||||
endif()
|
||||
|
||||
# Set core CMake toolchain variables so that they get applied to all projects.
|
||||
# A bit nasty, but /shrug, this way our third party libraries can be mostly sanitized/etc as well.
|
||||
# We do NOT do this for CMake compiler features however.
|
||||
|
||||
set(CMAKE_C_FLAGS "${NANOSM_CORE_COMPILE_ARGS}")
|
||||
set(CMAKE_CXX_FLAGS "${NANOSM_CORE_COMPILE_ARGS}")
|
||||
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -O0 -g3")
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS} -O3 -g3")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -O3")
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g3")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -O3 -g3")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O3")
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${NANOSM_CORE_LINKER_ARGS} -Wl,-z,noexecstack,-z,relro,-z,now")
|
|
@ -0,0 +1,9 @@
|
|||
# Core compile arguments used for the whole project
|
||||
#
|
||||
# This is the driver, we include compiler/platform specific files here
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
include(CompilerFlags-GNU)
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported (for now?) compiler ${CMAKE_CXX_COMPILER_ID}")
|
||||
endif()
|
|
@ -0,0 +1,22 @@
|
|||
# CMake policy configuration
|
||||
|
||||
# Macro to enable new CMake policy.
|
||||
# Makes this file a *LOT* shorter.
|
||||
macro (_new_cmake_policy policy)
|
||||
if(POLICY ${policy})
|
||||
#message(STATUS "Enabling new policy ${policy}")
|
||||
cmake_policy(SET ${policy} NEW)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
_new_cmake_policy(CMP0026) # CMake 3.0: Disallow use of the LOCATION property for build targets.
|
||||
_new_cmake_policy(CMP0042) # CMake 3.0+ (2.8.12): MacOS "@rpath" in target's install name
|
||||
_new_cmake_policy(CMP0046) # warn about non-existent dependencies
|
||||
_new_cmake_policy(CMP0048) # CMake 3.0+: project() command now maintains VERSION
|
||||
_new_cmake_policy(CMP0054) # CMake 3.1: Only interpret if() arguments as variables or keywords when unquoted.
|
||||
_new_cmake_policy(CMP0056) # try_compile() linker flags
|
||||
_new_cmake_policy(CMP0066) # CMake 3.7: try_compile(): use per-config flags, like CMAKE_CXX_FLAGS_RELEASE
|
||||
_new_cmake_policy(CMP0067) # CMake 3.8: try_compile(): honor language standard variables (like C++11)
|
||||
_new_cmake_policy(CMP0068) # CMake 3.9+: `RPATH` settings on macOS do not affect `install_name`.
|
||||
_new_cmake_policy(CMP0075) # CMake 3.12+: Include file check macros honor `CMAKE_REQUIRED_LIBRARIES`
|
||||
_new_cmake_policy(CMP0077) # CMake 3.13+: option() honors normal variables.
|
|
@ -0,0 +1,26 @@
|
|||
function(nanosm_target target)
|
||||
target_compile_definitions(${target} PRIVATE "$<$<CONFIG:DEBUG>:NANOSM_DEBUG>")
|
||||
target_compile_features(${target} PUBLIC cxx_std_20)
|
||||
target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endfunction()
|
||||
|
||||
function(nanosm_set_alternate_linker)
|
||||
find_program(LINKER_EXECUTABLE ld.${NANOSM_LINKER} ${NANOSM_LINKER})
|
||||
if(LINKER_EXECUTABLE)
|
||||
message(STATUS "Using ${NANOSM_LINKER} as argument to -fuse-ld=")
|
||||
else()
|
||||
message(FATAL_ERROR "Linker ${NANOSM_LINKER} does not exist on your system. Please specify one which does or omit this option from your configure command.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# set the default linker based on compiler id, if one is not provided
|
||||
# This is provided so that it can be overridden
|
||||
if(NOT NANOSM_LINKER AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
set(NANOSM_LINKER "lld")
|
||||
elseif(NOT NANOSM_LINKER)
|
||||
set(NANOSM_LINKER "bfd")
|
||||
endif()
|
||||
|
||||
nanosm_set_alternate_linker()
|
||||
|
||||
|
|
@ -2,17 +2,19 @@
|
|||
|
||||
[nanosm]
|
||||
# The window manager you want to use. This is the first application
|
||||
# launched, and this will always be true
|
||||
# launched
|
||||
window-manager="/path/to/wm/binary --any-additional-args-here"
|
||||
|
||||
# Enable verbose debug logging. Only useful for debugging issues.
|
||||
verbose=false
|
||||
|
||||
# Restart delay in seconds.
|
||||
# Restart delay in seconds.
|
||||
restart-delay=1
|
||||
|
||||
# Any applications besides your window manager you want to run at startup.
|
||||
# Note that applications are executed in the order they are declared.
|
||||
# Note that applications are executed in the order they are declared, but
|
||||
# this will not hold true if any (or all apps) crash (they will be restarted
|
||||
# effectively in a psuedorandom order).
|
||||
[nanosm.apps]
|
||||
lxpanel = { command = "lxpanel" }
|
||||
pcmanfm-desktop = { command = "pcmanfm --desktop" }
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
#include "EventLoop.hpp"
|
||||
|
||||
#include <sys/epoll.h>
|
||||
|
||||
namespace nanosm {
|
||||
|
||||
/// A little ergonomic wrapper over
|
||||
/// std::unique_ptr<T[]>, for a "kinda-vector"
|
||||
/// that lives on the heap and is statically sized
|
||||
template <class T>
|
||||
struct UniqueArray final {
|
||||
explicit UniqueArray(usize size)
|
||||
: array(std::make_unique<T[]>(size)),
|
||||
size(size) {
|
||||
}
|
||||
|
||||
UniqueArray(UniqueArray&& move) {
|
||||
array = std::move(move.array);
|
||||
size = move.size;
|
||||
|
||||
// invalidate
|
||||
move.array = nullptr;
|
||||
move.size = 0;
|
||||
}
|
||||
|
||||
T& operator[](usize index) { return Get()[index]; }
|
||||
const T& operator[](usize index) const { return Get()[index]; }
|
||||
|
||||
T* Get() { return array.get(); }
|
||||
const T* Get() const { return array.get(); }
|
||||
usize Size() const { return size; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<T[]> array {};
|
||||
usize size {};
|
||||
};
|
||||
|
||||
EventLoop::EventLoop() {
|
||||
epollFd = epoll_create1(EPOLL_CLOEXEC);
|
||||
|
||||
if(epollFd == -1) {
|
||||
perror("You Banned From Epoll, Rules");
|
||||
}
|
||||
}
|
||||
|
||||
EventLoop::~EventLoop() {
|
||||
close(epollFd);
|
||||
}
|
||||
|
||||
void EventLoop::Post(PostFn func) {
|
||||
if(!func)
|
||||
return;
|
||||
postCallbacks.push_back(func);
|
||||
}
|
||||
|
||||
void EventLoop::AddObject(IoObject::Ptr obj) {
|
||||
if(!obj)
|
||||
return;
|
||||
|
||||
if(obj->GetFD() == -1)
|
||||
return;
|
||||
|
||||
ioObjects.insert(obj);
|
||||
|
||||
epoll_event ev {};
|
||||
|
||||
ev.events = obj->InterestedEvents();
|
||||
ev.data.fd = obj->GetFD();
|
||||
|
||||
epoll_ctl(epollFd, EPOLL_CTL_ADD, obj->GetFD(), &ev);
|
||||
}
|
||||
|
||||
void EventLoop::RemoveObject(IoObject::Ptr obj) {
|
||||
if(!obj)
|
||||
return;
|
||||
|
||||
ioObjects.erase(obj);
|
||||
epoll_event ev {};
|
||||
|
||||
ev.events = obj->InterestedEvents();
|
||||
ev.data.fd = obj->GetFD();
|
||||
|
||||
epoll_ctl(epollFd, EPOLL_CTL_DEL, obj->GetFD(), &ev);
|
||||
}
|
||||
|
||||
void EventLoop::Run() {
|
||||
UniqueArray<epoll_event> events { 16 };
|
||||
|
||||
while(!shouldStop) {
|
||||
auto nevents = epoll_wait(epollFd, events.Get(), events.Size(), 10);
|
||||
if(nevents == -1) {
|
||||
perror("epoll_wait");
|
||||
break;
|
||||
}
|
||||
|
||||
// All OK, let's check for events now
|
||||
|
||||
for(int i = 0; i < nevents; ++i) {
|
||||
for(auto pollable : ioObjects) {
|
||||
if(auto fd = pollable->GetFD(); fd != -1) {
|
||||
// Signal any events that occur for this
|
||||
if(events[i].data.fd == fd)
|
||||
pollable->OnReady(events[i].events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the topmost callback once every event loop iteration.
|
||||
if(!postCallbacks.empty()) {
|
||||
auto& frontCallback = postCallbacks.front();
|
||||
frontCallback();
|
||||
postCallbacks.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoop::Stop() {
|
||||
shouldStop = true;
|
||||
}
|
||||
|
||||
} // namespace nanosm
|
|
@ -1,34 +1,68 @@
|
|||
#include <memory>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
#include "Types.hpp"
|
||||
|
||||
namespace nanosm {
|
||||
|
||||
/// The nanosm event loop. Pretty barren.
|
||||
/// The nanosm event loop. Not that barren, but basically
|
||||
/// just enough to have the program working.
|
||||
struct EventLoop {
|
||||
|
||||
/// A pollable object.
|
||||
struct Pollable {
|
||||
virtual ~Pollable() = default;
|
||||
|
||||
/// Returns the FD. May return nullopt if there is no active file descriptor
|
||||
/// for this polled object (this simply means you won't get events until there is one)
|
||||
virtual std::optional<int> GetFD() const = 0;
|
||||
using PostFn = std::function<void()>;
|
||||
|
||||
/// Called when the object is ready (do any i/o or handling here)
|
||||
virtual void OnReady() = 0;
|
||||
/// A IO object.
|
||||
struct IoObject {
|
||||
using Ptr = std::shared_ptr<IoObject>;
|
||||
|
||||
explicit IoObject(EventLoop& assoc)
|
||||
: eventLoop(assoc) {
|
||||
}
|
||||
|
||||
virtual ~IoObject() = default;
|
||||
|
||||
/// Returns the file descriptor for this IO object.
|
||||
virtual int GetFD() const = 0;
|
||||
|
||||
/// Return raw event mask
|
||||
virtual int InterestedEvents() const = 0;
|
||||
|
||||
/// Called when the object is ready (do any i/o or handling here)
|
||||
/// [eventMask] is the raw epoll event mask
|
||||
virtual void OnReady(int eventMask) = 0;
|
||||
|
||||
protected:
|
||||
EventLoop& eventLoop;
|
||||
};
|
||||
|
||||
EventLoop();
|
||||
~EventLoop();
|
||||
|
||||
/// Add an object to the epoll event loop.
|
||||
void AddObject(int fd, std::shared_ptr<Pollable> obj);
|
||||
void AddObject(IoObject::Ptr obj);
|
||||
|
||||
void RemoveObject(IoObject::Ptr obj);
|
||||
|
||||
/// Posts a function to run after the epoll events are dispatched.
|
||||
/// This queue of functions is mutually exclusive, so is also usable by
|
||||
/// I/O objects to post completion notifications/callbacks.
|
||||
void Post(PostFn func);
|
||||
|
||||
/// Runs the main loop.
|
||||
void Run();
|
||||
|
||||
private:
|
||||
int epollFd{};
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
int epollFd {};
|
||||
bool shouldStop { false };
|
||||
|
||||
std::set<std::shared_ptr<IoObject>> ioObjects {};
|
||||
std::deque<PostFn> postCallbacks {};
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace nanosm
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
using u8 = std::uint8_t;
|
||||
using i8 = std::int8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using i16 = std::int16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using i32 = std::int32_t;
|
||||
using u64 = std::uint64_t;
|
||||
using i64 = std::int64_t;
|
||||
using usize = std::size_t;
|
||||
using isize = std::intptr_t;
|
|
@ -0,0 +1,57 @@
|
|||
#include "WordExp.hpp"
|
||||
|
||||
#include "Types.hpp"
|
||||
|
||||
namespace nanosm {
|
||||
WordExp WordExp::Expand(std::string_view string) {
|
||||
WordExp res {};
|
||||
|
||||
usize startIndex {};
|
||||
usize endIndex {};
|
||||
bool inQuotes { false };
|
||||
|
||||
auto len = [&]() { return (endIndex - startIndex); };
|
||||
// auto currentWord = [&]() { return std::string(string.data() + startIndex, len()); };
|
||||
auto addWord = [&](auto nextIndex) {
|
||||
// a bit of a HACK. should be fixed properly, I think
|
||||
if(len() > 1)
|
||||
res.words.emplace_back(string.data() + startIndex, len());
|
||||
|
||||
startIndex = nextIndex;
|
||||
endIndex = startIndex;
|
||||
};
|
||||
|
||||
for(usize i = 0; i < string.length(); ++i) {
|
||||
switch(string[i]) {
|
||||
case '"':
|
||||
if(!inQuotes) {
|
||||
inQuotes = true;
|
||||
startIndex = i + 1;
|
||||
} else {
|
||||
inQuotes = false;
|
||||
addWord(i + 1);
|
||||
}
|
||||
|
||||
endIndex++;
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
// Spaces are allowed in quoted strings
|
||||
if(!inQuotes)
|
||||
addWord(i + 1);
|
||||
else
|
||||
endIndex++;
|
||||
break;
|
||||
|
||||
default: endIndex++; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last word
|
||||
if(len() != 0)
|
||||
addWord(0);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace nanosm
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace nanosm {
|
||||
|
||||
/// A kind-of reimplementation of wordexp(3) in C++, since it's rife with security issues,
|
||||
/// annoying, and very poorly implemented (one implementation actually
|
||||
/// ends up spawning a shell, that end up running Perl code. I'm not kidding.)
|
||||
struct WordExp {
|
||||
std::vector<std::string> words;
|
||||
|
||||
/// Expand a string (e.g "hello world \"testing 1234\"") into individual parts.
|
||||
/// This function also respects quotation marks.
|
||||
///
|
||||
/// This function does NOT:
|
||||
/// - Expand environment strings (e.g: $PWD or etc.)
|
||||
/// - Expand shell ~~injection~~ strings (eg `uname -r`)
|
||||
///
|
||||
/// For the sample input, the returned object's `words` vector would look like:
|
||||
/// [0]: hello
|
||||
/// [1]: world
|
||||
/// [2]: testing 1234
|
||||
static WordExp Expand(std::string_view string);
|
||||
};
|
||||
|
||||
} // namespace nanosm
|
191
src/main.cpp
191
src/main.cpp
|
@ -1,7 +1,196 @@
|
|||
#include <bits/types/struct_itimerspec.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
|
||||
#include "EventLoop.hpp"
|
||||
#include "WordExp.hpp"
|
||||
|
||||
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 :)
|
||||
|
||||
auto timer = std::make_shared<TestTimer>(ev);
|
||||
|
||||
void test() {
|
||||
auto process = std::make_shared<TestProcess>(ev);
|
||||
|
||||
// Do magic
|
||||
process->SetExitCallback([p = process](int exitCode) {
|
||||
printf("exited with %d exitcode\n", exitCode);
|
||||
|
||||
timer->SetExpiryCallback([pp = p]() {
|
||||
printf("5s elapsed, restarting Nowr\n");
|
||||
pp->Respawn();
|
||||
});
|
||||
|
||||
// Start the timer to wait a bit before restarting the process
|
||||
timer->Arm(5);
|
||||
});
|
||||
|
||||
ev.AddObject(timer);
|
||||
process->Spawn("xterm");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
nanosm::EventLoop ev;
|
||||
ev.Post(test);
|
||||
|
||||
ev.Run();
|
||||
return 0;
|
||||
|
|
Loading…
Reference in New Issue