async using lockfree queue and bug fixes regarding usage of cppformat

This commit is contained in:
gabi
2014-12-02 16:41:12 +02:00
parent 0e3120ba51
commit 243dc61e58
9 changed files with 171 additions and 100 deletions

View File

@@ -0,0 +1,252 @@
/*************************************************************************/
/* spdlog - an extremely fast and easy to use c++11 logging library. */
/* Copyright (c) 2014 Gabi Melman. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
// async log helper :
// Process logs asynchronously using a back thread.
//
// If the internal queue of log messages reaches its max size,
// then the client call will block until there is more room.
//
// If the back thread throws during logging, a spdlog::spdlog_ex exception
// will be thrown in client's thread when tries to log the next message
#pragma once
#include <thread>
#include <chrono>
#include <atomic>
#include "../sinks/sink.h"
#include "../logger.h"
#include "../details/mpcs_q.h"
#include "../details/log_msg.h"
#include "../details/format.h"
namespace spdlog
{
namespace details
{
class async_log_helper
{
struct async_msg
{
std::string logger_name;
level::level_enum level;
log_clock::time_point time;
std::tm tm_time;
std::string raw_msg_str;
async_msg() = default;
async_msg(const details::log_msg& m) :
logger_name(m.logger_name),
level(m.level),
time(m.time),
tm_time(m.tm_time),
raw_msg_str(m.raw.data(), m.raw.size())
{
}
log_msg to_log_msg()
{
log_msg msg;
msg.logger_name = logger_name;
msg.level = level;
msg.time = time;
msg.tm_time = tm_time;
msg.raw << raw_msg_str;
return msg;
}
};
public:
using q_type = details::mpsc_q < std::unique_ptr<async_msg> >;
explicit async_log_helper(size_t max_queue_size);
void log(const details::log_msg& msg);
//Stop logging and join the back thread
~async_log_helper();
void add_sink(sink_ptr sink);
void remove_sink(sink_ptr sink_ptr);
void set_formatter(formatter_ptr);
//Wait to remaining items (if any) in the queue to be written and shutdown
void shutdown(const log_clock::duration& timeout);
private:
std::vector<std::shared_ptr<sinks::sink>> _sinks;
std::atomic<bool> _active;
q_type _q;
std::thread _worker_thread;
std::mutex _mutex;
// last exception thrown from the worker thread
std::shared_ptr<spdlog_ex> _last_workerthread_ex;
// worker thread formatter
formatter_ptr _formatter;
// will throw last back thread exception or if worker hread no active
void _push_sentry();
// worker thread loop
void _thread_loop();
// clear all remaining messages(if any), stop the _worker_thread and join it
void _join();
};
}
}
///////////////////////////////////////////////////////////////////////////////
// async_sink class implementation
///////////////////////////////////////////////////////////////////////////////
inline spdlog::details::async_log_helper::async_log_helper(size_t max_queue_size)
:_sinks(),
_active(true),
_q(max_queue_size),
_worker_thread(&async_log_helper::_thread_loop, this)
{}
inline spdlog::details::async_log_helper::~async_log_helper()
{
_join();
}
inline void spdlog::details::async_log_helper::log(const details::log_msg& msg)
{
_push_sentry();
_q.push(std::unique_ptr<async_msg>(new async_msg(msg)));
}
inline void spdlog::details::async_log_helper::_thread_loop()
{
while (_active)
{
q_type::item_type async_msg;
if (_q.pop(async_msg))
{
try
{
details::log_msg log_msg = async_msg->to_log_msg();
_formatter->format(log_msg);
for (auto &s : _sinks)
s->log(log_msg);
}
catch (const std::exception& ex)
{
_last_workerthread_ex = std::make_shared<spdlog_ex>(ex.what());
}
catch (...)
{
_last_workerthread_ex = std::make_shared<spdlog_ex>("Unknown exception");
}
}
else //Sleep and retry if empty
{
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
}
inline void spdlog::details::async_log_helper::add_sink(spdlog::sink_ptr s)
{
std::lock_guard<std::mutex> guard(_mutex);
_sinks.push_back(s);
}
inline void spdlog::details::async_log_helper::remove_sink(spdlog::sink_ptr s)
{
std::lock_guard<std::mutex> guard(_mutex);
_sinks.erase(std::remove(_sinks.begin(), _sinks.end(), s), _sinks.end());
}
inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_formatter)
{
_formatter = msg_formatter;
}
inline void spdlog::details::async_log_helper::shutdown(const log_clock::duration& timeout)
{
if (timeout > std::chrono::milliseconds::zero())
{
auto until = log_clock::now() + timeout;
while (_q.approx_size() > 0 && log_clock::now() < until)
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
_join();
}
inline void spdlog::details::async_log_helper::_push_sentry()
{
if (_last_workerthread_ex)
{
auto ex = std::move(_last_workerthread_ex);
_last_workerthread_ex.reset();
throw *ex;
}
if (!_active)
throw(spdlog_ex("async_sink not active"));
}
inline void spdlog::details::async_log_helper::_join()
{
_active = false;
if (_worker_thread.joinable())
{
try
{
_worker_thread.join();
}
catch (const std::system_error&) //Dont crash if thread not joinable
{
}
}
}

View File

@@ -25,8 +25,7 @@
#pragma once
#include <memory>
#include "../sinks/async_sink.h"
#include "./async_log_helper.h"
//
// Async Logger implementation
@@ -38,11 +37,11 @@ template<class It>
inline spdlog::async_logger::async_logger(const std::string& logger_name, const It& begin, const It& end, size_t queue_size, const log_clock::duration& shutdown_duration) :
logger(logger_name, begin, end),
_shutdown_duration(shutdown_duration),
_as(std::unique_ptr<sinks::async_sink>(new sinks::async_sink(queue_size)))
_async_log_helper(new details::async_log_helper(queue_size))
{
_as->set_formatter(_formatter);
_async_log_helper->set_formatter(_formatter);
for (auto &s : _sinks)
_as->add_sink(s);
_async_log_helper->add_sink(s);
}
inline spdlog::async_logger::async_logger(const std::string& logger_name, sinks_init_list sinks, size_t queue_size, const log_clock::duration& shutdown_duration) :
@@ -52,21 +51,16 @@ inline spdlog::async_logger::async_logger(const std::string& logger_name, sink_p
async_logger(logger_name, { single_sink }, queue_size, shutdown_duration) {}
inline void spdlog::async_logger::_log_msg(details::log_msg& msg)
{
_as->log(msg);
}
inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter)
{
_formatter = msg_formatter;
_as->set_formatter(_formatter);
_async_log_helper->set_formatter(_formatter);
}
inline void spdlog::async_logger::_set_pattern(const std::string& pattern)
{
_formatter = std::make_shared<pattern_formatter>(pattern);
_as->set_formatter(_formatter);
_async_log_helper->set_formatter(_formatter);
}
@@ -74,5 +68,10 @@ inline void spdlog::async_logger::_set_pattern(const std::string& pattern)
inline void spdlog::async_logger::_stop()
{
set_level(level::OFF);
_as->shutdown(_shutdown_duration);
_async_log_helper->shutdown(_shutdown_duration);
}
inline void spdlog::async_logger::_log_msg(details::log_msg& msg)
{
_async_log_helper->log(msg);
}

View File

@@ -102,7 +102,7 @@ public:
size_t size = msg.formatted.size();
auto data = msg.formatted.data();
if(std::fwrite(data, sizeof(char), size, _fd) != size)
if(std::fwrite(data, 1, size, _fd) != size)
throw spdlog_ex("Failed writing to file " + _filename);
if(_auto_flush)

View File

@@ -31,7 +31,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#undef _SCL_SECURE_NO_WARNINGS
#define _SCL_SECURE_NO_WARNINGS
#include "format.h"
#include <string.h>

View File

@@ -1050,7 +1050,7 @@ public:
{
default:
assert(false);
// Fall through.
// Fall through.
case Arg::INT:
return FMT_DISPATCH(visit_int(arg.int_value));
case Arg::UINT:
@@ -2219,7 +2219,7 @@ void BasicWriter<Char>::write_double(
// MSVC's printf doesn't support 'F'.
type = 'f';
#endif
// Fall through.
// Fall through.
case 'E':
case 'G':
case 'A':

View File

@@ -48,9 +48,13 @@ struct log_msg
level(other.level),
time(other.time),
tm_time(other.tm_time)
{
raw.write(other.raw.data(), other.raw.size());
formatted.write(other.formatted.data(), other.formatted.size());
if (other.raw.size())
raw << fmt::BasicStringRef<char>(other.raw.data(), other.raw.size());
if (other.formatted.size())
formatted << fmt::BasicStringRef<char>(other.formatted.data(), other.formatted.size());
}
log_msg(log_msg&& other) :

View File

@@ -1,7 +1,5 @@
#pragma once
/*************************************************************************/
/*
Modified version of Intrusive MPSC node-based queue
A modified version of Intrusive MPSC node-based queue
Original code from
http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
@@ -32,9 +30,10 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the authors and
should not be interpreted as representing official policies, either expressed or implied, of Dmitry Vyukov.
*/
/*************************************************************************/
/* The code in its current form adds the license below: */
/********* The code in its current form adds the license below: **********/
/*************************************************************************/
/* spdlog - an extremely fast and easy to use c++11 logging library. */
/* Copyright (c) 2014 Gabi Melman. */
@@ -59,6 +58,7 @@ should not be interpreted as representing official policies, either expressed or
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#pragma once
#include <atomic>
namespace spdlog
@@ -70,35 +70,40 @@ class mpsc_q
{
public:
mpsc_q(size_t size) :_stub(T()), _head(&_stub), _tail(&_stub)
using item_type = T;
mpsc_q(size_t max_size) :
_max_size(max_size),
_size(0),
_stub(),
_head(&_stub),
_tail(&_stub)
{
_stub.next = nullptr;
}
~mpsc_q()
{
reset();
clear();
}
void reset()
template<typename TT>
bool push(TT&& value)
{
T dummy_val;
while (pop(dummy_val));
}
bool push(const T& value)
{
mpscq_node_t* new_node = new mpscq_node_t(value);
if (_size >= _max_size)
return false;
mpscq_node_t* new_node = new mpscq_node_t(std::forward<TT>(value));
push_node(new_node);
++_size;
return true;
}
// Try to pop or return false immediatly is queue is empty
bool pop(T& value)
{
mpscq_node_t* node = pop_node();
if (node != nullptr)
{
value = node->value;
--_size;
value = std::move(node->value);
delete(node);
return true;
}
@@ -108,23 +113,48 @@ public:
}
}
// Empty the queue by popping all its elements
void clear()
{
while (mpscq_node_t* node = pop_node())
{
--_size;
delete(node);
}
}
// Return approx size
size_t approx_size() const
{
return _size.load();
}
private:
struct mpscq_node_t
{
std::atomic<mpscq_node_t*> next;
T value;
explicit mpscq_node_t(const T& value) :next(nullptr), value(value)
{
}
mpscq_node_t() :next(nullptr) {}
explicit mpscq_node_t(const T& value):
next(nullptr),
value(value) {}
explicit mpscq_node_t(T&& value) :
next(nullptr),
value(std::move(value)) {}
};
size_t _max_size;
std::atomic<size_t> _size;
mpscq_node_t _stub;
std::atomic<mpscq_node_t*> _head;
mpscq_node_t* _tail;
// Lockfree push
void push_node(mpscq_node_t* n)
{
n->next = nullptr;
@@ -132,6 +162,8 @@ private:
prev->next = n;
}
// Clever lockfree pop algorithm by Dmitry Vyukov using single xchng instruction..
// Return pointer to the poppdc node or nullptr if no items left in the queue
mpscq_node_t* pop_node()
{
mpscq_node_t* tail = _tail;
@@ -151,7 +183,7 @@ private:
}
mpscq_node_t* head = _head;
if (tail != head)
return 0;
return nullptr;
push_node(&_stub);
next = tail->next;
@@ -163,8 +195,6 @@ private:
return nullptr;
}
};
}
}

View File

@@ -345,7 +345,7 @@ class v_formatter :public flag_formatter
{
void format(details::log_msg& msg) override
{
msg.formatted.write(msg.raw.data(), msg.raw.size());
msg.formatted << fmt::BasicStringRef<char>(msg.raw.data(), msg.raw.size());
}
};
@@ -413,7 +413,7 @@ class full_formatter :public flag_formatter
<< fmt::pad(static_cast<int>(millis), 3, '0') << "] ";
msg.formatted << '[' << msg.logger_name << "] [" << level::to_str(msg.level) << "] ";
msg.formatted.write(msg.raw.data(), msg.raw.size());
msg.formatted << fmt::BasicStringRef<char>(msg.raw.data(), msg.raw.size());
}
};
@@ -460,7 +460,7 @@ inline void spdlog::pattern_formatter::handle_flag(char flag)
{
switch (flag)
{
// logger name
// logger name
case 'n':
_formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::name_formatter()));
break;
@@ -581,7 +581,7 @@ inline void spdlog::pattern_formatter::format(details::log_msg& msg)
f->format(msg);
}
//write eol
msg.formatted.write(details::os::eol(), details::os::eol_size());
msg.formatted << details::os::eol();
}
catch(const fmt::FormatError& e)
{