Skip to content

Commit a0dc81e

Browse files
Fix the data race in NamedMutex class (#1357)
The race appears when the condition variable spins on the predicate, while the notify is not protected with a mutex. Additionally set the cancellation method to notify, which enables the faster tasks cancellation. Relates-To: OAM-1784 Signed-off-by: Mykhailo Kuchma <ext-mykhailo.kuchma@here.com>
1 parent 9e2142d commit a0dc81e

File tree

4 files changed

+142
-5
lines changed

4 files changed

+142
-5
lines changed

olp-cpp-sdk-dataservice-read/src/repositories/NamedMutex.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ boost::optional<client::ApiError> NamedMutexStorage::GetError(
133133
}
134134

135135
NamedMutex::NamedMutex(NamedMutexStorage& storage, const std::string& name,
136-
const client::CancellationContext& context)
136+
client::CancellationContext& context)
137137
: storage_{storage},
138138
context_{context},
139139
is_locked_{false},
@@ -145,10 +145,15 @@ NamedMutex::NamedMutex(NamedMutexStorage& storage, const std::string& name,
145145
NamedMutex::~NamedMutex() { storage_.ReleaseLock(name_); }
146146

147147
void NamedMutex::lock() {
148-
if (!context_.IsCancelled() && !try_lock()) {
148+
const bool valid = context_.ExecuteOrCancelled(
149+
[&]() { return client::CancellationToken([&]() { Notify(); }); });
150+
151+
if (valid) {
149152
std::unique_lock<std::mutex> unique_lock{lock_mutex_};
150153
lock_condition_.wait(unique_lock,
151154
[&] { return context_.IsCancelled() || try_lock(); });
155+
// Reset the cancel token
156+
context_.ExecuteOrCancelled([&]() { return client::CancellationToken(); });
152157
}
153158
}
154159

@@ -158,7 +163,7 @@ void NamedMutex::unlock() {
158163
if (is_locked_) {
159164
mutex_.unlock();
160165
is_locked_ = false;
161-
lock_condition_.notify_all();
166+
Notify();
162167
}
163168
}
164169

@@ -170,6 +175,11 @@ boost::optional<client::ApiError> NamedMutex::GetError() {
170175
return storage_.GetError(name_);
171176
}
172177

178+
void NamedMutex::Notify() {
179+
std::unique_lock<std::mutex> unique_lock{lock_mutex_};
180+
lock_condition_.notify_all();
181+
}
182+
173183
} // namespace repository
174184
} // namespace read
175185
} // namespace dataservice

olp-cpp-sdk-dataservice-read/src/repositories/NamedMutex.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class NamedMutexStorage {
8080
class NamedMutex final {
8181
public:
8282
NamedMutex(NamedMutexStorage& storage, const std::string& name,
83-
const client::CancellationContext& context);
83+
client::CancellationContext& context);
8484

8585
NamedMutex(const NamedMutex&) = delete;
8686
NamedMutex(NamedMutex&&) = delete;
@@ -111,8 +111,11 @@ class NamedMutex final {
111111
boost::optional<client::ApiError> GetError();
112112

113113
private:
114+
/// Notify waiting threads method.
115+
void Notify();
116+
114117
NamedMutexStorage& storage_;
115-
const client::CancellationContext& context_;
118+
client::CancellationContext& context_;
116119
bool is_locked_;
117120
std::string name_;
118121
std::mutex& mutex_;

olp-cpp-sdk-dataservice-read/tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ set(OLP_SDK_DATASERVICE_READ_TEST_SOURCES
2424
DataRepositoryTest.cpp
2525
JsonResultParserTest.cpp
2626
MetadataApiTest.cpp
27+
NamedMutexTest.cpp
2728
ParserTest.cpp
2829
PartitionsCacheRepositoryTest.cpp
2930
PartitionsRepositoryTest.cpp
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (C) 2022 HERE Europe B.V.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
#include <gtest/gtest.h>
21+
#include <atomic>
22+
#include <thread>
23+
24+
#include "repositories/NamedMutex.h"
25+
26+
namespace {
27+
28+
namespace client = olp::client;
29+
namespace repository = olp::dataservice::read::repository;
30+
31+
TEST(NamedMutexTest, Lock) {
32+
repository::NamedMutexStorage storage;
33+
34+
client::CancellationContext context;
35+
36+
repository::NamedMutex main_mutex(storage, "mutex", context);
37+
main_mutex.lock();
38+
39+
std::atomic_int counter{0};
40+
41+
std::thread thread_1([&]() {
42+
repository::NamedMutex mutex(storage, "mutex", context);
43+
mutex.lock();
44+
counter.fetch_add(1);
45+
mutex.unlock();
46+
});
47+
48+
std::thread thread_2([&]() {
49+
repository::NamedMutex mutex(storage, "mutex", context);
50+
mutex.lock();
51+
counter.fetch_add(1);
52+
mutex.unlock();
53+
});
54+
55+
EXPECT_EQ(counter.load(), 0);
56+
57+
main_mutex.unlock();
58+
59+
thread_1.join();
60+
thread_2.join();
61+
62+
EXPECT_EQ(counter.load(), 2);
63+
}
64+
65+
TEST(NamedMutexTest, Cancel) {
66+
repository::NamedMutexStorage storage;
67+
68+
client::CancellationContext main_context;
69+
client::CancellationContext thread_1_context;
70+
client::CancellationContext thread_2_context;
71+
72+
repository::NamedMutex main_mutex(storage, "mutex", main_context);
73+
main_mutex.lock();
74+
75+
std::atomic_int counter{0};
76+
77+
std::thread thread_1([&]() {
78+
repository::NamedMutex mutex(storage, "mutex", thread_1_context);
79+
mutex.lock();
80+
if (thread_1_context.IsCancelled()) {
81+
counter.fetch_add(1);
82+
}
83+
mutex.unlock();
84+
});
85+
86+
std::thread thread_2([&]() {
87+
repository::NamedMutex mutex(storage, "mutex", thread_2_context);
88+
mutex.lock();
89+
if (thread_2_context.IsCancelled()) {
90+
counter.fetch_add(1);
91+
}
92+
mutex.unlock();
93+
});
94+
95+
EXPECT_EQ(counter.load(), 0);
96+
97+
thread_1_context.CancelOperation();
98+
thread_2_context.CancelOperation();
99+
100+
// Give other threads time to react.
101+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
102+
103+
EXPECT_EQ(counter.load(), 2);
104+
105+
main_mutex.unlock();
106+
107+
thread_1.join();
108+
thread_2.join();
109+
}
110+
111+
TEST(NamedMutexTest, CancelationLifetime) {
112+
repository::NamedMutexStorage storage;
113+
114+
client::CancellationContext main_context;
115+
{
116+
repository::NamedMutex main_mutex(storage, "mutex", main_context);
117+
main_mutex.lock();
118+
main_mutex.unlock();
119+
}
120+
main_context.CancelOperation();
121+
}
122+
123+
} // namespace

0 commit comments

Comments
 (0)