-
Hello, I tried the suggessions in #658, and it seemed that last option (3) would be the best, i.e.
It looks simple, but cannot I get it working stably. I suspect some thread tries to log through a logger that is being removed from another thread. Is there a way to prevent this? Thanks a lot! |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 13 replies
-
Hey, after calling remove_logger the user isn’t expected to use it anymore from other threads. The logger is marked as invalid but still exists. Backend thread waits until every log from this logger is processed and then removes it. Then the logger* is invalid and if you try to access it you will likely crash. That is only checked by an assert in the library Line 96 in 99b7cb3 You could check for quill/include/quill/core/LoggerBase.h Line 111 in 99b7cb3 but it doesn’t solve the problem as the so you need to sync it on your side std::atomic<Logger*> logger;
Logger* l = logger.exchange(nullptr)
if (l) {
remove_logger(l);
}
// before logging
if (auto* l = logger.load(memory_order_acquire); l)
{
LOG_INFO(l,…);
} But instead of doing the above why don’t you just create a new logger with the redirection sink. When you want to redirect you just simply update the Logger* you pass to LOG_INFO with a new logger without additional checks. |
Beta Was this translation helpful? Give feedback.
-
on #include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/ConsoleSink.h"
#include <iostream>
#include <string>
#include <utility>
#include <chrono>
#define ACCESS_ONCE(x) (*(volatile typeof(x)*)&(x))
int main()
{
quill::BackendOptions backend_options;
quill::Backend::start(backend_options);
quill::Logger* logger_1 = quill::Frontend::create_or_get_logger(
"logger_1", quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"));
quill::Logger* logger_2 = quill::Frontend::create_or_get_logger(
"session_logger", quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"));
alignas(8) quill::Logger* logger = logger_1;
auto t1 = std::thread(
[&logger]()
{
for (int i = 0; i < 20; ++i)
{
LOG_INFO(logger, "{}", i);
// or strictly:
// LOG_INFO(ACCESS_ONCE(logger), "{}", i);
std::this_thread::sleep_for(std::chrono::milliseconds {300});
}
});
auto next_session = std::chrono::steady_clock::now() + std::chrono::seconds{1};
for (size_t session_transition = 0; session_transition < 4; ++session_transition)
{
auto now = std::chrono::steady_clock::now();
while (now < next_session)
{
// wait
std::this_thread::sleep_for(std::chrono::microseconds{100});
now = std::chrono::steady_clock::now();
}
// Session transition
next_session = now + std::chrono::seconds{1};
if (logger == logger_1)
{
logger = logger_2;
}
else
{
logger = logger_1;
}
}
t1.join();
} will also work with globals, e.g #include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/ConsoleSink.h"
#include <iostream>
#include <string>
#include <utility>
#include <chrono>
#define ACCESS_ONCE(x) (*(volatile typeof(x)*)&(x))
quill::Logger* logger_1 = quill::Frontend::create_or_get_logger(
"logger_1", quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"));
quill::Logger* logger_2 = quill::Frontend::create_or_get_logger(
"session_logger", quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"));
alignas(8) quill::Logger* logger = logger_1;
int main()
{
quill::BackendOptions backend_options;
quill::Backend::start(backend_options);
auto t1 = std::thread(
[]()
{
for (int i = 0; i < 20; ++i)
{
LOG_INFO(logger, "{}", i);
// or strictly:
// LOG_INFO(ACCESS_ONCE(logger), "{}", i);
std::this_thread::sleep_for(std::chrono::milliseconds {300});
}
});
auto next_session = std::chrono::steady_clock::now() + std::chrono::seconds{1};
for (size_t session_transition = 0; session_transition < 4; ++session_transition)
{
auto now = std::chrono::steady_clock::now();
while (now < next_session)
{
// wait
std::this_thread::sleep_for(std::chrono::microseconds{100});
now = std::chrono::steady_clock::now();
}
// Session transition
next_session = now + std::chrono::seconds{1};
if (logger == logger_1)
{
logger = logger_2;
}
else
{
logger = logger_1;
}
}
t1.join();
} |
Beta Was this translation helpful? Give feedback.
-
Hi, |
Beta Was this translation helpful? Give feedback.
-
another solution would be to create a logger with unique id per thread, that way you can call remove_logger without having to worry that another thread is using it while it is being removed for example #include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/FileSink.h"
#include <chrono>
#include <iostream>
#include <string>
#include <utility>
int main()
{
quill::BackendOptions backend_options;
quill::Backend::start(backend_options);
std::atomic<uint32_t> counter{0};
std::vector<std::thread> log_threads;
for (size_t t = 0; t < 2; ++t)
{
log_threads.push_back(std::thread(
[&counter, t]()
{
uint32_t logger_id = 0;
uint32_t counter_cached = counter.load();
std::string unique_logger_id = std::to_string(t) + "_" + std::to_string(logger_id);
quill::Logger* logger_for_thread = quill::Frontend::create_or_get_logger(
"logger_" + unique_logger_id,
quill::Frontend::create_or_get_sink<quill::FileSink>("log_file" + std::to_string(counter_cached)));
for (int i = 0; i < 30; ++i)
{
LOG_INFO(logger_for_thread, "{}", i);
uint32_t const count_val = counter.load();
if (counter_cached != count_val)
{
counter_cached = count_val;
// session switch
quill::Logger* old_logger = logger_for_thread;
// create new logger
logger_id += 1;
unique_logger_id = std::to_string(t) + "_" + std::to_string(logger_id);
logger_for_thread = quill::Frontend::create_or_get_logger(
"logger_" + unique_logger_id,
quill::Frontend::create_or_get_sink<quill::FileSink>("log_file" + std::to_string(counter_cached)));
// safe - because no other thread is using that logger (see unique_logger_id)
quill::Frontend::remove_logger(old_logger);
}
std::this_thread::sleep_for(std::chrono::milliseconds{100 + i * 10});
}
}));
}
auto next_session = std::chrono::steady_clock::now() + std::chrono::seconds{1};
for (size_t session_transition = 0; session_transition < 4; ++session_transition)
{
auto now = std::chrono::steady_clock::now();
while (now < next_session)
{
// wait
std::this_thread::sleep_for(std::chrono::microseconds{50});
now = std::chrono::steady_clock::now();
}
// Session transition
next_session = now + std::chrono::seconds{1};
counter.fetch_add(1);
}
for (auto& t : log_threads)
{
t.join();
}
} |
Beta Was this translation helpful? Give feedback.
-
hey, I have made the follow improvements to master :
|
Beta Was this translation helpful? Give feedback.
yeah it shouldn't happen, make sure you are defining
QUILL_DLL_IMPORT
correctly plus-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE
e.g.quill/examples/shared_library/CMakeLists.txt
Line 14 in 8d7d5ea
Might worth it to add the same protection there as in the backend