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/WordExp.cpp
src/Process.cpp
src/RestartingProcess.cpp
)
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.
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

View File

@ -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"

View File

@ -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.
# 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" }

View File

@ -4,37 +4,6 @@
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);

View File

@ -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<std::shared_ptr<IoObject>> ioObjects {};
/// FIFO queue of functions to run semi-asynchronously
std::deque<PostFn> postCallbacks {};
};

View File

@ -53,6 +53,8 @@ namespace nanosm {
} else {
// Parent: monitor FD
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)
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);
});
}
@ -77,6 +79,10 @@ namespace nanosm {
kill(pid, SIGTERM);
}
void Process::SetSpawnCallback(std::function<void()> f) {
onProcessSpawn = f;
}
void Process::SetExitCallback(std::function<void(int)> f) {
onProcessExit = f;
}

View File

@ -25,6 +25,12 @@ namespace nanosm {
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);
private:
@ -35,6 +41,7 @@ namespace nanosm {
siginfo_t siginfo {};
std::string commLine;
std::function<void()> onProcessSpawn;
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
#include <cstdint>
#include <memory>
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<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 "EventLoop.hpp"
#include "WordExp.hpp"
#include "Timer.hpp"
#include "Process.hpp"
#include "RestartingProcess.hpp"
#include "Timer.hpp"
nanosm::EventLoop ev;
auto process = std::make_shared<nanosm::RestartingProcess>(ev);
// tests stuff :)
auto timer = std::make_shared<nanosm::Timer>(ev);
auto process = std::make_shared<nanosm::Process>(ev);
void test() {
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();
process->SetExitCallback([](pid_t pid, int exitCode) {
printf("\"%s\" pid %d exited with %d exitcode\n", process->GetCommandLine().c_str(), pid, exitCode);
});
});
// Start the timer to wait a bit before restarting the process
timer->Arm(1);
});
ev.AddObject(timer);
process->Spawn("xterm");
}