Skip to content

Commit 0702485

Browse files
authored
Refactor synchronization primitives from Mutex to RwLock for improved concurrency (#296)
Fixes #286
1 parent 15a9e5c commit 0702485

File tree

5 files changed

+108
-79
lines changed

5 files changed

+108
-79
lines changed

crates/pet-conda/src/lib.rs

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
2222
use std::{
2323
collections::HashMap,
2424
path::{Path, PathBuf},
25-
sync::{Arc, Mutex},
25+
sync::{Arc, RwLock},
2626
thread,
2727
};
2828
use telemetry::{get_conda_rcs_and_env_dirs, report_missing_envs};
@@ -61,24 +61,24 @@ pub struct CondaTelemetryInfo {
6161
}
6262

6363
pub struct Conda {
64-
pub environments: Arc<Mutex<HashMap<PathBuf, PythonEnvironment>>>,
65-
pub managers: Arc<Mutex<HashMap<PathBuf, CondaManager>>>,
64+
pub environments: Arc<RwLock<HashMap<PathBuf, PythonEnvironment>>>,
65+
pub managers: Arc<RwLock<HashMap<PathBuf, CondaManager>>>,
6666
pub env_vars: EnvVariables,
67-
conda_executable: Arc<Mutex<Option<PathBuf>>>,
67+
conda_executable: Arc<RwLock<Option<PathBuf>>>,
6868
}
6969

7070
impl Conda {
7171
pub fn from(env: &dyn Environment) -> Conda {
7272
Conda {
73-
environments: Arc::new(Mutex::new(HashMap::new())),
74-
managers: Arc::new(Mutex::new(HashMap::new())),
73+
environments: Arc::new(RwLock::new(HashMap::new())),
74+
managers: Arc::new(RwLock::new(HashMap::new())),
7575
env_vars: EnvVariables::from(env),
76-
conda_executable: Arc::new(Mutex::new(None)),
76+
conda_executable: Arc::new(RwLock::new(None)),
7777
}
7878
}
7979
fn clear(&self) {
80-
self.environments.lock().unwrap().clear();
81-
self.managers.lock().unwrap().clear();
80+
self.environments.write().unwrap().clear();
81+
self.managers.write().unwrap().clear();
8282
}
8383
}
8484

@@ -91,7 +91,7 @@ impl CondaLocator for Conda {
9191
// Look for environments that we couldn't find without spawning conda.
9292
let user_provided_conda_exe = conda_executable.is_some();
9393
let conda_info = CondaInfo::from(conda_executable)?;
94-
let environments = self.environments.lock().unwrap().clone();
94+
let environments = self.environments.read().unwrap().clone();
9595
let new_envs = conda_info
9696
.envs
9797
.clone()
@@ -119,7 +119,7 @@ impl CondaLocator for Conda {
119119

120120
fn get_info_for_telemetry(&self, conda_executable: Option<PathBuf>) -> CondaTelemetryInfo {
121121
let can_spawn_conda = CondaInfo::from(conda_executable).is_some();
122-
let environments = self.environments.lock().unwrap().clone();
122+
let environments = self.environments.read().unwrap().clone();
123123
let environments = environments
124124
.into_values()
125125
.collect::<Vec<PythonEnvironment>>();
@@ -132,7 +132,7 @@ impl CondaLocator for Conda {
132132
environments_txt = Some(file);
133133
}
134134

135-
let conda_exe = &self.conda_executable.lock().unwrap().clone();
135+
let conda_exe = &self.conda_executable.read().unwrap().clone();
136136
let envs_found = get_conda_environment_paths(&self.env_vars, conda_exe);
137137
let mut user_provided_env_found = None;
138138
if let Some(conda_dir) = get_conda_dir_from_exe(conda_exe) {
@@ -159,7 +159,7 @@ impl CondaLocator for Conda {
159159
if let Some(conda_dir) = manager.conda_dir.clone() {
160160
// Keep track to search again later.
161161
// Possible we'll find environments in other directories created using this manager
162-
let mut managers = self.managers.lock().unwrap();
162+
let mut managers = self.managers.write().unwrap();
163163
// Keep track to search again later.
164164
// Possible we'll find environments in other directories created using this manager
165165
managers.insert(conda_dir.clone(), manager.clone());
@@ -170,7 +170,7 @@ impl CondaLocator for Conda {
170170
get_conda_environments(&get_environments(&conda_dir), &manager.clone().into())
171171
{
172172
// If reported earlier, no point processing this again.
173-
let mut environments = self.environments.lock().unwrap();
173+
let mut environments = self.environments.write().unwrap();
174174
if environments.contains_key(&conda_env.prefix) {
175175
continue;
176176
}
@@ -194,13 +194,17 @@ impl CondaLocator for Conda {
194194

195195
impl Conda {
196196
fn get_manager(&self, conda_dir: &Path) -> Option<CondaManager> {
197-
let mut managers = self.managers.lock().unwrap();
198-
// If we have a conda install folder, then use that to get the manager.
199-
if let Some(mgr) = managers.get(conda_dir) {
200-
return Some(mgr.clone());
197+
// First try to read from cache
198+
{
199+
let managers = self.managers.read().unwrap();
200+
if let Some(mgr) = managers.get(conda_dir) {
201+
return Some(mgr.clone());
202+
}
201203
}
202204

205+
// If not found, acquire write lock and insert
203206
if let Some(manager) = CondaManager::from(conda_dir) {
207+
let mut managers = self.managers.write().unwrap();
204208
managers.insert(conda_dir.into(), manager.clone());
205209
Some(manager)
206210
} else {
@@ -215,7 +219,7 @@ impl Locator for Conda {
215219
}
216220
fn configure(&self, config: &pet_core::Configuration) {
217221
if let Some(ref conda_exe) = config.conda_executable {
218-
let mut conda_executable = self.conda_executable.lock().unwrap();
222+
let mut conda_executable = self.conda_executable.write().unwrap();
219223
conda_executable.replace(conda_exe.clone());
220224
}
221225
}
@@ -246,16 +250,19 @@ impl Locator for Conda {
246250
return None;
247251
}
248252

249-
let mut environments = self.environments.lock().unwrap();
250-
251-
// Do we already have an env for this.
252-
if let Some(env) = environments.get(path) {
253-
return Some(env.clone());
253+
// First check cache with read lock
254+
{
255+
let environments = self.environments.read().unwrap();
256+
if let Some(env) = environments.get(path) {
257+
return Some(env.clone());
258+
}
254259
}
260+
// Not in cache, build the environment and insert with write lock
255261
if let Some(env) = get_conda_environment_info(path, &None) {
256262
if let Some(conda_dir) = &env.conda_dir {
257263
if let Some(manager) = self.get_manager(conda_dir) {
258264
let env = env.to_python_environment(Some(manager.to_manager()));
265+
let mut environments = self.environments.write().unwrap();
259266
environments.insert(path.clone(), env.clone());
260267
return Some(env);
261268
} else {
@@ -264,6 +271,7 @@ impl Locator for Conda {
264271
// The client can activate this env either using another conda manager or using the activation scripts
265272
error!("Unable to find Conda Manager for env (even though we have a conda_dir): {:?}", env);
266273
let env = env.to_python_environment(None);
274+
let mut environments = self.environments.write().unwrap();
267275
environments.insert(path.clone(), env.clone());
268276
return Some(env);
269277
}
@@ -273,6 +281,7 @@ impl Locator for Conda {
273281
// The client can activate this env either using another conda manager or using the activation scripts
274282
error!("Unable to find Conda Manager for env: {:?}", env);
275283
let env = env.to_python_environment(None);
284+
let mut environments = self.environments.write().unwrap();
276285
environments.insert(path.clone(), env.clone());
277286
return Some(env);
278287
}
@@ -286,7 +295,7 @@ impl Locator for Conda {
286295
self.clear();
287296

288297
let env_vars = self.env_vars.clone();
289-
let executable = self.conda_executable.lock().unwrap().clone();
298+
let executable = self.conda_executable.read().unwrap().clone();
290299
thread::scope(|s| {
291300
// 1. Get a list of all know conda environments file paths
292301
let possible_conda_envs = get_conda_environment_paths(&env_vars, &executable);
@@ -306,7 +315,7 @@ impl Locator for Conda {
306315
error!("Unable to find Conda Manager for the Conda env: {:?}", env);
307316
let prefix = env.prefix.clone();
308317
let env = env.to_python_environment(None);
309-
let mut environments = self.environments.lock().unwrap();
318+
let mut environments = self.environments.write().unwrap();
310319
environments.insert(prefix, env.clone());
311320
reporter.report_environment(&env);
312321
return None;
@@ -319,7 +328,7 @@ impl Locator for Conda {
319328
{
320329
// 3.1 Check if we have already reported this environment.
321330
// Closure to quickly release lock
322-
let environments = self.environments.lock().unwrap();
331+
let environments = self.environments.read().unwrap();
323332
if environments.contains_key(&env.prefix) {
324333
return None;
325334
}
@@ -328,14 +337,14 @@ impl Locator for Conda {
328337

329338
// 4 Get the manager for this env.
330339
let conda_dir = &env.conda_dir.clone()?;
331-
let managers = self.managers.lock().unwrap();
340+
let managers = self.managers.read().unwrap();
332341
let mut manager = managers.get(conda_dir).cloned();
333342
drop(managers);
334343

335344
if manager.is_none() {
336345
// 4.1 Build the manager from the conda dir if we do not have it.
337346
if let Some(conda_manager) = CondaManager::from(conda_dir) {
338-
let mut managers = self.managers.lock().unwrap();
347+
let mut managers = self.managers.write().unwrap();
339348
managers.insert(conda_dir.to_path_buf().clone(), conda_manager.clone());
340349
manager = Some(conda_manager);
341350
}
@@ -346,7 +355,7 @@ impl Locator for Conda {
346355
let env = env.to_python_environment(
347356
Some(manager.to_manager()),
348357
);
349-
let mut environments = self.environments.lock().unwrap();
358+
let mut environments = self.environments.write().unwrap();
350359
environments.insert(prefix.clone(), env.clone());
351360
reporter.report_manager(&manager.to_manager());
352361
reporter.report_environment(&env);
@@ -356,7 +365,7 @@ impl Locator for Conda {
356365
// The client can activate this env either using another conda manager or using the activation scripts
357366
error!("Unable to find Conda Manager for Conda env (even though we have a conda_dir {:?}): Env Details = {:?}", conda_dir, env);
358367
let env = env.to_python_environment(None);
359-
let mut environments = self.environments.lock().unwrap();
368+
let mut environments = self.environments.write().unwrap();
360369
environments.insert(prefix.clone(), env.clone());
361370
reporter.report_environment(&env);
362371
}

crates/pet-linux-global-python/src/lib.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55
collections::{HashMap, HashSet},
66
fs,
77
path::{Path, PathBuf},
8-
sync::{Arc, Mutex},
8+
sync::{Arc, RwLock},
99
thread,
1010
};
1111

@@ -21,15 +21,15 @@ use pet_python_utils::{env::ResolvedPythonEnv, executable::find_executables};
2121
use pet_virtualenv::is_virtualenv;
2222

2323
pub struct LinuxGlobalPython {
24-
reported_executables: Arc<Mutex<HashMap<PathBuf, PythonEnvironment>>>,
24+
reported_executables: Arc<RwLock<HashMap<PathBuf, PythonEnvironment>>>,
2525
}
2626

2727
impl LinuxGlobalPython {
2828
pub fn new() -> LinuxGlobalPython {
2929
LinuxGlobalPython {
30-
reported_executables: Arc::new(
31-
Mutex::new(HashMap::<PathBuf, PythonEnvironment>::new()),
32-
),
30+
reported_executables: Arc::new(RwLock::new(
31+
HashMap::<PathBuf, PythonEnvironment>::new(),
32+
)),
3333
}
3434
}
3535

@@ -94,7 +94,7 @@ impl Locator for LinuxGlobalPython {
9494
}
9595

9696
self.reported_executables
97-
.lock()
97+
.read()
9898
.unwrap()
9999
.get(&executable)
100100
.cloned()
@@ -104,26 +104,26 @@ impl Locator for LinuxGlobalPython {
104104
if std::env::consts::OS == "macos" || std::env::consts::OS == "windows" {
105105
return;
106106
}
107-
self.reported_executables.lock().unwrap().clear();
107+
self.reported_executables.write().unwrap().clear();
108108
self.find_cached(Some(reporter))
109109
}
110110
}
111111

112112
fn find_and_report_global_pythons_in(
113113
bin: &Path,
114114
reporter: Option<&dyn Reporter>,
115-
reported_executables: &Arc<Mutex<HashMap<PathBuf, PythonEnvironment>>>,
115+
reported_executables: &Arc<RwLock<HashMap<PathBuf, PythonEnvironment>>>,
116116
) {
117117
let python_executables = find_executables(bin);
118118

119119
for exe in python_executables.clone().iter() {
120-
if reported_executables.lock().unwrap().contains_key(exe) {
120+
if reported_executables.read().unwrap().contains_key(exe) {
121121
continue;
122122
}
123123
if let Some(resolved) = ResolvedPythonEnv::from(exe) {
124124
if let Some(env) = get_python_in_bin(&resolved.to_python_env(), resolved.is64_bit) {
125125
resolved.add_to_cache(env.clone());
126-
let mut reported_executables = reported_executables.lock().unwrap();
126+
let mut reported_executables = reported_executables.write().unwrap();
127127
// env.symlinks = Some([symlinks, env.symlinks.clone().unwrap_or_default()].concat());
128128
if let Some(symlinks) = &env.symlinks {
129129
for symlink in symlinks {

0 commit comments

Comments
 (0)