• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / 1833

14 Nov 2023 03:52AM UTC coverage: 91.644% (-0.05%) from 91.69%
1833

push

Evergreen

web-flow
Merge feature/network-faultiness branch into master (#7120)

* Add baas-network-tests nightly task for testing sync client operation with non-ideal network conditions (#6852)

* Added support for starting baas proxy
* Fixed some issues when running the scripts
* minor updates to install_baas.sh
* Updates to scripts to run on evergreen spawn host
* Added total time output to object store tests
* Increased initial ssh connect attempts; renamed proxy to 'baas_proxy'
* Minor updates to help output
* Added baas network test to run bass with the proxy
* Added support for separate baas admin api url value
* Minor port check adjustments
* Removed 'compile' task from network_tests
* Disable development mode by default
* Added baas remote host and network tests instructions doc
* Minor updates to the instructions
* Minor updates to documentation

* Add non-ideal transfer and network fault tests with non-ideal network conditions (#7063)

* Added non-ideal transfer and network fault tests
* Added baas-network-tests to be allowed for PR runs
* Updated changelog
* Updated curl command to be silent
* Updated changelog
* Reverted some changes and added descriptions to config.yml

* Fixed test extra delay

* Added some evergreen script updates from another branch

* Disabled baas network faults tests

* Update flx migration test to enable dev mode

* Fix baas branch for network tests

* add more time to 'too large sync message error handling' test

* Added network faults test and pulled nonideal out of nightly tests until fixes are added

92116 of 168852 branches covered (0.0%)

179 of 192 new or added lines in 14 files covered. (93.23%)

140 existing lines in 19 files now uncovered.

231053 of 252119 relevant lines covered (91.64%)

6637899.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.32
/test/object-store/util/test_file.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2016 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
#include "util/test_file.hpp"
20

21
#include "util/test_utils.hpp"
22
#include "util/sync/baas_admin_api.hpp"
23
#include "../util/crypt_key.hpp"
24
#include "../util/test_path.hpp"
25

26
#include <realm/db.hpp>
27
#include <realm/disable_sync_to_disk.hpp>
28
#include <realm/history.hpp>
29
#include <realm/string_data.hpp>
30
#include <realm/object-store/impl/realm_coordinator.hpp>
31
#include <realm/util/base64.hpp>
32
#include <realm/util/file.hpp>
33

34
#if REALM_ENABLE_SYNC
35
#include <realm/object-store/sync/mongo_client.hpp>
36
#include <realm/object-store/sync/mongo_database.hpp>
37
#include <realm/object-store/sync/mongo_collection.hpp>
38
#include <realm/object-store/sync/sync_manager.hpp>
39
#include <realm/object-store/sync/sync_session.hpp>
40
#include <realm/object-store/sync/sync_user.hpp>
41
#include <realm/object-store/schema.hpp>
42
#endif
43

44
#include <cstdlib>
45
#include <iostream>
46

47
#ifdef _WIN32
48
#include <io.h>
49
#include <fcntl.h>
50

51
inline static int mkstemp(char* _template)
52
{
53
    return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE);
54
}
55
#else
56
#include <unistd.h>
57
#endif
58

59
#if REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
60
#include <condition_variable>
61
#include <functional>
62
#include <thread>
63
#include <map>
64
#endif
65

66
using namespace realm;
67

68
TestFile::TestFile()
69
{
8,904✔
70
    disable_sync_to_disk();
8,904✔
71
    m_temp_dir = util::make_temp_dir();
8,904✔
72
    path = (fs::path(m_temp_dir) / "realm.XXXXXX").string();
8,904✔
73
    util::Logger::set_default_level_threshold(realm::util::Logger::Level::TEST_LOGGING_LEVEL);
8,904✔
74
    if (const char* crypt_key = test_util::crypt_key()) {
8,904✔
75
        encryption_key = std::vector<char>(crypt_key, crypt_key + 64);
×
76
    }
×
77
    int fd = mkstemp(path.data());
8,904✔
78
    if (fd < 0) {
8,904✔
79
        int err = errno;
×
80
        throw std::system_error(err, std::system_category());
×
81
    }
×
82
#ifdef _WIN32
83
    _close(fd);
84
    _unlink(path.c_str());
85
#else // POSIX
86
    close(fd);
8,904✔
87
    unlink(path.c_str());
8,904✔
88
#endif
8,904✔
89

4,413✔
90
    schema_version = 0;
8,904✔
91
}
8,904✔
92

93
TestFile::~TestFile()
94
{
8,904✔
95
    if (!m_persist) {
8,904✔
96
        try {
8,820✔
97
            util::Logger::get_default_logger()->detail("~TestFile() removing '%1' and '%2'", path, m_temp_dir);
8,820✔
98
            util::File::try_remove(path);
8,820✔
99
            util::try_remove_dir_recursive(m_temp_dir);
8,820✔
100
        }
8,820✔
101
        catch (const std::exception& e) {
4,373✔
102
            util::Logger::get_default_logger()->warn("~TestFile() cleanup failed for '%1': %2", path, e.what());
4✔
103
            // clean up is best effort, ignored.
2✔
104
        }
4✔
105
    }
8,820✔
106
}
8,904✔
107

108
DBOptions TestFile::options() const
109
{
26✔
110
    DBOptions options;
26✔
111
    options.durability = in_memory ? DBOptions::Durability::MemOnly : DBOptions::Durability::Full;
26✔
112
    return options;
26✔
113
}
26✔
114

115
InMemoryTestFile::InMemoryTestFile()
116
{
8,548✔
117
    in_memory = true;
8,548✔
118
    schema_version = 0;
8,548✔
119
    encryption_key = std::vector<char>();
8,548✔
120
}
8,548✔
121

122
DBOptions InMemoryTestFile::options() const
123
{
×
124
    DBOptions options;
×
125
    options.durability = DBOptions::Durability::MemOnly;
×
126
    return options;
×
127
}
×
128

129
#if REALM_ENABLE_SYNC
130

131
static const std::string fake_refresh_token = ENCODE_FAKE_JWT("not_a_real_token");
132
static const std::string fake_access_token = ENCODE_FAKE_JWT("also_not_real");
133
static const std::string fake_device_id = "123400000000000000000000";
134

135
static std::shared_ptr<SyncUser> get_fake_user(app::App& app, const std::string& user_name)
136
{
6,668✔
137
    return app.sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, fake_device_id);
6,668✔
138
}
6,668✔
139

140
SyncTestFile::SyncTestFile(std::shared_ptr<app::App> app, std::string name, std::string user_name)
141
    : SyncTestFile(get_fake_user(*app, user_name), bson::Bson(name))
142
{
6,662✔
143
}
6,662✔
144

145
SyncTestFile::SyncTestFile(std::shared_ptr<SyncUser> user, bson::Bson partition, util::Optional<Schema> schema)
146
{
7,112✔
147
    REALM_ASSERT(user);
7,112✔
148
    sync_config = std::make_shared<realm::SyncConfig>(user, partition);
7,112✔
149
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
7,112✔
150
    sync_config->error_handler = [](std::shared_ptr<SyncSession>, SyncError error) {
3,520✔
151
        util::format(std::cerr, "An unexpected sync error was caught by the default SyncTestFile handler: '%1'\n",
×
152
                     error.status);
×
153
        abort();
×
154
    };
×
155
    schema_version = 1;
7,112✔
156
    this->schema = std::move(schema);
7,112✔
157
    schema_mode = SchemaMode::AdditiveExplicit;
7,112✔
158
}
7,112✔
159

160
SyncTestFile::SyncTestFile(std::shared_ptr<SyncUser> user, bson::Bson partition,
161
                           realm::util::Optional<realm::Schema> schema,
162
                           std::function<SyncSessionErrorHandler>&& error_handler)
163
{
2✔
164
    REALM_ASSERT(user);
2✔
165
    sync_config = std::make_shared<realm::SyncConfig>(user, partition);
2✔
166
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
2✔
167
    sync_config->error_handler = std::move(error_handler);
2✔
168
    schema_version = 1;
2✔
169
    this->schema = std::move(schema);
2✔
170
    schema_mode = SchemaMode::AdditiveExplicit;
2✔
171
}
2✔
172

173
SyncTestFile::SyncTestFile(std::shared_ptr<realm::SyncUser> user, realm::Schema _schema, SyncConfig::FLXSyncEnabled)
174
{
317✔
175
    REALM_ASSERT(user);
317✔
176
    sync_config = std::make_shared<realm::SyncConfig>(user, SyncConfig::FLXSyncEnabled{});
317✔
177
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
317✔
178
    sync_config->error_handler = [](std::shared_ptr<SyncSession> session, SyncError error) {
158✔
179
        util::format(std::cerr,
×
180
                     "An unexpected sync error was caught by the default SyncTestFile handler: '%1' for '%2'",
×
181
                     error.status, session->path());
×
182
        abort();
×
183
    };
×
184
    schema_version = 1;
317✔
185
    schema = _schema;
317✔
186
    schema_mode = SchemaMode::AdditiveExplicit;
317✔
187
}
317✔
188

189
SyncTestFile::SyncTestFile(std::shared_ptr<app::App> app, bson::Bson partition, Schema schema)
190
    : SyncTestFile(app->current_user(), std::move(partition), std::move(schema))
191
{
126✔
192
}
126✔
193

194
// MARK: - SyncServer
195
SyncServer::SyncServer(const SyncServer::Config& config)
196
    : m_local_root_dir(config.local_dir.empty() ? util::make_temp_dir() : config.local_dir)
197
    , m_server(m_local_root_dir, util::none, ([&] {
3,638✔
198
                   using namespace std::literals::chrono_literals;
3,638✔
199

1,792✔
200
#if TEST_ENABLE_LOGGING
3,638✔
201
                   m_logger = util::Logger::get_default_logger();
3,638✔
202

1,792✔
203
#else
204
                   // Logging is disabled, use a NullLogger to prevent printing anything
205
                   m_logger.reset(new util::NullLogger());
206
#endif
207

1,792✔
208
                   sync::Server::Config c;
3,638✔
209
                   c.logger = m_logger;
3,638✔
210
                   c.token_expiration_clock = this;
3,638✔
211
                   c.listen_address = "127.0.0.1";
3,638✔
212
                   c.disable_sync_to_disk = true;
3,638✔
213
                   c.ssl = config.ssl;
3,638✔
214
                   if (c.ssl) {
3,638✔
215
                       c.ssl_certificate_path = test_util::get_test_resource_path() + "test_util_network_ssl_ca.pem";
2✔
216
                       c.ssl_certificate_key_path =
2✔
217
                           test_util::get_test_resource_path() + "test_util_network_ssl_key.pem";
2✔
218
                   }
2✔
219

1,792✔
220
                   return c;
3,638✔
221
               })())
3,638✔
222
{
3,638✔
223
    m_server.start();
3,638✔
224
    m_url = util::format("%1://127.0.0.1:%2", config.ssl ? "wss" : "ws", m_server.listen_endpoint().port());
3,637✔
225
    if (config.start_immediately)
3,638✔
226
        start();
3,579✔
227
}
3,638✔
228

229
SyncServer::~SyncServer()
230
{
3,638✔
231
    stop();
3,638✔
232
}
3,638✔
233

234
void SyncServer::start()
235
{
3,612✔
236
    REALM_ASSERT(!m_thread.joinable());
3,612✔
237
    m_thread = std::thread([this] {
3,612✔
238
        m_server.run();
3,612✔
239
    });
3,612✔
240
}
3,612✔
241

242
void SyncServer::stop()
243
{
3,688✔
244
    m_server.stop();
3,688✔
245
    if (m_thread.joinable())
3,688✔
246
        m_thread.join();
3,612✔
247
}
3,688✔
248

249
std::string SyncServer::url_for_realm(StringData realm_name) const
250
{
×
251
    return util::format("%1/%2", m_url, realm_name);
×
252
}
×
253

254
struct WaitForSessionState {
255
    std::condition_variable cv;
256
    std::mutex mutex;
257
    bool complete = false;
258
    Status status = Status::OK();
259
};
260

261
static Status wait_for_session(Realm& realm, void (SyncSession::*fn)(util::UniqueFunction<void(Status)>&&),
262
                               std::chrono::seconds timeout)
263
{
1,478✔
264
    auto shared_state = std::make_shared<WaitForSessionState>();
1,478✔
265
    auto& session = *realm.config().sync_config->user->session_for_on_disk_path(realm.config().path);
1,478✔
266
    auto delay = TEST_TIMEOUT_EXTRA > 0 ? timeout + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : timeout;
1,478✔
267
    (session.*fn)([weak_state = std::weak_ptr<WaitForSessionState>(shared_state)](Status s) {
1,478✔
268
        auto shared_state = weak_state.lock();
1,478✔
269
        if (!shared_state) {
1,478✔
270
            return;
×
271
        }
×
272
        std::lock_guard<std::mutex> lock(shared_state->mutex);
1,478✔
273
        shared_state->complete = true;
1,478✔
274
        shared_state->status = s;
1,478✔
275
        shared_state->cv.notify_one();
1,478✔
276
    });
1,478✔
277
    std::unique_lock<std::mutex> lock(shared_state->mutex);
1,478✔
278
    bool completed = shared_state->cv.wait_for(lock, delay, [&]() {
2,956✔
279
        return shared_state->complete == true;
2,956✔
280
    });
2,956✔
281
    if (!completed) {
1,478✔
NEW
282
        throw std::runtime_error(util::format("wait_for_session() exceeded %1 ms", delay.count()));
×
283
    }
×
284
    return shared_state->status;
1,478✔
285
}
1,478✔
286

287
bool wait_for_upload(Realm& realm, std::chrono::seconds timeout)
288
{
718✔
289
    return !wait_for_session(realm, &SyncSession::wait_for_upload_completion, timeout).is_ok();
718✔
290
}
718✔
291

292
bool wait_for_download(Realm& realm, std::chrono::seconds timeout)
293
{
760✔
294
    return !wait_for_session(realm, &SyncSession::wait_for_download_completion, timeout).is_ok();
760✔
295
}
760✔
296

297
void set_app_config_defaults(app::App::Config& app_config,
298
                             const std::shared_ptr<app::GenericNetworkTransport>& transport)
299
{
4,163✔
300
    if (!app_config.transport)
4,163✔
301
        app_config.transport = transport;
3,640✔
302
    if (app_config.device_info.platform_version.empty())
4,163✔
303
        app_config.device_info.platform_version = "Object Store Test Platform Version";
3,640✔
304
    if (app_config.device_info.sdk_version.empty())
4,163✔
305
        app_config.device_info.sdk_version = "SDK Version";
3,640✔
306
    if (app_config.device_info.sdk.empty())
4,163✔
307
        app_config.device_info.sdk = "SDK Name";
3,640✔
308
    if (app_config.device_info.device_name.empty())
4,163✔
309
        app_config.device_info.device_name = "Device Name";
3,640✔
310
    if (app_config.device_info.device_version.empty())
4,163✔
311
        app_config.device_info.device_version = "Device Version";
3,640✔
312
    if (app_config.device_info.framework_name.empty())
4,163✔
313
        app_config.device_info.framework_name = "Framework Name";
3,640✔
314
    if (app_config.device_info.framework_version.empty())
4,163✔
315
        app_config.device_info.framework_version = "Framework Version";
3,640✔
316
    if (app_config.device_info.bundle_id.empty())
4,163✔
317
        app_config.device_info.bundle_id = "Bundle Id";
3,640✔
318
    if (app_config.app_id.empty())
4,163✔
319
        app_config.app_id = "app_id";
3,604✔
320
}
4,163✔
321

322
// MARK: - TestAppSession
323

324
#if REALM_ENABLE_AUTH_TESTS
325

326
TestAppSession::TestAppSession()
327
    : TestAppSession(get_runtime_app_session(), nullptr, DeleteApp{false})
328
{
138✔
329
}
138✔
330

331
TestAppSession::TestAppSession(AppSession session,
332
                               std::shared_ptr<realm::app::GenericNetworkTransport> custom_transport,
333
                               DeleteApp delete_app, ReconnectMode reconnect_mode,
334
                               std::shared_ptr<realm::sync::SyncSocketProvider> custom_socket_provider)
335
    : m_app_session(std::make_unique<AppSession>(session))
336
    , m_base_file_path(util::make_temp_dir() + random_string(10))
337
    , m_delete_app(delete_app)
338
    , m_transport(custom_transport)
339
{
467✔
340
    if (!m_transport)
467✔
341
        m_transport = instance_of<SynchronousTestTransport>;
312✔
342
    auto app_config = get_config(m_transport, *m_app_session);
467✔
343
    util::Logger::set_default_level_threshold(realm::util::Logger::Level::TEST_LOGGING_LEVEL);
467✔
344
    set_app_config_defaults(app_config, m_transport);
467✔
345

225✔
346
    util::try_make_dir(m_base_file_path);
467✔
347
    SyncClientConfig sc_config;
467✔
348
    sc_config.base_file_path = m_base_file_path;
467✔
349
    sc_config.metadata_mode = realm::SyncManager::MetadataMode::NoEncryption;
467✔
350
    sc_config.reconnect_mode = reconnect_mode;
467✔
351
    sc_config.socket_provider = custom_socket_provider;
467✔
352
    // With multiplexing enabled, the linger time controls how long a
225✔
353
    // connection is kept open for reuse. In tests, we want to shut
225✔
354
    // down sync clients immediately.
225✔
355
    sc_config.timeouts.connection_linger_time = 0;
467✔
356

225✔
357
    m_app = app::App::get_uncached_app(app_config, sc_config);
467✔
358

225✔
359
    // initialize sync client
225✔
360
    m_app->sync_manager()->get_sync_client();
467✔
361
    create_user_and_log_in(m_app);
467✔
362
}
467✔
363

364
TestAppSession::~TestAppSession()
365
{
467✔
366
    if (util::File::exists(m_base_file_path)) {
467✔
367
        try {
467✔
368
            m_app->sync_manager()->reset_for_testing();
467✔
369
            util::try_remove_dir_recursive(m_base_file_path);
467✔
370
        }
467✔
371
        catch (const std::exception& ex) {
225✔
372
            std::cerr << ex.what() << "\n";
×
373
        }
×
374
        app::App::clear_cached_apps();
467✔
375
    }
467✔
376
    if (m_delete_app) {
467✔
377
        m_app_session->admin_api.delete_app(m_app_session->server_app_id);
303✔
378
    }
303✔
379
}
467✔
380

381
std::vector<bson::BsonDocument> TestAppSession::get_documents(SyncUser& user, const std::string& object_type,
382
                                                              size_t expected_count) const
383
{
25✔
384
    app::MongoClient remote_client = user.mongo_client("BackingDB");
25✔
385
    app::MongoDatabase db = remote_client.db(m_app_session->config.mongo_dbname);
25✔
386
    app::MongoCollection collection = db[object_type];
25✔
387
    int sleep_time = 10;
25✔
388
    timed_wait_for(
25✔
389
        [&] {
128✔
390
            uint64_t count = 0;
128✔
391
            collection.count({}, [&](uint64_t c, util::Optional<app::AppError> error) {
128✔
392
                REQUIRE(!error);
128!
393
                count = c;
128✔
394
            });
128✔
395
            if (count < expected_count) {
128✔
396
                // querying the server too frequently makes it take longer to process the sync changesets we're
30✔
397
                // waiting for
30✔
398
                millisleep(sleep_time);
77✔
399
                if (sleep_time < 500) {
77✔
400
                    sleep_time *= 2;
61✔
401
                }
61✔
402
                return false;
77✔
403
            }
77✔
404
            return true;
51✔
405
        },
51✔
406
        std::chrono::minutes(5));
25✔
407

10✔
408
    std::vector<bson::BsonDocument> documents;
25✔
409
    collection.find({}, {},
25✔
410
                    [&](util::Optional<std::vector<bson::Bson>>&& result, util::Optional<app::AppError> error) {
25✔
411
                        REQUIRE(result);
25!
412
                        REQUIRE(!error);
25!
413
                        REQUIRE(result->size() == expected_count);
25!
414
                        documents.reserve(result->size());
25✔
415
                        for (auto&& bson : *result) {
2,294✔
416
                            REQUIRE(bson.type() == bson::Bson::Type::Document);
2,294!
417
                            documents.push_back(std::move(static_cast<bson::BsonDocument&>(bson)));
2,294✔
418
                        }
2,294✔
419
                    });
25✔
420
    return documents;
25✔
421
}
25✔
422
#endif // REALM_ENABLE_AUTH_TESTS
423

424
// MARK: - TestSyncManager
425

426
TestSyncManager::TestSyncManager(const Config& config, const SyncServer::Config& sync_server_config)
427
    : transport(config.transport ? config.transport : std::make_shared<Transport>(network_callback))
428
    , m_sync_server(sync_server_config)
429
    , m_should_teardown_test_directory(config.should_teardown_test_directory)
430
{
3,638✔
431
    app::App::Config app_config = config.app_config;
3,638✔
432
    set_app_config_defaults(app_config, transport);
3,638✔
433
    util::Logger::set_default_level_threshold(config.log_level);
3,638✔
434

1,792✔
435
    SyncClientConfig sc_config;
3,638✔
436
    m_base_file_path = config.base_path.empty() ? util::make_temp_dir() + random_string(10) : config.base_path;
3,617✔
437
    util::try_make_dir(m_base_file_path);
3,638✔
438
    sc_config.base_file_path = m_base_file_path;
3,638✔
439
    sc_config.metadata_mode = config.metadata_mode;
3,638✔
440

1,792✔
441
    m_app = app::App::get_uncached_app(app_config, sc_config);
3,638✔
442
    if (config.override_sync_route) {
3,638✔
443
        m_app->sync_manager()->set_sync_route(m_sync_server.base_url() + "/realm-sync");
3,636✔
444
    }
3,636✔
445

1,792✔
446
    if (config.start_sync_client) {
3,638✔
447
        // initialize sync client
1,790✔
448
        m_app->sync_manager()->get_sync_client();
3,634✔
449
    }
3,634✔
450
}
3,638✔
451

452
TestSyncManager::~TestSyncManager()
453
{
3,638✔
454
    if (m_should_teardown_test_directory) {
3,638✔
455
        if (!m_base_file_path.empty() && util::File::exists(m_base_file_path)) {
3,600✔
456
            try {
3,598✔
457
                m_app->sync_manager()->reset_for_testing();
3,598✔
458
                util::try_remove_dir_recursive(m_base_file_path);
3,598✔
459
            }
3,598✔
460
            catch (const std::exception& ex) {
1,772✔
461
                std::cerr << ex.what() << "\n";
×
462
            }
×
463
            app::App::clear_cached_apps();
3,598✔
464
        }
3,598✔
465
    }
3,600✔
466
}
3,638✔
467

468
std::shared_ptr<realm::SyncUser> TestSyncManager::fake_user(const std::string& name)
469
{
6✔
470
    return get_fake_user(*m_app, name);
6✔
471
}
6✔
472

473
#endif // REALM_ENABLE_SYNC
474

475
#if REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
476
// MARK: - TsanNotifyWorker
477
// A helper which synchronously runs on_change() on a fixed background thread
478
// so that ThreadSanitizer can potentially detect issues
479
// This deliberately uses an unsafe spinlock for synchronization to ensure that
480
// the code being tested has to supply all required safety
481
static class TsanNotifyWorker {
482
public:
483
    TsanNotifyWorker()
484
    {
485
        m_thread = std::thread([&] {
486
            work();
487
        });
488
    }
489

490
    void work()
491
    {
492
        while (true) {
493
            auto value = m_signal.load(std::memory_order_relaxed);
494
            if (value == 0 || value == 1)
495
                continue;
496
            if (value == 2)
497
                return;
498

499
            if (value & 1) {
500
                // Synchronize on the first handover of a given coordinator.
501
                value &= ~1;
502
                m_signal.load();
503
            }
504

505
            auto c = reinterpret_cast<_impl::RealmCoordinator*>(value);
506
            c->on_change();
507
            m_signal.store(1, std::memory_order_relaxed);
508
        }
509
    }
510

511
    ~TsanNotifyWorker()
512
    {
513
        m_signal = 2;
514
        m_thread.join();
515
    }
516

517
    void on_change(const std::shared_ptr<_impl::RealmCoordinator>& c)
518
    {
519
        auto& it = m_published_coordinators[c.get()];
520
        if (it.lock()) {
521
            m_signal.store(reinterpret_cast<uintptr_t>(c.get()), std::memory_order_relaxed);
522
        }
523
        else {
524
            // Synchronize on the first handover of a given coordinator.
525
            it = c;
526
            m_signal = reinterpret_cast<uintptr_t>(c.get()) | 1;
527
        }
528

529
        while (m_signal.load(std::memory_order_relaxed) != 1)
530
            ;
531
    }
532

533
private:
534
    std::atomic<uintptr_t> m_signal{0};
535
    std::thread m_thread;
536
    std::map<_impl::RealmCoordinator*, std::weak_ptr<_impl::RealmCoordinator>> m_published_coordinators;
537
} s_worker;
538

539
void on_change_but_no_notify(Realm& realm)
540
{
541
    s_worker.on_change(_impl::RealmCoordinator::get_existing_coordinator(realm.config().path));
542
}
543

544
void advance_and_notify(Realm& realm)
545
{
546
    on_change_but_no_notify(realm);
547
    realm.notify();
548
}
549

550
#else // REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
551

552
void on_change_but_no_notify(Realm& realm)
553
{
19,343✔
554
    _impl::RealmCoordinator::get_coordinator(realm.config().path)->on_change();
19,343✔
555
}
19,343✔
556

557
void advance_and_notify(Realm& realm)
558
{
16,178✔
559
    on_change_but_no_notify(realm);
16,178✔
560
    realm.notify();
16,178✔
561
}
16,178✔
562
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc