From 396ad4d6b257c8a3afb143954606aca4ecb57e74 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Tue, 26 Nov 2019 19:19:59 +0300 Subject: [PATCH] SystemServer: Implement keepalive When reaping a child, SystemServer will now match up child's pid with its own record of the services, and respawn the service if keepalive is enabled for it. For example, we want to restart the WindowServer if it crashes, but we wouldn't want to restart the Terminal if it gets closed. --- Base/etc/SystemServer.ini | 5 +++++ Servers/SystemServer/Service.cpp | 29 +++++++++++++++++++++++++++++ Servers/SystemServer/Service.h | 5 +++++ Servers/SystemServer/main.cpp | 29 +++++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Base/etc/SystemServer.ini b/Base/etc/SystemServer.ini index 22bf7be1089..02d93d59d56 100644 --- a/Base/etc/SystemServer.ini +++ b/Base/etc/SystemServer.ini @@ -6,22 +6,27 @@ Priority=high [ProtocolServer] Priority=low +KeepAlive=1 User=anon [LookupServer] Priority=low +KeepAlive=1 User=anon [WindowServer] Priority=high +KeepAlive=1 User=anon [AudioServer] Priority=high +KeepAlive=1 User=anon [Taskbar] Priority=high +KeepAlive=1 User=anon [Terminal] diff --git a/Servers/SystemServer/Service.cpp b/Servers/SystemServer/Service.cpp index c27e8372e73..b955e1efd5a 100644 --- a/Servers/SystemServer/Service.cpp +++ b/Servers/SystemServer/Service.cpp @@ -16,6 +16,7 @@ struct UidAndGid { }; static HashMap* s_user_map; +static HashMap s_service_map; void Service::resolve_user() { @@ -35,6 +36,14 @@ void Service::resolve_user() m_gid = user.value().gid; } +Service* Service::find_by_pid(pid_t pid) +{ + auto it = s_service_map.find(pid); + if (it == s_service_map.end()) + return nullptr; + return (*it).value; +} + void Service::spawn() { dbg() << "Spawning " << name(); @@ -81,9 +90,26 @@ void Service::spawn() rc = execv(argv[0], argv); perror("exec"); ASSERT_NOT_REACHED(); + } else { + // We are the parent. + s_service_map.set(m_pid, this); } } +void Service::did_exit(int exit_code) +{ + ASSERT(m_pid > 0); + (void)exit_code; + + dbg() << "Service " << name() << " has exited"; + + s_service_map.remove(m_pid); + m_pid = -1; + + if (m_keep_alive) + spawn(); +} + Service::Service(const CConfigFile& config, const StringView& name) : CObject(nullptr) { @@ -106,6 +132,8 @@ Service::Service(const CConfigFile& config, const StringView& name) else ASSERT_NOT_REACHED(); + m_keep_alive = config.read_bool_entry(name, "KeepAlive"); + m_user = config.read_entry(name, "User"); if (!m_user.is_null()) resolve_user(); @@ -127,6 +155,7 @@ void Service::save_to(JsonObject& json) json.set("stdio_file_path", m_stdio_file_path); json.set("priority", m_priority); + json.set("keep_alive", m_keep_alive); json.set("user", m_user); json.set("uid", m_uid); json.set("gid", m_gid); diff --git a/Servers/SystemServer/Service.h b/Servers/SystemServer/Service.h index 06209cbd4d4..d685ff4c375 100644 --- a/Servers/SystemServer/Service.h +++ b/Servers/SystemServer/Service.h @@ -15,6 +15,9 @@ class Service final : public CObject { public: void spawn(); + void did_exit(int exit_code); + + static Service* find_by_pid(pid_t); void save_to(AK::JsonObject&) override; @@ -28,6 +31,8 @@ private: // File path to open as stdio fds. String m_stdio_file_path; int m_priority { 1 }; + // Whether we should re-launch it if it exits. + bool m_keep_alive { false }; // The name of the user we should run this service as. String m_user; uid_t m_uid { 0 }; diff --git a/Servers/SystemServer/main.cpp b/Servers/SystemServer/main.cpp index 0030180a438..603fdd45535 100644 --- a/Servers/SystemServer/main.cpp +++ b/Servers/SystemServer/main.cpp @@ -10,12 +10,28 @@ #include #include -void sigchld_handler(int) +static void sigchld_handler(int) { int status = 0; pid_t pid = waitpid(-1, &status, WNOHANG); - if (pid) - dbg() << "reaped pid " << pid; + if (!pid) + return; + + dbg() << "Reaped child with pid " << pid; + Service* service = Service::find_by_pid(pid); + if (service == nullptr) { + dbg() << "There was no service with this pid, what is going on?"; + return; + } + + // Call service->did_exit(status) some time soon. + // We wouldn't want to run the complex logic, such + // as possibly spawning the service again, from the + // signal handler, so defer it. + CEventLoop::main().post_event(*service, make([=](CObject&) { + service->did_exit(status); + })); + CEventLoop::wake(); } static void check_for_test_mode() @@ -63,7 +79,12 @@ int main(int, char**) { mount_all_filesystems(); - signal(SIGCHLD, sigchld_handler); + struct sigaction sa = { + .sa_handler = sigchld_handler, + .sa_mask = 0, + .sa_flags = SA_RESTART + }; + sigaction(SIGCHLD, &sa, nullptr); CEventLoop event_loop;