Compare commits

...

2 Commits

Author SHA1 Message Date
Lily Tsuru 753164b393 implement a process which can restart itself 2024-02-02 05:34:37 -05:00
Lily Tsuru ef904f5403 add spawn callback to Process 2024-02-01 23:30:02 -05:00
12 changed files with 188 additions and 67 deletions

View File

@ -22,6 +22,7 @@ add_executable(nanosm
src/Timer.cpp src/Timer.cpp
src/WordExp.cpp src/WordExp.cpp
src/Process.cpp src/Process.cpp
src/RestartingProcess.cpp
) )
target_compile_definitions(nanosm PRIVATE target_compile_definitions(nanosm PRIVATE

View File

@ -2,7 +2,7 @@
A nano-sized not-really-a session manager intended to replace less-than-robust Bash/etc scripts. 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? # 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. 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 # Installation

View File

@ -1,6 +1,6 @@
# TODO: This currently assumes libstdc++, later on we should *probably* set this with some detection # 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.) # 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}") set(NANOSM_CORE_LINKER_ARGS "-fuse-ld=${NANOSM_LINKER}")
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release") # OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" if("${CMAKE_BUILD_TYPE}" STREQUAL "Release") # OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo"

View File

@ -1,20 +1,20 @@
# The nanosm(1) configuration file. # The nanosm(1) configuration file.
[nanosm] [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. # Controls if nanosm should be more verbose. Defaults to false.
verbose=false verbose = false
# Restart delay in seconds. # The time in seconds nanosm will wait before restarting any app which exits.
restart-delay=1 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 # 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 # this will not hold true if any (or all apps) crash (they will be restarted
# effectively in a psuedorandom order). # effectively in a psuedorandom order).
[nanosm.apps] [nanosm.apps]
# The window manager you want to use.
window-manager = { command = "/path/to/wm/binary --any-additional-args-here" }
lxpanel = { command = "lxpanel" } lxpanel = { command = "lxpanel" }
pcmanfm-desktop = { command = "pcmanfm --desktop" } pcmanfm-desktop = { command = "pcmanfm --desktop" }

View File

@ -4,37 +4,6 @@
namespace nanosm { 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() { EventLoop::EventLoop() {
epollFd = epoll_create1(EPOLL_CLOEXEC); epollFd = epoll_create1(EPOLL_CLOEXEC);

View File

@ -30,11 +30,11 @@ namespace nanosm {
/// Returns the file descriptor for this IO object. /// Returns the file descriptor for this IO object.
virtual int GetFD() const = 0; 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; virtual int InterestedEvents() const = 0;
/// Called when the object is ready (do any i/o or handling here) /// 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; virtual void OnReady(int eventMask) = 0;
protected: protected:
@ -44,9 +44,10 @@ namespace nanosm {
EventLoop(); EventLoop();
~EventLoop(); ~EventLoop();
/// Add an object to the epoll event loop. /// Add an object to the event loop.
void AddObject(IoObject::Ptr obj); void AddObject(IoObject::Ptr obj);
/// Removes a IO object from the event loop
void RemoveObject(IoObject::Ptr obj); void RemoveObject(IoObject::Ptr obj);
/// Posts a function to run after the epoll events are dispatched. /// Posts a function to run after the epoll events are dispatched.
@ -57,13 +58,17 @@ namespace nanosm {
/// Runs the main loop. /// Runs the main loop.
void Run(); void Run();
/// Requests the mainloop to stop.
void Stop(); void Stop();
private: private:
int epollFd {}; int epollFd {};
bool shouldStop { false }; bool shouldStop { false };
/// All tracked IO objects.
std::set<std::shared_ptr<IoObject>> ioObjects {}; std::set<std::shared_ptr<IoObject>> ioObjects {};
/// FIFO queue of functions to run semi-asynchronously
std::deque<PostFn> postCallbacks {}; std::deque<PostFn> postCallbacks {};
}; };

View File

@ -53,6 +53,8 @@ namespace nanosm {
} else { } else {
// Parent: monitor FD // Parent: monitor FD
eventLoop.AddObject(IoObject::Ptr(shared_from_this())); eventLoop.AddObject(IoObject::Ptr(shared_from_this()));
if(onProcessSpawn)
eventLoop.Post(onProcessSpawn);
} }
} }
@ -63,12 +65,12 @@ namespace nanosm {
// Post a callback to call the user's exit callback (if one exists) // Post a callback to call the user's exit callback (if one exists)
eventLoop.Post([self = shared_from_this()]() { eventLoop.Post([self = shared_from_this()]() {
if(self->onProcessExit)
self->onProcessExit(self->siginfo.si_status);
// Prepare for re-attaching, or etc. // Prepare for re-attaching, or etc.
self->eventLoop.RemoveObject(self); self->eventLoop.RemoveObject(self);
self->Reset(); self->Reset();
if(self->onProcessExit)
self->onProcessExit(self->siginfo.si_status);
}); });
} }
@ -77,6 +79,10 @@ namespace nanosm {
kill(pid, SIGTERM); kill(pid, SIGTERM);
} }
void Process::SetSpawnCallback(std::function<void()> f) {
onProcessSpawn = f;
}
void Process::SetExitCallback(std::function<void(int)> f) { void Process::SetExitCallback(std::function<void(int)> f) {
onProcessExit = f; onProcessExit = f;
} }

View File

@ -25,6 +25,12 @@ namespace nanosm {
void Kill(); void Kill();
auto GetPID() const { return pid; }
const std::string& GetCommandLine() const { return commLine; }
void SetSpawnCallback(std::function<void()> f);
void SetExitCallback(std::function<void(int)> f); void SetExitCallback(std::function<void(int)> f);
private: private:
@ -35,6 +41,7 @@ namespace nanosm {
siginfo_t siginfo {}; siginfo_t siginfo {};
std::string commLine; std::string commLine;
std::function<void()> onProcessSpawn;
std::function<void(int)> onProcessExit; std::function<void(int)> onProcessExit;
}; };

65
src/RestartingProcess.cpp Normal file
View File

@ -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<nanosm::Process>(eventLoop)), processRestartTimer(std::make_shared<nanosm::Timer>(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<void(pid_t)> f) {
spawnCallback = f;
}
void RestartingProcess::SetExitCallback(std::function<void(pid_t, int)> 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

42
src/RestartingProcess.hpp Normal file
View File

@ -0,0 +1,42 @@
#pragma once
#include "Types.hpp"
#include <memory>
#include <functional>
namespace nanosm {
struct EventLoop;
struct Timer;
struct Process;
/// A process which automatically restarts.
struct RestartingProcess : public std::enable_shared_from_this<RestartingProcess> {
RestartingProcess(nanosm::EventLoop& eventLoop);
~RestartingProcess();
void Spawn(const std::string& commandLine);
void SetSpawnCallback(std::function<void(pid_t)> f);
void SetExitCallback(std::function<void(pid_t, int)> f);
/// Set the restart time.
void SetRestartTime(u32 newRestartTime);
const std::string& GetCommandLine() const;
private:
void Init();
nanosm::EventLoop& eventLoop;
std::shared_ptr<nanosm::Process> process;
std::shared_ptr<nanosm::Timer> processRestartTimer;
bool initialized { false };
u32 restartTimeSeconds { 1 };
std::function<void(pid_t pid)> spawnCallback;
std::function<void(pid_t pid, int exitCode)> exitCallback;
};
} // namespace nanosm

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <memory>
using u8 = std::uint8_t; using u8 = std::uint8_t;
using i8 = std::int8_t; using i8 = std::int8_t;
@ -11,3 +12,38 @@ using u64 = std::uint64_t;
using i64 = std::int64_t; using i64 = std::int64_t;
using usize = std::size_t; using usize = std::size_t;
using isize = std::intptr_t; using isize = std::intptr_t;
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 {};
};
} // namespace nanosm

View File

@ -4,34 +4,24 @@
#include <memory> #include <memory>
#include "EventLoop.hpp" #include "EventLoop.hpp"
#include "WordExp.hpp"
#include "Timer.hpp"
#include "Process.hpp" #include "Process.hpp"
#include "RestartingProcess.hpp"
#include "Timer.hpp"
nanosm::EventLoop ev; nanosm::EventLoop ev;
auto process = std::make_shared<nanosm::RestartingProcess>(ev);
// tests stuff :) // tests stuff :)
auto timer = std::make_shared<nanosm::Timer>(ev);
auto process = std::make_shared<nanosm::Process>(ev);
void test() { void test() {
process->SetSpawnCallback([](pid_t pid) {
printf("\"%s\" spawned as pid %d successfully\n", process->GetCommandLine().c_str(), pid);
// Do magic // Do magic
process->SetExitCallback([p = process](int exitCode) { process->SetExitCallback([](pid_t pid, int exitCode) {
printf("exited with %d exitcode\n", exitCode); printf("\"%s\" pid %d exited with %d exitcode\n", process->GetCommandLine().c_str(), pid, 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);
});
ev.AddObject(timer);
process->Spawn("xterm"); process->Spawn("xterm");
} }