Skip to content

Commit a217db7

Browse files
committed
Fix a crash when beginning an async write triggers notifications which close the Realm
Beginning a write transaction can result in the Realm being closed as it can trigger notifications, and the async write loop was missing a check for this.
1 parent 1edcd92 commit a217db7

File tree

3 files changed

+51
-13
lines changed

3 files changed

+51
-13
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
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
99
* Performing a query like "{1, 2, 3, ...} IN list" where the array is longer than 8 and all elements are smaller than some values in list, the program would crash ([#1183](https://github.com/realm/realm-kotlin/issues/1183), v12.5.0)
10+
* Fix a null pointer dereference if beginning an async write transaction refreshed the Realm and one of the notification handlers closed the Realm ([PR #6548](https://github.com/realm/realm-core/pull/6548), since v11.8.0).
1011

1112
### Breaking changes
1213
* None.

src/realm/object-store/shared_realm.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,12 @@ void Realm::run_writes()
796796

797797
do_begin_transaction();
798798

799+
// Beginning the transaction may have delivered notifications, which
800+
// then may have closed the Realm.
801+
if (!m_transaction) {
802+
return;
803+
}
804+
799805
auto write_desc = std::move(m_async_write_q.front());
800806
m_async_write_q.pop_front();
801807

test/object-store/realm.cpp

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,31 +1759,62 @@ TEST_CASE("SharedRealm: async writes") {
17591759
REQUIRE(complete_count == 1);
17601760
verify_persisted_count(1);
17611761
}
1762-
SECTION("within did_change()") {
1763-
struct Context : public BindingContext {
1764-
int i;
1765-
Context(int i)
1766-
: i(i)
1767-
{
1768-
}
1769-
void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
1770-
{
1771-
std::invoke(close_functions[i], *realm.lock());
1772-
}
1773-
};
1774-
realm->m_binding_context.reset(new Context(i));
1762+
1763+
struct Context : public BindingContext {
1764+
int i;
1765+
bool& called;
1766+
Context(int i, bool& called)
1767+
: i(i)
1768+
, called(called)
1769+
{
1770+
}
1771+
void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
1772+
{
1773+
called = true;
1774+
std::invoke(close_functions[i], *realm.lock());
1775+
}
1776+
};
1777+
SECTION("within did_change() after committing") {
1778+
bool called = false;
1779+
realm->m_binding_context.reset(new Context(i, called));
17751780
realm->m_binding_context->realm = realm;
17761781

17771782
realm->async_begin_transaction([&] {
17781783
table->create_object().set(col, 45);
1784+
CHECK_FALSE(called);
17791785
realm->async_commit_transaction([&](std::exception_ptr) {
1786+
CHECK(called);
17801787
done = true;
17811788
});
17821789
});
17831790

17841791
wait_for_done();
17851792
verify_persisted_count(1);
17861793
}
1794+
1795+
SECTION("within did_change() when beginning") {
1796+
realm->m_binding_context.reset(new Context(i, done));
1797+
realm->m_binding_context->realm = realm;
1798+
1799+
// Make a write on a different instance while autorefresh is
1800+
// off to ensure that beginning the transaction advances the
1801+
// read version and thus sends notifications
1802+
realm->set_auto_refresh(false);
1803+
auto realm2 = Realm::get_shared_realm(config);
1804+
realm2->begin_transaction();
1805+
realm2->commit_transaction();
1806+
1807+
bool called = false;
1808+
realm->async_begin_transaction([&] {
1809+
called = true;
1810+
});
1811+
wait_for_done();
1812+
1813+
// close() inside a notification closes the Realm, but invalidate()
1814+
// is a no-op. This means the write callback should be invoked
1815+
// if we're testing invalidate() but not if we're testing close().
1816+
REQUIRE(called == i);
1817+
}
17871818
}
17881819
}
17891820

0 commit comments

Comments
 (0)