eventlog-socket 0.1.1.0 → 0.1.2.0
raw patch · 17 files changed
+1904/−1227 lines, 17 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ GHC.Eventlog.Socket: testControlStatus :: IO ()
+ GHC.Eventlog.Socket: testWorkerStatus :: IO ()
Files
- CHANGELOG.md +20/−8
- README.md +9/−10
- cbits/eventlog_socket.c +243/−746
- cbits/eventlog_socket/control.c +346/−299
- cbits/eventlog_socket/control.h +38/−25
- cbits/eventlog_socket/error.c +9/−9
- cbits/eventlog_socket/error.h +47/−3
- cbits/eventlog_socket/init_state.h +26/−0
- cbits/eventlog_socket/string.c +2/−2
- cbits/eventlog_socket/string.h +2/−2
- cbits/eventlog_socket/worker.c +782/−0
- cbits/eventlog_socket/worker.h +48/−0
- cbits/eventlog_socket/write_buffer.c +5/−8
- cbits/eventlog_socket/write_buffer.h +4/−3
- eventlog-socket.cabal +32/−3
- include/eventlog_socket.h +154/−68
- src/GHC/Eventlog/Socket.hsc +137/−41
CHANGELOG.md view
@@ -1,15 +1,27 @@ # Revision history for eventlog-socket +## 0.1.2.0 -- 2026-03-25++**Warning**: The C API exposed from this version contains breaking changes over the C API exposed from version 0.1.1.0. This is justified by the fact that the C API exposed from version 0.1.1.0 is broken and that version is deprecated and not known to be in use.++- **BREAKING**: Change `eventlog_socket_wrap_hs_main` to accept `EventlogSocketAddr` and `EventlogSocketOpts` and call `eventlog_socket_init`.+- **BREAKING**: Remove `eventlog_socket_attach_rts_config`.+- Fixed issue where `eventlog_socket_start` would ignore `eso_wait`.+- Fixed issue where `EventlogSocketWriter->writeEventLog` would drop data if there was no connection, resulting in a truncated eventlog.+- Fixed race condition between the calls to `endEventLogging` and `startEventLogging` that happened on startup and at first connection. If these restarts were interleaved, this could lead to the startup call to `endEventLogging` immediately detaching the event logger attached by the call to `startEventLogging` in the worker thread listening for incoming connections. This would cause the socket to immediately be closed and lead to a truncated eventlog. Furthermore, the startup call to `endEventLogging` happened unconditionally, which means that an event logger attached in a C main (as in `examples/fibber-c-main`) would be detached on the first connection, which would drop prior all events from the buffer. The new version does not call `endEventLogging` or `startEventLogging` on startup. This means that an event logger installed in a C main is able to capture events from before the first connection. It also means that the default event logger – which may be the file event logger – will be active until the first connection, rather than until `eventlog_socket_start` is called.+- Add `testWorkerStatus` and `testControlStatus` functions to Haskell API.+- Add `eventlog_socket_worker_status` and `eventlog_socket_control_status` to C API.+ ## 0.1.1.0 -- 2026-02-23 -* Added high-level API (`startWith` with `EventlogSocketAddr` and `EventlogSocketOpts`).-* Added support for TCP sockets (via `EventlogSocketAddr`).-* Added support for setting the send buffer size (via `EventlogSocketOpts`).-* Added support for reading the configuration from environment variables (`fromEnv` and `startFromEnv`).-* Added public C API.-* Added support for control commands (if compiled with `+control`).-* Added support for custom control commands.+- Added high-level API (`startWith` with `EventlogSocketAddr` and `EventlogSocketOpts`).+- Added support for TCP sockets (via `EventlogSocketAddr`).+- Added support for setting the send buffer size (via `EventlogSocketOpts`).+- Added support for reading the configuration from environment variables (`fromEnv` and `startFromEnv`).+- Added public C API.+- Added support for control commands (if compiled with `+control`).+- Added support for custom control commands. ## 0.1.0.0 -- 2023-04-14 -* First version. Released on an unsuspecting world.+- First version. Released on an unsuspecting world.
README.md view
@@ -3,6 +3,8 @@ The `eventlog-socket` package supports streaming the GHC eventlog over Unix domain and TCP/IP sockets. It streams the GHC eventlog in the [standard binary format](https://downloads.haskell.org/ghc/latest/docs/users_guide/eventlog-formats.html), identical to the contents of a binary `.eventlog` file, which can be parsed using [ghc-events](https://hackage.haskell.org/package/ghc-events). Whenever a new client connects, the event header is repeated, which guarantees that the client receives important events such as [`RTS_IDENTIFIER`](https://downloads.haskell.org/ghc/latest/docs/users_guide/eventlog-formats.html#event-type-RTS_IDENTIFIER) and [`WALL_CLOCK_TIME`](https://downloads.haskell.org/ghc/latest/docs/users_guide/eventlog-formats.html#event-type-WALL_CLOCK_TIME). +When compiled with `+control` the `eventlog-socket` package also exposes a control command server over the socket, which receives and executes builtin or pre-registered commands, which allows you to dynamically enable, e.g., heap profiling from the external monitoring process. The `eventlog-socket-control` package provides utilities for constructing messages for the the control command protocol used by this feature.+ ## Getting Started To use the code in this repository to profile your own application, follow these steps.@@ -47,17 +49,17 @@ For more detailed configuration options, including TCP/IP sockets, you should use the `startWith` function, which takes a socket address and socket options as its arguments. -> :warning:+> ⚠️ > On most platforms, Unix domain socket paths are limited to 107 characters or less. -> :warning:+> ⚠️ > <a name="default-writer"></a>If you instrument your application from Haskell, the GHC RTS will start with the default eventlog writer, which is the file writer. This means that your application will write the first few events to a file called `my-app.eventlog`. Usually, this is not an issue, as all initialization events are repeated every time a new client connects to the socket. However, it may give you problems if your application is stored on a read-only filesystem, such as the Nix store. To change the file path, you can use the `-ol` RTS option. To disable the file writer entirely, use the `--null-eventlog-writer` RTS option. If it's important that you capture *all* of the GHC eventlog, you must [instrument your application from C](#instrument-your-application-from-c), so that the GHC RTS is started with the `eventlog-socket` writer. ### Instrument your application from C The `eventlog-socket` package installs a C header file, `eventlog_socket.h`, which enables you to instrument your application from a C main function. There is one reason you may want to instrument your application from C, which is that you want to capture *all* of the GHC eventlog. If your application is instrumented from Haskell, it loses the first few events. See the note [above](#default-writer). -For an example of an application instrumented from a custom C main, see [`examples/fibber-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.1.0/examples/fibber-c-main).+For an example of an application instrumented from a custom C main, see [`examples/fibber-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/examples/fibber-c-main/). ### Configure your application to enable the eventlog @@ -91,7 +93,7 @@ The heap profiling commands require that the RTS is initialised with one of the `-h` options. See [RTS options for heap profiling](https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#rts-options-heap-prof). If any heap profiling option is passed, heap profiling is started by default. To avoid this, pass the `--no-automatic-heap-samples` to the RTS *in addition to* the selected `-h` option, e.g., `./my-app +RTS -hT --no-automatic-heap-samples -RTS`. -The [`eventlog-socket-control`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.1.0/eventlog-socket-control/) package provides utilities for creating control command protocol messages to write to the eventlog socket.+The [`eventlog-socket-control`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/eventlog-socket-control/) package provides utilities for creating control command protocol messages to write to the eventlog socket. If you want to send control commands from a different programming language, see [Control Command Protocol](#control-command-protocol) for the specification of the binary control command protocol. ### Control Command Protocol@@ -135,9 +137,9 @@ Commands can be passed some user data at registration time. This is intended to let you defines multiple different commands using the same handler. -For an example of an application instrumented with custom control commands using the Haskell API, see [`examples/custom-command`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.1.0/examples/custom-command/).+For an example of an application instrumented with custom control commands using the Haskell API, see [`examples/custom-command`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/examples/custom-command/). -For an example of an application instrumented with custom control commands using the C API, see [`examples/custom-command-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.1.0/examples/custom-command-c-main/).+For an example of an application instrumented with custom control commands using the C API, see [`examples/custom-command-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/examples/custom-command-c-main/). The following snippet defines the `registerGreeters` function, which registers two commands via the Haskell API: @@ -245,10 +247,7 @@ The `scripts` directory contains various scripts for contributors to this repository. -The `./scripts/test-haskell.sh` script runs the Haskell test suite, which is located in [`eventlog-socket-tests`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.1.0/eventlog-socket-tests/).--The `./scripts/test-python.sh` scripts runs the Python test suite, which is located in [`eventlog-socket/tests`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.1.0/eventlog-socket/tests).-This test suite is not fully portable, and is primarily intended to be run on Linux machines.+The `./scripts/test.sh` script runs the test suite, which is located in [`eventlog-socket-tests`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/eventlog-socket-tests/). The `./scripts/pre-commit.sh` script runs the pre-commit hooks, which contains various formatters and linters.
cbits/eventlog_socket.c view
@@ -15,7 +15,6 @@ #include <unistd.h> #include <Rts.h>-#include <rts/prof/Heap.h> #ifdef EVENTLOG_SOCKET_FEATURE_CONTROL #include "./eventlog_socket/control.h"@@ -23,20 +22,12 @@ #include "./eventlog_socket/debug.h" #include "./eventlog_socket/error.h"-#include "./eventlog_socket/poll.h"+#include "./eventlog_socket/init_state.h" #include "./eventlog_socket/string.h"+#include "./eventlog_socket/worker.h" #include "./eventlog_socket/write_buffer.h" #include "eventlog_socket.h" -#define LISTEN_BACKLOG 5--#ifndef NI_MAXHOST-#define NI_MAXHOST 1025-#endif-#ifndef NI_MAXSERV-#define NI_MAXSERV 32-#endif- /********************************************************************************* * globals *********************************************************************************/@@ -49,29 +40,27 @@ * socket) */ -// variables read and written by worker only:-static bool g_initialized = false;-static int g_listen_fd = -1;-static const char *g_sock_path = NULL;-static int g_wake_pipe[2] = {-1, -1};- // Worker thread handle.-static pthread_t *g_listen_thread_ptr = NULL;+static pthread_t *g_worker_thread_ptr = NULL; // Control thread handle. #ifdef EVENTLOG_SOCKET_FEATURE_CONTROL static pthread_t *g_control_thread_ptr = NULL; #endif /* EVENTLOG_SOCKET_FEATURE_CONTROL */ -// Condition for new connections.-static pthread_cond_t g_new_conn_cond = PTHREAD_COND_INITIALIZER;+/// @brief The condition on which to wait for the signal that a new connection+/// has been established.+static pthread_cond_t g_new_connection_cond = PTHREAD_COND_INITIALIZER; +/// @brief The condition on which to wait for the signal that the GHC RTS is+/// ready.+static pthread_cond_t g_ghc_rts_ready_cond = PTHREAD_COND_INITIALIZER;+ // Global mutex guarding all shared state between RTS threads, the worker // thread, and the detached control receiver. Only client_fd and wt need // protection, but using a single mutex ensures we keep their updates // consistent.-static pthread_mutex_t g_write_buffer_and_client_fd_mutex =- PTHREAD_MUTEX_INITIALIZER;+static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; // variables accessed by multiple threads and guarded by mutex: // * client_fd: written by worker, writer_stop, and control receiver to signal@@ -87,160 +76,146 @@ .last = NULL, }; -static void cleanup(void) {- // Remove socket file.- if (g_sock_path) {- unlink(g_sock_path);- }- // Close the wake pipes.- if (g_wake_pipe[0] != -1) {- close(g_wake_pipe[0]);- g_wake_pipe[0] = -1;- }- if (g_wake_pipe[1] != -1) {- close(g_wake_pipe[1]);- g_wake_pipe[1] = -1;- }- // Stop the control thread.-#ifdef EVENTLOG_SOCKET_FEATURE_CONTROL- if (g_control_thread_ptr != NULL) {- DEBUG_DEBUG("%s", "Cancelling control thread.");- if (pthread_cancel(*g_control_thread_ptr) != 0) {- DEBUG_ERRNO("pthread_cancel() failed for control thread");- } else {- if (pthread_join(*g_control_thread_ptr, NULL) != 0) {- DEBUG_ERRNO("pthread_join() failed for control thread");- }- }- free((void *)g_control_thread_ptr);- }--#endif /* EVENTLOG_SOCKET_FEATURE_CONTROL */- // Stop the worker thread.- if (g_listen_thread_ptr != NULL) {- DEBUG_DEBUG("%s", "Cancelling worker thread.");- if (pthread_cancel(*g_listen_thread_ptr) != 0) {- DEBUG_ERRNO("pthread_cancel() failed for worker thread");- } else {- if (pthread_join(*g_listen_thread_ptr, NULL) != 0) {- DEBUG_ERRNO("pthread_join() failed for worker thread");- }- }- free((void *)g_listen_thread_ptr);- }-}--#define EVENTLOG_SOCKET_WORKER_CHUNK_SIZE 32--static void drain_worker_wake(void) {- if (g_wake_pipe[0] == -1) {- return;- }- uint8_t chunk[EVENTLOG_SOCKET_WORKER_CHUNK_SIZE];- while (true) {- const ssize_t success_or_error = read(g_wake_pipe[0], chunk, sizeof(chunk));- if (success_or_error > 0) {- continue;- } else if (success_or_error == 0) {- break;- } else if (errno == EINTR) {- continue;- } else if (errno == EAGAIN || errno == EWOULDBLOCK) {- break;- } else {- DEBUG_ERRNO("read() failed");- break;- }- }-}--static void wake_worker(void) {- if (g_wake_pipe[1] == -1) {- return;- }-- const uint8_t byte = 1;- const ssize_t success_or_error = write(g_wake_pipe[1], &byte, sizeof(byte));- if (success_or_error == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {- DEBUG_ERRNO("write() failed");- }-}+/// @brief This variable is used to track the state of the eventlog writer.+///+/// 1. Whether or not `eventlog_socket_init` was called. This function+/// allocates resources that are used throughout the lifetime of the+/// process and it should not be called twice.+///+/// 2. Whether or not `EventlogSocketWriter` was attached in a C main.+/// If it was, then we don't want to restart event logging when the first+/// client connects, because that would drop all buffered events.+///+/// **NOTE**: While our advertised purpose for a C main is the ability to+/// attach an `EventlogSocketWriter`, it's possible to write a C main that+/// only calls `eventlog_socket_init` and+/// `eventlog_socket_signal_ghc_rts_ready`, which behaves exactly like+/// `eventlog_socket_start`.+///+/// The state tracks four signals:+///+/// 1. `EVENTLOG_SOCKET_SIG_INITIALIZED` is set when `eventlog_socket_init` is+/// called.+/// 2. `EVENTLOG_SOCKET_SIG_ATTACHED` is set when `EventlogSocketWriter` member+/// @c initEventLogWriter is called.+/// 3. `EVENTLOG_SOCKET_SIG_RTS_READY` is set when+/// `eventlog_socket_signal_ghc_rts_ready` is called.+/// 4. `EVENTLOG_SOCKET_SIG_HAD_FIRST_CONNECTION` is set when the first client+/// connects to the eventlog socket.+///+/// If `EventlogSocketWriter` was attached in a C main, then these signals are+/// received in order (1, 2, 3, 4).+///+/// If `eventlog_socket_start` is used, then these signals are received in+/// order (1, 4, 3, 2).+///+static volatile EventlogSocketInitState g_init_state = 0; /********************************************************************************* * EventLogWriter *********************************************************************************/ static void writer_init(void) {- // nothing+ DEBUG_DEBUG("%s", "Attached eventlog-socket writer.");+ pthread_mutex_lock(&g_mutex);+ g_init_state |= EVENTLOG_SOCKET_SIG_ATTACHED;+ pthread_mutex_unlock(&g_mutex); } static void writer_enqueue(uint8_t *data, size_t size) {- DEBUG_TRACE("size: %p %lu", (void *)data, size);+ DEBUG_TRACE("Enqueueing %lu bytes.", size); bool was_empty = g_write_buffer.head == NULL; // TODO: check the size of the queue // if it's too big, we can start dropping blocks. // for now, we just push everythinb to the back of the buffer.- write_buffer_push(&g_write_buffer, data, size);-- DEBUG_TRACE("wt.head = %p", (void *)g_write_buffer.head);+ es_write_buffer_push(&g_write_buffer, size, data); if (was_empty) {- wake_worker();+ es_worker_wake(); } } static bool writer_write(void *eventlog, size_t size) {- DEBUG_TRACE("size: %lu", size);- // Serialize against worker/control threads so that client_fd and wt are read- // atomically with respect to connection establishment/teardown.- pthread_mutex_lock(&g_write_buffer_and_client_fd_mutex);- int fd = g_client_fd;- if (fd < 0) {- goto exit;- }-- DEBUG_TRACE("client_fd = %d; wt.head = %p", fd, (void *)g_write_buffer.head);+ DEBUG_TRACE("Received %lu bytes.", size);+ // Acquire the write buffer lock.+ pthread_mutex_lock(&g_mutex); - if (g_write_buffer.head != NULL) {- // if there is stuff in queue already, we enqueue the current block.+ // Check if the write buffer is non-empty.+ const bool is_empty = g_write_buffer.head == NULL;+ if (!is_empty) {+ // If there is data is the write buffer, enqueue the new data.+ DEBUG_TRACE("%s", "Write buffer is non-empty. Enqueueing new data."); writer_enqueue(eventlog, size);- } else {-- // and if there isn't, we can write immediately.- const ssize_t num_bytes_written_or_err = write(fd, eventlog, size);- DEBUG_TRACE("write return %zd", num_bytes_written_or_err);+ goto exit;+ }+ assert(is_empty); - if (num_bytes_written_or_err == -1) {- if (errno == EAGAIN || errno == EWOULDBLOCK) {- // couldn't write anything, enqueue whole block- writer_enqueue(eventlog, size);- goto exit;- } else if (errno == EPIPE) {- // connection closed, simply exit- goto exit;+ // Check if this is prior to the first connection.+ const bool is_connected = g_client_fd >= 0;+ if (!is_connected) {+ // Prior to the first connection, enqueue the new data.+ const bool had_first_connection =+ g_init_state & EVENTLOG_SOCKET_SIG_HAD_FIRST_CONNECTION;+ if (!had_first_connection) {+ DEBUG_TRACE("%s", "Waiting for first connection. Enqueueing new data.");+ writer_enqueue(eventlog, size);+ goto exit;+ }+ // Otherwise, drop the data.+ else {+ goto exit;+ }+ }+ assert(is_connected); - } else {- DEBUG_ERRNO("write() failed");- goto exit;- }+ // Try to write the data over the socket.+ const ssize_t num_bytes_written_or_error = write(g_client_fd, eventlog, size);+ if (num_bytes_written_or_error != -1) {+ // Write was successful.+ DEBUG_TRACE("Wrote %zd bytes to eventlog socket.",+ num_bytes_written_or_error);+ // The call to write may not have written all of the buffer.+ // Cast from ssize_t to size_t is safe as num_bytes_written_or_err != -1.+ const size_t num_bytes_written = num_bytes_written_or_error;+ // If we wrote everything...+ if (num_bytes_written >= size) {+ // ...simply exit.+ goto exit; } else {- // cast from ssize_t to size_t is safe as num_bytes_written_or_err != -1- const size_t num_bytes_written = num_bytes_written_or_err;- // we wrote something- if (num_bytes_written >= size) {- // we wrote everything, nothing to do- goto exit;- } else {- // we wrote only part of the buffer- writer_enqueue((uint8_t *)eventlog + num_bytes_written,- size - num_bytes_written);- }+ // Otherwise, enqueue the remaining new data.+ // we wrote only part of the buffer+ writer_enqueue((uint8_t *)eventlog + num_bytes_written,+ size - num_bytes_written); }+ } else {+ // If write failed with the advice to retry...+ if (errno == EAGAIN || errno == EWOULDBLOCK) {+ // ...enqueue the new data.+ DEBUG_TRACE("Call to write(%d, _, %zu) failed. Enqueueing new data",+ g_client_fd, size);+ writer_enqueue(eventlog, size);+ goto exit;+ }+ // If write failed because the connection as closed...+ else if (errno == EPIPE) {+ // ...drop the data and exit.+ // TODO: Is this the correct behaviour? Is it guaranteed someone else will+ // free the write buffer?+ goto exit;+ }+ // If write failed for any other reason...+ else {+ // This error can be one of EBADF, EDESTADDRREQ, EDQUOT, EFAULT, EFBIG,+ // EINTR, EINVAL, EIO, ENOSPC, EPERM.+ // TODO: Should EINTR be handled differently?+ DEBUG_ERRNO("write() failed");+ goto exit;+ } }- exit:- pthread_mutex_unlock(&g_write_buffer_and_client_fd_mutex);+ // Release the write buffer lock.+ pthread_mutex_unlock(&g_mutex); return true; } @@ -251,535 +226,32 @@ static void writer_stop(void) { // RTS shutdown path must hold mutex so updates to client_fd/wt stay ordered // with the worker thread noticing the disconnect.- pthread_mutex_lock(&g_write_buffer_and_client_fd_mutex);+ pthread_mutex_lock(&g_mutex);+ // Mark EventlogSocketWriter as detached.+ g_init_state &= ~EVENTLOG_SOCKET_SIG_ATTACHED;++ // Close the connection. if (g_client_fd >= 0) { close(g_client_fd); g_client_fd = -1;- write_buffer_free(&g_write_buffer);+ es_write_buffer_free(&g_write_buffer); }- pthread_mutex_unlock(&g_write_buffer_and_client_fd_mutex);+ pthread_mutex_unlock(&g_mutex); } /// @brief The eventlog socket writer. /// /// @warning It is only safe to pass this value to the GHC RTS *after* /// `eventlog_socket_init` or `eventlog_socket_start` is called.-static const EventLogWriter SocketEventLogWriter = {- .initEventLogWriter = writer_init,- .writeEventLog = writer_write,- .flushEventLog = writer_flush,- .stopEventLogWriter = writer_stop};+const EventLogWriter EventLogSocketWriter = {.initEventLogWriter = writer_init,+ .writeEventLog = writer_write,+ .flushEventLog = writer_flush,+ .stopEventLogWriter = writer_stop}; /********************************************************************************* * Main worker (in own thread) *********************************************************************************/ -static void worker_step_listen(void) {- bool start_eventlog = false;-- if (listen(g_listen_fd, LISTEN_BACKLOG) == -1) {- DEBUG_ERRNO("listen() failed");- abort();- }-- struct sockaddr_storage remote;- socklen_t remote_len = sizeof(remote);-- struct pollfd pfds[1] = {{- .fd = g_listen_fd,- .events = POLLIN,- .revents = 0,- }};-- DEBUG_TRACE("listen_iteration: waiting for accept on fd %d", g_listen_fd);-- // poll until we can accept- while (true) {- const int ready_or_error = poll(pfds, 1, POLL_LISTEN_TIMEOUT);- if (ready_or_error == -1) {- DEBUG_ERRNO("poll() failed");- return;- } else if (ready_or_error == 0) {- DEBUG_TRACE("%s", "accept poll timed out");- } else {- // got connection- DEBUG_TRACE("%s", "accept poll ready");- break;- }- }-- // accept- const int client_fd =- accept(g_listen_fd, (struct sockaddr *)&remote, &remote_len);- if (client_fd == -1) {- DEBUG_ERRNO("accept() failed");- return;- }- DEBUG_TRACE("accepted new connection fd=%d", client_fd);-- // set socket into non-blocking mode- const int flags = fcntl(client_fd, F_GETFL);- if (flags == -1) {- DEBUG_ERRNO("fnctl() failed for F_GETFL");- }- if (fcntl(client_fd, F_SETFL, flags | O_NONBLOCK) == -1) {- DEBUG_ERRNO("fnctl() failed for F_SETFL");- }-- // we stop existing logging so we can replay header on the new connection- if (eventLogStatus() == EVENTLOG_RUNNING) {- endEventLogging();- start_eventlog = true;- }-- // we got client_id now.- // Publish new fd under mutex so RTS writers either see the connection along- // with an empty queue or not at all. Keep the lock held through the condition- // broadcast so the predicate update stays atomic on every platform.- pthread_mutex_lock(&g_write_buffer_and_client_fd_mutex);- DEBUG_TRACE("publishing client_fd=%d (previous=%d)", client_fd, g_client_fd);- g_client_fd = client_fd;- pthread_cond_broadcast(&g_new_conn_cond);- pthread_mutex_unlock(&g_write_buffer_and_client_fd_mutex);-- if (start_eventlog) {- // start writing- startEventLogging(&SocketEventLogWriter);- }-- // we are done.-}--// nothing to write iteration.-//-// we poll only for whether the connection is closed.-static void worker_step_nonwrite(int fd) {- DEBUG_TRACE("(%d)", fd);-- // Wait for socket to disconnect or for pending data.- struct pollfd pfds[2];- pfds[0].fd = fd;- pfds[0].events = POLLRDHUP;- pfds[0].revents = 0;- int nfds = 1;- if (g_wake_pipe[0] != -1) {- pfds[1].fd = g_wake_pipe[0];- pfds[1].events = POLLIN;- pfds[1].revents = 0;- nfds = 2;- }-- const int ready_or_error = poll(pfds, nfds, -1);- if (ready_or_error == -1) {- if (errno == EINTR) {- return;- }- DEBUG_ERRNO("poll() failed");- return;- }-- if (nfds == 2 && (pfds[1].revents & POLLIN)) {- drain_worker_wake();- return;- }-- if (pfds[0].revents & POLLHUP) {- DEBUG_TRACE("(%d) POLLRDHUP", fd);-- pthread_mutex_lock(&g_write_buffer_and_client_fd_mutex);- g_client_fd = -1;- write_buffer_free(&g_write_buffer);- pthread_mutex_unlock(&g_write_buffer_and_client_fd_mutex);- return;- }-}--// write iteration.-//-// we poll for both: can we write, and whether the connection is closed.-static void worker_step_write(int fd) {- DEBUG_TRACE("(%d)", fd);-- // Wait for socket to disconnect- struct pollfd pfds[1] = {{- .fd = fd,- .events = POLLOUT | POLLRDHUP,- .revents = 0,- }};-- const int num_ready_or_err = poll(pfds, 1, POLL_WRITE_TIMEOUT);- if (num_ready_or_err == -1 && errno != EAGAIN) {- // error- DEBUG_ERRNO("poll() failed");- return;- } else if (num_ready_or_err == 0) {- // timeout- return;- }-- // reset client_fd on RDHUP.- if (pfds[0].revents & POLLHUP) {- DEBUG_TRACE("(%d) POLLRDHUP", fd);-- // reset client_fd- // Protect concurrent access to client_fd and wt during teardown.- pthread_mutex_lock(&g_write_buffer_and_client_fd_mutex);- assert(fd == g_client_fd);- g_client_fd = -1;- write_buffer_free(&g_write_buffer);- pthread_mutex_unlock(&g_write_buffer_and_client_fd_mutex);- return;- }-- if (pfds[0].revents & POLLOUT) {- DEBUG_TRACE("(%d) POLLOUT", fd);-- // RTS writers also access wt, so consume queued buffers under the mutex.- pthread_mutex_lock(&g_write_buffer_and_client_fd_mutex);- while (g_write_buffer.head) {- WriteBufferItem *item = g_write_buffer.head;- const ssize_t num_bytes_written_or_err =- write(g_client_fd, item->data, item->size);-- if (num_bytes_written_or_err == -1) {- if (errno == EAGAIN || errno == EWOULDBLOCK) {- // couldn't write anything, shouldn't happen.- // do nothing.- } else if (errno == EPIPE) {- g_client_fd = -1;- write_buffer_free(&g_write_buffer);- } else {- DEBUG_ERRNO("write() failed");- }-- // break out of the loop- break;-- } else {- // cast from ssize_t to size_t is safe as num_bytes_written_or_err != -1- const size_t num_bytes_written = num_bytes_written_or_err;- // we wrote something- if (num_bytes_written >= item->size) {- // we wrote whole element, try to write next element too- write_buffer_pop(&g_write_buffer);- continue;- } else {- item->size -= num_bytes_written;- item->data += num_bytes_written;- break;- }- }- }- pthread_mutex_unlock(&g_write_buffer_and_client_fd_mutex);- }-}--static void worker_step(void) {- // Snapshot shared state under lock so worker decisions (listen vs write)- // align with the current connection/queue state.- pthread_mutex_lock(&g_write_buffer_and_client_fd_mutex);- const int client = g_client_fd;- const bool write_buffer_empty = g_write_buffer.head == NULL;- DEBUG_TRACE("fd = %d, wt.head = %p", client, (void *)g_write_buffer.head);- pthread_mutex_unlock(&g_write_buffer_and_client_fd_mutex);-- if (client != -1) {- if (write_buffer_empty) {- worker_step_nonwrite(client);- } else {- worker_step_write(client);- }- } else {- worker_step_listen();- }-}--/* Main loop of eventlog-socket own thread:- * Currently it is two states:- * - either we have connection, then we poll for writes (and drop of- * connection).- * - or we don't have, then we poll for accept.- */-static void *worker_loop(void *arg) {- (void)arg;- while (true) {- worker_step();- }- return NULL; // unreachable-}--/// @brief Initialize the Unix domain socket and bind it to the provided path.-///-/// This function does not start any threads; @c worker_start() completes the-/// setup.-///-/// @return See `EventlogSocketStatus`.-///-/// @par Errors-/// @parblock-/// This function may return the following system errors:-/// `EADDRINUSE`, `EADDRNOTAVAIL`, `EAFNOSUPPORT`, `EBADF`, `EDOM`,-/// `EFAULT`, `EINVAL`, `EISCONN`, `ELOOP`, `EMFILE`, `ENAMETOOLONG`, `ENFILE`,-/// `ENOBUFS`, `ENOENT`, `ENOMEM`, `ENOPROTOOPT`, `ENOTDIR`, `ENOTSOCK`,-/// `EPROTONOSUPPORT`, or `EROFS`.-/// @endparblock-static EventlogSocketStatus-worker_socket_init_unix(const EventlogSocketUnixAddr *const unix_addr,- const EventlogSocketOpts *const opts) {- DEBUG_TRACE("init Unix listener: %s", unix_addr->esa_unix_path);-- // Create a socket.- g_listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);- if (g_listen_fd == -1) {- return STATUS_FROM_ERRNO(); // `setsockopt` sets errno.- }-- // Record the sock_path so it can be unlinked at exit- g_sock_path = ess_strdup(unix_addr->esa_unix_path);- if (g_sock_path == NULL) {- return STATUS_FROM_ERRNO(); // `ess_strdup` sets errno.- }-- // Set socket receive low water mark.- if (setsockopt(g_listen_fd, SOL_SOCKET, SO_RCVLOWAT, &(int){1},- sizeof(int)) == -1) {- DEBUG_ERRNO("setsockopt() failed for SO_RCVLOWAT");- const int setsockopt_errno = errno; // `setsockopt` sets errno.- close(g_listen_fd);- g_listen_fd = -1;- errno = setsockopt_errno;- return STATUS_FROM_ERRNO();- }-- // Set socket send buffer size.- if (opts != NULL && opts->eso_sndbuf > 0) {- if (setsockopt(g_listen_fd, SOL_SOCKET, SO_SNDBUF, &opts->eso_sndbuf,- sizeof(opts->eso_sndbuf)) == -1) {- DEBUG_ERRNO("setsockopt() failed for SO_SNDBUF");- const int setsockopt_errno = errno; // `setsockopt` sets errno.- close(g_listen_fd);- g_listen_fd = -1;- errno = setsockopt_errno;- return STATUS_FROM_ERRNO();- }- }-- // Set socket linger.- if (opts != NULL && opts->eso_linger > 0) {- const struct linger so_linger = {- .l_onoff = true,- .l_linger = opts->eso_linger,- };- if (setsockopt(g_listen_fd, SOL_SOCKET, SO_LINGER, &so_linger,- sizeof(so_linger)) == -1) {- DEBUG_ERRNO("setsockopt() failed for SO_LINGER");- const int setsockopt_errno = errno; // `setsockopt` sets errno.- close(g_listen_fd);- g_listen_fd = -1;- errno = setsockopt_errno;- return STATUS_FROM_ERRNO();- }- }-- struct sockaddr_un local;- memset(&local, 0, sizeof(local));- local.sun_family = AF_UNIX;- strncpy(local.sun_path, unix_addr->esa_unix_path, sizeof(local.sun_path) - 1);- unlink(unix_addr->esa_unix_path);- if (bind(g_listen_fd, (struct sockaddr *)&local,- sizeof(struct sockaddr_un)) == -1) {- DEBUG_ERRNO("bind() failed");- return STATUS_FROM_ERRNO(); // `bind` sets errno.- }- return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);-}--/// @brief Initialize the TCP/IP socket and bind it to the provided address.-///-/// This function does not start any threads; @c worker_start() completes the-/// setup.-///-/// Either host or port may be NULL, in which case the defaults used by-/// @c getaddrinfo apply.-///-/// @return See `EventlogSocketStatus`.-///-/// @par Errors-/// @parblock-/// This function may return the following system errors:-/// `EADDRINUSE`, `EADDRNOTAVAIL`, `EAFNOSUPPORT`, `EAGAIN`, `EALREADY`,-/// `EBADF`, `EDESTADDRREQ`, `EDOM`, `EFAULT`, `EINPROGRESS`, `EINVAL`, `EIO`,-/// `EISCONN`, `EISDIR`, `ELOOP`, `EMFILE`, `ENAMETOOLONG`, `ENFILE`, `ENOBUFS`,-/// `ENOENT`, `ENOMEM`, `ENOPROTOOPT`, `ENOTDIR`, `ENOTSOCK`, `EOPNOTSUPP`,-/// `EPROTONOSUPPORT`, or `EROFS`.-///-/// This function may return the following @c getaddrinfo errors:-/// `EAI_ADDRFAMILY`, `EAI_AGAIN`, `EAI_BADFLAGS`, `EAI_FAIL`, `EAI_FAMILY`,-/// `EAI_MEMORY`, `EAI_NODATA`, `EAI_NONAME`, `EAI_SERVICE`, `EAI_SOCKTYPE`, or-/// `EAI_SYSTEM`.-/// @endparblock-static EventlogSocketStatus-worker_socket_init_inet(const EventlogSocketInetAddr *const inet_addr,- const EventlogSocketOpts *const opts) {- struct addrinfo hints;- memset(&hints, 0, sizeof(hints));- hints.ai_family = AF_UNSPEC;- hints.ai_socktype = SOCK_STREAM;- hints.ai_flags = AI_PASSIVE;-- struct addrinfo *res = NULL;- const int success_or_gai_error = getaddrinfo(- inet_addr->esa_inet_host, inet_addr->esa_inet_port, &hints, &res);- if (success_or_gai_error != 0) {- DEBUG_ERROR("getaddrinfo(\"%s\", \"%s\") failed: %s",- inet_addr->esa_inet_host, inet_addr->esa_inet_port,- gai_strerror(success_or_gai_error));- return STATUS_FROM_GAI_ERROR(success_or_gai_error);- }-- struct addrinfo *rp;- for (rp = res; rp != NULL; rp = rp->ai_next) {- g_listen_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);- if (g_listen_fd == -1) {- continue;- }-- // Set socket reuse address.- if (setsockopt(g_listen_fd, SOL_SOCKET, SO_REUSEADDR, &(int){1},- sizeof(int)) != 0) {- DEBUG_ERRNO("setsockopt() failed for SO_REUSEADDR");- close(g_listen_fd);- g_listen_fd = -1;- continue;- }-- // set socket send buffer size- if (opts != NULL && opts->eso_sndbuf > 0) {- if (setsockopt(g_listen_fd, SOL_SOCKET, SO_SNDBUF, &opts->eso_sndbuf,- sizeof(opts->eso_sndbuf)) != 0) {- DEBUG_ERRNO("setsockopt() failed for SO_SNDBUF");- close(g_listen_fd);- g_listen_fd = -1;- continue;- }- }-- if (bind(g_listen_fd, rp->ai_addr, rp->ai_addrlen) == -1) {- DEBUG_ERRNO("bind() failed");- close(g_listen_fd);- g_listen_fd = -1;- continue;- } else {- char hostbuf[NI_MAXHOST];- char servbuf[NI_MAXSERV];- if (getnameinfo(rp->ai_addr, rp->ai_addrlen, hostbuf, sizeof(hostbuf),- servbuf, sizeof(servbuf),- NI_NUMERICHOST | NI_NUMERICSERV) == 0) {- DEBUG_TRACE("Bound TCP listener to %s:%s", hostbuf, servbuf);- }- break; // success- }- }-- freeaddrinfo(res);-- if (g_listen_fd == -1) {- DEBUG_ERROR("%s", "Unable to bind TCP listener");- errno = EAGAIN;- return STATUS_FROM_ERRNO();- }-- return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);-}--/// @brief Initialize the worker thread.-///-/// @return See `EventlogSocketStatus`.-///-/// @par Errors-/// @parblock-/// This function may return the following system errors:-/// `EACCES`, `EAGAIN`, `EBADF`, `EFAULT`, `EINVAL`, `EMFILE`, `ENFILE`,-/// `ENFILE`, or `ENOPKG`.-/// @endparblock-static EventlogSocketStatus worker_init(void) {- if (!g_initialized) {- if (pipe(g_wake_pipe) == -1) {- DEBUG_ERRNO("pipe() failed");- return STATUS_FROM_ERRNO();- }- for (int i = 0; i < 2; i++) {- const int flags = fcntl(g_wake_pipe[i], F_GETFL, 0);- if (flags == -1) {- DEBUG_ERRNO("fcntl() failed for F_GETFL");- return STATUS_FROM_ERRNO();- }- if (fcntl(g_wake_pipe[i], F_SETFL, flags | O_NONBLOCK) == -1) {- DEBUG_ERRNO("fcntl() failed for F_SETFL");- return STATUS_FROM_ERRNO();- }- }- atexit(cleanup);- g_initialized = true;- }- return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);-}--/// @brief Start the worker thread.-///-/// @return Upon successful completion, 0 is returned.-///-/// @return On error, one of the following error codes is returned.-///-/// @par Errors-/// @parblock-/// `EAI_ADDRFAMILY`, `EAI_AGAIN`, `EAI_BADFLAGS`, `EAI_FAIL`, `EAI_FAMILY`,-/// `EAI_MEMORY`, `EAI_NODATA`, `EAI_NONAME`, `EAI_SERVICE`, `EAI_SOCKTYPE`, or-/// `EAI_SYSTEM`.-///-/// If `EAI_SYSTEM` is returned, errno is set to one of the following error-/// codes, in addition to any of the system errors that may be produced by-/// `getaddrinfo`:-///-/// `EACCES`, `EADDRINUSE`, `EADDRNOTAVAIL`, `EAFNOSUPPORT`, `EAGAIN`,-/// `EALREADY`, `EBADF`, `EDESTADDRREQ`, `EDOM`, `EFAULT`, `EINPROGRESS`,-/// `EINVAL`, `EIO`, `EISCONN`, `EISDIR`, `ELOOP`, `EMFILE`, `ENAMETOOLONG`,-/// `ENFILE`, `ENOBUFS`, `ENOENT`, `ENOMEM`, `ENOPKG`, `ENOPROTOOPT`, `ENOTDIR`,-/// `ENOTSOCK`, `EOPNOTSUPP`, `EPERM`, `EPROTONOSUPPORT`, or `EROFS`.-/// @endparblock-static EventlogSocketStatus-worker_start(const EventlogSocketAddr *const eventlog_socket_addr,- const EventlogSocketOpts *const eventlog_socket_opts) {- DEBUG_TRACE("%s", "Starting worker thread.");- switch (eventlog_socket_addr->esa_tag) {- case EVENTLOG_SOCKET_UNIX: {- RETURN_ON_ERROR(worker_socket_init_unix(- &eventlog_socket_addr->esa_unix_addr, eventlog_socket_opts));- break;- }- case EVENTLOG_SOCKET_INET: {- RETURN_ON_ERROR(worker_socket_init_inet(- &eventlog_socket_addr->esa_inet_addr, eventlog_socket_opts));- break;- }- default: {- DEBUG_ERROR("%s", "unknown listener kind");- errno = EINVAL;- return STATUS_FROM_ERRNO();- }- }-- // start the worker thread- g_listen_thread_ptr = (pthread_t *)malloc(sizeof(pthread_t));- const int success_or_errno =- pthread_create(g_listen_thread_ptr, NULL, worker_loop, NULL);- if (success_or_errno != 0) {- DEBUG_ERRNO("pthread_create() failed");- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);-}- /********************************************************************************* * Internal Helpers *********************************************************************************/@@ -798,12 +270,30 @@ EventlogSocketStatus eventlog_socket_init(const EventlogSocketAddr *const eventlog_socket_addr, const EventlogSocketOpts *const eventlog_socket_opts) {+ DEBUG_DEBUG("%s", "Initialising eventlog-socket."); - // Initialise worker thread.- RETURN_ON_ERROR(worker_init());+ // Check if this version of the GHC RTS supports the eventlog.+ if (eventLogStatus() == EVENTLOG_NOT_SUPPORTED) {+ DEBUG_ERROR("%s", "eventlog is not supported.");+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_ERR_RTS_NOSUPPORT);+ } // Start worker thread.- RETURN_ON_ERROR(worker_start(eventlog_socket_addr, eventlog_socket_opts));+ g_worker_thread_ptr = (pthread_t *)malloc(sizeof(pthread_t));+ if (g_worker_thread_ptr == NULL) {+ return STATUS_FROM_ERRNO(); // `malloc` sets errno.+ }+ const WorkerState worker_state = {+ .worker_thread_ptr = g_worker_thread_ptr,+ .client_fd_ptr = &g_client_fd,+ .write_buffer_ptr = &g_write_buffer,+ .mutex_ptr = &g_mutex,+ .init_state_ptr = &g_init_state,+ .new_connection_cond_ptr = &g_new_connection_cond,+ .ghc_rts_ready_cond_ptr = &g_ghc_rts_ready_cond,+ };+ RETURN_ON_ERROR(es_worker_init(worker_state));+ RETURN_ON_ERROR(es_worker_start(eventlog_socket_addr, eventlog_socket_opts)); // Start control thread. #ifdef EVENTLOG_SOCKET_FEATURE_CONTROL@@ -811,9 +301,14 @@ if (g_control_thread_ptr == NULL) { return STATUS_FROM_ERRNO(); // `malloc` sets errno. }- RETURN_ON_ERROR(control_start(g_control_thread_ptr, &g_client_fd,- &g_write_buffer_and_client_fd_mutex,- &g_new_conn_cond));+ const ControlState control_state = {+ .control_thread_ptr = g_control_thread_ptr,+ .client_fd_ptr = &g_client_fd,+ .mutex_ptr = &g_mutex,+ .init_state_ptr = &g_init_state,+ .new_connection_cond_ptr = &g_new_connection_cond,+ .ghc_rts_ready_cond_ptr = &g_ghc_rts_ready_cond};+ RETURN_ON_ERROR(es_control_start(control_state)); #endif /* EVENTLOG_SOCKET_FEATURE_CONTROL */ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);@@ -823,64 +318,73 @@ EventlogSocketStatus eventlog_socket_wait(void) { // Condition variable pairs with the mutex so reader threads can wait for the // worker to publish a connected client_fd atomically.- {- const int success_or_errno =- pthread_mutex_lock(&g_write_buffer_and_client_fd_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }- DEBUG_TRACE("initial client_fd=%d", g_client_fd);+ DEBUG_TRACE("%s", "Checking for connection.");+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(pthread_mutex_lock(&g_mutex)));+ DEBUG_TRACE("Old connection fd: %d", g_client_fd); while (g_client_fd == -1) {- DEBUG_TRACE("%s", "blocking for connection");- const int success_or_errno = pthread_cond_wait(- &g_new_conn_cond, &g_write_buffer_and_client_fd_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- DEBUG_TRACE("woke up, client_fd=%d", g_client_fd);- }- DEBUG_TRACE("proceeding with client_fd=%d", g_client_fd);- const int success_or_errno =- pthread_mutex_unlock(&g_write_buffer_and_client_fd_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);+ DEBUG_DEBUG("%s", "Waiting for connection.");+ RETURN_ON_ERROR_CLEANUP(STATUS_FROM_PTHREAD(pthread_cond_wait(+ &g_new_connection_cond, &g_mutex)),+ pthread_mutex_unlock(&g_mutex));+ DEBUG_TRACE("New connection fd: %d", g_client_fd); }+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(pthread_mutex_unlock(&g_mutex))); return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } /* PUBLIC - see documentation in eventlog_socket.h */-RtsConfig eventlog_socket_attach_rts_config(RtsConfig rts_config) {- rts_config.eventlog_writer = &SocketEventLogWriter;- return rts_config;-}--/* PUBLIC - see documentation in eventlog_socket.h */ EventlogSocketStatus eventlog_socket_signal_ghc_rts_ready(void) {-#ifdef EVENTLOG_SOCKET_FEATURE_CONTROL- return control_signal_ghc_rts_ready();-#else+ DEBUG_DEBUG("%s", "Received signal that the GHC RTS is ready.");++ // Acquire a lock on the global mutex.+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(pthread_mutex_lock(&g_mutex)));+ // If the RTS is not marked as ready...+ if (!(g_init_state & EVENTLOG_SOCKET_SIG_RTS_READY)) {+ // ...broadcast on the relevant condition and...+ RETURN_ON_ERROR_CLEANUP(+ STATUS_FROM_PTHREAD(pthread_cond_broadcast(&g_ghc_rts_ready_cond)),+ pthread_mutex_unlock(&g_mutex));+ // ...mark the GHC RTS as ready.+ g_init_state |= EVENTLOG_SOCKET_SIG_RTS_READY;+ }+ // Release the log on the global mutex.+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(pthread_mutex_unlock(&g_mutex))); return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);-#endif /* EVENTLOG_SOCKET_FEATURE_CONTROL */ } /* PUBLIC - see documentation in eventlog_socket.h */-void eventlog_socket_wrap_hs_main(int argc, char *argv[], RtsConfig rts_config,- StgClosure *main_closure) {+void eventlog_socket_wrap_hs_main(+ int argc, char *argv[], RtsConfig rts_config, StgClosure *main_closure,+ const EventlogSocketAddr *eventlog_socket_addr,+ const EventlogSocketOpts *eventlog_socket_opts) { SchedulerStatus status; int exit_status; + // Initialise eventlog socket.+ // TODO: handle error+ eventlog_socket_init(eventlog_socket_addr, eventlog_socket_opts);+ // Set the eventlog socket writer.- rts_config = eventlog_socket_attach_rts_config(rts_config);+ DEBUG_DEBUG("%s", "Attach the EventlogSocketWriter.");+ rts_config.eventlog_writer = &EventLogSocketWriter; // Initialize the GHC RTS.+ DEBUG_DEBUG("%s", "Initialise the GHC RTS."); hs_init_ghc(&argc, &argv, rts_config); - // Tell the control thread that the GHC RTS is initialised.- // TODO: print error message+ // Signal that the GHC RTS is initialised.+ // TODO: handle error+ DEBUG_DEBUG("%s", "Send signal that the GHC RTS is ready."); eventlog_socket_signal_ghc_rts_ready(); + // If eso_wait is set, wait.+ if (eventlog_socket_opts->eso_wait) {+ // TODO: handle error+ eventlog_socket_wait();+ }+ // Evaluate the Haskell main closure.+ DEBUG_DEBUG("%s", "Evaluate the Haskell main closure."); { Capability *cap = rts_lock(); rts_evalLazyIO(&cap, main_closure, NULL);@@ -889,6 +393,7 @@ } // Handle the return status.+ DEBUG_DEBUG("%s", "Handle the return status."); switch (status) { case Killed: errorBelch("main thread exited (uncaught exception)");@@ -912,47 +417,23 @@ shutdownHaskellAndExit(exit_status, 0 /* !fastExit */); } -/// @brief Start event logging with `SocketEventLogWriter` and start the control-/// thread.-///-/// @pre The GHC RTS is ready.-///-/// @pre The function `eventlog_socket_init` has been called.-EventlogSocketStatus eventlog_socket_attach(void) {- // Check if this version of the GHC RTS supports the eventlog.- if (eventLogStatus() == EVENTLOG_NOT_SUPPORTED) {- DEBUG_ERROR("%s", "eventlog is not supported.");- return STATUS_FROM_CODE(EVENTLOG_SOCKET_ERR_RTS_NOSUPPORT);- }-- // Stop the existing eventlog writer.- if (eventLogStatus() == EVENTLOG_RUNNING) {- endEventLogging();- }-- // Attach the `SocketEventLogWriter` eventlog writer.- if (!startEventLogging(&SocketEventLogWriter)) {- return STATUS_FROM_CODE(EVENTLOG_SOCKET_ERR_RTS_FAIL);- }-- return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);-}- /* PUBLIC - see documentation in eventlog_socket.h */ EventlogSocketStatus eventlog_socket_start(const EventlogSocketAddr *eventlog_socket_addr, const EventlogSocketOpts *eventlog_socket_opts) { - // Initialize eventlog_socket.+ // Initialize eventlog_socket with the local copy. RETURN_ON_ERROR( eventlog_socket_init(eventlog_socket_addr, eventlog_socket_opts)); - // Attach the eventlog writer to the GHC RTS.- RETURN_ON_ERROR(eventlog_socket_attach());- // Signal that the GHC RTS is ready. RETURN_ON_ERROR(eventlog_socket_signal_ghc_rts_ready()); + // If eventlog_socket_opts->eso_wait is set, wait for a connection.+ if (eventlog_socket_opts->eso_wait) {+ RETURN_ON_ERROR(eventlog_socket_wait());+ }+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } @@ -1023,9 +504,9 @@ } // Write the configuration:- char *unix_path_copy = ess_strndup(unix_path_len, unix_path);+ char *unix_path_copy = es_strndup(unix_path_len, unix_path); if (unix_path_copy == NULL) {- return STATUS_FROM_ERRNO(); // `ess_strndup` sets errno.+ return STATUS_FROM_ERRNO(); // `es_strndup` sets errno. } *eventlog_socket_addr_out = (EventlogSocketAddr){ .esa_tag = EVENTLOG_SOCKET_UNIX,@@ -1049,18 +530,18 @@ if (has_inet_host || has_inet_port) { // Copy the inet_host: char *inet_host_copy = (has_inet_host)- ? ess_strndup(strlen(inet_host), inet_host)- : ess_strndup(0, "");+ ? es_strndup(strlen(inet_host), inet_host)+ : es_strndup(0, ""); if (inet_host_copy == NULL) {- return STATUS_FROM_ERRNO(); // `ess_strndup` sets errno.+ return STATUS_FROM_ERRNO(); // `es_strndup` sets errno. } // Copy the inet_port: char *inet_port_copy = (has_inet_port)- ? ess_strndup(strlen(inet_port), inet_port)- : ess_strndup(0, "");+ ? es_strndup(strlen(inet_port), inet_port)+ : es_strndup(0, ""); if (inet_port_copy == NULL) { free(inet_host_copy);- return STATUS_FROM_ERRNO(); // `ess_strndup` sets errno.+ return STATUS_FROM_ERRNO(); // `es_strndup` sets errno. } // Write the configuration: *eventlog_socket_addr_out =@@ -1093,7 +574,7 @@ const char *eventlog_socket_control_strnamespace( EventlogSocketControlNamespace *namespace) { #ifdef EVENTLOG_SOCKET_FEATURE_CONTROL- return control_strnamespace(namespace);+ return es_control_strnamespace(namespace); #else (void)namespace; return NULL;@@ -1105,7 +586,7 @@ uint8_t namespace_len, const char namespace[namespace_len], EventlogSocketControlNamespace **namespace_out) { #ifdef EVENTLOG_SOCKET_FEATURE_CONTROL- return control_register_namespace(namespace_len, namespace, namespace_out);+ return es_control_register_namespace(namespace_len, namespace, namespace_out); #else (void)namespace_len; (void)namespace;@@ -1121,13 +602,29 @@ EventlogSocketControlCommandHandler *command_handler, const void *command_data) { #ifdef EVENTLOG_SOCKET_FEATURE_CONTROL- return control_register_command(namespace, command_id, command_handler,- command_data);+ return es_control_register_command(namespace, command_id, command_handler,+ command_data); #else (void)namespace; (void)command_id; (void)command_handler; (void)command_data; return STATUS_FROM_CODE(EVENTLOG_SOCKET_ERR_CTL_NOSUPPORT);+#endif /* EVENTLOG_SOCKET_FEATURE_CONTROL */+}++/* PUBLIC - see documentation in eventlog_socket.h */+EventlogSocketStatus eventlog_socket_worker_status(void) {+ DEBUG_DEBUG("%s", "Reading worker status.");+ return es_worker_status();+}++/* PUBLIC - see documentation in eventlog_socket.h */+EventlogSocketStatus eventlog_socket_control_status(void) {+ DEBUG_DEBUG("%s", "Reading control status.");+#ifdef EVENTLOG_SOCKET_FEATURE_CONTROL+ return es_control_status();+#else+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); #endif /* EVENTLOG_SOCKET_FEATURE_CONTROL */ }
cbits/eventlog_socket/control.c view
@@ -7,7 +7,7 @@ /// and `eventlog_socket_control_register_command`. /// @author Wen Kokke /// @author Matthew Pickering-/// @version 0.1.1.0+/// @version 0.1.2.0 /// @date 2025-2026 /// @copyright BSD-3-Clause License. ///@@ -36,6 +36,7 @@ #include "./error.h" #include "./poll.h" #include "eventlog_socket.h"+#include "init_state.h" // CONTROL_MAGIC should be the UTF-8 encoding of some code point between // U+010000 and U+10FFFF. Let's pick code point U+01E5CC, for Eventlog@@ -96,7 +97,7 @@ ******************************************************************************/ /// @brief Handler for "StartHeapProfiling" command (eventlog-socket::0).-static void control_start_heap_profiling(+static void es_control_start_heap_profiling( const EventlogSocketControlNamespace *const namespace, const EventlogSocketControlCommandId command_id, const void *user_data) { (void)namespace;@@ -107,7 +108,7 @@ } /// @brief Handler for "StopHeapProfiling" command (eventlog-socket::1).-static void control_stop_heap_profiling(+static void es_control_stop_heap_profiling( const EventlogSocketControlNamespace *const namespace, const EventlogSocketControlCommandId command_id, const void *user_data) { (void)namespace;@@ -118,7 +119,7 @@ } /// @brief Handler for "RequestHeapCensus" command (eventlog-socket::2).-static void control_request_heap_census(+static void es_control_request_heap_census( const EventlogSocketControlNamespace *const namespace, const EventlogSocketControlCommandId command_id, const void *user_data) { (void)namespace;@@ -186,25 +187,25 @@ /// See `eventlog_socket_control_namespace`. /// /// This is initialised with the builtin namespace and the builtin commands.-EventlogSocketControlNamespace g_control_namespace_registry = {+static EventlogSocketControlNamespace g_control_namespace_registry = { .namespace = BUILTIN_NAMESPACE, .namespace_len = strlen(BUILTIN_NAMESPACE), .next = NULL, .command_registry = &(EventlogSocketControlCommand){ .command_id = BUILTIN_COMMAND_ID_START_HEAP_PROFILING,- .command_handler = control_start_heap_profiling,+ .command_handler = es_control_start_heap_profiling, .command_data = NULL, .next = &(EventlogSocketControlCommand){ .command_id = BUILTIN_COMMAND_ID_STOP_HEAP_PROFILING,- .command_handler = control_stop_heap_profiling,+ .command_handler = es_control_stop_heap_profiling, .command_data = NULL, .next = &(EventlogSocketControlCommand){ .command_id = BUILTIN_COMMAND_ID_REQUEST_HEAP_CENSUS,- .command_handler = control_request_heap_census,+ .command_handler = es_control_request_heap_census, .command_data = NULL, .next = NULL, },@@ -215,13 +216,82 @@ /// @brief The mutex that guards the global namespace registry. /// /// See `g_control_namespace_registry`.-pthread_mutex_t g_control_namespace_registry_mutex = PTHREAD_MUTEX_INITIALIZER;+static pthread_mutex_t g_control_namespace_registry_mutex =+ PTHREAD_MUTEX_INITIALIZER; +/******************************************************************************+ * Control Thread Variables and Entry-Point+ *****************************************************************************/++/// @brief The entry-point for the control thread.+static void *es_control_loop(void *arg);++/// @brief The control thread reads chunks of this size from the eventlog+/// socket.+#define CHUNK_SIZE 256++/// @brief The state that is shared with the control thread.+///+/// **NOTE**: Initialising with @c {0} should guarantee that all pointers are+/// NULL pointers.+static ControlState g_control_state = {0};++/// @brief A stable view the eventlog socket file descriptor.+///+/// See `g_control_state`.+static int g_client_fd = -1;++/******************************************************************************+ * Public API+ *+ * These functions run in user threads. They should return a status on error.+ ******************************************************************************/++/// @brief Control cleanup.+static void es_control_cleanup(void) {+ if (g_control_state.control_thread_ptr != NULL) {+ DEBUG_DEBUG("%s", "Cancelling control thread.");+ if (pthread_cancel(*g_control_state.control_thread_ptr) != 0) {+ DEBUG_ERRNO("pthread_cancel() failed for control thread");+ } else {+ if (pthread_join(*g_control_state.control_thread_ptr, NULL) != 0) {+ DEBUG_ERRNO("pthread_join() failed for control thread");+ }+ }+ free((void *)g_control_state.control_thread_ptr);+ }+}++/* HIDDEN - see documentation in control.h */+HIDDEN EventlogSocketStatus es_control_start(const ControlState control_state) {+ assert(control_state.control_thread_ptr != NULL);+ assert(control_state.client_fd_ptr != NULL);+ assert(control_state.mutex_ptr != NULL);+ assert(control_state.init_state_ptr != NULL);+ assert(control_state.new_connection_cond_ptr != NULL);+ assert(control_state.ghc_rts_ready_cond_ptr != NULL);+ DEBUG_DEBUG("%s", "Starting control thread.");+ memcpy(&g_control_state, &control_state, sizeof(ControlState));+ assert(g_control_state.control_thread_ptr != NULL);+ assert(g_control_state.client_fd_ptr != NULL);+ assert(g_control_state.mutex_ptr != NULL);+ assert(g_control_state.init_state_ptr != NULL);+ assert(g_control_state.new_connection_cond_ptr != NULL);+ assert(g_control_state.ghc_rts_ready_cond_ptr != NULL);+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(pthread_create(+ g_control_state.control_thread_ptr, NULL, es_control_loop, NULL)));+ RETURN_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_detach(*g_control_state.control_thread_ptr)));+ atexit(es_control_cleanup);+ // Return OK.+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);+}+ /// @brief Check if the given entry in the namespace registry matches a given /// name. /// /// @pre namespace_entry != NULL-bool control_namespace_store_match(+static bool es_control_namespace_store_match( const EventlogSocketControlNamespace *const namespace_entry, const size_t namespace_len, const char namespace[namespace_len]) { // if namespace_entry == NULL, then...@@ -239,45 +309,14 @@ return namespace_cmp == 0; } -/// @brief Resolve a namespace by name.-///-/// @return If the namespace is found, this function returns a stable pointer to-/// it. Otherwise, it returns NULL. The returned pointer should not be freed.-const EventlogSocketControlNamespace *-control_namespace_store_resolve(const size_t namespace_len,- const char namespace[namespace_len]) {-- // Acquire a lock on g_control_namespace_registry.- pthread_mutex_lock(&g_control_namespace_registry_mutex);-- // Initialise the namespace_entry pointer.- EventlogSocketControlNamespace *namespace_entry =- &g_control_namespace_registry;-- while (namespace_entry != NULL) {- // Is this the namespace you are looking for?- if (control_namespace_store_match(namespace_entry, namespace_len,- namespace)) {- // Release the lock on g_control_namespace_registry.- pthread_mutex_unlock(&g_control_namespace_registry_mutex);- return namespace_entry;- }- // Otherwise, continue with the next namespace_entry.- namespace_entry = namespace_entry->next;- }- // Release the lock on g_control_namespace_registry.- pthread_mutex_unlock(&g_control_namespace_registry_mutex);- return false;-}- /* HIDDEN - see documentation in control.h */ HIDDEN const char *-control_strnamespace(EventlogSocketControlNamespace *namespace) {+es_control_strnamespace(EventlogSocketControlNamespace *namespace) { return namespace == NULL ? NULL : namespace->namespace; } /* HIDDEN - see documentation in control.h */-HIDDEN EventlogSocketStatus control_register_namespace(+HIDDEN EventlogSocketStatus es_control_register_namespace( const uint8_t namespace_len, const char namespace[namespace_len], EventlogSocketControlNamespace **namespace_out) { @@ -288,13 +327,8 @@ } // Acquire the lock on g_control_namespace_registry.- {- const int success_or_errno =- pthread_mutex_lock(&g_control_namespace_registry_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_lock(&g_control_namespace_registry_mutex))); // Initialise the namespace_entry pointer. EventlogSocketControlNamespace *namespace_entry =@@ -303,8 +337,8 @@ // Is the requested namespace already registered? do { // Is this the namespace you are trying to register?- if (control_namespace_store_match(namespace_entry, namespace_len,- namespace)) {+ if (es_control_namespace_store_match(namespace_entry, namespace_len,+ namespace)) { // If so, return false. pthread_mutex_unlock(&g_control_namespace_registry_mutex); return STATUS_FROM_CODE(EVENTLOG_SOCKET_ERR_CTL_EXISTS);@@ -316,12 +350,20 @@ } // Otherwise, continue with the next entry. namespace_entry = namespace_entry->next;++ // Ensure the loop has a cancellation point.+ pthread_testcancel(); } while (true); // Register the requested namespace. assert(namespace_entry != NULL); assert(namespace_entry->next == NULL); char *const next_namespace = malloc(namespace_len + 1);+ if (next_namespace == NULL) {+ // If so, return false.+ pthread_mutex_unlock(&g_control_namespace_registry_mutex);+ return STATUS_FROM_ERRNO(); // `malloc` sets errno.+ } strncpy(next_namespace, namespace, namespace_len); next_namespace[namespace_len] = '\0'; const EventlogSocketControlNamespace next = (EventlogSocketControlNamespace){@@ -330,29 +372,30 @@ .next = NULL, }; namespace_entry->next = malloc(sizeof(EventlogSocketControlNamespace));+ if (namespace_entry->next == NULL) {+ // If so, return false.+ pthread_mutex_unlock(&g_control_namespace_registry_mutex);+ return STATUS_FROM_ERRNO(); // `malloc` sets errno.+ } memcpy(namespace_entry->next, &next, sizeof(EventlogSocketControlNamespace)); DEBUG_DEBUG("Registered namespace %.*s", (int)namespace_len, namespace); // Release the lock on g_control_namespace_registry.- {- const int success_or_errno =- pthread_mutex_unlock(&g_control_namespace_registry_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_unlock(&g_control_namespace_registry_mutex))); - // Return the namespace entry.+ // Write out the namespace entry. *namespace_out = namespace_entry->next;+ // Return OK. return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } /* HIDDEN - see documentation in control.h */ HIDDEN EventlogSocketStatus-control_register_command(EventlogSocketControlNamespace *const namespace,- const EventlogSocketControlCommandId command_id,- EventlogSocketControlCommandHandler command_handler,- const void *command_data) {+es_control_register_command(EventlogSocketControlNamespace *const namespace,+ const EventlogSocketControlCommandId command_id,+ EventlogSocketControlCommandHandler command_handler,+ const void *command_data) { // Check if namespace is NULL. if (namespace == NULL) {@@ -370,13 +413,8 @@ command_id, namespace->namespace_len, namespace->namespace); // Acquire the lock on g_control_namespace_registry.- {- const int success_or_errno =- pthread_mutex_lock(&g_control_namespace_registry_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_lock(&g_control_namespace_registry_mutex))); // Create the data for the new command_entry. const EventlogSocketControlCommand next = (EventlogSocketControlCommand){@@ -393,11 +431,12 @@ if (namespace->command_registry == NULL) { // Allocate memory for the new command_entry. namespace->command_registry = malloc(sizeof(EventlogSocketControlCommand));- command_entry = namespace->command_registry;- if (command_entry == NULL) {+ if (namespace->command_registry == NULL) {+ // If so, return false. pthread_mutex_unlock(&g_control_namespace_registry_mutex);- return STATUS_FROM_ERRNO();+ return STATUS_FROM_ERRNO(); // `malloc` sets errno. }+ command_entry = namespace->command_registry; } // Otherwise, traverse the command_store to the last position... else {@@ -419,11 +458,11 @@ // Allocate memory for the new command_entry. command_entry->next = malloc(sizeof(EventlogSocketControlCommand));- command_entry = command_entry->next;- if (command_entry == NULL) {+ if (command_entry->next == NULL) { pthread_mutex_unlock(&g_control_namespace_registry_mutex);- return STATUS_FROM_ERRNO();+ return STATUS_FROM_ERRNO(); // `malloc` sets errno. }+ command_entry = command_entry->next; } // Write the data for the new command_entry. DEBUG_TRACE("Registered command 0x%02x in namespace %.*s", command_id,@@ -431,20 +470,88 @@ memcpy(command_entry, &next, sizeof(EventlogSocketControlCommand)); // Release the lock on g_control_namespace_registry.- {- const int success_or_errno =- pthread_mutex_unlock(&g_control_namespace_registry_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_unlock(&g_control_namespace_registry_mutex)));+ // Return OK.+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);+}++/******************************************************************************+ * Control Status+ ******************************************************************************/++/// @brief The current status of the control thread.+static EventlogSocketStatus g_status = STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);++/// @brief The mutex that protects @c g_status.+static pthread_mutex_t g_status_mutex = PTHREAD_MUTEX_INITIALIZER;++/* HIDDEN - see documentation in control.h */+HIDDEN EventlogSocketStatus es_control_status(void) {+ pthread_mutex_lock(&g_status_mutex);+ const EventlogSocketStatus status = g_status;+ pthread_mutex_unlock(&g_status_mutex);+ return status;+}++/******************************************************************************+ * Control Thread+ *+ * These functions run in the control thread. Most of these function should+ * return a status on error, with the exception of `es_control_loop`.+ ******************************************************************************/++/// @brief Resolve a namespace by name.+///+/// @return Upon successful completion, the function returns a status with code+/// `EVENTLOG_SOCKET_OK`. If the namespace was found, then @p namespace_out+/// contains a stable pointer to the namespace that should not be freed. If the+/// namespace was not found and @p namespace_out contains @c NULL.+static EventlogSocketStatus es_control_namespace_store_resolve(+ const size_t namespace_len, const char namespace[namespace_len],+ EventlogSocketControlNamespace **namespace_out) {++ // Acquire a lock on g_control_namespace_registry.+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_lock(&g_control_namespace_registry_mutex)));++ // Initialise the namespace_entry pointer.+ EventlogSocketControlNamespace *namespace_entry =+ &g_control_namespace_registry;++ while (namespace_entry != NULL) {+ // Is this the namespace you are looking for?+ if (es_control_namespace_store_match(namespace_entry, namespace_len,+ namespace)) {++ // Release the lock on g_control_namespace_registry.+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_unlock(&g_control_namespace_registry_mutex)));++ // Write out the namespace.+ *namespace_out = namespace_entry;++ // Return OK.+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); }+ // Otherwise, continue with the next namespace_entry.+ namespace_entry = namespace_entry->next; }+ // Release the lock on g_control_namespace_registry.+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_unlock(&g_control_namespace_registry_mutex)));++ // Write out NULL.+ *namespace_out = NULL;++ // Return OK. return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } /// @brief Call a command by namespace and ID.-static bool-control_command_handle(const EventlogSocketControlNamespace *const namespace,- const EventlogSocketControlCommandId command_id) {+static EventlogSocketStatus+es_control_command_handle(const EventlogSocketControlNamespace *const namespace,+ const EventlogSocketControlCommandId command_id) { assert(namespace != NULL); @@ -452,13 +559,8 @@ namespace->namespace_len, namespace->namespace); // Acquire the lock on g_control_namespace_registry.- {- const int success_or_errno =- pthread_mutex_lock(&g_control_namespace_registry_mutex);- if (success_or_errno != 0) {- return false;- }- }+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_lock(&g_control_namespace_registry_mutex))); // Traverse the command_registry to find the command.... {@@ -471,9 +573,10 @@ command_entry->command_handler(namespace, command_id, command_entry->command_data); // ...release the lock on g_control_namespace_registry...- pthread_mutex_unlock(&g_control_namespace_registry_mutex);- // ...and return true.- return true;+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_unlock(&g_control_namespace_registry_mutex)));+ // ...and return OK.+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } // Otherwise, continue with the next command... command_entry = command_entry->next;@@ -484,63 +587,11 @@ DEBUG_ERROR("Could not resolve command 0x%02x in namespace %.*s", command_id, namespace->namespace_len, namespace->namespace); // ...release the lock on g_control_namespace_registry...- {- const int success_or_errno =- pthread_mutex_unlock(&g_control_namespace_registry_mutex);- if (success_or_errno != 0) {- return false;- }- }- // ...and return false.- return false;-}--/******************************************************************************- * Waiting for the GHC RTS- ******************************************************************************/--/// @brief A global variable that tracks whether the GHC RTS is ready.-static volatile bool g_ghc_rts_ready = false;--/// @brief The condition on which to wait for the signal that the GHC RTS is-/// ready.-static pthread_cond_t g_ghc_rts_ready_cond = PTHREAD_COND_INITIALIZER;--/// @brief The mutex that corresponds to `g_ghc_rts_ready_cond`.-static pthread_mutex_t g_ghc_rts_ready_mutex = PTHREAD_MUTEX_INITIALIZER;--/// @brief Wait for the signal that the GHC RTS is ready.-static void control_wait_ghc_rts_ready(void) {- DEBUG_DEBUG("%s", "Waiting for signal that GHC RTS is ready.");- pthread_mutex_lock(&g_ghc_rts_ready_mutex);- while (!g_ghc_rts_ready) {- pthread_cond_wait(&g_ghc_rts_ready_cond, &g_ghc_rts_ready_mutex);- }- pthread_mutex_unlock(&g_ghc_rts_ready_mutex);-}--/* HIDDEN - see documentation in control.h */-HIDDEN EventlogSocketStatus control_signal_ghc_rts_ready(void) {- DEBUG_DEBUG("%s", "Sending signal that GHC RTS is ready.");- {- const int success_or_errno = pthread_mutex_lock(&g_ghc_rts_ready_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }- if (!g_ghc_rts_ready) {- g_ghc_rts_ready = true;- const int success_or_errno = pthread_cond_broadcast(&g_ghc_rts_ready_cond);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }- {- const int success_or_errno = pthread_mutex_unlock(&g_ghc_rts_ready_mutex);- if (success_or_errno != 0) {- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(+ pthread_mutex_unlock(&g_control_namespace_registry_mutex)));+ // ...and return OK.+ // NOTE: Other than debug logging, there is no distinction between "found" and+ // "not found", because the control thread must be robust against noise. return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } @@ -568,7 +619,8 @@ /// @brief Show a value of type `ControlCommandParserStateTag` as a /// string.-const char *ControlCommandParserStateag_show(ControlCommandParserStateTag tag) {+static const char *+es_show_ControlCommandParserStateTag(ControlCommandParserStateTag tag) { switch (tag) { case CONTROL_COMMAND_PARSER_STATE_MAGIC: return "CONTROL_COMMAND_PARSER_STATE_MAGIC";@@ -635,7 +687,7 @@ } ControlCommandParserState; /// @brief The global command parser state.-ControlCommandParserState g_control_command_parser_state = {+static ControlCommandParserState g_control_command_parser_state = { .tag = CONTROL_COMMAND_PARSER_STATE_MAGIC, .header_pos = 0, };@@ -655,13 +707,13 @@ /// If the target tag is `CONTROL_COMMAND_PARSER_STATE_NAMESPACE` state, then a /// pointer to the namespace length may be provided as the second argument. This /// function will use this byte to initialise the new state.-static void-control_command_parser_enter_state(const ControlCommandParserStateTag tag,- const uint8_t *const data) {+static EventlogSocketStatus+es_control_command_parser_enter_state(const ControlCommandParserStateTag tag,+ const uint8_t *const data) { DEBUG_TRACE( "%s -> %s",- ControlCommandParserStateag_show(g_control_command_parser_state.tag),- ControlCommandParserStateag_show(tag));+ es_show_ControlCommandParserStateTag(g_control_command_parser_state.tag),+ es_show_ControlCommandParserStateTag(tag)); // this should only be called when restarting or moving to a different // state...@@ -708,6 +760,9 @@ // allocate space for the namespace, with one additional byte to ensure // that the string is always null-terminated in memory. g_control_command_parser_state.namespace_buffer = malloc(namespace_len + 1);+ if (g_control_command_parser_state.namespace_buffer == NULL) {+ RETURN_ON_ERROR(STATUS_FROM_ERRNO()); // `malloc` sets errno.+ } g_control_command_parser_state.namespace_buffer[namespace_len] = '\0'; break; }@@ -716,15 +771,16 @@ break; } }+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } /// @brief Parse a chunk. /// /// This is the incremental command parser. It parses a chunk of bytes and /// updates the command parser state.-static void-control_command_parser_handle_chunk(const size_t chunk_size,- const uint8_t chunk[chunk_size]) {+static EventlogSocketStatus+es_control_command_parser_handle_chunk(const size_t chunk_size,+ const uint8_t chunk[chunk_size]) { DEBUG_DEBUG("Received chunk of size %zd.", chunk_size); // iterate over the bytes in the chunk... for (size_t chunk_index = 0; chunk_index < chunk_size; ++chunk_index) {@@ -747,8 +803,8 @@ // if header_pos moves out of control_magic... if (g_control_command_parser_state.header_pos >= CONTROL_MAGIC_LEN) { // ...continue reading the namespace length...- control_command_parser_enter_state(- CONTROL_COMMAND_PARSER_STATE_PROTOCOL_VERSION, NULL);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_PROTOCOL_VERSION, NULL)); } // ...continue processing with the _next_ byte... continue;@@ -757,8 +813,8 @@ else { // ...there has been a protocol error... // ...restart with the _current_ byte...- control_command_parser_enter_state(CONTROL_COMMAND_PARSER_STATE_MAGIC,- ¤t_byte);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_MAGIC, ¤t_byte)); // ...continue processing with the _next_ byte... continue; }@@ -770,14 +826,14 @@ if (current_byte == EVENTLOG_SOCKET_CONTROL_PROTOCOL_VERSION) { // ...then we should be able to parse the message... // ...continue processing with the _next_ byte...- control_command_parser_enter_state(- CONTROL_COMMAND_PARSER_STATE_NAMESPACE_LEN, NULL);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_NAMESPACE_LEN, NULL)); continue; } else { // ...otherwise, let's not try and parse this message... // ...restart with the _current_ byte...- control_command_parser_enter_state(CONTROL_COMMAND_PARSER_STATE_MAGIC,- ¤t_byte);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_MAGIC, ¤t_byte)); } } // the parser is currently reading the namespace length...@@ -789,14 +845,14 @@ // todo: write an error to the eventlog DEBUG_ERROR("%s", "Received namespace length 0"); // ...restart with the _current_ byte...- control_command_parser_enter_state(CONTROL_COMMAND_PARSER_STATE_MAGIC,- ¤t_byte);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_MAGIC, ¤t_byte)); continue; } else { DEBUG_DEBUG("Matched namespace_len byte %d", current_byte); // otherwise, accept the namespace length and move to the next state...- control_command_parser_enter_state(- CONTROL_COMMAND_PARSER_STATE_NAMESPACE, ¤t_byte);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_NAMESPACE, ¤t_byte)); continue; } }@@ -851,10 +907,10 @@ g_control_command_parser_state.namespace_buffer); // ...try to resolve the namespace...- const EventlogSocketControlNamespace *namespace =- control_namespace_store_resolve(- g_control_command_parser_state.namespace_buffer_len,- g_control_command_parser_state.namespace_buffer);+ EventlogSocketControlNamespace *namespace = NULL;+ RETURN_ON_ERROR(es_control_namespace_store_resolve(+ g_control_command_parser_state.namespace_buffer_len,+ g_control_command_parser_state.namespace_buffer, &namespace)); // if the namespace was successfully resolved, then... if (namespace != NULL) { DEBUG_DEBUG("Resolved namespace %.*s",@@ -865,8 +921,8 @@ // note: the subtraction is safe because available_bytes > 0 chunk_index += available_bytes - 1; // ...move to the next state...- control_command_parser_enter_state(- CONTROL_COMMAND_PARSER_STATE_COMMAND_ID, NULL);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_COMMAND_ID, NULL)); g_control_command_parser_state.namespace = namespace; // ...continue processing with the _next_ byte... continue;@@ -903,12 +959,12 @@ // + '\0' // // todo: write an error to the eventlog- DEBUG_ERROR("unknown namespace %.*s",+ DEBUG_ERROR("Unknown namespace %.*s", g_control_command_parser_state.namespace_buffer_len, g_control_command_parser_state.namespace_buffer); // ...restart with the _current_ byte...- control_command_parser_enter_state(CONTROL_COMMAND_PARSER_STATE_MAGIC,- ¤t_byte);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_MAGIC, ¤t_byte)); // ...continue processing with the _next_ byte... continue; }@@ -916,144 +972,170 @@ case CONTROL_COMMAND_PARSER_STATE_COMMAND_ID: { DEBUG_DEBUG("Matched command_id byte 0x%02x", current_byte); // Handle the command.- control_command_handle(g_control_command_parser_state.namespace,- current_byte);+ RETURN_ON_ERROR(es_control_command_handle(+ g_control_command_parser_state.namespace, current_byte)); // ...restart _without_ the current byte...- control_command_parser_enter_state(CONTROL_COMMAND_PARSER_STATE_MAGIC,- NULL);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_MAGIC, NULL)); // ...continue processing with the _next_ byte... continue; } } }+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } /****************************************************************************** * Control Thread ******************************************************************************/ -/// @brief The control thread reads chunks of this size from the eventlog-/// socket.-#define CHUNK_SIZE 256--/// @brief A volatile view of the eventlog socket file descriptor.-///-/// This file descriptor is *not* managed by the control thread.-static const volatile int *g_control_fd_ptr = NULL;--/// @brief A pointer to the mutex that guards the eventlog socket file-/// descriptor.-///-/// See `g_control_fd_ptr`.-static pthread_mutex_t *g_control_fd_mutex_ptr = NULL;--/// @brief A pointer to the condition used to signal a new connection on the-/// eventlog socket file descriptor.-///-/// This condition should be used with `g_control_fd_mutex_ptr`.-static pthread_cond_t *g_new_conn_cond_ptr = NULL;--/// @brief A stable view the eventlog socket file descriptor.-///-/// See `g_control_fd_ptr`.-static int g_control_fd = -1;- /// Reset the control thread state when the connection changes. /// /// @param new_control_fd The new eventlog socket file descriptor. May be `-1`.-static void control_fd_reset_to(const int new_control_fd) {+static EventlogSocketStatus es_control_fd_reset_to(const int new_control_fd) { DEBUG_DEBUG("%s", "Resetting control server state."); // Reset eventlog socket file descriptor.- g_control_fd = new_control_fd;+ g_client_fd = new_control_fd; // Reset parser state.- control_command_parser_enter_state(CONTROL_COMMAND_PARSER_STATE_MAGIC, NULL);+ RETURN_ON_ERROR(es_control_command_parser_enter_state(+ CONTROL_COMMAND_PARSER_STATE_MAGIC, NULL));+ // Return OK.+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } +/// @brief Wait for the signal that the GHC RTS is ready.+static EventlogSocketStatus es_control_wait_ghc_rts_ready(void) {+ DEBUG_DEBUG("%s", "Waiting for signal that GHC RTS is ready.");+ assert(g_control_state.init_state_ptr != NULL);+ assert(g_control_state.mutex_ptr != NULL);+ assert(g_control_state.ghc_rts_ready_cond_ptr != NULL);+ RETURN_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_lock(g_control_state.mutex_ptr)));+ while (true) {+ // Check whether or not the GHC RTS is ready.+ const bool ghc_rts_ready =+ (*g_control_state.init_state_ptr) & EVENTLOG_SOCKET_SIG_RTS_READY;+ if (ghc_rts_ready) {+ // If the GHC RTS is ready, break from the loop.+ break;+ } else {+ // If the GHC RTS is NOT ready, wait on the relevant condition and+ // re-enter the loop.+ //+ // NOTE: This call acts as the cancellation point for this infinite loop.+ RETURN_ON_ERROR_CLEANUP(STATUS_FROM_PTHREAD(pthread_cond_wait(+ g_control_state.ghc_rts_ready_cond_ptr,+ g_control_state.mutex_ptr)),+ pthread_mutex_unlock(g_control_state.mutex_ptr));+ }+ }+ RETURN_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_unlock(g_control_state.mutex_ptr)));+ // Return OK.+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);+}+ /// @brief Wait for a new connection. /// /// @pre The caller must have a lock on `g_control_fd_mutex_ptr`. /// @post The caller will have a lock on `g_control_fd_mutex_ptr`.-static void control_fd_wait_for_connection(void) {+static EventlogSocketStatus es_control_wait_for_connection(void) {+ assert(g_control_state.mutex_ptr != NULL);+ assert(g_control_state.new_connection_cond_ptr != NULL); DEBUG_DEBUG("%s", "Waiting to be notified of new connection.");- pthread_cond_wait(g_new_conn_cond_ptr, g_control_fd_mutex_ptr);+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(pthread_cond_wait(+ g_control_state.new_connection_cond_ptr, g_control_state.mutex_ptr)));+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); } -static void *control_loop(void *arg) {- (void)arg;+/// @brief The memory for the current chunk.+static uint8_t chunk[CHUNK_SIZE] = {0}; - assert(g_control_fd_ptr != NULL);- assert(g_control_fd_mutex_ptr != NULL);- assert(g_new_conn_cond_ptr != NULL);+/// @brief The entry-point for the control thread.+static void *es_control_loop(void *arg) {+ (void)arg; - // Allocate memory for chunks:- uint8_t *const chunk = malloc(CHUNK_SIZE);+ assert(g_control_state.client_fd_ptr != NULL);+ assert(g_control_state.mutex_ptr != NULL);+ assert(g_control_state.new_connection_cond_ptr != NULL); // Wait for the GHC RTS to become ready.- control_wait_ghc_rts_ready();+ EXIT_ON_ERROR(es_control_wait_ghc_rts_ready()); /* BEGIN: The main control loop. */ while (true) { DEBUG_TRACE("%s", "Starting new control iteration."); + // Ensure the loop has a cancellation point.+ pthread_testcancel();+ /* BEGIN: Wake up. */ // At the start of each control iteration, we update the eventlog socket // file descriptor. // Acquire the lock on the connection file description.- pthread_mutex_lock(g_control_fd_mutex_ptr);+ EXIT_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_lock(g_control_state.mutex_ptr))); // Read current connection file description.- const int new_control_fd = *g_control_fd_ptr;- if (g_control_fd != new_control_fd) {- DEBUG_TRACE("Old connection fd: %d", g_control_fd);+ const int new_control_fd = *g_control_state.client_fd_ptr;+ if (g_client_fd != new_control_fd) {+ DEBUG_TRACE("Old connection fd: %d", g_client_fd); DEBUG_TRACE("New connection fd: %d", new_control_fd); } // If there WAS NO connection and there IS NO connection, then...- if (g_control_fd == -1 && new_control_fd == -1) {+ if (g_client_fd == -1 && new_control_fd == -1) { DEBUG_TRACE("%s", "There WAS NO connection and there IS NO connection."); // ...wait to be notified of a new connection...- control_fd_wait_for_connection();+ EXIT_ON_ERROR_CLEANUP(es_control_wait_for_connection(),+ pthread_mutex_unlock(g_control_state.mutex_ptr)); // ...release the lock...- pthread_mutex_unlock(g_control_fd_mutex_ptr);+ EXIT_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_unlock(g_control_state.mutex_ptr))); // ...and re-enter the loop. continue; } // If there WAS NO connection but there IS A connection, then...- else if (g_control_fd == -1 && new_control_fd != -1) {+ else if (g_client_fd == -1 && new_control_fd != -1) { DEBUG_TRACE("%s", "There WAS NO connection but there IS A connection."); // ...DON'T wait to be notified of a new connection... // ...we may we have already missed the signal... // ...reset the control server state...- control_fd_reset_to(new_control_fd);+ EXIT_ON_ERROR_CLEANUP(es_control_fd_reset_to(new_control_fd),+ pthread_mutex_unlock(g_control_state.mutex_ptr)); // ...continue to try to handle a command. } // If there WAS A connection but there IS NO connection, then...- else if (g_control_fd != -1 && new_control_fd == -1) {+ else if (g_client_fd != -1 && new_control_fd == -1) { DEBUG_TRACE("%s", "There WAS A connection but there IS NO connection."); // ...reset the control server state...- control_fd_reset_to(new_control_fd);+ EXIT_ON_ERROR_CLEANUP(es_control_fd_reset_to(new_control_fd),+ pthread_mutex_unlock(g_control_state.mutex_ptr)); // ...wait to be notified of a new connection...- control_fd_wait_for_connection();+ EXIT_ON_ERROR_CLEANUP(es_control_wait_for_connection(),+ pthread_mutex_unlock(g_control_state.mutex_ptr)); // ...release the lock...- pthread_mutex_unlock(g_control_fd_mutex_ptr);+ EXIT_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_unlock(g_control_state.mutex_ptr))); // ...and re-enter the loop. continue; } // If there WAS A connection and there IS A connection, then...- else if (g_control_fd != -1 && new_control_fd != -1) {+ else if (g_client_fd != -1 && new_control_fd != -1) { // If it is A DIFFERENT connection, then...- if (g_control_fd != new_control_fd) {+ if (g_client_fd != new_control_fd) { DEBUG_TRACE( "%s", "There WAS A connection and there IS A DIFFERENT connection."); // ...DON'T wait to be notified of a new connection... // ...we may we have already missed the signal... // ...reset the control server state...- control_fd_reset_to(new_control_fd);+ EXIT_ON_ERROR_CLEANUP(es_control_fd_reset_to(new_control_fd),+ pthread_mutex_unlock(g_control_state.mutex_ptr)); // ...continue to try to handle a command. } // If it is THE SAME connection, then...@@ -1071,10 +1153,11 @@ } // Release the lock on the connection file description.- pthread_mutex_unlock(g_control_fd_mutex_ptr);+ EXIT_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_unlock(g_control_state.mutex_ptr))); // Check that g_control_fd is up-to-date:- assert(g_control_fd == new_control_fd);+ assert(g_client_fd == new_control_fd); /* END: Wake up. */ /* BEGIN: Wait for input. */@@ -1085,22 +1168,16 @@ // note: POLLHUP and POLLRDHUP are output only and are ignored input. struct pollfd pfds[1] = {{- .fd = g_control_fd,+ .fd = g_client_fd, .events = POLLIN, .revents = 0, }}; const int ready_or_error = poll(pfds, 1, POLL_LISTEN_TIMEOUT); // if ready_or_error is -1, an error occurred... if (ready_or_error == -1) {- // if errno is EINTR, the receive was interrupted...- if (errno == EINTR) {- goto onexit;- }- // if errno is anything else, there is some other error...- else {- DEBUG_ERRNO("poll() failed");- continue;- }+ // poll may fail with EFAULT, EINTR, EINVAL, EINVAL, or ENOMEM,+ // none of which are recoverable...+ EXIT_ON_ERROR(STATUS_FROM_ERRNO()); } // if ready_or_error is 0, the call to poll timed out... else if (ready_or_error == 0) {@@ -1118,7 +1195,7 @@ // so this condition should be checked _after_ POLLIN. if ((revents & POLLNVAL) || (revents & POLLHUP) || (revents & POLLERR)) { // todo: wait for a new connection...- DEBUG_TRACE("Connection on fd %d closed.", g_control_fd);+ DEBUG_TRACE("Connection on fd %d closed.", g_client_fd); continue; } // otherwise, the POLLIN bit should be set...@@ -1132,72 +1209,42 @@ // Once we know that there is some input, we read and handle one chunk. // read a chunk:- const ssize_t chunk_size_or_error =- recv(g_control_fd, chunk, CHUNK_SIZE, 0);+ const ssize_t chunk_size_or_error = recv(g_client_fd, chunk, CHUNK_SIZE, 0); // if num_bytes_or_error == -1, an error occurred... if (chunk_size_or_error == -1) {- // if errno is EINTR, the receive was interrupted...- if (errno == EINTR) {- goto onexit;- } // if errno is EGAIN or EWOULDBLOCK, recv timed out...- else if (errno == EAGAIN || errno == EWOULDBLOCK) {+ if (errno == EAGAIN || errno == EWOULDBLOCK) { DEBUG_TRACE("%s", "recv() timed out or was interrupted."); // note: the socket should have SO_RCVTIMEO set. continue; }- // if errno is anything else, there is some other error...- else {+ // if errno is ECONNREFUSED, ENOTCONN, or ENOTSOCK, it may recover wit a+ // new connection...+ else if (errno == ECONNREFUSED || errno == ENOTCONN ||+ errno == ENOTSOCK) { DEBUG_ERRNO("recv() failed"); continue; }+ // otherwise, the error is non-recoverable...+ else if (errno == EINTR) {+ EXIT_ON_ERROR(STATUS_FROM_ERRNO());+ } } // if num_bytes_or_error == 0, the connection was closed... else if (chunk_size_or_error == 0) {- DEBUG_TRACE("%s", "recv() failed: the connection was closed."); // todo: wait for a new connection...- DEBUG_TRACE("Connection on fd %d closed.", g_control_fd);+ DEBUG_TRACE("Connection closed on fd: %d", g_client_fd); continue; } // otherwise, handle the received chunk... else {- DEBUG_TRACE("recv() read %zd bytes", chunk_size_or_error);+ DEBUG_TRACE("Received %zd bytes.", chunk_size_or_error); assert(chunk_size_or_error > 0);- control_command_parser_handle_chunk(chunk_size_or_error, chunk);+ EXIT_ON_ERROR(+ es_control_command_parser_handle_chunk(chunk_size_or_error, chunk)); } /* END: Handle up to one chunk of input. */ } /* END: The main control loop. */- goto onexit;--onexit:- free(chunk); return NULL;-}--/* HIDDEN - see documentation in control.h */-HIDDEN EventlogSocketStatus control_start(- pthread_t *const control_thread, const volatile int *const control_fd_ptr,- pthread_mutex_t *const control_fd_mutex_ptr,- pthread_cond_t *const new_conn_cond_ptr) {- DEBUG_DEBUG("%s", "Starting control thread.");- g_control_fd_ptr = control_fd_ptr;- g_control_fd_mutex_ptr = control_fd_mutex_ptr;- g_new_conn_cond_ptr = new_conn_cond_ptr;- {- const int success_or_errno =- pthread_create(control_thread, NULL, control_loop, NULL);- if (success_or_errno != 0) {- DEBUG_ERRNO("pthread_create() failed");- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }- {- const int success_or_errno = pthread_detach(*control_thread);- if (success_or_errno != 0) {- DEBUG_ERRNO("pthread_detach() failed");- return STATUS_FROM_PTHREAD_ERROR(success_or_errno);- }- }- return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK); }
cbits/eventlog_socket/control.h view
@@ -4,23 +4,39 @@ #include <pthread.h> #include <stdbool.h> +#include "./init_state.h" #include "./macros.h" #include "eventlog_socket.h" +/// @brief The state that is shared with the control thread.+typedef struct {+ /// @brief The function writes the thread handle to this location. It should+ /// be nonnull.+ pthread_t *const control_thread_ptr;+ /// @brief The control thread reads the eventlog socket file descriptor from+ /// this pointer. It should be nonnull.+ const volatile int *const client_fd_ptr;+ /// @brief The control thread uses this mutex to guard its reads of+ /// `client_fd_ptr`. All other accesses of the memory location pointed to by+ /// `client_fd_ptr` should also be guarded using this mutex. It should be+ /// nonnull. This file descriptor is *not* managed by the control thread.+ pthread_mutex_t *const mutex_ptr;+ /// @brief The initialization state. Should be used with the bit flag macros+ /// from @c init_state.h. Should be guarded by @c mutex_ptr. See @c+ /// g_init_state in @c eventlog_socket.c.+ const volatile EventlogSocketInitState *init_state_ptr;+ /// @brief The control thread uses this condition together with `mutex_ptr` to+ /// wait for changes in `client_fd_ptr`. It should be nonnull.+ pthread_cond_t *const restrict new_connection_cond_ptr;+ /// @brief The control thread uses this condition together with `mutex_ptr` to+ /// wait for the GHC RTS to be initialised. It should be nonnull.+ pthread_cond_t *const restrict ghc_rts_ready_cond_ptr;+} ControlState;+ /// @brief Start the control thread. ///-/// @param control_thread-/// The function writes the thread handle to this location.-/// @param control_fd_ptr-/// The control thread reads the eventlog socket file descriptor from this-/// pointer. It should be nonnull.-/// @param control_fd_mutex_ptr-/// The control thread uses this mutex to guard its reads of `control_fd_ptr`.-/// All other accesses of the memory location pointed to by `control_fd_ptr`-/// should also be guarded using this mutex. Should be nonnull.-/// @param new_conn_cond_ptr-/// The control thread uses this condition together with-/// `control_fd_mutex_ptr` to wait for changes in `control_fd_ptr`.+/// @param control_thread_state The state that is shared with the control+/// thread. /// /// @return Upon successful completion, 0 is returned. ///@@ -31,28 +47,25 @@ /// @parblock /// `EAGAIN`, `EINVAL`, `EPERM`, or `ESRCH`. /// @endparblock-HIDDEN EventlogSocketStatus control_start(pthread_t *control_thread,- const volatile int *control_fd_ptr,- pthread_mutex_t *control_fd_mutex_ptr,- pthread_cond_t *new_conn_cond_ptr);--/// @see eventlog_socket_signal_ghc_rts_ready-HIDDEN EventlogSocketStatus control_signal_ghc_rts_ready(void);+HIDDEN EventlogSocketStatus es_control_start(ControlState control_thread_state); /// @see eventlog_socket_control_strnamespace HIDDEN const char *-control_strnamespace(EventlogSocketControlNamespace *namespace);+es_control_strnamespace(EventlogSocketControlNamespace *namespace); /// @see eventlog_socket_control_register_namespace-HIDDEN EventlogSocketStatus control_register_namespace(+HIDDEN EventlogSocketStatus es_control_register_namespace( uint8_t namespace_len, const char namespace[namespace_len], EventlogSocketControlNamespace **namespace_out); /// @see eventlog_socket_control_register_command HIDDEN EventlogSocketStatus-control_register_command(EventlogSocketControlNamespace *namespace,- EventlogSocketControlCommandId command_id,- EventlogSocketControlCommandHandler command_handler,- const void *command_data);+es_control_register_command(EventlogSocketControlNamespace *namespace,+ EventlogSocketControlCommandId command_id,+ EventlogSocketControlCommandHandler command_handler,+ const void *command_data);++/// @brief Read the current status of the control thread.+HIDDEN EventlogSocketStatus es_control_status(void); #endif /* EVENTLOG_SOCKET_CONTROL_H */
cbits/eventlog_socket/error.c view
@@ -13,35 +13,35 @@ break; } case EVENTLOG_SOCKET_ERR_RTS_NOSUPPORT: {- return ess_strdup("The GHC RTS does not support the eventlog.");+ return es_strdup("The GHC RTS does not support the eventlog."); } case EVENTLOG_SOCKET_ERR_RTS_FAIL: {- return ess_strdup("The GHC RTS could not start the eventlog writer.");+ return es_strdup("The GHC RTS could not start the eventlog writer."); } case EVENTLOG_SOCKET_ERR_ENV_NOADDR: {- return ess_strdup("No socket address was found in the environment.");+ return es_strdup("No socket address was found in the environment."); } case EVENTLOG_SOCKET_ERR_ENV_TOOLONG: {- return ess_strdup("The Unix domain socket path found was too long.");+ return es_strdup("The Unix domain socket path found was too long."); } case EVENTLOG_SOCKET_ERR_ENV_NOHOST: {- return ess_strdup(+ return es_strdup( "A TCP/IP port number was found, but no host name was found."); } case EVENTLOG_SOCKET_ERR_ENV_NOPORT: {- return ess_strdup(+ return es_strdup( "A TCP/IP host name was found, but no port number was found."); } case EVENTLOG_SOCKET_ERR_CTL_NOSUPPORT: {- return ess_strdup(+ return es_strdup( "This binary was compiled without support for control commands."); } case EVENTLOG_SOCKET_ERR_CTL_EXISTS: {- return ess_strdup(+ return es_strdup( "The requested namespace or command ID is already in use."); } case EVENTLOG_SOCKET_ERR_GAI: {- return ess_strdup(gai_strerror(status.ess_error_code));+ return es_strdup(gai_strerror(status.ess_error_code)); } case EVENTLOG_SOCKET_ERR_SYS: { size_t buflen = STRERROR_BUFLEN_INIT;
cbits/eventlog_socket/error.h view
@@ -14,7 +14,7 @@ .ess_error_code = errno}) /// @brief Construct a status from a `pthread.h` error code.-#define STATUS_FROM_PTHREAD_ERROR(pthread_errno) \+#define STATUS_FROM_PTHREAD(pthread_errno) \ ((EventlogSocketStatus){.ess_status_code = EVENTLOG_SOCKET_ERR_SYS, \ .ess_error_code = (pthread_errno)}) @@ -23,15 +23,59 @@ ((EventlogSocketStatus){.ess_status_code = EVENTLOG_SOCKET_ERR_GAI, \ .ess_error_code = (gai_error)}) +/// @brief A condition that evaluates to @c true if @p expr is an error.+///+/// @warning This macro evaluates its first argument multiple times.+#define STATUS_IS_ERROR(expr) \+ /* If none of the following are true... */ \+ (!((/* ...the status is an OK. */ \+ (expr).ess_status_code == EVENTLOG_SOCKET_OK) || \+ (/* ...the status is a successful getaddrinfo exit code. */ \+ (expr).ess_status_code == EVENTLOG_SOCKET_ERR_GAI && \+ (expr).ess_error_code == 0) || \+ (/* ...the status is a successful system call exit code. */ \+ (expr).ess_status_code == EVENTLOG_SOCKET_ERR_SYS && \+ (expr).ess_error_code == 0)))+ /// @brief If the @p expr returns an error status, immediately return it.-#define RETURN_ON_ERROR(expr) \+#define RETURN_ON_ERROR_CLEANUP(expr, cleanup) \ do { \ const EventlogSocketStatus status = (expr); \- if (status.ess_status_code != EVENTLOG_SOCKET_OK) { \+ if (STATUS_IS_ERROR(status)) { \ char *strerr = eventlog_socket_strerror(status); \ DEBUG_ERROR("%s", strerr); \+ free(strerr); \+ (cleanup); \ return status; \ } \ } while (0)++/// @brief If the @p expr returns an error status, immediately return it.+#define RETURN_ON_ERROR(expr) RETURN_ON_ERROR_CLEANUP(expr, (void)0)++/// @brief If the @p expr returns an error status, save, cleanup, and exit.+///+/// @warning This macro assumes the existence of two global variables `g_status`+/// and `g_status_mutex`.+#define EXIT_ON_ERROR_CLEANUP(expr, cleanup) \+ do { \+ const EventlogSocketStatus status = (expr); \+ if (STATUS_IS_ERROR(status)) { \+ char *strerr = eventlog_socket_strerror(status); \+ DEBUG_ERROR("%s", strerr); \+ free(strerr); \+ pthread_mutex_lock(&g_status_mutex); \+ memcpy(&g_status, &status, sizeof(EventlogSocketStatus)); \+ pthread_mutex_unlock(&g_status_mutex); \+ (cleanup); \+ pthread_exit(NULL); \+ } \+ } while (0)++/// @brief If the @p expr returns an error status, save and exit.+///+/// @warning This macro assumes the existence of two global variables `g_status`+/// and `g_status_mutex`.+#define EXIT_ON_ERROR(expr) EXIT_ON_ERROR_CLEANUP(expr, (void)0) #endif /* EVENTLOG_SOCKET_ERR_H */
+ cbits/eventlog_socket/init_state.h view
@@ -0,0 +1,26 @@+#ifndef EVENTLOG_SOCKET_INIT_STATE_H+#define EVENTLOG_SOCKET_INIT_STATE_H++#include <stdlib.h>++/// @brief Bit flag for `g_init_state` that tracks whether or not the function+/// @c eventlog_socket_init has been called.+#define EVENTLOG_SOCKET_SIG_INITIALIZED 1++/// @brief Bit flag for `g_init_state` that tracks whether or not the function+/// @c EventlogSocketWriter->initEventLogWriter has been called.+#define EVENTLOG_SOCKET_SIG_ATTACHED 2++/// @brief Bit flag for `g_init_state` that tracks whether or not the function+/// @c eventlog_socket_signal_ghc_rts_ready has been called.+#define EVENTLOG_SOCKET_SIG_RTS_READY 4++/// @brief Bit flag for `g_init_state` that tracks whether or not the first+/// client has connected to the eventlog socket. This flag is tracks the first+/// connection and does not reset when the client disconnects.+#define EVENTLOG_SOCKET_SIG_HAD_FIRST_CONNECTION 8++/// @brief The variable that holds the init state flags.+typedef uint8_t EventlogSocketInitState;++#endif /* EVENTLOG_SOCKET_INIT_STATE_H */
cbits/eventlog_socket/string.c view
@@ -1,6 +1,6 @@ #include "./string.h" -HIDDEN char *ess_strdup(const char *const str) {+HIDDEN char *es_strdup(const char *const str) { if (str == NULL) { errno = EINVAL; return NULL;@@ -13,7 +13,7 @@ return str_dup; } -HIDDEN char *ess_strndup(const size_t str_len, const char str[str_len + 1]) {+HIDDEN char *es_strndup(const size_t str_len, const char str[str_len + 1]) { if (str == NULL) { errno = EINVAL; return NULL;
cbits/eventlog_socket/string.h view
@@ -14,7 +14,7 @@ /// /// @return On error, a null pointer is returned and errno is set to indicate /// the error.-HIDDEN char *ess_strdup(const char *str);+HIDDEN char *es_strdup(const char *str); /// @brief Copy the first @p str_len bytes of @p str into allocated memory. ///@@ -23,6 +23,6 @@ /// /// @return On error, a null pointer is returned and errno is set to indicate /// the error.-HIDDEN char *ess_strndup(size_t str_len, const char str[str_len + 1]);+HIDDEN char *es_strndup(size_t str_len, const char str[str_len + 1]); #endif /* EVENTLOG_SOCKET_STRING_H */
+ cbits/eventlog_socket/worker.c view
@@ -0,0 +1,782 @@+#include <assert.h>+#include <ctype.h>+#include <errno.h>+#include <fcntl.h>+#include <netdb.h>+#include <pthread.h>+#include <stdbool.h>+#include <stddef.h>+#include <stdint.h>+#include <stdlib.h>+#include <string.h>+#include <sys/socket.h>+#include <sys/types.h>+#include <sys/un.h>+#include <unistd.h>++#include "./debug.h"+#include "./error.h"+#include "./init_state.h"+#include "./poll.h"+#include "./string.h"+#include "./worker.h"+#include "eventlog_socket.h"++/// @brief The maximum length of the queue for pending connections.+#define LISTEN_BACKLOG 5++#ifndef NI_MAXHOST+/// @brief The maximum length of the hostname.+///+/// NOTE: This symbol is conditionally exported by @c netdb.h.+#define NI_MAXHOST 1025+#endif++#ifndef NI_MAXSERV+/// @brief The maximum length of the service name.+///+/// NOTE: This symbol is conditionally exported by @c netdb.h.+#define NI_MAXSERV 32+#endif++/// @brief The current status of the worker thread.+static EventlogSocketStatus g_status = STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);++/// @brief The mutex that protects @c g_status.+static pthread_mutex_t g_status_mutex = PTHREAD_MUTEX_INITIALIZER;++/* HIDDEN - see documentation in worker.h */+HIDDEN EventlogSocketStatus es_worker_status(void) {+ pthread_mutex_lock(&g_status_mutex);+ const EventlogSocketStatus status = g_status;+ pthread_mutex_unlock(&g_status_mutex);+ return status;+}++/// @brief The file descriptor for the socket on which the worker listens for+/// new connections.+static int g_listen_fd = -1;++/// @brief The file path for the open Unix domain socket, if any.+static const char *g_sock_path = NULL;++/// @brief A pipe used internally by the worker.+static int g_wake_pipe[2] = {-1, -1};++/// @brief The state that is shared with the worker thread.+///+/// **NOTE**: Initialising with @c {0} should guarantee that all pointers are+/// NULL pointers.+static WorkerState g_worker_state = {0};++/// @brief Wait for the signal that the GHC RTS is ready.+static void es_worker_wait_rts_ready(void) {+ assert(g_worker_state.init_state_ptr != NULL);+ assert(g_worker_state.mutex_ptr != NULL);+ assert(g_worker_state.ghc_rts_ready_cond_ptr != NULL);+ EXIT_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_lock(g_worker_state.mutex_ptr)));+ while (true) {+ // Check whether or not the GHC RTS is ready.+ DEBUG_DEBUG("%s", "Checking if GHC RTS is ready.");+ const EventlogSocketInitState init_state = *g_worker_state.init_state_ptr;+ DEBUG_TRACE("init_state: %d", init_state);+ const bool is_rts_ready = init_state & EVENTLOG_SOCKET_SIG_RTS_READY;+ DEBUG_TRACE("is_rts_ready: %s", is_rts_ready ? "true" : "false");+ if (is_rts_ready) {+ // If the GHC RTS is ready, break from the loop.+ break;+ } else {+ // If the GHC RTS is NOT ready, wait on the relevant condition and+ // re-enter the loop.+ //+ // NOTE: This call acts as the cancellation point for this infinite loop.+ DEBUG_DEBUG("%s", "Waiting for signal that GHC RTS is ready.");+ EXIT_ON_ERROR_CLEANUP(+ STATUS_FROM_PTHREAD(pthread_cond_wait(+ g_worker_state.ghc_rts_ready_cond_ptr, g_worker_state.mutex_ptr)),+ pthread_mutex_unlock(g_worker_state.mutex_ptr));+ }+ }+ EXIT_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_unlock(g_worker_state.mutex_ptr)));+}++/// @brief Cleanup handler for the worker thread.+///+/// TODO: Move the first portion of this handler into a pthread_cleanup_push+/// handler.+static void es_worker_cleanup(void) {+ // Remove socket file.+ if (g_sock_path) {+ unlink(g_sock_path);+ }+ // Close the wake pipes.+ if (g_wake_pipe[0] != -1) {+ close(g_wake_pipe[0]);+ g_wake_pipe[0] = -1;+ }+ if (g_wake_pipe[1] != -1) {+ close(g_wake_pipe[1]);+ g_wake_pipe[1] = -1;+ }+ // Stop the worker thread.+ if (g_worker_state.worker_thread_ptr != NULL) {+ DEBUG_DEBUG("%s", "Cancelling worker thread.");+ if (pthread_cancel(*g_worker_state.worker_thread_ptr) != 0) {+ DEBUG_ERRNO("pthread_cancel() failed for worker thread");+ } else {+ if (pthread_join(*g_worker_state.worker_thread_ptr, NULL) != 0) {+ DEBUG_ERRNO("pthread_join() failed for worker thread");+ }+ }+ free((void *)g_worker_state.worker_thread_ptr);+ }+}++#define EVENTLOG_SOCKET_WORKER_CHUNK_SIZE 32++static void es_worker_wake_drain(void) {+ if (g_wake_pipe[0] == -1) {+ return;+ }+ uint8_t chunk[EVENTLOG_SOCKET_WORKER_CHUNK_SIZE];+ while (true) {+ // NOTE: The call to read acts as the cancellation point for this infinite+ // loop.+ const ssize_t success_or_error = read(g_wake_pipe[0], chunk, sizeof(chunk));+ if (success_or_error > 0) {+ continue;+ } else if (success_or_error == 0) {+ break;+ } else if (errno == EINTR) {+ continue;+ } else if (errno == EAGAIN || errno == EWOULDBLOCK) {+ break;+ } else {+ DEBUG_ERRNO("read() failed");+ break;+ }+ }+}++/* HIDDEN - see documentation in worker.h */+HIDDEN void es_worker_wake(void) {+ if (g_wake_pipe[1] == -1) {+ return;+ }++ const uint8_t byte = 1;+ const ssize_t success_or_error = write(g_wake_pipe[1], &byte, sizeof(byte));+ // NOTE: write may fail with EAGAIN, EWOULDBLOCK, EBADF, EDESTADDRREQ,+ // EDQUOT, EFAULT, EFBIG, EINTR, EINVAL, EIO, ENOSPC, EPERM, EPIPE.+ // Of those, EAGAIN and EWOULDBLOCK should lead to retry.+ if (success_or_error == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {+ EXIT_ON_ERROR(STATUS_FROM_ERRNO());+ }+}++static void es_worker_step_listen(void) {+ if (listen(g_listen_fd, LISTEN_BACKLOG) == -1) {+ // NOTE: This call should only fail with EADDRINUSE, EBADF, ENOTSOCK,+ // EOPNOTSUPP. While some of these errors may be recoverable, it would+ // require reengineering of eventlog-socket to enable changing the socket+ // address on the fly, which means that currently it isn't recoverable.+ EXIT_ON_ERROR(STATUS_FROM_ERRNO());+ }++ struct sockaddr_storage remote;+ socklen_t remote_len = sizeof(remote);++ struct pollfd pfds[1] = {{+ .fd = g_listen_fd,+ .events = POLLIN,+ .revents = 0,+ }};++ DEBUG_TRACE("Listening for connection on fd: %d", g_listen_fd);++ // poll until we can accept+ while (true) {+ // NOTE: poll acts as the cancellation point for this infinite loop.+ // NOTE: poll can fail with EFAULT, EINTR, EINVAL, and ENOMEM.+ // On all of those errors, the worker thread should shut down.+ const int ready_or_error = poll(pfds, 1, POLL_LISTEN_TIMEOUT);+ if (ready_or_error == -1) {+ EXIT_ON_ERROR(STATUS_FROM_ERRNO());+ } else if (ready_or_error == 0) {+ DEBUG_TRACE("%s", "poll() timed out");+ } else {+ // got connection+ DEBUG_TRACE("%s", "poll() ready");+ break;+ }+ }++ // accept+ const int client_fd =+ accept(g_listen_fd, (struct sockaddr *)&remote, &remote_len);+ if (client_fd == -1) {+ // NOTE: accept can fail with EAGAIN, EWOULDBLOCK, EBADF, ECONNABORTED,+ // EFAULT, EINTR, EINVAL, EMFILE, ENFILE, ENOBUFS, ENOMEM, ENOTSOCK,+ // EOPNOTSUPP, EPERM, and EPROTO. Of those, only EAGAIN, EWOULDBLOCK,+ // ECONNABORTED should lead to retrying.+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ECONNABORTED) {+ return;+ } else {+ EXIT_ON_ERROR(STATUS_FROM_ERRNO());+ }+ }+ DEBUG_TRACE("Accepted new connection fd: %d", client_fd);++ // set socket into non-blocking mode+ const int flags = fcntl(client_fd, F_GETFL);+ if (flags == -1) {+ DEBUG_ERRNO("fnctl() failed for F_GETFL");+ }+ if (fcntl(client_fd, F_SETFL, flags | O_NONBLOCK) == -1) {+ DEBUG_ERRNO("fnctl() failed for F_SETFL");+ }++ // Wait for the GHC RTS to be ready.+ es_worker_wait_rts_ready();++ // Figure out whether we should restart event logging.+ //+ // Do we need this mutex? Who else can edit `*g_worker_state.init_state_ptr`?+ //+ // - It is edited by `worker_init`, which should not run a second time.+ // Moreover, `worker_init` is monotone.+ //+ // - It is edited by this thread. Since the calls to listen and accept are+ // in this very function, there is no risk of a second connection being+ // accepted as this is being evaluated.+ //+ // - It is edited by `worker_stop`. This could happen if another user thread+ // concurrently decides to end event logging. This COULD happen, but only+ // if the user is doing some shady things that we aren't anticipating.+ //+ // - It is edited by `eventlog_socket_signal_rts_ready`. This can happen if+ // the user signals that the GHC RTS is ready while a new client connects.+ // This is actually VERY LIKELY to happen.+ //+ // If `startEventLogging` is called before the GHC RTS is initialised, the+ // call completes _successfully_, but the event log writer is overwritten+ // when the GHC RTS is initialised without calling `endEventLogging`.+ //+ pthread_mutex_lock(g_worker_state.mutex_ptr);+ const EventlogSocketInitState init_state = *g_worker_state.init_state_ptr;+ // TODO: Wait for RTS init.+ pthread_mutex_unlock(g_worker_state.mutex_ptr);+ DEBUG_TRACE("init_state: %d", init_state);++ // Whether the RTS is currently initialised.+ const bool is_rts_ready = init_state & EVENTLOG_SOCKET_SIG_RTS_READY;+ DEBUG_TRACE("is_rts_ready: %s", is_rts_ready ? "true" : "false");++ // Whether EventlogSocketWriter is currently attached.+ const bool is_attached = init_state & EVENTLOG_SOCKET_SIG_ATTACHED;+ DEBUG_TRACE("is_attached: %s", is_attached ? "true" : "false");++ // Whether we've had our first connection.+ const bool had_first_connection =+ init_state & EVENTLOG_SOCKET_SIG_HAD_FIRST_CONNECTION;+ DEBUG_TRACE("had_first_connection: %s", is_attached ? "true" : "false");++ // Whether event logging is currently running.+ const bool is_running = eventLogStatus() == EVENTLOG_RUNNING;+ DEBUG_TRACE("is_running: %s", is_running ? "true" : "false");++ // We should STOP event logging if...+ const bool should_stop =+ // ...some eventlog writer is currently running and either:+ is_running && (+ // ...(1) it's not EventlogSocketWriter, or...+ !is_attached ||+ // ...(2) we've already had our first connection.+ //+ // NOTE: This forces the RTS to resent the init events.+ had_first_connection);+ DEBUG_TRACE("should_stop: %s", should_stop ? "true" : "false");++ // We should START event logging if...+ const bool should_start =+ // ...(1) we stopped it, or...+ should_stop+ // ...(2) it wasn't running in the first place...+ || !is_running;+ DEBUG_TRACE("should_start: %s", should_start ? "true" : "false");++ // We stop event logging.+ if (should_stop) {+ DEBUG_DEBUG("%s", "Stopping current event logger.");+ endEventLogging();+ }++ pthread_mutex_lock(g_worker_state.mutex_ptr);+ // Publish the client ID to `g_client_id`.+ DEBUG_TRACE("Broadcasting new connection fd: %d -> %d",+ *g_worker_state.client_fd_ptr, client_fd);+ *g_worker_state.client_fd_ptr = client_fd;+ // Trigger the `g_new_conn_cond` condition.+ pthread_cond_broadcast(g_worker_state.new_connection_cond_ptr);+ pthread_mutex_unlock(g_worker_state.mutex_ptr);++ // We start event logging with EventlogSocketWriter.+ if (should_start) {+ DEBUG_DEBUG("%s", "Starting new event logger.");+ // TODO: Add retry loop.+ const bool is_started = startEventLogging(&EventLogSocketWriter);+ DEBUG_TRACE("is_started: %s", is_started ? "true" : "false");+ }++ // Update *g_worker_state.init_state_ptr to record that we've seen our first+ // connection.+ if (!had_first_connection) {+ pthread_mutex_lock(g_worker_state.mutex_ptr);+ *g_worker_state.init_state_ptr |= EVENTLOG_SOCKET_SIG_HAD_FIRST_CONNECTION;+ pthread_mutex_unlock(g_worker_state.mutex_ptr);+ }+}++// nothing to write iteration.+//+// we poll only for whether the connection is closed.+static void es_worker_step_nonwrite(int fd) {+ // Wait for socket to disconnect or for pending data.+ struct pollfd pfds[2];+ pfds[0].fd = fd;+ pfds[0].events = POLLRDHUP;+ pfds[0].revents = 0;+ int nfds = 1;+ if (g_wake_pipe[0] != -1) {+ pfds[1].fd = g_wake_pipe[0];+ pfds[1].events = POLLIN;+ pfds[1].revents = 0;+ nfds = 2;+ }++ const int ready_or_error = poll(pfds, nfds, -1);+ if (ready_or_error == -1) {+ if (errno == EINTR) {+ return;+ }+ DEBUG_ERRNO("poll() failed");+ return;+ }++ if (nfds == 2 && (pfds[1].revents & POLLIN)) {+ es_worker_wake_drain();+ return;+ }++ if (pfds[0].revents & POLLHUP) {+ pthread_mutex_lock(g_worker_state.mutex_ptr);+ *g_worker_state.client_fd_ptr = -1;+ es_write_buffer_free(g_worker_state.write_buffer_ptr);+ pthread_mutex_unlock(g_worker_state.mutex_ptr);+ return;+ }+}++// write iteration.+//+// we poll for both: can we write, and whether the connection is closed.+static void es_worker_step_write(int fd) {+ // Wait for socket to disconnect+ struct pollfd pfds[1] = {{+ .fd = fd,+ .events = POLLOUT | POLLRDHUP,+ .revents = 0,+ }};++ const int num_ready_or_err = poll(pfds, 1, POLL_WRITE_TIMEOUT);+ if (num_ready_or_err == -1 && errno != EAGAIN) {+ // error+ DEBUG_ERRNO("poll() failed");+ return;+ } else if (num_ready_or_err == 0) {+ // timeout+ return;+ }++ // reset client_fd on RDHUP.+ if (pfds[0].revents & POLLHUP) {+ // reset client_fd+ // Protect concurrent access to client_fd and wt during teardown.+ pthread_mutex_lock(g_worker_state.mutex_ptr);+ assert(fd == *g_worker_state.client_fd_ptr);+ *g_worker_state.client_fd_ptr = -1;+ es_write_buffer_free(g_worker_state.write_buffer_ptr);+ pthread_mutex_unlock(g_worker_state.mutex_ptr);+ return;+ }++ if (pfds[0].revents & POLLOUT) {+ // RTS writers also access wt, so consume queued buffers under the mutex.+ pthread_mutex_lock(g_worker_state.mutex_ptr);+ while (g_worker_state.write_buffer_ptr->head) {+ WriteBufferItem *item = g_worker_state.write_buffer_ptr->head;+ const ssize_t num_bytes_written_or_err =+ write(*g_worker_state.client_fd_ptr, item->data, item->size);++ if (num_bytes_written_or_err == -1) {+ if (errno == EAGAIN || errno == EWOULDBLOCK) {+ // couldn't write anything, shouldn't happen.+ // do nothing.+ } else if (errno == EPIPE) {+ *g_worker_state.client_fd_ptr = -1;+ es_write_buffer_free(g_worker_state.write_buffer_ptr);+ } else {+ DEBUG_ERRNO("write() failed");+ }++ // break out of the loop+ break;++ } else {+ // cast from ssize_t to size_t is safe as num_bytes_written_or_err != -1+ const size_t num_bytes_written = num_bytes_written_or_err;+ // we wrote something+ if (num_bytes_written >= item->size) {+ // we wrote whole element, try to write next element too+ es_write_buffer_pop(g_worker_state.write_buffer_ptr);+ continue;+ } else {+ item->size -= num_bytes_written;+ item->data += num_bytes_written;+ break;+ }+ }+ }+ pthread_mutex_unlock(g_worker_state.mutex_ptr);+ }+}++static void es_worker_step(void) {+ // Snapshot shared state under lock so worker decisions (listen vs write)+ // align with the current connection/queue state.+ EXIT_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_lock(g_worker_state.mutex_ptr)));+ const int client_fd = *g_worker_state.client_fd_ptr;+ const bool write_buffer_empty = g_worker_state.write_buffer_ptr->head == NULL;+ EXIT_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_unlock(g_worker_state.mutex_ptr)));+ if (client_fd != -1) {+ if (write_buffer_empty) {+ es_worker_step_nonwrite(client_fd);+ } else {+ es_worker_step_write(client_fd);+ }+ } else {+ es_worker_step_listen();+ }+}++/* Main loop of eventlog-socket own thread:+ * Currently it is two states:+ * - either we have connection, then we poll for writes (and drop of+ * connection).+ * - or we don't have, then we poll for accept.+ */+static void *es_worker_loop(void *arg) {+ (void)arg;+ while (true) {+ // Ensure the loop has a cancellation point.+ pthread_testcancel();++ // Perform one worker step.+ es_worker_step();+ }+ return NULL; // unreachable+}++/// @brief Initialize the Unix domain socket and bind it to the provided path.+///+/// This function does not start any threads; @c worker_start() completes the+/// setup.+///+/// @return See `EventlogSocketStatus`.+///+/// @par Errors+/// @parblock+/// This function may return the following system errors:+/// `EADDRINUSE`, `EADDRNOTAVAIL`, `EAFNOSUPPORT`, `EBADF`, `EDOM`,+/// `EFAULT`, `EINVAL`, `EISCONN`, `ELOOP`, `EMFILE`, `ENAMETOOLONG`, `ENFILE`,+/// `ENOBUFS`, `ENOENT`, `ENOMEM`, `ENOPROTOOPT`, `ENOTDIR`, `ENOTSOCK`,+/// `EPROTONOSUPPORT`, or `EROFS`.+/// @endparblock+static EventlogSocketStatus+es_worker_socket_init_unix(const EventlogSocketUnixAddr *const unix_addr,+ const EventlogSocketOpts *const opts) {+ DEBUG_TRACE("Initialising Unix socket on %s", unix_addr->esa_unix_path);++ // Create a socket.+ g_listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);+ if (g_listen_fd == -1) {+ return STATUS_FROM_ERRNO(); // `setsockopt` sets errno.+ }++ // Record the sock_path so it can be unlinked at exit+ g_sock_path = es_strdup(unix_addr->esa_unix_path);+ if (g_sock_path == NULL) {+ return STATUS_FROM_ERRNO(); // `es_strdup` sets errno.+ }++ // Set socket receive low water mark.+ if (setsockopt(g_listen_fd, SOL_SOCKET, SO_RCVLOWAT, &(int){1},+ sizeof(int)) == -1) {+ DEBUG_ERRNO("setsockopt() failed for SO_RCVLOWAT");+ const int setsockopt_errno = errno; // `setsockopt` sets errno.+ close(g_listen_fd);+ g_listen_fd = -1;+ errno = setsockopt_errno;+ return STATUS_FROM_ERRNO();+ }++ // Set socket send buffer size.+ if (opts != NULL && opts->eso_sndbuf > 0) {+ if (setsockopt(g_listen_fd, SOL_SOCKET, SO_SNDBUF, &opts->eso_sndbuf,+ sizeof(opts->eso_sndbuf)) == -1) {+ DEBUG_ERRNO("setsockopt() failed for SO_SNDBUF");+ const int setsockopt_errno = errno; // `setsockopt` sets errno.+ close(g_listen_fd);+ g_listen_fd = -1;+ errno = setsockopt_errno;+ return STATUS_FROM_ERRNO();+ }+ }++ // Set socket linger.+ if (opts != NULL && opts->eso_linger > 0) {+ const struct linger so_linger = {+ .l_onoff = true,+ .l_linger = opts->eso_linger,+ };+ if (setsockopt(g_listen_fd, SOL_SOCKET, SO_LINGER, &so_linger,+ sizeof(so_linger)) == -1) {+ DEBUG_ERRNO("setsockopt() failed for SO_LINGER");+ const int setsockopt_errno = errno; // `setsockopt` sets errno.+ close(g_listen_fd);+ g_listen_fd = -1;+ errno = setsockopt_errno;+ return STATUS_FROM_ERRNO();+ }+ }++ struct sockaddr_un local;+ memset(&local, 0, sizeof(local));+ local.sun_family = AF_UNIX;+ strncpy(local.sun_path, unix_addr->esa_unix_path, sizeof(local.sun_path) - 1);+ unlink(unix_addr->esa_unix_path);+ if (bind(g_listen_fd, (struct sockaddr *)&local,+ sizeof(struct sockaddr_un)) == -1) {+ DEBUG_ERRNO("bind() failed");+ return STATUS_FROM_ERRNO(); // `bind` sets errno.+ }+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);+}++/// @brief Initialize the TCP/IP socket and bind it to the provided address.+///+/// This function does not start any threads; @c worker_start() completes the+/// setup.+///+/// Either host or port may be NULL, in which case the defaults used by+/// @c getaddrinfo apply.+///+/// @return See `EventlogSocketStatus`.+///+/// @par Errors+/// @parblock+/// This function may return the following system errors:+/// `EADDRINUSE`, `EADDRNOTAVAIL`, `EAFNOSUPPORT`, `EAGAIN`, `EALREADY`,+/// `EBADF`, `EDESTADDRREQ`, `EDOM`, `EFAULT`, `EINPROGRESS`, `EINVAL`, `EIO`,+/// `EISCONN`, `EISDIR`, `ELOOP`, `EMFILE`, `ENAMETOOLONG`, `ENFILE`, `ENOBUFS`,+/// `ENOENT`, `ENOMEM`, `ENOPROTOOPT`, `ENOTDIR`, `ENOTSOCK`, `EOPNOTSUPP`,+/// `EPROTONOSUPPORT`, or `EROFS`.+///+/// This function may return the following @c getaddrinfo errors:+/// `EAI_ADDRFAMILY`, `EAI_AGAIN`, `EAI_BADFLAGS`, `EAI_FAIL`, `EAI_FAMILY`,+/// `EAI_MEMORY`, `EAI_NODATA`, `EAI_NONAME`, `EAI_SERVICE`, `EAI_SOCKTYPE`, or+/// `EAI_SYSTEM`.+/// @endparblock+static EventlogSocketStatus+es_worker_socket_init_inet(const EventlogSocketInetAddr *const inet_addr,+ const EventlogSocketOpts *const opts) {+ struct addrinfo hints;+ memset(&hints, 0, sizeof(hints));+ hints.ai_family = AF_UNSPEC;+ hints.ai_socktype = SOCK_STREAM;+ hints.ai_flags = AI_PASSIVE;++ struct addrinfo *res = NULL;+ RETURN_ON_ERROR(STATUS_FROM_GAI_ERROR(getaddrinfo(+ inet_addr->esa_inet_host, inet_addr->esa_inet_port, &hints, &res)));++ struct addrinfo *rp;+ for (rp = res; rp != NULL; rp = rp->ai_next) {+ g_listen_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);+ if (g_listen_fd == -1) {+ continue;+ }++ // Set socket reuse address.+ if (setsockopt(g_listen_fd, SOL_SOCKET, SO_REUSEADDR, &(int){1},+ sizeof(int)) != 0) {+ DEBUG_ERRNO("setsockopt() failed for SO_REUSEADDR");+ close(g_listen_fd);+ g_listen_fd = -1;+ continue;+ }++ // set socket send buffer size+ if (opts != NULL && opts->eso_sndbuf > 0) {+ if (setsockopt(g_listen_fd, SOL_SOCKET, SO_SNDBUF, &opts->eso_sndbuf,+ sizeof(opts->eso_sndbuf)) != 0) {+ DEBUG_ERRNO("setsockopt() failed for SO_SNDBUF");+ close(g_listen_fd);+ g_listen_fd = -1;+ continue;+ }+ }++ if (bind(g_listen_fd, rp->ai_addr, rp->ai_addrlen) == -1) {+ DEBUG_ERRNO("bind() failed");+ close(g_listen_fd);+ g_listen_fd = -1;+ continue;+ } else {+ char hostbuf[NI_MAXHOST];+ char servbuf[NI_MAXSERV];+ if (getnameinfo(rp->ai_addr, rp->ai_addrlen, hostbuf, sizeof(hostbuf),+ servbuf, sizeof(servbuf),+ NI_NUMERICHOST | NI_NUMERICSERV) == 0) {+ DEBUG_TRACE("Bound TCP listener to %s:%s", hostbuf, servbuf);+ }+ break; // success+ }+ }++ freeaddrinfo(res);++ if (g_listen_fd == -1) {+ DEBUG_ERROR("%s", "Unable to bind TCP listener");+ errno = EAGAIN;+ return STATUS_FROM_ERRNO();+ }++ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);+}++/// @brief Initialize the worker thread.+///+/// @return See `EventlogSocketStatus`.+///+/// @par Errors+/// @parblock+/// If `EVENTLOG_SOCKET_ERR_SYS` is returned, @c error_code is set to one of+/// the following error codes:+///+/// `EACCES`, `EAGAIN`, `EBADF`, `EFAULT`, `EINVAL`, `EMFILE`, `ENFILE`,+/// `ENFILE`, or `ENOPKG`.+/// @endparblock+HIDDEN EventlogSocketStatus es_worker_init(const WorkerState worker_state) {+ assert(worker_state.worker_thread_ptr != NULL);+ assert(worker_state.client_fd_ptr != NULL);+ assert(worker_state.write_buffer_ptr != NULL);+ assert(worker_state.mutex_ptr != NULL);+ assert(worker_state.init_state_ptr != NULL);+ assert(worker_state.new_connection_cond_ptr != NULL);+ assert(worker_state.ghc_rts_ready_cond_ptr != NULL);+ DEBUG_DEBUG("%s", "Initialising worker thread.");+ memcpy(&g_worker_state, &worker_state, sizeof(WorkerState));+ assert(g_worker_state.worker_thread_ptr != NULL);+ assert(g_worker_state.client_fd_ptr != NULL);+ assert(g_worker_state.write_buffer_ptr != NULL);+ assert(g_worker_state.mutex_ptr != NULL);+ assert(g_worker_state.init_state_ptr != NULL);+ assert(g_worker_state.new_connection_cond_ptr != NULL);+ assert(g_worker_state.ghc_rts_ready_cond_ptr != NULL);+ RETURN_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_lock(g_worker_state.mutex_ptr)));+ if (!(*g_worker_state.init_state_ptr & EVENTLOG_SOCKET_SIG_INITIALIZED)) {+ if (pipe(g_wake_pipe) == -1) {+ DEBUG_ERRNO("pipe() failed");+ pthread_mutex_unlock(g_worker_state.mutex_ptr);+ return STATUS_FROM_ERRNO();+ }+ for (int i = 0; i < 2; i++) {+ const int flags = fcntl(g_wake_pipe[i], F_GETFL, 0);+ if (flags == -1) {+ DEBUG_ERRNO("fcntl() failed for F_GETFL");+ pthread_mutex_unlock(g_worker_state.mutex_ptr);+ return STATUS_FROM_ERRNO();+ }+ if (fcntl(g_wake_pipe[i], F_SETFL, flags | O_NONBLOCK) == -1) {+ DEBUG_ERRNO("fcntl() failed for F_SETFL");+ pthread_mutex_unlock(g_worker_state.mutex_ptr);+ return STATUS_FROM_ERRNO();+ }+ }+ atexit(es_worker_cleanup);+ *g_worker_state.init_state_ptr |= EVENTLOG_SOCKET_SIG_INITIALIZED;+ }+ RETURN_ON_ERROR(+ STATUS_FROM_PTHREAD(pthread_mutex_unlock(g_worker_state.mutex_ptr)));+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);+}++/// @brief Start the worker thread.+///+/// @return Upon successful completion, 0 is returned.+///+/// @return On error, one of the following error codes is returned.+///+/// @par Errors+/// @parblock+/// If `EVENTLOG_SOCKET_ERR_GAI` is returned, @c error_code is set to one of+/// the following error codes:+///+/// `EAI_ADDRFAMILY`, `EAI_AGAIN`, `EAI_BADFLAGS`, `EAI_FAIL`, `EAI_FAMILY`,+/// `EAI_MEMORY`, `EAI_NODATA`, `EAI_NONAME`, `EAI_SERVICE`, `EAI_SOCKTYPE`, or+/// `EAI_SYSTEM`.+///+/// If `EVENTLOG_SOCKET_ERR_SYS` is returned, @c error_code is set to one of+/// the following error codes:+///+/// `EACCES`, `EADDRINUSE`, `EADDRNOTAVAIL`, `EAFNOSUPPORT`, `EAGAIN`,+/// `EALREADY`, `EBADF`, `EDESTADDRREQ`, `EDOM`, `EFAULT`, `EINPROGRESS`,+/// `EINVAL`, `EIO`, `EISCONN`, `EISDIR`, `ELOOP`, `EMFILE`, `ENAMETOOLONG`,+/// `ENFILE`, `ENOBUFS`, `ENOENT`, `ENOMEM`, `ENOPKG`, `ENOPROTOOPT`, `ENOTDIR`,+/// `ENOTSOCK`, `EOPNOTSUPP`, `EPERM`, `EPROTONOSUPPORT`, or `EROFS`.+/// @endparblock+HIDDEN EventlogSocketStatus+es_worker_start(const EventlogSocketAddr *const eventlog_socket_addr,+ const EventlogSocketOpts *const eventlog_socket_opts) {+ // Bind the eventlog socket.+ DEBUG_TRACE("%s", "Binding eventlog socket.");+ switch (eventlog_socket_addr->esa_tag) {+ case EVENTLOG_SOCKET_UNIX: {+ RETURN_ON_ERROR(es_worker_socket_init_unix(+ &eventlog_socket_addr->esa_unix_addr, eventlog_socket_opts));+ break;+ }+ case EVENTLOG_SOCKET_INET: {+ RETURN_ON_ERROR(es_worker_socket_init_inet(+ &eventlog_socket_addr->esa_inet_addr, eventlog_socket_opts));+ break;+ }+ default: {+ DEBUG_ERROR("%s", "unknown listener kind");+ errno = EINVAL;+ return STATUS_FROM_ERRNO();+ }+ }++ // Start the worker thread.+ DEBUG_TRACE("%s", "Starting worker thread.");+ RETURN_ON_ERROR(STATUS_FROM_PTHREAD(pthread_create(+ g_worker_state.worker_thread_ptr, NULL, es_worker_loop, NULL)));+ return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);+}
+ cbits/eventlog_socket/worker.h view
@@ -0,0 +1,48 @@+#ifndef EVENTLOG_SOCKET_WORKER_H+#define EVENTLOG_SOCKET_WORKER_H++#include "./init_state.h"+#include "./macros.h"+#include "./write_buffer.h"+#include "eventlog_socket.h"++/// @brief The state that is shared with the worker thread.+typedef struct {+ /// @brief The function writes the thread handle to this location. It should+ /// be nonnull.+ pthread_t *const worker_thread_ptr;+ /// @brief The worker thread reads the eventlog socket file descriptor from+ /// this pointer. If EVENTLOG_SOCKET_FEATURE_CONTROL is defined, it should be+ /// nonnull.+ volatile int *const client_fd_ptr;+ // TODO: documentation.+ WriteBuffer *write_buffer_ptr;+ /// @brief The worker thread uses this mutex to guard its reads of+ /// `client_fd_ptr`. All other accesses of the memory location pointed to by+ /// `client_fd_ptr` should also be guarded using this mutex. It should be+ /// nonnull. This file descriptor is *not* managed by the worker thread.+ pthread_mutex_t *const mutex_ptr;+ /// @brief The initialization state. Should be used with the bit flag macros+ /// from @c init_state.h. Should be guarded by @c mutex_ptr. See @c+ /// g_init_state in @c eventlog_socket.c.+ volatile EventlogSocketInitState *init_state_ptr;+ /// @brief The worker thread uses this condition together with `mutex_ptr` to+ /// wait for changes in `client_fd_ptr`. It should be nonnull.+ pthread_cond_t *const restrict new_connection_cond_ptr;+ /// @brief The worker thread uses this condition together with `mutex_ptr` to+ /// wait for the GHC RTS to be initialised. It should be nonnull.+ pthread_cond_t *const restrict ghc_rts_ready_cond_ptr;+} WorkerState;++HIDDEN EventlogSocketStatus es_worker_init(WorkerState worker_state);++HIDDEN EventlogSocketStatus+es_worker_start(const EventlogSocketAddr *eventlog_socket_addr,+ const EventlogSocketOpts *eventlog_socket_opts);++HIDDEN void es_worker_wake(void);++/// @brief Read the current status of the worker thread.+HIDDEN EventlogSocketStatus es_worker_status(void);++#endif /* EVENTLOG_SOCKET_WORKER_H */
cbits/eventlog_socket/write_buffer.c view
@@ -2,11 +2,10 @@ #include <stdlib.h> #include <string.h> -#include "./debug.h" #include "./write_buffer.h" -void HIDDEN write_buffer_push(WriteBuffer *wb, uint8_t *data, size_t size) {- DEBUG_TRACE("%p, %lu\n", (void *)data, size);+HIDDEN void es_write_buffer_push(WriteBuffer *wb, size_t size,+ uint8_t data[size]) { uint8_t *copy = malloc(size); memcpy(copy, data, size); @@ -26,11 +25,9 @@ last->next = item; wb->last = item; }-- DEBUG_TRACE("%p %p\n", (void *)wb, (void *)wb->head); } -void HIDDEN write_buffer_pop(WriteBuffer *wb) {+HIDDEN void es_write_buffer_pop(WriteBuffer *wb) { WriteBufferItem *head = wb->head; if (head == NULL) { // buffer is empty: nothing to do.@@ -45,10 +42,10 @@ } } -void HIDDEN write_buffer_free(WriteBuffer *wb) {+HIDDEN void es_write_buffer_free(WriteBuffer *wb) { // not the most efficient implementation, // but should be obviously correct. while (wb->head) {- write_buffer_pop(wb);+ es_write_buffer_pop(wb); } }
cbits/eventlog_socket/write_buffer.h view
@@ -25,14 +25,15 @@ // push to the back. // Caller must serialize externally (writer_write/write_iteration hold mutex) // so that head/last invariants stay intact.-void HIDDEN write_buffer_push(WriteBuffer *wb, uint8_t *data, size_t size);+HIDDEN void es_write_buffer_push(WriteBuffer *wb, size_t size,+ uint8_t data[size]); // pop from the front. // Requires the same external synchronization as write_buffer_push.-void HIDDEN write_buffer_pop(WriteBuffer *wb);+HIDDEN void es_write_buffer_pop(WriteBuffer *wb); // buf itself is not freed. // it's safe to call write_buffer_free multiple times on the same buf.-void HIDDEN write_buffer_free(WriteBuffer *wb);+HIDDEN void es_write_buffer_free(WriteBuffer *wb); #endif /* EVENTLOG_SOCKET_WRITE_BUFFER_H */
eventlog-socket.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: eventlog-socket-version: 0.1.1.0+version: 0.1.2.0 synopsis: Stream GHC eventlog events to external processes. description: The @eventlog-socket@ package lets you stream the GHC eventlog over a socket in realtime.@@ -29,10 +29,13 @@ cbits/eventlog_socket/debug.h cbits/eventlog_socket/error.c cbits/eventlog_socket/error.h+ cbits/eventlog_socket/init_state.h cbits/eventlog_socket/macros.h cbits/eventlog_socket/poll.h cbits/eventlog_socket/string.c cbits/eventlog_socket/string.h+ cbits/eventlog_socket/worker.c+ cbits/eventlog_socket/worker.h cbits/eventlog_socket/write_buffer.c cbits/eventlog_socket/write_buffer.h cbits/eventlog_socket.c@@ -50,7 +53,7 @@ source-repository this type: git location: https://github.com/well-typed/eventlog-socket.git- tag: eventlog-socket-0.1.1.0+ tag: eventlog-socket-0.1.2.0 subdir: eventlog-socket flag debug@@ -58,6 +61,16 @@ default: False manual: True +flag debug-verbosity-trace+ description: Set the debug verbosity to TRACE.+ default: False+ manual: True++flag debug-verbosity-quiet+ description: Set the debug verbosity to QUIET.+ default: False+ manual: True+ flag control description: Enable control commands. default: False@@ -86,10 +99,11 @@ c-sources: cbits/eventlog_socket/error.c cbits/eventlog_socket/string.c+ cbits/eventlog_socket/worker.c cbits/eventlog_socket/write_buffer.c cbits/eventlog_socket.c - include-dirs: include+ include-dirs: cbits include install-includes: eventlog_socket.h if flag(debug)@@ -97,6 +111,21 @@ -- cpp-options are not passed to C code, see: -- https://github.com/haskell/cabal/issues/7635 cc-options: -DDEBUG=1++ else+ cc-options: -DNDEBUG=1++ if flag(debug-verbosity-trace)+ -- 2025-12-17:+ -- cpp-options are not passed to C code, see:+ -- https://github.com/haskell/cabal/issues/7635+ cc-options: -DDEBUG_VERBOSITY=5++ if flag(debug-verbosity-quiet)+ -- 2025-12-17:+ -- cpp-options are not passed to C code, see:+ -- https://github.com/haskell/cabal/issues/7635+ cc-options: -DDEBUG_VERBOSITY=0 if flag(control) -- 2025-12-17:
include/eventlog_socket.h view
@@ -11,7 +11,7 @@ /// Haskell API, then the RTS is started with the default file writer, which /// means that the first few events are written to a file instead of the /// eventlog socket. To avoid this, you can instrument your application using-/// the C API. See `eventlog_socket_init` and `eventlog_socket_wrap_hs_main`.+/// the C API. See `eventlog_socket_wrap_hs_main`. /// /// If you want to register custom commands via the C API, see /// `eventlog_socket_control_register_namespace` and@@ -31,6 +31,9 @@ * Error Types ******************************************************************************/ +/// @brief The status codes for `EventlogSocketStatus`.+///+/// @since 0.1.2.0 typedef enum EventlogSocketStatusCode { /// @brief The operation completed successfully. EVENTLOG_SOCKET_OK = 0,@@ -61,6 +64,9 @@ EVENTLOG_SOCKET_ERR_SYS, } EventlogSocketStatusCode; +/// @brief The return status for the functions in the @c eventlog-socket C API.+///+/// @since 0.1.2.0 typedef struct EventlogSocketStatus { /// @brief The eventlog socket error code. const EventlogSocketStatusCode ess_status_code;@@ -68,7 +74,7 @@ const int ess_error_code; } EventlogSocketStatus; -/// @brief Return a string that describese the status.+/// @brief Return a string that describes the status. /// /// @return Upon successful completion, a buffer containing a string that /// describes the @p status is returned. This buffer is allocated with @c malloc@@ -76,6 +82,8 @@ /// /// @return On error, @c NULL is returned and @c errno is set to indicate the /// error.+///+/// @since 0.1.2.0 char *eventlog_socket_strerror(EventlogSocketStatus status); /******************************************************************************@@ -85,6 +93,8 @@ /// @brief The address family of the eventlog socket. /// /// Used as the tag for the tagged union `EventlogSocketAddr`.+///+/// @since 0.1.2.0 typedef enum EventlogSocketTag { /// @brief A Unix domain socket address. EVENTLOG_SOCKET_UNIX,@@ -93,12 +103,16 @@ } EventlogSocketTag; /// @brief The address for a Unix domain socket.+///+/// @since 0.1.2.0 typedef struct EventlogSocketUnixAddr { /// The path to the Unix domain socket. char *esa_unix_path; } EventlogSocketUnixAddr; /// @brief The address for a TCP/IPv4 socket.+///+/// @since 0.1.2.0 typedef struct EventlogSocketInetAddr { /// The host name. char *esa_inet_host;@@ -107,6 +121,8 @@ } EventlogSocketInetAddr; /// @brief The options for an eventlog socket.+///+/// @since 0.1.2.0 typedef struct EventlogSocketOpts { /// @brief Whether or not to wait for a client to connect. bool eso_wait;@@ -119,6 +135,8 @@ } EventlogSocketOpts; /// @brief The address for an eventlog socket.+///+/// @since 0.1.2.0 typedef struct EventlogSocketAddr { /// @brief The address family. ///@@ -137,33 +155,47 @@ /// @brief Free any memory allocated by the members of the `EventlogSocketAddr` /// value.+///+/// @since 0.1.2.0 void eventlog_socket_addr_free(EventlogSocketAddr *eventlog_socket); /// @brief Initialise the `EventlogSocketOpts` object with the default options.+///+/// @since 0.1.2.0 void eventlog_socket_opts_init(EventlogSocketOpts *eventlog_socket_opts); /// @brief Free any memory allocated by the members of the `EventlogSocketOpts` /// value.+///+/// @since 0.1.2.0 void eventlog_socket_opts_free(EventlogSocketOpts *opts); /// @brief /// The name of the environment variable used by `eventlog_socket_from_env` /// to determine the path to the Unix domain socket.+///+/// @since 0.1.2.0 #define EVENTLOG_SOCKET_ENV_UNIX_PATH "GHC_EVENTLOG_UNIX_PATH" /// @brief /// The name of the environment variable used by `eventlog_socket_from_env` /// to determine the host name for a TCP/IPv4 socket.+///+/// @since 0.1.2.0 #define EVENTLOG_SOCKET_ENV_INET_HOST "GHC_EVENTLOG_INET_HOST" /// @brief /// The name of the environment variable used by `eventlog_socket_from_env` /// to determine the port number for a TCP/IPv4 socket.+///+/// @since 0.1.2.0 #define EVENTLOG_SOCKET_ENV_INET_PORT "GHC_EVENTLOG_INET_PORT" /// @brief /// The name of the environment variable used by `eventlog_socket_from_env` /// to determine whether or not to wait.+///+/// @since 0.1.2.0 #define EVENTLOG_SOCKET_ENV_WAIT "GHC_EVENTLOG_WAIT" /// @brief Start the eventlog socket writer.@@ -178,6 +210,8 @@ /// @pre The GHC RTS is initialized. /// /// @pre The argument `eventlog_socket_addr` is nonnull.+///+/// @since 0.1.2.0 EventlogSocketStatus eventlog_socket_start(const EventlogSocketAddr *eventlog_socket_addr, const EventlogSocketOpts *eventlog_socket_opts);@@ -210,49 +244,73 @@ /// @return If any other status is returned, the contents of /// @p eventlog_socket_addr_out and @p eventlog_socket_opts_out are unchanged. ///+/// @since 0.1.2.0+/// /// @par Examples /// @parblock-/// The following function initialises eventlog socket using a socket address-/// and options from the environment.+/// The following function initialises eventlog socket from a C main function. /// @code{.c}-/// EventlogSocketFromEnvStatus init_from_env(void) {+/// #include <stdio.h>+/// #include <stdlib.h>+/// #include <Rts.h>+/// #include <eventlog_socket.h> ///+/// // Define the eventlog Unix domain socket path.+/// #define MY_EVENTLOG_SOCKET "/tmp/my_eventlog.socket"+///+/// // Get the closure for the Haskell main.+/// extern StgClosure ZCMain_main_closure;+///+/// int main(int argc, char* argv[]) {+///+/// // Create a GHC RTS configuration object.+/// RtsConfig rts_config = {0};+/// memcpy(&rts_config, &defaultRtsConfig, sizeof(RtsConfig));+/// rts_config.rts_opts_enabled = RtsOptsAll; // Enable all RTS options.+/// rts_config.rts_opts = "-l"; // Enable binary eventlog.+/// /// // Read the socket address and options from the environment.-/// EventlogSocketAddr eventlog_socket_addr = {0};-/// EventlogSocketOpts eventlog_socket_opts = {0};-/// const EventlogSocketFromEnvStatus status =-/// eventlog_socket_from_env(&eventlog_socket_addr,-/// &eventlog_socket_opts);+/// EventlogSocketAddr addr = {0};+/// EventlogSocketOpts opts = {0};+/// EventlogSocketStatus status = eventlog_socket_from_env(&addr, &opts); /// /// // Handle the return status. /// switch (status) { /// case EVENTLOG_SOCKET_FROM_ENV_OK:-/// // Initialise eventlog socket.-/// eventlog_socket_init(&eventlog_socket_addr, &eventlog_socket_opts);-/// break;-/// case EVENTLOG_SOCKET_FROM_ENV_NONE:-/// return status; // Skip free.-/// case EVENTLOG_SOCKET_FROM_ENV_UNIX_PATH_TOO_LONG:+/// // Evaluate the wrapped Haskell main closure.+/// return eventlog_socket_wrap_hs_main(argc, argv, rts_config,+/// &ZCMain_main_closure, &addr, &opts);+/// case EVENTLOG_SOCKET_ERR_ENV_NOADDR:+/// // Return with an exit failure, skip free.+/// exit(EXIT_FAILURE);+/// case EVENTLOG_SOCKET_ERR_ENV_TOOLONG: /// fprintf(stderr, "Error: value of %s (%s) is too long\n", /// EVENTLOG_SOCKET_ENV_UNIX_PATH, /// eventlog_socket_addr.esa_unix_addr.esa_unix_path); /// break;-/// case EVENTLOG_SOCKET_FROM_ENV_INET_HOST_MISSING:+/// case EVENTLOG_SOCKET_ERR_ENV_NOHOST: /// fprintf(stderr, "Error: no value given for %s\n", /// EVENTLOG_SOCKET_ENV_INET_HOST); /// break;-/// case EVENTLOG_SOCKET_FROM_ENV_INET_PORT_MISSING:+/// case EVENTLOG_SOCKET_ERR_ENV_NOPORT: /// fprintf(stderr, "Error: no value given for %s\n", /// EVENTLOG_SOCKET_ENV_INET_PORT); /// break;-/// case EVENTLOG_SOCKET_FROM_ENV_SYSTEM:+/// default:+/// char *errmsg = eventlog_socket_strerror(status);+/// if (errmsg != NULL) {+/// fprintf(stderr, "Error: %s\n", errmsg);+/// free(errmsg);+/// }+/// break; /// } /// /// // Free the memory held by socket address and options. /// eventlog_socket_addr_free(&eventlog_socket_addr); /// eventlog_socket_opts_free(&eventlog_socket_opts); ///-/// return status;+/// // Return with an exit failure.+/// exit(EXIT_FAILURE); /// } /// @endcode /// @endparblock@@ -267,8 +325,16 @@ /// To install the eventlog socket writer *after* the GHC RTS has started, use /// `eventlog_socket_start`. ///+/// @note You do not need to call this function if you're starting eventlog+/// socket using the Haskell API, via `eventlog_socket_start`, or via+/// `eventlog_socket_wrap_hs_main`.+///+/// @warning `eventlog_socket_init` ignores the @c eso_wait option.+/// /// @return See `EventlogSocketStatus`. ///+/// @since 0.1.2.0+/// /// @par Errors /// @parblock /// This function may return the following system errors:@@ -283,15 +349,31 @@ /// `EAI_MEMORY`, `EAI_NODATA`, `EAI_NONAME`, `EAI_SERVICE`, `EAI_SOCKTYPE`, or /// `EAI_SYSTEM`. /// @endparblock+EventlogSocketStatus+eventlog_socket_init(const EventlogSocketAddr *eventlog_socket_addr,+ const EventlogSocketOpts *eventlog_socket_opts);++/// @brief Evaluate the Haskell main closure using the eventlog socket writer. ///+/// Use this when you install the eventlog socket writer *before* the GHC RTS+/// has started. This is primarily intended to be called from a C main function.+/// To install the eventlog socket writer *after* the GHC RTS has started, use+/// `eventlog_socket_start`.+///+/// This function attaches the eventlog-socket writer to the @p rts_config,+/// initializes eventlog-socket and the GHC RTS, and evaluates the Haskell+/// main closure.+///+/// If your needs are more fine-grained, you can use `eventlog_socket_init`,+/// `EventlogSocketWriter`, `eventlog_socket_signal_ghc_rts_ready`, and the+/// GHC RTS API directly. For details, see the code for this function.+/// /// @par Examples /// @parblock /// The following function initialises eventlog socket from a C main function. /// @code{.c} /// #include <Rts.h> /// #include <eventlog_socket.h>-/// #include <netdb.h>-/// #include <stdlib.h> /// /// // Define the eventlog Unix domain socket path. /// #define MY_EVENTLOG_SOCKET "/tmp/my_eventlog.socket"@@ -301,65 +383,41 @@ /// /// int main(int argc, char* argv[]) { ///-/// // Create a GHC RTS configuration object.-/// RtsConfig rts_config = {0};-/// memcpy(&rts_config, &defaultRtsConfig, sizeof(RtsConfig));-/// rts_config.rts_opts_enabled = RtsOptsAll; // Enable all RTS options.-/// rts_config.rts_opts = "-l"; // Enable binary eventlog.+/// // Create a GHC RTS configuration object.+/// RtsConfig rts_config = {0};+/// memcpy(&rts_config, &defaultRtsConfig, sizeof(RtsConfig));+/// rts_config.rts_opts_enabled = RtsOptsAll; // Enable all RTS options.+/// rts_config.rts_opts = "-l"; // Enable binary eventlog. ///-/// // Initialise eventlog socket using the default options.-/// EventlogSocketAddr eventlog_socket_addr = {+/// // Create the eventlog-socket address+/// const EventlogSocketAddr addr = { /// .esa_tag = EVENTLOG_SOCKET_UNIX, /// .esa_unix_addr = { /// .esa_unix_path = MY_EVENTLOG_SOCKET,-/// }};-/// const int success_or_eaino =-/// eventlog_socket_init(&eventlog_socket_addr, NULL);-/// if (success_or_eaino != 0) {-/// fprintf(stderr, "eventlog_socket_init() failed: %s",-/// gai_strerror(success_or_eaino));-/// }+/// }}; ///-/// // Evaluate the close for the Haskell main.+/// // Create the eventlog-socket options+/// EventlogSocketOpts opts = {0};+/// eventlog_socket_opts_init(&opts);+/// opts->eso_wait = true; // Ask eventlog-socket to wait for a connection.+///+/// // Evaluate the wrapped Haskell main closure. /// return eventlog_socket_wrap_hs_main(argc, argv, rts_config,-/// &ZCMain_main_closure);+/// &ZCMain_main_closure, &addr, &opts); /// } /// @endcode /// @endparblock-EventlogSocketStatus-eventlog_socket_init(const EventlogSocketAddr *eventlog_socket_addr,- const EventlogSocketOpts *eventlog_socket_opts);--/// @brief Evaluate the Haskell main closure using the eventlog socket writer.-///-/// Use this when you install the eventlog socket writer *before* the GHC RTS-/// has started. This is primarily intended to be called from a C main function.-/// To install the eventlog socket writer *after* the GHC RTS has started, use-/// `eventlog_socket_start`.-///-/// This function attaches the `SocketEventLogWriter` to the @p rts_config,-/// initializes the GHC RTS and calls `eventlog_socket_signal_ghc_rts_ready`,-/// and finally evaluates the Haskell main closure.-///-/// If your needs are more fine-grained, you can use `SocketEventLogWriter`,-/// `eventlog_socket_signal_ghc_rts_ready`, and the GHC RTS API directly.-///-/// @pre The eventlog socket was successfully initialised using-/// `eventlog_socket_init`.-///-/// @par Examples-/// @parblock-/// See `eventlog_socket_init`.-/// @endparblock-void eventlog_socket_wrap_hs_main(int argc, char *argv[], RtsConfig rts_config,- StgClosure *main_closure);+void eventlog_socket_wrap_hs_main(+ int argc, char *argv[], RtsConfig rts_config, StgClosure *main_closure,+ const EventlogSocketAddr *eventlog_socket_addr,+ const EventlogSocketOpts *eventlog_socket_opts); -/// @brief Attaches the global `EventLogWriter` object to an @c RtsConfig.+/// The global `EventLogWriter` object for @c eventlog-socket. ///-/// @note You do not need to call this function if you're starting eventlog+/// @note You do not need to use this object if you're starting eventlog /// socket using the Haskell API, via `eventlog_socket_start`, or via /// `eventlog_socket_wrap_hs_main`.-RtsConfig eventlog_socket_attach_rts_config(RtsConfig rts_config);+extern const EventLogWriter EventLogSocketWriter; /// @brief Signal that the GHC RTS is ready. ///@@ -382,6 +440,8 @@ /// `EAGAIN`, `EBUSY`, `EDEADLK`, `EINVAL`, `ENOTRECOVERABLE`, `EOWNERDEAD`, or /// `EPERM`. /// @endparblock+///+/// @since 0.1.2.0 EventlogSocketStatus eventlog_socket_signal_ghc_rts_ready(void); /// @brief Wait for a client to connect to the eventlog socket.@@ -403,24 +463,42 @@ /// `EAGAIN`, `EBUSY`, `EDEADLK`, `EINVAL`, `ENOTRECOVERABLE`, `EOWNERDEAD`, or /// `EPERM`. /// @endparblock+///+/// @since 0.1.2.0 EventlogSocketStatus eventlog_socket_wait(void); +/// @brief Read the current status of the worker thread.+///+/// @since 0.1.2.0+EventlogSocketStatus eventlog_socket_worker_status(void);++/// @brief Read the current status of the control thread.+///+/// @since 0.1.2.0+EventlogSocketStatus eventlog_socket_control_status(void);+ /****************************************************************************** * Control Commands ******************************************************************************/ /// @brief The version of the eventlog-socket control command protocol /// implemented by this version of the library.+///+/// @since 0.1.2.0 #define EVENTLOG_SOCKET_CONTROL_PROTOCOL_VERSION 0 /// @brief A control command namespace. /// /// See `eventlog_socket_control_register_namespace`.+///+/// @since 0.1.2.0 typedef struct EventlogSocketControlNamespace EventlogSocketControlNamespace; /// @brief A control command ID. /// /// Individual commands are identified by numbers (0-255).+///+/// @since 0.1.2.0 typedef uint8_t EventlogSocketControlCommandId; /// @brief A control command handler.@@ -428,6 +506,8 @@ /// This function is called when the corresponding control command message is /// received on the eventlog socket. The `command_data` parameter will contain /// the pointer provided to `eventlog_socket_control_register_command`.+///+/// @since 0.1.2.0 typedef void EventlogSocketControlCommandHandler( const EventlogSocketControlNamespace *const namespace, const EventlogSocketControlCommandId command_id, const void *command_data);@@ -441,6 +521,8 @@ /// /// @return If the binary was compiled without control support, this function /// returns @c NULL.+///+/// @since 0.1.2.0 const char * eventlog_socket_control_strnamespace(EventlogSocketControlNamespace *namespace); @@ -459,6 +541,8 @@ /// /// @return If the binary was compiled without control support, this function /// returns a status with code @c EVENTLOG_SOCKET_ERR_CTL_NOSUPPORT.+///+/// @since 0.1.2.0 EventlogSocketStatus eventlog_socket_control_register_namespace( uint8_t namespace_len, const char namespace[namespace_len + 1], EventlogSocketControlNamespace **namespace_out);@@ -484,6 +568,8 @@ /// returns a status with code @c EVENTLOG_SOCKET_ERR_CTL_NOSUPPORT. /// /// @note The @p command_handler should not use the functions from this header.+///+/// @since 0.1.2.0 EventlogSocketStatus eventlog_socket_control_register_command( EventlogSocketControlNamespace *namespace, EventlogSocketControlCommandId command_id,
src/GHC/Eventlog/Socket.hsc view
@@ -54,19 +54,23 @@ registerCommand, EventlogSocketControlError(..), + -- ** Low-level API #low_level_api#+ testWorkerStatus,+ testControlStatus,+ -- * Legacy API #legacy_api# startWait, start, wait, ) where -import Control.Exception (Exception (..), assert, bracket, bracket_, bracketOnError, throwIO)+import Control.Exception (AssertionFailed (..), Exception (..), assert, bracket, bracket_, bracketOnError, throwIO) import Control.Monad((<=<), when) import Data.Foldable (traverse_) import Data.Function ((&)) import Data.Int (Int32) import Data.Maybe (fromMaybe)-import Data.Void (Void)+import Data.Void (Void, vacuous) import Data.Word (Word8, Word32) import Foreign.C (CBool (..)) import Foreign.C.String (CString, peekCString, withCString, withCStringLen)@@ -77,6 +81,7 @@ import GHC.Enum (toEnumError) import System.IO.Unsafe (unsafePerformIO) +#include <eventlog_socket/macros.h> #include <eventlog_socket.h> #include <string.h> @@ -87,7 +92,7 @@ {- | Start an @eventlog-socket@ writer using the given socket address and options. -@since 0.1.1.0+@since 0.1.2.0 -} startWith :: EventlogSocketAddr ->@@ -101,10 +106,8 @@ status <- peekEventlogSocketStatus essPtr -- If the status is an error, throw a user error.- when (essStatusCode status /= EVENTLOG_SOCKET_OK) $ do- strPtr <- eventlog_socket_strerror essPtr- str <- peekNullableCString strPtr- throwIO $ userError str+ when (essStatusCode status /= EVENTLOG_SOCKET_OK) $+ vacuous $ throwEventlogSocketStatus essPtr -------------------------------------------------------------------------------- -- Configuration types@@ -112,7 +115,7 @@ {- | A type representing the supported eventlog socket modes. -@since 0.1.1.0+@since 0.1.2.0 -} data {-# CTYPE "eventlog_socket.h" "EventlogSocketAddr" #-}@@ -158,7 +161,7 @@ See the documentation for @SO_LINGER@ in @socket.h@. -@since 0.1.1.0+@since 0.1.2.0 -} data {-# CTYPE "eventlog_socket.h" "EventlogSocketOpts" #-}@@ -174,7 +177,7 @@ See t`EventlogSocketOpts`. -@since 0.1.1.0+@since 0.1.2.0 -} defaultEventlogSocketOpts :: EventlogSocketOpts defaultEventlogSocketOpts =@@ -192,7 +195,7 @@ Read the eventlog socket configuration from the environment. If this succeeds, start an @eventlog-socket@ writer with that configuration. -@since 0.1.1.0+@since 0.1.2.0 -} startFromEnv :: IO () startFromEnv = fromEnv >>= traverse_ (uncurry startWith)@@ -200,7 +203,7 @@ {- | Read the eventlog socket configuration from the environment. -@since 0.1.1.0+@since 0.1.2.0 -} fromEnv :: IO (Maybe (EventlogSocketAddr, EventlogSocketOpts))@@ -239,10 +242,7 @@ esa <- peekEventlogSocketAddr esaPtr throwIO $ EventlogSocketAddrInetHostMissing (esaInetHost esa) | otherwise =- withEventlogSocketStatus status $ \essPtr -> do- strPtr <- eventlog_socket_strerror essPtr- str <- peekNullableCString strPtr- throwIO $ userError str+ vacuous $ withEventlogSocketStatus status throwEventlogSocketStatus bracket tryGet maybeFree maybePeek @@ -267,6 +267,8 @@ {- | The type of exceptions thrown by `fromEnv`.++@since 0.1.2.0 -} data EventlogSocketAddrError = EventlogSocketAddrUnixPathTooLong FilePath@@ -302,6 +304,70 @@ <> eventlogSocketEnvInetPort <> " was not set." +{- |+Test the current status of the worker thread. If it has failed, throw an `IOException`.++@since 0.1.2.0+-}+testWorkerStatus :: IO ()+testWorkerStatus =+ throwEventlogSocketStatusAsIOException =<< workerStatus++{- |+Test the current status of the control thread. If it has failed, throw an `IOException`.++@since 0.1.2.0+-}+testControlStatus :: IO ()+testControlStatus =+ throwEventlogSocketStatusAsIOException =<< controlStatus++{- |+Internal helper.++Read the current worker status.+-}+workerStatus :: IO EventlogSocketStatus+workerStatus =+ allocaBytes #{size EventlogSocketStatus} $ \essPtr -> do+ eventlog_socket_worker_status essPtr+ peekEventlogSocketStatus essPtr++{- |+Internal helper.++Read the current control status.+-}+controlStatus :: IO EventlogSocketStatus+controlStatus =+ allocaBytes #{size EventlogSocketStatus} $ \essPtr -> do+ eventlog_socket_control_status essPtr+ peekEventlogSocketStatus essPtr++{- |+Internal helper.++Throw an `EventlogSocketStatus` as an `IOException`.+-}+throwEventlogSocketStatusAsIOException :: EventlogSocketStatus -> IO ()+throwEventlogSocketStatusAsIOException ess =+ when (essStatusCode ess /= EVENTLOG_SOCKET_OK) $+ vacuous $ withEventlogSocketStatus ess throwEventlogSocketStatus++{- |+Internal helper.++Throw an `EventlogSocketStatus` as an `IOException`.++__Warning__: This function _still_ throws an error if the status code is `EVENTLOG_SOCKET_OK`.+-}+throwEventlogSocketStatus :: Ptr EventlogSocketStatus -> IO Void+throwEventlogSocketStatus essPtr = do+ strPtr <- eventlog_socket_strerror essPtr+ str <- peekNullableCString strPtr+ free strPtr+ throwIO $ userError str+ -------------------------------------------------------------------------------- -- Control Commands API --------------------------------------------------------------------------------@@ -311,7 +377,7 @@ Namespaces are opaque and can only be obtained using `registerNamespace`. -@since 0.1.1.0+@since 0.1.2.0 -} newtype {-# CTYPE "eventlog_socket.h" "EventlogSocketControlNamespace" #-}@@ -320,7 +386,7 @@ {- | Get the `String` name for the given t`Namespace`. -@since 0.1.1.0+@since 0.1.2.0 -} namespaceName :: Namespace -> IO String namespaceName (Namespace namespacePtr) = do@@ -331,7 +397,7 @@ Command IDs must be non-zero integers between 1 and 255. -@since 0.1.1.0+@since 0.1.2.0 -} newtype {-# CTYPE "eventlog_socket.h" "EventlogSocketControlCommandId" #-}@@ -345,7 +411,7 @@ __Warning__: The command handler /must not/ call back into the @eventlog-socket@ API. -@since 0.1.1.0+@since 0.1.2.0 -} type CommandHandler = IO () @@ -362,7 +428,7 @@ __Warning__: Namespaces cannot be unregistered and will be kept in memory until program exit. -@since 0.1.1.0+@since 0.1.2.0 -} registerNamespace :: -- | The name for the namespace.@@ -408,10 +474,7 @@ -- The remaining errors are all system errors: _otherwise ->- withEventlogSocketStatus status $ \essPtr -> do- strPtr <- eventlog_socket_strerror essPtr- str <- peekNullableCString strPtr- throwIO $ userError str+ vacuous $ withEventlogSocketStatus status throwEventlogSocketStatus {- | Register an @eventlog-socket@ control command with the given ID and handler in the given namespace.@@ -422,7 +485,7 @@ __Warning__: Commands cannot be unregistered and will be kept in memory until program exit. -@since 0.1.1.0+@since 0.1.2.0 -} registerCommand :: -- | The namespace.@@ -477,10 +540,7 @@ -- The remaining errors are all system errors: _otherwise ->- withEventlogSocketStatus status $ \essPtr -> do- strPtr <- eventlog_socket_strerror essPtr- str <- peekNullableCString strPtr- throwIO $ userError str+ vacuous $ withEventlogSocketStatus status throwEventlogSocketStatus -------------------------------------------------------------------------------- -- Errors@@ -488,7 +548,7 @@ {- | The type of exceptions thrown by `registerNamespace` and `registerCommand`. -@since 0.1.1.0+@since 0.1.2.0 -} data EventlogSocketControlError = EventlogSocketControlNamespaceTooLong@@ -838,7 +898,7 @@ -------------------------------------------------------------------------------- -- eventlog_socket_start -foreign import capi safe "GHC/Eventlog/Socket_hsc.h _wrap_eventlog_socket_start"+foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_start" eventlog_socket_start :: Ptr EventlogSocketStatus -> Ptr EventlogSocketAddr ->@@ -846,7 +906,7 @@ IO () #{def- void _wrap_eventlog_socket_start(+ HIDDEN void eventlog_socket_wrap_start( EventlogSocketStatus *eventlog_socket_status, EventlogSocketAddr *eventlog_socket_addr, EventlogSocketOpts *eventlog_socket_opts@@ -863,7 +923,7 @@ -------------------------------------------------------------------------------- -- eventlog_socket_from_env -foreign import capi safe "GHC/Eventlog/Socket_hsc.h _wrap_eventlog_socket_from_env"+foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_from_env" eventlog_socket_from_env :: Ptr EventlogSocketStatus -> Ptr EventlogSocketAddr ->@@ -871,7 +931,7 @@ IO () #{def- void _wrap_eventlog_socket_from_env(+ HIDDEN void eventlog_socket_wrap_from_env( EventlogSocketStatus *eventlog_socket_status, EventlogSocketAddr *eventlog_socket_addr, EventlogSocketOpts *eventlog_socket_opts@@ -894,13 +954,13 @@ -------------------------------------------------------------------------------- -- eventlog_socket_strerror -foreign import capi safe "GHC/Eventlog/Socket_hsc.h _wrap_eventlog_socket_strerror"+foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_strerror" eventlog_socket_strerror :: Ptr EventlogSocketStatus -> IO CString #{def- char *_wrap_eventlog_socket_strerror(+ HIDDEN char *eventlog_socket_wrap_strerror( EventlogSocketStatus *eventlog_socket_status ) {@@ -909,6 +969,42 @@ } --------------------------------------------------------------------------------+-- eventlog_socket_worker_status++foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_worker_status"+ eventlog_socket_worker_status ::+ Ptr EventlogSocketStatus ->+ IO ()++#{def+ HIDDEN void eventlog_socket_wrap_worker_status(+ EventlogSocketStatus *eventlog_socket_status_out+ )+ {+ const EventlogSocketStatus eventlog_socket_status = eventlog_socket_worker_status();+ memcpy(eventlog_socket_status_out, &eventlog_socket_status, sizeof(EventlogSocketStatus));+ }+}++--------------------------------------------------------------------------------+-- eventlog_socket_control_status++foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_control_status"+ eventlog_socket_control_status ::+ Ptr EventlogSocketStatus ->+ IO ()++#{def+ HIDDEN void eventlog_socket_wrap_control_status(+ EventlogSocketStatus *eventlog_socket_status_out+ )+ {+ const EventlogSocketStatus eventlog_socket_status = eventlog_socket_control_status();+ memcpy(eventlog_socket_status_out, &eventlog_socket_status, sizeof(EventlogSocketStatus));+ }+}++-------------------------------------------------------------------------------- -- eventlog_socket_control_strnamespace -- NOTE: This uses `ccall` rather than `capi` because the underlying function@@ -922,7 +1018,7 @@ -------------------------------------------------------------------------------- -- eventlog_socket_control_register_namespace -foreign import capi safe "GHC/Eventlog/Socket_hsc.h _wrap_eventlog_socket_control_register_namespace"+foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_control_register_namespace" eventlog_socket_control_register_namespace :: Ptr EventlogSocketStatus -> ( #{type uint8_t} ) ->@@ -931,7 +1027,7 @@ IO () #{def- void _wrap_eventlog_socket_control_register_namespace(+ HIDDEN void eventlog_socket_wrap_control_register_namespace( EventlogSocketStatus *eventlog_socket_status, uint8_t eventlog_socket_namespace_len, char eventlog_socket_namespace[eventlog_socket_namespace_len],@@ -949,7 +1045,7 @@ -------------------------------------------------------------------------------- -- eventlog_socket_control_register_command -foreign import capi safe "GHC/Eventlog/Socket_hsc.h _wrap_eventlog_socket_control_register_command"+foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_control_register_command" eventlog_socket_control_register_command :: Ptr EventlogSocketStatus -> Ptr Namespace ->@@ -964,7 +1060,7 @@ IO (FunPtr (Ptr Namespace -> CommandId -> Ptr a -> IO ())) #{def- void _wrap_eventlog_socket_control_register_command(+ HIDDEN void eventlog_socket_wrap_control_register_command( EventlogSocketStatus *eventlog_socket_status, EventlogSocketControlNamespace *eventlog_socket_namespace, EventlogSocketControlCommandId eventlog_socket_command_id,