Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of concept of dyn dispatch based report handler #97

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

yaahc
Copy link
Contributor

@yaahc yaahc commented Jun 6, 2020

No description provided.

tests/test_handler.rs Outdated Show resolved Hide resolved
tests/test_handler.rs Outdated Show resolved Hide resolved
Copy link
Owner

@dtolnay dtolnay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great -- I am optimistic about the approach. I think I wouldn't want to land this in anyhow right away but I would be on board with accepting something like this after iterating on the specific signatures in a separate crate and having some built out ecosystem of those handlers to ensure we've covered the use cases that this is intended to cover.

@dtolnay
Copy link
Owner

dtolnay commented Jun 21, 2020

On the backtrace() function -- one thing I would like in std is a constructor on Backtrace to cheaply make a backtrace with Disabled status. Does that help here? That can be the backtrace if one is not available from the handler.

@yaahc
Copy link
Contributor Author

yaahc commented Jun 21, 2020

On the backtrace() function -- one thing I would like in std is a constructor on Backtrace to cheaply make a backtrace with Disabled status. Does that help here? That can be the backtrace if one is not available from the handler.

That sounds like a great approach

@yaahc
Copy link
Contributor Author

yaahc commented Jun 21, 2020

This looks great -- I am optimistic about the approach. I think I wouldn't want to land this in anyhow right away but I would be on board with accepting something like this after iterating on the specific signatures in a separate crate and having some built out ecosystem of those handlers to ensure we've covered the use cases that this is intended to cover.

Sounds reasonable. I'm in the process of applying this change to eyre right now eyre-rs/eyre#29, which should hopefully provide sufficient dogfooding of the design to prove that it works and iron out all of the kinks.

Once I'm done making the changes in eyre and color-eyre I'll finalize this PR and release an anyhow based version of color-eyre that provides all the same features on top of anyhow::Error.

@yaahc
Copy link
Contributor Author

yaahc commented Jul 18, 2020

This has been in eyre for a few weeks now and has been working great. I'm going to move forward with cleaning up this PR and then I'll write an anyhow based version of color-eyre to leverage the new feature.

@yaahc
Copy link
Contributor Author

yaahc commented Jul 18, 2020

Because there's not currently an API for capturing the disabled backtrace I'm going to go ahead and add a required fn on the ReportHandler:

    fn backtrace(&self, error: &(dyn StdError + 'static)) -> &crate::backtrace::Backtrace;

That way any customizers will just have to implement this properly or explicitly panic if they don't want to support backtraces, then in the future we can change this to a default trait fn that returns a reference to a statically captured disabled backtrace if the source error didn't capture one.

@dtolnay
Copy link
Owner

dtolnay commented Jul 18, 2020

Sounds good to me.

Could you make sure to publish release notes or a changelog for eyre, including retroactively for the past couple releases if possible? I noticed there have been a couple breaking changes quite recently and couldn't find a writeup of what changed, which would help me evaluate how baked this is. Separately, from skimming crates.io it looks like the majority of the downstream crates not written by you are using old versions of eyre i.e. without this implementation; ideally we'd make sure they are able to upgrade and are happy with the new design.

@yaahc
Copy link
Contributor Author

yaahc commented Jul 18, 2020

Could you make sure to publish release notes or a changelog for eyre, including retroactively for the past couple releases if possible?

Sure! I can tell you now what the recent breaking changes are. The most recent one was removing the generic parameter in favor of the trait object approach. The one before that was to fix a bug in the eyre! macro where I'd incorrectly implemented the kind lookup for E: Error so it was not generic and immediately resolved to the default parameter provided by eyre, which broke usage with color_eyre if you tried to do something like Err(eyre!(e)).suggestion("foo").

@yaahc
Copy link
Contributor Author

yaahc commented Jul 18, 2020

Separately, from skimming crates.io it looks like the majority of the downstream crates not written by you are using old versions of eyre i.e. without this implementation; ideally we'd make sure they are able to upgrade and are happy with the new design.

I've had a couple of upgrade reports, the biggest issue so far has been people forgetting to add the color_eyre::install() and thinking the functionality broke in the new version. I'll check out the other reverse deps and see if I can't get more of them to upgrade and report back on how it went.

@@ -584,15 +588,142 @@ pub trait Context<T, E>: context::private::Sealed {
F: FnOnce() -> C;
}

static HOOK: OnceCell<ErrorHook> = OnceCell::new();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lmk if you want me to move this all to a handler.rs file or something

}

#[cfg(not(feature = "std"))]
trait ReportHandler: core::any::Any + Send + Sync {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find a better way to make this trait conditionally pub, we can't export this trait under no_std because the StdError trait is private but we still need to use this trait to format the error reports via the DefaultHandler when in no_std.

}
}

#[cfg(feature = "std")]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might want to add some doc_cfg's for all these publicly exported std only apis.

@yaahc yaahc requested a review from dtolnay July 18, 2020 20:36

impl crate::ReportHandler for crate::DefaultHandler {
#[cfg(backtrace)]
fn backtrace<'a>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we can have a publicly exported method that depends on a non feature cfg, I'll make sure to test this when I port color-eyre to depend on this branch

@yaahc
Copy link
Contributor Author

yaahc commented Jul 18, 2020

https://github.com/yaahc/color-anyhow/actions/runs/174054634 seems to be working ^_^

Edit: JK, it doesn't work for the fn backtrace on nightly, as I feared. It looks like the cfg(backtrace) doesn't transitively apply to color-anyhow just by depending on anyhow, sad day. I'm pretty sure I can solve this by vendoring the build.rs logic from anyhow and applying the same approach it uses to feature gate that fn, but I'm not confident this is a good way to solve this issue.

@yaahc
Copy link
Contributor Author

yaahc commented Jul 18, 2020

I've run into another snag with implenting backtrace support in color-anyhow. by default color-anyhow always captures a backtrace, even on stable via backtrace-rs, where it then uses the frame api provided in that crate to iterate over the frames and produce the nice colorful backtrace. I wanted to maintain that support so I figured I'd actually use backtrace only on stable and use std::backtrace::Backtrace and btparse on nightly to get the frames from the debug impl and maintain the nice color backtrace output.

Here's the problem though...

Screenshot from 2020-07-18 14-50-21

Apparently you can't do optional deps based on cfg values, only on features 😢.

I can work around this by copying the backtrace build.rs file once again into btparse to make it conditionally compile the entire API, but I'll still end up with an unused backtrace dep on nightly :/

@yaahc
Copy link
Contributor Author

yaahc commented Jul 18, 2020

Well, with sufficient application of build.rs files I got it all working, I now have a full version of color-eyre working ontop of anyhow that even uses std::backtrace::Backtrace on nightly and gets colorized backtraces! This is extremely cool.

https://github.com/yaahc/color-anyhow/actions/runs/174105901

@yaahc
Copy link
Contributor Author

yaahc commented Jul 18, 2020

@mystor raised some pretty good concerns about the #[cfg(backtrace)] public trait fn and requiring implementors of ReportHandler to vendor the build.rs logic to correctly implement the trait being fragile and probably not good, so I'm going to go ahead and open a PR to libstd to add a Backtrace::disabled() fn

@yaahc
Copy link
Contributor Author

yaahc commented Jul 22, 2020

Regarding the earlier question about how "Baked" the design is. One area where I don't think its fully baked yet is how it interacts with #[track_caller]. I recently tried using it on beta and ended up finding a miscompilation bug when using track caller on function calls through trait objects. Ideally I'd like to just support #[track_caller] on custom handlers, where they will grab the callsite on their own and we never have to interact with it other than attributing the relevant fns in anyhow to get it to forward the information along. That way customized error reports can be more consistent with panic reports which already capture the location they were constructed by default.

My worry is that if it isn't possible to just pass the location all the way into the custom handler via #[track_caller] attributes we may need to manually pass across the trait boundary which would require some tweaks to the API. This can be done backwards compatibly but since we've not yet committed to an API for anyhow I think we might want to consider API changes from eyre if it means we get a better interface.

My existing ideas for working around the issue (if it indeed ends up being an issue) include either:

  • (breaking change) changing the Hook signature from Fn(&self, error: &(dyn Error + 'static)) -> Box<dyn ReportHandler> too Fn(&self, error(&dyn Error + 'static), location: &Location<'static>) -> Box<dyn ReportHandler>
  • (backwards compatible) Adding a fn track_caller(&mut self, location: &Location<'static>) { } fn to the ReportHandler trait that is defaulted to just discarding the location, where the implementors can customize the impl to save the location, then anyhow would first construct the context then try passing in the location every time it constructs a new anyhow::Error.
  • (backwards compatible) Add a second set_hook fn that takes a closure with the second signature from the first bullet instead of the original signature, so downstream crates could opt into the new construction API but the old API is still supported.

Manishearth added a commit to Manishearth/rust that referenced this pull request Jul 23, 2020
add a Backtrace::disabled function

Based upon @dtolnay's suggestion here: dtolnay/anyhow#97 (comment)
@yaahc
Copy link
Contributor Author

yaahc commented Nov 17, 2020

Status update

I've had track_caller in eyre for a while now, been working great no issues there, and a breaking change was not necessary. Other than that for a while now I've been thinking about how this design should integrate with libraries that want to use eyre::Report or anyhow::Error as their error type, such as tide/http-types, and was worried some more changes might be necessary to better support these libraries. However recently I came to the conclusion that we don't need to add special support for these kinds of use cases, since they could be built on top of the existing abstractions, and nobody is actually asking for such support as far as I can tell.

More details can be found here eyre-rs/eyre#35 (comment)

All in all I'm confident in the current design and would like to move forward with finalizing this PR, getting it up to date and consistent with eyre, and addressing any final blockers.

Ecosystem Compatibility

That said, I do have one extra goal that I want to aim for. Ideally, I'd like custom hook libraries like color-eyre to be compatible with both eyre and anyhow. Would you be open to creating another dependency just for the trait interface, set_hook and get_hook logic so hooks and handlers defined by any crate work with both reporting types?

@vultix
Copy link

vultix commented Jul 26, 2021

@yaahc @dtolnay What's the status on this PR? There’s a bit of an ecosystem split between anyhow and eyre and this seems like a great way to help unify the two

@yaahc
Copy link
Contributor Author

yaahc commented Jul 26, 2021

@yaahc @dtolnay What's the status on this PR? There’s a bit of an ecosystem split between anyhow and eyre and this seems like a great way to help unify the two

Status is more or less that we need to resolve the following blockers:

  • Figure out if we're going to create a shared dependency to define the Handler trait
    • Assuming yes, and reading backscroll it looks like David already suggested doing so, update eyre to use this shared handler dep
  • Update PR to resolve merge conflicts
  • Come to a consensus that anyhow would like to merge this

@rongcuid
Copy link

Is this still possible?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants