fix: stall on "starting new singleton" on linux if keychain blocks (#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.
This commit is contained in:
Connor Peet
2023-07-06 14:49:31 -07:00
committed by GitHub
parent eec239dc35
commit 30fde0d369
2 changed files with 59 additions and 31 deletions

View File

@@ -10,7 +10,8 @@ use crate::{
trace, trace,
util::{ util::{
errors::{ errors::{
wrap, AnyError, OAuthError, RefreshTokenNotAvailableError, StatusError, WrappedError, wrap, AnyError, CodeError, OAuthError, RefreshTokenNotAvailableError, StatusError,
WrappedError,
}, },
input::prompt_options, input::prompt_options,
}, },
@@ -172,9 +173,9 @@ pub struct Auth {
} }
trait StorageImplementation: Send + Sync { trait StorageImplementation: Send + Sync {
fn read(&mut self) -> Result<Option<StoredCredential>, WrappedError>; fn read(&mut self) -> Result<Option<StoredCredential>, AnyError>;
fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError>; fn store(&mut self, value: StoredCredential) -> Result<(), AnyError>;
fn clear(&mut self) -> Result<(), WrappedError>; fn clear(&mut self) -> Result<(), AnyError>;
} }
// unseal decrypts and deserializes the value // unseal decrypts and deserializes the value
@@ -217,16 +218,34 @@ struct ThreadKeyringStorage {
} }
impl ThreadKeyringStorage { impl ThreadKeyringStorage {
fn thread_op<R, Fn>(&mut self, f: Fn) -> R fn thread_op<R, Fn>(&mut self, f: Fn) -> Result<R, AnyError>
where where
Fn: 'static + Send + FnOnce(&mut KeyringStorage) -> R, Fn: 'static + Send + FnOnce(&mut KeyringStorage) -> Result<R, AnyError>,
R: 'static + Send, R: 'static + Send,
{ {
let mut s = self.s.take().unwrap(); let mut s = match self.s.take() {
let handler = thread::spawn(move || (f(&mut s), s)); Some(s) => s,
let (r, s) = handler.join().unwrap(); None => return Err(CodeError::KeyringTimeout.into()),
self.s = Some(s); };
r
// It seems like on Linux communication to the keyring can block indefinitely.
// Fall back after a 5 second timeout.
let (sender, receiver) = std::sync::mpsc::channel();
let tsender = sender.clone();
thread::spawn(move || sender.send(Some((f(&mut s), s))));
thread::spawn(move || {
thread::sleep(std::time::Duration::from_secs(5));
let _ = tsender.send(None);
});
match receiver.recv().unwrap() {
Some((r, s)) => {
self.s = Some(s);
r
}
None => Err(CodeError::KeyringTimeout.into()),
}
} }
} }
@@ -239,15 +258,15 @@ impl Default for ThreadKeyringStorage {
} }
impl StorageImplementation for ThreadKeyringStorage { impl StorageImplementation for ThreadKeyringStorage {
fn read(&mut self) -> Result<Option<StoredCredential>, WrappedError> { fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
self.thread_op(|s| s.read()) self.thread_op(|s| s.read())
} }
fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError> { fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
self.thread_op(move |s| s.store(value)) self.thread_op(move |s| s.store(value))
} }
fn clear(&mut self) -> Result<(), WrappedError> { fn clear(&mut self) -> Result<(), AnyError> {
self.thread_op(|s| s.clear()) self.thread_op(|s| s.clear())
} }
} }
@@ -273,7 +292,7 @@ macro_rules! get_next_entry {
} }
impl StorageImplementation for KeyringStorage { impl StorageImplementation for KeyringStorage {
fn read(&mut self) -> Result<Option<StoredCredential>, WrappedError> { fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
let mut str = String::new(); let mut str = String::new();
for i in 0.. { for i in 0.. {
@@ -281,7 +300,7 @@ impl StorageImplementation for KeyringStorage {
let next_chunk = match entry.get_password() { let next_chunk = match entry.get_password() {
Ok(value) => value, Ok(value) => value,
Err(keyring::Error::NoEntry) => return Ok(None), // missing entries? Err(keyring::Error::NoEntry) => return Ok(None), // missing entries?
Err(e) => return Err(wrap(e, "error reading keyring")), Err(e) => return Err(wrap(e, "error reading keyring").into()),
}; };
if next_chunk.ends_with(CONTINUE_MARKER) { if next_chunk.ends_with(CONTINUE_MARKER) {
@@ -295,7 +314,7 @@ impl StorageImplementation for KeyringStorage {
Ok(unseal(&str)) Ok(unseal(&str))
} }
fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError> { fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
let sealed = seal(&value); let sealed = seal(&value);
let step_size = KEYCHAIN_ENTRY_LIMIT - CONTINUE_MARKER.len(); let step_size = KEYCHAIN_ENTRY_LIMIT - CONTINUE_MARKER.len();
@@ -312,14 +331,14 @@ impl StorageImplementation for KeyringStorage {
}; };
if let Err(e) = stored { if let Err(e) = stored {
return Err(wrap(e, "error updating keyring")); return Err(wrap(e, "error updating keyring").into());
} }
} }
Ok(()) Ok(())
} }
fn clear(&mut self) -> Result<(), WrappedError> { fn clear(&mut self) -> Result<(), AnyError> {
self.read().ok(); // make sure component parts are available self.read().ok(); // make sure component parts are available
for entry in self.entries.iter() { for entry in self.entries.iter() {
entry entry
@@ -335,16 +354,16 @@ impl StorageImplementation for KeyringStorage {
struct FileStorage(PersistedState<Option<String>>); struct FileStorage(PersistedState<Option<String>>);
impl StorageImplementation for FileStorage { impl StorageImplementation for FileStorage {
fn read(&mut self) -> Result<Option<StoredCredential>, WrappedError> { fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
Ok(self.0.load().and_then(|s| unseal(&s))) Ok(self.0.load().and_then(|s| unseal(&s)))
} }
fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError> { fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
self.0.save(Some(seal(&value))) self.0.save(Some(seal(&value))).map_err(|e| e.into())
} }
fn clear(&mut self) -> Result<(), WrappedError> { fn clear(&mut self) -> Result<(), AnyError> {
self.0.save(None) self.0.save(None).map_err(|e| e.into())
} }
} }
@@ -374,7 +393,7 @@ impl Auth {
let mut file_storage = FileStorage(PersistedState::new(self.file_storage_path.clone())); let mut file_storage = FileStorage(PersistedState::new(self.file_storage_path.clone()));
let keyring_storage_result = match std::env::var("VSCODE_CLI_USE_FILE_KEYCHAIN") { let keyring_storage_result = match std::env::var("VSCODE_CLI_USE_FILE_KEYCHAIN") {
Ok(_) => Err(wrap("", "user prefers file storage")), Ok(_) => Err(wrap("", "user prefers file storage").into()),
_ => keyring_storage.read(), _ => keyring_storage.read(),
}; };
@@ -383,10 +402,17 @@ impl Auth {
last_read: Cell::new(Ok(v)), last_read: Cell::new(Ok(v)),
storage: Box::new(keyring_storage), storage: Box::new(keyring_storage),
}, },
Err(_) => StorageWithLastRead { Err(e) => {
last_read: Cell::new(file_storage.read()), debug!(self.log, "Using file keychain storage due to: {}", e);
storage: Box::new(file_storage), StorageWithLastRead {
}, last_read: Cell::new(
file_storage
.read()
.map_err(|e| wrap(e, "could not read from file storage")),
),
storage: Box::new(file_storage),
}
}
}; };
let out = op(&mut storage); let out = op(&mut storage);
@@ -419,7 +445,7 @@ impl Auth {
} }
/// Clears login info from the keyring. /// Clears login info from the keyring.
pub fn clear_credentials(&self) -> Result<(), WrappedError> { pub fn clear_credentials(&self) -> Result<(), AnyError> {
self.with_storage(|storage| { self.with_storage(|storage| {
storage.storage.clear()?; storage.storage.clear()?;
storage.last_read.set(Ok(None)); storage.last_read.set(Ok(None));

View File

@@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
use crate::{ use crate::{
constants::{APPLICATION_NAME, CONTROL_PORT, DOCUMENTATION_URL, QUALITYLESS_PRODUCT_NAME}, constants::{APPLICATION_NAME, CONTROL_PORT, DOCUMENTATION_URL, QUALITYLESS_PRODUCT_NAME},
rpc::ResponseError, rpc::ResponseError,
}; };
@@ -511,6 +511,8 @@ pub enum CodeError {
AuthChallengeNotIssued, AuthChallengeNotIssued,
#[error("unauthorized client refused")] #[error("unauthorized client refused")]
AuthMismatch, AuthMismatch,
#[error("keyring communication timed out after 5s")]
KeyringTimeout,
} }
makeAnyError!( makeAnyError!(