Skip to content

Commit 2798089

Browse files
.
1 parent 28b59e8 commit 2798089

8 files changed

Lines changed: 44 additions & 26 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/forge_api/src/forge_api.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use forge_app::{
1010
FileDiscoveryService, ForgeApp, GitApp, GrpcInfra, McpConfigManager, McpService,
1111
ProviderAuthService, ProviderService, Services, User, UserUsage, Walker, WorkspaceService,
1212
};
13-
use forge_config::ForgeConfig;
1413
use forge_domain::{Agent, ConsoleWriter, *};
1514
use forge_infra::ForgeInfra;
1615
use forge_repo::ForgeRepo;
@@ -42,19 +41,6 @@ impl<A, F> ForgeAPI<A, F> {
4241
}
4342

4443
impl ForgeAPI<ForgeServices<ForgeRepo<ForgeInfra>>, ForgeRepo<ForgeInfra>> {
45-
/// Creates a fully-initialized [`ForgeAPI`] from a pre-read configuration.
46-
///
47-
/// # Arguments
48-
/// * `cwd` - The working directory path for environment and file resolution
49-
/// * `config` - Pre-read application configuration (from startup)
50-
/// * `services_url` - Pre-validated URL for the gRPC workspace server
51-
pub fn init(cwd: PathBuf, config: ForgeConfig) -> Self {
52-
let infra = Arc::new(ForgeInfra::new(cwd, config));
53-
let repo = Arc::new(ForgeRepo::new(infra.clone()));
54-
let app = Arc::new(ForgeServices::new(repo.clone()));
55-
ForgeAPI::new(app, repo)
56-
}
57-
5844
pub async fn get_skills_internal(&self) -> Result<Vec<Skill>> {
5945
use forge_domain::SkillRepository;
6046
self.infra.load_skills().await

crates/forge_main/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ forge_display.workspace = true
2323
forge_tracker.workspace = true
2424

2525
forge_spinner.workspace = true
26+
forge_repo.workspace = true
27+
forge_infra.workspace = true
28+
forge_services.workspace = true
2629
forge_select.workspace = true
2730

2831
merge.workspace = true

crates/forge_main/src/main.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::io::Read;
22
use std::panic;
33
use std::path::PathBuf;
4+
use std::sync::Arc;
45

56
use anyhow::{Context, Result};
67
use clap::Parser;
78
use forge_api::ForgeAPI;
9+
use forge_app::EnvironmentInfra;
810
use forge_config::ForgeConfig;
911
use forge_domain::TitleFormat;
12+
use forge_infra::ForgeInfra;
1013
use forge_main::{Cli, Sandbox, TitleDisplayExt, UI, tracker};
1114

1215
/// Enables ENABLE_VIRTUAL_TERMINAL_PROCESSING on the stdout console handle.
@@ -120,8 +123,25 @@ async fn run() -> Result<()> {
120123
(_, _) => std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
121124
};
122125

126+
// ForgeInfra is created once and reused across /new — it owns long-lived
127+
// resources (HTTP client, gRPC) that don't need to reset per conversation.
128+
let infra = Arc::new(ForgeInfra::new(cwd.clone(), config.clone()));
129+
123130
let mut ui = UI::init(cli, config, move |config| {
124-
ForgeAPI::init(cwd.clone(), config)
131+
// Config is intentionally unused here — ForgeInfra is frozen at startup.
132+
let _ = config;
133+
134+
// Fresh pool on every /new. SQLite's busy_timeout handles any brief
135+
// contention while the old pool drains from lingering hydration tasks.
136+
let db_pool = Arc::new(
137+
forge_repo::DatabasePool::try_from(forge_repo::PoolConfig::new(
138+
infra.get_environment().database_path(),
139+
))
140+
.context("Failed to open Forge database")?,
141+
);
142+
let repo = Arc::new(forge_repo::ForgeRepo::new(infra.clone(), db_pool));
143+
let services = Arc::new(forge_services::ForgeServices::new(repo.clone()));
144+
Ok(ForgeAPI::new(services, repo))
125145
})?;
126146
ui.run().await;
127147

crates/forge_main/src/ui.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ fn format_mcp_headers(server: &forge_domain::McpServerConfig) -> Option<String>
101101
}
102102
}
103103

104-
pub struct UI<A: ConsoleWriter, F: Fn(ForgeConfig) -> A> {
104+
pub struct UI<A: ConsoleWriter, F: Fn(ForgeConfig) -> anyhow::Result<A>> {
105105
markdown: MarkdownFormat,
106106
state: UIState,
107-
api: Arc<F::Output>,
107+
api: Arc<A>,
108108
new_api: Arc<F>,
109109
console: Console,
110110
command: Arc<ForgeCommandManager>,
@@ -115,7 +115,7 @@ pub struct UI<A: ConsoleWriter, F: Fn(ForgeConfig) -> A> {
115115
_guard: forge_tracker::Guard,
116116
}
117117

118-
impl<A: API + ConsoleWriter + 'static, F: Fn(ForgeConfig) -> A + Send + Sync> UI<A, F> {
118+
impl<A: API + ConsoleWriter + 'static, F: Fn(ForgeConfig) -> anyhow::Result<A> + Send + Sync> UI<A, F> {
119119
/// Writes a line to the console output
120120
/// Takes anything that implements ToString trait
121121
fn writeln<T: ToString>(&mut self, content: T) -> anyhow::Result<()> {
@@ -161,7 +161,7 @@ impl<A: API + ConsoleWriter + 'static, F: Fn(ForgeConfig) -> A + Send + Sync> UI
161161
async fn on_new(&mut self) -> Result<()> {
162162
let config = forge_config::ForgeConfig::read().unwrap_or_default();
163163
self.config = config.clone();
164-
self.api = Arc::new((self.new_api)(config));
164+
self.api = Arc::new((self.new_api)(config)?);
165165
self.init_state(false).await?;
166166

167167
// Set agent if provided via CLI
@@ -216,7 +216,7 @@ impl<A: API + ConsoleWriter + 'static, F: Fn(ForgeConfig) -> A + Send + Sync> UI
216216
/// from `forge config set` are reflected in new conversations
217217
pub fn init(cli: Cli, config: ForgeConfig, f: F) -> Result<Self> {
218218
// Parse CLI arguments first to get flags
219-
let api = Arc::new(f(config.clone()));
219+
let api = Arc::new(f(config.clone())?);
220220
let env = api.environment();
221221
let command = Arc::new(ForgeCommandManager::default());
222222
let spinner = SharedSpinner::new(SpinnerManager::new(api.clone()));

crates/forge_repo/src/database/pool.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#![allow(dead_code)]
21
use std::path::PathBuf;
32
use std::time::Duration;
43

crates/forge_repo/src/forge_repo.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use url::Url;
2626
use crate::agent::ForgeAgentRepository;
2727
use crate::context_engine::ForgeContextEngineRepository;
2828
use crate::conversation::ConversationRepositoryImpl;
29-
use crate::database::{DatabasePool, PoolConfig};
29+
use crate::database::DatabasePool;
3030
use crate::fs_snap::ForgeFileSnapshotService;
3131
use crate::fuzzy_search::ForgeFuzzySearchRepository;
3232
use crate::provider::{ForgeChatRepository, ForgeProviderRepository};
@@ -60,13 +60,17 @@ impl<
6060
+ HttpInfra,
6161
> ForgeRepo<F>
6262
{
63-
pub fn new(infra: Arc<F>) -> Self {
63+
/// Creates a new [`ForgeRepo`] with the provided infrastructure and a
64+
/// shared database pool.
65+
///
66+
/// The pool is created once at application startup and reused across
67+
/// `/new` conversation resets so that a fresh `ForgeRepo` never races
68+
/// with the previous instance for the same SQLite file.
69+
pub fn new(infra: Arc<F>, db_pool: Arc<DatabasePool>) -> Self {
6470
let env = infra.get_environment();
6571
let file_snapshot_service = Arc::new(ForgeFileSnapshotService::new(env.clone()));
66-
let db_pool =
67-
Arc::new(DatabasePool::try_from(PoolConfig::new(env.database_path())).unwrap());
6872
let conversation_repository = Arc::new(ConversationRepositoryImpl::new(
69-
db_pool.clone(),
73+
db_pool,
7074
env.workspace_hash(),
7175
));
7276

crates/forge_repo/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,8 @@ mod proto_generated {
1414
tonic::include_proto!("forge.v1");
1515
}
1616

17+
// Expose the database pool types so callers (e.g. forge_api) can construct
18+
// and share a pool without depending on internal module paths.
19+
pub use database::{DatabasePool, PoolConfig};
1720
// Only expose forge_repo container
1821
pub use forge_repo::*;

0 commit comments

Comments
 (0)