This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/celix.git
The following commit(s) were added to refs/heads/master by this push: new 712c1040b Add SBRM documentation 712c1040b is described below commit 712c1040bbf8671713837890579adde9505bbb49 Author: Pepijn Noltes <pnol...@apache.org> AuthorDate: Mon Aug 18 20:41:59 2025 +0200 Add SBRM documentation --------- Co-authored-by: PengZheng <howto...@gmail.com> --- documents/README.md | 1 + documents/SBRM.md | 84 +++++++++++++++++++++++++++++++++++++++++ documents/development/README.md | 65 +++++++++++++++++-------------- 3 files changed, 122 insertions(+), 28 deletions(-) diff --git a/documents/README.md b/documents/README.md index c174acbe7..fefec7971 100644 --- a/documents/README.md +++ b/documents/README.md @@ -98,3 +98,4 @@ bundles contains binaries depending on the stdlibc++ library. * [Apache Celix CMake Commands](cmake_commands) * [Apache Celix Sub Projects](subprojects.md) * [Apache Celix Coding Conventions Guide](development/README.md) +* [Apache Celix Scope Bound Resource Management](SBRM.md) diff --git a/documents/SBRM.md b/documents/SBRM.md new file mode 100644 index 000000000..c4da9aa2f --- /dev/null +++ b/documents/SBRM.md @@ -0,0 +1,84 @@ +--- +title: Scope-Based Resource Management +--- + +<!-- +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +# Scope-Based Resource Management (SBRM) + +Apache Celix provides *auto pointers* and *auto values* as a lightweight form of scope-based resource management in C. +These features are inspired by the RAII (Resource Acquisition Is Initialization) paradigm from C++, +as well as ideas from [Scope-based resource management for the kernel](https://lwn.net/Articles/934679/). + +Using the macros defined in `celix_cleanup.h` and related headers (`celix_stdio_cleanup.h`, `celix_stdlib_cleanup.h`), +resources are automatically released when their associated variables go out of scope. This enables safer and more +concise C code, especially when handling early returns or error paths. + +## Defining Cleanup Functions + +Before using auto cleanup, types must opt-in by defining a cleanup function. + +- For pointer types: + +```C + CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_filter_t, celix_filter_destroy) +``` + +- For value types: + +```C +CELIX_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(celix_mutex_lock_guard_t, celixMutexLockGuard_deinit) +``` + +## Using Auto Pointers and Auto Values + +### Auto Pointers + +Use `celix_autoptr(type)` to declare a pointer that will be cleaned up automatically: + +```C +void my_function(void) { + celix_autoptr(celix_filter_t) filter = celix_filter_create("(foo=bar)"); + // use filter +} // filter is destroyed automatically +``` + +To transfer ownership and disable automatic cleanup, use: + +```C +celix_filter_t* raw = celix_steal_ptr(filter); +``` + +### Auto Values + +Use `celix_auto(type)` to declare a value-based resource with automatic cleanup: + +```C +void my_function(void) { + celix_auto(celix_mutex_lock_guard_t) lock = celixMutexLockGuard_init(&myMutex); + // use guarded memory +} // lock guard is cleaned up automatically (myMutex is unlocked) +``` + +## Benefits + +Celix's SBRM features allow for: + +- Simplified cleanup logic +- RAII-style early returns +- Cleaner, more maintainable C code diff --git a/documents/development/README.md b/documents/development/README.md index e1f4a99a2..d521acb34 100644 --- a/documents/development/README.md +++ b/documents/development/README.md @@ -333,10 +333,8 @@ add_library(celix::MyBundle ALIAS MyBundle) - Use `while` statements for loops that may not execute. - Use `do`/`while` statements for loops that must execute at least once. - Use `for` statements for loops with a known number of iterations. -- The use of `goto` is not allowed, except for error handling in C (for C++ use RAII). -- For C, try to prevent deeply nested control structures and prefer early returns or error handling `goto` statements. - - To prevent deeply nested control structures, the `CELIX_DO_IF`, `CELIX_GOTO_IF_NULL` and `CELIX_GOTO_IF_ERR` - macros can also be used. +- Avoid using `goto` for error handling. Prefer early returns and automatic cleanup using celix auto pointers. +- To prevent deeply nested control structures, the `CELIX_DO_IF` macro can also be used. ## Functions and Methods @@ -350,7 +348,7 @@ add_library(celix::MyBundle ALIAS MyBundle) - For C++ functions with a lot of different parameters, consider using a builder pattern. - A builder pattern can be updated backwards compatible. - A builder pattern ensure that a lot of parameters can be configured, but also direct set on construction. - + ## Error Handling and Logging - For C++, throw an exception when an error occurs and use RAII to ensure that resources are freed. @@ -362,9 +360,10 @@ add_library(celix::MyBundle ALIAS MyBundle) - Use consistent error handling techniques, such as returning error codes or using designated error handling functions. - Log errors, warnings, and other important events using the Apache Celix log helper functions or - for libraries - the `celix_err` functionality. -- Always check for errors and log them. +- Always check for errors and log them. - Error handling should free resources in the reverse order of their allocation/creation. - Ensure error handling is correct, using test suite with error injection. + - Prefer early returns together with celix auto pointers to ensure cleanup without using `goto`. For log levels use the following guidelines: - trace: Use this level for very detailed that you would only want to have while diagnosing problems. @@ -381,37 +380,47 @@ For log levels use the following guidelines: - fatal: Use this level to report severe errors that prevent the program from continuing to run. After logging a fatal error, the program will typically terminate. -Example of error handling and logging: +Example of error handling and logging using auto pointers: ```c +typedef struct celix_foo { + celix_thread_mutex_t mutex; + celix_array_list_t* list; + celix_long_hash_map_t* map; +} celix_foo_t; + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_foo_t, celix_foo_destroy) + celix_foo_t* celix_foo_create(celix_log_helper_t* logHelper) { - celix_foo_t* foo = calloc(1, sizeof(*foo)); + celix_autofree celix_foo_t* foo = calloc(1, sizeof(*foo)); if (!foo) { - goto create_enomem_err; + celix_logHelper_log(logHelper, CELIX_LOG_LEVEL_ERROR, + "Error creating foo, out of memory"); + return NULL; } - - CELIX_GOTO_IF_ERR(create_mutex_err, celixThreadMutex_create(&foo->mutex, NULL)); - - foo->list = celix_arrayList_create(); - foo->map = celix_longHashMap_create(); - if (!foo->list || !foo->map) { - goto create_enomem_err; + + if (celixThreadMutex_create(&foo->mutex, NULL) != CELIX_SUCCESS) { + celix_logHelper_log(logHelper, CELIX_LOG_LEVEL_ERROR, + "Error creating mutex"); + return NULL; //foo cleaned up automatically (celix_autofree will call free) } - return foo; -create_mutex_err: - celix_logHelper_log(logHelper, CELIX_LOG_LEVEL_ERROR, "Error creating mutex"); - free(foo); //mutex not created, do not use celix_foo_destroy to prevent mutex destroy - return NULL; -create_enomem_err: - celix_logHelper_log(logHelper, CELIX_LOG_LEVEL_ERROR, "Error creating foo, out of memory"); - celix_foo_destroy(foo); //note celix_foo_destroy can handle NULL - return NULL; + celix_autoptr(celix_thread_mutex_t) mutex = &foo->mutex; + celix_autoptr(celix_array_list_t) list = celix_arrayList_create(); + celix_autoptr(celix_long_hash_map_t) map = celix_longHashMap_create(); + if (!list || !map) { + celix_logHelper_log(logHelper, CELIX_LOG_LEVEL_ERROR, + "Error creating foo, out of memory"); + return NULL; //foo, list and/or map are cleaned up automatically + } + + celix_steal_ptr(mutex); + foo->list = celix_steal_ptr(list); + foo->map = celix_steal_ptr(map); + return celix_steal_ptr(foo); } void celix_foo_destroy(celix_foo_t* foo) { - if (foo != NULL) { - //note reverse order of creation - celixThreadMutex_destroy(&foo->mutex); + if (foo) { celix_arrayList_destroy(foo->list); celix_longHashMap_destroy(foo->map); free(foo);