Skip to content

Commit 0e94a90

Browse files
committed
Fix a data race in collection notifier logging and extract some duplicated code
m_run_time_point was read on the target thread while being written on the worker thread without any locking involved.
1 parent 5edb85d commit 0e94a90

File tree

6 files changed

+97
-93
lines changed

6 files changed

+97
-93
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
### Fixed
88
* Fixed conflict resolution bug which may result in an crash when the AddInteger instruction on Mixed properties is merged against updates to a non-integer type ([PR #7353](https://github.com/realm/realm-core/pull/7353)).
99
* Fix a spurious crash related to opening a Realm on background thread while the process was in the middle of exiting ([#7420](https://github.com/realm/realm-core/issues/7420jj))
10+
* Fix a data race in change notification delivery when running at debug log level ([PR #7402](https://github.com/realm/realm-core/pull/7402), since v14.0.0).
1011

1112
### Breaking changes
1213
* Remove `realm_scheduler_set_default_factory()` and `realm_scheduler_has_default_factory()`, and change the `Scheduler` factory function to a bare function pointer rather than a `UniqueFunction` so that it does not have a non-trivial destructor.

src/realm/object-store/impl/collection_notifier.cpp

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,9 @@ void CollectionNotifier::prepare_handover()
307307
REALM_ASSERT(m_change.empty());
308308
m_has_run = true;
309309

310-
#ifdef REALM_DEBUG
311310
util::CheckedLockGuard lock(m_callback_mutex);
311+
m_run_time_point = std::chrono::steady_clock::now();
312+
#ifdef REALM_DEBUG
312313
for (auto& callback : m_callbacks)
313314
REALM_ASSERT(!callback.skip_next);
314315
#endif
@@ -330,10 +331,59 @@ void CollectionNotifier::before_advance()
330331
});
331332
}
332333

334+
static void log_changeset(util::Logger* logger, const CollectionChangeSet& changes, std::string_view description,
335+
std::chrono::microseconds elapsed)
336+
{
337+
if (!logger) {
338+
return;
339+
}
340+
341+
logger->log(util::LogCategory::notification, util::Logger::Level::debug,
342+
"Delivering notifications for %1 after %2 us", description, elapsed.count());
343+
if (!logger->would_log(util::Logger::Level::trace)) {
344+
return;
345+
}
346+
if (changes.empty()) {
347+
logger->log(util::LogCategory::notification, util::Logger::Level::trace, " No changes");
348+
}
349+
else {
350+
if (changes.collection_root_was_deleted) {
351+
logger->log(util::LogCategory::notification, util::Logger::Level::trace, " collection deleted");
352+
}
353+
else if (changes.collection_was_cleared) {
354+
logger->log(util::LogCategory::notification, util::Logger::Level::trace, " collection cleared");
355+
}
356+
else {
357+
auto log = [logger](const char* change, const IndexSet& index_set) {
358+
if (auto cnt = index_set.count()) {
359+
std::ostringstream ostr;
360+
bool first = true;
361+
for (auto [a, b] : index_set) {
362+
if (!first)
363+
ostr << ',';
364+
if (b > a + 1) {
365+
ostr << '[' << a << ',' << b - 1 << ']';
366+
}
367+
else {
368+
ostr << a;
369+
}
370+
first = false;
371+
}
372+
logger->log(util::LogCategory::notification, util::Logger::Level::trace, " %1 %2: %3", cnt,
373+
change, ostr.str().c_str());
374+
}
375+
};
376+
log("deletions", changes.deletions);
377+
log("insertions", changes.insertions);
378+
log("modifications", changes.modifications);
379+
}
380+
}
381+
}
382+
333383
void CollectionNotifier::after_advance()
334384
{
335385
using namespace std::chrono;
336-
auto t1 = steady_clock::now();
386+
auto now = steady_clock::now();
337387

338388
for_each_callback([&](auto& lock, auto& callback) {
339389
if (callback.initial_delivered && callback.changes_to_deliver.empty()) {
@@ -346,51 +396,9 @@ void CollectionNotifier::after_advance()
346396
// acquire a local reference to the callback so that removing the
347397
// callback from within it can't result in a dangling pointer
348398
auto cb = callback.fn;
399+
auto elapsed = duration_cast<microseconds>(now - m_run_time_point);
349400
lock.unlock_unchecked();
350-
if (m_logger) {
351-
m_logger->log(util::LogCategory::notification, util::Logger::Level::debug,
352-
"Delivering notifications for %1 after %2 us", m_description,
353-
duration_cast<microseconds>(t1 - m_run_time_point).count());
354-
if (m_logger->would_log(util::Logger::Level::trace)) {
355-
if (changes.empty()) {
356-
m_logger->log(util::LogCategory::notification, util::Logger::Level::trace, " No changes");
357-
}
358-
else {
359-
if (changes.collection_root_was_deleted) {
360-
m_logger->log(util::LogCategory::notification, util::Logger::Level::trace,
361-
" collection deleted");
362-
}
363-
else if (changes.collection_was_cleared) {
364-
m_logger->log(util::LogCategory::notification, util::Logger::Level::trace,
365-
" collection cleared");
366-
}
367-
else {
368-
auto log = [this](const char* change, const IndexSet& index_set) {
369-
if (auto cnt = index_set.count()) {
370-
std::ostringstream ostr;
371-
bool first = true;
372-
for (auto [a, b] : index_set) {
373-
if (!first)
374-
ostr << ',';
375-
if (b > a + 1) {
376-
ostr << '[' << a << ',' << b - 1 << ']';
377-
}
378-
else {
379-
ostr << a;
380-
}
381-
first = false;
382-
}
383-
m_logger->log(util::LogCategory::notification, util::Logger::Level::trace,
384-
" %1 %2: %3", cnt, change, ostr.str().c_str());
385-
}
386-
};
387-
log("deletions", changes.deletions);
388-
log("insertions", changes.insertions);
389-
log("modifications", changes.modifications);
390-
}
391-
}
392-
}
393-
}
401+
log_changeset(m_logger.get(), changes, m_description, elapsed);
394402
cb.after(changes);
395403
});
396404
}
@@ -529,3 +537,24 @@ void NotifierPackage::after_advance()
529537
for (auto& notifier : m_notifiers)
530538
notifier->after_advance();
531539
}
540+
541+
NotifierRunLogger::NotifierRunLogger(util::Logger* logger, std::string_view name, std::string_view description)
542+
: m_logger(logger)
543+
, m_name(name)
544+
, m_description(description)
545+
{
546+
if (logger && logger->would_log(util::Logger::Level::debug)) {
547+
m_logger = logger;
548+
m_start = std::chrono::steady_clock::now();
549+
}
550+
}
551+
552+
NotifierRunLogger::~NotifierRunLogger()
553+
{
554+
using namespace std::chrono;
555+
if (m_logger) {
556+
auto now = steady_clock::now();
557+
m_logger->log(util::LogCategory::notification, util::Logger::Level::debug, "%1 %2 ran in %3 us", m_name,
558+
m_description, duration_cast<microseconds>(now - m_start).count());
559+
}
560+
}

src/realm/object-store/impl/collection_notifier.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,18 @@ class NotifierPackage {
381381
RealmCoordinator* m_coordinator = nullptr;
382382
};
383383

384+
class NotifierRunLogger {
385+
public:
386+
NotifierRunLogger(util::Logger* logger, std::string_view name, std::string_view description);
387+
~NotifierRunLogger();
388+
389+
private:
390+
util::Logger* m_logger;
391+
std::string_view m_name;
392+
std::string_view m_description;
393+
std::chrono::steady_clock::time_point m_start;
394+
};
395+
384396
} // namespace realm::_impl
385397

386398
#endif /* REALM_BACKGROUND_COLLECTION_HPP */

src/realm/object-store/impl/list_notifier.cpp

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ ListNotifier::ListNotifier(std::shared_ptr<Realm> realm, CollectionBase const& l
3333
, m_prev_size(list.size())
3434
{
3535
attach(list);
36-
if (m_logger) {
36+
if (m_logger && m_logger->would_log(util::Logger::Level::debug)) {
3737
auto path = m_list->get_short_path();
3838
auto prop_name = m_list->get_table()->get_column_name(path[0].get_col_key());
3939
path[0] = PathElement(prop_name);
@@ -94,16 +94,7 @@ bool ListNotifier::do_add_required_change_info(TransactionChangeInfo& info)
9494

9595
void ListNotifier::run()
9696
{
97-
using namespace std::chrono;
98-
auto t1 = steady_clock::now();
99-
util::ScopeExit cleanup([&]() noexcept {
100-
m_run_time_point = steady_clock::now();
101-
if (m_logger) {
102-
m_logger->log(util::LogCategory::notification, util::Logger::Level::debug,
103-
"ListNotifier %1 did run in %2 us", m_description,
104-
duration_cast<microseconds>(m_run_time_point - t1).count());
105-
}
106-
});
97+
NotifierRunLogger log(m_logger.get(), "ListNotifier", m_description);
10798

10899
if (!m_list || !m_list->is_attached()) {
109100
// List was deleted, so report all of the rows being removed if this is

src/realm/object-store/impl/object_notifier.cpp

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,7 @@ void ObjectNotifier::run()
6464
{
6565
if (!m_table || !m_info)
6666
return;
67-
using namespace std::chrono;
68-
auto t1 = steady_clock::now();
69-
util::ScopeExit cleanup([&]() noexcept {
70-
m_run_time_point = steady_clock::now();
71-
if (m_logger) {
72-
m_logger->log(util::LogCategory::notification, util::Logger::Level::debug,
73-
"ObjectNotifier %1 did run in %2 us", m_description,
74-
duration_cast<microseconds>(m_run_time_point - t1).count());
75-
}
76-
});
67+
NotifierRunLogger log(m_logger.get(), "ObjectNotifier", m_description);
7768

7869
auto it = m_info->tables.find(m_table->get_key());
7970
if (it != m_info->tables.end() && it->second.deletions_contains(m_obj_key)) {

src/realm/object-store/impl/results_notifier.cpp

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ ResultsNotifier::ResultsNotifier(Results& target)
6565
, m_target_is_in_table_order(target.is_in_table_order())
6666
{
6767
if (m_logger) {
68-
m_description = std::string("'") + std::string(m_query->get_table()->get_class_name()) + std::string("'");
68+
m_description = "'" + std::string(m_query->get_table()->get_class_name()) + "'";
6969
if (m_query->has_conditions()) {
7070
m_description += " where \"";
7171
m_description += m_query->get_description_safe() + "\"";
7272
}
73-
m_logger->log(util::LogCategory::notification, util::Logger::Level::debug, "Creating ResultsNotifier for ",
73+
m_logger->log(util::LogCategory::notification, util::Logger::Level::debug, "Creating ResultsNotifier for %1",
7474
m_description);
7575
}
7676
reattach();
@@ -151,20 +151,10 @@ void ResultsNotifier::calculate_changes()
151151

152152
void ResultsNotifier::run()
153153
{
154-
using namespace std::chrono;
154+
NotifierRunLogger log(m_logger.get(), "ResultsNotifier", m_description);
155155

156156
REALM_ASSERT(m_info || !has_run());
157157

158-
auto t1 = steady_clock::now();
159-
util::ScopeExit cleanup([&]() noexcept {
160-
m_run_time_point = steady_clock::now();
161-
if (m_logger) {
162-
m_logger->log(util::LogCategory::notification, util::Logger::Level::debug,
163-
"ResultsNotifier %1 did run in %2 us", m_description,
164-
duration_cast<microseconds>(m_run_time_point - t1).count());
165-
}
166-
});
167-
168158
// Table's been deleted, so report all objects as deleted
169159
if (!m_query->get_table()) {
170160
m_change = {};
@@ -278,7 +268,7 @@ ListResultsNotifier::ListResultsNotifier(Results& target)
278268
auto path = m_list->get_short_path();
279269
auto prop_name = m_list->get_table()->get_column_name(path[0].get_col_key());
280270
path[0] = PathElement(prop_name);
281-
std::string sort_order;
271+
std::string_view sort_order = "";
282272
if (m_sort_order) {
283273
sort_order = *m_sort_order ? " sorted ascending" : " sorted descending";
284274
}
@@ -358,17 +348,6 @@ void ListResultsNotifier::calculate_changes()
358348

359349
void ListResultsNotifier::run()
360350
{
361-
using namespace std::chrono;
362-
auto t1 = steady_clock::now();
363-
util::ScopeExit cleanup([&]() noexcept {
364-
m_run_time_point = steady_clock::now();
365-
if (m_logger) {
366-
m_logger->log(util::LogCategory::notification, util::Logger::Level::debug,
367-
"ListResultsNotifier %1 did run in %2 us", m_description,
368-
duration_cast<microseconds>(m_run_time_point - t1).count());
369-
}
370-
});
371-
372351
if (!m_list || !m_list->is_attached()) {
373352
// List was deleted, so report all of the rows being removed
374353
m_change = {};
@@ -379,10 +358,11 @@ void ListResultsNotifier::run()
379358
}
380359

381360
if (!need_to_run()) {
382-
cleanup.cancel();
383361
return;
384362
}
385363

364+
NotifierRunLogger log(m_logger.get(), "ListResultsNotifier", m_description);
365+
386366
m_run_indices = std::vector<size_t>();
387367
if (m_distinct)
388368
m_list->distinct(*m_run_indices, m_sort_order);

0 commit comments

Comments
 (0)