Skip to content

Commit 30fde0d

Browse files
authored
fix: stall on "starting new singleton" on linux if keychain blocks (microsoft#187182)
Adds a 5s timeout to keychain access on Linux. We had an issue about this a long time ago, but I never repro'd it until today and can't find the original... If this timeout is hit, it'll fall back to the file-based keychain.
1 parent eec239d commit 30fde0d

File tree

2 files changed

+59
-31
lines changed

2 files changed

+59
-31
lines changed

cli/src/auth.rs

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use crate::{
1010
trace,
1111
util::{
1212
errors::{
13-
wrap, AnyError, OAuthError, RefreshTokenNotAvailableError, StatusError, WrappedError,
13+
wrap, AnyError, CodeError, OAuthError, RefreshTokenNotAvailableError, StatusError,
14+
WrappedError,
1415
},
1516
input::prompt_options,
1617
},
@@ -172,9 +173,9 @@ pub struct Auth {
172173
}
173174

174175
trait StorageImplementation: Send + Sync {
175-
fn read(&mut self) -> Result<Option<StoredCredential>, WrappedError>;
176-
fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError>;
177-
fn clear(&mut self) -> Result<(), WrappedError>;
176+
fn read(&mut self) -> Result<Option<StoredCredential>, AnyError>;
177+
fn store(&mut self, value: StoredCredential) -> Result<(), AnyError>;
178+
fn clear(&mut self) -> Result<(), AnyError>;
178179
}
179180

180181
// unseal decrypts and deserializes the value
@@ -217,16 +218,34 @@ struct ThreadKeyringStorage {
217218
}
218219

219220
impl ThreadKeyringStorage {
220-
fn thread_op<R, Fn>(&mut self, f: Fn) -> R
221+
fn thread_op<R, Fn>(&mut self, f: Fn) -> Result<R, AnyError>
221222
where
222-
Fn: 'static + Send + FnOnce(&mut KeyringStorage) -> R,
223+
Fn: 'static + Send + FnOnce(&mut KeyringStorage) -> Result<R, AnyError>,
223224
R: 'static + Send,
224225
{
225-
let mut s = self.s.take().unwrap();
226-
let handler = thread::spawn(move || (f(&mut s), s));
227-
let (r, s) = handler.join().unwrap();
228-
self.s = Some(s);
229-
r
226+
let mut s = match self.s.take() {
227+
Some(s) => s,
228+
None => return Err(CodeError::KeyringTimeout.into()),
229+
};
230+
231+
// It seems like on Linux communication to the keyring can block indefinitely.
232+
// Fall back after a 5 second timeout.
233+
let (sender, receiver) = std::sync::mpsc::channel();
234+
let tsender = sender.clone();
235+
236+
thread::spawn(move || sender.send(Some((f(&mut s), s))));
237+
thread::spawn(move || {
238+
thread::sleep(std::time::Duration::from_secs(5));
239+
let _ = tsender.send(None);
240+
});
241+
242+
match receiver.recv().unwrap() {
243+
Some((r, s)) => {
244+
self.s = Some(s);
245+
r
246+
}
247+
None => Err(CodeError::KeyringTimeout.into()),
248+
}
230249
}
231250
}
232251

@@ -239,15 +258,15 @@ impl Default for ThreadKeyringStorage {
239258
}
240259

241260
impl StorageImplementation for ThreadKeyringStorage {
242-
fn read(&mut self) -> Result<Option<StoredCredential>, WrappedError> {
261+
fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
243262
self.thread_op(|s| s.read())
244263
}
245264

246-
fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError> {
265+
fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
247266
self.thread_op(move |s| s.store(value))
248267
}
249268

250-
fn clear(&mut self) -> Result<(), WrappedError> {
269+
fn clear(&mut self) -> Result<(), AnyError> {
251270
self.thread_op(|s| s.clear())
252271
}
253272
}
@@ -273,15 +292,15 @@ macro_rules! get_next_entry {
273292
}
274293

275294
impl StorageImplementation for KeyringStorage {
276-
fn read(&mut self) -> Result<Option<StoredCredential>, WrappedError> {
295+
fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
277296
let mut str = String::new();
278297

279298
for i in 0.. {
280299
let entry = get_next_entry!(self, i);
281300
let next_chunk = match entry.get_password() {
282301
Ok(value) => value,
283302
Err(keyring::Error::NoEntry) => return Ok(None), // missing entries?
284-
Err(e) => return Err(wrap(e, "error reading keyring")),
303+
Err(e) => return Err(wrap(e, "error reading keyring").into()),
285304
};
286305

287306
if next_chunk.ends_with(CONTINUE_MARKER) {
@@ -295,7 +314,7 @@ impl StorageImplementation for KeyringStorage {
295314
Ok(unseal(&str))
296315
}
297316

298-
fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError> {
317+
fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
299318
let sealed = seal(&value);
300319
let step_size = KEYCHAIN_ENTRY_LIMIT - CONTINUE_MARKER.len();
301320

@@ -312,14 +331,14 @@ impl StorageImplementation for KeyringStorage {
312331
};
313332

314333
if let Err(e) = stored {
315-
return Err(wrap(e, "error updating keyring"));
334+
return Err(wrap(e, "error updating keyring").into());
316335
}
317336
}
318337

319338
Ok(())
320339
}
321340

322-
fn clear(&mut self) -> Result<(), WrappedError> {
341+
fn clear(&mut self) -> Result<(), AnyError> {
323342
self.read().ok(); // make sure component parts are available
324343
for entry in self.entries.iter() {
325344
entry
@@ -335,16 +354,16 @@ impl StorageImplementation for KeyringStorage {
335354
struct FileStorage(PersistedState<Option<String>>);
336355

337356
impl StorageImplementation for FileStorage {
338-
fn read(&mut self) -> Result<Option<StoredCredential>, WrappedError> {
357+
fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
339358
Ok(self.0.load().and_then(|s| unseal(&s)))
340359
}
341360

342-
fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError> {
343-
self.0.save(Some(seal(&value)))
361+
fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
362+
self.0.save(Some(seal(&value))).map_err(|e| e.into())
344363
}
345364

346-
fn clear(&mut self) -> Result<(), WrappedError> {
347-
self.0.save(None)
365+
fn clear(&mut self) -> Result<(), AnyError> {
366+
self.0.save(None).map_err(|e| e.into())
348367
}
349368
}
350369

@@ -374,7 +393,7 @@ impl Auth {
374393
let mut file_storage = FileStorage(PersistedState::new(self.file_storage_path.clone()));
375394

376395
let keyring_storage_result = match std::env::var("VSCODE_CLI_USE_FILE_KEYCHAIN") {
377-
Ok(_) => Err(wrap("", "user prefers file storage")),
396+
Ok(_) => Err(wrap("", "user prefers file storage").into()),
378397
_ => keyring_storage.read(),
379398
};
380399

@@ -383,10 +402,17 @@ impl Auth {
383402
last_read: Cell::new(Ok(v)),
384403
storage: Box::new(keyring_storage),
385404
},
386-
Err(_) => StorageWithLastRead {
387-
last_read: Cell::new(file_storage.read()),
388-
storage: Box::new(file_storage),
389-
},
405+
Err(e) => {
406+
debug!(self.log, "Using file keychain storage due to: {}", e);
407+
StorageWithLastRead {
408+
last_read: Cell::new(
409+
file_storage
410+
.read()
411+
.map_err(|e| wrap(e, "could not read from file storage")),
412+
),
413+
storage: Box::new(file_storage),
414+
}
415+
}
390416
};
391417

392418
let out = op(&mut storage);
@@ -419,7 +445,7 @@ impl Auth {
419445
}
420446

421447
/// Clears login info from the keyring.
422-
pub fn clear_credentials(&self) -> Result<(), WrappedError> {
448+
pub fn clear_credentials(&self) -> Result<(), AnyError> {
423449
self.with_storage(|storage| {
424450
storage.storage.clear()?;
425451
storage.last_read.set(Ok(None));

cli/src/util/errors.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
5-
use crate::{
5+
use crate::{
66
constants::{APPLICATION_NAME, CONTROL_PORT, DOCUMENTATION_URL, QUALITYLESS_PRODUCT_NAME},
77
rpc::ResponseError,
88
};
@@ -511,6 +511,8 @@ pub enum CodeError {
511511
AuthChallengeNotIssued,
512512
#[error("unauthorized client refused")]
513513
AuthMismatch,
514+
#[error("keyring communication timed out after 5s")]
515+
KeyringTimeout,
514516
}
515517

516518
makeAnyError!(

0 commit comments

Comments
 (0)