return errors back to user (#4075)
* Log any Errors to stderr This isn't perfect -- in fact, this is maybe not even very good at all, but it's better than what we have today. Currently, when we get an Erorr back from the WebSocket, we drop it in kcl-lib. The web-app logs these to the console (I can't find my commit doing that off the top of my head, but I remember doing it) -- so this is some degree of partity. This won't be very useful at all for wasm usage, but it will fix issues with the zoo cli silently breaking with a "WebSocket Closed" error -- which is the same issue I was solving for in the desktop app too. In the future perhaps this can be a real Error? I'm not totally sure yet, since we can't align to the request-id, so we can't really tie it to a specific call (yet). * add to responses Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) * add a test Signed-off-by: Jess Frazelle <github@jessfraz.com> * clippy[ Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) * empty * fix error Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * docs Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
@ -35,6 +35,7 @@ type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocket
|
|||||||
pub struct EngineConnection {
|
pub struct EngineConnection {
|
||||||
engine_req_tx: mpsc::Sender<ToEngineReq>,
|
engine_req_tx: mpsc::Sender<ToEngineReq>,
|
||||||
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
||||||
|
pending_errors: Arc<Mutex<Vec<String>>>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
tcp_read_handle: Arc<TcpReadHandle>,
|
tcp_read_handle: Arc<TcpReadHandle>,
|
||||||
socket_health: Arc<Mutex<SocketHealth>>,
|
socket_health: Arc<Mutex<SocketHealth>>,
|
||||||
@ -193,6 +194,8 @@ impl EngineConnection {
|
|||||||
let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
|
let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
|
||||||
let responses_clone = responses.clone();
|
let responses_clone = responses.clone();
|
||||||
let socket_health = Arc::new(Mutex::new(SocketHealth::Active));
|
let socket_health = Arc::new(Mutex::new(SocketHealth::Active));
|
||||||
|
let pending_errors = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let pending_errors_clone = pending_errors.clone();
|
||||||
|
|
||||||
let socket_health_tcp_read = socket_health.clone();
|
let socket_health_tcp_read = socket_health.clone();
|
||||||
let tcp_read_handle = tokio::spawn(async move {
|
let tcp_read_handle = tokio::spawn(async move {
|
||||||
@ -242,6 +245,30 @@ impl EngineConnection {
|
|||||||
let mut sd = session_data2.lock().unwrap();
|
let mut sd = session_data2.lock().unwrap();
|
||||||
sd.replace(session.clone());
|
sd.replace(session.clone());
|
||||||
}
|
}
|
||||||
|
WebSocketResponse::Failure(FailureWebSocketResponse {
|
||||||
|
success: _,
|
||||||
|
request_id,
|
||||||
|
errors,
|
||||||
|
}) => {
|
||||||
|
if let Some(id) = request_id {
|
||||||
|
responses_clone.insert(
|
||||||
|
*id,
|
||||||
|
WebSocketResponse::Failure(FailureWebSocketResponse {
|
||||||
|
success: false,
|
||||||
|
request_id: *request_id,
|
||||||
|
errors: errors.clone(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Add it to our pending errors.
|
||||||
|
let mut pe = pending_errors_clone.lock().unwrap();
|
||||||
|
for error in errors {
|
||||||
|
if !pe.contains(&error.message) {
|
||||||
|
pe.push(error.message.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +294,7 @@ impl EngineConnection {
|
|||||||
handle: Arc::new(tcp_read_handle),
|
handle: Arc::new(tcp_read_handle),
|
||||||
}),
|
}),
|
||||||
responses,
|
responses,
|
||||||
|
pending_errors,
|
||||||
socket_health,
|
socket_health,
|
||||||
batch: Arc::new(Mutex::new(Vec::new())),
|
batch: Arc::new(Mutex::new(Vec::new())),
|
||||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||||
@ -351,10 +379,19 @@ impl EngineManager for EngineConnection {
|
|||||||
while current_time.elapsed().as_secs() < 60 {
|
while current_time.elapsed().as_secs() < 60 {
|
||||||
if let Ok(guard) = self.socket_health.lock() {
|
if let Ok(guard) = self.socket_health.lock() {
|
||||||
if *guard == SocketHealth::Inactive {
|
if *guard == SocketHealth::Inactive {
|
||||||
return Err(KclError::Engine(KclErrorDetails {
|
// Check if we have any pending errors.
|
||||||
message: "Modeling command failed: websocket closed early".to_string(),
|
let pe = self.pending_errors.lock().unwrap();
|
||||||
source_ranges: vec![source_range],
|
if !pe.is_empty() {
|
||||||
}));
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: pe.join(", ").to_string(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: "Modeling command failed: websocket closed early".to_string(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We pop off the responses to cleanup our mappings.
|
// We pop off the responses to cleanup our mappings.
|
||||||
|
@ -15,7 +15,16 @@ pub struct RequestBody {
|
|||||||
/// Executes a kcl program and takes a snapshot of the result.
|
/// Executes a kcl program and takes a snapshot of the result.
|
||||||
/// This returns the bytes of the snapshot.
|
/// This returns the bytes of the snapshot.
|
||||||
pub async fn execute_and_snapshot(code: &str, units: UnitLength) -> anyhow::Result<image::DynamicImage> {
|
pub async fn execute_and_snapshot(code: &str, units: UnitLength) -> anyhow::Result<image::DynamicImage> {
|
||||||
let ctx = new_context(units).await?;
|
let ctx = new_context(units, true).await?;
|
||||||
|
do_execute_and_snapshot(&ctx, code).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute_and_snapshot_no_auth(code: &str, units: UnitLength) -> anyhow::Result<image::DynamicImage> {
|
||||||
|
let ctx = new_context(units, false).await?;
|
||||||
|
do_execute_and_snapshot(&ctx, code).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_execute_and_snapshot(ctx: &ExecutorContext, code: &str) -> anyhow::Result<image::DynamicImage> {
|
||||||
let tokens = crate::token::lexer(code)?;
|
let tokens = crate::token::lexer(code)?;
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
let program = parser.ast()?;
|
let program = parser.ast()?;
|
||||||
@ -31,7 +40,7 @@ pub async fn execute_and_snapshot(code: &str, units: UnitLength) -> anyhow::Resu
|
|||||||
Ok(img)
|
Ok(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn new_context(units: UnitLength) -> anyhow::Result<ExecutorContext> {
|
async fn new_context(units: UnitLength, with_auth: bool) -> anyhow::Result<ExecutorContext> {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
||||||
let http_client = reqwest::Client::builder()
|
let http_client = reqwest::Client::builder()
|
||||||
.user_agent(user_agent)
|
.user_agent(user_agent)
|
||||||
@ -47,13 +56,19 @@ async fn new_context(units: UnitLength) -> anyhow::Result<ExecutorContext> {
|
|||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
.tcp_keepalive(std::time::Duration::from_secs(600))
|
||||||
.http1_only();
|
.http1_only();
|
||||||
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
let token = if with_auth {
|
||||||
|
std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set")
|
||||||
|
} else {
|
||||||
|
"bad_token".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
// Create the client.
|
// Create the client.
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
||||||
// Set a local engine address if it's set.
|
// Set a local engine address if it's set.
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
||||||
client.set_base_url(addr);
|
if with_auth {
|
||||||
|
client.set_base_url(addr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = ExecutorContext::new(
|
let ctx = ExecutorContext::new(
|
||||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |