mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Speed up TUI startup by reusing plugin discovery (#26469)
## Summary TUI startup loads related plugin data from `hooks/list`, session MCP initialization, and plugin skill warmup. These paths repeated filesystem discovery and emitted the same plugin warnings, while `hooks/list` and account/model bootstrap ran serially. This change: - Reuses one immutable plugin load outcome across startup consumers. - Keys the cache only on plugin-relevant configuration. - Single-flights concurrent plugin loads and prevents invalidated loads from repopulating the cache. - Runs hook discovery and account/model bootstrap concurrently. - Preserves configuration-migration ordering, hook review behavior, and accurate startup telemetry. In 10 alternating release-build launches in the Ruff repository with the existing `~/.codex` configuration, median time to the first editable composer decreased from 833ms to 504ms. The branch was faster in 9 of 10 pairs, with a paired median improvement of 312ms.
This commit is contained in:
1 parent
345cf6e8d0
commit
055c7a7c53
9 files changed
+303
-37
No files matched your search
@@ -57,8 +57,9 @@ use codex_config::apply_user_plugin_config_edits;
|
||||
use codex_config::clear_user_plugin;
|
||||
use codex_config::set_user_plugin_enabled;
|
||||
use codex_config::types::PluginConfig;
|
||||
use codex_config::version_for_toml;
|
||||
use codex_core_skills::SkillMetadata;
|
||||
use codex_core_skills::config_rules::SkillConfigRules;
|
||||
use codex_core_skills::config_rules::skill_config_rules_from_stack;
|
||||
use codex_hooks::plugin_hook_declarations;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
@@ -403,7 +404,8 @@ pub struct PluginsManager {
|
||||
featured_plugin_ids_cache: RwLock<Option<CachedFeaturedPluginIds>>,
|
||||
configured_marketplace_upgrade_state: RwLock<ConfiguredMarketplaceUpgradeState>,
|
||||
non_curated_cache_refresh_state: RwLock<NonCuratedCacheRefreshState>,
|
||||
cached_enabled_outcome: RwLock<Option<CachedPluginLoadOutcome>>,
|
||||
enabled_outcome_cache: RwLock<EnabledOutcomeCache>,
|
||||
enabled_outcome_load_semaphore: Semaphore,
|
||||
remote_installed_plugins_cache: RwLock<Option<Vec<RemoteInstalledPlugin>>>,
|
||||
remote_installed_plugins_cache_refresh_state: RwLock<RemoteInstalledPluginsCacheRefreshState>,
|
||||
remote_sync_lock: Semaphore,
|
||||
@@ -413,10 +415,23 @@ pub struct PluginsManager {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CachedPluginLoadOutcome {
|
||||
config_version: String,
|
||||
key: PluginLoadCacheKey,
|
||||
outcome: PluginLoadOutcome,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EnabledOutcomeCache {
|
||||
generation: u64,
|
||||
outcome: Option<CachedPluginLoadOutcome>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
struct PluginLoadCacheKey {
|
||||
configured_plugins: HashMap<String, PluginConfig>,
|
||||
skill_config_rules: SkillConfigRules,
|
||||
remote_plugin_enabled: bool,
|
||||
}
|
||||
|
||||
impl PluginsManager {
|
||||
pub fn new(codex_home: PathBuf) -> Self {
|
||||
Self::new_with_restriction_product(codex_home, Some(Product::Codex))
|
||||
@@ -441,7 +456,8 @@ impl PluginsManager {
|
||||
ConfiguredMarketplaceUpgradeState::default(),
|
||||
),
|
||||
non_curated_cache_refresh_state: RwLock::new(NonCuratedCacheRefreshState::default()),
|
||||
cached_enabled_outcome: RwLock::new(None),
|
||||
enabled_outcome_cache: RwLock::new(EnabledOutcomeCache::default()),
|
||||
enabled_outcome_load_semaphore: Semaphore::new(/*permits*/ 1),
|
||||
remote_installed_plugins_cache: RwLock::new(None),
|
||||
remote_installed_plugins_cache_refresh_state: RwLock::new(
|
||||
RemoteInstalledPluginsCacheRefreshState::default(),
|
||||
@@ -484,11 +500,23 @@ impl PluginsManager {
|
||||
return PluginLoadOutcome::default();
|
||||
}
|
||||
|
||||
let config_version = version_for_toml(&config.config_layer_stack.effective_config());
|
||||
if !force_reload && let Some(outcome) = self.cached_enabled_outcome(&config_version) {
|
||||
let cache_key = PluginLoadCacheKey {
|
||||
configured_plugins: configured_plugins_from_stack(&config.config_layer_stack),
|
||||
skill_config_rules: skill_config_rules_from_stack(&config.config_layer_stack),
|
||||
remote_plugin_enabled: config.remote_plugin_enabled,
|
||||
};
|
||||
if !force_reload && let Some(outcome) = self.cached_enabled_outcome(&cache_key) {
|
||||
return outcome;
|
||||
}
|
||||
|
||||
let Ok(_load_permit) = self.enabled_outcome_load_semaphore.acquire().await else {
|
||||
warn!("plugin load semaphore closed");
|
||||
return PluginLoadOutcome::default();
|
||||
};
|
||||
if !force_reload && let Some(outcome) = self.cached_enabled_outcome(&cache_key) {
|
||||
return outcome;
|
||||
}
|
||||
let cache_generation = self.enabled_outcome_cache_generation();
|
||||
let outcome = load_plugins_from_layer_stack(
|
||||
&config.config_layer_stack,
|
||||
self.remote_installed_plugin_configs(),
|
||||
@@ -498,14 +526,7 @@ impl PluginsManager {
|
||||
)
|
||||
.await;
|
||||
log_plugin_load_errors(&outcome);
|
||||
let mut cache = match self.cached_enabled_outcome.write() {
|
||||
Ok(cache) => cache,
|
||||
Err(err) => err.into_inner(),
|
||||
};
|
||||
*cache = Some(CachedPluginLoadOutcome {
|
||||
config_version,
|
||||
outcome: outcome.clone(),
|
||||
});
|
||||
self.cache_enabled_outcome_if_current(cache_generation, cache_key, outcome.clone());
|
||||
outcome
|
||||
}
|
||||
|
||||
@@ -519,11 +540,12 @@ impl PluginsManager {
|
||||
}
|
||||
|
||||
fn clear_enabled_outcome_cache(&self) {
|
||||
let mut cached_enabled_outcome = match self.cached_enabled_outcome.write() {
|
||||
let mut cache = match self.enabled_outcome_cache.write() {
|
||||
Ok(cache) => cache,
|
||||
Err(err) => err.into_inner(),
|
||||
};
|
||||
*cached_enabled_outcome = None;
|
||||
cache.generation = cache.generation.wrapping_add(1);
|
||||
cache.outcome = None;
|
||||
}
|
||||
|
||||
/// Load plugins for a config layer stack without touching the plugins cache.
|
||||
@@ -574,20 +596,44 @@ impl PluginsManager {
|
||||
.effective_plugin_skill_roots()
|
||||
}
|
||||
|
||||
fn cached_enabled_outcome(&self, config_version: &str) -> Option<PluginLoadOutcome> {
|
||||
match self.cached_enabled_outcome.read() {
|
||||
fn cached_enabled_outcome(&self, key: &PluginLoadCacheKey) -> Option<PluginLoadOutcome> {
|
||||
match self.enabled_outcome_cache.read() {
|
||||
Ok(cache) => cache
|
||||
.outcome
|
||||
.as_ref()
|
||||
.filter(|cached| cached.config_version == config_version)
|
||||
.filter(|cached| cached.key == *key)
|
||||
.map(|cached| cached.outcome.clone()),
|
||||
Err(err) => err
|
||||
.into_inner()
|
||||
.outcome
|
||||
.as_ref()
|
||||
.filter(|cached| cached.config_version == config_version)
|
||||
.filter(|cached| cached.key == *key)
|
||||
.map(|cached| cached.outcome.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn enabled_outcome_cache_generation(&self) -> u64 {
|
||||
match self.enabled_outcome_cache.read() {
|
||||
Ok(cache) => cache.generation,
|
||||
Err(err) => err.into_inner().generation,
|
||||
}
|
||||
}
|
||||
|
||||
fn cache_enabled_outcome_if_current(
|
||||
&self,
|
||||
generation: u64,
|
||||
key: PluginLoadCacheKey,
|
||||
outcome: PluginLoadOutcome,
|
||||
) {
|
||||
let mut cache = match self.enabled_outcome_cache.write() {
|
||||
Ok(cache) => cache,
|
||||
Err(err) => err.into_inner(),
|
||||
};
|
||||
if cache.generation == generation {
|
||||
cache.outcome = Some(CachedPluginLoadOutcome { key, outcome });
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_installed_plugin_configs(&self) -> HashMap<String, PluginConfig> {
|
||||
let cache = match self.remote_installed_plugins_cache.read() {
|
||||
Ok(cache) => cache,
|
||||
|
||||
Reference in new issue
Block a user