-
Notifications
You must be signed in to change notification settings - Fork 108
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
[Draft] Detached tasks #334
Conversation
@sebsto @adam-fowler any feedback is welcome |
I love the idea. I need more time to look at the implementation |
That's great! I would love to contribute to a more Swift Concurrency friendly runtime. |
@swift-server-bot test this please |
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 not in favor of this pr for two reasons:
-
AWS Lambda does not support background processing in the way this pr tries to add this functionality. AWS Lambda freezes the users execution environment as soon as a request has finished. This means we can not rely on any background processing in Lambda. The correct way to implement this is to wrap the Extensions API, that allows background processing for which users are then charged correctly.
-
The proposed approach does not integrate nicely with Swift structured concurrency. The correct Swift structured concurrency approach should work as follows:
enum MyBackgroundEvents {
case myEvent(String)
}
let (stream, continuation) = AsyncStream.makeStream(of: MyBackgroundEvents.self)
let runtime = LambdaRuntime { (request, context) -> String in
continuation.yield(.myEvent("whatever"))
return "Woohoo"
}
await withTaskGroup {
taskGroup.addTask {
try await runtime.run()
}
taskGroup.addTask {
for await event in stream {
// do the actual background processing
}
}
}
We intend to build out an API like the one above. However currently we are blocked on this ServiceLifecycle issue.
However even though this is the correct Swift approach, it is very wrong in the Lambda usecase (see 1.). The real thing is to write a wrapper based on the same primitives that correctly implements the Extensions API.
// Pure Swift
Task.detached {
try await fireAndForget()
}
// Lambda handler
context.task.detached {
try await fireAndForget()
} |
Having thought about this a bit more, I think I have to conclusion that I'm open to this patch in v1. For v2, we should 100% make the concurrency approach structured. A new API proposal will be made shortly. @Buratti thanks for pointing out that lambda supports background processing. |
extension DetachedTasksContainer: @unchecked Sendable {} | ||
extension DetachedTasksContainer.Storage: @unchecked Sendable {} | ||
extension DetachedTasksContainer.RegistrationKey: Sendable {} |
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.
We must make sure that all those types are Sendable
without using @unchecked
. I'm sure we can achieve 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 believe we can, I took inspiration from Terminator.swift for that part, but I'll change it.
@Buratti Sorry for not making this explicit. Thanks for pushing on this! Really appreciated. Gave me quite a bit food for thought! Let's try to land this! |
Hi @fabianfett! Thank you for giving this a deeper thought and for the feedbacks, I'll make sure to address them in the next couple of days. |
// 3. report results to runtime engine | ||
self.runtimeClient.reportResults(logger: logger, invocation: invocation, result: result).peekError { error in | ||
logger.error("could not report results to lambda runtime engine: \(error)") | ||
} | ||
// To discuss: |
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 is the only point that remains open @fabianfett. When the runtime fails to report a result to AWS Lambda, do we want to wait for the background tasks to complete before stopping the execution of the whole process?
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.
In the error case we should await all subtasks.
Thank you @Buratti for having submitted this change. |
Hi @sebsto! Please forgive me, last few weeks have been very full! I'll try my best to have them ready before tomorrow EOD! |
@swift-server-bot test this please |
Thank you for the changes ! Can you add a example code + a section in the README doc - that will allows developers to learn about that feature without having to look at the internals of the code :-) It looks like the code does not compile on 5.7 - Is this something easy to fix ?
|
Hi @sebsto @fabianfett. Thanks for sharing the V2 proposal with me, I will read it thoroughly as soon as I finished the changes to this PR. |
@swift-server-bot test this please |
Should I run the soundness script? |
I’m off this week with just my phone and crappy network. I’ll check the submission next week. But to answer your question, yes the soundness script must run without reporting errors. |
Hello @Buratti ! There is still a minor problem on the soudness script, just a formatting issue on DetachedTasks. |
commit ab8166a Author: Franz Busch <privat@franz-busch.de> Date: Mon Aug 26 16:36:07 2024 +0200 [CI] Add GHA CI and release flow (swift-server#340) Co-authored-by: Fabian Fett <fabianfett@apple.com> Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com> Co-authored-by: Mahdi Bahrami <github@mahdibm.com> commit 5ecc24f Author: Andrea Scuderi <andreascuderi@ymail.com> Date: Mon Aug 26 13:00:07 2024 +0200 Add Breeze to projects.md (swift-server#343) authored-by: Andrea Scuderi <andrea.scuderi@ymail.com> commit 8676c89 Author: Sébastien Stormacq <sebastien.stormacq@gmail.com> Date: Mon Aug 26 12:25:41 2024 +0200 apply swiftformat (swift-server#342) * apply swiftformat * update dep on Swift Docc to v1.3.0 * force usage of swift docc plugin 1.3.0 commit 79fa2c2 Author: Alessio Buratti <9006089+Buratti@users.noreply.github.com> Date: Fri Aug 23 18:50:22 2024 +0200 [Draft] Detached tasks (swift-server#334) * First prototype * Fix build * Removes task cancellation swift-server#334 (comment) * Force user to handle errors swift-server#334 (comment) * Remove EventLoop API swift-server#334 (comment) * Make DetachedTaskContainer internal swift-server#334 (comment) swift-server#334 (comment) * Removes @unchecked Sendable swift-server#334 (comment) * Invoke awaitAll() from async context * Fix ambiguous expression type for swift 5.7 * Fix visibility of detachedBackgroundTask * Add swift-doc * Add example usage to readme * Add tests --------- Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com>
Introduces a system that allows to keep the execution environment running after submitting the response for a synchronous invocation.
Note:
This is a draft PR, only meant to gather feedbacks. This code requires some polishing and testing.
The lines commented with
// To discuss:
require careful consideration.Motivation:
It is common for a Lambda function to depend on several other serverless services (SQS, APIGW WebSockets, SNS, EventBridge, X-Ray just to name a few). In many occasions, such services might perform non-critical work like sending a push notification to the user, store metrics or flush a set of spans. In these scenarios, the occurrence of a non-recoverable error usually doesn't mean that the whole invocation should fail and result in a sad 500. At the same time, awaiting for such tasks to finish gives no benefits and drastically increases the latency for the API consumers.
This PR implements a hook in the runtime which allows the developer to dispatch code that can continue its execution after the invocation of
/response
and before having the environment being frozen by/next
. This is a common practice described here.Modifications:
A new class called
DetachedTaskContainer
has been added, which follows the design ofLambdaTerminator
as close as possible. ALambdaContext
now owns an instance ofDetachedTaskContainer
, which can be used by the handler to dispatch asynchrouns non-critical work that can finish after the invocation of/response
and before/next
.Result:
It is now possible to return the result of a synchronous invocation (like API Gateway integration, CloudFront origin, Lambda function URL, Cognito Custom Auth, etc), as soon as it is ready, reducing the overall API latency.