From 2aa71808be8584c5d4a6b39a30464940595bdb04 Mon Sep 17 00:00:00 2001 From: Jason Heeris Date: Sun, 2 Jan 2022 23:40:03 +0800 Subject: [PATCH] Updated book with changes to library error handling. A new section covers error handling with a couple of recommendations. All examples and existing sections are updated with the changes. --- doc/src/SUMMARY.md | 1 + doc/src/adapt_io_example.rs | 11 ++-- doc/src/async_example.rs | 10 ++-- doc/src/ch02-06-errors.md | 36 +++++++++++++ ...02-creating-our-source-part-1-our-types.md | 11 ++-- ...reating-our-source-part-2-setup-methods.md | 12 ++--- ...-source-part-3-processing-events-almost.md | 51 +++++++++++++----- ...-source-part-4-processing-events-really.md | 53 ++++++++++++++----- ...04-06-the-full-zeromq-event-source-code.md | 8 ++- doc/src/timer_example.rs | 20 +++---- doc/src/zmqsource.rs | 42 ++++++++++----- 11 files changed, 180 insertions(+), 75 deletions(-) create mode 100644 doc/src/ch02-06-errors.md diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 6aa53adb..0c8efa8c 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -9,6 +9,7 @@ - [Ping](ch02-03-ping.md) - [Channels](ch02-04-channels.md) - [Unix Signals](ch02-05-signals.md) + - [Error handling](ch02-06-errors.md) - [I need async/await!](ch03-00-async-await.md) - [Run async code](ch03-01-run-async-code.md) - [Async IO types](ch03-02-async-io-types.md) diff --git a/doc/src/adapt_io_example.rs b/doc/src/adapt_io_example.rs index d0d62f68..38d838a2 100644 --- a/doc/src/adapt_io_example.rs +++ b/doc/src/adapt_io_example.rs @@ -2,7 +2,6 @@ use calloop::EventLoop; // ANCHOR: use_futures_io_traits -// futures = "0.3" use futures::io::{AsyncReadExt, AsyncWriteExt}; // ANCHOR_END: use_futures_io_traits @@ -15,10 +14,12 @@ fn main() -> std::io::Result<()> { let mut event_loop = EventLoop::try_new()?; let handle = event_loop.handle(); - handle.insert_source(exec, |evt, _metadata, _shared| { - // Print the value of the async block ie. the return value. - println!("Async block ended with: {}", evt); - })?; + handle + .insert_source(exec, |evt, _metadata, _shared| { + // Print the value of the async block ie. the return value. + println!("Async block ended with: {}", evt); + }) + .map_err(|e| e.error)?; // ANCHOR_END: decl_loop // ANCHOR: decl_io diff --git a/doc/src/async_example.rs b/doc/src/async_example.rs index f2b03811..969fd164 100644 --- a/doc/src/async_example.rs +++ b/doc/src/async_example.rs @@ -14,10 +14,12 @@ fn main() -> std::io::Result<()> { let mut event_loop = EventLoop::try_new()?; let handle = event_loop.handle(); - handle.insert_source(exec, |evt, _metadata, _shared| { - // Print the value of the async block ie. the return value. - println!("Async block ended with: {}", evt); - })?; + handle + .insert_source(exec, |evt, _metadata, _shared| { + // Print the value of the async block ie. the return value. + println!("Async block ended with: {}", evt); + }) + .map_err(|e| e.error)?; // ANCHOR_END: decl_loop // ANCHOR: decl_async diff --git a/doc/src/ch02-06-errors.md b/doc/src/ch02-06-errors.md new file mode 100644 index 00000000..2702ad69 --- /dev/null +++ b/doc/src/ch02-06-errors.md @@ -0,0 +1,36 @@ +# Error handling in Calloop + +## Overview + +Most error handling crates/guides/documentation for Rust focus on one of two situations: + +- Creating errors that an API can propagate out to a user of the API, or +- Making your library deal nicely with the `Result`s from closure or trait methods that it might call + +Calloop has to do both of these things. It needs to provide a library user with errors that work well with `?` and common error-handling idioms in their own code, and it needs to handle errors from the callbacks you give to `process_events()` or `insert_source()`. It *also* needs to provide some flexibility in the `EventSource` trait, which is used both for internal event sources and by users of the library. + +Because of this, error handling in Calloop leans more towards having separate error types for different concerns. This may mean that there is some extra conversion code in places like returning results from `process_events()`, or in callbacks that use other libraries. However, we try to make it smoother to do these conversions, and to make sure information isn't lost in doing so. + +If your crate already has some form of structured error handling, Calloop's error types should pose no problem to integrate into this. All of Calloop's errors implement `std::error::Error` and can be manipulated the same as any other error types. + +The place where this becomes the most complex is in the `process_events()` method on the `EventSource` trait. + +## The Error type on the EventSource trait + +The `EventSource` trait contains an associated type named `Error`, which forms part of the return type from `process_events()`. This type must be convertible into `Box`, which means you can use: + +- Your own error type that implements `std::error::Error` +- A structured error type created with [*Thiserror*](https://crates.io/crates/thiserror) +- `Box` +- A flexible string-based error type such as [*Anyhow's*](https://crates.io/crates/anyhow) `anyhow::Error` + +As a rule, if you implement `EventSource` you should try to split your errors into two different categories: + +- Errors that make sense as a kind of event. These should be a part of the `Event` associated type eg. as an enum or `Result`. +- Errors that mean your event source simply cannot process more events. These should form the `Error` associated type. + +For an example, take Calloop's channel type, [`calloop::channel::Channel`](api/calloop/channel/struct.Channel.html). When the sending end is dropped, no more messages can be received after that point. But this is not returned as an error when calling `process_events()`, because you still want to (and can!) receive messages sent before that point that might still be in the queue. Hence the events received by the callback for this source can be `Msg(e)` or `Closed`. + +However, if the internal ping source produces an error, there is no way for the sending end of the channel to notify the receiver. It is impossible to process more events on this event source, and the caller needs to decide how to recover from this situation. Hence this is returned as a `ChannelError` from `process_events()`. + +Another example might be an event source that represents a running subprocess. If the subprocess exits with a non-zero status code, or the executable can't be found, those don't mean that events can no longer be processed. They can be provided to the caller through the callback. But if the lower level sources being used to run (eg. an asynchronous executor or subprocess file descriptor) fail to work as expected, `process_events()` should return an error. diff --git a/doc/src/ch04-02-creating-our-source-part-1-our-types.md b/doc/src/ch04-02-creating-our-source-part-1-our-types.md index 4abb73aa..e5371610 100644 --- a/doc/src/ch04-02-creating-our-source-part-1-our-types.md +++ b/doc/src/ch04-02-creating-our-source-part-1-our-types.md @@ -12,7 +12,7 @@ So at a minimum, our type needs to contain these: pub struct ZeroMQSource { // Calloop components. - socket_source: calloop::generic::Generic, + socket_source: calloop::generic::Generic, mpsc_receiver: calloop::channel::Channel, wake_ping_receiver: calloop::ping::PingSource, } @@ -26,7 +26,7 @@ What else do we need? If the `PingSource` is there to wake up the loop manually, pub struct ZeroMQSource { // Calloop components. - socket_source: calloop::generic::Generic, + socket_source: calloop::generic::Generic, mpsc_receiver: calloop::channel::Channel, wake_ping_receiver: calloop::ping::PingSource, @@ -59,7 +59,7 @@ where > Remember that it's not just `Vec` and other sequence types that implement `IntoIterator` — `Option` implements it too! There is also `std::iter::Once`. So if a user of our API wants to enforce that all "multi"-part messages actually contain exactly one part, they can use this API with `T` being, say, `std::iter::Once` (or even just `[zmq::Message; 1]` in Rust 2021 edition). ## Associated types -The `EventSource` trait has three associated types: +The `EventSource` trait has four associated types: - `Event` - when an event is generated that our caller cares about (ie. not some internal thing), this is the data we provide to their callback. This will be another sequence of messages, but because we're constructing it we can be more opinionated about the type and use the return type of `zmq::Socket::recv_multipart()` which is `Vec>`. @@ -67,6 +67,8 @@ The `EventSource` trait has three associated types: - `Ret` - this is the return type of the callback that's called on an event. Usually this will be a `Result` of some sort; in our case it's `std::io::Result<()>` just to signal whether some underlying operation failed or not. +- `Error` - this is the error type returned by `process_events()` (not the user callback!). Having this as an associated type allows you to have more control over error propagation in nested event sources. We will use [Anyhow](https://crates.io/crates/anyhow), which is like a more fully-features `Box`. It allows you to add context to any other error with a `context()` method. + So together these are: ```rust,noplayground @@ -78,10 +80,11 @@ where type Event = Vec>; type Metadata = (); type Ret = io::Result<()>; + type Error = anyhow::Error; // ... } ``` ---- -I have saved one surprise for later to emphasise some important principles, but for now, let's move on to defining some methods! \ No newline at end of file +I have saved one surprise for later to emphasise some important principles, but for now, let's move on to defining some methods! diff --git a/doc/src/ch04-03-creating-our-source-part-2-setup-methods.md b/doc/src/ch04-03-creating-our-source-part-2-setup-methods.md index e1b7c464..6be756fa 100644 --- a/doc/src/ch04-03-creating-our-source-part-2-setup-methods.md +++ b/doc/src/ch04-03-creating-our-source-part-2-setup-methods.md @@ -38,9 +38,9 @@ pub fn from_socket(socket: zmq::Socket) -> io::Result<(Self, calloop::channel::S Calloop's event sources have a kind of life cycle, starting with *registration*. When you add an event source to the event loop, under the hood the source will *register* itself with the loop. Under certain circumstances a source will need to re-register itself. And finally there is the *unregister* action when an event source is removed from the loop. These are expressed via the `calloop::EventSource` methods: -- `fn register(&mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory) -> std::io::Result<()>` -- `fn reregister(&mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory) -> std::io::Result<()>` -- `fn unregister(&mut self, poll: &mut calloop::Poll) -> std::io::Result<()>` +- `fn register(&mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory) -> calloop::Result<()>` +- `fn reregister(&mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory) -> calloop::Result<()>` +- `fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()>` The first two methods take a *token factory*, which is a way for Calloop to keep track of why your source was woken up. When we get to actually processing events, you'll see how this works. But for now, all you need to do is recursively pass the token factory into whatever sources your own event source is composed of. This includes other composed sources, which will pass the token factory into *their* sources, and so on. @@ -51,7 +51,7 @@ fn register( &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory -) -> io::Result<()> +) -> calloop::Result<()> { self.socket_source.register(poll, token_factory)?; self.mpsc_receiver.register(poll, token_factory)?; @@ -65,7 +65,7 @@ fn reregister( &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory -) -> io::Result<()> +) -> calloop::Result<()> { self.socket_source.reregister(poll, token_factory)?; self.mpsc_receiver.reregister(poll, token_factory)?; @@ -77,7 +77,7 @@ fn reregister( } -fn unregister(&mut self, poll: &mut calloop::Poll)-> std::io::Result<()> { +fn unregister(&mut self, poll: &mut calloop::Poll)-> calloop::Result<()> { self.socket_source.unregister(poll)?; self.mpsc_receiver.unregister(poll)?; self.wake_ping_receiver.unregister(poll)?; diff --git a/doc/src/ch04-04-creating-our-source-part-3-processing-events-almost.md b/doc/src/ch04-04-creating-our-source-part-3-processing-events-almost.md index 024eed31..c0ac12f4 100644 --- a/doc/src/ch04-04-creating-our-source-part-3-processing-events-almost.md +++ b/doc/src/ch04-04-creating-our-source-part-3-processing-events-almost.md @@ -8,7 +8,7 @@ fn process_events( readiness: calloop::Readiness, token: calloop::Token, mut callback: F, -) -> io::Result +) -> Result where F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, ``` @@ -31,6 +31,8 @@ Implementing `process_events()` for a type that contains various Calloop sources If we were woken up because of the ping source, then the ping source's `process_events()` will see that the token matches its own, and call the callback (possibly multiple times). If we were woken up because a message was sent through the MPSC channel, then the channel's `process_events()` will match on the token instead and call the callback for every message waiting. The zsocket is a little different, and we'll go over that in detail. +For error handling we're using [Anyhow](https://crates.io/crates/anyhow), hence the `context()` calls on each fallible operation. These just add a message to any error that might appear in a traceback. + So a first draft of our code might look like: ```rust,noplayground @@ -39,13 +41,14 @@ fn process_events( readiness: calloop::Readiness, token: calloop::Token, mut callback: F, -) -> io::Result +) -> Result where F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, { // Runs if we were woken up on startup/registration. self.wake_ping_receiver - .process_events(readiness, token, |_, _| {})?; + .process_events(readiness, token, |_, _| {}) + .context("Failed after registration")?; // Runs if we received a message over the MPSC channel. self.mpsc_receiver @@ -53,22 +56,32 @@ where // 'evt' could be a message or a "sending end closed" // notification. We don't care about the latter. if let calloop::channel::Event::Msg(msg) = evt { - self.socket.send_multipart(msg, 0)?; + self.socket + .send_multipart(msg, 0) + .context("Failed to send message")?; } })?; // Runs if the zsocket became read/write-able. self.socket .process_events(readiness, token, |_, _| { - let events = self.socket.get_events()?; + let events = + self.socket + .get_events() + .context("Failed to read ZeroMQ events")?; if events.contains(zmq::POLLOUT) { // Wait, what do we do here? } if events.contains(zmq::POLLIN) { - let messages = self.socket.recv_multipart(0)?; - callback(messages, &mut ())?; + let messages = + self.socket + .recv_multipart(0) + .context("Failed to receive message")?; + + callback(messages, &mut ()) + .context("Error in event callback")?; } })?; @@ -90,7 +103,9 @@ Thirdly, we commit one of the worst sins you can commit in an event-loop-based s self.mpsc_receiver .process_events(readiness, token, |evt, _| { if let calloop::channel::Event::Msg(msg) = evt { - self.socket.send_multipart(msg, 0)?; + self.socket + .send_multipart(msg, 0) + .context("Failed to send message")?; } })?; ``` @@ -108,7 +123,7 @@ where T::Item: Into, { // Calloop components. - socket_source: calloop::generic::Generic, + socket_source: calloop::generic::Generic, mpsc_receiver: calloop::channel::Channel, wake_ping_receiver: calloop::ping::PingSource, @@ -141,21 +156,29 @@ And our "zsocket is writeable" code becomes: ```rust,noplayground self.socket .process_events(readiness, token, |_, _| { - let events = self.socket.get_events()?; + let events = self + .socket + .get_events() + .context("Failed to read ZeroMQ events")?; if events.contains(zmq::POLLOUT) { if let Some(parts) = self.outbox.pop_front() { self.socket - .send_multipart(parts, 0)?; + .send_multipart(parts, 0) + .context("Failed to send message")?; } } if events.contains(zmq::POLLIN) { - let messages = self.socket.recv_multipart(0)?; - callback(messages, &mut ())?; + let messages = + self.socket + .recv_multipart(0) + .context("Failed to receive message")?; + callback(messages, &mut ()) + .context("Error in event callback")?; } })?; ``` -So we've not only solved problem #3, we've also figured out #2, which suggests we're on the right track. But we still have (at least) that first issue to sort out. \ No newline at end of file +So we've not only solved problem #3, we've also figured out #2, which suggests we're on the right track. But we still have (at least) that first issue to sort out. diff --git a/doc/src/ch04-05-creating-our-source-part-4-processing-events-really.md b/doc/src/ch04-05-creating-our-source-part-4-processing-events-really.md index 6bd833bd..669cb7a7 100644 --- a/doc/src/ch04-05-creating-our-source-part-4-processing-events-really.md +++ b/doc/src/ch04-05-creating-our-source-part-4-processing-events-really.md @@ -5,18 +5,26 @@ We have three events that could wake up our event source: the ping, the channel, Also notice that in the zsocket `process_events()` call, we don't use any of the arguments, including the event itself. That file descriptor is merely a signalling mechanism! Sending and receiving messages is what will actually clear any pending events on it, and reset it to a state where it will wake the event loop later. ```rust,noplayground -let events = self.socket.events()?; +let events = self + .socket + .get_events() + .context("Failed to read ZeroMQ events")?; if events.contains(zmq::POLLOUT) { if let Some(parts) = self.outbox.pop_front() { self.socket - .send_multipart(parts, 0)?; + .send_multipart(parts, 0) + .context("Failed to send message")?; } } if events.contains(zmq::POLLIN) { - let messages = self.socket.recv_multipart(0)?; - callback(messages, &mut ())?; + let messages = + self.socket + .recv_multipart(0) + .context("Failed to receive message")?; + callback(messages, &mut ()) + .context("Error in event callback")?; } ``` @@ -28,7 +36,7 @@ fn process_events( readiness: calloop::Readiness, token: calloop::Token, mut callback: F, -) -> io::Result +) -> Result where F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, { @@ -48,18 +56,26 @@ where // Always process any pending zsocket events. - let events = self.socket.get_events()?; + let events = self + .socket + .get_events() + .context("Failed to read ZeroMQ events")?; if events.contains(zmq::POLLOUT) { if let Some(parts) = self.outbox.pop_front() { self.socket - .send_multipart(parts, 0)?; + .send_multipart(parts, 0) + .context("Failed to send message")?; } } if events.contains(zmq::POLLIN) { - let messages = self.socket.recv_multipart(0)?; - callback(messages, &mut ())?; + let messages = + self.socket + .recv_multipart(0) + .context("Failed to receive message")?; + callback(messages, &mut ()) + .context("Error in event callback")?; } Ok(calloop::PostAction::Continue) @@ -90,23 +106,32 @@ The full solution is to recognise that any user action on a ZeroMQ socket can ca ```rust,noplayground loop { - let events = self.socket.get_events()?; + let events = self + .socket + .get_events() + .context("Failed to read ZeroMQ events")?; + let mut used_socket = false; if events.contains(zmq::POLLOUT) { if let Some(parts) = self.outbox.pop_front() { self.socket .as_ref() - .send_multipart(parts, 0)?; + .send_multipart(parts, 0) + .context("Failed to send message")?; used_socket = true; } } if events.contains(zmq::POLLIN) { - let messages = self.socket.recv_multipart(0)?; + let messages = + self.socket + .recv_multipart(0) + .context("Failed to receive message")?; used_socket = true; - callback(messages, &mut ())?; + callback(messages, &mut ()) + .context("Error in event callback")?; } if !used_socket { @@ -120,4 +145,4 @@ Now we have a flag that we set if, and only if, we call a send or receive method > ## Greediness > Remember my disclaimer at the start of the chapter, about this code being "greedy"? This is what I mean. This loop will run until the entire message queue is empty, so if it has a lot of messages in it, any other sources in our event loop will not be run until this loop is finished. > -> An alternative approach is to use more state to determine whether we want to run again on the next loop iteration (perhaps using the ping source), so that Calloop can run any other sources in between individual messages being received. \ No newline at end of file +> An alternative approach is to use more state to determine whether we want to run again on the next loop iteration (perhaps using the ping source), so that Calloop can run any other sources in between individual messages being received. diff --git a/doc/src/ch04-06-the-full-zeromq-event-source-code.md b/doc/src/ch04-06-the-full-zeromq-event-source-code.md index b0df7a75..20813c3d 100644 --- a/doc/src/ch04-06-the-full-zeromq-event-source-code.md +++ b/doc/src/ch04-06-the-full-zeromq-event-source-code.md @@ -6,10 +6,14 @@ This is the full source code for a Calloop event source based on a ZeroMQ socket {{#rustdoc_include zmqsource.rs}} ``` -Dependencies are only `calloop` and `zmq`: +Dependencies are: +- calloop (whatever version this document was built from) +- zmq 0.9 +- anyhow 1.0 ```toml [dependencies] -calloop = "0.8" +calloop = { path = '../..' } zmq = "0.9" +anyhow = "1.0" ``` diff --git a/doc/src/timer_example.rs b/doc/src/timer_example.rs index 49ed049e..e9afd7c6 100644 --- a/doc/src/timer_example.rs +++ b/doc/src/timer_example.rs @@ -4,30 +4,24 @@ use calloop::{timer::Timer, EventLoop, LoopSignal}; fn main() { // ANCHOR: decl_loop let mut event_loop: EventLoop = - EventLoop::try_new() - .expect("Failed to initialize the event loop!"); + EventLoop::try_new().expect("Failed to initialize the event loop!"); // ANCHOR_END: decl_loop // ANCHOR: decl_source - let source = Timer::new() - .expect("Failed to create timer event source!"); + let source = Timer::new().expect("Failed to create timer event source!"); let timer_handle = source.handle(); - timer_handle - .add_timeout(Duration::from_secs(5), "Timeout reached!"); + timer_handle.add_timeout(std::time::Duration::from_secs(5), "Timeout reached!"); // ANCHOR_END: decl_source // ANCHOR: insert_source let handle = event_loop.handle(); handle - .insert_source( - source, - |event, _metadata, shared_data| { - println!("Event fired: {}", event); - shared_data.stop(); - }, - ) + .insert_source(source, |event, _metadata, shared_data| { + println!("Event fired: {}", event); + shared_data.stop(); + }) .expect("Failed to insert event source!"); // ANCHOR_END: insert_source diff --git a/doc/src/zmqsource.rs b/doc/src/zmqsource.rs index ed0429b3..7d0c6825 100644 --- a/doc/src/zmqsource.rs +++ b/doc/src/zmqsource.rs @@ -1,6 +1,8 @@ //! A Calloop event source implementation for ZeroMQ sockets. -use std::{collections, io}; +use std::{collections, io, os::unix::io::RawFd}; + +use anyhow::Context; /// A Calloop event source that contains a ZeroMQ socket (of any kind) and a /// Calloop MPSC channel for sending over it. @@ -46,7 +48,7 @@ where { // Calloop components. /// Event source for ZeroMQ socket. - socket_source: calloop::generic::Generic, + socket_source: calloop::generic::Generic, /// Event source for channel. mpsc_receiver: calloop::channel::Channel, @@ -81,7 +83,7 @@ where let fd = socket.get_fd()?; let socket_source = - calloop::generic::Generic::from_fd(fd, calloop::Interest::READ, calloop::Mode::Edge); + calloop::generic::Generic::new(fd, calloop::Interest::READ, calloop::Mode::Edge); Ok(( Self { @@ -118,19 +120,21 @@ where type Event = Vec>; type Metadata = (); type Ret = io::Result<()>; + type Error = anyhow::Error; fn process_events( &mut self, readiness: calloop::Readiness, token: calloop::Token, mut callback: F, - ) -> io::Result + ) -> Result where F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, { // Runs if we were woken up on startup/registration. self.wake_ping_receiver - .process_events(readiness, token, |_, _| {})?; + .process_events(readiness, token, |_, _| {}) + .context("Failed after registration")?; // Runs if we were woken up because a message was sent on the channel. let outbox = &mut self.outbox; @@ -140,7 +144,8 @@ where if let calloop::channel::Event::Msg(msg) = evt { outbox.push_back(msg); } - })?; + }) + .context("Failed to processing outgoing messages")?; // The ZeroMQ file descriptor is edge triggered. This means that if (a) // messages are added to the queue before registration, or (b) the @@ -157,12 +162,18 @@ where // or receiving on the socket while processing such an event. The // "used_socket" flag below tracks whether we perform an operation // on the socket that warrants reading the events again. - let events = self.socket.get_events()?; + let events = self + .socket + .get_events() + .context("Failed to read ZeroMQ events")?; + let mut used_socket = false; if events.contains(zmq::POLLOUT) { if let Some(parts) = self.outbox.pop_front() { - self.socket.send_multipart(parts, 0)?; + self.socket + .send_multipart(parts, 0) + .context("Failed to send message")?; used_socket = true; } } @@ -170,12 +181,15 @@ where if events.contains(zmq::POLLIN) { // Batch up multipart messages. ZeroMQ guarantees atomic message // sending, which includes all parts of a multipart message. - let messages = self.socket.recv_multipart(0)?; + let messages = self + .socket + .recv_multipart(0) + .context("Failed to receive message")?; used_socket = true; // Capture and report errors from the callback, but don't propagate // them up. - callback(messages, &mut ())?; + callback(messages, &mut ()).context("Error in event callback")?; } if !used_socket { @@ -190,7 +204,7 @@ where &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory, - ) -> io::Result<()> { + ) -> calloop::Result<()> { self.socket_source.register(poll, token_factory)?; self.mpsc_receiver.register(poll, token_factory)?; self.wake_ping_receiver.register(poll, token_factory)?; @@ -204,7 +218,7 @@ where &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory, - ) -> io::Result<()> { + ) -> calloop::Result<()> { self.socket_source.reregister(poll, token_factory)?; self.mpsc_receiver.reregister(poll, token_factory)?; self.wake_ping_receiver.reregister(poll, token_factory)?; @@ -214,7 +228,7 @@ where Ok(()) } - fn unregister(&mut self, poll: &mut calloop::Poll) -> io::Result<()> { + fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> { self.socket_source.unregister(poll)?; self.mpsc_receiver.unregister(poll)?; self.wake_ping_receiver.unregister(poll)?; @@ -244,3 +258,5 @@ where } } } + +pub fn main() {}