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

realm / realm-core / 2214

10 Apr 2024 11:21PM UTC coverage: 91.813% (-0.8%) from 92.623%
2214

push

Evergreen

web-flow
Add missing availability checks for SecCopyErrorMessageString (#7577)

This requires iOS 11.3 and we currently target iOS 11.

94848 of 175770 branches covered (53.96%)

7 of 22 new or added lines in 2 files covered. (31.82%)

1815 existing lines in 77 files now uncovered.

242945 of 264608 relevant lines covered (91.81%)

6136478.37 hits per line

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

88.79
/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/sync/sync_test_utils.hpp"
24
#include "../util/crypt_key.hpp"
25
#include "../util/test_path.hpp"
26
#include "util/sync/sync_test_utils.hpp"
27

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

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

46
#include <cstdlib>
47
#include <iostream>
48

49
#ifdef _WIN32
50
#include <io.h>
51
#include <fcntl.h>
52

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

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

68
using namespace realm;
69

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

4,811✔
91
    schema_version = 0;
9,700✔
92
}
9,700✔
93

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

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

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

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

130
#if REALM_ENABLE_SYNC
131

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

136
SyncTestFile::SyncTestFile(TestSyncManager& tsm, std::string name, std::string user_name)
137
    : SyncTestFile(tsm.fake_user(user_name), bson::Bson(name))
138
{
196✔
139
}
196✔
140

141
SyncTestFile::SyncTestFile(OfflineAppSession& oas, std::string name)
142
    : SyncTestFile(oas.make_user(), bson::Bson(name))
143
{
3,562✔
144
}
3,562✔
145

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

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

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

190
SyncTestFile::SyncTestFile(TestSyncManager& tsm, bson::Bson partition, Schema schema)
191
    : SyncTestFile(tsm.fake_user("test"), std::move(partition), std::move(schema))
UNCOV
192
{
×
UNCOV
193
}
×
194

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

86✔
201
                   m_logger = util::Logger::get_default_logger();
226✔
202

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

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

224
SyncServer::~SyncServer()
225
{
226✔
226
    stop();
226✔
227
}
226✔
228

229
void SyncServer::start()
230
{
198✔
231
    REALM_ASSERT(!m_thread.joinable());
198✔
232
    m_thread = std::thread([this] {
198✔
233
        m_server.run();
198✔
234
    });
198✔
235
}
198✔
236

237
void SyncServer::stop()
238
{
276✔
239
    m_server.stop();
276✔
240
    if (m_thread.joinable())
276✔
241
        m_thread.join();
198✔
242
}
276✔
243

244
std::string SyncServer::url_for_realm(StringData realm_name) const
UNCOV
245
{
×
UNCOV
246
    return util::format("%1/%2", m_url, realm_name);
×
UNCOV
247
}
×
248

249
int SyncServer::port() const
250
{
14✔
251
    return m_server.listen_endpoint().port();
14✔
252
}
14✔
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,722✔
264
    auto shared_state = std::make_shared<WaitForSessionState>();
1,722✔
265
    auto& session = *realm.sync_session();
1,722✔
266
    auto delay = TEST_TIMEOUT_EXTRA > 0 ? timeout + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : timeout;
1,722✔
267
    (session.*fn)([weak_state = std::weak_ptr<WaitForSessionState>(shared_state)](Status s) {
1,722✔
268
        auto shared_state = weak_state.lock();
1,722✔
269
        if (!shared_state) {
1,722✔
UNCOV
270
            return;
×
UNCOV
271
        }
×
272
        std::lock_guard<std::mutex> lock(shared_state->mutex);
1,722✔
273
        shared_state->complete = true;
1,722✔
274
        shared_state->status = s;
1,722✔
275
        shared_state->cv.notify_one();
1,722✔
276
    });
1,722✔
277
    std::unique_lock<std::mutex> lock(shared_state->mutex);
1,722✔
278
    bool completed = shared_state->cv.wait_for(lock, delay, [&]() {
3,444✔
279
        return shared_state->complete == true;
3,444✔
280
    });
3,444✔
281
    if (!completed) {
1,722✔
UNCOV
282
        throw std::runtime_error(util::format("wait_for_session() exceeded %1 s", delay.count()));
×
UNCOV
283
    }
×
284
    return shared_state->status;
1,722✔
285
}
1,722✔
286

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

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

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

323
// MARK: - TestAppSession
324

325
#if REALM_ENABLE_AUTH_TESTS
326

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

332
TestAppSession::TestAppSession(AppSession session,
333
                               std::shared_ptr<realm::app::GenericNetworkTransport> custom_transport,
334
                               DeleteApp delete_app, ReconnectMode reconnect_mode,
335
                               std::shared_ptr<realm::sync::SyncSocketProvider> custom_socket_provider)
336
    : m_app_session(std::make_unique<AppSession>(session))
337
    , m_base_file_path(util::make_temp_dir() + random_string(10))
338
    , m_delete_app(delete_app)
339
    , m_transport(custom_transport)
340
{
515✔
341
    if (!m_transport)
515✔
342
        m_transport = instance_of<SynchronousTestTransport>;
310✔
343
    app_config = get_config(m_transport, *m_app_session);
515✔
344
    set_app_config_defaults(app_config, m_transport);
515✔
345
    app_config.base_file_path = m_base_file_path;
515✔
346
    app_config.metadata_mode = realm::app::AppConfig::MetadataMode::NoEncryption;
515✔
347

249✔
348
    util::try_make_dir(m_base_file_path);
515✔
349
    app_config.sync_client_config.reconnect_mode = reconnect_mode;
515✔
350
    app_config.sync_client_config.socket_provider = custom_socket_provider;
515✔
351
    // With multiplexing enabled, the linger time controls how long a
249✔
352
    // connection is kept open for reuse. In tests, we want to shut
249✔
353
    // down sync clients immediately.
249✔
354
    app_config.sync_client_config.timeouts.connection_linger_time = 0;
515✔
355

249✔
356
    m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config);
515✔
357

249✔
358
    // initialize sync client
249✔
359
    m_app->sync_manager()->get_sync_client();
515✔
360
    user_creds = create_user_and_log_in(m_app);
515✔
361
}
515✔
362

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

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

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

422
// MARK: - TestSyncManager
423

424
TestSyncManager::Config::Config() {}
220✔
425

426
TestSyncManager::TestSyncManager(const Config& config, const SyncServer::Config& sync_server_config)
427
    : m_sync_manager(SyncManager::create(SyncClientConfig()))
428
    , m_sync_server(sync_server_config)
429
    , m_base_file_path(config.base_path.empty() ? util::make_temp_dir() : config.base_path)
430
    , m_should_teardown_test_directory(config.should_teardown_test_directory)
431
{
220✔
432
    util::try_make_dir(m_base_file_path);
220✔
433

83✔
434
    m_sync_manager->set_sync_route(m_sync_server.base_url() + "/realm-sync", true);
220✔
435
    if (config.start_sync_client) {
220✔
436
        m_sync_manager->get_sync_client();
216✔
437
    }
216✔
438
}
220✔
439

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

456
std::shared_ptr<TestUser> TestSyncManager::fake_user(const std::string& name)
457
{
284✔
458
    auto user = std::make_shared<TestUser>(name, m_sync_manager);
284✔
459
    user->m_access_token = fake_access_token;
284✔
460
    user->m_refresh_token = fake_refresh_token;
284✔
461
    return user;
284✔
462
}
284✔
463

464
OfflineAppSession::Config::Config(std::shared_ptr<realm::app::GenericNetworkTransport> t)
465
    : transport(t)
466
{
3,690✔
467
}
3,690✔
468

469
OfflineAppSession::OfflineAppSession(OfflineAppSession::Config config)
470
    : m_transport(std::move(config.transport))
471
    , m_delete_storage(config.delete_storage)
472
{
3,704✔
473
    REALM_ASSERT(m_transport);
3,704✔
474
    app::AppConfig app_config;
3,704✔
475
    set_app_config_defaults(app_config, m_transport);
3,704✔
476

1,852✔
477
    if (config.storage_path) {
3,704✔
478
        m_base_file_path = *config.storage_path;
28✔
479
        util::try_make_dir(m_base_file_path);
28✔
480
    }
28✔
481
    else {
3,676✔
482
        m_base_file_path = util::make_temp_dir();
3,676✔
483
    }
3,676✔
484

1,852✔
485
    app_config.base_file_path = m_base_file_path;
3,704✔
486
    app_config.metadata_mode = config.metadata_mode;
3,704✔
487
    if (config.base_url) {
3,704✔
488
        app_config.base_url = *config.base_url;
48✔
489
    }
48✔
490
    if (config.app_id) {
3,704✔
UNCOV
491
        app_config.app_id = *config.app_id;
×
UNCOV
492
    }
×
493
    app_config.sync_client_config.socket_provider = config.socket_provider;
3,704✔
494
    m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config);
3,704✔
495
}
3,704✔
496

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

511
std::shared_ptr<realm::app::User> OfflineAppSession::make_user() const
512
{
3,634✔
513
    create_user_and_log_in(app());
3,634✔
514
    return app()->current_user();
3,634✔
515
}
3,634✔
516

517
#endif // REALM_ENABLE_SYNC
518

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

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

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

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

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

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

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

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

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

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

594
#else // REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
595

596
void on_change_but_no_notify(Realm& realm)
597
{
18,437✔
598
    _impl::RealmCoordinator::get_coordinator(realm.config().path)->on_change();
18,437✔
599
}
18,437✔
600

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