Skip to content

Commit 9fa448f

Browse files
refactor(Mountain): Optimize lock usage and restructure main entry
- Refactored `Binary::main` into `Binary::Fn` to explicitly build and block on the tokio runtime, improving control over the runtime configuration and shutdown sequence. This aligns with Tauri's requirements for mobile entry points. - Minimized lock contention in `DocumentProvider` by: - Reducing critical sections for document operations (open/save/apply changes) - Separating file I/O from state mutation - Using block scopes to release locks early - Improved `TerminalProvider` resource handling by: - Cloning the PTY input transmitter outside the lock - Using `take_writer()` for proper PTY resource management - Adjusting terminal disposal to remove from state immediately - Added `DispatchFrontendCommand` to Tauri's invoke handler to support command routing through `Track` - Simplified mobile entry point to call `Binary::Fn()` directly These changes enhance performance by reducing lock contention in core operations (document/terminal handling) and improve resource management during application shutdown, advancing the 'Application Startup & Handshake' and 'Integrated Terminal' workflows in Land's architecture.
1 parent 54aaa6b commit 9fa448f

4 files changed

Lines changed: 180 additions & 160 deletions

File tree

Source/Binary.rs

Lines changed: 89 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -27,91 +27,102 @@ use crate::{
2727
fn InitializeLogging() {
2828
let LogLevel = if cfg!(debug_assertions) { "debug" } else { "info" };
2929
if std::env::var("RUST_LOG").is_err() {
30+
// This is unsafe but only runs once at startup.
3031
unsafe { std::env::set_var("RUST_LOG", LogLevel) };
3132
}
3233
env_logger::init();
3334
}
3435

3536
/// The main asynchronous function that sets up and runs the application.
36-
#[tokio::main]
37-
pub async fn main() {
38-
InitializeLogging();
39-
info!("[Main] Starting Mountain application...");
40-
41-
// 1. Create the high-performance scheduler from the `Echo` crate.
42-
let NumberOfWorkers = num_cpus::get().max(2);
43-
let Scheduler = SchedulerBuilder::Create().WithWorkerCount(NumberOfWorkers).Build();
44-
45-
// We need an Arc<> to safely share the scheduler for shutdown handling.
46-
let SchedulerForShutdown = Arc::new(Scheduler);
47-
let SchedulerForRunTime = SchedulerForShutdown.clone();
48-
49-
let mut Builder = tauri::Builder::default();
50-
51-
Builder
52-
.manage(ApplicationState::default())
53-
.setup(move |Application| {
54-
info!("[Setup] Tauri setup hook initiated.");
55-
let ApplicationHandle = Application.handle().clone();
56-
57-
// 2. Create the application Environment and the Echo-powered
58-
// ApplicationRunTime.
59-
let Environment = Arc::new(MountainEnvironment::Create(ApplicationHandle.clone()));
60-
let RunTime = Arc::new(ApplicationRunTime::Create(SchedulerForRunTime, Environment));
61-
62-
// 3. Manage the ApplicationRunTime in Tauri's state so it's accessible to all
63-
// command handlers.
64-
ApplicationHandle.manage(RunTime);
65-
info!("[Setup] Echo scheduler and ApplicationRunTime created and managed.");
66-
67-
// 4. Spawn a detached task for all post-setup initializations.
68-
// This allows the UI to load faster while the backend finishes starting up.
69-
let PostSetupApplicationHandle = ApplicationHandle.clone();
70-
tauri::async_runtime::spawn(async move {
71-
info!("[SetupTask] Starting post-setup initializations...");
72-
let ApplicationState = PostSetupApplicationHandle.state::<ApplicationState>();
73-
74-
// TODO: Re-integrate handler logic for these initializations.
75-
// Handler::Configuration::InitializeConfiguration(&PostSetupApplicationHandle,
76-
// &ApplicationState).await;
77-
// Handler::ExtensionManagement::InitializeScanPaths(&
78-
// PostSetupApplicationHandle, &ApplicationState).await; ApplicationState.
79-
// ScanExtensions(&PostSetupApplicationHandle).await;
80-
81-
Vine::Server::Initialize::Initialize(PostSetupApplicationHandle.clone(), "[::1]:50051".to_string());
82-
InitializeCocoon(&PostSetupApplicationHandle).await;
83-
84-
info!("[SetupTask] Post-setup initializations complete.");
85-
});
86-
87-
Ok(())
88-
})
89-
.plugin(tauri_plugin_dialog::init())
90-
.invoke_handler(tauri::generate_handler![
91-
// TODO: Re-integrate all Tauri command handlers here.
92-
])
93-
.build(tauri::generate_context!())
94-
.expect("FATAL: Error while building Mountain Tauri application")
95-
.run(move |ApplicationHandle, Event| {
96-
if let RunEvent::ExitRequested { api, .. } = Event {
97-
info!("[RunEvent] Exit requested. Initiating graceful shutdown...");
98-
api.prevent_exit();
99-
let SchedulerHandle = SchedulerForShutdown.clone();
100-
101-
// Spawn a new async task to handle the shutdown to avoid blocking the
102-
// Tauri event loop.
103-
tokio::spawn(async move {
104-
info!("[Shutdown] Shutting down Echo scheduler...");
105-
if let Ok(mut Scheduler) = Arc::try_unwrap(SchedulerHandle) {
106-
Scheduler.Stop().await;
107-
} else {
108-
error!("[Shutdown] Could not get exclusive access to scheduler for shutdown.");
37+
pub fn Fn() {
38+
tokio::runtime::Builder::new_multi_thread()
39+
.enable_all()
40+
.build()
41+
.expect("Cannot build.")
42+
.block_on(async {
43+
InitializeLogging();
44+
info!("[Main] Starting Mountain application...");
45+
46+
// 1. Create the high-performance scheduler from the `Echo` crate.
47+
let NumberOfWorkers = num_cpus::get().max(2);
48+
let Scheduler = SchedulerBuilder::Create().WithWorkerCount(NumberOfWorkers).Build();
49+
50+
// We need an Arc<> to safely share the scheduler for shutdown handling.
51+
let SchedulerForShutdown = Arc::new(Scheduler);
52+
let SchedulerForRunTime = SchedulerForShutdown.clone();
53+
54+
let mut Builder = tauri::Builder::default();
55+
56+
Builder
57+
.manage(ApplicationState::default())
58+
.setup(move |Application| {
59+
info!("[Setup] Tauri setup hook initiated.");
60+
let ApplicationHandle = Application.handle().clone();
61+
62+
// 2. Create the application Environment and the Echo-powered
63+
// ApplicationRunTime.
64+
let Environment = Arc::new(MountainEnvironment::Create(ApplicationHandle.clone()));
65+
let RunTime = Arc::new(ApplicationRunTime::Create(SchedulerForRunTime, Environment));
66+
67+
// 3. Manage the ApplicationRunTime in Tauri's state so it's accessible to all
68+
// command handlers.
69+
ApplicationHandle.manage(RunTime);
70+
info!("[Setup] Echo scheduler and ApplicationRunTime created and managed.");
71+
72+
// 4. Spawn a detached task for all post-setup initializations.
73+
// This allows the UI to load faster while the backend finishes starting up.
74+
let PostSetupApplicationHandle = ApplicationHandle.clone();
75+
tauri::async_runtime::spawn(async move {
76+
info!("[SetupTask] Starting post-setup initializations...");
77+
let _ApplicationState = PostSetupApplicationHandle.state::<ApplicationState>();
78+
79+
// TODO: Re-integrate handler logic for these initializations.
80+
// Handler::Configuration::InitializeConfiguration(&PostSetupApplicationHandle,
81+
// &ApplicationState).await;
82+
// Handler::ExtensionManagement::InitializeScanPaths(&
83+
// PostSetupApplicationHandle, &ApplicationState).await; ApplicationState.
84+
// ScanExtensions(&PostSetupApplicationHandle).await;
85+
86+
Vine::Server::Initialize::Initialize(
87+
PostSetupApplicationHandle.clone(),
88+
"[::1]:50051".to_string(),
89+
);
90+
InitializeCocoon(&PostSetupApplicationHandle).await;
91+
92+
info!("[SetupTask] Post-setup initializations complete.");
93+
});
94+
95+
Ok(())
96+
})
97+
.plugin(tauri_plugin_dialog::init())
98+
.invoke_handler(tauri::generate_handler![
99+
// TODO: Re-integrate all Tauri command handlers here.
100+
crate::Track::DispatchLogic::DispatchFrontendCommand,
101+
])
102+
.build(tauri::generate_context!())
103+
.expect("FATAL: Error while building Mountain Tauri application")
104+
.run(move |ApplicationHandle, Event| {
105+
if let RunEvent::ExitRequested { api, .. } = Event {
106+
info!("[RunEvent] Exit requested. Initiating graceful shutdown...");
107+
api.prevent_exit();
108+
let SchedulerHandle = SchedulerForShutdown.clone();
109+
let ApplicationHandleClone = ApplicationHandle.clone();
110+
111+
// Spawn a new async task to handle the shutdown to avoid blocking the
112+
// Tauri event loop.
113+
tokio::spawn(async move {
114+
info!("[Shutdown] Shutting down Echo scheduler...");
115+
if let Ok(mut Scheduler) = Arc::try_unwrap(SchedulerHandle) {
116+
Scheduler.Stop().await;
117+
} else {
118+
error!("[Shutdown] Could not get exclusive access to scheduler for shutdown.");
119+
}
120+
info!("[Shutdown] Shutdown complete. Exiting application.");
121+
ApplicationHandleClone.exit(0);
122+
});
109123
}
110-
info!("[Shutdown] Shutdown complete. Exiting application.");
111-
ApplicationHandle.exit(0);
112124
});
113-
}
114-
});
115125

116-
info!("[Main] Mountain application has shut down.");
126+
info!("[Main] Mountain application has shut down.");
127+
});
117128
}

Source/Environment/DocumentProvider.rs

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,20 @@ impl DocumentProvider for MountainEnvironment {
3535
let URI = Utility::GetURLFromURIComponentsDTO(&URIComponentsDTO)?;
3636
info!("[DocumentProvider] Opening document: {}", URI);
3737

38-
let mut OpenDocumentsGuard = self
39-
.ApplicationState
40-
.OpenDocuments
41-
.lock()
42-
.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
43-
44-
if let Some(ExistingDocument) = OpenDocumentsGuard.get(URI.as_str()) {
45-
info!("[DocumentProvider] Document {} is already open.", URI);
46-
return Ok(ExistingDocument.URI.clone());
47-
}
38+
// First, check if the document is already open.
39+
{
40+
let OpenDocumentsGuard = self
41+
.ApplicationState
42+
.OpenDocuments
43+
.lock()
44+
.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
45+
if let Some(ExistingDocument) = OpenDocumentsGuard.get(URI.as_str()) {
46+
info!("[DocumentProvider] Document {} is already open.", URI);
47+
return Ok(ExistingDocument.URI.clone());
48+
}
49+
} // The lock is dropped here.
4850

51+
// Read file content *before* acquiring the lock again for mutation.
4952
let FileContent = if let Some(c) = Content {
5053
c
5154
} else if URI.scheme() == "file" {
@@ -59,15 +62,18 @@ impl DocumentProvider for MountainEnvironment {
5962
String::from_utf8(FileSystemReader.ReadFile(&FilePath).await?)
6063
.map_err(|e| CommonError::FileSystemIO { Path:FilePath, Description:e.to_string() })?
6164
} else {
62-
// For non-file schemes without initial content, start with an empty document.
6365
String::new()
6466
};
6567

6668
let NewDocument = DocumentStateDTO::Create(URI.clone(), LanguageIdentifier, FileContent);
6769
let DTOForNotification = NewDocument.ToDTO();
6870

69-
OpenDocumentsGuard.insert(URI.to_string(), NewDocument);
70-
drop(OpenDocumentsGuard);
71+
// Now, acquire the lock just to insert the new document.
72+
self.ApplicationState
73+
.OpenDocuments
74+
.lock()
75+
.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
76+
.insert(URI.to_string(), NewDocument);
7177

7278
NotifyModelAdded(self, &DTOForNotification).await;
7379
Ok(URI)
@@ -77,45 +83,44 @@ impl DocumentProvider for MountainEnvironment {
7783
async fn SaveDocument(&self, URI:Url) -> Result<bool, CommonError> {
7884
info!("[DocumentProvider] Saving document: {}", URI);
7985

80-
let mut OpenDocumentsGuard = self
81-
.ApplicationState
82-
.OpenDocuments
83-
.lock()
84-
.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
85-
86-
if let Some(Document) = OpenDocumentsGuard.get_mut(URI.as_str()) {
87-
if URI.scheme() != "file" {
88-
return Err(CommonError::NotImplemented {
89-
FeatureName:format!("Saving for URI scheme '{}'", URI.scheme()),
90-
});
86+
let (ContentBytes, FilePath) = {
87+
let mut OpenDocumentsGuard = self
88+
.ApplicationState
89+
.OpenDocuments
90+
.lock()
91+
.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
92+
93+
if let Some(Document) = OpenDocumentsGuard.get_mut(URI.as_str()) {
94+
if URI.scheme() != "file" {
95+
return Err(CommonError::NotImplemented {
96+
FeatureName:format!("Saving for URI scheme '{}'", URI.scheme()),
97+
});
98+
}
99+
Document.IsDirty = false;
100+
(
101+
Document.GetText().into_bytes(),
102+
URI.to_file_path().unwrap(), // Safe due to scheme check
103+
)
104+
} else {
105+
return Err(CommonError::FileSystemNotFound(URI.to_file_path().unwrap_or_default()));
91106
}
107+
}; // Lock is dropped here.
92108

93-
let FileSystemWriter:Arc<dyn FileSystemWriter> = self.Require();
94-
let FilePath = URI.to_file_path().unwrap(); // Safe due to scheme check
95-
let ContentBytes = Document.GetText().into_bytes();
96-
97-
FileSystemWriter.WriteFile(&FilePath, ContentBytes, true, true).await?;
98-
Document.IsDirty = false;
99-
drop(OpenDocumentsGuard);
109+
let FileSystemWriter:Arc<dyn FileSystemWriter> = self.Require();
110+
FileSystemWriter.WriteFile(&FilePath, ContentBytes, true, true).await?;
100111

101-
NotifyModelSaved(self, &URI).await;
102-
Ok(true)
103-
} else {
104-
Err(CommonError::FileSystemNotFound(URI.to_file_path().unwrap_or_default()))
105-
}
112+
NotifyModelSaved(self, &URI).await;
113+
Ok(true)
106114
}
107115

108116
/// Saves a document to a new location.
109117
async fn SaveDocumentAs(&self, _OriginalURI:Url, _NewTargetURI:Option<Url>) -> Result<Option<Url>, CommonError> {
110-
// A full implementation would use the UserInterfaceProvider to prompt the user.
111118
warn!("[DocumentProvider] SaveDocumentAs is not fully implemented.");
112119
Err(CommonError::NotImplemented { FeatureName:"SaveDocumentAs".into() })
113120
}
114121

115122
/// Saves all currently dirty documents.
116123
async fn SaveAllDocuments(&self, _IncludeUntitled:bool) -> Result<Vec<bool>, CommonError> {
117-
// A full implementation would iterate `ApplicationState.OpenDocuments` and
118-
// save dirty ones.
119124
warn!("[DocumentProvider] SaveAllDocuments is not fully implemented.");
120125
Err(CommonError::NotImplemented { FeatureName:"SaveAllDocuments".into() })
121126
}
@@ -131,22 +136,23 @@ impl DocumentProvider for MountainEnvironment {
131136
_IsRedoing:bool,
132137
) -> Result<(), CommonError> {
133138
trace!("[DocumentProvider] Applying changes to document: {}", URI);
134-
let mut OpenDocumentsGuard = self
135-
.ApplicationState
136-
.OpenDocuments
137-
.lock()
138-
.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
139-
140-
if let Some(Document) = OpenDocumentsGuard.get_mut(URI.as_str()) {
141-
if let Err(e) = Document.ApplyChanges(NewVersionIdentifier, &ChangesDTOCollection) {
142-
return Err(CommonError::InvalidArgument { ArgumentName:"ChangesDTOCollection".into(), Reason:e });
139+
{
140+
let mut OpenDocumentsGuard = self
141+
.ApplicationState
142+
.OpenDocuments
143+
.lock()
144+
.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
145+
146+
if let Some(Document) = OpenDocumentsGuard.get_mut(URI.as_str()) {
147+
if let Err(e) = Document.ApplyChanges(NewVersionIdentifier, &ChangesDTOCollection) {
148+
return Err(CommonError::InvalidArgument { ArgumentName:"ChangesDTOCollection".into(), Reason:e });
149+
}
150+
Document.IsDirty = true; // Assume any change makes it dirty
151+
} else {
152+
warn!("[DocumentProvider] Received changes for unknown document: {}", URI);
153+
return Ok(());
143154
}
144-
Document.IsDirty = true; // Assume any change makes it dirty
145-
} else {
146-
warn!("[DocumentProvider] Received changes for unknown document: {}", URI);
147-
return Ok(());
148-
}
149-
drop(OpenDocumentsGuard);
155+
} // Lock is dropped here.
150156

151157
NotifyModelChanged(self, &URI, NewVersionIdentifier, ChangesDTOCollection).await;
152158
Ok(())

0 commit comments

Comments
 (0)