Skip to content

Commit 6381594

Browse files
committed
bpp-lsp: Eliminate deadlock potential in ProgramPool
Restrict ourselves to only one mutex, and use atomic operations on the snapshot state instead of relying on a secondary mutex which could otherwise potentially cause deadlock
1 parent ff43028 commit 6381594

File tree

2 files changed

+62
-46
lines changed

2 files changed

+62
-46
lines changed

src/lsp/ProgramPool.cpp

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,23 @@ void ProgramPool::_remove_oldest_program() {
4141
_remove_program(0);
4242
}
4343

44+
const ProgramPool::Snapshot& ProgramPool::load_snapshot() const {
45+
return *std::atomic_load_explicit(&_snapshot, std::memory_order_acquire);
46+
}
47+
48+
void ProgramPool::_set_snapshot(std::shared_ptr<const Snapshot> new_snapshot) {
49+
std::atomic_store_explicit(&_snapshot, new_snapshot, std::memory_order_release);
50+
}
51+
4452
void ProgramPool::update_snapshot() {
45-
std::lock_guard<std::recursive_mutex> snapshot_lock(snapshot_mutex);
46-
programs_snapshot = programs; // Copy the current programs state
47-
program_indices_snapshot = program_indices; // Copy the current program indices state
48-
open_files_snapshot = open_files; // Copy the current open files state
53+
auto new_snapshot = std::make_shared<Snapshot>();
54+
{
55+
std::lock_guard<std::recursive_mutex> lock(pool_mutex);
56+
new_snapshot->programs_snapshot = programs;
57+
new_snapshot->program_indices_snapshot = program_indices;
58+
new_snapshot->open_files_snapshot = open_files;
59+
}
60+
_set_snapshot(new_snapshot); // Update the snapshot atomically
4961
}
5062

5163
void ProgramPool::_remove_program(size_t index) {
@@ -148,12 +160,12 @@ std::shared_ptr<bpp::bpp_program> ProgramPool::get_program(const std::string& fi
148160
if (jump_queue) {
149161
// Jump the queue:
150162
// Skip getting a lock on the pool's main state, since we're not modifying it
151-
// Instead, get a lock on the snapshot
163+
// Instead, read from the snapshot
152164
// And REFUSE to modify the pool if the file isn't already in it
153-
std::lock_guard<std::recursive_mutex> lock(snapshot_mutex);
154-
auto it = program_indices_snapshot.find(file_path);
155-
if (it != program_indices_snapshot.end()) {
156-
return programs_snapshot[it->second];
165+
Snapshot snapshot = load_snapshot();
166+
auto it = snapshot.program_indices_snapshot.find(file_path);
167+
if (it != snapshot.program_indices_snapshot.end()) {
168+
return snapshot.programs_snapshot[it->second];
157169
} else {
158170
return nullptr;
159171
}
@@ -193,56 +205,58 @@ std::shared_ptr<bpp::bpp_program> ProgramPool::get_program(const std::string& fi
193205

194206
bool ProgramPool::has_program(const std::string& file_path) {
195207
// Scan the SNAPSHOT for the program
196-
std::lock_guard<std::recursive_mutex> lock(snapshot_mutex);
208+
Snapshot snapshot = load_snapshot();
209+
const auto& program_indices_snapshot = snapshot.program_indices_snapshot;
197210
return program_indices_snapshot.find(file_path) != program_indices_snapshot.end();
198211
}
199212

200213
std::shared_ptr<bpp::bpp_program> ProgramPool::re_parse_program(const std::string& file_path) {
214+
if (!has_program(file_path)) {
215+
return get_program(file_path); // If it doesn't exist, create it
216+
}
217+
201218
std::lock_guard<std::recursive_mutex> lock(pool_mutex);
202-
if (has_program(file_path)) {
203-
size_t index = program_indices[file_path];
204-
std::string main_source_file = programs[index]->get_main_source_file();
219+
// The program exists, so we can re-parse it
220+
size_t index = program_indices[file_path];
221+
std::string main_source_file = programs[index]->get_main_source_file();
205222

206-
auto new_program = _parse_program(main_source_file);
207-
if (new_program == nullptr) {
208-
return nullptr; // Return nullptr if parsing fails
209-
}
223+
auto new_program = _parse_program(main_source_file);
224+
if (new_program == nullptr) {
225+
return nullptr; // Return nullptr if parsing fails
226+
}
210227

211-
programs[index] = new_program;
212-
open_files[file_path] = true; // Mark the file as open
228+
programs[index] = new_program;
229+
open_files[file_path] = true; // Mark the file as open
213230

214-
update_snapshot();
231+
update_snapshot();
215232

216-
return programs[index];
217-
} else {
218-
return get_program(file_path); // If it doesn't exist, create it
219-
}
233+
return programs[index];
220234
}
221235

222236
std::shared_ptr<bpp::bpp_program> ProgramPool::re_parse_program(
223237
const std::string& file_path,
224238
std::pair<std::string, std::string> replacement_file_contents
225239
) {
240+
if (!has_program(file_path)) {
241+
return nullptr;
242+
}
243+
226244
std::lock_guard<std::recursive_mutex> lock(pool_mutex);
227-
if (has_program(file_path)) {
228-
size_t index = program_indices[file_path];
229-
std::string main_source_file = programs[index]->get_main_source_file();
245+
size_t index = program_indices[file_path];
246+
std::string main_source_file = programs[index]->get_main_source_file();
230247

231-
auto new_program = _parse_program(main_source_file, replacement_file_contents);
248+
auto new_program = _parse_program(main_source_file, replacement_file_contents);
232249

233-
if (new_program == nullptr) {
234-
return nullptr; // Return nullptr if parsing fails
235-
}
250+
if (new_program == nullptr) {
251+
return nullptr; // Return nullptr if parsing fails
252+
}
236253

237-
programs[index] = new_program;
238-
open_files[file_path] = true; // Mark the file as open
254+
programs[index] = new_program;
255+
open_files[file_path] = true; // Mark the file as open
239256

240-
update_snapshot();
257+
update_snapshot();
241258

242-
return programs[index];
243-
} else {
244-
return nullptr;
245-
}
259+
return programs[index];
246260
}
247261

248262
void ProgramPool::open_file(const std::string& file_path) {
@@ -289,13 +303,10 @@ void ProgramPool::close_file(const std::string& file_path) {
289303

290304
void ProgramPool::clean() {
291305
std::lock_guard<std::recursive_mutex> lock(pool_mutex);
292-
std::lock_guard<std::recursive_mutex> snapshot_lock(snapshot_mutex);
293306

294307
programs.clear();
295308
program_indices.clear();
296309
open_files.clear();
297310

298-
programs_snapshot.clear();
299-
program_indices_snapshot.clear();
300-
open_files_snapshot.clear();
311+
update_snapshot(); // Update the snapshot after cleaning
301312
}

src/lsp/ProgramPool.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@ class ProgramPool {
4242
std::recursive_mutex pool_mutex; // Mutex to protect access to the pool
4343

4444
// Pool snapshots:
45-
std::vector<std::shared_ptr<bpp::bpp_program>> programs_snapshot; // Snapshot of the current programs in the pool
46-
std::unordered_map<std::string, size_t> program_indices_snapshot; // Snapshot of the current program indices
47-
std::unordered_map<std::string, bool> open_files_snapshot; // Snapshot of the current open files
48-
std::recursive_mutex snapshot_mutex; // Mutex to protect access to the snapshots
45+
struct Snapshot {
46+
std::vector<std::shared_ptr<bpp::bpp_program>> programs_snapshot; // Snapshot of the current programs in the pool
47+
std::unordered_map<std::string, size_t> program_indices_snapshot; // Snapshot of the current program indices
48+
std::unordered_map<std::string, bool> open_files_snapshot; // Snapshot of the current open files
49+
};
50+
std::shared_ptr<const Snapshot> _snapshot = std::make_shared<Snapshot>(); // DO NOT access directly
51+
// Use the following member functions instead:
52+
const Snapshot& load_snapshot() const;
53+
void _set_snapshot(std::shared_ptr<const Snapshot> new_snapshot);
4954

5055
bool utf16_mode = false; // Whether to use UTF-16 mode for character counting
5156

0 commit comments

Comments
 (0)