Skip to content

Commit 57844da

Browse files
committed
align mm2_main error handling between native and WASM implementations
1 parent 9716a52 commit 57844da

File tree

7 files changed

+167
-91
lines changed

7 files changed

+167
-91
lines changed

mm2src/coins/lp_coins.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4997,7 +4997,7 @@ pub async fn delegations_info(ctx: MmArc, req: DelegationsInfo) -> Result<Json,
49974997
DelegationsInfoDetails::Qtum => {
49984998
let MmCoinEnum::QtumCoin(qtum) = coin else {
49994999
return MmError::err(StakingInfoError::InvalidPayload {
5000-
reason: format!("{} is not a Qtum coin", req.coin)
5000+
reason: format!("{} is not a Qtum coin", req.coin),
50015001
});
50025002
};
50035003

@@ -5048,7 +5048,7 @@ pub async fn claim_staking_rewards(ctx: MmArc, req: ClaimStakingRewardsRequest)
50485048

50495049
let MmCoinEnum::Tendermint(tendermint) = coin else {
50505050
return MmError::err(DelegationError::InvalidPayload {
5051-
reason: format!("{} is not a Cosmos coin", req.coin)
5051+
reason: format!("{} is not a Cosmos coin", req.coin),
50525052
});
50535053
};
50545054

mm2src/mm2_bin_lib/src/lib.rs

+17
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,20 @@ async fn finalize_mm2_stop(ctx: MmArc) {
108108
dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await;
109109
let _ = ctx.stop().await;
110110
}
111+
112+
#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize))]
113+
#[derive(Clone, Debug, PartialEq, Primitive)]
114+
pub enum StartupErrorCode {
115+
/// Operation completed successfully
116+
Ok = 0,
117+
/// Invalid parameters were provided to the function
118+
InvalidParams = 1,
119+
/// The configuration was invalid (missing required fields, etc.)
120+
ConfigError = 2,
121+
/// MM2 is already running
122+
AlreadyRunning = 3,
123+
/// MM2 initialization failed
124+
InitError = 4,
125+
/// Failed to spawn the MM2 process/thread
126+
SpawnError = 5,
127+
}

mm2src/mm2_bin_lib/src/mm2_native_lib.rs

+55-28
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use enum_primitive_derive::Primitive;
77
use gstuff::any_to_str;
88
use libc::c_char;
99
use mm2_core::mm_ctx::MmArc;
10+
use mm2_main::LpMainParams;
1011
use num_traits::FromPrimitive;
1112
use serde_json::{self as json};
1213
use std::ffi::{CStr, CString};
@@ -15,15 +16,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
1516
use std::thread;
1617
use std::time::Duration;
1718

18-
#[derive(Debug, PartialEq, Primitive)]
19-
enum MainErr {
20-
Ok = 0,
21-
AlreadyRuns = 1,
22-
ConfIsNull = 2,
23-
ConfNotUtf8 = 3,
24-
CantThread = 5,
25-
}
26-
2719
/// Starts the MM2 in a detached singleton thread.
2820
#[no_mangle]
2921
#[allow(clippy::missing_safety_doc)]
@@ -48,40 +40,75 @@ pub unsafe extern "C" fn mm2_main(conf: *const c_char, log_cb: extern "C" fn(lin
4840
}};
4941
}
5042

51-
if LP_MAIN_RUNNING.load(Ordering::Relaxed) {
52-
eret!(MainErr::AlreadyRuns)
53-
}
54-
CTX.store(0, Ordering::Relaxed); // Remove the old context ID during restarts.
55-
5643
if conf.is_null() {
57-
eret!(MainErr::ConfIsNull)
44+
eret!(StartupErrorCode::InvalidParams, "Configuration is null")
5845
}
59-
let conf = CStr::from_ptr(conf);
60-
let conf = match conf.to_str() {
46+
let conf_cstr = CStr::from_ptr(conf);
47+
let conf_str = match conf_cstr.to_str() {
6148
Ok(s) => s,
62-
Err(e) => eret!(MainErr::ConfNotUtf8, e),
49+
Err(e) => eret!(
50+
StartupErrorCode::InvalidParams,
51+
format!("Configuration is not valid UTF-8: {}", e)
52+
),
53+
};
54+
55+
let conf: json::Value = match json::from_str(conf_str) {
56+
Ok(v) => v,
57+
Err(e) => eret!(
58+
StartupErrorCode::ConfigError,
59+
format!("Failed to parse configuration: {}", e)
60+
),
6361
};
64-
let conf = conf.to_owned();
62+
63+
if LP_MAIN_RUNNING.load(Ordering::Relaxed) {
64+
eret!(StartupErrorCode::AlreadyRunning, "MM2 is already running");
65+
}
66+
67+
CTX.store(0, Ordering::Relaxed); // Remove the old context ID during restarts.
6568

6669
register_callback(FfiCallback::with_ffi_function(log_cb));
70+
6771
let rc = thread::Builder::new().name("lp_main".into()).spawn(move || {
6872
if let Err(true) = LP_MAIN_RUNNING.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) {
6973
log!("lp_main already started!");
7074
return;
7175
}
76+
7277
let ctx_cb = &|ctx| CTX.store(ctx, Ordering::Relaxed);
73-
match catch_unwind(move || mm2_main::run_lp_main(Some(&conf), ctx_cb, KDF_VERSION.into(), KDF_DATETIME.into()))
74-
{
75-
Ok(Ok(_)) => log!("run_lp_main finished"),
76-
Ok(Err(err)) => log!("run_lp_main error: {}", err),
77-
Err(err) => log!("run_lp_main panic: {:?}", any_to_str(&*err)),
78+
79+
match catch_unwind(move || {
80+
let params = LpMainParams::with_conf(conf).log_filter(None);
81+
82+
match block_on(mm2_main::lp_main(
83+
params,
84+
&ctx_cb,
85+
KDF_VERSION.into(),
86+
KDF_DATETIME.into(),
87+
)) {
88+
Ok(ctx) => {
89+
if let Err(e) = block_on(mm2_main::lp_run(ctx)) {
90+
log!("MM2 runtime error: {}", e);
91+
}
92+
},
93+
Err(e) => log!("MM2 initialization failed: {}", e),
94+
}
95+
}) {
96+
Ok(_) => log!("MM2 thread completed normally"),
97+
Err(err) => log!("MM2 thread panicked: {:?}", any_to_str(&*err)),
7898
};
99+
79100
LP_MAIN_RUNNING.store(false, Ordering::Relaxed)
80101
});
102+
81103
if let Err(e) = rc {
82-
eret!(MainErr::CantThread, e)
104+
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
105+
eret!(
106+
StartupErrorCode::SpawnError,
107+
format!("Failed to spawn MM2 thread: {:?}", e)
108+
);
83109
}
84-
MainErr::Ok as i8
110+
111+
StartupErrorCode::Ok as i8
85112
}
86113

87114
/// Checks if the MM2 singleton thread is currently running or not.
@@ -177,8 +204,8 @@ pub extern "C" fn mm2_test(torch: i32, log_cb: extern "C" fn(line: *const c_char
177204
log!("mm2_test] Restarting MM…");
178205
let conf = CString::new(&conf[..]).unwrap();
179206
let rc = unsafe { mm2_main(conf.as_ptr(), log_cb) };
180-
let rc = MainErr::from_i8(rc).unwrap();
181-
if rc != MainErr::Ok {
207+
let rc = StartupErrorCode::from_i8(rc).unwrap();
208+
if rc != StartupErrorCode::Ok {
182209
log!("!mm2_main: {:?}", rc);
183210
return -1;
184211
}

mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs

+67-42
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
//! Some specifics of using the [`wasm_bindgen`] library:
22
//!
3-
//! # Currently only `Result<T, JsValue>` is allowed
4-
//! [tracking issue]: https://github.com/rustwasm/wasm-bindgen/issues/1004
5-
//!
63
//! # JavaScript enums do not support methods at all
74
//! [tracking issue]: https://github.com/rustwasm/wasm-bindgen/issues/1715
85
//!
@@ -21,18 +18,27 @@ use mm2_rpc::data::legacy::MmVersionResponse;
2118
use mm2_rpc::wasm_rpc::WasmRpcResponse;
2219
use serde::{Deserialize, Serialize};
2320
use serde_json::Value as Json;
24-
25-
/// The errors can be thrown when using the `mm2_main` function incorrectly.
2621
#[wasm_bindgen]
27-
#[derive(Primitive)]
28-
pub enum Mm2MainErr {
29-
AlreadyRuns = 1,
30-
InvalidParams = 2,
31-
NoCoinsInConf = 3,
22+
#[derive(Debug, Clone, Serialize)]
23+
struct StartupError {
24+
code: StartupErrorCode,
25+
message: String,
3226
}
3327

34-
impl From<Mm2MainErr> for JsValue {
35-
fn from(e: Mm2MainErr) -> Self { JsValue::from(e as i32) }
28+
#[wasm_bindgen]
29+
impl StartupError {
30+
fn new(code: StartupErrorCode, message: impl Into<String>) -> Self {
31+
Self {
32+
code,
33+
message: message.into(),
34+
}
35+
}
36+
37+
#[wasm_bindgen(getter)]
38+
pub fn code(&self) -> i8 { self.code.clone() as i8 }
39+
40+
#[wasm_bindgen(getter)]
41+
pub fn message(&self) -> String { self.message.clone() }
3642
}
3743

3844
#[derive(Deserialize)]
@@ -58,7 +64,7 @@ impl From<MainParams> for LpMainParams {
5864
/// # Usage
5965
///
6066
/// ```javascript
61-
/// import init, {mm2_main, LogLevel, Mm2MainErr} from "./path/to/mm2.js";
67+
/// import init, {mm2_main, LogLevel, StartupErrorCode} from "./path/to/mm2.js";
6268
///
6369
/// const params = {
6470
/// conf: { "gui":"WASMTEST", mm2:1, "passphrase":"YOUR_PASSPHRASE_HERE", "rpc_password":"test123", "coins":[{"coin":"ETH","protocol":{"type":"ETH"}}] },
@@ -68,8 +74,8 @@ impl From<MainParams> for LpMainParams {
6874
/// try {
6975
/// mm2_main(params, handle_log);
7076
/// } catch (e) {
71-
/// switch (e) {
72-
/// case Mm2MainErr.AlreadyRuns:
77+
/// switch (e.code) {
78+
/// case StartupErrorCode.AlreadyRunning:
7379
/// alert("MarketMaker2 already runs...");
7480
/// break;
7581
/// // handle other errors...
@@ -80,51 +86,70 @@ impl From<MainParams> for LpMainParams {
8086
/// }
8187
/// ```
8288
#[wasm_bindgen]
83-
pub fn mm2_main(params: JsValue, log_cb: js_sys::Function) -> Result<(), JsValue> {
89+
pub async fn mm2_main(params: JsValue, log_cb: js_sys::Function) -> Result<i8, JsValue> {
8490
let params: MainParams = match deserialize_from_js(params.clone()) {
8591
Ok(p) => p,
8692
Err(e) => {
87-
console_err!("Expected 'MainParams' as the first argument, found {:?}: {}", params, e);
88-
return Err(Mm2MainErr::InvalidParams.into());
93+
let error = StartupError::new(
94+
StartupErrorCode::InvalidParams,
95+
format!("Expected 'MainParams' as the first argument, found {:?}: {}", params, e),
96+
);
97+
console_err!("{}", error.message());
98+
return Err(error.into());
8999
},
90100
};
91101
if params.conf["coins"].is_null() {
92-
console_err!("Config must contain 'coins' field: {:?}", params.conf);
93-
return Err(Mm2MainErr::NoCoinsInConf.into());
102+
let error = StartupError::new(
103+
StartupErrorCode::ConfigError,
104+
format!("Config must contain 'coins' field: {:?}", params.conf),
105+
);
106+
console_err!("{}", error.message());
107+
return Err(error.into());
94108
}
95109
let params = LpMainParams::from(params);
96110

97111
if LP_MAIN_RUNNING.load(Ordering::Relaxed) {
98-
return Err(Mm2MainErr::AlreadyRuns.into());
112+
let error = StartupError::new(StartupErrorCode::AlreadyRunning, "MM2 is already running");
113+
console_err!("{}", error.message());
114+
return Err(error.into());
99115
}
100116
CTX.store(0, Ordering::Relaxed); // Remove the old context ID during restarts.
101117

102118
register_callback(WasmCallback::with_js_function(log_cb));
119+
// Setting up a global panic hook to log panic information to the console
120+
// This doesn't prevent termination of the WebAssembly instance, but ensures error details are visible
121+
// We can't use catch_unwind directly with async/await in WebAssembly, so this is our best option for diagnostics
122+
// If a panic occurs, the MM2 instance will terminate but the browser tab will remain responsive
103123
set_panic_hook();
104124

105-
let fut = async move {
106-
if let Err(true) = LP_MAIN_RUNNING.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) {
107-
console_err!("lp_main already started!");
108-
return;
109-
}
110-
let ctx_cb = |ctx| CTX.store(ctx, Ordering::Relaxed);
111-
// TODO figure out how to use catch_unwind here
112-
// use futures::FutureExt;
113-
// match mm2_main::lp_main(params, &ctx_cb).catch_unwind().await {
114-
// Ok(Ok(_)) => console_info!("run_lp_main finished"),
115-
// Ok(Err(err)) => console_err!("run_lp_main error: {}", err),
116-
// Err(err) => console_err!("run_lp_main panic: {:?}", any_to_str(&*err)),
117-
// };
118-
match mm2_main::lp_main(params, &ctx_cb, KDF_VERSION.into(), KDF_DATETIME.into()).await {
119-
Ok(()) => console_info!("run_lp_main finished"),
120-
Err(err) => console_err!("run_lp_main error: {}", err),
121-
};
122-
LP_MAIN_RUNNING.store(false, Ordering::Relaxed)
125+
if let Err(true) = LP_MAIN_RUNNING.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) {
126+
let error = StartupError::new(StartupErrorCode::AlreadyRunning, "lp_main already started!");
127+
console_err!("{}", error.message());
128+
return Err(error.into());
129+
}
130+
131+
let ctx_cb = |ctx| CTX.store(ctx, Ordering::Relaxed);
132+
let ctx = match mm2_main::lp_main(params, &ctx_cb, KDF_VERSION.into(), KDF_DATETIME.into()).await {
133+
Ok(ctx) => {
134+
console_info!("run_lp_main finished");
135+
ctx
136+
},
137+
Err(err) => {
138+
let error = StartupError::new(StartupErrorCode::InitError, format!("run_lp_main error: {}", err));
139+
console_err!("{}", error.message());
140+
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
141+
return Err(error.into());
142+
},
123143
};
124144

125-
// At this moment we still don't have `MmCtx` context to use its `MmCtx::abortable_system` spawner.
126-
executor::spawn_local(fut);
127-
Ok(())
145+
executor::spawn_local(async move {
146+
if let Err(e) = mm2_main::lp_run(ctx).await {
147+
console_err!("MM2 runtime error: {}", e);
148+
}
149+
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
150+
});
151+
152+
Ok(StartupErrorCode::Ok as i8)
128153
}
129154

130155
/// Returns the MarketMaker2 instance status.

mm2src/mm2_main/src/lp_native_dex.rs

+1-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
use bitcrypto::sha256;
2222
use coins::register_balance_update_handler;
23-
use common::executor::{SpawnFuture, Timer};
23+
use common::executor::SpawnFuture;
2424
use common::log::{info, warn};
2525
use crypto::{from_hw_error, CryptoCtx, HwError, HwProcessingError, HwRpcError, WithHwRpcError};
2626
use derive_more::Display;
@@ -49,7 +49,6 @@ use crate::lp_message_service::{init_message_service, InitMessageServiceError};
4949
use crate::lp_network::{lp_network_ports, p2p_event_process_loop, subscribe_to_topic, NetIdError};
5050
use crate::lp_ordermatch::{broadcast_maker_orders_keep_alive_loop, clean_memory_loop, init_ordermatch_context,
5151
lp_ordermatch_loop, orders_kick_start, BalanceUpdateOrdermatchHandler, OrdermatchInitError};
52-
use crate::lp_swap;
5352
use crate::lp_swap::swap_kick_starts;
5453
use crate::lp_wallet::{initialize_wallet_passphrase, WalletInitError};
5554
use crate::rpc::spawn_rpc;
@@ -508,16 +507,6 @@ pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitRes
508507
}
509508
});
510509

511-
// In the mobile version we might depend on `lp_init` staying around until the context stops.
512-
loop {
513-
if ctx.is_stopping() {
514-
break;
515-
};
516-
Timer::sleep(0.2).await
517-
}
518-
// Clearing up the running swaps removes any circular references that might prevent the context from being dropped.
519-
lp_swap::clear_running_swaps(&ctx);
520-
521510
Ok(())
522511
}
523512

0 commit comments

Comments
 (0)