These macros should replace the use of assert() in our entire codebase.
assert() have the following issues:
- can be turned off at compilation time
- they don't have consistent meaning in our code
kr_require() behaves similarly to assert - it checks a condition and
aborts if it fails. Unlike asserts, these aren't turned off by using
-DNDEBUG. kr_require() should be used for non-recoverable errors.
kr_assume() is a way to check for non-fatal errors which supports error
reporing, debugging and recovery. The function returns a boolean value
which the caller must use for error handling. An error log message is
produced when the condition fails. Optionally, when kr_debug_assumption
is set to true, the process will use fork() and the child will abort().
This generates a coredump for debugging purposes, while allowing the
parent process to keep running and recover from the non-fatal error.
This can be useful for debugging hard to reproduce errors in production
environments.
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
+#include <sys/wait.h>
#include <unistd.h>
#if ENABLE_CAP_NG
static void signal_handler(uv_signal_t *handle, int signum)
{
- uv_stop(uv_default_loop());
- uv_signal_stop(handle);
+ switch (signum) {
+ case SIGINT: /* Fallthrough. */
+ case SIGTERM:
+ uv_stop(uv_default_loop());
+ uv_signal_stop(handle);
+ break;
+ case SIGCHLD:
+ /* Wait for all dead processes. */
+ while (waitpid(-1, NULL, WNOHANG) > 0);
+ break;
+ default:
+ kr_log_error("unhandled signal: %d\n", signum);
+ break;
+ }
}
/** SIGBUS -> attempt to remove the overflowing cache file and abort. */
uv_loop_t *loop = uv_default_loop();
/* Catch some signals. */
- uv_signal_t sigint, sigterm;
+ uv_signal_t sigint, sigterm, sigchld;
if (true) ret = uv_signal_init(loop, &sigint);
if (!ret) ret = uv_signal_init(loop, &sigterm);
+ if (!ret) ret = uv_signal_init(loop, &sigchld);
if (!ret) ret = uv_signal_start(&sigint, signal_handler, SIGINT);
if (!ret) ret = uv_signal_start(&sigterm, signal_handler, SIGTERM);
+ if (!ret) ret = uv_signal_start(&sigchld, signal_handler, SIGCHLD);
/* Block SIGPIPE; see https://github.com/libuv/libuv/issues/45 */
if (!ret && signal(SIGPIPE, SIG_IGN) == SIG_ERR) ret = errno;
if (!ret) {
/* Logging & debugging */
bool kr_verbose_status = false;
+bool kr_debug_assumption = true;
+
+void kr_fail(bool is_fatal, const char *expr, const char *func, const char *file, int line)
+{
+ if (is_fatal)
+ kr_log_critical("requirement \"%s\" failed in %s@%s:%d\n", expr, func, file, line);
+ else
+ kr_log_error("assumption \"%s\" failed in %s@%s:%d\n", expr, func, file, line);
+
+ if (is_fatal || (kr_debug_assumption && fork() == 0))
+ abort();
+}
/*
* Macros.
#include <assert.h>
#include <dirent.h>
#include <stdio.h>
+#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/time.h>
#define kr_log_info printf
#define kr_log_error(...) fprintf(stderr, ## __VA_ARGS__)
+#define kr_log_critical(...) kr_log_error(__VA_ARGS__)
#define kr_log_deprecate(...) fprintf(stderr, "deprecation WARNING: " __VA_ARGS__)
+/** assert() but always, regardless of -DNDEBUG. See also kr_assume(). */
+#define kr_require(expression) do { if (!(expression)) { \
+ kr_fail(true, #expression, __func__, __FILE__, __LINE__); \
+ __builtin_unreachable(); /* aid code analysis */ \
+ } } while (false)
+
+/** Check an assumption that's recoverable. Return the expression.
+ *
+ * If the check fails, optionally fork()+abort() to generate coredump
+ * and continue running in parent process. Return value must be handled to
+ * ensure safe recovery from error. Use kr_require() for unrecoverable checks.
+ */
+#define kr_assume(expression) kr_assume_func((expression), #expression, \
+ __func__, __FILE__, __LINE__)
+
+/** Whether kr_assume() checks should result fork and abort. */
+KR_EXPORT extern bool kr_debug_assumption;
+
+/** Use kr_require() and kr_assume() instead of directly this function. */
+KR_EXPORT KR_COLD void kr_fail(bool is_fatal, const char* expr, const char *func,
+ const char *file, int line);
+
+/** Use kr_require() and kr_assume() instead of directly this function. */
+__attribute__ ((warn_unused_result))
+static inline bool kr_assume_func(bool result, const char *expr, const char *func,
+ const char *file, int line)
+{
+ if (!result)
+ kr_fail(false, expr, func, file, line);
+ return result;
+}
+
/* Always export these, but override direct calls by macros conditionally. */
/** Whether in --verbose mode. Only use this for reading. */
KR_EXPORT extern bool kr_verbose_status;