spa: utils: add scope based resource cleanup

systemd, dbus-broker, and many glib applications heavily
utilize the GNU C attribute "cleanup" to achieve C++ RAII-like
semantics for local resource management. This commit introduces
essentialy same mechanism into pipewire.

At the moment, this is inteded to be a strictly private API.

free() and close() as cleanup targets are sufficiently common
to warrant their own special macros:

  spa_autofree char *s = strdup(p);
  // will call `free(s)` at the end of the lifetime of `s`

  spa_autoclose int fd = openat(...);
  // will call `close(fd)` if `fd >= 0` at the end of the lifetime of `fd`

However, with `spa_auto()` or `spa_autoptr()` it is possible to define
other variables that will be cleaned up properly. Currently four are supported:

  spa_autoptr(FILE) f = fopen(...);
  // `f` has type `FILE *`
  // will call `fclose(f)` if `f != NULL`

  spa_autoptr(DIR) d = opendir(...);
  // `d` has type `DIR *`
  // will call `closedir(d)` if `d != NULL`

  spa_autoptr(pw_properties) p = pw_properties_new(NULL, NULL);
  // `p` has type `struct pw_properties *`
  // will call `pw_properties_free(p)`

  spa_auto(pw_strv) v = pw_split_strv(...);
  // `v` has type `char **`
  // will call `pw_strv_free(v)`

It is possible to add support for other types, e.g.

  SPA_DEFINE_AUTOPTR_CLEANUP(pw_main_loop, struct pw_main_loop, {
    // the pointer can be accessed using `*thing`
    // `thing` has type `struct pw_main_loop **`
    spa_clear_ptr(*thing, pw_main_loop_destroy);
  })

  spa_autoptr(pw_main_loop) l = ...;
  // `l` has type `struct pw_main_loop *`
  // will call `pw_main_loop_destroy(l)`

or

  SPA_DEFINE_AUTO_CLEANUP(spa_pod_dynamic_builder, struct spa_pod_dynamic_builder, {
    // `thing` has type `struct spa_pod_dynamic_builder *`
    spa_pod_dynamic_builder_clean(thing);
  })

  spa_auto(spa_pod_dynamic_builder) builder = ...
  // `builder` has type `struct spa_pod_dynamic_builder`
  // will call `spa_pod_dynamic_builder_clean(&builder)`

The first argument is always an arbitrary name. This name must be passed to
`spa_auto()` and `spa_autoptr()` as it is what determines the actual type
and destructor used. The second parameter is the concrete type. For
`SPA_DEFINE_AUTO_CLEANUP()` this is the concrete type to be used, while for
`SPA_DEFINE_AUTOPTR_CLEANUP()` it is the concrete type without the
outermost pointer. That is,

  SPA_DEFINE_AUTOPTR_CLEANUP(A, foo, ...)
  SPA_DEFINE_AUTO_CLEANUP(B, foo, ...)

  spa_autoptr(A) x; // `x` has type `foo *`
  spa_auto(B) y; // `y` has type `foo`

A couple other macros are also added:

  spa_clear_ptr(ptr, destructor)
  // calls `destructor(ptr)` if `ptr != NULL` and sets `ptr` to `NULL`

  spa_clear_fd(fd)
  // calls `close(fd)` if `fd >= 0` and sets `fd` to -1

  spa_steal_ptr(ptr)
  // sets `ptr` to `NULL` and returns the old value,
  // useful for preventing the auto cleanup mechanism from kicking in
  // when returning the pointer from a function

  spa_steal_fd(fd)
  // sets `fd` to -1 and returns the old value
This commit is contained in:
Barnabás Pőcze 2023-07-03 20:36:35 +02:00
parent 4456f2efd1
commit 65d949558b
4 changed files with 125 additions and 3 deletions

View file

@ -5,12 +5,12 @@
LIST=""
for i in $(find spa/include -name '*.h' | sed s#spa/include/##);
for i in $(find spa/include -name '*.h' -a -not -path 'spa/include/spa/utils/cleanup.h' | sed s#spa/include/##);
do
[ -f "$PREFIX/include/spa-0.2/$i" ] || LIST="$i $LIST"
done
for i in $(find src/pipewire -name '*.h' -a -not -name '*private.h' | sed s#src/##);
for i in $(find src/pipewire -name '*.h' -a -not -name '*private.h' -a -not -name 'cleanup.h' | sed s#src/##);
do
[ -f "$PREFIX/include/pipewire-0.3/$i" ] || LIST="$i $LIST"
done

View file

@ -14,5 +14,8 @@ spa_sections = [
spa_headers = 'spa' # used by doxygen
install_subdir('spa',
install_dir : get_option('includedir') / spa_name
install_dir : get_option('includedir') / spa_name,
exclude_files : [
'utils/cleanup.h',
],
)

View file

@ -0,0 +1,98 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2023 PipeWire authors */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_UTILS_CLEANUP_H
#define SPA_UTILS_CLEANUP_H
#if !defined(__has_attribute) || !__has_attribute(__cleanup__)
#error "attribute `cleanup` is required"
#endif
#define spa_cleanup(func) __attribute__((__cleanup__(func)))
#define SPA_DEFINE_AUTO_CLEANUP(name, type, ...) \
typedef __typeof__(type) _spa_auto_cleanup_type_ ## name; \
static inline void _spa_auto_cleanup_func_ ## name (__typeof__(type) *thing) \
{ \
__VA_ARGS__ \
}
#define spa_auto(name) \
spa_cleanup(_spa_auto_cleanup_func_ ## name) \
_spa_auto_cleanup_type_ ## name
#define SPA_DEFINE_AUTOPTR_CLEANUP(name, type, ...) \
typedef __typeof__(type) * _spa_autoptr_cleanup_type_ ## name; \
static inline void _spa_autoptr_cleanup_func_ ## name (__typeof__(type) **thing) \
{ \
__VA_ARGS__ \
}
#define spa_autoptr(name) \
spa_cleanup(_spa_autoptr_cleanup_func_ ## name) \
_spa_autoptr_cleanup_type_ ## name
#define spa_exchange(var, new_value) \
__extension__ ({ \
__typeof__(var) _old_value = (var); \
(var) = (new_value); \
_old_value; \
})
#define spa_steal_ptr(ptr) ((__typeof__(*(ptr)) *) spa_exchange((ptr), NULL))
#define spa_steal_fd(fd) spa_exchange((fd), -1)
/* ========================================================================== */
#include <stdlib.h>
#define spa_clear_ptr(ptr, destructor) \
__extension__ ({ \
__typeof__(*(ptr)) *_old_value = spa_steal_ptr(ptr); \
if (_old_value) \
destructor(_old_value); \
(void) 0; \
})
static inline void _spa_autofree_cleanup_func(void *p)
{
free(*(void **) p);
}
#define spa_autofree spa_cleanup(_spa_autofree_cleanup_func)
/* ========================================================================== */
#include <unistd.h>
#define spa_clear_fd(fd) \
__extension__ ({ \
int _old_value = spa_steal_fd(fd), _res = 0; \
if (_old_value >= 0) \
_res = close(_old_value); \
_res; \
})
static inline void _spa_autoclose_cleanup_func(int *fd)
{
spa_clear_fd(*fd);
}
#define spa_autoclose spa_cleanup(_spa_autoclose_cleanup_func)
/* ========================================================================== */
#include <stdio.h>
SPA_DEFINE_AUTOPTR_CLEANUP(FILE, FILE, {
spa_clear_ptr(*thing, fclose);
})
/* ========================================================================== */
#include <dirent.h>
SPA_DEFINE_AUTOPTR_CLEANUP(DIR, DIR, {
spa_clear_ptr(*thing, closedir);
})
#endif /* SPA_UTILS_CLEANUP_H */

21
src/pipewire/cleanup.h Normal file
View file

@ -0,0 +1,21 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2023 PipeWire authors */
/* SPDX-License-Identifier: MIT */
#ifndef PIPEWIRE_CLEANUP_H
#define PIPEWIRE_CLEANUP_H
#include <spa/utils/cleanup.h>
#include <pipewire/properties.h>
#include <pipewire/utils.h>
SPA_DEFINE_AUTOPTR_CLEANUP(pw_properties, struct pw_properties, {
spa_clear_ptr(*thing, pw_properties_free);
})
SPA_DEFINE_AUTO_CLEANUP(pw_strv, char **, {
spa_clear_ptr(*thing, pw_free_strv);
})
#endif /* PIPEWIRE_CLEANUP_H */