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

realm / realm-core / thomas.goyne_118

04 Nov 2023 12:29AM UTC coverage: 91.692% (+0.03%) from 91.66%
thomas.goyne_118

push

Evergreen

web-flow
Use a single write transaction for DiscardLocal client resets on FLX realms (#7110)

Updating the subscription store in a separate write transaction from the
recovery means that we temporarily commit an invalid state. If the application
crashes between committing the client reset diff and updating the subscription
store, the next launch of the application would try to use the now-invalid
pending subscriptions that should have been discarded.

92164 of 168854 branches covered (0.0%)

151 of 151 new or added lines in 7 files covered. (100.0%)

402 existing lines in 19 files now uncovered.

231057 of 251992 relevant lines covered (91.69%)

6568819.91 hits per line

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

90.32
/src/realm/sync/noinst/client_reset_operation.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2021 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/transaction.hpp>
20
#include <realm/sync/history.hpp>
21
#include <realm/sync/noinst/client_history_impl.hpp>
22
#include <realm/sync/noinst/client_reset.hpp>
23
#include <realm/sync/noinst/client_reset_operation.hpp>
24
#include <realm/util/scope_exit.hpp>
25

26
namespace realm::_impl {
27

28
namespace {
29

30
constexpr static std::string_view c_fresh_suffix(".fresh");
31

32
} // namespace
33

34
ClientResetOperation::ClientResetOperation(util::Logger& logger, DBRef db, DBRef db_fresh, ClientResyncMode mode,
35
                                           CallbackBeforeType notify_before, CallbackAfterType notify_after,
36
                                           bool recovery_is_allowed)
3,297,236✔
37
    : m_logger{logger}
3,297,236✔
38
    , m_db{db}
3,297,236✔
39
    , m_db_fresh(std::move(db_fresh))
3,297,236✔
40
    , m_mode(mode)
3,297,236✔
41
    , m_notify_before(std::move(notify_before))
3,297,236✔
42
    , m_notify_after(std::move(notify_after))
3,297,236✔
43
    , m_recovery_is_allowed(recovery_is_allowed)
44
{
45
    REALM_ASSERT(m_db);
3,346✔
46
    REALM_ASSERT_RELEASE(m_mode != ClientResyncMode::Manual);
3,346✔
47
    m_logger.debug("Create ClientResetOperation, realm_path = %1, mode = %2, recovery_allowed = %3", m_db->get_path(),
3,346✔
48
                   m_mode, m_recovery_is_allowed);
3,346✔
UNCOV
49
}
×
UNCOV
50

×
51
std::string ClientResetOperation::get_fresh_path_for(const std::string& path)
3,346✔
52
{
3,346✔
53
    const size_t suffix_len = c_fresh_suffix.size();
54
    REALM_ASSERT(path.length());
55
    REALM_ASSERT_DEBUG_EX(
56
        path.size() < suffix_len || path.substr(path.size() - suffix_len, suffix_len) != c_fresh_suffix, path);
57
    return path + c_fresh_suffix.data();
58
}
400✔
59

400✔
60
bool ClientResetOperation::is_fresh_path(const std::string& path)
400✔
61
{
400✔
62
    const size_t suffix_len = c_fresh_suffix.size();
200✔
63
    REALM_ASSERT(path.length());
400✔
64
    if (path.size() < suffix_len) {
400✔
65
        return false;
400✔
66
    }
400✔
67
    return path.substr(path.size() - suffix_len, suffix_len) == c_fresh_suffix;
400✔
68
}
400✔
69

400✔
70
bool ClientResetOperation::finalize(sync::SaltedFileIdent salted_file_ident, sync::SubscriptionStore* sub_store,
200✔
UNCOV
71
                                    util::UniqueFunction<void(int64_t)> on_flx_version_complete)
×
UNCOV
72
{
×
UNCOV
73
    m_salted_file_ident = salted_file_ident;
×
74
    // only do the reset if there is data to reset
UNCOV
75
    // if there is nothing in this Realm, then there is nothing to reset and
×
76
    // sync should be able to continue as normal
400✔
77
    auto latest_version = m_db->get_version_id_of_latest_snapshot();
200✔
78

200✔
79
    bool local_realm_exists = latest_version.version != 0;
200✔
80
    m_logger.debug("ClientResetOperation::finalize, realm_path = %1, local_realm_exists = %2, mode = %3",
200✔
81
                   m_db->get_path(), local_realm_exists, m_mode);
400✔
82
    if (!local_realm_exists) {
400✔
83
        return false;
400✔
84
    }
4✔
85

4✔
86
    REALM_ASSERT_EX(m_db_fresh, m_db->get_path(), m_mode);
4✔
87

198✔
88
    client_reset::LocalVersionIDs local_version_ids;
396✔
89
    auto always_try_clean_up = util::make_scope_exit([&]() noexcept {
198✔
90
        clean_up_state();
198✔
91
    });
396✔
92

396✔
93
    VersionID frozen_before_state_version = m_notify_before ? m_notify_before() : latest_version;
244✔
94

244✔
95
    // If m_notify_after is set, pin the previous state to keep it around.
396✔
96
    TransactionRef previous_state;
396✔
97
    if (m_notify_after) {
396✔
98
        previous_state = m_db->start_frozen(frozen_before_state_version);
396✔
99
    }
198✔
100
    bool did_recover_out = false;
396✔
101
    local_version_ids = client_reset::perform_client_reset_diff(
188✔
102
        *m_db, *m_db_fresh, m_salted_file_ident, m_logger, m_mode, m_recovery_is_allowed, &did_recover_out, sub_store,
188✔
103
        std::move(on_flx_version_complete)); // throws
198✔
104

396✔
105
    if (m_notify_after) {
396✔
106
        m_notify_after(previous_state->get_version_of_current_transaction(), did_recover_out);
107
    }
108

109
    m_client_reset_old_version = local_version_ids.old_version;
110
    m_client_reset_new_version = local_version_ids.new_version;
111

112
    return true;
113
}
114

115
void ClientResetOperation::clean_up_state() noexcept
116
{
117
    if (m_db_fresh) {
118
        std::string path_to_clean = m_db_fresh->get_path();
119
        try {
120
            // In order to obtain the lock and delete the realm, we first have to close
121
            // the Realm. This requires that we are the only remaining ref holder, and
122
            // this is expected. Releasing the last ref should release the hold on the
123
            // lock file and allow us to clean up.
124
            long use_count = m_db_fresh.use_count();
125
            REALM_ASSERT_DEBUG_EX(use_count == 1, use_count, path_to_clean);
126
            m_db_fresh.reset();
127
            // clean up the fresh Realm
128
            // we don't mind leaving the fresh lock file around because trying to delete it
129
            // here could cause a race if there are multiple resets ongoing
130
            bool did_lock = DB::call_with_lock(path_to_clean, [&](const std::string& path) {
131
                constexpr bool delete_lockfile = false;
132
                DB::delete_files(path, nullptr, delete_lockfile);
133
            });
134
            if (!did_lock) {
135
                m_logger.warn("In ClientResetOperation::finalize, the fresh copy '%1' could not be cleaned up. "
136
                              "There were %2 refs remaining.",
137
                              path_to_clean, use_count);
138
            }
139
        }
140
        catch (const std::exception& err) {
141
            m_logger.warn("In ClientResetOperation::finalize, the fresh copy '%1' could not be cleaned up due to "
142
                          "an exception: '%2'",
143
                          path_to_clean, err.what());
144
            // ignored, this is just a best effort
145
        }
146
    }
147
}
148

149
} // namespace realm::_impl
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