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

realm / realm-core / 2123

12 Mar 2024 11:18PM UTC coverage: 91.833% (+0.05%) from 91.787%
2123

push

Evergreen

web-flow
RCORE-1928 Use baasaas for baas integration tests in CI (#7423)

94732 of 174812 branches covered (54.19%)

201 of 288 new or added lines in 4 files covered. (69.79%)

62 existing lines in 11 files now uncovered.

242847 of 264443 relevant lines covered (91.83%)

5989046.65 hits per line

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

88.82
/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
{
9,672✔
70
    disable_sync_to_disk();
9,672✔
71
    m_temp_dir = util::make_temp_dir();
9,672✔
72
    path = (fs::path(m_temp_dir) / "realm.XXXXXX").string();
9,672✔
73
    if (const char* crypt_key = test_util::crypt_key()) {
9,672✔
UNCOV
74
        encryption_key = std::vector<char>(crypt_key, crypt_key + 64);
×
75
    }
×
76
    int fd = mkstemp(path.data());
9,672✔
77
    if (fd < 0) {
9,672✔
78
        int err = errno;
×
79
        throw std::system_error(err, std::system_category());
×
80
    }
×
81
#ifdef _WIN32
82
    _close(fd);
83
    _unlink(path.c_str());
84
#else // POSIX
85
    close(fd);
9,672✔
86
    unlink(path.c_str());
9,672✔
87
#endif
9,672✔
88

4,797✔
89
    schema_version = 0;
9,672✔
90
}
9,672✔
91

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

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

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

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

128
#if REALM_ENABLE_SYNC
129

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

134
static std::shared_ptr<SyncUser> get_fake_user(SyncManager& sync_manager, const std::string& user_name)
135
{
3,886✔
136
    return sync_manager.get_user(user_name, fake_refresh_token, fake_access_token, fake_device_id);
3,886✔
137
}
3,886✔
138

139
SyncTestFile::SyncTestFile(TestSyncManager& tsm, std::string name, std::string user_name)
140
    : SyncTestFile(tsm.fake_user(user_name), bson::Bson(name))
141
{
198✔
142
}
198✔
143

144
SyncTestFile::SyncTestFile(OfflineAppSession& oas, std::string name)
145
    : SyncTestFile(oas.make_user(), bson::Bson(name))
146
{
3,560✔
147
}
3,560✔
148

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

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

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

193
SyncTestFile::SyncTestFile(std::shared_ptr<app::App> app, bson::Bson partition, Schema schema)
194
    : SyncTestFile(app->current_user(), std::move(partition), std::move(schema))
195
{
126✔
196
}
126✔
197

198
// MARK: - SyncServer
199
SyncServer::SyncServer(const SyncServer::Config& config)
200
    : m_local_root_dir(config.local_dir.empty() ? util::make_temp_dir() : config.local_dir)
201
    , m_server(m_local_root_dir, util::none, ([&] {
344✔
202
                   using namespace std::literals::chrono_literals;
344✔
203

145✔
204
                   m_logger = util::Logger::get_default_logger();
344✔
205

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

145✔
218
                   return c;
344✔
219
               })())
344✔
220
{
344✔
221
    m_server.start();
344✔
222
    m_url = util::format("%1://127.0.0.1:%2", config.ssl ? "wss" : "ws", m_server.listen_endpoint().port());
343✔
223
    if (config.start_immediately)
344✔
224
        start();
283✔
225
}
344✔
226

227
SyncServer::~SyncServer()
228
{
344✔
229
    stop();
344✔
230
}
344✔
231

232
void SyncServer::start()
233
{
316✔
234
    REALM_ASSERT(!m_thread.joinable());
316✔
235
    m_thread = std::thread([this] {
316✔
236
        m_server.run();
316✔
237
    });
316✔
238
}
316✔
239

240
void SyncServer::stop()
241
{
394✔
242
    m_server.stop();
394✔
243
    if (m_thread.joinable())
394✔
244
        m_thread.join();
316✔
245
}
394✔
246

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

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

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

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

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

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

320
// MARK: - TestAppSession
321

322
#if REALM_ENABLE_AUTH_TESTS
323

324
TestAppSession::TestAppSession()
325
    : TestAppSession(get_runtime_app_session(), nullptr, DeleteApp{false})
326
{
140✔
327
}
140✔
328

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

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

252✔
354
    m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config, sc_config);
521✔
355

252✔
356
    // initialize sync client
252✔
357
    m_app->sync_manager()->get_sync_client();
521✔
358
    create_user_and_log_in(m_app);
521✔
359
}
521✔
360

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

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

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

420
// MARK: - TestSyncManager
421

422
TestSyncManager::Config::Config() {}
336✔
423

424
TestSyncManager::TestSyncManager(const Config& config, const SyncServer::Config& sync_server_config)
425
    : m_sync_server(sync_server_config)
426
    , m_base_file_path(config.base_path.empty() ? util::make_temp_dir() : config.base_path)
427
    , m_should_teardown_test_directory(config.should_teardown_test_directory)
428
{
344✔
429
    util::try_make_dir(m_base_file_path);
344✔
430
    SyncClientConfig sc_config;
344✔
431
    sc_config.base_file_path = m_base_file_path;
344✔
432
    sc_config.metadata_mode = config.metadata_mode;
344✔
433
    m_sync_manager = SyncManager::create(nullptr, m_sync_server.base_url() + "/realm-sync", sc_config, "app_id");
344✔
434

145✔
435
    if (config.start_sync_client) {
344✔
436
        m_sync_manager->get_sync_client();
340✔
437
    }
340✔
438
}
344✔
439

440
TestSyncManager::~TestSyncManager()
441
{
344✔
442
    if (m_should_teardown_test_directory) {
344✔
443
        if (!m_base_file_path.empty() && util::File::exists(m_base_file_path)) {
314✔
444
            try {
312✔
445
                m_sync_manager->tear_down_for_testing();
312✔
446
                util::try_remove_dir_recursive(m_base_file_path);
312✔
447
            }
312✔
448
            catch (const std::exception& ex) {
129✔
449
                std::cerr << ex.what() << "\n";
×
450
            }
×
451
            app::App::clear_cached_apps();
312✔
452
        }
312✔
453
    }
314✔
454
}
344✔
455

456
std::shared_ptr<realm::SyncUser> TestSyncManager::fake_user(const std::string& name)
457
{
316✔
458
    return get_fake_user(*m_sync_manager, name);
316✔
459
}
316✔
460

461
OfflineAppSession::Config::Config(std::shared_ptr<realm::app::GenericNetworkTransport> t)
462
    : transport(t)
463
{
3,622✔
464
}
3,622✔
465

466
OfflineAppSession::OfflineAppSession(OfflineAppSession::Config config)
467
    : m_transport(std::move(config.transport))
468
    , m_delete_storage(config.delete_storage)
469
{
3,624✔
470
    REALM_ASSERT(m_transport);
3,624✔
471
    if (config.storage_path) {
3,624✔
472
        m_base_file_path = *config.storage_path;
4✔
473
        util::try_make_dir(m_base_file_path);
4✔
474
    }
4✔
475
    else {
3,620✔
476
        m_base_file_path = util::make_temp_dir();
3,620✔
477
    }
3,620✔
478

1,812✔
479
    app::App::Config app_config;
3,624✔
480
    set_app_config_defaults(app_config, m_transport);
3,624✔
481
    if (config.base_url) {
3,624✔
482
        app_config.base_url = *config.base_url;
×
483
    }
×
484
    if (config.app_id) {
3,624✔
485
        app_config.app_id = *config.app_id;
×
486
    }
×
487

1,812✔
488
    SyncClientConfig sc_config;
3,624✔
489
    sc_config.base_file_path = m_base_file_path;
3,624✔
490
    sc_config.metadata_mode = config.metadata_mode;
3,624✔
491
    sc_config.socket_provider = config.socket_provider;
3,624✔
492

1,812✔
493
    m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config, sc_config);
3,624✔
494
}
3,624✔
495

496
OfflineAppSession::~OfflineAppSession()
497
{
3,624✔
498
    if (util::File::exists(m_base_file_path) && m_delete_storage) {
3,624✔
499
        try {
3,622✔
500
            m_app->sync_manager()->tear_down_for_testing();
3,622✔
501
            util::try_remove_dir_recursive(m_base_file_path);
3,622✔
502
        }
3,622✔
503
        catch (const std::exception& ex) {
1,811✔
504
            std::cerr << ex.what() << "\n";
×
505
        }
×
506
        app::App::clear_cached_apps();
3,622✔
507
    }
3,622✔
508
}
3,624✔
509

510
std::shared_ptr<realm::SyncUser> OfflineAppSession::make_user() const
511
{
3,570✔
512
    return get_fake_user(*m_app->sync_manager(), "test user");
3,570✔
513
}
3,570✔
514

515
#endif // REALM_ENABLE_SYNC
516

517
#if REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
518
// MARK: - TsanNotifyWorker
519
// A helper which synchronously runs on_change() on a fixed background thread
520
// so that ThreadSanitizer can potentially detect issues
521
// This deliberately uses an unsafe spinlock for synchronization to ensure that
522
// the code being tested has to supply all required safety
523
static class TsanNotifyWorker {
524
public:
525
    TsanNotifyWorker()
526
    {
527
        m_thread = std::thread([&] {
528
            work();
529
        });
530
    }
531

532
    void work()
533
    {
534
        while (true) {
535
            auto value = m_signal.load(std::memory_order_relaxed);
536
            if (value == 0 || value == 1)
537
                continue;
538
            if (value == 2)
539
                return;
540

541
            if (value & 1) {
542
                // Synchronize on the first handover of a given coordinator.
543
                value &= ~1;
544
                m_signal.load();
545
            }
546

547
            auto c = reinterpret_cast<_impl::RealmCoordinator*>(value);
548
            c->on_change();
549
            m_signal.store(1, std::memory_order_relaxed);
550
        }
551
    }
552

553
    ~TsanNotifyWorker()
554
    {
555
        m_signal = 2;
556
        m_thread.join();
557
    }
558

559
    void on_change(const std::shared_ptr<_impl::RealmCoordinator>& c)
560
    {
561
        auto& it = m_published_coordinators[c.get()];
562
        if (it.lock()) {
563
            m_signal.store(reinterpret_cast<uintptr_t>(c.get()), std::memory_order_relaxed);
564
        }
565
        else {
566
            // Synchronize on the first handover of a given coordinator.
567
            it = c;
568
            m_signal = reinterpret_cast<uintptr_t>(c.get()) | 1;
569
        }
570

571
        while (m_signal.load(std::memory_order_relaxed) != 1)
572
            ;
573
    }
574

575
private:
576
    std::atomic<uintptr_t> m_signal{0};
577
    std::thread m_thread;
578
    std::map<_impl::RealmCoordinator*, std::weak_ptr<_impl::RealmCoordinator>> m_published_coordinators;
579
} s_worker;
580

581
void on_change_but_no_notify(Realm& realm)
582
{
583
    s_worker.on_change(_impl::RealmCoordinator::get_existing_coordinator(realm.config().path));
584
}
585

586
void advance_and_notify(Realm& realm)
587
{
588
    on_change_but_no_notify(realm);
589
    realm.notify();
590
}
591

592
#else // REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
593

594
void on_change_but_no_notify(Realm& realm)
595
{
20,484✔
596
    _impl::RealmCoordinator::get_coordinator(realm.config().path)->on_change();
20,484✔
597
}
20,484✔
598

599
void advance_and_notify(Realm& realm)
600
{
17,028✔
601
    on_change_but_no_notify(realm);
17,028✔
602
    realm.notify();
17,028✔
603
}
17,028✔
604
#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

© 2025 Coveralls, Inc