libos+init: Add a Notifier API to know when child processes have finished initialization
init now supports the "WaitUntilReady" key, which will wait until the child calls os::IPC::notify_parent().
This commit is contained in:
parent
2aefbdc4ee
commit
0ab8efd405
@ -320,5 +320,78 @@ namespace os
|
|||||||
bool m_ipc_in_progress { false };
|
bool m_ipc_in_progress { false };
|
||||||
u8 m_ipc_saved_id { 0 };
|
u8 m_ipc_saved_id { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief API used to notify a parent process when a child process finishes initialization. The Notifier struct
|
||||||
|
* is the parent part of the API.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct Notifier
|
||||||
|
{
|
||||||
|
int pfds[2];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new Notifier.
|
||||||
|
*
|
||||||
|
* This function will create a pipe for the parent and child to communicate.
|
||||||
|
*
|
||||||
|
* @return Notifier The new Notifier object.
|
||||||
|
*/
|
||||||
|
static Notifier create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hook the Notifier into any child process started afterwards.
|
||||||
|
*
|
||||||
|
* This will set an environment variable, which if detected by a child process, will use it to notify the
|
||||||
|
* parent whenever it's ready.
|
||||||
|
*
|
||||||
|
* The recommended order to call this API is:
|
||||||
|
* hook()
|
||||||
|
* fork+exec
|
||||||
|
* unhook()
|
||||||
|
*/
|
||||||
|
void hook();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the previously created environment variable, so that any future child processes will not
|
||||||
|
* notify this Notifier.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void unhook();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wait for a child process to be ready. If several child processes are hooked by the hook() method,
|
||||||
|
* this method will only catch the first one that notifies the parent.
|
||||||
|
*
|
||||||
|
* @param timeout If positive, specifies the timeout after which the function fails if no notification is
|
||||||
|
* received.
|
||||||
|
* @return true The child is ready.
|
||||||
|
* @return false The method timed out.
|
||||||
|
*/
|
||||||
|
bool wait(int timeout = -1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Combines hook(), unhook() and wait() into one single method. This method takes a function, and
|
||||||
|
* executes it in a "hooked" context, so that any child process started by this function will automatically
|
||||||
|
* detect the parent when it's ready (if it supports the notification API). Then, the function waits for the
|
||||||
|
* child to be ready.
|
||||||
|
*
|
||||||
|
* @param action The function to run.
|
||||||
|
* @param timeout If positive, specifies the timeout after which the function fails if no notification is
|
||||||
|
* received.
|
||||||
|
* @return true The child is ready.
|
||||||
|
* @return false The method timed out.
|
||||||
|
*/
|
||||||
|
static bool run_and_wait(os::Action&& action, int timeout = -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Use this function to notify a parent process whenever your program finishes initialization.
|
||||||
|
*
|
||||||
|
* If the parent has not used the Notifier API to request a notification from the child process, this function
|
||||||
|
* does nothing.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void notify_parent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,13 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <luna/String.h>
|
||||||
#include <os/IPC.h>
|
#include <os/IPC.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
namespace os::IPC
|
namespace os::IPC
|
||||||
{
|
{
|
||||||
@ -84,4 +90,60 @@ namespace os::IPC
|
|||||||
Client::Client(OwnedPtr<LocalClient>&& connection) : m_connection(move(connection))
|
Client::Client(OwnedPtr<LocalClient>&& connection) : m_connection(move(connection))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Notifier Notifier::create()
|
||||||
|
{
|
||||||
|
Notifier result;
|
||||||
|
pipe(result.pfds);
|
||||||
|
fcntl(result.pfds[0], F_SETFD, FD_CLOEXEC);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::hook()
|
||||||
|
{
|
||||||
|
auto value = String::format("%d"_sv, pfds[1]).release_value();
|
||||||
|
setenv("LUNA_NOTIFY_FD", value.chars(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::unhook()
|
||||||
|
{
|
||||||
|
unsetenv("LUNA_NOTIFY_FD");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Notifier::wait(int timeout)
|
||||||
|
{
|
||||||
|
close(pfds[1]);
|
||||||
|
struct pollfd fds[] = { { .fd = pfds[0], .events = POLLIN, .revents = 0 } };
|
||||||
|
|
||||||
|
poll:
|
||||||
|
int result = poll(fds, 1, timeout);
|
||||||
|
if (result < 0 && errno == EINTR) goto poll;
|
||||||
|
|
||||||
|
close(pfds[0]);
|
||||||
|
|
||||||
|
return result > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Notifier::run_and_wait(os::Action&& action, int timeout)
|
||||||
|
{
|
||||||
|
auto notifier = create();
|
||||||
|
notifier.hook();
|
||||||
|
action();
|
||||||
|
notifier.unhook();
|
||||||
|
return notifier.wait(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notify_parent()
|
||||||
|
{
|
||||||
|
char* fd_string = getenv("LUNA_NOTIFY_FD");
|
||||||
|
if (!fd_string) return;
|
||||||
|
|
||||||
|
int fd = atoi(fd_string);
|
||||||
|
|
||||||
|
u8 data = 0;
|
||||||
|
write(fd, &data, 1);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
unsetenv("LUNA_NOTIFY_FD");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <os/ArgumentParser.h>
|
#include <os/ArgumentParser.h>
|
||||||
#include <os/Directory.h>
|
#include <os/Directory.h>
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
|
#include <os/IPC.h>
|
||||||
#include <os/Process.h>
|
#include <os/Process.h>
|
||||||
#include <os/Security.h>
|
#include <os/Security.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
@ -38,6 +39,7 @@ struct Service
|
|||||||
Option<uid_t> user {};
|
Option<uid_t> user {};
|
||||||
Option<gid_t> group {};
|
Option<gid_t> group {};
|
||||||
bool wait { false };
|
bool wait { false };
|
||||||
|
bool wait_notify { false };
|
||||||
Option<pid_t> pid {};
|
Option<pid_t> pid {};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,9 +134,13 @@ static Result<void> try_start_service(Service& service)
|
|||||||
new_stdin->set_close_on_exec();
|
new_stdin->set_close_on_exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os::IPC::Notifier notifier;
|
||||||
|
if (service.wait_notify) { notifier = os::IPC::Notifier::create(); }
|
||||||
|
|
||||||
pid_t pid = TRY(os::Process::fork());
|
pid_t pid = TRY(os::Process::fork());
|
||||||
if (pid == 0)
|
if (pid == 0)
|
||||||
{
|
{
|
||||||
|
if (service.wait_notify) { notifier.hook(); }
|
||||||
auto rc = service_child(service, new_stdout, new_stderr, new_stdin);
|
auto rc = service_child(service, new_stdout, new_stderr, new_stdin);
|
||||||
if (rc.has_error())
|
if (rc.has_error())
|
||||||
{
|
{
|
||||||
@ -157,7 +163,17 @@ static Result<void> try_start_service(Service& service)
|
|||||||
do_log("[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
|
do_log("[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (service.wait_notify)
|
||||||
|
{
|
||||||
|
bool success = notifier.wait(1000);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
do_log("[init] service %s with pid %d failed to start successfully\n", service.name.chars(), pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
service.pid = pid;
|
service.pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -273,6 +289,17 @@ static Result<void> load_service(const os::Path& path)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parts[0].view() == "WaitUntilReady")
|
||||||
|
{
|
||||||
|
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)
|
||||||
|
{
|
||||||
|
service.wait_notify = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
service.wait_notify = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
do_log("[init] skipping unknown entry name %s\n", parts[0].chars());
|
do_log("[init] skipping unknown entry name %s\n", parts[0].chars());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user