Skip to content

Commit 7ec957c

Browse files
Receiving write_not_allowed error crashes the app (#7002)
* Make compensating_write_server_version in ProtocolErrorInfo optional * Integration test for write_not_allowed(230) error * Update changelog * Add comment
1 parent a9c8f99 commit 7ec957c

File tree

5 files changed

+88
-9
lines changed

5 files changed

+88
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* None.
66

77
### Fixed
8-
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
98
* Fixed issue with double delete when using the CAPI for timers in platform networking ([#6993](https://github.com/realm/realm-core/issues/6993), since v13.3.0).
9+
* Receiving a write_not_allowed error from the server would have led to a crash. ([#6978](https://github.com/realm/realm-core/issues/6978), since v13.2.0)
1010

1111
### Breaking changes
1212
* Platform Networking CAPI has been updated to provide separate functions (instead of 1) for executing callback handlers depending on purpose ([PR #6994](https://github.com/realm/realm-core/pull/6994)).

src/realm/sync/noinst/client_impl_base.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,15 +1501,17 @@ void Session::gather_pending_compensating_writes(util::Span<Changeset> changeset
15011501
REALM_ASSERT_DEBUG(
15021502
std::is_sorted(m_pending_compensating_write_errors.begin(), m_pending_compensating_write_errors.end(),
15031503
[](const ProtocolErrorInfo& lhs, const ProtocolErrorInfo& rhs) {
1504-
return lhs.compensating_write_server_version < rhs.compensating_write_server_version;
1504+
REALM_ASSERT_DEBUG(lhs.compensating_write_server_version.has_value());
1505+
REALM_ASSERT_DEBUG(rhs.compensating_write_server_version.has_value());
1506+
return *lhs.compensating_write_server_version < *rhs.compensating_write_server_version;
15051507
}));
15061508
#endif
15071509

15081510
while (!m_pending_compensating_write_errors.empty() &&
1509-
m_pending_compensating_write_errors.front().compensating_write_server_version <=
1511+
*m_pending_compensating_write_errors.front().compensating_write_server_version <=
15101512
changesets.back().version) {
15111513
auto& cur_error = m_pending_compensating_write_errors.front();
1512-
REALM_ASSERT_3(cur_error.compensating_write_server_version, >=, changesets.front().version);
1514+
REALM_ASSERT_3(*cur_error.compensating_write_server_version, >=, changesets.front().version);
15131515
out->push_back(std::move(cur_error));
15141516
m_pending_compensating_write_errors.pop_front();
15151517
}
@@ -1552,7 +1554,7 @@ void Session::integrate_changesets(ClientReplication& repl, const SyncProgress&
15521554
for (const auto& pending_error : pending_compensating_write_errors) {
15531555
logger.info("Reporting compensating write for client version %1 in server version %2: %3",
15541556
pending_error.compensating_write_rejected_client_version,
1555-
pending_error.compensating_write_server_version, pending_error.message);
1557+
*pending_error.compensating_write_server_version, pending_error.message);
15561558
try {
15571559
on_connection_state_changed(
15581560
m_conn.get_state(),
@@ -2561,6 +2563,7 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info)
25612563
// If the client is not active, the compensating writes will not be processed now, but will be
25622564
// sent again the next time the client connects
25632565
if (m_state == Active) {
2566+
REALM_ASSERT(info.compensating_write_server_version.has_value());
25642567
m_pending_compensating_write_errors.push_back(info);
25652568
}
25662569
return Status::OK();

src/realm/sync/noinst/protocol_codec.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,12 @@ class ClientProtocol {
304304
info.compensating_writes.push_back(std::move(cwei));
305305
}
306306

307-
info.compensating_write_server_version =
308-
json.at("compensatingWriteServerVersion").get<int64_t>();
307+
// Not provided when 'write_not_allowed' (230) error is received from the server.
308+
if (auto server_version = json.find("compensatingWriteServerVersion");
309+
server_version != json.end()) {
310+
info.compensating_write_server_version =
311+
std::make_optional<version_type>(server_version->get<int64_t>());
312+
}
309313
info.compensating_write_rejected_client_version =
310314
json.at("rejectedClientVersion").get<int64_t>();
311315
}

src/realm/sync/protocol.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ struct ProtocolErrorInfo {
289289
bool client_reset_recovery_is_disabled = false;
290290
std::optional<bool> should_client_reset;
291291
std::optional<std::string> log_url;
292-
version_type compensating_write_server_version = 0;
292+
std::optional<version_type> compensating_write_server_version;
293293
version_type compensating_write_rejected_client_version = 0;
294294
std::vector<CompensatingWriteErrorInfo> compensating_writes;
295295
std::optional<ResumptionDelayInfo> resumption_delay_interval;

test/object-store/sync/flx_sync.cpp

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3078,6 +3078,78 @@ TEST_CASE("flx: data ingest - dev mode", "[sync][flx][data ingest][baas]") {
30783078
schema);
30793079
}
30803080

3081+
TEST_CASE("flx: data ingest - write not allowed", "[sync][flx][data ingest][baas]") {
3082+
AppCreateConfig::ServiceRole role;
3083+
role.name = "asymmetric_write_perms";
3084+
3085+
AppCreateConfig::ServiceRoleDocumentFilters doc_filters;
3086+
doc_filters.read = true;
3087+
doc_filters.write = false;
3088+
role.document_filters = doc_filters;
3089+
3090+
role.insert_filter = true;
3091+
role.delete_filter = true;
3092+
role.read = true;
3093+
role.write = true;
3094+
3095+
Schema schema({
3096+
{"Asymmetric",
3097+
ObjectSchema::ObjectType::TopLevelAsymmetric,
3098+
{
3099+
{"_id", PropertyType::ObjectId, Property::IsPrimary{true}},
3100+
{"location", PropertyType::String | PropertyType::Nullable},
3101+
{"embedded_obj", PropertyType::Object | PropertyType::Nullable, "Embedded"},
3102+
}},
3103+
{"Embedded",
3104+
ObjectSchema::ObjectType::Embedded,
3105+
{
3106+
{"value", PropertyType::String | PropertyType::Nullable},
3107+
}},
3108+
});
3109+
FLXSyncTestHarness::ServerSchema server_schema{schema, {}, {role}};
3110+
FLXSyncTestHarness harness("asymmetric_sync", server_schema);
3111+
3112+
auto error_received_pf = util::make_promise_future<void>();
3113+
SyncTestFile config(harness.app()->current_user(), harness.schema(), SyncConfig::FLXSyncEnabled{});
3114+
config.sync_config->on_sync_client_event_hook =
3115+
[promise = util::CopyablePromiseHolder(std::move(error_received_pf.promise))](
3116+
std::weak_ptr<SyncSession> weak_session, const SyncClientHookData& data) mutable {
3117+
if (data.event != SyncClientHookEvent::ErrorMessageReceived) {
3118+
return SyncClientHookAction::NoAction;
3119+
}
3120+
auto session = weak_session.lock();
3121+
REQUIRE(session);
3122+
3123+
auto error_code = sync::ProtocolError(data.error_info->raw_error_code);
3124+
3125+
if (error_code == sync::ProtocolError::initial_sync_not_completed) {
3126+
return SyncClientHookAction::NoAction;
3127+
}
3128+
3129+
REQUIRE(error_code == sync::ProtocolError::write_not_allowed);
3130+
REQUIRE_FALSE(data.error_info->compensating_write_server_version.has_value());
3131+
REQUIRE_FALSE(data.error_info->compensating_writes.empty());
3132+
promise.get_promise().emplace_value();
3133+
3134+
return SyncClientHookAction::EarlyReturn;
3135+
};
3136+
3137+
auto realm = Realm::get_shared_realm(config);
3138+
3139+
// Create an asymmetric object and upload it to the server.
3140+
{
3141+
realm->begin_transaction();
3142+
CppContext c(realm);
3143+
Object::create(c, realm, "Asymmetric",
3144+
std::any(AnyDict{{"_id", ObjectId::gen()}, {"embedded_obj", AnyDict{{"value", "foo"s}}}}));
3145+
realm->commit_transaction();
3146+
wait_for_upload(*realm);
3147+
}
3148+
3149+
error_received_pf.future.get();
3150+
realm->close();
3151+
}
3152+
30813153
TEST_CASE("flx: send client error", "[sync][flx][baas]") {
30823154
FLXSyncTestHarness harness("flx_client_error");
30833155

@@ -3607,7 +3679,7 @@ TEST_CASE("flx: compensating write errors get re-sent across sessions", "[sync][
36073679
std::lock_guard<std::mutex> lk(errors_mutex);
36083680
for (const auto& compensating_write : data.error_info->compensating_writes) {
36093681
error_to_download_version.emplace_back(compensating_write.primary_key.get_object_id(),
3610-
data.error_info->compensating_write_server_version);
3682+
*data.error_info->compensating_write_server_version);
36113683
}
36123684

36133685
return SyncClientHookAction::NoAction;

0 commit comments

Comments
 (0)