|
@ -0,0 +1,245 @@ |
|
|
|
|
|
#pragma once |
|
|
|
|
|
|
|
|
|
|
|
#pragma once |
|
|
|
|
|
|
|
|
|
|
|
#ifdef __cplusplus |
|
|
|
|
|
#include <cstdint> |
|
|
|
|
|
#include <cstddef> |
|
|
|
|
|
#include <iterator> |
|
|
|
|
|
|
|
|
|
|
|
#else |
|
|
|
|
|
#include <stdint.h> |
|
|
|
|
|
#include <stddef.h> |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __cplusplus |
|
|
|
|
|
struct sl_log_transaction; |
|
|
|
|
|
struct sl_log_transaction_internals; |
|
|
|
|
|
|
|
|
|
|
|
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 <tt>std::hardware_destructive_interference</tt> 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 { |
|
|
|
|
|
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 { |
|
|
|
|
|
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<difference_type>(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 <tt>std::hardware_destructive_interference</tt> bytes |
|
|
|
|
|
* - This constructor is both thread-safe and multiprocessing-safe |
|
|
|
|
|
* |
|
|
|
|
|
* @pre |
|
|
|
|
|
* We expect the provided <tt>internal_id</tt> 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 <tt>char</tt>, 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 <tt>NDEBUG</tt> 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 */ |
|
|
|
|
|
sl_log_transaction_internals* internals; /** Internal, do not touch */ |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
#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 |