diff --git a/util/include/rusefi/resource_protector.h b/util/include/rusefi/resource_protector.h new file mode 100644 index 0000000..732c89d --- /dev/null +++ b/util/include/rusefi/resource_protector.h @@ -0,0 +1,168 @@ +/** + * @file resource_protector.h + * + * @brief Reader-style access guard for a shared resource whose lifetime is managed + * by a single coordinator thread. + * + * ProtectedResource holds a pointer to an object of type T together with a mutex, + * a condition variable and a user counter. Any number of "user" threads may access + * the resource concurrently via enter()/leave() while it is open. A single + * "coordinator" thread can close the resource with free(): new users are rejected + * immediately and the call blocks until every current user has left (or a timeout + * expires - in which case the resource is force-closed), after which the coordinator + * receives the detached pointer, may safely tear down / replace the underlying object + * and later supply a (new) instance with open(). + * + * Typical use case: a mounted filesystem (e.g. SD card FATFS) that worker threads + * write to, while the SD state machine needs to unmount it safely. + * + * Notes: + * - The resource starts in the CLOSED state with no object attached; call open() + * with a valid pointer once it is ready. + * - enter()/leave() calls must be strictly balanced by each user thread. + * - free() always returns the previously attached pointer and clears the internal + * reference, even on timeout (forced close) - the coordinator decides whether + * it is safe to actually destroy the object. + * - This is not a recursive lock and provides no protection between users: + * T itself must be safe for concurrent use by multiple holders, or callers + * must serialize access by other means. + */ + +#pragma once + +#include "ch.hpp" + +template +class ProtectedResource { +private: + chibios_rt::Mutex mutex; + // Use the core C structure instead of the protected chibios_rt::Monitor wrapper + condition_variable_t cond_var; + int user_count; + bool is_closing; + bool is_forced_closed; + T* resource; + +public: + /** + * @brief Constructs the wrapper in the CLOSED state with no object attached. + * + * The coordinator must call open() with a valid pointer before users can + * successfully enter(). + */ + ProtectedResource() + : user_count(0), is_closing(true), is_forced_closed(false), resource(nullptr) { + chCondObjectInit(&cond_var); + } + + /** + * @brief Constructs the wrapper in the OPEN state with @p initial_resource attached. + * + * Users may enter() right away without a prior open() call. Passing nullptr + * still opens the resource, but enter() will refuse access until a valid + * pointer is attached with open(). + * + * @param initial_resource pointer to the object to protect from the start + */ + explicit ProtectedResource(T* initial_resource) + : user_count(0), is_closing(false), is_forced_closed(false), resource(initial_resource) { + chCondObjectInit(&cond_var); + } + + /** + * @brief Acquire shared access to the resource (user threads). + * + * Never blocks: access is refused if the resource is closing, force-closed + * or no object is attached. On success the caller is counted as an active + * user and MUST call leave() when done. + * + * @return pointer to the protected object on success, nullptr otherwise + */ + T* enter() { + chibios_rt::MutexLocker lock(mutex); + if (is_closing || is_forced_closed || resource == nullptr) { + return nullptr; + } + user_count++; + return resource; + } + + /** + * @brief Release shared access previously obtained with enter() (user threads). + * + * When the last user leaves, a coordinator blocked in free() is woken up + * (unless the resource was already force-closed by a timeout). + * Must be called exactly once per successful enter(). + */ + void leave() { + chibios_rt::MutexLocker lock(mutex); + + if (user_count > 0) { + user_count--; + } + + // Standard exit path: alert coordinator if it is waiting + if (user_count == 0 && !is_forced_closed) { + chCondSignal(&cond_var); + } + } + + /** + * @brief Close the resource, wait for users to leave and detach the object (coordinator). + * + * Immediately marks the resource as closing so any further enter() calls fail, + * then blocks until the user count drops to zero or the timeout expires. On + * timeout the resource is marked force-closed: remaining users may still hold + * stale pointers, but no new users are admitted. + * + * In all cases the internal reference is cleared and the previously attached + * pointer is returned, allowing the coordinator to perform (possibly forced) + * cleanup. Call open() afterwards to attach an object and admit users again. + * + * @param timeout maximum time to wait for users to leave + * @return the detached object pointer (nullptr if none was attached) + */ + T* free(sysinterval_t timeout) { + chibios_rt::MutexLocker lock(mutex); + + is_closing = true; + + sysinterval_t deadline = chVTGetSystemTimeX() + timeout; + + while (user_count > 0) { + sysinterval_t remaining = chTimeDiffX(chVTGetSystemTimeX(), deadline); + + // TIMEOUT ENCOUNTERED HERE + if (remaining <= 0) { + is_forced_closed = true; + break; + } + + if (chCondWaitTimeoutS(&cond_var, remaining) != MSG_OK) { + is_forced_closed = true; + break; + } + } + + // --- SAFE ZONE / FORCE ZONE --- + T* temp = resource; + resource = nullptr; // Immediately clear reference so incoming users get nullptr + return temp; // Return the pointer for deletion/deinit + } + + /** + * @brief Attach a (new) object and make the resource available to users (coordinator). + * + * Resets the user counter and the closing/force-closed flags, so subsequent + * enter() calls succeed and return @p new_resource. + * + * @param new_resource pointer to the object to protect from now on + */ + void open(T* new_resource) { + chibios_rt::MutexLocker lock(mutex); + user_count = 0; + is_forced_closed = false; + is_closing = false; + resource = new_resource; // Assign the new object pointer + } +};