summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-11 11:39:29 -0400
committerpommicket <pommicket@gmail.com>2025-09-11 12:15:37 -0400
commit7b8b6367492453958dd0eff6a264ca003002c4b5 (patch)
tree072a1fdc879f77ca348257f392f0a8bc1d9b9a6b
parentbe7b1a115b97dc543d0840d10d2d443ab066ce5b (diff)
Better thread safety in API
-rw-r--r--Doxyfile1
-rw-r--r--pom.h222
2 files changed, 161 insertions, 62 deletions
diff --git a/Doxyfile b/Doxyfile
index 01993ae..98c8a1e 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -14,6 +14,7 @@ HTML_OUTPUT = .
HTML_FILE_EXTENSION = .html
GENERATE_LATEX = NO
ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
MULTILINE_CPP_IS_BRIEF = YES
AUTOLINK_SUPPORT = NO
DISTRIBUTE_GROUP_DOC = YES
diff --git a/pom.h b/pom.h
index 465d042..7a86dd1 100644
--- a/pom.h
+++ b/pom.h
@@ -3,11 +3,21 @@
///
/// ## Thread-safety
///
-/// A single configuration should not be used in multiple threads
-/// (even for seemingly "read-only" operations)
-/// without proper synchronization.
-///
-/// Other than that, all these functions are fully thread-safe.
+/// Of course, you should not free a configuration while
+/// another thread is using it (even through a section
+/// obtained via \ref pom_conf_section).
+///
+/// Other than that, all these functions are fully thread-safe
+/// provided that C11 atomics are available
+/// (`__STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)`).
+/// But beware of race conditions when using \ref pom_conf_unread_keys
+/// (ensure there is synchronization so that all threads
+/// have certainly read their keys before calling it).
+///
+/// If C11 atomics are not available, you should not use
+/// the same configuration across multiple threads (even for
+/// seemingly "read-only" operations), but you can still use
+/// distinct configurations in different threads without worry.
/// \mainpage libpom doxygen documentation
///
@@ -22,6 +32,21 @@
#include <stdint.h>
#include <stdbool.h>
+#ifndef POM__MUST_USE_L
+ #if _MSC_VER >= 1700 // supposedly was added in VS2012
+ #define POM__MUST_USE_L _Check_return_
+ #else
+ #define POM__MUST_USE_L
+ #endif
+#endif
+#ifndef POM__MUST_USE_R
+ #if __GNUC__ >= 4
+ #define POM__MUST_USE_R __attribute__((warn_unused_result))
+ #else
+ #define POM__MUST_USE_R
+ #endif
+#endif
+
/// A POM configuration
typedef struct pom_conf pom_conf;
/// A POM-related error
@@ -34,6 +59,9 @@ typedef struct pom_error pom_error;
///
/// On failure, `NULL` is returned, and `*error` is filled out if `error` is not `NULL`,
/// in which case it must be freed with `free`.
+/// In the extremely rare case that `error` is not `NULL` and
+/// there isn’t even enough memory for an out-of-memory
+/// error, `NULL` is returned and `*error` is set to `NULL`.
///
/// Most of the time, you won't need this function:
/// use \ref pom_load_file to load from a `FILE *`,
@@ -47,7 +75,12 @@ typedef struct pom_error pom_error;
/// A return value less than `len` indicates the end of the file was reached.
///
/// `filename` is only used for errors.
-pom_conf *pom_load(const char *filename, size_t (*read_func)(void *userdata, char *buf, size_t len), void *userdata, pom_error **error);
+POM__MUST_USE_L
+pom_conf *
+pom_load(const char *filename,
+ size_t (*read_func)(void *userdata, char *buf, size_t len),
+ void *userdata, pom_error **error)
+POM__MUST_USE_R;
#ifndef POM_NO_STDIO
/// Load configuration from a `FILE *`.
///
@@ -58,7 +91,10 @@ pom_conf *pom_load(const char *filename, size_t (*read_func)(void *userdata, cha
/// in which case it must be freed with `free`.
///
/// `filename` is only used for errors.
-pom_conf *pom_load_file(const char *filename, FILE *file, pom_error **error);
+POM__MUST_USE_L
+pom_conf *
+pom_load_file(const char *filename, FILE *file, pom_error **error)
+POM__MUST_USE_R;
/// Load configuration from a file path.
///
/// On success, a configuration is returned and `*error` is set to `NULL`
@@ -66,7 +102,10 @@ pom_conf *pom_load_file(const char *filename, FILE *file, pom_error **error);
///
/// On failure, `NULL` is returned, and `*error` is filled out if `error` is not `NULL`,
/// in which case it must be freed with `free`.
-pom_conf *pom_load_path(const char *path, pom_error **error);
+POM__MUST_USE_L
+pom_conf *
+pom_load_path(const char *path, pom_error **error)
+POM__MUST_USE_R;
#endif
/// Load configuration from a string.
///
@@ -77,89 +116,135 @@ pom_conf *pom_load_path(const char *path, pom_error **error);
/// in which case it must be freed with `free`.
///
/// `filename` is only used for errors.
-pom_conf *pom_load_string(const char *filename, const char *string, pom_error **error);
+POM__MUST_USE_L
+pom_conf *
+pom_load_string(const char *filename, const char *string, pom_error **error)
+POM__MUST_USE_R;
/// Get the message of this error.
///
/// This will be a string such as `"Duplicate key: foo.bar"`
///
/// See also \ref pom_error_print and \ref pom_error_to_string, which
/// are probably actually what you want in most cases.
-const char *pom_error_message(const pom_error *error);
+const char *
+pom_error_message(const pom_error *error);
/// Get the name of the file where this error occured.
-const char *pom_error_file(const pom_error *error);
+const char *
+pom_error_file(const pom_error *error);
/// Get line number where this error occured.
-uint64_t pom_error_line(const pom_error *error);
+uint64_t
+pom_error_line(const pom_error *error);
/// Get next error in error list.
-const pom_error *pom_error_next(const pom_error *error);
+const pom_error *
+pom_error_next(const pom_error *error);
#ifndef POM_NO_STDIO
/// Print error to `stderr`.
///
/// Includes every error in an error list (see \ref pom_error_next).
-void pom_error_print(const pom_error *error);
+void
+pom_error_print(const pom_error *error);
#endif
-/// Convert error to string. Return value must be freed.
+/// Convert error to string. Return value must be `free()`d.
///
/// Includes every error in an error list (see \ref pom_error_next).
-char *pom_error_to_string(const pom_error *error);
-/// Get error from a POM function. Returns `NULL` if there was no error.
-///
-/// This does not need to be freed, and remainly valid only until \ref pom_conf_free,
-/// `pom_conf_get_<type>`, or `pom_conf_get_<type>_or_default`
-/// is called on `conf`.
-const pom_error *pom_conf_error(const pom_conf *conf);
-#ifndef POM_NO_STDIO
-/// Print last error to `stderr`.
-void pom_conf_print_last_error(const pom_conf *conf);
-#endif
+POM__MUST_USE_L
+char *
+pom_error_to_string(const pom_error *error)
+POM__MUST_USE_R;
/// Get value of `key` in configuration, or `NULL` if key is not present.
-const char *pom_conf_get(const pom_conf *conf, const char *key);
+const char *
+pom_conf_get(const pom_conf *conf, const char *key);
/// Get value of `key` in configuration, or use `dflt` if not present.
-const char *pom_conf_get_or_default(const pom_conf *conf, const char *key, const char *dflt);
+const char *
+pom_conf_get_or_default(const pom_conf *conf, const char *key, const char *dflt);
/// Get signed integer value of `key`.
///
-/// If `key` is not set, returns `false`, and \ref pom_conf_error will be `NULL`
-/// (`*value` is set to 0 in this case).
+/// Returns `NULL` on success, putting the integer value in `*value`.
///
-/// If `key` is set but not a valid integer,
-/// returns `false`, and \ref pom_conf_error will be non-`NULL`
-/// (`*value` is set to 0 in this case).
-bool pom_conf_get_int(const pom_conf *conf, const char *key, int64_t *value);
+/// If `key` is not set or `key` is set but not a valid integer,
+/// returns an error which must be `free()`d.
+/// `*value` is set to 0 in this case.
+POM__MUST_USE_L
+pom_error *
+pom_conf_get_int(const pom_conf *conf, const char *key, int64_t *value)
+POM__MUST_USE_R;
/// Get signed integer value of `key`, or `dflt` if not present.
///
+/// Returns `NULL` on success, putting the integer value in `*value`.
+///
/// If `key` is set but not a valid integer,
-/// returns `false`, and \ref pom_conf_error will be non-`NULL`
-/// (`*value` is still set to `dflt` in this case).
-bool pom_conf_get_int_or_default(const pom_conf *conf, const char *key, int64_t *value, int64_t dflt);
+/// returns an error which must be `free()`d.
+/// `*value` is still set to `dflt` in this case.
+POM__MUST_USE_L
+pom_error *
+pom_conf_get_int_or_default(const pom_conf *conf, const char *key, int64_t *value, int64_t dflt)
+POM__MUST_USE_R;
/// Get unsigned integer value of `key`.
///
-/// If `key` is not set, returns `false`, and \ref pom_conf_error will be `NULL`
-/// (`*value` is set to 0 in this case).
+/// Returns `NULL` on success, putting the unsigned integer value in `*value`.
///
-/// If `key` is set but not a valid unsigned integer,
-/// returns `false`, and \ref pom_conf_error will be non-`NULL`
-/// (`*value` is set to 0 in this case).
-bool pom_conf_get_uint(const pom_conf *conf, const char *key, uint64_t *value);
+/// If `key` is not set or `key` is set but not a valid unsigned integer,
+/// returns an error which must be `free()`d.
+/// `*value` is set to 0 in this case.
+POM__MUST_USE_L
+pom_error *
+pom_conf_get_uint(const pom_conf *conf, const char *key, uint64_t *value)
+POM__MUST_USE_R;
/// Get unsigned integer value of `key`, or `dflt` if not present.
///
+/// Returns `NULL` on success, putting the unsigned integer value in `*value`.
+///
/// If `key` is set but not a valid unsigned integer,
-/// returns `false`, and \ref pom_conf_error will be non-`NULL`
-/// (`*value` is still set to `dflt` in this case).
-bool pom_conf_get_uint_or_default(const pom_conf *conf, const char *key, uint64_t *value, uint64_t dflt);
+/// returns an error which must be `free()`d.
+/// `*value` is set to 0 in this case.
+POM__MUST_USE_L
+pom_error *
+pom_conf_get_uint_or_default(const pom_conf *conf, const char *key, uint64_t *value, uint64_t dflt)
+POM__MUST_USE_R;
/// Get floating-point value of `key`.
///
-/// If `key` is not set, returns `false`, and \ref pom_conf_error will be `NULL`
-/// (`*value` is set to 0.0 in this case).
+/// Returns `NULL` on success, putting the floating-point value in `*value`.
///
-/// If `key` is set but not a valid floating-point number,
-/// returns `false`, and \ref pom_conf_error will be non-`NULL`
-/// (`*value` is set to 0.0 in this case).
-bool pom_conf_get_float(const pom_conf *conf, const char *key, double *value);
+/// If `key` is not set or `key` is set but not a valid floating-point number,
+/// returns an error which must be `free()`d.
+/// `*value` is set to 0.0 in this case.
+POM__MUST_USE_L
+pom_error *
+pom_conf_get_float(const pom_conf *conf, const char *key, double *value)
+POM__MUST_USE_R;
/// Get floating-point value of `key`, or `dflt` if not present.
///
+/// Returns `NULL` on success, putting the floating-point value in `*value`.
+///
/// If `key` is set but not a valid floating-point number,
-/// returns `false`, and \ref pom_conf_error will be non-`NULL`
-/// (`*value` is still set to `dflt` in this case).
-bool pom_conf_get_float_or_default(const pom_conf *conf, const char *key, double *value, double dflt);
+/// returns an error which must be `free()`d.
+/// `*value` is still set to `dflt` in this case.
+POM__MUST_USE_L
+pom_error *
+pom_conf_get_float_or_default(const pom_conf *conf, const char *key, double *value, double dflt)
+POM__MUST_USE_R;
+/// Get boolean value of `key`.
+///
+/// Returns `NULL` on success, putting the boolean value in `*value`.
+///
+/// If `key` is not set or `key` is set but not a valid boolean,
+/// returns an error which must be `free()`d.
+/// `*value` is set to `false` in this case.
+POM__MUST_USE_L
+pom_error *
+pom_conf_get_bool(const pom_conf *conf, const char *key, bool *value)
+POM__MUST_USE_R;
+/// Get boolean value of `key`, or `dflt` if not present.
+///
+/// Returns `NULL` on success, putting the boolean value in `*value`.
+///
+/// If `key` is set but not a valid boolean,
+/// returns an error which must be `free()`d.
+/// `*value` is still set to `dflt` in this case.
+POM__MUST_USE_L
+pom_error *
+pom_conf_get_bool_or_default(const pom_conf *conf, const char *key, bool *value, bool dflt)
+POM__MUST_USE_R;
/// Get comma-separated list value of `key`, or `NULL` if not present. The return value must be freed with `free`.
///
/// The list is `NULL`-terminated.
@@ -168,12 +253,16 @@ bool pom_conf_get_float_or_default(const pom_conf *conf, const char *key, double
///
/// (`free`ing the list also frees the entries thanks to a "hack" where list entries are stored
/// inline after the entry pointers.)
-char **pom_conf_get_list(const pom_conf *conf, const char *key);
+POM__MUST_USE_L
+char **
+pom_conf_get_list(const pom_conf *conf, const char *key)
+POM__MUST_USE_R;
/// Extract section out of POM configuration.
///
/// The returned section doesn't need to be freed, and is valid until
/// \ref pom_conf_free is called on the original configuration.
-const pom_conf *pom_conf_section(const pom_conf *conf, const char *section);
+const pom_conf *
+pom_conf_section(const pom_conf *conf, const char *section);
/// Create a copy of `conf`.
///
/// The copy must be freed with \ref pom_conf_free.
@@ -182,21 +271,30 @@ const pom_conf *pom_conf_section(const pom_conf *conf, const char *section);
///
/// The copy is entirely independent from `conf` — it can be passed to a separate
/// thread without worry.
-pom_conf *pom_conf_copy(const pom_conf *conf);
+POM__MUST_USE_L
+pom_conf *
+pom_conf_copy(const pom_conf *conf)
+POM__MUST_USE_R;
/// Merge keys from `other` into `conf`, preferring keys in `other`.
-void pom_conf_merge(pom_conf *conf, const pom_conf *other);
+void
+pom_conf_merge(pom_conf *conf, const pom_conf *other);
/// Get all unread keys in `conf`.
///
/// The returned array is `NULL`-terminated, and must be freed with `free`.
///
/// (The entries of the array are stored inline with the array,
/// so they are freed when it is.)
-char **pom_conf_unread_keys(pom_conf *conf);
+POM__MUST_USE_L
+char **
+pom_conf_unread_keys(pom_conf *conf)
+POM__MUST_USE_R;
#ifndef POM_NO_STDIO
/// Print `key: value` for each `key` in `conf` to `stdout`.
-void pom_conf_print(const pom_conf *conf);
+void
+pom_conf_print(const pom_conf *conf);
#endif
/// Free a POM configuration.
-void pom_conf_free(pom_conf *conf);
+void
+pom_conf_free(pom_conf *conf);
#endif