From 753164b39396b31868417fbc46fb1f06f569e94f Mon Sep 17 00:00:00 2001 From: modeco80 Date: Fri, 2 Feb 2024 05:34:37 -0500 Subject: [PATCH] implement a process which can restart itself --- CMakeLists.txt | 1 + README.md | 4 +-- cmake/CompilerFlags-GNU.cmake | 2 +- doc/nanosm.toml | 16 ++++----- src/EventLoop.cpp | 31 ----------------- src/EventLoop.hpp | 11 ++++-- src/Process.cpp | 6 ++-- src/Process.hpp | 3 ++ src/RestartingProcess.cpp | 65 +++++++++++++++++++++++++++++++++++ src/RestartingProcess.hpp | 42 ++++++++++++++++++++++ src/Types.hpp | 36 +++++++++++++++++++ src/main.cpp | 24 ++++--------- 12 files changed, 176 insertions(+), 65 deletions(-) create mode 100644 src/RestartingProcess.cpp create mode 100644 src/RestartingProcess.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a344415..9585d7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable(nanosm src/Timer.cpp src/WordExp.cpp src/Process.cpp + src/RestartingProcess.cpp ) target_compile_definitions(nanosm PRIVATE diff --git a/README.md b/README.md index 0c9ce43..c2fde19 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A nano-sized not-really-a session manager intended to replace less-than-robust Bash/etc scripts. -Written in C++20. +Written in C++20, using epoll (without helper libraries), to stay nice and smol (Yes I know io_uring exists, but using it for something tiny that literally just waits and restarts processes would be far more overkill). # Why write this? @@ -10,7 +10,7 @@ Because `app &` then `exec wm` is impressively awful. What if your WM crashes, o And if your WM crashes? Your whole xorg server goes with it, meaning so does everything else. -A more robust solution that's still small and easy to setup is clearly a better idea. +A more robust solution that's still small and easy to setup (read: Not written in bash) is clearly a better idea. # Installation diff --git a/cmake/CompilerFlags-GNU.cmake b/cmake/CompilerFlags-GNU.cmake index bd93fee..3486d55 100644 --- a/cmake/CompilerFlags-GNU.cmake +++ b/cmake/CompilerFlags-GNU.cmake @@ -1,6 +1,6 @@ # 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_COMPILE_ARGS "-Wall -Wformat=2 -Wimplicit-fallthrough") set(NANOSM_CORE_LINKER_ARGS "-fuse-ld=${NANOSM_LINKER}") if("${CMAKE_BUILD_TYPE}" STREQUAL "Release") # OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" diff --git a/doc/nanosm.toml b/doc/nanosm.toml index be73f0a..c153599 100644 --- a/doc/nanosm.toml +++ b/doc/nanosm.toml @@ -1,20 +1,20 @@ # The nanosm(1) configuration file. [nanosm] -# The window manager you want to use. This is the first application -# launched -window-manager="/path/to/wm/binary --any-additional-args-here" -# Enable verbose debug logging. Only useful for debugging issues. -verbose=false +# Controls if nanosm should be more verbose. Defaults to false. +verbose = false -# Restart delay in seconds. -restart-delay=1 +# The time in seconds nanosm will wait before restarting any app which exits. +restart-time = 1 -# Any applications besides your window manager you want to run at startup. + +# Any applications you want to run at startup. # 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] +# The window manager you want to use. +window-manager = { command = "/path/to/wm/binary --any-additional-args-here" } lxpanel = { command = "lxpanel" } pcmanfm-desktop = { command = "pcmanfm --desktop" } diff --git a/src/EventLoop.cpp b/src/EventLoop.cpp index 672f87d..bde4652 100644 --- a/src/EventLoop.cpp +++ b/src/EventLoop.cpp @@ -4,37 +4,6 @@ namespace nanosm { - /// A little ergonomic wrapper over - /// std::unique_ptr, for a "kinda-vector" - /// that lives on the heap and is statically sized - template - struct UniqueArray final { - explicit UniqueArray(usize size) - : array(std::make_unique(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 array {}; - usize size {}; - }; - EventLoop::EventLoop() { epollFd = epoll_create1(EPOLL_CLOEXEC); diff --git a/src/EventLoop.hpp b/src/EventLoop.hpp index cbf5a5e..f65f9d1 100644 --- a/src/EventLoop.hpp +++ b/src/EventLoop.hpp @@ -30,11 +30,11 @@ namespace nanosm { /// Returns the file descriptor for this IO object. virtual int GetFD() const = 0; - /// Return raw event mask + /// Returns the raw event mask declaring what events the IO object is interested in 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 + /// [eventMask] is the raw epoll event mask, which can be used to pick what event to fire/do virtual void OnReady(int eventMask) = 0; protected: @@ -44,9 +44,10 @@ namespace nanosm { EventLoop(); ~EventLoop(); - /// Add an object to the epoll event loop. + /// Add an object to the event loop. void AddObject(IoObject::Ptr obj); + /// Removes a IO object from the event loop void RemoveObject(IoObject::Ptr obj); /// Posts a function to run after the epoll events are dispatched. @@ -57,13 +58,17 @@ namespace nanosm { /// Runs the main loop. void Run(); + /// Requests the mainloop to stop. void Stop(); private: int epollFd {}; bool shouldStop { false }; + /// All tracked IO objects. std::set> ioObjects {}; + + /// FIFO queue of functions to run semi-asynchronously std::deque postCallbacks {}; }; diff --git a/src/Process.cpp b/src/Process.cpp index 6fff459..660cacb 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -65,12 +65,12 @@ namespace nanosm { // Post a callback to call the user's exit callback (if one exists) eventLoop.Post([self = shared_from_this()]() { + if(self->onProcessExit) + self->onProcessExit(self->siginfo.si_status); + // Prepare for re-attaching, or etc. self->eventLoop.RemoveObject(self); self->Reset(); - - if(self->onProcessExit) - self->onProcessExit(self->siginfo.si_status); }); } diff --git a/src/Process.hpp b/src/Process.hpp index 02bd071..e153293 100644 --- a/src/Process.hpp +++ b/src/Process.hpp @@ -25,6 +25,9 @@ namespace nanosm { void Kill(); + auto GetPID() const { return pid; } + + const std::string& GetCommandLine() const { return commLine; } void SetSpawnCallback(std::function f); diff --git a/src/RestartingProcess.cpp b/src/RestartingProcess.cpp new file mode 100644 index 0000000..d2a24d3 --- /dev/null +++ b/src/RestartingProcess.cpp @@ -0,0 +1,65 @@ +#include "RestartingProcess.hpp" + +#include "EventLoop.hpp" +#include "Process.hpp" +#include "Timer.hpp" + +namespace nanosm { + + RestartingProcess::RestartingProcess(nanosm::EventLoop& eventLoop) + : eventLoop(eventLoop), process(std::make_shared(eventLoop)), processRestartTimer(std::make_shared(eventLoop)) { + // Add timer to the event loop + eventLoop.AddObject(processRestartTimer); + } + + RestartingProcess::~RestartingProcess() { + eventLoop.RemoveObject(processRestartTimer); + } + + void RestartingProcess::Spawn(const std::string& commandLine) { + if(!initialized) + Init(); + + process->Spawn(commandLine); + } + + void RestartingProcess::SetSpawnCallback(std::function f) { + spawnCallback = f; + } + + void RestartingProcess::SetExitCallback(std::function f) { + exitCallback = f; + } + + void RestartingProcess::SetRestartTime(u32 newRestartTime) { + restartTimeSeconds = newRestartTime; + } + + const std::string& RestartingProcess::GetCommandLine() const { + return process->GetCommandLine(); + } + + void RestartingProcess::Init() { + process->SetSpawnCallback([self = shared_from_this()]() { + self->process->SetExitCallback([self](int exitCode) { + // TODO: bool to not arm restart timer? + // Also instead of hacking this into the RestartingProcess layer, + // why not have normal Process expose PID too? (not that it's important) + if(self->exitCallback) + self->exitCallback(self->process->GetPID(), exitCode); + + self->processRestartTimer->SetExpiryCallback([self]() { + self->process->Respawn(); + }); + + // Start the timer to wait a bit before restarting the process + self->processRestartTimer->Arm(self->restartTimeSeconds); + }); + + if(self->spawnCallback) + self->spawnCallback(self->process->GetPID()); + }); + initialized = true; + } + +} // namespace nanosm diff --git a/src/RestartingProcess.hpp b/src/RestartingProcess.hpp new file mode 100644 index 0000000..14d9a31 --- /dev/null +++ b/src/RestartingProcess.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "Types.hpp" +#include +#include + + +namespace nanosm { + + struct EventLoop; + struct Timer; + struct Process; + + /// A process which automatically restarts. + struct RestartingProcess : public std::enable_shared_from_this { + RestartingProcess(nanosm::EventLoop& eventLoop); + + ~RestartingProcess(); + void Spawn(const std::string& commandLine); + + void SetSpawnCallback(std::function f); + + void SetExitCallback(std::function f); + + /// Set the restart time. + void SetRestartTime(u32 newRestartTime); + + const std::string& GetCommandLine() const; + + private: + void Init(); + + nanosm::EventLoop& eventLoop; + std::shared_ptr process; + std::shared_ptr processRestartTimer; + bool initialized { false }; + u32 restartTimeSeconds { 1 }; + + std::function spawnCallback; + std::function exitCallback; + }; + +} // namespace nanosm diff --git a/src/Types.hpp b/src/Types.hpp index 8ac1e2c..152192b 100644 --- a/src/Types.hpp +++ b/src/Types.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include using u8 = std::uint8_t; using i8 = std::int8_t; @@ -11,3 +12,38 @@ using u64 = std::uint64_t; using i64 = std::int64_t; using usize = std::size_t; using isize = std::intptr_t; + +namespace nanosm { + + /// A little ergonomic wrapper over + /// std::unique_ptr, for a "kinda-vector" + /// that lives on the heap and is statically sized + template + struct UniqueArray final { + explicit UniqueArray(usize size) + : array(std::make_unique(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 array {}; + usize size {}; + }; + +} // namespace nanosm diff --git a/src/main.cpp b/src/main.cpp index 1549259..c5e4429 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,33 +5,23 @@ #include "EventLoop.hpp" #include "Process.hpp" +#include "RestartingProcess.hpp" #include "Timer.hpp" -#include "WordExp.hpp" nanosm::EventLoop ev; +auto process = std::make_shared(ev); + // tests stuff :) - -auto timer = std::make_shared(ev); -auto process = std::make_shared(ev); - void test() { - process->SetSpawnCallback([p = process]() { + process->SetSpawnCallback([](pid_t pid) { + printf("\"%s\" spawned as pid %d successfully\n", process->GetCommandLine().c_str(), pid); // Do magic - process->SetExitCallback([p = process](int exitCode) { - printf("exited with %d exitcode\n", exitCode); - - timer->SetExpiryCallback([pp = p]() { - printf("Timer elapsed, restarting Nowr\n"); - pp->Respawn(); - }); - - // Start the timer to wait a bit before restarting the process - timer->Arm(1); + process->SetExitCallback([](pid_t pid, int exitCode) { + printf("\"%s\" pid %d exited with %d exitcode\n", process->GetCommandLine().c_str(), pid, exitCode); }); }); - ev.AddObject(timer); process->Spawn("xterm"); }