-
Notifications
You must be signed in to change notification settings - Fork 266
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
Move Giraffe.Tasks into separate NuGet package? #81
Comments
Is the Tasks CE specific to Giraffe or could it be its own package?
…On Mon, 14 Aug 2017, 17:22 Dustin Moris Gorski, ***@***.***> wrote:
Since there is a lot of lower level libraries which then would get used
higher up from a Giraffe web application they will probably need/want to
expose endpoints which return a Task as well instead of an Async if there
is some asynchronous work to be done.
In this instance it might make more sense to only have to reference a
smaller NuGet package which only exposes the task {} workflow rather than
having to reference the entire Giraffe web framework.
I think this will become a common use case, so should think about it?
What do people think?
/cc @gerardtoconnor <https://github.com/gerardtoconnor>
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#81>, or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAo-AM9YgGl8n_7sCn4RPELGW4CTyDp5ks5sYHRZgaJpZM4O2l7A>
.
|
TLDR: yes, separate package. I downloaded beta1 today (not on purpose, NuGet restore did it) and could not build until I rewrote my routes and helpers to use Tasks. This brought me to 3 conclusions.
I think the Task API will be useful for heavy C# interop (and therefore good as a separate package), but it is not my preferred default for Giraffe. Edit: Some usage info. I'm using only for API endpoint, with HTML5 front-end (served separately from API). |
@kspeakman Good feedback, thank you. I am not sure if I understood you 100% correctly, but given that the HttpHandler has changed to return a Task<..> now is this an issue for you? In theory not much has changed, because ASP.NET Core was always task only, the only difference is that we moved that requirement one level higher up to Giraffe now, but your internal code can still work with F# Async and then convert back to a Task at the Giraffe level. Is that an acceptable use case or does that cause any problems? |
The Task CE is just syntactic sugar for binding Task continuations, and TPL Tasks are a BCL for .net so any low-level libraries one would use would already have their own methods for handling Tasks. When building a Giraffe app, package dependencies will be mostly Nuget, so I presume you are talking about a custom personal project dependencies (lib) that are to be used from within a separate Giraffe app? In this case, it might be useful to allow the external use of task CE although is it painful to maintain another separate nuget package? @dustinmoris May be worth adding @JonCanning As mentioned above, it is mainly for binding tasks but there are specific functionality that will be built in specifically for Giraffe (Context-Insensitive, Async Overloading) but functionality that doesn't depend on Giraffe and can be used without it. @kspeakman Unfortunately, without HKT or Type Classes, we are not able to maintain both an async {} & task {} version of Giraffe as it is used explicitly throughout. Giraffe tries to be idomatic with both asp.net core as much as it is idomatic with f#; being a light functional wrapper (with helper extensions) around asp.net core; so it makes sense to use tasks throughout. I am hoping to update task CE with async overload shortly so that you can use async with task {}, hope this will make the transition easier. Async performance is not good enough for some people's usage and no-one ever complains about things being too fast so we need to focus on performance case but appreciate where you're coming from. |
(separate issue) The Giraffe API change with beta1 was breaking from the previous version (alpha 025), but was versioned in such a way that NuGet thought it was a minor (non-breaking) change. So just doing a restore broke my code, which was unexpected. With Web API 2, I was used to returning Tasks from my code anyway. But one of the reasons I was drawn to initially to Suave was because I could use idiomatic F# all the way to the edge (of my code... I know Suave had to convert to Task at some point). And then I came to Giraffe because I could do the same on top of aspnet stack. So change to Task is not a problem for me, per se. Just highlighting that it seems like a goal change toward performance at expense of API. Additionally, not sure if Tasks can be tail call optimized as Asyncs are. See last section of this README. |
Just curious. What are the performance-oriented use cases you are targeting? |
This is not an issue in our implementation as we are not doing recursive calls on task continueWith, the bind is executed as progressed. The TCO from using the continuations in the handler is referring to the stack frames built up from piped functions (stack frames from few async operations acceptable). |
I'll get back to you on this one ...
I know everyone wants to keep the API as simple as possible but after lengthy discussion, we thought that this was a minimal impact on API while greatly improving performance. is it the continuation format or task computation expression that you feel is complicating API? |
I must admit I did find the I think the performance improvement of going Task-based is impressive. (I saw the numbers on TheAngryByrd's benchmark project.) However, it is not a primary motivator for me. In the larger picture, it is our least-concerning bottleneck (with database being first), and the easiest one to scale (since our web services are stateless, just add more nodes behind a load balancer). |
The signature is not as descriptive as before I agree, I prefer to look at it as If databases are your issue then it may be worth looking into what kind of local caching you can do to reduce DB runs, as Giraffe is using asp.net core and you can inject different types of caching services into the context, hopefully, you can alleviate bottleneck but as you know, persistence concurrency is whole other can of worms we best not get into! |
We aren't having perf issues with the database currently. Just saying from a holistic perspective, the potential ops of most databases including ours is far lower than even async Giraffe. |
@gerardtoconnor @dustinmoris Thanks for switching to Task, this was a blocker for us to reach good enough performance and so we migrated to asp.net 2.0 core without a nice functional layer in between. https://twitter.com/NinoFloris/status/892478914345541632 Definitely going to revisit this soon (Giraffe needs a 2.0 migration/branch though, would you mind if I submit a PR for that?) |
@NinoFloris Great to hear! Yes a PR for the 2.0 migration would be perfect. I was going to have a crack at it myself over the weekend if nobody else would have provided something till then :). I know that @Banashek was having a go in #76 but I don't know what the latest status is today. |
I agree with @kspeakman - it was nice having a data/domain layer that could be idiomatic F# with |
@ian-moore It's actually not too bad to setup custom middleware to implement async-based again. See this gist. Should be able to copy most of the HttpHandlers from alpha-025. I put only limited ones in the gist (in Combinators.fs) and tweaked them slightly. |
@kspeakman thanks for that gist. I would hate to ditch Giraffe but if I can get what I want out of 2 simple modules I may give it a shot. |
@kspeakman @ian-moore Hey guys, thanks for your feedback. I understand your motivation. The plan is however that we will make it possible to use async workflows from within the @gerardtoconnor I think before we make any more perf improvements we should make this a priority to further reduce any friction for existing Giraffe users. |
@kspeakman @ian-moore @dustinmoris I will try do PR this morning to implement |
PR now added #84
I hope this mitigates some of the reservations @kspeakman & @ian-moore have in using the new TaskBuilder? |
@gerardtoconnor Excellent, thank you. This was extremely fast! |
Ok, so Giraffe 0.1.0-beta-002 has been released and hopefully resolves the issues you had. That still leaves me with the question if we think it would be beneficial to have the tasks exposed through a small NuGet package which can be used in custom projects which are built for Giraffe or leave it the way it is now? |
I am happy with either, can see the benefit of splitting out purely for the development of giraffe compatible libraries to be referenced by a giraffe app, and I also like the simplicity of just leaving embedded but laziness aside, should probably be split out into a separate NuGet package. If any of the NuGet pros in here would like to assist, would be greatly appreciated, just a simple case of moving Tasks.fs to Giraffe.Tasks package (he says not knowing how to do himself |
Cool... yes this should be fairly simple and I can do it. I'll wait for a few more responses and then do something about it :) |
@NinoFloris Giraffe has a focus on performance as well as being functionally idiomatic with asp.net core. We will be looking to have framework performance equal or better than MVC, but with a significantly easier functional API. Hopefully, we'll be .net code 2.0 shortly.
@ian-moore We naturally didn't want to cause a major disruption to existing (&future) apps and I am wondering how are you architecting your application that it is so reliant in The Combinators.fs Gist is very similar to original Giraffe Alpha implementation but as discussed, has performance issues from binding, wrapping sync in async, and scheduling duplication from constant async <> task conversions. Out of curiosity, what is the primary motivation for using asp.net core if not for performance & use of its platform services/infrastructure? |
So why not create a package of the original you've included? https://github.com/rspeele/TaskBuilder.fs |
@gerardtoconnor Single node perf is an important advantage to asp.net core, but there are others. Another motivation for me is being able to run on linux boxes for lower overhead (i.e. compute/mem cost) and large ecosystem of supporting services. If I need more ops than what a single-node provides, I add more nodes since my services are stateless. To really gain the single-node perf from using Tasks, I will have to use the task CE all the way down in my code. Otherwise I am still doing Async-to-Task switching. I can (and do) use async tail recursively for my own internal operations (e.g. MailboxProcessor), which I don't think I would be able to do with Tasks. xx% better single-node perf makes it marginally cheaper per node to operate. But the dev cost of switching everything over to Tasks to actually reap that benefit is a far larger cost for my project. If I were already using Tasks everywhere, I could see it being worth it. I'm not suggesting you change directions, just providing feedback. (Was in the middle of porting our apis to core when beta1 hit.) Based on @NinoFloris comments, this change obviously hits somebody's use case, just not mine. That's ok, no hard feelings here. As for putting Giraffe.Task in its own NuGet. I would find it really odd to reference Giraffe.Task in, for example, my database library which takes no dependencies on web stuff. I think it would be better if the Task CE implementation was wholly separate from Giraffe, and Giraffe happened to use it, and so could consumers. Or they could use a different one if they chose. |
@NinoFloris We need the flexibility to be able to modify going forward, we have already removed the context-sensitive code and added async overloading, and I will be tweaking performance in the near future to remove DU bindings so makes sense to have our own isolated version Giraffe controls.
@kspeakman I think you should be able to workaround this, only the root calling HttpHandler needs to return a task, you can very simply internally use async throughout such that there is just one switch in the root handler, you could even define your own async child handler to be used internally that can bind the old way, and only use continuation & task format in root HttpHandler. The changing to task format is not supposed to stop or break people using async, just be mindful of only binding async to tasks as infrequently as possible. As the vast majority of asp.net core api & services are
MailboxProcessors are for shared access/mutation of state (and they are bit slow at that)!?! There may be a better (context-insensitive) architecture alternative out there? |
@gerardtoconnor That was the idea: that whatever web routing library I was using would take care of converting to tasks and still allow me to use idiomatic structures in my code. I'll check out your PR. Good eye regarding MailboxProcessor vs statelessness. It was only an example that f# users might be using which would be incompatible with Task CE since it is recursive async by default. I use recursive async in a few places like processing bulk changes in a unit of work pattern. Should be able to refactor to fold, but not high on priority list. |
Since it's only one file using Add |
I think I'll close this for now. If more people come and join to push for a separation then I can re-open again. |
Any thoughts to use this library inside Giraffe? https://github.com/kekyo/FSharp.Control.FusionTasks It is already available as a nuget package. |
@draganjovanovic1 Hey I think we looked at this library, but I don't remember 100% anymore why we didn't end up using it but I'll have a closer look again and let you know what I think! |
/cc @gerardtoconnor |
@draganjovanovic1 Hi, thanks for looking out for this possible solution. The reason we cannot/should not use Fusion Tasks is that it overloads Async to accept Tasks, and return Async so the interop is great but all these Tasks are being rescheduled as Async with is where the performance hit comes. This is why we had to opt for a CE that stays as a Task, accepts Task & Async, but returns Task, overloading async requires rescheduling but given most operations with asp.net core ( HandlerDelegate, Reading & Writing) are all Task, means we have minimal waste in pipeline. Many F# users are not concerned about perf and think that easy interop is the same as unified constructs as it reasonably makes no difference to them ... threading constructs, both Task&Async are expensive to use (allocation, GC, scheduling) and should only be used explicitly when needed for performance critical applications. If I am wrong on my presumptions on FusionTasks please let me know as happy to look into it further with you, best performance without compromising simplicity/flexibility seems to be the goal and @dustinmoris very open to all input. |
@gerardtoconnor, I understand your concern regarding the performance. I would say your presumptions are correct but will definitely dig deeper into the FusionTasks as I found it recently. BTW Having TaskBuilder as a separate nuget would be really great. |
@gerardtoconnor @dustinmoris To follow up on this, I implemented my own idiomatic API on top of Kestrel -- like the old version of Giraffe -- for my own uses. I do not need that many features, so it was a tiny effort. However, I did eventually arrive at the conclusion that Task-based is probably the right approach, especially if you return |
Since there is a lot of lower level libraries which then would get used higher up from a Giraffe web application they will probably need/want to expose endpoints which return a Task as well instead of an Async if there is some asynchronous work to be done.
In this instance it might make more sense to only have to reference a smaller NuGet package which only exposes the
task {}
workflow rather than having to reference the entire Giraffe web framework.I think this will become a common use case, so should think about it?
What do people think?
/cc @gerardtoconnor
The text was updated successfully, but these errors were encountered: