diff --git a/rust/demo-app/move/Move.lock b/rust/demo-app/move/Move.lock index dc993909..2edc8594 100644 --- a/rust/demo-app/move/Move.lock +++ b/rust/demo-app/move/Move.lock @@ -30,6 +30,25 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.22.0" -edition = "legacy" +compiler-version = "1.23.0" +edition = "2024.beta" flavor = "sui" + +[env] + + + +[env.suibase] +test = 12 + +[env.localnet_proxy] +chain-id = "599ba668" +original-published-id = "0x4fc94af2a4f1982f56ad734a50b8df98cbf7e45553e0dbb475daab6cd0ab5436" +latest-published-id = "0x4fc94af2a4f1982f56ad734a50b8df98cbf7e45553e0dbb475daab6cd0ab5436" +published-version = "1" + +[env.testnet_proxy] +chain-id = "4c78adac" +original-published-id = "0xb7f03f869344fc7359bc709b54f923e581c9d1ceb8bbfc673dab182d7b3f50a0" +latest-published-id = "0xb7f03f869344fc7359bc709b54f923e581c9d1ceb8bbfc673dab182d7b3f50a0" +published-version = "1" diff --git a/rust/suibase/crates/dtp-daemon/src/api/impl_dtp_api.rs b/rust/suibase/crates/dtp-daemon/src/api/impl_dtp_api.rs index 9db67619..2d26a477 100644 --- a/rust/suibase/crates/dtp-daemon/src/api/impl_dtp_api.rs +++ b/rust/suibase/crates/dtp-daemon/src/api/impl_dtp_api.rs @@ -774,10 +774,10 @@ impl DtpApiServer for DtpApiImpl { .create_send_callback(workdir_idx, host_sla_idx, tc_address.clone()) .await; - let message = if message.is_none() { - "ping".to_string() + let message = if let Some(message) = message { + message } else { - message.unwrap() + "ping".to_string() }; let _ = { diff --git a/rust/suibase/crates/dtp-daemon/src/workers/websocket_worker_io.rs b/rust/suibase/crates/dtp-daemon/src/workers/websocket_worker_io.rs index 49f457f9..047d76a7 100644 --- a/rust/suibase/crates/dtp-daemon/src/workers/websocket_worker_io.rs +++ b/rust/suibase/crates/dtp-daemon/src/workers/websocket_worker_io.rs @@ -332,19 +332,18 @@ impl WebSocketWorkerIOThread { // Assume this is a valid response to the subscription request. // Get the host_sla_idx and trig a response for it if exists in the globals. let host_sla_idx = ipipe.host_sla_idx; + { - { - let mut conns_state_guard = self - .params - .globals - .dtp_conns_state_client(self.params.workdir_idx) - .write() - .await; - let conns_state = &mut *conns_state_guard; - - conns_state.trigger_subs_callback(host_sla_idx); - } - }; + let mut conns_state_guard = self + .params + .globals + .dtp_conns_state_client(self.params.workdir_idx) + .write() + .await; + let conns_state = &mut *conns_state_guard; + + conns_state.trigger_subs_callback(host_sla_idx); + } } } } @@ -757,9 +756,7 @@ impl WebSocketWorkerIOThread { .await; let conns_state = &mut *conns_state_guard; - let host_sla_idx = conns_state - .conns - .get_if_some(service_idx, &srv_host_addr, 0); + let host_sla_idx = conns_state.conns.get_if_some(service_idx, srv_host_addr, 0); if host_sla_idx.is_none() { // TODO It should have been created on "publish", but can be created here as needed. diff --git a/rust/suibase/crates/suibase-daemon/src/api/def_header.rs b/rust/suibase/crates/suibase-daemon/src/api/def_header.rs index 771bafd3..7bb8a1d6 100644 --- a/rust/suibase/crates/suibase-daemon/src/api/def_header.rs +++ b/rust/suibase/crates/suibase-daemon/src/api/def_header.rs @@ -74,6 +74,12 @@ impl Versioned { self.uuid.clone() } + // When owner did get_mut_data and made modifications to the data + // directly, it must call this method to increment the version. + pub fn inc_uuid(&mut self) { + self.uuid.increment(); + } + // readonly access pub fn get_data(&self) -> &T { &self.data diff --git a/rust/suibase/crates/suibase-daemon/src/api/def_methods.rs b/rust/suibase/crates/suibase-daemon/src/api/def_methods.rs index 35eb8186..996deecd 100644 --- a/rust/suibase/crates/suibase-daemon/src/api/def_methods.rs +++ b/rust/suibase/crates/suibase-daemon/src/api/def_methods.rs @@ -185,16 +185,6 @@ pub struct WorkdirStatusResponse { // Finer grain status for each process/feature/service. #[serde(skip_serializing_if = "Option::is_none")] pub services: Option>, - - // This is the output when the option 'display' is true. - // Will also change the default to false for all the other fields. - #[serde(skip_serializing_if = "Option::is_none")] - pub display: Option, - - // This is the output when the option 'debug' is true. - // Will also change the default to true for the other fields. - #[serde(skip_serializing_if = "Option::is_none")] - pub debug: Option, } impl WorkdirStatusResponse { @@ -207,8 +197,6 @@ impl WorkdirStatusResponse { network_version: None, asui_selection: None, services: None, - display: None, - debug: None, } } } @@ -369,7 +357,7 @@ impl Default for MoveConfig { #[serde_as] #[derive(Clone, Debug, JsonSchema, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct PackagesConfigResponse { +pub struct WorkdirPackagesResponse { pub header: Header, // One entry per distinct Move.toml published. @@ -384,38 +372,25 @@ pub struct PackagesConfigResponse { // Among the move_configs, there is an additional constraint: // - The MoveConfig.path must all be distinct. // - #[serde(skip_serializing_if = "Option::is_none")] - pub move_configs: Option>, - - // This is the output when the option 'display' is true. - // Will also change the default to false for all the other fields. - #[serde(skip_serializing_if = "Option::is_none")] - pub display: Option, - - // This is the output when the option 'debug' is true. - // Will also change the default to true for the other fields. - #[serde(skip_serializing_if = "Option::is_none")] - pub debug: Option, + pub move_configs: HashMap, } -impl PackagesConfigResponse { +impl WorkdirPackagesResponse { pub fn new() -> Self { Self { header: Header::default(), - move_configs: None, - display: None, - debug: None, + move_configs: HashMap::new(), } } } -impl Default for PackagesConfigResponse { +impl Default for WorkdirPackagesResponse { fn default() -> Self { Self::new() } } -impl VersionedEq for PackagesConfigResponse { +impl VersionedEq for WorkdirPackagesResponse { fn versioned_eq(&self, other: &Self) -> bool { // Purposely do not include header in the comparison. self.move_configs == other.move_configs @@ -527,16 +502,13 @@ pub trait PackagesApi { last_ts: Option, ) -> RpcResult; - #[method(name = "getWorkdirPackagesConfig")] - async fn get_workdir_packages_config( + #[method(name = "getWorkdirPackages")] + async fn get_workdir_packages( &self, workdir: String, - data: Option, - display: Option, - debug: Option, method_uuid: Option, data_uuid: Option, - ) -> RpcResult; + ) -> RpcResult; #[method(name = "prePublish")] async fn pre_publish( diff --git a/rust/suibase/crates/suibase-daemon/src/api/impl_general_api.rs b/rust/suibase/crates/suibase-daemon/src/api/impl_general_api.rs index 95b7f580..899baefc 100644 --- a/rust/suibase/crates/suibase-daemon/src/api/impl_general_api.rs +++ b/rust/suibase/crates/suibase-daemon/src/api/impl_general_api.rs @@ -15,6 +15,7 @@ use super::{ }; use super::def_header::Versioned; +use crate::api::WorkdirPackagesResponse; pub struct GeneralApiImpl { pub globals: Globals, @@ -376,17 +377,17 @@ impl GeneralApiImpl { // Does costly CLI calls. Use only on initialization, recovery, etc... // Update the status of workdirs one-by-one until you get one with a known asui_selection! - for workdir_idx in 0..WORKDIRS_KEYS.len() { + for (workdir_idx, workdir_key) in WORKDIRS_KEYS.iter().enumerate() { let mut api_mutex_guard = self.globals.get_api_mutex(workdir_idx as u8).lock().await; let api_mutex = &mut *api_mutex_guard; - let last_api_call_timestamp = &mut api_mutex.last_api_call_timestamp; + let last_api_call_timestamp = &mut api_mutex.last_get_workdir_status_time; // Use the internal implementation { let update_result = self .update_globals_workdir_status( - WORKDIRS_KEYS[workdir_idx].to_string(), + workdir_key.to_string(), workdir_idx as u8, last_api_call_timestamp, ) @@ -472,10 +473,11 @@ impl GeneralApiServer for GeneralApiImpl { let mut api_mutex_guard = self.globals.get_api_mutex(workdir_idx).lock().await; let api_mutex = &mut *api_mutex_guard; - let last_api_call_timestamp = &mut api_mutex.last_api_call_timestamp; + let last_api_call_timestamp = &mut api_mutex.last_get_workdir_status_time; - // Use the internal implementation + // Section for getWorkdirStatus version. { + // Use the internal implementation let update_result = self .update_globals_workdir_status(workdir, workdir_idx, last_api_call_timestamp) .await; @@ -483,11 +485,27 @@ impl GeneralApiServer for GeneralApiImpl { // Read access to globals for versioning all components. // If no change, then the version remains the same for that global component. if let Ok(results) = update_result { - resp.versions.push(results.0); + let mut status_resp = results.0; + status_resp.key = None; // No need to repeat the key here (already in the getVersions header). + resp.versions.push(status_resp); resp.asui_selection = results.1; } } + // Section for getWorkdirPackages version. + { + // Get the data from the globals.get_packages + let globals_read_guard = self.globals.get_packages(workdir_idx).read().await; + let globals = &*globals_read_guard; + if let Some(ui) = &globals.ui { + // Create an header that has the same UUID as the globals. + let mut wp_resp = WorkdirPackagesResponse::new(); + wp_resp.header.method = "getWorkdirPackages".to_string(); + wp_resp.header.set_from_uuids(ui.get_uuid()); + resp.versions.push(wp_resp.header); + } + } + // Initialize the uuids in the response header. // Use api_mutex.last_responses to detect if this response is equivalent to the previous one. // If not, increment the uuid_data. @@ -529,7 +547,7 @@ impl GeneralApiServer for GeneralApiImpl { let mut api_mutex_guard = self.globals.get_api_mutex(workdir_idx).lock().await; let api_mutex = &mut *api_mutex_guard; - let last_api_call_timestamp = &mut api_mutex.last_api_call_timestamp; + let last_api_call_timestamp = &mut api_mutex.last_get_workdir_status_time; // Use the internal implementation (same logic as done with get_versions). @@ -568,7 +586,7 @@ impl GeneralApiServer for GeneralApiImpl { } } let mut resp = ui.get_data().clone(); - resp.header.set_from_uuids(&ui.get_uuid()); + resp.header.set_from_uuids(ui.get_uuid()); return Ok(resp); } else { return Err( diff --git a/rust/suibase/crates/suibase-daemon/src/api/impl_packages_api.rs b/rust/suibase/crates/suibase-daemon/src/api/impl_packages_api.rs index fd9f1fae..12060fec 100644 --- a/rust/suibase/crates/suibase-daemon/src/api/impl_packages_api.rs +++ b/rust/suibase/crates/suibase-daemon/src/api/impl_packages_api.rs @@ -8,11 +8,11 @@ use chrono::Utc; use crate::admin_controller::{AdminController, AdminControllerTx}; use crate::api::RpcSuibaseError; -use crate::shared_types::{Globals, GlobalsPackagesConfigST, GlobalsWorkdirsST}; +use crate::shared_types::{Globals, GlobalsWorkdirsST}; use super::{ - MoveConfig, PackageInstance, PackagesApiServer, PackagesConfigResponse, RpcInputError, - SuccessResponse, WorkdirSuiEventsResponse, + MoveConfig, PackageInstance, PackagesApiServer, RpcInputError, SuccessResponse, Versioned, + WorkdirPackagesResponse, WorkdirSuiEventsResponse, }; pub struct PackagesApiImpl { @@ -145,55 +145,68 @@ impl PackagesApiServer for PackagesApiImpl { // Insert the data in the globals. { - let mut globals_write_guard = self.globals.packages_config.write().await; + let mut globals_write_guard = self.globals.get_packages(workdir_idx).write().await; let globals = &mut *globals_write_guard; - let move_configs = - GlobalsPackagesConfigST::get_mut_move_configs(&mut globals.workdirs, workdir_idx); + // Create the globals.ui if does not exists. + if globals.ui.is_none() { + let versioned_resp = Versioned::new(WorkdirPackagesResponse::new()); + globals.ui = Some(versioned_resp); + } - let mut move_config = move_configs.get_mut(&package_uuid); - if move_config.is_none() { - // Delete any other move_configs element where path equals move_toml_path. - move_configs.retain(|_, config| { - if let Some(path) = &config.path { - if path == &move_toml_path { - return false; + if let Some(ui) = &mut globals.ui { + let wp_resp = ui.get_mut_data(); + let move_configs = &mut wp_resp.move_configs; + let mut move_config = move_configs.get_mut(&package_uuid); + + if move_config.is_none() { + // Delete any other move_configs element where path equals move_toml_path. + move_configs.retain(|_, config| { + if let Some(path) = &config.path { + if path == &move_toml_path { + return false; + } } + true + }); + + let mut new_move_config = MoveConfig::new(); + new_move_config.path = Some(move_toml_path.clone()); + move_configs.insert(package_uuid.clone(), new_move_config); + move_config = Some(move_configs.get_mut(&package_uuid).unwrap()); + } + let move_config = move_config.unwrap(); + + if let Some(current_package) = move_config.latest_package.take() { + if current_package.package_id == package_id { + // This package is already the latest. Ignore this redundant publish request. + move_config.latest_package = Some(current_package); // Put it back. + resp.result = true; + resp.info = Some("Package is already the current one.".to_string()); + return Ok(resp); } - true - }); - let mut new_move_config = MoveConfig::new(); - new_move_config.path = Some(move_toml_path.clone()); - move_configs.insert(package_uuid.clone(), new_move_config); - move_config = Some(move_configs.get_mut(&package_uuid).unwrap()); - } - let move_config = move_config.unwrap(); - - if let Some(current_package) = move_config.latest_package.take() { - if current_package.package_id == package_id { - // This package is already the latest. Ignore this redundant publish request. - move_config.latest_package = Some(current_package); // Put it back. - resp.result = true; - resp.info = Some("Package is already the current one.".to_string()); - return Ok(resp); + // Move current package into the list of previous packages. + move_config.older_packages.push(current_package); } - // Move current package into the list of previous packages. - move_config.older_packages.push(current_package); - } + // Initialize this new current package. + move_config.latest_package = Some(PackageInstance::new( + package_id.clone(), + package_name.clone(), + package_timestamp.clone(), + )); + + // Make sure the latest known path is correctly reflected in globals. + if move_config.path.is_none() + || (move_config.path.as_ref().unwrap() != &move_toml_path) + { + move_config.path = Some(move_toml_path.clone()); + } - // Initialize this new current package. - move_config.latest_package = Some(PackageInstance::new( - package_id.clone(), - package_name.clone(), - package_timestamp.clone(), - )); - - // Make sure the latest known path is correctly reflected in globals. - if move_config.path.is_none() || (move_config.path.as_ref().unwrap() != &move_toml_path) - { - move_config.path = Some(move_toml_path.clone()); + // Always bump the UUIDs. + ui.inc_uuid(); + ui.write_uuids_into_header_param(&mut resp.header); } } @@ -214,27 +227,12 @@ impl PackagesApiServer for PackagesApiImpl { Ok(resp) } - async fn get_workdir_packages_config( + async fn get_workdir_packages( &self, workdir: String, - data: Option, - display: Option, - debug: Option, method_uuid: Option, data_uuid: Option, - ) -> RpcResult { - // data/display/debug allow variations of how the output - // is produced (and they may be combined). - // - // They all default to false when not specified - // with the exception of data defaulting to true when - // the other (display and debug) are false. - - // TODO Implement display/debug requests. - let debug = debug.unwrap_or(false); - let display = display.unwrap_or(debug); - let _data = data.unwrap_or(!(debug || display)); - + ) -> RpcResult { // Verify workdir param is OK and get its corresponding workdir_idx. let workdir_idx = match GlobalsWorkdirsST::get_workdir_idx_by_name(&self.globals, &workdir) .await @@ -243,44 +241,45 @@ impl PackagesApiServer for PackagesApiImpl { None => return Err(RpcInputError::InvalidParams("workdir".to_string(), workdir).into()), }; - let mut resp_ready: Option = None; + let mut resp_ready: Option = None; + + if method_uuid.is_none() && data_uuid.is_none() { + // Just return what is already built in-memory, or empty. - // Just return what is already built in-memory, or empty. - { // Get the globals for the target workdir_idx. - let globals_read_guard = self.globals.packages_config.read().await; + let globals_read_guard = self.globals.get_packages(workdir_idx).read().await; let globals = &*globals_read_guard; - let globals = globals.workdirs.get_if_some(workdir_idx); - - if let Some(globals) = globals { - if let Some(ui) = &globals.ui { - if let (Some(method_uuid), Some(data_uuid)) = (method_uuid, data_uuid) { - let globals_data_uuid = ui.get_uuid().get_data_uuid(); - if data_uuid == globals_data_uuid { - let globals_method_uuid = ui.get_uuid().get_method_uuid(); - if method_uuid == globals_method_uuid { - // The caller requested the same data that it already have a copy of. - // Respond with the same UUID as a way to say "no change". - let mut resp = PackagesConfigResponse::new(); - ui.write_uuids_into_header_param(&mut resp.header); - resp_ready = Some(resp); - } + + if let Some(ui) = &globals.ui { + if let (Some(method_uuid), Some(data_uuid)) = (method_uuid, data_uuid) { + let globals_data_uuid = ui.get_uuid().get_data_uuid(); + if data_uuid == globals_data_uuid { + let globals_method_uuid = ui.get_uuid().get_method_uuid(); + if method_uuid == globals_method_uuid { + // The caller requested the same data that it already have a copy of. + // Respond with the same UUID as a way to say "no change". + let mut new_resp = WorkdirPackagesResponse::new(); + ui.write_uuids_into_header_param(&mut new_resp.header); + resp_ready = Some(new_resp); } - } else { - // The caller did not specify a method_uuid or data_uuid and - // there is an in-memory response ready. Just respond with it. - resp_ready = Some(ui.get_data().clone()); } + } else { + // The caller did not specify a method_uuid or data_uuid and + // there is an in-memory response ready. Just respond with it. + let mut existing_resp = ui.get_data().clone(); + ui.write_uuids_into_header_param(&mut existing_resp.header); + resp_ready = Some(existing_resp); } } } if resp_ready.is_none() { - resp_ready = Some(PackagesConfigResponse::new()); + // There was no data in the globals, so revert to empty response. + resp_ready = Some(WorkdirPackagesResponse::new()); } let mut resp_ready = resp_ready.unwrap(); - resp_ready.header.method = "getPackagesConfig".to_string(); + resp_ready.header.method = "getWorkdirPackages".to_string(); resp_ready.header.key = Some(workdir.clone()); return Ok(resp_ready); } diff --git a/rust/suibase/crates/suibase-daemon/src/shared_types/globals.rs b/rust/suibase/crates/suibase-daemon/src/shared_types/globals.rs index 4219286d..b374987f 100644 --- a/rust/suibase/crates/suibase-daemon/src/shared_types/globals.rs +++ b/rust/suibase/crates/suibase-daemon/src/shared_types/globals.rs @@ -4,24 +4,21 @@ // // Simple design: // -// - Group of global variables shared between the subsystems/threads -// (AdminController, NetworkMonitor, ProxyServer etc...) +// - Each thread get a reference count (Arc) on the same multi-threaded 'MT' instance. // -// - Each thread get a reference count (Arc) on the same 'multi-thread 'MT' instance. +// - A thread can lock read/write access on the single-thread 'ST' instance. // -// - A thread can lock read/write access on the single writer thread 'ST' instance. -// -// - Although globals are not encouraged, they are carefully used here in a balanced way -// and as a stepping stone toward a more optimized design. Ask the dev for more details. +// - Although globals are not encouraged, they are carefully used here in a balanced way. Also, +// mutex blocking are carefully kept fine grain (split by workdir, by data type etc...). // // Note: This app also uses message passing between threads to minimize sharing. See NetmonMsg as an example. use std::sync::Arc; -use crate::api::{Versioned, VersionsResponse, WorkdirStatusResponse}; +use crate::api::{Versioned, VersionsResponse, WorkdirPackagesResponse, WorkdirStatusResponse}; use crate::shared_types::InputPort; use common::basic_types::{AutoSizeVec, ManagedVec, WorkdirIdx}; -use super::{workdirs, GlobalsEventsDataST, GlobalsPackagesConfigST, GlobalsWorkdirsST}; +use super::{workdirs, GlobalsEventsDataST, GlobalsWorkdirPackagesST, GlobalsWorkdirsST}; #[derive(Debug)] pub struct GlobalsProxyST { @@ -94,6 +91,7 @@ pub struct APIResponses { // pub versions: Option>, pub workdir_status: Option, + pub workdir_packages: Option, } impl APIResponses { @@ -101,6 +99,7 @@ impl APIResponses { Self { versions: None, workdir_status: None, + workdir_packages: None, } } } @@ -113,14 +112,16 @@ impl Default for APIResponses { #[derive(Debug)] pub struct GlobalsAPIMutexST { - pub last_api_call_timestamp: tokio::time::Instant, + pub last_get_workdir_status_time: tokio::time::Instant, + pub last_get_workdir_packages_time: tokio::time::Instant, pub last_responses: APIResponses, } impl GlobalsAPIMutexST { pub fn new() -> Self { Self { - last_api_call_timestamp: tokio::time::Instant::now(), + last_get_workdir_status_time: tokio::time::Instant::now(), + last_get_workdir_packages_time: tokio::time::Instant::now(), last_responses: APIResponses::default(), } } @@ -171,7 +172,7 @@ impl Default for GlobalsConfigST { pub type GlobalsProxyMT = Arc>; pub type GlobalsWorkdirStatusMT = Arc>; pub type GlobalsConfigMT = Arc>; -pub type GlobalsPackagesConfigMT = Arc>; +pub type GlobalsWorkdirPackagesMT = Arc>; pub type GlobalsEventsDataMT = Arc>; pub type GlobalsWorkdirsMT = Arc>; pub type GlobalsAPIMutexMT = Arc>; @@ -206,7 +207,10 @@ pub struct Globals { pub status_mainnet: GlobalsWorkdirStatusMT, // Configuration related to Sui Move modules, particularly for monitoring management. - pub packages_config: GlobalsPackagesConfigMT, + pub packages_localnet: GlobalsWorkdirPackagesMT, + pub packages_devnet: GlobalsWorkdirPackagesMT, + pub packages_testnet: GlobalsWorkdirPackagesMT, + pub packages_mainnet: GlobalsWorkdirPackagesMT, // In-memory access to events data of actively monitored modules. pub events_data_localnet: GlobalsEventsDataMT, @@ -231,7 +235,10 @@ impl Globals { status_devnet: Arc::new(tokio::sync::RwLock::new(GlobalsWorkdirStatusST::new())), status_testnet: Arc::new(tokio::sync::RwLock::new(GlobalsWorkdirStatusST::new())), status_mainnet: Arc::new(tokio::sync::RwLock::new(GlobalsWorkdirStatusST::new())), - packages_config: Arc::new(tokio::sync::RwLock::new(GlobalsPackagesConfigST::new())), + packages_localnet: Arc::new(tokio::sync::RwLock::new(GlobalsWorkdirPackagesST::new())), + packages_devnet: Arc::new(tokio::sync::RwLock::new(GlobalsWorkdirPackagesST::new())), + packages_testnet: Arc::new(tokio::sync::RwLock::new(GlobalsWorkdirPackagesST::new())), + packages_mainnet: Arc::new(tokio::sync::RwLock::new(GlobalsWorkdirPackagesST::new())), events_data_localnet: Arc::new(tokio::sync::RwLock::new(GlobalsEventsDataST::new())), events_data_devnet: Arc::new(tokio::sync::RwLock::new(GlobalsEventsDataST::new())), events_data_testnet: Arc::new(tokio::sync::RwLock::new(GlobalsEventsDataST::new())), @@ -244,7 +251,6 @@ impl Globals { } pub fn get_status(&self, workdir_idx: WorkdirIdx) -> &GlobalsWorkdirStatusMT { - // Use hard coded workdir_idx to dispatch the right data. match workdir_idx { workdirs::WORKDIR_IDX_LOCALNET => &self.status_localnet, workdirs::WORKDIR_IDX_DEVNET => &self.status_devnet, @@ -254,6 +260,16 @@ impl Globals { } } + pub fn get_packages(&self, workdir_idx: WorkdirIdx) -> &GlobalsWorkdirPackagesMT { + match workdir_idx { + workdirs::WORKDIR_IDX_LOCALNET => &self.packages_localnet, + workdirs::WORKDIR_IDX_DEVNET => &self.packages_devnet, + workdirs::WORKDIR_IDX_TESTNET => &self.packages_testnet, + workdirs::WORKDIR_IDX_MAINNET => &self.packages_mainnet, + _ => panic!("Invalid workdir_idx {}", workdir_idx), + } + } + pub fn get_api_mutex(&self, workdir_idx: WorkdirIdx) -> &GlobalsAPIMutexMT { match workdir_idx { workdirs::WORKDIR_IDX_LOCALNET => &self.api_mutex_localnet, @@ -267,7 +283,6 @@ impl Globals { } pub fn events_data(&self, workdir_idx: WorkdirIdx) -> Option<&GlobalsEventsDataMT> { - // Use hard coded workdir_idx to dispatch the right data. match workdir_idx { workdirs::WORKDIR_IDX_LOCALNET => Some(&self.events_data_localnet), workdirs::WORKDIR_IDX_DEVNET => Some(&self.events_data_devnet), @@ -280,7 +295,6 @@ impl Globals { &mut self, workdir_idx: WorkdirIdx, ) -> Option<&mut GlobalsEventsDataMT> { - // Use hard coded workdir_idx to dispatch the right data. match workdir_idx { workdirs::WORKDIR_IDX_LOCALNET => Some(&mut self.events_data_localnet), workdirs::WORKDIR_IDX_DEVNET => Some(&mut self.events_data_devnet), diff --git a/rust/suibase/crates/suibase-daemon/src/shared_types/packages.rs b/rust/suibase/crates/suibase-daemon/src/shared_types/packages.rs index 96b0a710..6a4d25c3 100644 --- a/rust/suibase/crates/suibase-daemon/src/shared_types/packages.rs +++ b/rust/suibase/crates/suibase-daemon/src/shared_types/packages.rs @@ -1,15 +1,14 @@ -use std::collections::HashMap; -use crate::api::{MoveConfig, PackagesConfigResponse, Versioned}; +use crate::api::{Versioned, WorkdirPackagesResponse}; -use common::basic_types::{AutoSizeVec, WorkdirIdx}; +//use common::basic_types::{AutoSizeVec, WorkdirIdx}; #[derive(Debug, Clone)] pub struct PackagesWorkdirConfig { // Mostly store everything in the same struct // as the response of the GetEventsConfig API. That way, // the UI queries can be served very quickly. - pub ui: Option>, + pub ui: Option>, pub last_ui_update: tokio::time::Instant, } @@ -29,20 +28,20 @@ impl std::default::Default for PackagesWorkdirConfig { } #[derive(Debug, Clone)] -pub struct GlobalsPackagesConfigST { - // One per workdir, WorkdirIdx maintained by workdirs. - pub workdirs: AutoSizeVec, +pub struct GlobalsWorkdirPackagesST { + // Mostly store everything in the same struct + // as the response of the GetWorkdirPackages API. That way, + // the UI queries can be served very quickly. + pub ui: Option>, } -impl GlobalsPackagesConfigST { +impl GlobalsWorkdirPackagesST { pub fn new() -> Self { - Self { - workdirs: AutoSizeVec::new(), - } + Self { ui: None } } // Convenient access to the move_configs for a given workdir. - +/* pub fn get_move_configs( workdirs: &AutoSizeVec, workdir_idx: WorkdirIdx, @@ -68,7 +67,7 @@ impl GlobalsPackagesConfigST { pub fn get_config_resp( workdirs: &AutoSizeVec, workdir_idx: WorkdirIdx, - ) -> Option<&PackagesConfigResponse> { + ) -> Option<&WorkdirPackagesResponse> { // Will return None if an object is missing while trying to reach the PackageConfigResponse (should not happen). let packages_workdir_config = workdirs.get_if_some(workdir_idx)?; let ui = packages_workdir_config.ui.as_ref()?; @@ -84,10 +83,10 @@ impl GlobalsPackagesConfigST { pub fn get_mut_config_resp( workdirs: &mut AutoSizeVec, workdir_idx: WorkdirIdx, - ) -> &mut PackagesConfigResponse { + ) -> &mut WorkdirPackagesResponse { let packages_workdir_config = workdirs.get_mut(workdir_idx); if packages_workdir_config.ui.is_none() { - packages_workdir_config.ui = Some(Versioned::new(PackagesConfigResponse::new())); + packages_workdir_config.ui = Some(Versioned::new(WorkdirPackagesResponse::new())); } let ui = packages_workdir_config.ui.as_mut().unwrap(); let config_resp = ui.get_mut_data(); @@ -97,10 +96,10 @@ impl GlobalsPackagesConfigST { } config_resp - } + }*/ } -impl Default for GlobalsPackagesConfigST { +impl Default for GlobalsWorkdirPackagesST { fn default() -> Self { Self::new() } diff --git a/rust/suibase/crates/suibase-daemon/src/workers/websocket_worker.rs b/rust/suibase/crates/suibase-daemon/src/workers/websocket_worker.rs index fd7790ec..4fbcf935 100644 --- a/rust/suibase/crates/suibase-daemon/src/workers/websocket_worker.rs +++ b/rust/suibase/crates/suibase-daemon/src/workers/websocket_worker.rs @@ -11,8 +11,8 @@ use std::{collections::HashMap, sync::Arc}; use crate::shared_types::{ - Globals, GlobalsPackagesConfigST, WORKDIRS_KEYS, WORKDIR_IDX_DEVNET, WORKDIR_IDX_LOCALNET, - WORKDIR_IDX_MAINNET, WORKDIR_IDX_TESTNET, + Globals, WORKDIRS_KEYS, WORKDIR_IDX_DEVNET, WORKDIR_IDX_LOCALNET, WORKDIR_IDX_MAINNET, + WORKDIR_IDX_TESTNET, }; use anyhow::Result; @@ -316,9 +316,8 @@ impl WebSocketWorkerThread { } let package_id = &package_id[2..]; let expected_package_id = tracker - .package_filter() - .map(|s| s.clone()) - .unwrap_or_else(|| "".to_string()); + .package_filter().cloned() + .unwrap_or_default(); if package_id != expected_package_id { log::error!( "packageId {} not matching {} in Sui Event message. workdir={} message={:?}", @@ -429,83 +428,81 @@ impl WebSocketWorkerThread { return; } } else { - log::error!("Unexpected workdir_idx {:?}", msg); + log::error!("Missing workdir_idx {:?}", msg); return; } + let workdir_idx = self.params.workdir_idx; // log::info!("Received an audit message: {:?}", msg); let mut state_change = false; { // Get a reader lock on the globals packages_config. - let globals_read_guard = self.params.globals.packages_config.read().await; - let workdirs = &globals_read_guard.workdirs; - - // Get the element in packages_config for workdir_idx. - - let move_configs = - GlobalsPackagesConfigST::get_move_configs(workdirs, self.params.workdir_idx); - if move_configs.is_none() { - return; // Normal when the workdir never had any published package. - } - let move_configs = move_configs.unwrap(); - - // Check for adding PackagesTracking. - // Add a PackagesTracking in the packages HashMap for every latests in packages_config. - // Once created, the PackagesTracking remains until removed from packages_config. - // The package_id is used as the key in the packages HashMap. - for (uuid, move_config) in move_configs { - let latest = move_config.latest_package.as_ref().unwrap(); - // Check if the package is already in the packages HashMap. - if !self.package_subs.contains_key(&latest.package_id) { - if move_config.path.is_none() { - log::error!("Missing path in move_config {:?}", move_config); - continue; + let globals_read_guard = self.params.globals.get_packages(workdir_idx).read().await; + let globals = &globals_read_guard; + + // Get the move_configs in ui for workdir_idx. + if let Some(ui) = &globals.ui { + let resp = ui.get_data(); + let move_configs = &resp.move_configs; + + // Check for adding PackagesTracking. + // Add a PackagesTracking in the packages HashMap for every latests in packages_config. + // Once created, the PackagesTracking remains until removed from ui. + // The package_id is used as the key in the packages HashMap. + for (uuid, move_config) in move_configs { + let latest = move_config.latest_package.as_ref().unwrap(); + // Check if the package is already in the packages HashMap. + if !self.package_subs.contains_key(&latest.package_id) { + if move_config.path.is_none() { + log::error!("Missing path in move_config {:?}", move_config); + continue; + } + let toml_path = move_config.path.as_ref().unwrap().clone(); + + // Create a new PackagesTracking. + let package_tracking = SubscriptionTracking::new_for_managed_package( + toml_path, + latest.package_name.clone(), + uuid.to_string(), + latest.package_id.clone(), + ); + // Add the PackagesTracking to the packages HashMap. + self.package_subs + .insert(latest.package_id.clone(), package_tracking); } - let toml_path = move_config.path.as_ref().unwrap().clone(); - - // Create a new PackagesTracking. - let package_tracking = SubscriptionTracking::new_for_managed_package( - toml_path, - latest.package_name.clone(), - uuid.to_string(), - latest.package_id.clone(), - ); - // Add the PackagesTracking to the packages HashMap. - self.package_subs - .insert(latest.package_id.clone(), package_tracking); } - } - // Transition package to Unsubscribing state when no longer in the config. - // Remove the package tracking once unsubscription confirmed (or timeout). - self.package_subs.retain(|package_id, package_tracking| { - let mut retain = true; - let move_config = move_configs.get(package_tracking.uuid().as_str()); - if let Some(move_config) = move_config { - // Verify if this package_id is still the latest published for this package UUID. - if move_config.latest_package.is_none() { - retain = false; - } else { - let latest = move_config.latest_package.as_ref().unwrap(); - if latest.package_id != *package_id { + // Transition package to Unsubscribing state when no longer in the config. + // Remove the package tracking once unsubscription confirmed (or timeout). + self.package_subs.retain(|package_id, package_tracking| { + let mut retain = true; + let move_config = move_configs.get(package_tracking.uuid().as_str()); + if let Some(move_config) = move_config { + // Verify if this package_id is still the latest published for this package UUID. + if move_config.latest_package.is_none() { retain = false; + } else { + let latest = move_config.latest_package.as_ref().unwrap(); + if latest.package_id != *package_id { + retain = false; + } } + } else { + retain = false; } - } else { - retain = false; - } - if !retain { - if package_tracking.can_be_deleted() { - log::info!("Deleting tracking for package_id={}", package_id); - return false; // Delete the element in the HashMap. - } - // Transition toward eventual deletion after Unsubscribing completes (or timeout). - if !package_tracking.is_remove_requested() { - package_tracking.report_remove_request(); + if !retain { + if package_tracking.can_be_deleted() { + log::info!("Deleting tracking for package_id={}", package_id); + return false; // Delete the element in the HashMap. + } + // Transition toward eventual deletion after Unsubscribing completes (or timeout). + if !package_tracking.is_remove_requested() { + package_tracking.report_remove_request(); + } } - } - true // Keep the element in the HashMap. - }); + true // Keep the element in the HashMap. + }); + } } // End of reader lock. let websocket = &mut self.websocket; @@ -570,11 +567,10 @@ impl WebSocketWorkerThread { } async fn process_update_msg(&mut self, msg: GenericChannelMsg) { - // This function takes care of synching from self.packages to - // the global packages_config. + // This function takes care of synching from self.packages to the global ui. + // + // Unlike an audit, changes to ui are allowed here. // - // Unlike an audit, changes to packages_config globals are - // allowed here. //log::info!("Received an update message: {:?}", msg); // Make sure the event_id is EVENT_UPDATE. @@ -597,50 +593,50 @@ impl WebSocketWorkerThread { log::error!("Unexpected workdir_idx {:?}", msg); return; } + let workdir_idx = self.params.workdir_idx; let mut trig_audit = false; { - // Get a writer lock on the globals packages_config. - let mut globals_write_guard = self.params.globals.packages_config.write().await; + // Get a writer lock on the globals ui. + let mut globals_write_guard = + self.params.globals.get_packages(workdir_idx).write().await; let globals = &mut *globals_write_guard; - // Get the element in packages_config for workdir_idx. - - let move_configs = GlobalsPackagesConfigST::get_mut_move_configs( - &mut globals.workdirs, - self.params.workdir_idx, - ); - - // Check for adding PackagesTracking. - // Add a PackagesTracking in the packages HashMap for every latests in packages_config. - // Once created, the PackagesTracking remains until removed from packages_config. - // The package_id is used as the key in the packages HashMap. - for (uuid, move_config) in &mut *move_configs { - let latest = move_config.latest_package.as_ref().unwrap(); - // Check if the package is already in the packages HashMap. - if !self.package_subs.contains_key(&latest.package_id) { - if move_config.path.is_none() { - log::error!("Missing path in move_config {:?}", move_config); - continue; - } - let toml_path = move_config.path.as_ref().unwrap().clone(); - - // Create a new PackagesTracking. - let package_tracking = SubscriptionTracking::new_for_managed_package( - toml_path, - latest.package_name.clone(), - uuid.to_string(), - latest.package_id.clone(), - ); - // Add the PackagesTracking to the packages HashMap. - self.package_subs - .insert(latest.package_id.clone(), package_tracking); - trig_audit = true; - } else { - let package_tracking = &self.package_subs[&latest.package_id]; - let package_tracking_state: u32 = package_tracking.state().clone().into(); - if move_config.tracking_state != package_tracking_state { - move_config.tracking_state = package_tracking_state; + if let Some(ui) = &mut globals.ui { + let resp = ui.get_mut_data(); + let move_configs = &mut resp.move_configs; + + // Check for adding PackagesTracking. + // Add a PackagesTracking in the packages HashMap for every latests in packages_config. + // Once created, the PackagesTracking remains until removed from packages_config. + // The package_id is used as the key in the packages HashMap. + for (uuid, move_config) in &mut *move_configs { + let latest = move_config.latest_package.as_ref().unwrap(); + // Check if the package is already in the packages HashMap. + if !self.package_subs.contains_key(&latest.package_id) { + if move_config.path.is_none() { + log::error!("Missing path in move_config {:?}", move_config); + continue; + } + let toml_path = move_config.path.as_ref().unwrap().clone(); + + // Create a new PackagesTracking. + let package_tracking = SubscriptionTracking::new_for_managed_package( + toml_path, + latest.package_name.clone(), + uuid.to_string(), + latest.package_id.clone(), + ); + // Add the PackagesTracking to the packages HashMap. + self.package_subs + .insert(latest.package_id.clone(), package_tracking); + trig_audit = true; + } else { + let package_tracking = &self.package_subs[&latest.package_id]; + let package_tracking_state: u32 = package_tracking.state().clone().into(); + if move_config.tracking_state != package_tracking_state { + move_config.tracking_state = package_tracking_state; + } } } } @@ -708,9 +704,8 @@ impl WebSocketWorkerThread { return false; } let package_id = tracker - .package_filter() - .map(|s| s.clone()) - .unwrap_or_else(|| "".to_string()); + .package_filter().cloned() + .unwrap_or_default(); // Check if retrying and log error only on first retry and once in a while after. if tracker.request_retry() % 3 == 1 { diff --git a/scripts/common/run-daemon.sh b/scripts/common/run-daemon.sh index 0376bdd3..67ff94be 100755 --- a/scripts/common/run-daemon.sh +++ b/scripts/common/run-daemon.sh @@ -33,7 +33,7 @@ case "$PARAM_NAME" in source "$DTP_DIR/scripts/common/__dtp-daemon.sh" ;; *) - echo "ERROR: Invalid daemon name: $NAME_LC" + echo "ERROR: Invalid daemon name: $PARAM_NAME" exit 1 ;; esac @@ -73,7 +73,7 @@ main() { _DAEMON_NAME="$DTP_DAEMON_NAME" ;; *) - echo "ERROR: Invalid daemon name: $NAME_LC" + echo "ERROR: Invalid daemon name: $PARAM_NAME" exit 1 ;; esac diff --git a/scripts/common/run-suibase-daemon.sh b/scripts/common/run-suibase-daemon.sh deleted file mode 100755 index 806bff6a..00000000 --- a/scripts/common/run-suibase-daemon.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash - -# This is not intended to be called by the user directly. It is used by the suibase scripts -# to start the execution of the suibase-daemon. -# -# Does also: -# - Helps prevent running multiple instance of the daemon at the same time. -# - Restart the daemon on exit/panic (after a brief delay). -# -# Why not systemd? Portability. If the host can run bash, then this works. -# - -# Source '__globals.sh'. -SUIBASE_DIR="$HOME/suibase" -SCRIPT_COMMON_CALLER="$(readlink -f "$0")" -WORKDIR="localnet" -# shellcheck source=SCRIPTDIR/__globals.sh -source "$SUIBASE_DIR/scripts/common/__globals.sh" "$SCRIPT_COMMON_CALLER" "$WORKDIR" - -# shellcheck source=SCRIPTDIR/__suibase-daemon.sh -source "$SUIBASE_DIR/scripts/common/__suibase-daemon.sh" - -# Check what is available, prefer flock over lockf. -# Reference: https://github.com/Freaky/run-one/blob/master/run-one -if is_installed flock; then - _LOCK_CMD="flock -xn" -else - if is_installed lockf; then - _LOCK_CMD="lockf -st0" - else - setup_error "Neither 'flock' or 'lockf' are available! Install one of them" - fi -fi - -locked_command() { - exec $_LOCK_CMD "$@" -} - -main() { - # Only command supported is "foreground" for special execution - # when developing/debugging. - local _CMD="$1" - - # Detect if suibase is not installed! - if [ ! -d "$SUIBASE_DIR" ]; then - echo "ERROR: suibase is not installed! Check https://suibase.io/how-to/install" - exit 1 - fi - - if [ ! -f "$SUIBASE_DAEMON_BIN" ]; then - echo "The $SUIBASE_DAEMON_NAME binary does not exists!" - exit 1 - fi - - # Run the daemon from a script that constantly restart it on - # abnormal exit (e.g. panic). - mkdir -p "$SUIBASE_LOGS_DIR" - mkdir -p "$SUIBASE_TMP_DIR" - - local _LOCKFILE="$SUIBASE_TMP_DIR/$SUIBASE_DAEMON_NAME.lock" - local _LOG="$SUIBASE_LOGS_DIR/$SUIBASE_DAEMON_NAME.log" - local _CMD_LINE="$SUIBASE_DAEMON_BIN run" - - if [ "$_CMD" == "foreground" ]; then - # Run in foreground, with no restart on exit/panic. - # shellcheck disable=SC2086,SC2016 - locked_command "$_LOCKFILE" /bin/sh -uec '"$@" 2>&1 | tee -a $0' "$_LOG" $_CMD_LINE - else - # Run in background, with auto-restart on exit/panic. - # shellcheck disable=SC2086,SC2016 - locked_command "$_LOCKFILE" /bin/sh -uec 'while true; do "$@" > $0 2>&1; echo "Restarting process" > $0 2>&1; sleep 1; done' "$_LOG" $_CMD_LINE - fi - -} - -main "$@" diff --git a/typescript/vscode-extension/src/SuibaseExec.ts b/typescript/vscode-extension/src/SuibaseExec.ts index 7bf71d7c..98728ce4 100644 --- a/typescript/vscode-extension/src/SuibaseExec.ts +++ b/typescript/vscode-extension/src/SuibaseExec.ts @@ -125,7 +125,7 @@ export class SuibaseExec { if (!suibaseRunning) { // Start suibase daemon - const result = await execShell("~/suibase/scripts/common/run-daemon.sh suibase"); + const result = await execShell("~/suibase/scripts/common/run-daemon.sh suibase &"); // TODO Implement retry and error handling of run-daemon.sh for faster startup.