diff --git a/Cargo.lock b/Cargo.lock index 4dacc4f822..357fabe7ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1089,6 +1089,7 @@ dependencies = [ "signal-hook-tokio", "smart-default", "swayipc-async", + "thiserror", "tokio", "toml 0.8.0", "unicode-segmentation", @@ -2308,18 +2309,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 9c70ef94e4..f1ce11552d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ signal-hook = "0.3" signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } smart-default = "0.7" swayipc-async = "2.0" +thiserror = "1.0" toml = "0.8" unicode-segmentation = "1.10.1" wayrs-client = { version = "0.12", features = ["tokio"] } diff --git a/src/blocks.rs b/src/blocks.rs index 4a4dd4f2c9..518b00823d 100644 --- a/src/blocks.rs +++ b/src/blocks.rs @@ -29,8 +29,8 @@ mod prelude; -use crate::BoxedFuture; use futures::future::FutureExt; +use futures::stream::FuturesUnordered; use serde::de::{self, Deserialize}; use tokio::sync::{mpsc, Notify}; @@ -41,7 +41,7 @@ use std::time::Duration; use crate::click::MouseButton; use crate::errors::*; use crate::widget::Widget; -use crate::{Request, RequestCmd}; +use crate::{BoxedFuture, Request, RequestCmd}; macro_rules! define_blocks { { @@ -74,29 +74,27 @@ macro_rules! define_blocks { } } - pub fn run(self, api: CommonApi) -> BlockFuture { - let id = api.id; + pub fn spawn(self, api: CommonApi, futures: &mut FuturesUnordered>) { match self { $( $(#[cfg(feature = $feat)])? - Self::$block(config) => async move { + Self::$block(config) => futures.push(async move { while let Err(err) = $block::run(&config, &api).await { - api.set_error(err)?; + if api.set_error(err).is_err() { + return; + } tokio::select! { _ = tokio::time::sleep(api.error_interval) => (), _ = api.wait_for_update_request() => (), } } - Ok(()) - }.boxed_local(), + }.boxed_local()), )* - Self::Err(name, err) => { - std::future::ready(Err(Error { - kind: ErrorKind::Config, - message: None, + Self::Err(_name, err) => { + let _ = api.set_error(Error { + message: Some("Configuration error".into()), cause: Some(Arc::new(err)), - block: Some((name, id)), - })).boxed_local() + }); }, } } @@ -183,7 +181,14 @@ define_blocks!( xrandr, ); -pub type BlockFuture = BoxedFuture>; +/// An error which originates from a block +#[derive(Debug, thiserror::Error)] +#[error("In block {}: {}", .block_name, .error)] +pub struct BlockError { + pub block_id: usize, + pub block_name: &'static str, + pub error: Error, +} pub type BlockAction = Cow<'static, str>; diff --git a/src/blocks/backlight.rs b/src/blocks/backlight.rs index aec16a088f..9997263e26 100644 --- a/src/blocks/backlight.rs +++ b/src/blocks/backlight.rs @@ -175,10 +175,8 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { } Some(e) => { api.set_error(Error { - kind: ErrorKind::Other, message: None, cause: Some(Arc::new(e)), - block: None, })?; } None => { diff --git a/src/blocks/weather.rs b/src/blocks/weather.rs index e0d5e6311b..e3201a53c5 100644 --- a/src/blocks/weather.rs +++ b/src/blocks/weather.rs @@ -292,10 +292,8 @@ async fn find_ip_location(interval: Duration) -> Result { let location = if response.error { return Err(Error { - kind: ErrorKind::Other, message: Some("ipapi.co error".into()), cause: Some(Arc::new(response.reason)), - block: None, }); } else { response diff --git a/src/click.rs b/src/click.rs index 14614fa934..ef5cbdf8ac 100644 --- a/src/click.rs +++ b/src/click.rs @@ -3,7 +3,7 @@ use std::fmt; use serde::de::{self, Deserializer, Visitor}; use serde::Deserialize; -use crate::errors::{Result, ResultExt}; +use crate::errors::{ErrorContext, Result}; use crate::protocol::i3bar_event::I3BarEvent; use crate::subprocess::{spawn_shell, spawn_shell_sync}; diff --git a/src/errors.rs b/src/errors.rs index 81c5767fe7..fb8ac737d2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -12,172 +12,62 @@ type ErrorMsg = Cow<'static, str>; /// Error type #[derive(Debug, Clone)] pub struct Error { - pub kind: ErrorKind, pub message: Option, pub cause: Option>, - pub block: Option<(&'static str, usize)>, -} - -/// A set of errors that can occur during the runtime -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ErrorKind { - Config, - Format, - Other, } impl Error { pub fn new>(message: T) -> Self { Self { - kind: ErrorKind::Other, message: Some(message.into()), cause: None, - block: None, - } - } - - pub fn new_format>(message: T) -> Self { - Self { - kind: ErrorKind::Format, - message: Some(message.into()), - cause: None, - block: None, } } } -pub trait InBlock { - fn in_block(self, block: &'static str, block_id: usize) -> Self; -} - -impl InBlock for Error { - fn in_block(mut self, block: &'static str, block_id: usize) -> Self { - self.block = Some((block, block_id)); - self - } -} - -impl InBlock for Result { - fn in_block(self, block: &'static str, block_id: usize) -> Self { - self.map_err(|e| e.in_block(block, block_id)) - } -} - -pub trait ResultExt { +pub trait ErrorContext { fn error>(self, message: M) -> Result; fn or_error, F: FnOnce() -> M>(self, f: F) -> Result; - fn config_error(self) -> Result; - fn format_error>(self, message: M) -> Result; } -impl ResultExt for Result { +impl ErrorContext for Result { fn error>(self, message: M) -> Result { self.map_err(|e| Error { - kind: ErrorKind::Other, message: Some(message.into()), cause: Some(Arc::new(e)), - block: None, }) } fn or_error, F: FnOnce() -> M>(self, f: F) -> Result { self.map_err(|e| Error { - kind: ErrorKind::Other, message: Some(f().into()), cause: Some(Arc::new(e)), - block: None, - }) - } - - fn config_error(self) -> Result { - self.map_err(|e| Error { - kind: ErrorKind::Config, - message: None, - cause: Some(Arc::new(e)), - block: None, - }) - } - - fn format_error>(self, message: M) -> Result { - self.map_err(|e| Error { - kind: ErrorKind::Format, - message: Some(message.into()), - cause: Some(Arc::new(e)), - block: None, }) } } -pub trait OptionExt { - fn error>(self, message: M) -> Result; - fn or_error, F: FnOnce() -> M>(self, f: F) -> Result; - fn config_error(self) -> Result; - fn or_format_error, F: FnOnce() -> M>(self, f: F) -> Result; -} - -impl OptionExt for Option { +impl ErrorContext for Option { fn error>(self, message: M) -> Result { self.ok_or_else(|| Error { - kind: ErrorKind::Other, message: Some(message.into()), cause: None, - block: None, }) } fn or_error, F: FnOnce() -> M>(self, f: F) -> Result { self.ok_or_else(|| Error { - kind: ErrorKind::Other, message: Some(f().into()), cause: None, - block: None, - }) - } - - fn config_error(self) -> Result { - self.ok_or(Error { - kind: ErrorKind::Config, - message: None, - cause: None, - block: None, - }) - } - - fn or_format_error, F: FnOnce() -> M>(self, f: F) -> Result { - self.ok_or_else(|| Error { - kind: ErrorKind::Format, - message: Some(f().into()), - cause: None, - block: None, }) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.block { - Some(block) => { - match self.kind { - ErrorKind::Config | ErrorKind::Format => f.write_str("Configuration error")?, - ErrorKind::Other => f.write_str("Error")?, - } - - write!(f, " in {}", block.0)?; - - if let Some(message) = &self.message { - write!(f, ": {message}")?; - } - - if let Some(cause) = &self.cause { - write!(f, ". (Cause: {cause})")?; - } - } - None => { - f.write_str(self.message.as_deref().unwrap_or("Error"))?; - if let Some(cause) = &self.cause { - write!(f, ". (Cause: {cause})")?; - } - } + f.write_str(self.message.as_deref().unwrap_or("Error"))?; + + if let Some(cause) = &self.cause { + write!(f, ". Cause: {cause}")?; } Ok(()) diff --git a/src/formatting.rs b/src/formatting.rs index b6fff467bf..6af4fd7536 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -115,6 +115,16 @@ use value::Value; pub type Values = HashMap, Value>; +#[derive(Debug, thiserror::Error)] +pub enum FormatError { + #[error("Placeholder '{0}' not found")] + PlaceholderNotFound(String), + #[error("{} cannot be formatted with '{}' formatter", .ty, .fmt)] + IncompatibleFormatter { ty: &'static str, fmt: &'static str }, + #[error(transparent)] + Other(#[from] Error), +} + #[derive(Debug, Clone)] pub struct Format { full: Arc, diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs index 3c79d63a0a..4d5b751fa4 100644 --- a/src/formatting/formatter.rs +++ b/src/formatting/formatter.rs @@ -11,6 +11,7 @@ use super::parse::Arg; use super::prefix::Prefix; use super::unit::Unit; use super::value::ValueInner as Value; +use super::FormatError; use crate::config::SharedConfig; use crate::errors::*; use crate::escape::CollectEscaped; @@ -57,7 +58,7 @@ pub static DEFAULT_DATETIME_FORMATTER: Lazy = pub const DEFAULT_FLAG_FORMATTER: FlagFormatter = FlagFormatter; pub trait Formatter: Debug + Send + Sync { - fn format(&self, val: &Value, config: &SharedConfig) -> Result; + fn format(&self, val: &Value, config: &SharedConfig) -> Result; fn interval(&self) -> Option { None @@ -195,7 +196,7 @@ pub struct StrFormatter { } impl Formatter for StrFormatter { - fn format(&self, val: &Value, config: &SharedConfig) -> Result { + fn format(&self, val: &Value, config: &SharedConfig) -> Result { match val { Value::Text(text) => { let text: Vec<&str> = text.graphemes(true).collect(); @@ -228,11 +229,11 @@ impl Formatter for StrFormatter { .collect_pango_escaped(), }) } - Value::Icon(icon, value) => config.get_icon(icon, *value), - other => Err(Error::new_format(format!( - "{} cannot be formatted with 'str' formatter", - other.type_name(), - ))), + Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into), + other => Err(FormatError::IncompatibleFormatter { + ty: other.type_name(), + fmt: "str", + }), } } @@ -245,14 +246,14 @@ impl Formatter for StrFormatter { pub struct PangoStrFormatter; impl Formatter for PangoStrFormatter { - fn format(&self, val: &Value, config: &SharedConfig) -> Result { + fn format(&self, val: &Value, config: &SharedConfig) -> Result { match val { Value::Text(x) => Ok(x.clone()), // No escaping - Value::Icon(icon, value) => config.get_icon(icon, *value), - other => Err(Error::new_format(format!( - "{} cannot be formatted with 'str' formatter", - other.type_name(), - ))), + Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into), + other => Err(FormatError::IncompatibleFormatter { + ty: other.type_name(), + fmt: "pango-str", + }), } } } @@ -275,7 +276,7 @@ const VERTICAL_BAR_CHARS: [char; 9] = [ ]; impl Formatter for BarFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { + fn format(&self, val: &Value, _config: &SharedConfig) -> Result { match val { Value::Number { mut val, .. } => { val = (val / self.max_value).clamp(0., 1.); @@ -292,10 +293,10 @@ impl Formatter for BarFormatter { .collect()) } } - other => Err(Error::new_format(format!( - "{} cannot be formatted with 'bar' formatter", - other.type_name(), - ))), + other => Err(FormatError::IncompatibleFormatter { + ty: other.type_name(), + fmt: "bar", + }), } } } @@ -403,7 +404,7 @@ impl EngFixConfig { pub struct EngFormatter(EngFixConfig); impl Formatter for EngFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { + fn format(&self, val: &Value, _config: &SharedConfig) -> Result { match val { Value::Number { mut val, mut unit } => { let is_negative = val.is_sign_negative(); @@ -468,10 +469,10 @@ impl Formatter for EngFormatter { Ok(retval) } - other => Err(Error::new_format(format!( - "{} cannot be formatted with 'eng' formatter", - other.type_name(), - ))), + other => Err(FormatError::IncompatibleFormatter { + ty: other.type_name(), + fmt: "eng", + }), } } } @@ -480,18 +481,18 @@ impl Formatter for EngFormatter { pub struct FixFormatter(EngFixConfig); impl Formatter for FixFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { + fn format(&self, val: &Value, _config: &SharedConfig) -> Result { match val { Value::Number { .. // mut val, // unit, // icon, - } => Err(Error::new_format("'fix' formatter is not implemented yet")), - other => Err(Error::new_format(format!( - "{} cannot be formatted with 'fix' formatter", - other.type_name(), - ))) + } => Err(Error::new("'fix' formatter is not implemented yet").into()), + other => Err(FormatError::IncompatibleFormatter { + ty: other.type_name(), + fmt: "fix", + }), } } } @@ -532,7 +533,7 @@ impl DatetimeFormatter { } impl Formatter for DatetimeFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { + fn format(&self, val: &Value, _config: &SharedConfig) -> Result { match val { Value::Datetime(datetime, timezone) => Ok(match self.locale { Some(locale) => match timezone { @@ -553,10 +554,10 @@ impl Formatter for DatetimeFormatter { }, } .to_string()), - other => Err(Error::new_format(format!( - "{} cannot be formatted with 'datetime' formatter", - other.type_name(), - ))), + other => Err(FormatError::IncompatibleFormatter { + ty: other.type_name(), + fmt: "datetime", + }), } } } @@ -565,7 +566,7 @@ impl Formatter for DatetimeFormatter { pub struct FlagFormatter; impl Formatter for FlagFormatter { - fn format(&self, val: &Value, _config: &SharedConfig) -> Result { + fn format(&self, val: &Value, _config: &SharedConfig) -> Result { match val { Value::Flag => Ok(String::new()), _ => { @@ -580,12 +581,12 @@ mod tests { use super::*; macro_rules! fmt { - ($name:ident, $($key:ident : $value:tt),*) => { - new_formatter(stringify!($name), &[ - $( Arg { key: stringify!($key), val: stringify!($value) } ),* - ]).unwrap() - }; -} + ($name:ident, $($key:ident : $value:tt),*) => { + new_formatter(stringify!($name), &[ + $( Arg { key: stringify!($key), val: stringify!($value) } ),* + ]).unwrap() + }; + } #[test] fn eng_rounding_and_negatives() { diff --git a/src/formatting/template.rs b/src/formatting/template.rs index 727b9edd34..9270076527 100644 --- a/src/formatting/template.rs +++ b/src/formatting/template.rs @@ -1,6 +1,5 @@ use super::formatter::{new_formatter, Formatter}; -use super::parse; -use super::{Fragment, Values}; +use super::{parse, FormatError, Fragment, Values}; use crate::config::SharedConfig; use crate::errors::*; @@ -36,13 +35,18 @@ impl FormatTemplate { }) } - pub fn render(&self, values: &Values, config: &SharedConfig) -> Result> { + pub fn render( + &self, + values: &Values, + config: &SharedConfig, + ) -> Result, FormatError> { for (i, token_list) in self.0.iter().enumerate() { match token_list.render(values, config) { Ok(res) => return Ok(res), - Err(e) if e.kind != ErrorKind::Format => return Err(e), - Err(e) if i == self.0.len() - 1 => return Err(e), - _ => (), + Err( + FormatError::PlaceholderNotFound(_) | FormatError::IncompatibleFormatter { .. }, + ) if i != self.0.len() - 1 => (), + Err(e) => return Err(e), } } Ok(Vec::new()) @@ -68,7 +72,11 @@ impl FormatTemplate { } impl TokenList { - pub fn render(&self, values: &Values, config: &SharedConfig) -> Result> { + pub fn render( + &self, + values: &Values, + config: &SharedConfig, + ) -> Result, FormatError> { let mut retval = Vec::new(); let mut cur = Fragment::default(); for token in &self.0 { @@ -93,7 +101,7 @@ impl TokenList { Token::Placeholder { name, formatter } => { let value = values .get(name.as_str()) - .or_format_error(|| format!("Placeholder '{name}' not found"))?; + .ok_or_else(|| FormatError::PlaceholderNotFound(name.into()))?; let formatter = formatter .as_ref() .map(Box::as_ref) diff --git a/src/lib.rs b/src/lib.rs index 48e2d6a228..2e4ca93b32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ use once_cell::sync::Lazy; use tokio::process::Command; use tokio::sync::{mpsc, Notify}; -use crate::blocks::{BlockAction, BlockFuture, CommonApi}; +use crate::blocks::{BlockAction, BlockError, CommonApi}; use crate::click::{ClickHandler, MouseButton}; use crate::config::{BlockConfigEntry, Config, SharedConfig}; use crate::errors::*; @@ -108,7 +108,7 @@ pub struct BarState { blocks: Vec, fullscreen_block: Option, - running_blocks: FuturesUnordered, + running_blocks: FuturesUnordered>, widget_updates_sender: WidgetUpdatesSender, blocks_render_cache: Vec, @@ -186,6 +186,12 @@ impl Block { } fn set_error(&mut self, fullscreen: bool, error: Error) { + let error = BlockError { + block_id: self.id, + block_name: self.name, + error, + }; + let mut widget = Widget::new() .with_state(State::Critical) .with_format(if fullscreen { @@ -195,7 +201,7 @@ impl Block { }); widget.set_values(map! { "full_error_message" => Value::text(error.to_string()), - [if let Some(v) = &error.message] "short_error_message" => Value::text(v.to_string()), + [if let Some(v) = &error.error.message] "short_error_message" => Value::text(v.to_string()), }); self.state = BlockState::Error { widget }; } @@ -274,12 +280,9 @@ impl BarState { .error_fullscreen_format .with_default_config(&self.config.error_fullscreen_format); - let block_name = block_config.config.name(); - let block_fut = block_config.config.run(api); - let block = Block { id: self.blocks.len(), - name: block_name, + name: block_config.config.name(), update_request, action_sender: None, @@ -295,7 +298,8 @@ impl BarState { state: BlockState::None, }; - self.running_blocks.push(block_fut); + block_config.config.spawn(api, &mut self.running_blocks); + self.blocks.push(block); self.blocks_render_cache.push(RenderedBlock { segments: Vec::new(), @@ -333,7 +337,7 @@ impl BarState { block.notify_intervals(&self.widget_updates_sender); } - fn render_block(&mut self, id: usize) -> Result<()> { + fn render_block(&mut self, id: usize) -> Result<(), BlockError> { let block = &mut self.blocks[id]; let data = &mut self.blocks_render_cache[id].segments; match &block.state { @@ -343,7 +347,11 @@ impl BarState { BlockState::Normal { widget } | BlockState::Error { widget, .. } => { *data = widget .get_data(&block.shared_config, id) - .in_block(block.name, id)?; + .map_err(|error| BlockError { + block_id: id, + block_name: block.name, + error, + })?; } } Ok(()) @@ -357,19 +365,16 @@ impl BarState { } } - async fn process_event(&mut self, restart: fn() -> !) -> Result<()> { + async fn process_event(&mut self, restart: fn() -> !) -> Result<(), BlockError> { tokio::select! { - // Handle blocks' errors - Some(block_result) = self.running_blocks.next() => { - block_result - } + // Poll blocks + Some(()) = self.running_blocks.next() => (), // Receive messages from blocks Some(request) = self.request_receiver.recv() => { let id = request.block_id; self.process_request(request); self.render_block(id)?; self.render(); - Ok(()) } // Handle scheduled updates Some(ids) = self.widget_updates_stream.next() => { @@ -377,15 +382,19 @@ impl BarState { self.render_block(id)?; } self.render(); - Ok(()) } // Handle clicks Some(event) = self.events_stream.next() => { - let block = self.blocks.get_mut(event.id).error("Events receiver: ID out of bounds")?; + let block = self.blocks.get_mut(event.id).expect("Events receiver: ID out of bounds"); match &mut block.state { BlockState::None => (), BlockState::Normal { .. } => { - match block.click_handler.handle(&event).await.in_block(block.name, event.id)? { + let result = block.click_handler.handle(&event).await.map_err(|error| BlockError { + block_id: event.id, + block_name: block.name, + error, + })?; + match result { Some(post_actions) => { if let Some(action) = post_actions.action { block.send_action(Cow::Owned(action)); @@ -416,7 +425,6 @@ impl BarState { self.render(); } } - Ok(()) } // Handle signals Some(signal) = self.signals_stream.next() => match signal { @@ -424,7 +432,6 @@ impl BarState { for block in &self.blocks { block.update_request.notify_one(); } - Ok(()) } Signal::Usr2 => restart(), Signal::Custom(signal) => { @@ -433,33 +440,28 @@ impl BarState { block.update_request.notify_one(); } } - Ok(()) } } } + Ok(()) } - pub async fn run_event_loop(mut self, restart: fn() -> !) -> Result<()> { + pub async fn run_event_loop(mut self, restart: fn() -> !) -> Result<(), BlockError> { loop { if let Err(error) = self.process_event(restart).await { - match error.block { - Some((_, id)) => { - let block = &mut self.blocks[id]; - - if matches!(block.state, BlockState::Error { .. }) { - // This should never happen. If this code runs, it could mean that we - // got an error while trying to display and error. We better stop here. - return Err(error); - } + let block = &mut self.blocks[error.block_id]; - block.set_error(self.fullscreen_block == Some(id), error); - block.notify_intervals(&self.widget_updates_sender); - - self.render_block(id)?; - self.render(); - } - None => return Err(error), + if matches!(block.state, BlockState::Error { .. }) { + // This should never happen. If this code runs, it could mean that we + // got an error while trying to display and error. We better stop here. + return Err(error); } + + block.set_error(self.fullscreen_block == Some(block.id), error.error); + block.notify_intervals(&self.widget_updates_sender); + + self.render_block(error.block_id)?; + self.render(); } } } diff --git a/src/main.rs b/src/main.rs index c9ea3489f7..528564e020 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,20 @@ use clap::Parser; +use i3status_rs::blocks::BlockError; use i3status_rs::config::Config; use i3status_rs::errors::*; use i3status_rs::escape::Escaped; use i3status_rs::widget::{State, Widget}; use i3status_rs::{protocol, util, BarState}; +#[derive(Debug, thiserror::Error)] +enum ErrorMaybeInBlock { + #[error(transparent)] + InBlock(#[from] BlockError), + #[error(transparent)] + NotInBlock(#[from] Error), +} + fn main() { env_logger::init(); @@ -16,7 +25,7 @@ fn main() { protocol::init(args.never_pause); } - let result = tokio::runtime::Builder::new_current_thread() + let result: Result<(), ErrorMaybeInBlock> = tokio::runtime::Builder::new_current_thread() .max_blocking_threads(blocking_threads) .enable_all() .build() @@ -30,7 +39,8 @@ fn main() { for block_config in blocks { bar.spawn_block(block_config).await?; } - bar.run_event_loop(restart).await + bar.run_event_loop(restart).await?; + Ok(()) }); if let Err(error) = result { let error_widget = Widget::new()