#pragma once #pragma once #ifdef __cplusplus #include #include #include #else #include #include #endif struct sl_log_transaction; struct sl_log_transaction_internals; #ifdef __cplusplus namespace sl { using cstyle_log_transaction = sl_log_transaction; using cstyle_log_transaction_internals = sl_log_transaction_internals; /** * @brief This class represents a logging transaction * @details * This class has the following guarantees: * - Can be built on the stack safely * - This class never allocates using the system allocator * - This class does not perform any system call * - Finalization cannot happen twice from the public interface * - This class may pad the log by up to std::hardware_destructive_interference bytes * - This class is both thread-safe and multiprocessing-safe to finalize * * @details * This class expects the finalization to occur in the same thread that wrote any data to the log. * @pre * This class expects the logging buffer to be at least 2 binary orders of magnitude bigger than the largest log * message. It is recommended that it is at least 6 binary orders of magnitude bigger than the largest log. * @post * This class leaves the log in a valid state in accordance to the defined log strategies. */ class log_transaction final { char* local_start; size_t local_size; char* buffer_start; size_t buffer_size; void* buffer_initiator_page; bool is_finalized = false; void finalize_impl(); /** * @brief A forward iterator to the current log transaction. */ class iterator final { using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = char; using pointer = char*; using reference = char&; log_transaction& owner_ref; size_t index; public: explicit iterator(log_transaction& parent, size_t position = 0) : owner_ref(parent) , index(((parent.buffer_start - parent.local_start) + position)%parent.buffer_size) {} iterator& operator++() { index+=1; index%=owner_ref.buffer_size; return *this; } iterator operator++(int) { auto cpy = *this; index+=1; index%=owner_ref.buffer_size; return cpy; } difference_type operator-(iterator other) const { size_t forward, backward; forward = owner_ref.buffer_size - index; forward += other.index; backward = other.index - index; return static_cast(std::min(forward, backward)); } reference operator*() { return owner_ref.buffer_start[index]; } }; public: /** * @brief Starts a transaction. You have to then write your logs between the start and the end of the * transaction, then finalize it. * @details * Guarantees are as follow: * - This constructor never allocates using the system allocator * - This constructor does not perform any system call * - This constructor may pad the log by up to std::hardware_destructive_interference bytes * - This constructor is both thread-safe and multiprocessing-safe * * @pre * We expect the provided internal_id to be valid and registered, if it is not, the transaction may look for a * log named "UNHANDLED_ERRORS" and log there instead silently. * @post * A started transaction MUST be finalized. The log can, upon initiation of the transaction, no longer be * pushed to its sink past the current sequence point of the transaction. * * @post * This class will finalize an un-finalized transaction * @param internal_id the logger id where the transaction is to happen * @param log_size the amount of bytes to write in the transaction. */ explicit log_transaction(int internal_id, size_t log_size); log_transaction(const log_transaction&) = delete; log_transaction(log_transaction&&) = delete; void operator=(const log_transaction&) = delete; void operator=(log_transaction&&) = delete; /** * @brief Unsafely accesses the underlying buffer * @param index The index to the buffer to access * @return a reference to an underlying char, undefined behaviour is the index is invalid */ char& operator[](size_t index) { return buffer_start[((buffer_start - local_start) + index)%buffer_size]; } /** * @brief Obtains a forward iterator to the start of the writable range * @return a non-descript iterator to the beginning of the range */ iterator begin() { return iterator(*this, 0); } /** * @brief Obtains a forward iterator to the end of the writable range * @return a non-descript iterator to one-past-the-end of the range */ iterator end() { return iterator(*this, local_size); } /** * @brief Finalizes the transaction * @details * Finalizes a transaction, makes access to the finalized buffer undefined behaviour. If NDEBUG is * defined, accesses will lead to a near-nullptr dereference. * * @details * You are NOT protected against erroneous double finalization in debug mode. You are protected against double * finalization out of debug mode. This protection and these erroneous states do not interact with the * destructor, which will finalize transactions if not finalized yet. */ void finalize(); /** * @see{void finalize()} */ ~log_transaction(){ if(!is_finalized) finalize_impl(); } }; } #else typedef struct sl_log_transaction log_transaction; typedef struct sl_log_transaction_internals log_transaction_internals; #endif /** * @brief Represents a transaction in the log. Must be finalized for proper operation of the log. */ struct sl_log_transaction { char* begin; /**< The start of the given writable span */ char* end; /**< 1 element past the end of the writable span */ /** * @brief Internal, do not touch * @internal this is a pointer to a registry_slab, hence the need for reference stability/pointer stability */ sl_log_transaction_internals* internals; }; #ifdef __cplusplus extern "C" { #endif /** * @brief Starts a transaction. You have to then write your logs between the start and the end of the transaction, * then finalize it. * @details * Guarantees are as follow: * - This function never allocates using the system allocator * - This function does not perform any system call * - This function may need to pad the log to be operable by C easily * - This function is both thread-safe and multiprocessing-safe * * @pre * We expect the provided internal_id to be valid and registered, if it is not, the transaction may look for a log * named "UNHANDLED_ERRORS" and log there instead silently. * @post * A started transaction MUST be finalized. The log can, upon initiation of the transaction, no longer be pushed to * its sink past the current sequence point of the transaction. * @param internal_id the logger id where the transaction is to happen * @param log_size the amount of bytes to write in the transaction. * @return a transaction of at least log_size bytes */ sl_log_transaction sl_start_log_transaction(int internal_id, size_t log_size); /** * @brief Finalizes a transaction. * @details * Be cautious. Finalizing a finalized transaction should NEVER be done. It may wait forever, screw up your log, and * clog your log queue forever. * @pre * The transaction to finalize must never have been finalized before. If it has been finalized before, the * finalization may never terminate. * @post * All data written in the transaction is thenceforth scheduled to be written. The transaction state is now moot * and should not be accessed further, and should possibly be cleared. * @param target the transaction to finalize. */ void sl_finalize_log_transaction(sl_log_transaction target); /** * @brief Clears a transaction object. This is not required but may help prevent bugs. * @param to_clear */ inline void sl_clear_transaction(sl_log_transaction* to_clear) { #ifdef NDEBUG do {} while(false); #else #ifdef __cplusplus to_clear->begin = nullptr; to_clear->end = nullptr; to_clear->internals = nullptr; #else to_clear->begin = NULL; to_clear->end = NULL; to_clear->internals = NULL; #endif #endif } #ifdef __cplusplus } #endif