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

realm / realm-core / 2104

06 Mar 2024 05:28PM UTC coverage: 90.902% (-0.05%) from 90.949%
2104

push

Evergreen

web-flow
Add * wildcard matching for benchmark filters and fix a crash (#7413)

* add * wildcard matching for benchmark filters and fix a crash

* Use full regex as a filter for benchmark names

---------

Co-authored-by: Kirill Burtsev <kirill.burtsev@mongodb.com>

93884 of 173072 branches covered (54.25%)

238284 of 262132 relevant lines covered (90.9%)

6142248.78 hits per line

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

69.36
/src/realm/util/file_mapper.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 <realm/util/features.h>
20

21
#include <realm/util/file_mapper.hpp>
22

23
#ifdef _WIN32
24
#include <windows.h>
25
#else
26
#include <cerrno>
27
#include <sys/mman.h>
28
#endif
29

30
#include <realm/exceptions.hpp>
31
#include <realm/impl/simulated_failure.hpp>
32
#include <realm/util/errno.hpp>
33
#include <realm/util/to_string.hpp>
34
#include <system_error>
35

36
#if REALM_ENABLE_ENCRYPTION
37

38
#include <realm/util/encrypted_file_mapping.hpp>
39
#include <realm/util/aes_cryptor.hpp>
40

41
#include <atomic>
42
#include <memory>
43
#include <csignal>
44
#include <sys/stat.h>
45
#include <cstring>
46
#include <atomic>
47
#include <fstream>
48
#include <sstream>
49
#include <regex>
50
#include <thread>
51

52
#include <realm/util/file.hpp>
53
#include <realm/util/errno.hpp>
54
#include <realm/util/terminate.hpp>
55
#include <realm/util/thread.hpp>
56
#include <cstring> // for memset
57

58
#if REALM_PLATFORM_APPLE
59
#include <dispatch/dispatch.h>
60
#endif
61

62
#endif // enable encryption
63

64
namespace {
65

66
inline bool is_mmap_memory_error(int err)
67
{
×
68
    return (err == EAGAIN || err == EMFILE || err == ENOMEM);
×
69
}
×
70

71
} // Unnamed namespace
72

73
using namespace realm;
74
using namespace realm::util;
75

76
namespace realm {
77
namespace util {
78

79
size_t round_up_to_page_size(size_t size) noexcept
80
{
1,597,887✔
81
    return (size + page_size() - 1) & ~(page_size() - 1);
1,597,887✔
82
}
1,597,887✔
83

84

85
#if REALM_ENABLE_ENCRYPTION
86

87
// A list of all of the active encrypted mappings for a single file
88
struct mappings_for_file {
89
    File::UniqueID file_unique_id;
90
    std::shared_ptr<SharedFileInfo> info;
91
};
92

93
// Group the information we need to map a SIGSEGV address to an
94
// EncryptedFileMapping for the sake of cache-friendliness with 3+ active
95
// mappings (and no worse with only two)
96
struct mapping_and_addr {
97
    std::shared_ptr<EncryptedFileMapping> mapping;
98
    void* addr;
99
    size_t size;
100
};
101

102
util::Mutex& mapping_mutex = *(new util::Mutex);
103
namespace {
104
std::vector<mapping_and_addr>& mappings_by_addr = *new std::vector<mapping_and_addr>;
105
std::vector<mappings_for_file>& mappings_by_file = *new std::vector<mappings_for_file>;
106
static unsigned int file_reclaim_index = 0;
107
static std::atomic<size_t> num_decrypted_pages(0); // this is for statistical purposes
108
static std::atomic<size_t> reclaimer_target(0);    // do.
109
static std::atomic<size_t> reclaimer_workload(0);  // do.
110
// helpers
111

112
int64_t fetch_value_in_file(const std::string& fname, const char* scan_pattern)
113
{
2,304✔
114
    std::ifstream file(fname);
2,304✔
115
    if (file) {
2,304✔
116
        std::stringstream buffer;
810✔
117
        buffer << file.rdbuf();
810✔
118

810✔
119
        std::string s = buffer.str();
810✔
120
        std::smatch m;
810✔
121
        std::regex e(scan_pattern);
810✔
122

810✔
123
        if (std::regex_search(s, m, e)) {
810!
124
            std::string ibuf = m[1];
810✔
125
            return strtol(ibuf.c_str(), nullptr, 10);
810✔
126
        }
810✔
127
    }
1,494✔
128
    return PageReclaimGovernor::no_match;
1,494✔
129
}
1,494✔
130

131

132
/* Default reclaim governor
133
 *
134
 */
135

136
class DefaultGovernor : public PageReclaimGovernor {
137
public:
138
    static int64_t pick_lowest_valid(int64_t a, int64_t b)
139
    {
1,152✔
140
        if (a == PageReclaimGovernor::no_match)
1,152✔
141
            return b;
×
142
        if (b == PageReclaimGovernor::no_match)
1,152✔
143
            return a;
612✔
144
        return std::min(a, b);
540✔
145
    }
540✔
146

147
    static int64_t pick_if_valid(int64_t source, int64_t target)
148
    {
1,728✔
149
        if (source == PageReclaimGovernor::no_match)
1,728✔
150
            return PageReclaimGovernor::no_match;
612✔
151
        return target;
1,116✔
152
    }
1,116✔
153

154
    static int64_t get_target_from_system(const std::string& cfg_file_name)
155
    {
576✔
156
        int64_t target;
576✔
157
        auto local_spec = fetch_value_in_file(cfg_file_name, "target ([[:digit:]]+)");
576✔
158
        if (local_spec != no_match) { // overrides everything!
576✔
159
            target = local_spec;
×
160
        }
×
161
        else {
576✔
162
            // no local spec, try to deduce something reasonable from platform info
270✔
163
            auto from_proc = fetch_value_in_file("/proc/meminfo", "MemTotal:[[:space:]]+([[:digit:]]+) kB") * 1024;
576✔
164
            auto from_cgroup = fetch_value_in_file("/sys/fs/cgroup/memory/memory.limit_in_bytes", "^([[:digit:]]+)");
576✔
165
            auto cache_use = fetch_value_in_file("/sys/fs/cgroup/memory/memory.stat", "cache ([[:digit:]]+)");
576✔
166
            target = pick_if_valid(from_proc, from_proc / 4);
576✔
167
            target = pick_lowest_valid(target, pick_if_valid(from_cgroup, from_cgroup / 4));
576✔
168
            target = pick_lowest_valid(target, pick_if_valid(cache_use, cache_use));
576✔
169
        }
576✔
170
        return target;
576✔
171
    }
576✔
172

173
    util::UniqueFunction<int64_t()> current_target_getter(size_t load) override
174
    {
6,168✔
175
        static_cast<void>(load);
6,168✔
176
        if (m_refresh_count > 0) {
6,168✔
177
            --m_refresh_count;
5,592✔
178
            return [target = m_target] {
5,592✔
179
                return target;
5,592✔
180
            };
5,592✔
181
        }
5,592✔
182
        m_refresh_count = 10;
576✔
183

270✔
184
        return [file_name = m_cfg_file_name] {
576✔
185
            return get_target_from_system(file_name);
576✔
186
        };
576✔
187
    }
576✔
188

189
    void report_target_result(int64_t target) override
190
    {
6,168✔
191
        m_target = target;
6,168✔
192
    }
6,168✔
193

194
    DefaultGovernor()
195
    {
24✔
196
        auto cfg_name = getenv("REALM_PAGE_GOVERNOR_CFG");
24✔
197
        if (cfg_name) {
24✔
198
            m_cfg_file_name = cfg_name;
×
199
        }
×
200
    }
24✔
201

202
private:
203
    std::string m_cfg_file_name;
204
    int64_t m_target = 0;
205
    int m_refresh_count = 0;
206
};
207

208
static DefaultGovernor default_governor;
209
static PageReclaimGovernor* governor = &default_governor;
210

211
void reclaim_pages();
212

213
#if !REALM_PLATFORM_APPLE
214
static std::atomic<bool> reclaimer_shutdown(false);
215
static std::unique_ptr<std::thread> reclaimer_thread;
216

217
static void ensure_reclaimer_thread_runs()
218
{
714✔
219
    if (reclaimer_thread == nullptr) {
714✔
220
        reclaimer_thread = std::make_unique<std::thread>([] {
12✔
221
            while (!reclaimer_shutdown) {
2,898✔
222
                reclaim_pages();
2,886✔
223
                millisleep(1000);
2,886✔
224
            }
2,886✔
225
        });
12✔
226
    }
12✔
227
}
714✔
228

229
struct ReclaimerThreadStopper {
230
    ~ReclaimerThreadStopper()
231
    {
232
        if (reclaimer_thread) {
233
            reclaimer_shutdown = true;
234
            reclaimer_thread->join();
235
        }
236
    }
237
} reclaimer_thread_stopper;
238
#else // REALM_PLATFORM_APPLE
239
static dispatch_source_t reclaimer_timer;
240
static dispatch_queue_t reclaimer_queue;
241

242
static void ensure_reclaimer_thread_runs()
243
{
582✔
244
    if (!reclaimer_timer) {
582✔
245
        if (__builtin_available(iOS 10, macOS 12, tvOS 10, watchOS 3, *)) {
12✔
246
            reclaimer_queue = dispatch_queue_create_with_target("io.realm.page-reclaimer", DISPATCH_QUEUE_SERIAL,
247
                                                                dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
248
        }
249
        else {
12✔
250
            reclaimer_queue = dispatch_queue_create("io.realm.page-reclaimer", DISPATCH_QUEUE_SERIAL);
12✔
251
        }
12✔
252
        reclaimer_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, reclaimer_queue);
12✔
253
        dispatch_source_set_timer(reclaimer_timer, DISPATCH_TIME_NOW, NSEC_PER_SEC, NSEC_PER_SEC);
12✔
254
        dispatch_source_set_event_handler(reclaimer_timer, ^{
3,282✔
255
            reclaim_pages();
3,282✔
256
        });
3,282✔
257
        dispatch_resume(reclaimer_timer);
12✔
258
    }
12✔
259
}
582✔
260

261
struct ReclaimerThreadStopper {
262
    ~ReclaimerThreadStopper()
263
    {
12✔
264
        if (reclaimer_timer) {
12✔
265
            dispatch_source_cancel(reclaimer_timer);
12✔
266
            // Block until any currently-running timer tasks are done
267
            dispatch_sync(reclaimer_queue, ^{
12✔
268
                          });
12✔
269
            dispatch_release(reclaimer_timer);
12✔
270
            dispatch_release(reclaimer_queue);
12✔
271
        }
12✔
272
    }
12✔
273
} reclaimer_thread_stopper;
274
#endif
275
} // anonymous namespace
276

277
void set_page_reclaim_governor(PageReclaimGovernor* new_governor)
278
{
×
279
    UniqueLock lock(mapping_mutex);
×
280
    governor = new_governor ? new_governor : &default_governor;
×
281
    ensure_reclaimer_thread_runs();
×
282
}
×
283

284
size_t get_num_decrypted_pages()
285
{
×
286
    return num_decrypted_pages.load();
×
287
}
×
288

289
void encryption_note_reader_start(SharedFileInfo& info, const void* reader_id)
290
{
1,296✔
291
    UniqueLock lock(mapping_mutex);
1,296✔
292
    ensure_reclaimer_thread_runs();
1,296✔
293
    auto j = std::find_if(info.readers.begin(), info.readers.end(), [=](auto& reader) {
906✔
294
        return reader.reader_ID == reader_id;
450✔
295
    });
450✔
296
    if (j == info.readers.end()) {
1,296✔
297
        ReaderInfo i = {reader_id, info.current_version};
1,191✔
298
        info.readers.push_back(i);
1,191✔
299
    }
1,191✔
300
    else {
105✔
301
        j->version = info.current_version;
105✔
302
    }
105✔
303
    ++info.current_version;
1,296✔
304
}
1,296✔
305

306
void encryption_note_reader_end(SharedFileInfo& info, const void* reader_id) noexcept
307
{
1,557✔
308
    UniqueLock lock(mapping_mutex);
1,557✔
309
    for (auto j = info.readers.begin(); j != info.readers.end(); ++j)
1,878✔
310
        if (j->reader_ID == reader_id) {
1,506✔
311
            // move last over
642✔
312
            *j = info.readers.back();
1,185✔
313
            info.readers.pop_back();
1,185✔
314
            return;
1,185✔
315
        }
1,185✔
316
}
1,557✔
317

318
void encryption_mark_pages_for_IV_check(EncryptedFileMapping* mapping)
319
{
1,467✔
320
    UniqueLock lock(mapping_mutex);
1,467✔
321
    mapping->mark_pages_for_IV_check();
1,467✔
322
}
1,467✔
323

324
namespace {
325
size_t collect_total_workload() // must be called under lock
326
{
6,168✔
327
    size_t total = 0;
6,168✔
328
    for (auto i = mappings_by_file.begin(); i != mappings_by_file.end(); ++i) {
6,213✔
329
        SharedFileInfo& info = *i->info;
45✔
330
        info.num_decrypted_pages = 0;
45✔
331
        for (auto it = info.mappings.begin(); it != info.mappings.end(); ++it) {
99✔
332
            info.num_decrypted_pages += (*it)->collect_decryption_count();
54✔
333
        }
54✔
334
        total += info.num_decrypted_pages;
45✔
335
    }
45✔
336
    return total;
6,168✔
337
}
6,168✔
338

339
/* Compute the amount of work allowed in an attempt to reclaim pages.
340
 * please refer to EncryptedFileMapping::reclaim_untouched() for more details.
341
 *
342
 * The function starts slowly when the load is 0.5 of target, then turns
343
 * up the volume as the load nears 1.0 - where it sets a work limit of 10%.
344
 * Since the work is expressed (roughly) in terms of pages released, this means
345
 * that about 10 runs has to take place to reclaim all pages possible - though
346
 * if successful the load will rapidly decrease, turning down the work limit.
347
 */
348

349
struct work_limit_desc {
350
    float base;
351
    float effort;
352
};
353
const std::vector<work_limit_desc> control_table = {{0.5f, 0.001f},  {0.75f, 0.002f}, {0.8f, 0.003f},
354
                                                    {0.85f, 0.005f}, {0.9f, 0.01f},   {0.95f, 0.03f},
355
                                                    {1.0f, 0.1f},    {1.5f, 0.2f},    {2.0f, 0.3f}};
356

357
size_t get_work_limit(size_t decrypted_pages, size_t target)
358
{
42✔
359
    if (target == 0)
42✔
360
        target = 1;
×
361
    float load = 1.0f * decrypted_pages / target;
42✔
362
    float akku = 0.0f;
42✔
363
    for (const auto& e : control_table) {
42✔
364
        if (load <= e.base)
42✔
365
            break;
42✔
366
        akku += (load - e.base) * e.effort;
×
367
    }
×
368
    size_t work_limit = size_t(target * akku);
42✔
369
    return work_limit;
42✔
370
}
42✔
371

372
/* Find the oldest version that is still of interest to somebody */
373
uint64_t get_oldest_version(SharedFileInfo& info) // must be called under lock
374
{
×
375
    auto oldest_version = info.current_version;
×
376
    for (const auto& e : info.readers) {
×
377
        if (e.version < oldest_version) {
×
378
            oldest_version = e.version;
×
379
        }
×
380
    }
×
381
    return oldest_version;
×
382
}
×
383

384
// Reclaim pages for ONE file, limited by a given work limit.
385
void reclaim_pages_for_file(SharedFileInfo& info, size_t& work_limit)
386
{
×
387
    uint64_t oldest_version = get_oldest_version(info);
×
388
    if (info.last_scanned_version < oldest_version || info.mappings.empty()) {
×
389
        // locate the mapping matching the progress index. No such mapping may
390
        // exist, and if so, we'll update the index to the next mapping
391
        for (auto& e : info.mappings) {
×
392
            auto start_index = e->get_start_index();
×
393
            if (info.progress_index < start_index) {
×
394
                info.progress_index = start_index;
×
395
            }
×
396
            if (info.progress_index <= e->get_end_index()) {
×
397
                e->reclaim_untouched(info.progress_index, work_limit);
×
398
                if (work_limit == 0)
×
399
                    return;
×
400
            }
×
401
        }
×
402
        // if we get here, all mappings have been considered
403
        info.progress_index = 0;
×
404
        info.last_scanned_version = info.current_version;
×
405
        ++info.current_version;
×
406
    }
×
407
}
×
408

409
// Reclaim pages from all files, limited by a work limit that is derived
410
// from a target for the amount of dirty (decrypted) pages. The target is
411
// set by the governor function.
412
void reclaim_pages()
413
{
6,168✔
414
    size_t load;
6,168✔
415
    util::UniqueFunction<int64_t()> runnable;
6,168✔
416
    {
6,168✔
417
        UniqueLock lock(mapping_mutex);
6,168✔
418
        load = collect_total_workload();
6,168✔
419
        num_decrypted_pages = load;
6,168✔
420
        runnable = governor->current_target_getter(load * page_size());
6,168✔
421
    }
6,168✔
422
    // callback to governor defined function without mutex held
2,886✔
423
    int64_t target = PageReclaimGovernor::no_match;
6,168✔
424
    if (runnable) {
6,168✔
425
        target = runnable();
6,168✔
426
    }
6,168✔
427
    {
6,168✔
428
        UniqueLock lock(mapping_mutex);
6,168✔
429
        reclaimer_workload = 0;
6,168✔
430
        reclaimer_target = size_t(target / page_size());
6,168✔
431
        // Putting the target back into the govenor object will allow the govenor
2,886✔
432
        // to return a getter producing this value again next time it is called
2,886✔
433
        governor->report_target_result(target);
6,168✔
434

2,886✔
435
        if (target == PageReclaimGovernor::no_match) // temporarily disabled by governor returning no_match
6,168✔
436
            return;
×
437

2,886✔
438
        if (mappings_by_file.size() == 0)
6,168✔
439
            return;
6,126✔
440

30✔
441
        size_t work_limit = get_work_limit(load, reclaimer_target);
42✔
442
        reclaimer_workload = work_limit;
42✔
443
        if (file_reclaim_index >= mappings_by_file.size())
42✔
444
            file_reclaim_index = 0;
×
445

30✔
446
        while (work_limit > 0) {
42✔
447
            SharedFileInfo& info = *mappings_by_file[file_reclaim_index].info;
×
448
            reclaim_pages_for_file(info, work_limit);
×
449
            if (work_limit > 0) { // consider next file:
×
450
                ++file_reclaim_index;
×
451
                if (file_reclaim_index >= mappings_by_file.size())
×
452
                    return;
×
453
            }
×
454
        }
×
455
    }
42✔
456
}
42✔
457

458

459
mapping_and_addr* find_mapping_for_addr(void* addr, size_t size)
460
{
1,371,081✔
461
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
1,391,526✔
462
        mapping_and_addr& m = mappings_by_addr[i];
25,638✔
463
        if (m.addr == addr && m.size == size)
25,638✔
464
            return &m;
5,193✔
465
        REALM_ASSERT(m.addr != addr);
20,445✔
466
    }
20,445✔
467

709,824✔
468
    return 0;
1,368,498✔
469
}
1,371,081✔
470
} // anonymous namespace
471

472
SharedFileInfo* get_file_info_for_file(File& file)
473
{
87,108✔
474
    LockGuard lock(mapping_mutex);
87,108✔
475
    File::UniqueID id = file.get_unique_id();
87,108✔
476
    std::vector<mappings_for_file>::iterator it;
87,108✔
477
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
99,567✔
478
        if (it->file_unique_id == id) {
12,726✔
479
            break;
267✔
480
        }
267✔
481
    }
12,726✔
482
    if (it == mappings_by_file.end())
87,108✔
483
        return nullptr;
86,844✔
484
    else
264✔
485
        return it->info.get();
264✔
486
}
87,108✔
487

488

489
namespace {
490
EncryptedFileMapping* add_mapping(void* addr, size_t size, const FileAttributes& file, size_t file_offset)
491
{
3,357✔
492
    size_t fs = to_size_t(File::get_size_static(file.fd));
3,357✔
493
    if (fs > 0 && fs < page_size())
3,357✔
494
        throw DecryptionFailed();
6✔
495

1,611✔
496
    LockGuard lock(mapping_mutex);
3,351✔
497

1,611✔
498
    File::UniqueID fuid = File::get_unique_id(file.fd, file.path);
3,351✔
499

1,611✔
500
    std::vector<mappings_for_file>::iterator it;
3,351✔
501
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
4,788✔
502
        if (it->file_unique_id == fuid) {
2,802✔
503
            break;
1,365✔
504
        }
1,365✔
505
    }
2,802✔
506

1,611✔
507
    // Get the potential memory allocation out of the way so that mappings_by_addr.push_back can't throw
1,611✔
508
    mappings_by_addr.reserve(mappings_by_addr.size() + 1);
3,351✔
509

1,611✔
510
    if (it == mappings_by_file.end()) {
3,351✔
511
        mappings_by_file.reserve(mappings_by_file.size() + 1);
1,986✔
512
        mappings_for_file f;
1,986✔
513
        f.info = std::make_shared<SharedFileInfo>(reinterpret_cast<const uint8_t*>(file.encryption_key));
1,986✔
514
        f.info->fd = File::dup_file_desc(file.fd);
1,986✔
515
        f.file_unique_id = fuid;
1,986✔
516

870✔
517
        mappings_by_file.push_back(f); // can't throw due to reserve() above
1,986✔
518
        it = mappings_by_file.end() - 1;
1,986✔
519
    }
1,986✔
520
    else {
1,365✔
521
        it->info->cryptor.check_key(reinterpret_cast<const uint8_t*>(file.encryption_key));
1,365✔
522
    }
1,365✔
523

1,611✔
524
    try {
3,351✔
525
        mapping_and_addr m;
3,351✔
526
        m.addr = addr;
3,351✔
527
        m.size = size;
3,351✔
528
        m.mapping = std::make_shared<EncryptedFileMapping>(*it->info, file_offset, addr, size, file.access);
3,351✔
529
        mappings_by_addr.push_back(m); // can't throw due to reserve() above
3,351✔
530
        return m.mapping.get();
3,351✔
531
    }
3,351✔
532
    catch (...) {
×
533
        if (it->info->mappings.empty()) {
×
534
            FileDesc fd_to_close = it->info->fd;
×
535
            mappings_by_file.erase(it);
×
536
            File::close_static(fd_to_close); // Throws
×
537
        }
×
538
        throw;
×
539
    }
×
540
}
3,351✔
541

542
void remove_mapping(void* addr, size_t size)
543
{
3,345✔
544
    size = round_up_to_page_size(size);
3,345✔
545
    LockGuard lock(mapping_mutex);
3,345✔
546
    mapping_and_addr* m = find_mapping_for_addr(addr, size);
3,345✔
547
    if (!m)
3,345✔
548
        return;
×
549

1,608✔
550
    mappings_by_addr.erase(mappings_by_addr.begin() + (m - &mappings_by_addr[0]));
3,345✔
551

1,608✔
552
    for (std::vector<mappings_for_file>::iterator it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
6,189✔
553
        if (it->info->mappings.empty()) {
4,830✔
554
            FileDesc fd_to_close = it->info->fd;
1,986✔
555
            mappings_by_file.erase(it);
1,986✔
556
            File::close_static(fd_to_close); // Throws
1,986✔
557
            break;
1,986✔
558
        }
1,986✔
559
    }
4,830✔
560
}
3,345✔
561
} // anonymous namespace
562

563
void* mmap(const FileAttributes& file, size_t size, size_t offset, EncryptedFileMapping*& mapping)
564
{
1,318,242✔
565
    _impl::SimulatedFailure::trigger_mmap(size);
1,318,242✔
566
    if (file.encryption_key) {
1,318,242✔
567
        size = round_up_to_page_size(size);
3,084✔
568
        void* addr = mmap_anon(size);
3,084✔
569
        mapping = add_mapping(addr, size, file, offset);
3,084✔
570
        return addr;
3,084✔
571
    }
3,084✔
572
    else {
1,315,158✔
573
        mapping = nullptr;
1,315,158✔
574
        return mmap(file, size, offset);
1,315,158✔
575
    }
1,315,158✔
576
}
1,318,242✔
577

578

579
EncryptedFileMapping* reserve_mapping(void* addr, const FileAttributes& file, size_t offset)
580
{
273✔
581
    return add_mapping(addr, 0, file, offset);
273✔
582
}
273✔
583

584
void extend_encrypted_mapping(EncryptedFileMapping* mapping, void* addr, size_t offset, size_t old_size,
585
                              size_t new_size)
586
{
312✔
587
    LockGuard lock(mapping_mutex);
312✔
588
    auto m = find_mapping_for_addr(addr, old_size);
312✔
589
    REALM_ASSERT(m);
312✔
590
    m->size = new_size;
312✔
591
    mapping->extend_to(offset, new_size);
312✔
592
}
312✔
593

594
void remove_encrypted_mapping(void* addr, size_t size)
595
{
3,345✔
596
    remove_mapping(addr, size);
3,345✔
597
}
3,345✔
598

599
void* mmap_reserve(const FileAttributes& file, size_t reservation_size, size_t offset_in_file,
600
                   EncryptedFileMapping*& mapping)
601
{
×
602
    auto addr = mmap_reserve(file.fd, reservation_size, offset_in_file);
×
603
    if (file.encryption_key) {
×
604
        REALM_ASSERT(reservation_size == round_up_to_page_size(reservation_size));
×
605
        // we create a mapping for the entire reserved area. This causes full initialization of some fairly
606
        // large std::vectors, which it would be nice to avoid. This is left as a future optimization.
607
        mapping = add_mapping(addr, reservation_size, file, offset_in_file);
×
608
    }
×
609
    else {
×
610
        mapping = nullptr;
×
611
    }
×
612
    return addr;
×
613
}
×
614

615
void* mmap_fixed(FileDesc fd, void* address_request, size_t size, File::AccessMode access, size_t offset,
616
                 const char* enc_key, EncryptedFileMapping* encrypted_mapping)
617
{
×
618
    REALM_ASSERT((enc_key == nullptr) ==
×
619
                 (encrypted_mapping == nullptr)); // Mapping must already have been set if encryption is used
×
620
    if (encrypted_mapping) {
×
621
// Since the encryption layer must be able to WRITE into the memory area,
622
// we have to map it read/write regardless of the request.
623
// FIXME: Make this work for windows!
624
#ifdef _WIN32
625
        return nullptr;
626
#else
627
        return ::mmap(address_request, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
×
628
#endif
×
629
    }
×
630
    else {
×
631
        return mmap_fixed(fd, address_request, size, access, offset, enc_key);
×
632
    }
×
633
}
×
634

635

636
#endif // REALM_ENABLE_ENCRYPTION
637

638
void* mmap_anon(size_t size)
639
{
3,084✔
640
#ifdef _WIN32
641
    HANDLE hMapFile;
642
    LPCTSTR pBuf;
643

644
    ULARGE_INTEGER s;
645
    s.QuadPart = size;
646

647
    hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, s.HighPart, s.LowPart, nullptr);
648
    if (hMapFile == NULL) {
649
        throw std::system_error(GetLastError(), std::system_category(), "CreateFileMapping() failed");
650
    }
651

652
    pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, size);
653
    if (pBuf == nullptr) {
654
        throw std::system_error(GetLastError(), std::system_category(), "MapViewOfFile() failed");
655
    }
656

657
    CloseHandle(hMapFile);
658
    return (void*)pBuf;
659
#else
660
    void* addr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
3,084✔
661
    if (addr == MAP_FAILED) {
3,084✔
662
        int err = errno; // Eliminate any risk of clobbering
×
663
        if (is_mmap_memory_error(err)) {
×
664
            throw AddressSpaceExhausted(get_errno_msg("mmap() failed: ", err) + " size: " + util::to_string(size));
×
665
        }
×
666
        throw std::system_error(err, std::system_category(),
×
667
                                std::string("mmap() failed (size: ") + util::to_string(size) + ", offset is 0)");
×
668
    }
×
669
    return addr;
3,084✔
670
#endif
3,084✔
671
}
3,084✔
672

673
void* mmap_fixed(FileDesc fd, void* address_request, size_t size, File::AccessMode access, size_t offset,
674
                 const char* enc_key)
675
{
105,459✔
676
    _impl::SimulatedFailure::trigger_mmap(size);
105,459✔
677
    static_cast<void>(enc_key); // FIXME: Consider removing this parameter
105,459✔
678
#ifdef _WIN32
679
    REALM_ASSERT(false);
680
    return nullptr; // silence warning
681
#else
682
    auto prot = PROT_READ;
105,459✔
683
    if (access == File::access_ReadWrite)
105,459✔
684
        prot |= PROT_WRITE;
×
685
    auto addr = ::mmap(address_request, size, prot, MAP_SHARED | MAP_FIXED, fd, offset);
105,459✔
686
    if (addr != MAP_FAILED && addr != address_request) {
105,459✔
687
        throw std::runtime_error(get_errno_msg("mmap() failed: ", errno) +
×
688
                                 ", when mapping an already reserved memory area");
×
689
    }
×
690
    return addr;
105,459✔
691
#endif
105,459✔
692
}
105,459✔
693

694
void* mmap_reserve(FileDesc fd, size_t reservation_size, size_t offset_in_file)
695
{
×
696
    // The other mmap operations take an fd as a parameter, so we do too.
697
    // We're not using it for anything currently, but this may change.
698
    // Similarly for offset_in_file.
699
    static_cast<void>(fd);
×
700
    static_cast<void>(offset_in_file);
×
701
#ifdef _WIN32
702
    REALM_ASSERT(false); // unsupported on windows
703
    return nullptr;
704
#else
705
    auto addr = ::mmap(0, reservation_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
×
706
    if (addr == MAP_FAILED) {
×
707
        throw std::runtime_error(get_errno_msg("mmap() failed: ", errno));
×
708
    }
×
709
    return addr;
×
710
#endif
×
711
}
×
712

713

714
void* mmap(const FileAttributes& file, size_t size, size_t offset)
715
{
1,315,140✔
716
    _impl::SimulatedFailure::trigger_mmap(size);
1,315,140✔
717
#if REALM_ENABLE_ENCRYPTION
1,315,140✔
718
    if (file.encryption_key) {
1,315,140✔
719
        size = round_up_to_page_size(size);
×
720
        void* addr = mmap_anon(size);
×
721
        add_mapping(addr, size, file, offset);
×
722
        return addr;
×
723
    }
×
724
    else
1,315,140✔
725
#else
726
    REALM_ASSERT(!file.encryption_key);
727
#endif
728
    {
1,315,140✔
729

664,110✔
730
#ifndef _WIN32
1,315,140✔
731
        int prot = PROT_READ;
1,315,140✔
732
        switch (file.access) {
1,315,140✔
733
            case File::access_ReadWrite:
1,203,471✔
734
                prot |= PROT_WRITE;
1,203,471✔
735
                break;
1,203,471✔
736
            case File::access_ReadOnly:
111,675✔
737
                break;
111,675✔
738
        }
1,315,104✔
739

664,083✔
740
        void* addr = ::mmap(nullptr, size, prot, MAP_SHARED, file.fd, offset);
1,315,104✔
741
        if (addr != MAP_FAILED)
1,315,104✔
742
            return addr;
1,315,416✔
743

2,147,483,647✔
744
        int err = errno; // Eliminate any risk of clobbering
4,294,967,294✔
745
        if (is_mmap_memory_error(err)) {
4,294,967,294✔
746
            throw AddressSpaceExhausted(get_errno_msg("mmap() failed: ", err) + " size: " + util::to_string(size) +
×
747
                                        " offset: " + util::to_string(offset));
×
748
        }
×
749

2,147,483,647✔
750
        throw SystemError(err, std::string("mmap() failed (size: ") + util::to_string(size) +
4,294,967,294✔
751
                                   ", offset: " + util::to_string(offset));
4,294,967,294✔
752

2,147,483,647✔
753
#else
754
        // FIXME: Is there anything that we must do on Windows to honor map_NoSync?
755

756
        DWORD protect = PAGE_READONLY;
757
        DWORD desired_access = FILE_MAP_READ;
758
        switch (file.access) {
759
            case File::access_ReadOnly:
760
                break;
761
            case File::access_ReadWrite:
762
                protect = PAGE_READWRITE;
763
                desired_access = FILE_MAP_WRITE;
764
                break;
765
        }
766
        LARGE_INTEGER large_int;
767
        if (int_cast_with_overflow_detect(offset + size, large_int.QuadPart))
768
            throw std::runtime_error("Map size is too large");
769
        HANDLE map_handle = CreateFileMappingFromApp(file.fd, 0, protect, offset + size, nullptr);
770
        if (!map_handle)
771
            throw AddressSpaceExhausted(get_errno_msg("CreateFileMapping() failed: ", GetLastError()) +
772
                                        " size: " + util::to_string(size) + " offset: " + util::to_string(offset));
773

774
        if (int_cast_with_overflow_detect(offset, large_int.QuadPart))
775
            throw RuntimeError(ErrorCodes::RangeError, "Map offset is too large");
776

777
        SIZE_T _size = size;
778
        void* addr = MapViewOfFileFromApp(map_handle, desired_access, offset, _size);
779
        BOOL r = CloseHandle(map_handle);
780
        REALM_ASSERT_RELEASE(r);
781
        if (!addr)
782
            throw AddressSpaceExhausted(get_errno_msg("MapViewOfFileFromApp() failed: ", GetLastError()) +
783
                                        " size: " + util::to_string(_size) + " offset: " + util::to_string(offset));
784

785
        return addr;
786
#endif
787
    }
4,294,967,294✔
788
}
1,315,140✔
789

790
void munmap(void* addr, size_t size)
791
{
×
792
#if REALM_ENABLE_ENCRYPTION
×
793
    remove_mapping(addr, size);
×
794
#endif
×
795

796
#ifdef _WIN32
797
    if (!UnmapViewOfFile(addr))
798
        throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
799

800
#else
801
    if (::munmap(addr, size) != 0) {
×
802
        int err = errno;
×
803
        throw std::system_error(err, std::system_category(), "munmap() failed");
×
804
    }
×
805
#endif
×
806
}
×
807

808
void* mremap(const FileAttributes& file, size_t file_offset, void* old_addr, size_t old_size, size_t new_size)
809
{
×
810
#if REALM_ENABLE_ENCRYPTION
×
811
    if (file.encryption_key) {
×
812
        LockGuard lock(mapping_mutex);
×
813
        size_t rounded_old_size = round_up_to_page_size(old_size);
×
814
        if (mapping_and_addr* m = find_mapping_for_addr(old_addr, rounded_old_size)) {
×
815
            size_t rounded_new_size = round_up_to_page_size(new_size);
×
816
            if (rounded_old_size == rounded_new_size)
×
817
                return old_addr;
×
818

819
            void* new_addr = mmap_anon(rounded_new_size);
×
820
            m->mapping->set(new_addr, rounded_new_size, file_offset);
×
821
            m->addr = new_addr;
×
822
            m->size = rounded_new_size;
×
823
#ifdef _WIN32
824
            if (!UnmapViewOfFile(old_addr))
825
                throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
826
#else
827
            if (::munmap(old_addr, rounded_old_size)) {
×
828
                int err = errno;
×
829
                throw std::system_error(err, std::system_category(), "munmap() failed");
×
830
            }
×
831
#endif
×
832
            return new_addr;
×
833
        }
×
834
        // If we are using encryption, we must have used mmap and the mapping
835
        // must have been added to the cache therefore find_mapping_for_addr()
836
        // will succeed. Otherwise we would continue to mmap it below without
837
        // the encryption key which is an error.
838
        REALM_UNREACHABLE();
839
    }
×
840
#endif
×
841

842
#ifdef _GNU_SOURCE
843
    {
844
        void* new_addr = ::mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE);
845
        if (new_addr != MAP_FAILED)
846
            return new_addr;
847
        int err = errno; // Eliminate any risk of clobbering
848
        // Do not throw here if mremap is declared as "not supported" by the
849
        // platform Eg. When compiling with GNU libc on OSX, iOS.
850
        // In this case fall through to no-mremap case below.
851
        if (err != ENOTSUP && err != ENOSYS) {
852
            if (is_mmap_memory_error(err)) {
853
                throw AddressSpaceExhausted(get_errno_msg("mremap() failed: ", err) + " old size: " +
854
                                            util::to_string(old_size) + " new size: " + util::to_string(new_size));
855
            }
856
            throw std::system_error(err, std::system_category(),
857
                                    std::string("_gnu_src mmap() failed (") + "old_size: " +
858
                                        util::to_string(old_size) + ", new_size: " + util::to_string(new_size) + ")");
859
        }
860
    }
861
#endif
862

863
    void* new_addr = mmap(file, new_size, file_offset);
×
864

865
#ifdef _WIN32
866
    if (!UnmapViewOfFile(old_addr))
867
        throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
868
#else
869
    if (::munmap(old_addr, old_size) != 0) {
×
870
        int err = errno;
×
871
        throw std::system_error(err, std::system_category(), "munmap() failed");
×
872
    }
×
873
#endif
×
874

875
    return new_addr;
×
876
}
×
877

878
void msync(FileDesc fd, void* addr, size_t size)
879
{
1,367,085✔
880
#if REALM_ENABLE_ENCRYPTION
1,367,085✔
881
    {
1,367,085✔
882
        // first check the encrypted mappings
707,823✔
883
        LockGuard lock(mapping_mutex);
1,367,085✔
884
        if (mapping_and_addr* m = find_mapping_for_addr(addr, round_up_to_page_size(size))) {
1,367,085✔
885
            m->mapping->flush();
1,536✔
886
            m->mapping->sync();
1,536✔
887
            return;
1,536✔
888
        }
1,536✔
889
    }
1,365,549✔
890
#endif
1,365,549✔
891

706,998✔
892
    // not an encrypted mapping
706,998✔
893

706,998✔
894
    // FIXME: on iOS/OSX fsync may not be enough to ensure crash safety.
706,998✔
895
    // Consider adding fcntl(F_FULLFSYNC). This most likely also applies to msync.
706,998✔
896
    //
706,998✔
897
    // See description of fsync on iOS here:
706,998✔
898
    // https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html
706,998✔
899
    //
706,998✔
900
    // See also
706,998✔
901
    // https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdPersistentStores.html
706,998✔
902
    // for a discussion of this related to core data.
706,998✔
903

706,998✔
904
#ifdef _WIN32
905
    // FlushViewOfFile() is asynchronous and won't flush metadata (file size, etc)
906
    if (!FlushViewOfFile(addr, size)) {
907
        throw std::system_error(GetLastError(), std::system_category(), "FlushViewOfFile() failed");
908
    }
909
    // Block until data and metadata is written physically to the media
910
    if (!FlushFileBuffers(fd)) {
911
        throw std::system_error(GetLastError(), std::system_category(), "FlushFileBuffers() failed");
912
    }
913
    return;
914
#else
915
    static_cast<void>(fd);
1,365,549✔
916
    int retries_left = 1000;
1,365,549✔
917
    while (::msync(addr, size, MS_SYNC) != 0) {
1,365,549✔
918
        int err = errno; // Eliminate any risk of clobbering
×
919
        if (--retries_left < 0)
×
920
            throw std::system_error(err, std::system_category(), "msync() retries exhausted");
×
921
        if (err != EINTR)
×
922
            throw std::system_error(err, std::system_category(), "msync() failed");
×
923
    }
×
924
#endif
1,365,549✔
925
}
1,365,549✔
926
} // namespace util
927
} // namespace realm
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