1 /*
2  * Copyright (C) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "ringtone_rdb_transaction.h"
17 
18 #include "ringtone_log.h"
19 
20 namespace OHOS::Media {
21 using namespace std;
22 constexpr int32_t E_HAS_DB_ERROR = -222;
23 constexpr int32_t E_OK = 0;
24 constexpr int RDB_TRANSACTION_WAIT_MS = 1000;
25 std::mutex RingtoneRdbTransaction::transactionMutex_;
26 std::condition_variable RingtoneRdbTransaction::transactionCV_;
27 std::atomic<bool> RingtoneRdbTransaction::isInTransaction_(false);
28 constexpr int32_t MAX_TRY_TIMES = 30;
29 constexpr int32_t TRANSACTION_WAIT_INTERVAL = 50; // in milliseconds.
30 
RingtoneRdbTransaction(const shared_ptr<OHOS::NativeRdb::RdbStore> & rdbStore)31 RingtoneRdbTransaction::RingtoneRdbTransaction(
32     const shared_ptr<OHOS::NativeRdb::RdbStore> &rdbStore) : rdbStore_(rdbStore) {}
33 
~RingtoneRdbTransaction()34 RingtoneRdbTransaction::~RingtoneRdbTransaction()
35 {
36     if (isStart && !isFinish) {
37         TransactionRollback();
38     }
39 }
40 
Start()41 int32_t RingtoneRdbTransaction::Start()
42 {
43     if (isStart || isFinish) {
44         return 0;
45     }
46     int32_t errCode = BeginTransaction();
47     if (errCode == 0) {
48         isStart = true;
49     }
50     return errCode;
51 }
52 
Finish()53 void RingtoneRdbTransaction::Finish()
54 {
55     if (!isStart) {
56         return;
57     }
58     if (!isFinish) {
59         int32_t ret = TransactionCommit();
60         if (ret == 0) {
61             isFinish = true;
62         }
63     }
64 }
65 
BeginTransaction()66 int32_t RingtoneRdbTransaction::BeginTransaction()
67 {
68     if (rdbStore_ == nullptr) {
69         RINGTONE_ERR_LOG("Pointer rdbStore_ is nullptr. Maybe it didn't init successfully.");
70         return E_HAS_DB_ERROR;
71     }
72     RINGTONE_INFO_LOG("Start transaction");
73 
74     unique_lock<mutex> cvLock(transactionMutex_);
75     if (isInTransaction_.load()) {
76         transactionCV_.wait_for(cvLock, chrono::milliseconds(RDB_TRANSACTION_WAIT_MS),
77             [this] () { return !(this->isInTransaction_.load()); });
78     }
79 
80     int curTryTime = 0;
81     while (curTryTime < MAX_TRY_TIMES) {
82         if (rdbStore_->IsInTransaction()) {
83             this_thread::sleep_for(chrono::milliseconds(TRANSACTION_WAIT_INTERVAL));
84             if (isInTransaction_.load() || rdbStore_->IsInTransaction()) {
85                 curTryTime++;
86                 RINGTONE_INFO_LOG("RdbStore is in transaction, try %{public}d times...", curTryTime);
87                 continue;
88             }
89         }
90 
91         int32_t errCode = rdbStore_->BeginTransaction();
92         if (errCode == SQLITE3_DATABASE_LOCKER) {
93             curTryTime++;
94             RINGTONE_ERR_LOG("Sqlite database file is locked! try %{public}d times...", curTryTime);
95             continue;
96         } else if (errCode != NativeRdb::E_OK) {
97             RINGTONE_ERR_LOG("Start Transaction failed, errCode=%{public}d", errCode);
98             isInTransaction_.store(false);
99             transactionCV_.notify_one();
100             return E_HAS_DB_ERROR;
101         } else {
102             isInTransaction_.store(true);
103             return E_OK;
104         }
105     }
106 
107     RINGTONE_ERR_LOG("RdbStore is still in transaction after try %{public}d times, abort.", MAX_TRY_TIMES);
108     return E_HAS_DB_ERROR;
109 }
110 
TransactionCommit()111 int32_t RingtoneRdbTransaction::TransactionCommit()
112 {
113     if (rdbStore_ == nullptr) {
114         return E_HAS_DB_ERROR;
115     }
116     RINGTONE_INFO_LOG("Try commit transaction");
117 
118     if (!(isInTransaction_.load()) || !(rdbStore_->IsInTransaction())) {
119         RINGTONE_ERR_LOG("no transaction now");
120         return E_HAS_DB_ERROR;
121     }
122 
123     int32_t errCode = rdbStore_->Commit();
124     isInTransaction_.store(false);
125     transactionCV_.notify_all();
126     if (errCode != NativeRdb::E_OK) {
127         RINGTONE_ERR_LOG("commit failed, errCode=%{public}d", errCode);
128         return E_HAS_DB_ERROR;
129     }
130 
131     return E_OK;
132 }
133 
TransactionRollback()134 int32_t RingtoneRdbTransaction::TransactionRollback()
135 {
136     if (rdbStore_ == nullptr) {
137         return E_HAS_DB_ERROR;
138     }
139     RINGTONE_INFO_LOG("Try rollback transaction");
140 
141     if (!(isInTransaction_.load()) || !(rdbStore_->IsInTransaction())) {
142         RINGTONE_ERR_LOG("no transaction now");
143         return E_HAS_DB_ERROR;
144     }
145 
146     int32_t errCode = rdbStore_->RollBack();
147     isInTransaction_.store(false);
148     transactionCV_.notify_all();
149     if (errCode != NativeRdb::E_OK) {
150         RINGTONE_ERR_LOG("rollback failed, errCode=%{public}d", errCode);
151         return E_HAS_DB_ERROR;
152     }
153 
154     return E_OK;
155 }
156 } // namespace OHOS::Media
157