-
Notifications
You must be signed in to change notification settings - Fork 597
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
refactor(error): migrate function errors to anyhow #14159
Conversation
Signed-off-by: Runji Wang <wangrunji0408@163.com>
Signed-off-by: Runji Wang <wangrunji0408@163.com>
Signed-off-by: Runji Wang <wangrunji0408@163.com>
Signed-off-by: Runji Wang <wangrunji0408@163.com>
caa5b65 adds function name to the error context, which mostly resolves #13394. cc @TennyZhuang
|
I haven't completely thought it through, but I feel like anyhow might be a bit too heavy for Expr, especially with backtrace. Errors in Expr often occur in succession, and frequent backtracing might cause system crashes. |
Anyway, I like the PR so much. Just some concern about performance. |
Signed-off-by: Runji Wang <wangrunji0408@163.com>
Signed-off-by: Runji Wang <wangrunji0408@163.com>
988d0f2
to
5ba42e2
Compare
Signed-off-by: Runji Wang <wangrunji0408@163.com>
Any ideas on how to disable backtrace inside the |
Signed-off-by: Runji Wang <wangrunji0408@163.com>
Signed-off-by: Runji Wang <wangrunji0408@163.com>
4229025
to
55c2cce
Compare
55c2cce
to
c206788
Compare
@BugenZhao Can you give some insight? A simple solution is |
let error_context = format!("in function \"{}\"", self.name); | ||
output = match user_fn.return_type_kind { | ||
// XXX: we don't support void type yet. return null::int for now. | ||
_ if self.ret == "void" => quote! { { #output; Option::<i32>::None } }, | ||
ReturnTypeKind::T => quote! { Some(#output) }, | ||
ReturnTypeKind::Option => output, | ||
ReturnTypeKind::Result => quote! { Some(#output?) }, | ||
ReturnTypeKind::ResultOption => quote! { #output? }, | ||
ReturnTypeKind::Result => quote! { Some(#output.context(#error_context)?) }, | ||
ReturnTypeKind::ResultOption => quote! { #output.context(#error_context)? }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
qq: So the most important change is here right? Could you teach me why anyhow
is necessary for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is the most important part. It is just a side-effect that anyhow
makes it easy to add context around the error. 🤡
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me rephrase my points: I mean the most important change is the ability to .context
, right? But does this require anyhow
? 🤔 (I mean anyhow
is the implementation detail, instead of the goal) I roughly remember bugen's thiserror_ext
also have context.
BTW, the most important user-facing change looks indeed like the function name. 🤣 Can you show me some other exciting examples?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I roughly remember bugen's thiserror_ext also have context.
Well, it seems to be ContextInto
, instead of ContextInfo
🤡
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From developer's view, they can freely use anyhow
error and no longer need to consider which ExprError
to use. 🤡
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, I'm thinking about sth like this might suffice
struct ContextError {
contexts: Vec<String>,
}
impl ContextError {
fn context(self, context: impl Display) -> Self {
let mut contexts = self.contexts;
contexts.push(context.to_string());
Self { contexts }
}
}
// This trait refers to anyhow
trait Context<T> {
/// Wrap the error value with additional context.
fn context<C>(self, context: C) -> Result<T, ContextError>
where
C: Display + Send + Sync + 'static;
/// Wrap the error value with additional context that is evaluated lazily
/// only once an error does occur.
fn with_context<C, F>(self, f: F) -> Result<T, ContextError>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C;
}
impl<T> Context<T> for Result<T, ContextError> {
fn context<C>(self, context: C) -> Result<T, ContextError>
where
C: Display + Send + Sync + 'static,
{
// Not using map_err to save 2 useless frames off the captured backtrace
// in ext_context.
match self {
Ok(ok) => Ok(ok),
Err(error) => Err(error.context(context)),
}
}
fn with_context<C, F>(self, context: F) -> Result<T, ContextError>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
match self {
Ok(ok) => Ok(ok),
Err(error) => Err(error.context(context())),
}
}
}
fn foo() -> Result<(), ContextError> {
Err(ContextError {
contexts: vec!["foo".to_string()],
})
}
fn bar() -> Result<(), ContextError> {
foo().context("wtf").context("woo")
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this is just similar to tygg's comment above
A simple solution is Box with some syntax sugur (e.g. context).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, the implementation of anyhow::Context::context
uses an anonymous error type to provide the context and put the original error as its source
, in which way the source chain is correctly maintained.
https://docs.rs/anyhow/latest/src/anyhow/error.rs.html#305-308
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I view these expr errors as leaf errors. So just feel some string stuff is enough.
I guess there's no way to disable that. The backtrace will always be captured in nightly channel. https://docs.rs/anyhow/latest/src/anyhow/lib.rs.html#131-132 |
We can use a forked |
about the backtrace's overhead: dtolnay/anyhow#41 (comment) and some numbers I tried previously #6205 (comment) I think the most important question is whether this sentence holds true 🤔
|
I have a rough idea (partially inspired by @yuhao-su).
According to the doc of anyhow, it seems that they will uses /// Underlying, real error in the implementation
struct ParseIntError;
struct DisableBacktrace<E: Error>(E);
impl Error for DisabeBacktrace {
fn provide<'a>(&'a self, req: &mut Demand<'a>) {
// Pseudo code, need more investigation
req.provide(Backtrace::disabled);
}
}
anyhow::Error::new(DisableBacktrace(ParseIntError)); |
After discussion with @TennyZhuang, we decided to encourage function implementors to return custom error types rather than anyhow, so that functions can be reused outside of expression framework. The |
If we really want a backtrace in the upper layer, it's worth noting that we might be misguided as well. A simple workaround could be not marking |
If this is the main motivation, I guess we can achieve similar effects with Check this example: https://github.com/BugenZhao/thiserror-ext/blob/8e8d7c65f3ddbc1f9097e9200a83f8d3bf9d2a70/examples/context.rs UPDATE: Lazily evaluate the context: https://github.com/risingwavelabs/thiserror-ext/blob/f28a7b9188460d14aa7f12ccaa79cbeb7a6e2e25/examples/context.rs#L31-L33 |
Previously I was thinking adding context to the outer struct. 🤣 This solution is quite simple (reminds me of linked list) |
I hereby agree to the terms of the RisingWave Labs, Inc. Contributor License Agreement.
What's changed and what's your intention?
SQL functions now return
anyhow
error. Making it easy for implementors to construct errors usinganyhow!
,bail!
and.context()
.Checklist
./risedev check
(or alias,./risedev c
)Documentation