GithubHelp home page GithubHelp logo

Comments (36)

kspeakman avatar kspeakman commented on June 25, 2024 1

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.

  1. The primary reason I want to use Giraffe is to have an idiomatic F# API for specifying routes.
  2. Task is not a built-in idiomatic F# pattern.
  3. Async performance is good enough for my usage -- especially considering my previous tech was Web API 2 (order of magnitude slower than async Giraffe).

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).

from giraffe.

kspeakman avatar kspeakman commented on June 25, 2024 1

(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.

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024 1

Additionally, not sure if Tasks can be tail call optimized as Asyncs are. See last section of this README.

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).

from giraffe.

kspeakman avatar kspeakman commented on June 25, 2024 1

I must admit I did find the HttpFunc -> HttpFunc definition more confusing than the previous API. However, it is similar in concept to ASP.NET's own Func<RequestDelegate, RequestDelegate> definition for middleware. I understand CEs pretty well. But task is a new one, whereas most F# users will already be using async and familiar with its characteristics.

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).

from giraffe.

kspeakman avatar kspeakman commented on June 25, 2024 1

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.

from giraffe.

kspeakman avatar kspeakman commented on June 25, 2024 1

@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.

from giraffe.

NinoFloris avatar NinoFloris commented on June 25, 2024 1

So why not create a package of the original you've included? https://github.com/rspeele/TaskBuilder.fs
rspeele/TaskBuilder.fs#4

from giraffe.

nojaf avatar nojaf commented on June 25, 2024 1

Since it's only one file using paket there is no real need to have a separate nuget package.

Add github dustinmoris/Giraffe src/Giraffe/Tasks.fs to paket.dependencies and be done with it right?

from giraffe.

JonCanning avatar JonCanning commented on June 25, 2024

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

@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?

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

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 AutoOpenAttribute to task file so that users do not have to open Giraffe.Tasks explicitly every time as it is so common?

@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.

from giraffe.

kspeakman avatar kspeakman commented on June 25, 2024

Async performance is not good enough for some people's usage

Just curious. What are the performance-oriented use cases you are targeting?

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

Just curious. What are the performance-oriented use cases you are targeting?

I'll get back to you on this one ...

Just highlighting that it seems like a goal change toward performance at expense of API.

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?

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

The signature is not as descriptive as before I agree, I prefer to look at it as (Continuation & Context) -> Result, the middleware signature is exactly how we're using it as you noticed.

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!

from giraffe.

NinoFloris avatar NinoFloris commented on June 25, 2024

@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?)

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

@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.

from giraffe.

ian-moore avatar ian-moore commented on June 25, 2024

I agree with @kspeakman - it was nice having a data/domain layer that could be idiomatic F# with async that operated seamlessly with Giraffe handlers. Now I'm in a spot where I have to rewrite my entire app to use Tasks, or constantly pass one back to the other, which feels like pollution.

from giraffe.

ian-moore avatar ian-moore commented on June 25, 2024

@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.

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

@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 task {} CE without any additional conversions, which would mean that apart that the the signature of a HttpHandler has changed to Task instead of Async you shouldn't notice any difference from before. Would this address your current issue sufficiently? You'll essentially be able to stay completely Fsharpy with Async throughout your entire codebase and on the top layer in Giraffe use it natively from within a HttpHandler which has task {}.

@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.

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

@kspeakman @ian-moore @dustinmoris I will try do PR this morning to implement Async binding overload as a priority so people can use Async methods in the task {} natively. As before, any async used would be a duplication of scheduling so task methods should be used as a preference where possible.

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

PR now added #84

HttpHandler will still return Task<'T> but inside any task { }, you can now bind async methods/operations into the pipeline.

I hope this mitigates some of the reservations @kspeakman & @ian-moore have in using the new TaskBuilder?

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

@gerardtoconnor Excellent, thank you. This was extremely fast!

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

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?

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

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 ¯\_(ツ)_/¯ ).

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

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 :)

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

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.

@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.

Now I'm in a spot where I have to rewrite my entire app to use Tasks, or constantly pass one back to the other, which feels like pollution.

@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 async? asp.net core and its services all use .net Task so can you perhaps share some code examples of async usage issues that have made Task problematic so we can assist and perhaps address for you & others going forward?

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?

from giraffe.

kspeakman avatar kspeakman commented on June 25, 2024

@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.

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

So why not create a package of the original you've included?

@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.

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.

@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 Task, for most people it makes sense to use Task and apps already using large async workflows can bundle together in async {} and do a bind in final root HttpHandler.

"I add more nodes since my services are stateless." ... "I can (and do) use async for my own internal operations (e.g. MailboxProcessor)"

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?

from giraffe.

kspeakman avatar kspeakman commented on June 25, 2024

@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.

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

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.

from giraffe.

draganjovanovic1 avatar draganjovanovic1 commented on June 25, 2024

Any thoughts to use this library inside Giraffe? https://github.com/kekyo/FSharp.Control.FusionTasks

It is already available as a nuget package.

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

@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!

from giraffe.

dustinmoris avatar dustinmoris commented on June 25, 2024

/cc @gerardtoconnor

from giraffe.

gerardtoconnor avatar gerardtoconnor commented on June 25, 2024

@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.

from giraffe.

draganjovanovic1 avatar draganjovanovic1 commented on June 25, 2024

@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.

from giraffe.

kspeakman avatar kspeakman commented on June 25, 2024

@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 ValueTask for combinators that just filter out requests based on HttpContext. You pay a pretty hefty cost for wrapping synchronous combinators in F#'s Async, whereas ValueTask is very low-cost for those cases. And most of the combinators I use could be synchronous right up to passing the execution off to my app logic. FWIW

from giraffe.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.