--
You received this message because you are subscribed to the Google Groups "Ring" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ring-clojure...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
However, Readable/Writable ByteChannels are not really asynchronous. SelectableChannels allow for multiplexing, so it's at least not one thread per connection, but at the cost of significant implementation complexity. Every server implementation would need a thread set aside to run the selection loop, and for streaming requests some sort of execution model outside of that loop would be needed to handle the reads. In other words, every Ring-CPS implementation would asymptotically approach Netty. Given that efficient interaction with WritableByteChannels also requires selectors, this probably is also true of any attempt to extend ChannelWritable over core.async, etc.
So while I think the CPS approach is adequate, pretty much everything relating to the bodies of the requests and responses needs some more thought, and definitely one or more reference implementations before the spec is finalized.
--
But I would like to ask the following of James and Zach. Why keep the complexity of the ring call model? Ring handlers combine the modification of a context with the calling of a new context, but for what purpose?
Yes, I think I'd favor protocols that resemble the read and write halves of http://docs.oracle.com/javase/7/docs/api/java/nio/channels/AsynchronousByteChannel.html. Note that we can't just use AsynchronousByteChannel, since it's only a way of representing sockets, not arbitrary flows of bytes in a process. Note that you'd probably want synchronous AND asynchronous read/write methods, and some way for the stream object to indicate which is native for it. Otherwise, you'd end up pushing every "asynchronous" read from an InputStream onto another thread so it doesn't block, rather than just spinning up a thread to do a series of synchronous reads. So something like https://gist.github.com/ztellman/2e2a266076f34e3f5892.
The reason for a buffer on reads is to allow buffer reuse.
--
You received this message because you are subscribed to the Google Groups "Ring" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ring-clojure...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
"I'm somewhat leery about abstractions that seek to improve upon the runtime environment, because it tends to lead to a situation where you need to reimplement at least part of the language's toolchain. "This is true of CPS as well, things like exceptions and stack traces will be quite unusable in CPS as well, and proper handling of them will need to be re-implemented in a continuation style.
Have you considered the fact that this would allow for spec-compliant
HTTP clients in both Clojure and ClojureScript ?
I've in fact been using the exact design you're proposing in a number
of client-side middlewares and it's worked very well for us.
My contention was that all the existing client libraries are very
"complected"; they re-implement the same functionality in ad-hoc,
subtly incompatible ways, and most don't provide clean extension
points like middlewares do. Were they to adopt this spec, most of
their features could be packaged as compatible middlewares, and it
would leave them as simple adapters in Ring parlance.
Hi all,
Sorry for being so late to this thread, someone just linked me to it today.
The system Timothy Baldridge described above is effectively how the interceptor chain works. On Pedestal Master, the interceptor chain is a separate module (pedestal.interceptor) -- I definitely invite you to explore it, see how it integrates into your existing work, and weigh the trade-offs against a purely CPS approach. While I don't care for the CPS approach (I don't think the trade-offs shake out as a net positive when compared against alternatives), I do share Zach's view that CPS is the logical async equivalent of Ring's current model.
I've had to tackle the container-specific NIO/AIO challenges in Pedestal. All response types are programmed against two protocols (one for blocking IO bodies, another for non-blocking bodies). I'm more than happy to talk through the challenges, approaches we tried and reviewed, and discuss what we finally ended up upon.
At this point, Pedestal really is just a set of separate libraries, all programmed against protocols/interfaces. If you see something you like, please steal liberally :) If you have questions, or want to openly work through design decisions and trade-offs, I'm definitely open to participating.
Cheers,
Paul
I'm aware of Pedestal's interceptors. My understanding is that their functionality is equivalent CPS-style Ring middleware, in that there are functions for entering, leaving and raising an error.
CPS-style functions are backward compatible with Ring's current design, which is the main draw to me.
Interceptors are arguably more transparent, but if I understand right, not significantly so in isolation. An map containing three opaque functions doesn't reveal much more than a single opaque function.
The key advantage of interceptors that I can see is that they are held in a persistent queue, whereas middleware are typically composed. But there's no reason why we can't place middleware in a queue and compose them later.
Am I missing something, or is that an accurate assessment?
Interceptors are arguably more transparent, but if I understand right, not significantly so in isolation. An map containing three opaque functions doesn't reveal much more than a single opaque function.
The functions themselves are opaque, but what you get is a first-class shared/established container, on which you can attach other properties (ie: the "interceptor"). You can hold the three functions together as a logical "slice" in the handling/operating pipeline.
The key advantage of interceptors that I can see is that they are held in a persistent queue, whereas middleware are typically composed. But there's no reason why we can't place middleware in a queue and compose them later.
Regarding error handling and composition, I think you eventually have to make this jump (or something similar to it), or accept the standard CPS debugging story. Much of Pedestal's core capabilities come directly from the interceptor chain's queue -- it's how one can have dynamic and rule-based interceptors, how you can easily trace and inject in the handling of a request, and how Pedestal can treat async-debugging as if it were synchronous (with a chain, you can pattern match on "steps" and errors within those steps). It's also how we can run execution of steps backwards, which you'd lose in CPS -- this is going to put more strain on developers to correctly compose the independent, but related pieces of the CPS handling within Ring.
All-in-all an accurate assessment, though I think the fallout of the design decisions might give you some ideas or new angles to consider for Ring. One example, I can envision a model where Ring's exposed API is in a CPS style, but is then composed and executed on the interceptor chain under the hood.
I'm looking forward to what shakes out!
Presumably the functions are passed the interceptor they belong to, so they can read these additional keys?
Interesting. Why would you want to execute the steps backward?
All-in-all an accurate assessment, though I think the fallout of the design decisions might give you some ideas or new angles to consider for Ring. One example, I can envision a model where Ring's exposed API is in a CPS style, but is then composed and executed on the interceptor chain under the hood.The thought had struck me as well. There are a lot of things I like about the interceptor model, but I also get the impression I don't fully appreciate the advantages of its design.
Presumably the functions are passed the interceptor they belong to, so they can read these additional keys?
This is the motivating factor behind all functions taking only a single argument -- the context. The context holds the interceptor queue, which is how other interceptor functions can dynamically change (or instrument) the processing of a request. Interceptors are just records with :enter, :leave, :error, and :name -- a single interceptor represents that one logical slice. An interceptor could always look at itself in the queue.
Interesting. Why would you want to execute the steps backward?
Once you process the chain in one direction (:enter), you can then just turn around and process it in the other direction (:leave), and you get a similar effect to middleware, but completely decoupled. Related, you could also just run the chain in a single direction to process streaming data (SSE, WebSockets, etc.), while still leaving all the error-handling properties of the interceptor chain intact.
How often does an interceptor need to change the interceptor queue? It seems like it could make things more confusing if the sequence of interceptors can change on the fly.
What are the practical advantages of decoupling?I mean, the author of a middleware function always has the option of decoupling the "request" and "response" parts, so the only time I can think of that running the "enter" or "leave" part of an interceptor in isolation would be to do something the original author didn't intend.
Thanks for answering my questions, by the way.
It occurs more often than one might think, but it rarely is done in an ad-hoc, unpredictable fashion. Let's consider two (related) use-cases. Keep in mind, absolutely everything in Pedestal is an interceptor, interceptors are placed on the queue (the interceptor chain), and the chain is executed linearly.