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

realm / realm-core / 1818

04 Nov 2023 12:29AM UTC coverage: 91.677% (+0.02%) from 91.66%
1818

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.

92136 of 168844 branches covered (0.0%)

141 of 146 new or added lines in 7 files covered. (96.58%)

65 existing lines in 12 files now uncovered.

230767 of 251718 relevant lines covered (91.68%)

6408028.78 hits per line

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

87.23
/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)
37
    : m_logger{logger}
38
    , m_db{db}
39
    , m_db_fresh(std::move(db_fresh))
40
    , m_mode(mode)
41
    , m_notify_before(std::move(notify_before))
42
    , m_notify_after(std::move(notify_after))
43
    , m_recovery_is_allowed(recovery_is_allowed)
44
{
336✔
45
    REALM_ASSERT(m_db);
336✔
46
    REALM_ASSERT_RELEASE(m_mode != ClientResyncMode::Manual);
336✔
47
    m_logger.debug("Create ClientResetOperation, realm_path = %1, mode = %2, recovery_allowed = %3", m_db->get_path(),
336✔
48
                   m_mode, m_recovery_is_allowed);
336✔
49
}
336✔
50

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

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

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

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

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

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

168✔
93
    VersionID frozen_before_state_version = m_notify_before ? m_notify_before() : latest_version;
296✔
94

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

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

168✔
109
    m_client_reset_old_version = local_version_ids.old_version;
336✔
110
    m_client_reset_new_version = local_version_ids.new_version;
336✔
111

168✔
112
    return true;
336✔
113
}
336✔
114

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