diff options
author | pommicket <pommicket@gmail.com> | 2025-09-11 11:39:29 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-09-11 12:15:37 -0400 |
commit | 7b8b6367492453958dd0eff6a264ca003002c4b5 (patch) | |
tree | 072a1fdc879f77ca348257f392f0a8bc1d9b9a6b /pom.h | |
parent | be7b1a115b97dc543d0840d10d2d443ab066ce5b (diff) |
Better thread safety in API
Diffstat (limited to 'pom.h')
-rw-r--r-- | pom.h | 222 |
1 files changed, 160 insertions, 62 deletions
@@ -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 |