-
Notifications
You must be signed in to change notification settings - Fork 353
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
[Do not merge] Refactor to address RFCs #63
Conversation
…ve ErrorReponse directly
…e to handle any error type
…ration to the new() function on the ErrorResponse
…+ Send + Sync + Debug. Updated samples to reflect this
…nes a Vec<u8> based handler
lambda-runtime-client/src/error.rs
Outdated
@@ -135,6 +153,8 @@ impl Error for ApiError { | |||
None | |||
} | |||
} | |||
unsafe impl Send for ApiError {} |
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.
Hmm, why's this is necessary? would be useful to add a comment.
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.
This will go away, with the next iteration all errors will switch to failure. I have quite a bit of legacy error handling code in here. Just wanted to make one big change at a time.
/// | ||
/// # Returns | ||
/// A new `RuntimeError` instance with the `recoverable` property set to `false`. | ||
pub(crate) fn unrecoverable(msg: &str) -> RuntimeError { |
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.
This could be late, but a potential option would be to have a struct named RuntimeError
, several convenience methods, and an internal enum called RuntimeErrorKind
that has several variants that include whether or not this an irrecoverable error.
While I'm questioning assumptions in this crate, what do we consider to be an irrevocable error? Is it just an error that wasn't caught and handled?
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.
Same comment as above.
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.
See my comment above.
/// The `RuntimeError` object is returned by the custom runtime as it polls | ||
/// for new events and tries to execute the handler function. The error | ||
/// is primarily used by other methods within this crate and should not be relevant | ||
/// to developers building Lambda functions. Handlers are expected to return |
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.
should not be relevant to developers building Lambda functions.
I'd maybe phrase this along the lines of:
This error is primarily used by the Lambda runtime itself. It not expected that Lambda function implementations will need to construct or handle this error.
As I wrote that, I realized that maybe RuntimeError can be pub(crate)
. Thoughts?
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.
Agree, no need to expose this. I will make the change as I work on the errors refactor.
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.
Wanted to followup—should these remain public?
lambda-runtime-core/src/runtime.rs
Outdated
@@ -252,7 +194,7 @@ where | |||
"Error for {} is not recoverable, sending fail_init signal and panicking", | |||
request_id | |||
); | |||
self.runtime_client.fail_init(&e); | |||
self.runtime_client.fail_init(&ErrorResponse::from(Box::new(e))); |
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.
Perhaps I'm missing something, but why does this need to be boxed first?
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.
Same as above - haven't touched errors at all
lambda-runtime/src/lib.rs
Outdated
/// defined in the `lambda_runtime_core` crate. The closure simply uses `serde_json` | ||
/// to serialize and deserialize the incoming event from a `Vec<u8>` and the output | ||
/// to a `Vec<u8>`. | ||
fn wrap<E, O>(mut h: impl Handler<E, O>) -> impl FnMut(Vec<u8>, Context) -> Result<Vec<u8>, error::HandlerError> |
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 like this!
… runtime core now imports a generated function to collect the runtime info
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.
These changes look good to me.
runtime_agent: String, | ||
host: String, |
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.
Why the addition of host
and change from endpoint
to next_endpoint
?
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.
next_endpoint
is the pre-parsed uri for the /next
endpoint, which remains stable throughout the lifetime of the client. The host is used to re-generate the Uri
for the response endpoints for each request. I'm caching the next_endpoint
Uri
value to avoid parsing it each time.
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.
Posted a handful of comments. Notably I'm wondering if this change could been broken down into digestable chunks (pulls). This felt like a lot to absorb in one pull.
lambda-runtime-client/src/error.rs
Outdated
|
||
err | ||
} | ||
/*#[test] skip this tests as it's broken right now |
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 aways feel better about using git branches to manage unfinished code. Does this test provide ant value in it's commented out form? If it's a signal I'd suggesting using #[ignore]
https://doc.rust-lang.org/1.5.0/book/testing.html#the-ignore-attribute
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 removed this as per @davidbarsky's suggestion. We will re-introduce this once we finally figure out what is going on with backtraces in failure... #!@?!!&
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.
yeah, it might've been an older commit.
lambda-runtime/src/lib.rs
Outdated
} | ||
|
||
/// Initializes the Lambda runtime with the given handler. Optionally this macro can | ||
/// also receive an instance of Tokio runtime for the Hyper HTTP client. If the Tokio |
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 feel like it would be more useful for readers of these api docs to see something like "receive a customized instance of Tokio runtime to drive internal lambda operations to completion". The mention of Hyper HTTP client is kind of an implementation detail that's not useful for readers.
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.
Like it. Made the change
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 think this might've been addressed partially in #63 (comment).
impl<Function, Event, Output, EventError> Handler<Event, Output, EventError> for Function | ||
where | ||
Function: FnMut(Event, Context) -> Result<Output, EventError>, | ||
EventError: Fail + LambdaErrorExt + Display + Send + Sync, |
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.
Few notes here.
Because Fail requires Display + Send + Sync. Adding Display + Send + Sync
on top of Fail
is redundant.
I'll have to read through a bit more to understand the purpose of LambdaErrorExt
but my initial impression is this. The core interface should make the trivial easy and the complex possible. If a dependency on Fail
wasn't enough implementing LambdaErrorExt
may seem like a tall order for making the simple trivial. Thoughts?
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.
The reason why we have Display, Send, and Sync there is that I don't trust the failure crate to maintain those in the Fail
trait. This library has burned me more than once recently (scorched me to death is more appropriate). Since in the next iteration of the library we plan to make the core async we wanted to introduce a constraint of our own in this, even if failure removes it.
Lambda gives us the ability to return an error type field which we plan to use to specify the error's root cause. The LambdaErrorExt trait defines the error_type(&self) -> &str
to provide this. In the crate we have included implementations of the trait for almost all errors in the standard library as well as failure types. With these default implementations, I'm hoping to still make the trivial easy (+1 on this turn of phrase by the way, very nicely put).
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.
The reason why we have Display, Send, and Sync there is that I don't trust the failure crate to maintain those in the Fail trait. This library has burned me more than once recently (scorched me to death is more appropriate). Since in the next iteration of the library we plan to make the core async we wanted to introduce a constraint of our own in this, even if failure removes it.
You're not wrong to distrust Failure given your experience, but the way the Failure is evolving is to just provide a derive for std::error::Error + Display + Send + Sync bounds. Additionally, the standard library encourages implementing Debug + Display on errors, and I can think of a single error in the standard library (std::sync::PoisonError
) that doesn't implement Send/Sync. Most errors I've seen implement Send/Sync, and if they don't, that's often grounds for opening a PR/issue.
(With regard to std::sync::PoisonError
—I believe that most people just panic on a poisoned mutex. I'll be shocked if a Lambda customer has to worry about poisoned mutexes.
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.
Im thinking then may it's a good idea to put the Failure dependency behind a feature flag to maintain a more stable API. Scorch free!
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.
Responded in another comment too (perhaps we should switch to the main PR thread for this conversation). With the current implementation, we don't force the failure crate or the Fail
trait on developers building Lambda functions, their errors become Fail for us automatically because we include failure. This gives us the ability to switch away from failure in the future without making a breaking change.
} | ||
// the value return by the error_type function is included as the | ||
// `errorType` in the AWS Lambda response | ||
impl LambdaErrorExt for CustomError { |
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 can foresee this getting very onerous. Not only for those familiar with rust but for those new to rust dabbling through experimenting transitioning a lambda to rust. Is there any thing the runtime could provide that could provide a default impl that allows for specialization when needed? This goes back to making the trivial easy and the complex possible.
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.
Something I'm debating—not really brought up with Stefano—is to point people to just use Failure and be done with it, but I'm not convinced that's the right approach.
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.
Failure sounds like a same middle ground as it plays nice with the ecosystem what already exists. The complexity seems to come from an implementation detail but if feels like too much weight to on users which won't benefit as much/appreciate the feature of the named errors inside lambda responses. It's likely user logging errors to cloudwatch logs will provide the same debugging benefit at no extra interface cost.
That said do we gain anything from failure over Box beside the back trace? Std Errors are really the least common denominator and work well with ? Chaining
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.
Failure sounds like a same middle ground as it plays nice with the ecosystem what already exists. The complexity seems to come from an implementation detail but if feels like too much weight to on users which won't benefit as much/appreciate the feature of the named errors inside lambda responses. It's likely user logging errors to cloudwatch logs will provide the same debugging benefit at no extra interface cost.
I keep going back and forth on this, but the InvocationError endpoint does expect an ErrorType. I wonder if we should have a top-level error handler—required that a customer implement this—that handles such reporting. The issue with a Display impl, iirc, is that the Display won't give an accurate error report.
The other thing is that I have opinions as to how things should be, but Stefano works with customers who use Lambda, and he sees how people use.
That said do we gain anything from failure over Box beside the back trace? Std Errors are really the least common denominator and work well with
?
Chaining
Easier derives + From impls. It's really a nicer experience, IMO.
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'm a customer as well!
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.
That's why I had included the error derive crate, to allow developers to just:
#[derive(LambdaErrorExt)]
struct BasicCustomError;
Do you feel we need more? What should our generic be for the trait implementation be, any Fail
?
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'm a customer as well!
That's true, sorry! I just remember him telling me that people are creating Dashboards on specific error strings.
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.
One more thing. We implemented errors this way to avoid forcing the failure crate on users of the runtime - it's purely a utility for us. When they take the runtime as a dependency their errors automatically become fails because we bring the failure crate. Once the standard library errors include back traces we can remove failure from the runtime implementation without it being a breaking change
|
||
#[test] | ||
fn does_not_produce_stack_trace() { | ||
env::remove_var("RUST_BACKTRACE"); |
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.
Backtraces... are kinda tricky to test. I'm not sure of a better way, though.
/// The `RuntimeError` object is returned by the custom runtime as it polls | ||
/// for new events and tries to execute the handler function. The error | ||
/// is primarily used by other methods within this crate and should not be relevant | ||
/// to developers building Lambda functions. Handlers are expected to return |
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.
Wanted to followup—should these remain public?
|
||
pub use crate::error_ext_impl::*; | ||
|
||
use failure::{format_err, Compat, Error, Fail}; |
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 think Failure can be placed behind a feature flag that's on by default. Do you disagree?
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.
Crowbar did that with error_chain. It's actually a nice feature of rust that fits the 0 cost tagline. Only pay for what you use, and make it possible to opt out of what you dont
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.
See my answer in the other comments - why put it behind a feature flag if it has no impact on developers using the library? They do not have to use failure, it's just a utility for us.
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.
The feature flag allows you conditionally disable compilation for libraries that you're not using.
I think we should use Failure internally, but customers shouldn't need to pay the compilation cost if they're not using Failure in their application. The conditional compilation/feature flag might make some the conversions easier.
} | ||
// the value return by the error_type function is included as the | ||
// `errorType` in the AWS Lambda response | ||
impl LambdaErrorExt for CustomError { |
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.
Something I'm debating—not really brought up with Stefano—is to point people to just use Failure and be done with it, but I'm not convinced that's the right approach.
Apologies for the huge number of changes. We worked on bite-sized chunks with @davidbarsky as he reviewed each commit during development. The nature of the changes (refactor) made them hard to publish them in separate PRs without potentially breaking stuff for anyone depending on master. Hopefully it won't (need to) happen again. |
}*/ | ||
|
||
for v in s.variants() { | ||
println!("Variant: {}", v.ast().ident); |
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.
println?
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.
whoopsie - some careful debugging there on my part.
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.
The derive crate is very much a work in progress. Few more things I'd like to do.
Moving the I spoke with @davidbarsky this morning. The dependency on Both David and I agree that this is a temporary dependency that we will remove in the future, once backtraces are available in the standard library. Since we do not require Other than compilation time, and on the basis that we plan to remove failure in the future once backtraces are stable in the standard library, do you have any other concern with releasing |
No concerns here. I didn't realize all you need is std Error. That sounds great. I'd love a release. I've got a growing number of serverless framework templates that depend on git urls for the http lambda module. I'd love to update those to a release |
Huzza! I'll merge and push 0.2 tomorrow. The http crate will go out as a 0.1. I'll probably hold off on the errors_derive crate until I've had time to clean it up properly. |
This is a major refactor of the crate structure to address the comments in RFCs #53. While the changes are major, they should not be breaking for existing code.
Description of changes: Moved all of the "main loop" logic in a
runtime-core
crate that defines aVec<u8>
-based handler. The runtime crate now simply wraps the bytes handler into a typed handler method using serde.By submitting this pull request