Prototype of the discussed HTTP API Spec

Hi,

+1 on Chris’ comment.

Two wishes:

1)
One thing I would like to see is a separate item in the repository that contains just the actual API. Instead of the API intermingled with the concrete implementation. Similar to what Johannes’ did. Could be a document called API.md.
Update this when the API changes due to workgroup work.

Or more fancy: Put just the API (and no2 below) into the master branch. The same .swift files, just w/o any implementation code. (Yes, this won’t compile of course). Then have branches for concrete implementations, say ‘blue-socket’ for the current one.
This way the API can evolve in master and you can always see where the concrete implementation is at, and other people can contribute different implementations for comparison (e.g. the low hanging fruit would be a simple one that doesn’t require a socket library but just libdispatch).

2)
The other thing that would be nice is a demo tool in the Sources directory, again similar to what Johannes showed (his tiny echod sample).

hh

···

On 30. May 2017, at 12:08, Chris Bailey via swift-server-dev <swift-server-dev@swift.org> wrote:

Hi Paulo:

One of the things we're trying to avoid is a waterfall design approach, requiring a fully settled API before any implementation work starts - its much more preferable to have a reasonable starting point, make it work, and then make it better. One of the advantages of this approach is that it widens the funnel of participation out from "API designers" to include users, who can try out the API and provide feedback.

The proposal that's been implemented was the result of a number of weeks of debate on the mailing list, and as such gives us that starting point. That doesn't mean that your proposal is ignored - in fact it gives us an excellent list of areas that we can debate incrementally and see what the effect would be via working use cases and performance tests.

That then gets us working and collaboration on on a single code base - which is really the primary goal of the workgroup!

Chris

Hi all,

David here, working with Paulo on Zewo and more. There’s absolutely a lack of process, as Paulo mentioned. We have a very rough road ahead of us, with a ton of extra work, if we need to start writing implementation code at this level. Tests and dummy implementations should be well enough to allow us to gather feedback.

The way forward, as I see it, is agreeing on the API for HTTP Request/Response/Headers/Method/Status/Version based on a proposal structure and pull requests.

I should also mention that I strongly oppose bringing in dependencies from any actor in the Swift community to the work group org at this stage.

David

···

On 30 May 2017, 12:08 +0200, Chris Bailey via swift-server-dev <swift-server-dev@swift.org>, wrote:

Hi Paulo:

One of the things we're trying to avoid is a waterfall design approach, requiring a fully settled API before any implementation work starts - its much more preferable to have a reasonable starting point, make it work, and then make it better. One of the advantages of this approach is that it widens the funnel of participation out from "API designers" to include users, who can try out the API and provide feedback.

The proposal that's been implemented was the result of a number of weeks of debate on the mailing list, and as such gives us that starting point. That doesn't mean that your proposal is ignored - in fact it gives us an excellent list of areas that we can debate incrementally and see what the effect would be via working use cases and performance tests.

That then gets us working and collaboration on on a single code base - which is really the primary goal of the workgroup!

Chris

From: Paulo Faria via swift-server-dev <swift-server-dev@swift.org>
To: Michael Chiu <hatsuneyuji@icloud.com>
Cc: swift-server-dev <swift-server-dev@swift.org>
Date: 27/05/2017 12:06
Subject: Re: [swift-server-dev] Prototype of the discussed HTTP API Spec
Sent by: swift-server-dev-bounces@swift.org

Sorry if I'm being annoying, but I really feel what we lack is process. There's no formal proposal and proposal review. I really think we should move incrementally with well defined scopes and deadlines for every round. We didn't have that so far. Carl and others said that my suggestion is counter productive. I think the opposite, of course, as what I'm proposing is a well defined process where when we settle on a design for a particular set of base APIs then we move on to a higher absctraction. This way we won't be discussing the same things over and over again. I'll repeat, if we can't agree on the base types how can we move on? I'm saying let's first *settle* on Version, Headers, Message, Request and Response. Really *define* the API so then we move on, incrementally, from lower abstraction to higher abstraction.

On May 27, 2017 07:53, "Paulo Faria" <paulo@zewo.io> wrote:
I'm just proposing we move the code incrementally. The "most consentual" list I sent yesterday isn't radically different from the code Johannes proposed. I really don't want to discuss this over and over, ad infinitum. The most important in the messages I sent before is that we need a well defined process. We don't have it. Just taking the first implementation, with a lot of things that admitedly don't fit the scope, and adding that so we can rework doesn't feel right to me. I'm actually confused about the scope this project is taking. The code there explicitly mentions a WebApp, which is a higher responsibility than HTTP. When we started this project the scope as very clear. Crypto/TLS, Socket, HTTP. A well designed HTTP module shouldn't depend *at all* on the socket implementation. Providing an implementation would be just a matter of injecting a dependency. Moving that code as is to the org really doesn't feel right to me. All I'm saying is that we definitely should start having code on the org. But I say we move first Version, Headers, Message, Request, Response. And again, the "least controversial" I sent yesterday isn't radically strange. It's an evolution of Johaness original proposal, plus Carl's, plus Helge's suggestions, plus my suggestions. The only thing I added that wasn't discussed before is HTTPHeader.Field which does a case insensitive comparison in its equatable implementation, and the Message protocol which holds the properties common to request and response (version and headers). If we can't agree on that, which is the sum of everything that was discussed about these particular types. How can we agree on that full implementation?

On May 27, 2017 05:13, "Michael Chiu" <hatsuneyuji@icloud.com> wrote:
Hi Carl

> This email thread isn’t about an API proposal. It’s about a prototype implementation of an API that was already proposed and discussed a month and a half ago. The prototype isn't a full-featured framework like Vapor or Kitura, but it does actually work and it even has XCTests with decent (>75%) code coverage.

I see, I was confused by the email contents instead of reading the subject and thought we are finally implementing some code. TBH, I don’t see any reason why this should not move to swift-server on github, It sounds a good start to me.
Thank you guys’ hard work for building it.

> Also, please note that I didn’t play any part in proposing this API back in March/April - it’s not “Carl’s proposal.” I just took the existing API that the group had previously discussed and implemented enough of it so that we could measure the utility and performance of the API as proposed and so that we could have better informed discussions about potential alternatives.

You’re right. it was Johannes’ proposal, I’m so sorry for that.

Michael.

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number 741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU
_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Hi,

Michael, I narrowed the scope of the proposal and focused the rationale. I answered your comments about switch for HTTPMethod and HTTPStatus. Basically, we can switch on them because I implemented the pattern matching operator ~=. I also removed the HTTPMessage as it is not *needed* and users can define it and make HTTPRequest and HTTPResponse conform to it in extensions in their own module.

https://github.com/paulofaria/http-api-proposal#rationale

Carl, Helge, Chris and Rien what do you think about the code? This is pretty close to what Carl suggested.

The main differences are..

HTTPVersion as a struct.
HTTPHeaders backed by a single array storage and case insensitive checking is done with the Field type to improve performance.

not opposed to having a 'Field', but how does that improve performance? It reduces memory usage but having a single list makes the lookup of a certain header field with .filter O(n) instead of O(1).

HTTPMethod as a struct instead of enum with pattern matching added allowing switch cases. (pointed by Helge)
HTTPStatus as a struct instead of enum with pattern matching added allowing switch cases. (pointed by Helge)

happy with those. Except the proposed hashing functions

return method.hashValue ^ uri.hashValue ^ version.hashValue ^ headers.hashValue

is really not appropriate but I guess it's more a proof of concept.

-- Johannes

···

On 27 May 2017, at 2:18 pm, Paulo Faria via swift-server-dev <swift-server-dev@swift.org> wrote:

Apart from that there's only naming differences. I added some tests to showcase what I mentioned.

https://github.com/paulofaria/http-api-proposal/blob/master/Tests/HTTPTests/HTTPTests.swift

On 27 May 2017 at 08:41, Michael Chiu <hatsuneyuji@icloud.com> wrote:
The thing is, as I have mentioned before if we move on that route we have to have a well defined structure before hand, for example, there’s no such protocol “Message” in what Johannes proposed.

As Chris and Carl mentioned before, this is only a prototype of the discussed API, so at least we have something to test, experiment and branchmark on.

Also things like WebApp in Johannes’ proposal can easily remove without huge changes, since WebApp is simply a typealias. I really don’t think that’s a big issue, every other parts of the prototype is in fact pretty http-specific.

Yes the prototype might dependent on some other frameworks like blue socket, but that’s how we can at least test and branchmark.

Sincerely
Michael

On May 27, 2017, at 4:06 AM, Paulo Faria <paulo@zewo.io> wrote:

Sorry if I'm being annoying, but I really feel what we lack is process. There's no formal proposal and proposal review. I really think we should move incrementally with well defined scopes and deadlines for every round. We didn't have that so far. Carl and others said that my suggestion is counter productive. I think the opposite, of course, as what I'm proposing is a well defined process where when we settle on a design for a particular set of base APIs then we move on to a higher absctraction. This way we won't be discussing the same things over and over again. I'll repeat, if we can't agree on the base types how can we move on? I'm saying let's first *settle* on Version, Headers, Message, Request and Response. Really *define* the API so then we move on, incrementally, from lower abstraction to higher abstraction.

On May 27, 2017 07:53, "Paulo Faria" <paulo@zewo.io> wrote:
I'm just proposing we move the code incrementally. The "most consentual" list I sent yesterday isn't radically different from the code Johannes proposed. I really don't want to discuss this over and over, ad infinitum. The most important in the messages I sent before is that we need a well defined process. We don't have it. Just taking the first implementation, with a lot of things that admitedly don't fit the scope, and adding that so we can rework doesn't feel right to me. I'm actually confused about the scope this project is taking. The code there explicitly mentions a WebApp, which is a higher responsibility than HTTP. When we started this project the scope as very clear. Crypto/TLS, Socket, HTTP. A well designed HTTP module shouldn't depend *at all* on the socket implementation. Providing an implementation would be just a matter of injecting a dependency. Moving that code as is to the org really doesn't feel right to me. All I'm saying is that we definitely should start having code on the org. But I say we move first Version, Headers, Message, Request, Response. And again, the "least controversial" I sent yesterday isn't radically strange. It's an evolution of Johaness original proposal, plus Carl's, plus Helge's suggestions, plus my suggestions. The only thing I added that wasn't discussed before is HTTPHeader.Field which does a case insensitive comparison in its equatable implementation, and the Message protocol which holds the properties common to request and response (version and headers). If we can't agree on that, which is the sum of everything that was discussed about these particular types. How can we agree on that full implementation?

On May 27, 2017 05:13, "Michael Chiu" <hatsuneyuji@icloud.com> wrote:
Hi Carl

> This email thread isn’t about an API proposal. It’s about a prototype implementation of an API that was already proposed and discussed a month and a half ago. The prototype isn't a full-featured framework like Vapor or Kitura, but it does actually work and it even has XCTests with decent (>75%) code coverage.

I see, I was confused by the email contents instead of reading the subject and thought we are finally implementing some code. TBH, I don’t see any reason why this should not move to swift-server on github, It sounds a good start to me.
Thank you guys’ hard work for building it.

> Also, please note that I didn’t play any part in proposing this API back in March/April - it’s not “Carl’s proposal.” I just took the existing API that the group had previously discussed and implemented enough of it so that we could measure the utility and performance of the API as proposed and so that we could have better informed discussions about potential alternatives.

You’re right. it was Johannes’ proposal, I’m so sorry for that.

Michael.

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Hi,

Running everything on one thread with an async-only API is pretty much exactly what Node.js has as its only implementation and you might find that the APIs look remarkably similar [1].

Sorry if this is a little off-topic, but I just wanted to mention that I think neither is really true.

I’m not completely sure, but I think the Node.js HTTP implementation actually runs on multiple threads, it is just the user level JavaScript stack that is single threaded (I think it sparks CPU-count threads). May be wrong on that ;-)

Also the API of Node.js is nothing like the proposal, unless you are saying that ‘callbacks’ are the main criteria for Equatable here. Node.js is not built on simple callbacks like the proposal, but adds the concept of streams (two variants, pull and push). Node push streams are a little like GCD channels. But streams add quite a few more features, including piping, transformations, buffers and automatic back pressure handling.

well, the basic API is similar I think

response writing:
  • response.writeHead(statusCode[, statusMessage][, headers])
  • response.writeContinue()
  • response.write(chunk[, encoding][, callback])
  • response.addTrailers(headers)
  • response.end([data][, encoding][, callback])

getting a request (get the request and a response (writer)):
  Event: 'request'#
    • request <http.IncomingMessage>
    • response <http.ServerResponse>

and the IncomingMessage (HTTPRequestHead for us) has headers, http version and stuff but not the body.

Instead of returning a closure, you register a handler in the request à la

--- SNIP ---
request.on('data',function(message){
    response.write(message);
});

request.on('end',function(){
    response.end();
});
--- SNAP ---

which is reasonably close to

--- SNAP ---
    return .processBody { (chunk, stop) in
        switch chunk {
            case .chunk(let data, let finishedProcessing): /* like request.on('data', ...) */
                res.writeBody(data: data) { _ in
                    finishedProcessing()
                }
            case .end: /* like request.on('end') */
                res.done()
            default:
                stop = true /* don't call us anymore */
                res.abort()
        }
    }
--- SNAP ---

don't you think? To me it looks quite straightforward to port a Node HTTP app to the proposal we're discussing.

But I do agree that we don't have streams. I thought about something like that but I thought that's too much just for HTTP. If we want to have a more general streaming mechanism, we should probably discuss that in the Transport/Networking meetings, no?

That is why I said since the beginning that I’m more interested in how the API for the body stream will look like (as this affects sockets as well as HTTP). Now we essentially get separate APIs for the same thing ...

I do agree with you here. But the networking group basically hasn't happened yet so I was proposing something here as we happened to have an implementation that works.

While I’d love to have a nice streaming framework like the one Node has built right into Swift, maybe just some extension of GCD channels would do (if you don’t want to go the full stream-everything approach Noze.io does), but I understand that this is a little out of scope and probably impossible to agree on ;-) Doesn’t matter too much as you can put adapters on top of the stuff.

:+1:

-- J

···

On 31 May 2017, at 2:50 pm, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:
On 31 May 2017, at 15:20, Johannes Weiss via swift-server-dev <swift-server-dev@swift.org> wrote:

Hence: off-topic, srz, couldn’t resist.

hh

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Sorry if this is a little off-topic, but I just wanted to mention that I think neither is really true.

I’m not completely sure, but I think the Node.js HTTP implementation actually runs on multiple threads, it is just the user level JavaScript stack that is single threaded (I think it sparks CPU-count threads). May be wrong on that ;-)

https://www.quora.com/Is-Node-js-single-threaded

Well, yes. This doesn’t discuss what I was talking about. What I think (but I may be wrong) is that Node.js (the kernel, not the JS API) may run multiple HTTP threads where sockets are accepted, headers parsed etc, before those are handed over to the JavaScript thread. Or in other words, where those parts run multi-threaded (accepting, HTTP header parsing).

This makes some sense because doing all those things also takes time.

I said, I may be completely wrong here, it would make sense though :-)

well, the basic API is similar I think

… yeah sorry, but no, the basic API is nothing like it … You completely ignore that IncomingMessage and ServerResponse themselves are full streams and generic event emitters with everything attached.

Even IncomingMessage is nothing like our HTTPRequestHead - our head is just an inactive struct whereas the IncomingMessage is an actual object. Representing the read stream, and the headers, and even state whether the headers has been sent or not.
I don’t think that this is necessarily a good design, but it is really different to what was proposed.

and the IncomingMessage (HTTPRequestHead for us) has headers, http version and stuff but not the body.

IncomingMessage *is* the body stream. It is like

  class IncomingMessage : AsyncInputStream ( : ErrorEmitter : EventEmitter)

Instead of returning a closure, you register a handler in the request à la

--- SNIP ---
request.on('data',function(message){
   response.write(message);
});

request.on('end',function(){
   response.end();
});
--- SNAP —

This is the old style ‘push’ stream API. And even that has more features than callbacks. In ‘modern’ (years old) Node you would do:

  request.on(‘readable’) {
    let block = request.read();
    … do stuff with it ...
  }

but more usually you would pipe it somewhere,

  let file = fs.createWriteStream(“/tmp/filedump”)
  request.pipe(file)

or request.pipe(transform(func() { … })).pipe(file)

This is a great (and pretty quick) intro to streams:

  https://github.com/substack/stream-handbook

which is reasonably close to .. return .processBody ..

Not at all, it is a huge superset of that.

don't you think? To me it looks quite straightforward to port a Node HTTP app to the proposal we're discussing.

Since it provides no streams nor piping I have a hard time seeing you port anything but the most trivial demo.

But yes, it can be used as a *basis* to layer a Node-like framework on top. I.e. I could write a Noze.io stream which reads from the Swift HTTP API.

This is what we should focus on: Make sure that the stuff can be used as a basis for more advanced stuff.

But I do agree that we don't have streams. I thought about something like that but I thought that's too much just for HTTP. If we want to have a more general streaming mechanism, we should probably discuss that in the Transport/Networking meetings, no?

Well, HTTP sits on top of two byte streams. Which can be plain TCP sockets, or TLS, or a local domain socket, even UDP, why not read from files for testing purposes? In short, HTTP should technically sit on top of arbitrary streams.
It’s not utterly important (nor necessarily better for performance), but it would make a nicer architecture.

That is why I said since the beginning that I’m more interested in how the API for the body stream will look like (as this affects sockets as well as HTTP). Now we essentially get separate APIs for the same thing ...

I do agree with you here. But the networking group basically hasn't happened yet so I was proposing something here as we happened to have an implementation that works.

Sure. In a way that really is something for Foundation anyways, IMO.

While I’d love to have a nice streaming framework like the one Node has built right into Swift, maybe just some extension of GCD channels would do (if you don’t want to go the full stream-everything approach Noze.io does), but I understand that this is a little out of scope and probably impossible to agree on ;-) Doesn’t matter too much as you can put adapters on top of the stuff.

:+1:

hh

···

On 31 May 2017, at 16:07, Johannes Weiss <johannesweiss@apple.com> wrote:

Hi Michael,

I think i need to clarify something: I’m ok with a asynchronous api that executes synchronously, for example if the api is something like [[ a. { b() } ; c() ]], executes as [[ a(); b(); c() ]], it is totally fine since it’s just synchronous api with syntactic sugar.

We actually have a synchronous implementation of the proposed API next to the DispatchIO one that we normally use. The synchronous one uses problem system calls and only services one request per thread. It's handy for unit testing and for specialised use-cases. The synchronous implementation only uses the following syscalls: open, close, read and write, that's it so nothing fancy.

Not really, what you do when you use kqueue/(e)poll/select is that only said calls are blocking and you set your file descriptors to non-blocking.

Despite kqueue is a blocking, it really only blocks when there’s nothing to do. So semantic-wise, the thread will never block as long as there’s work to do.

That is generally true. Also read only blocks if there's nothing to read. However you unfortunately don't know when the other end of the network connection will write something. It might be immediate, it might be minutes, hours or days

I’m not quite sure how this is relevant.

That is exactly inversion of control. You'll notice that you can't just read when you feel like, you can only read (and write) when kevent tell you to. If you want, do extend your example to make it an echo server, ie. write the bytes that you read. You'll notice that you can't just do what a straightforward synchronous API would look like:

   var bytes = ...
   let readLengh = read(..., &bytes, maxLen)
   write(..., bytes, readLength)

you will need to save the bytes and write them when kevent _tells you_ to write them. That is inversion of control.

You don’t have to, especially for write, you can write read/to a socket anytime you want despite kevent or not, If you don’t want things to block, use the MSG_DONTWAIT flag.
A working example is here: https://gist.github.com/michael-yuji/f06ac328c3cb052fbe7aaa325486fcf1#file-main-c-L57 . It does contains some kevent logic but no, you don’t have to write when kevent tells you to.
I’d love to discuss more on kqueue but it’s a bit off topic here.

Well, in your example code, you don't set the file descriptor to non-blocking, so write will just block. Sure, you're free to do that but then we're back at square one, a synchronous and (kernel thread) blocking API. In other words, if you just delete all the kevent stuff, your program will be simpler and have the same effect. If you block write() you might just as well block read() too, nothing lost here and much more straightforward.

If you were to put a `fcntl(new, F_SETFD, O_NONBLOCK);` after the `accept` and you were to check what write() returns, you'd see that it would start returning -1 with errno EAGAIN/EWOULDBLOCK which means that it's not ready to be written. To know when it's ready to be written you will have to ask kevent when you'll be able to write next. That's what I meant when I said kevent will tell you when to write, you can't just write whenever you want. (Technically you can but it'll either block or not write your bytes).

If you don't believe me, do apply this little patch to your program

--- SNIP ---
--- main_orig.c 2017-06-01 11:00:47.000000000 +0100
+++ main.c 2017-06-01 11:18:07.000000000 +0100
@@ -7,6 +7,7 @@
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/event.h>
+#include <time.h>

#define PORT 8080
#define min(a, b) ((a > b) ? b : a)
@@ -56,7 +57,12 @@

             /* actual code starts here */
             int readLength = read(ev.ident, buf, min(1024, ev.data));
+ time_t t_start = time(NULL);
             write(ev.ident, buf, min(1024, ev.data));
+ time_t t_end = time(NULL);
+ if (t_end - t_start > 2) {
+ fprintf(stderr, "Blocked for %lds :(.\n", t_end - t_start);
+ }
             bzero(buf, 1024);
         }
         
@@ -88,4 +94,4 @@
     EV_SET(changelist, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
     kevent(kq, changelist, 1, NULL, 0, NULL);
     return fd;
-}
\ No newline at end of file
+}
--- SNAP ---

which will now print if write blocked the calling thread for more than 2s.

And then connect it with a program that connects to this socket which writes fast and reads very slowly. Then you'll see your program printing.

One way of emulating this on macOS is this

  yes "$(python -c 'print "x"*100000')" | telnet localhost 8080 | while read line; do sleep 2.5; echo received one line...; done

what this does is to continuously print lines with 100,000 'x' characters and send them to your server. But it'll only read one line of them every 2.5 seconds. That means we have a fast writer and a slow reader. If you now run your program with the patch from above, it will after half a minute or so print lines like:

--- SNIP ---
Blocked for 15s :(.
Blocked for 5s :(.
Blocked for 16s :(.
Blocked for 5s :(.
Blocked for 8s :(.
Blocked for 7s :(.
Blocked for 6s :(.
Blocked for 15s :(.
Blocked for 5s :(.
Blocked for 16s :(.
Blocked for 5s :(.
Blocked for 13s :(.
Blocked for 7s :(.
Blocked for 13s :(.
Blocked for 8s :(.
--- SNAP ---

ie. you use write as a blocking system call because the file descriptor isn't set to be non-blocking.

Just as a side note: You won't be able to repro this issue by replacing the macOS `telnet` with the macOS `nc` (netcat) as netcat will only read more to the socket after it was able to write it. Ie. the implementation of standard macOS `nc` happens to make your implementation appear non-blocking. But the macOS provided telnet seems to do the right thing. You can use pbjnc (http://www.chiark.greenend.org.uk/~peterb/linux/pjbnc/\) if you prefer which also doesn't have the same bug as `nc`.

I'd guess that most programmers prefer an asynchronous API with callback (akin to Node.js/DispatchIO) to using the eventing mechanism directly and I was therefore assuming you wanted to build that from kevent() (which is what they're often used for). Nevertheless, kevent() won't make your programming model any nicer than asynchronous APIs and as I mentioned before you can build one from the other in a quite straightforward way. What we don't get from that is ordinary synchronous APIs that don't block kernel threads and that happens to be what most people would prefer eventually. Hence libdill/mill/venice and Zewo :).

Johannes, I totally agree with you. A asynchronous API is more intuitive and I agree with that. But since we are providing low level API for ppl like Zewo, Prefect, and Kitura, it is not right for us to assume their model of programming.

For libdill/mill/venice, even with green threads they will block when there’s nothing to do,

If you read in libdill/mill/venice, it will switch the user-level thread to _not_ block a kernel thread. That's the difference and that's what we can't achieve with Swift today (without using UB).

in fact all the example you listed above all uses events api internally. Hence I don’t think if an api will block a kernel thread is a good argument here.

kernel threads are a finite resource and most modern networking APIs try hard to only spawn a finite number of kernel threads way smaller than the number of connections handled concurrently. If you use Dispatch as your concurrency mechanism, your thread pool will have a maximum size of 64 threads by default on Darwin. (Sure you can spawn more using (NS)Thread from Foundation or pthreads or so)

And even if such totally non-blocking programming model it will be expensive since the kernel is constantly scheduling a do-nothing-thread. (( if the io thread of a server-side application need to do something constantly despite there’s no user and no connection it sounds like a ghost story to me )).

what is the do-nothing-thread? The IO thread will only be scheduled if there's something to do and then normally the processing starts on that very thread. In systems like Netty they try very hard to reduce hopping between different threads and to only spawn a few. Node.js is the extreme which handles everything on one thread. It will be able to do thousands of connections with only one thread.

Btw, I just made a working echo server with no event/coroutine api and it’s truly synchronous and non-blocking in C++ just for fun (no it’s not good code).
https://gist.github.com/michael-yuji/94eda2f8cf3910aa3ff670112728f641

This "working echo server" is indeed synchronous and non-blocking but it just drops all the bytes on the floor that it can't send, don't think that counts as working...

If you don't believe me, try running this against the echo server (do remove the print to stdout in the echo server):

yes "$(python -c 'print "x"*100000')" | cat -n | telnet localhost 8080 | while read line; do echo $line; sleep 2.5; done | sed 's/x//g'

that'll print the same very long lines as above but prepend line numbers, send it through the echo server and read them off very slowly. Then it just drops all "x" characters. We should now expect 1, 2, 3, 4, 5 and so on if we don't lose any lines.

The real output for me though:

--- SNIP ---
$ yes "$(python -c 'print "x"*100000')" | cat -n | telnet localhost 8080 | while read line; do echo $line; sleep 2.5; done | sed 's/x//g'
telnet: connect to address ::1: Connection refused
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
1
2
3
4
5
6
7
8
9

2224
2225
2226

2976
2977
2978

4108
4109
4110

6332
6333
--- SNAP ---

ie. we lost thousands of lines in the middle. If you want, also run this dtrace script whilst you're running your echo server:

sudo dtrace -n 'syscall::write:return / pid==$target && arg1 == -1 / { printf("%s returned %d with errno %d", probefunc, arg1, errno); }' -p $(pgrep echo)

it'll print every time that write() returned an error for a program called "echo". That only works if there's only one program called "echo". What we see is a massive stream of

--- SNIP ---
  4 156 write:return write returned -1 with errno 9
  4 156 write:return write returned -1 with errno 9
  4 156 write:return write returned -1 with errno 9
  4 156 write:return write returned -1 with errno 9
  4 156 write:return write returned -1 with errno 35
  4 156 write:return write returned -1 with errno 9
  4 156 write:return write returned -1 with errno 9
  4 156 write:return write returned -1 with errno 9
  4 156 write:return write returned -1 with errno 9
  4 156 write:return write returned -1 with errno 9
--- SNAP ---

errno 35 is EAGAIN/EWOULDBLOCK, ie. you're dropping bytes. The errno 9 is EBADF which is an illegal file descriptor. I'm sure you can fix the bug with the EBADFs but to fix the EAGAINs you'll need to invert control using kevent().

You should definitely check the return value of write(), it's very important. Even if positive you need to handle the case that it's less bytes than you wanted it to write. And if negative, the bytes are _lost_ which happens all the time with the current implementation.

Anyway, to fix the EAGAIN you'll need to ask kevent() when you can write next.

---
The main difference between (a)sync api is that sync api do only one thing, read/write to an io, async api is doing two things, read/write and scheduling.

It is true that we can expose protocol to programmers and let them integrate their own scheduling model, but that will be dead painful to do and introduce unnecessary overheads vs just give them a synchronous API and they can just call it from their code.

It’s like ordering food over phone from a restaurant, you can either pickup (synchronous) your order or have it deliver to you (asynchronous). Yes most people will prefer delivery, But a pickup option as always more flexible despite you have to wait in the restaurant if the food is not ready. Now if we only expose the asynchronous model, what happen is the main code from the user tell the restaurant where to deliver and what to do when different things happen, which can be a very complex piece of logic.

On the other hand, with a synchronous (pick up) option available, we can do something like ask Bob to pickup the food in situation A, give Alice the food if B happens. Now with some event notification system, says the restaurant will give you a phone call when the food is ready, it is much easier to integrate your every day schedule, without having a delivery guy constantly drive around a shared highway and hence creating traffic (thread scheduling). [[[ you can probably tell i just have a really bad experience with a food delivery guy at this moment ]]]

Generally speaking, we as the server side work group should always give the end user that has a bit more room and freedom, from the API. If they call a blocking call, they probably know what they’re doing.

now you lost me

I'm aware of that but you'll suffer from the inversion of control. I'm pretty sure you'll end up with an event loop that calls kevent/epoll all the time and once there's something to do, it'll call the handler registered for that file descriptor (which is what DispatchSources are).

Yes and at least I can do it.

you can do the same in the asynchronous API with back-pressure. I'll quote again from the echo server

--- SNIP ---
    return .processBody { (chunk, stop) in
        switch chunk {
            case .chunk(let data, let finishedProcessing):
                res.writeBody(data: data) { _ in
                    finishedProcessing()
                }
            case .end:
                res.done()
            default:
                stop = true /* don't call us anymore */
                res.abort()
        }
    }
--- SNAP ---

the call the finishedProcessing() meant that you're ready for more data to be read, ie. the callback to processBody will only be called again after finishedProcessing() was called inside it. That makes sure that the bytes have been written (res.writeBody(...) { ... }) successfully before we accept more.

As I mentioned the parsing is another story, and your solution also apply to the synchronous version (by no mean I think it’s good. just prove of concept) I provided. I’ll quote it again.

parser.processBody = { (chunk, stop) in
         switch chunk {
=====ommit
         }
     }

extension Parser {
  func feed(data: AnyCollection<UnsafeBufferPointer>) -> Done
}

We're only talking about the API and not the implementation. You can absolutely implement the API I proposed with only one thread with one large shared buffer for all connections if you want. Running everything on one thread with an async-only API is pretty much exactly what Node.js has as its only implementation and you might find that the APIs look remarkably similar [1].

There's nothing stopping you from implementing the proposed API in one thread that services many requests with as much sharing of buffers as you like. Should there be something concrete missing that is making your life hard, please flag it as soon as possible and we can add it. The only implementations I have done of this API are one multithreaded one on top of DispatchIO and one fully synchronous one with one thread per request. Said that, the DispatchIO implementation should be configurable to only use one thread (using a serial dispatch queue instead of a concurrent one for all the event callbacks).

[1]: HTTP | Node.js v21.2.0 Documentation

That only says asynchronous is good and useful and it indeed is, but not a reason to abandon synchronous api.

Foundation/Cocoa is I guess the Swift standard library and they abandon synchronous&blocking APIs completely. I don't think we should create something different (without it being better) than what people are used to.

Again, there are two options for IO at the moment:
1) synchronous & blocking kernel threads
2) asynchronous/inversion of control & not blocking kernel threads

Even though I would love a synchronous programming model, I'd chose option (2) because the drawbacks of (1) are just too big. The designers of Foundation/Cocoa/Netty/Node.js/many more have made the same decision. Not saying all other options aren't useful but I'd like the API to be implementable with high-performance and not requiring the implementors to block a kernel thread per connection.

Cheers,
  Johannes

···

On 1 Jun 2017, at 1:00 am, Michael Chiu <hatsuneyuji@icloud.com> wrote:

2)
The other thing that would be nice is a demo tool in the Sources directory, again similar to what Johannes showed (his tiny echod sample).

I have several of those (Echo, HelloWorld, etc) that I use for end-to-end testing, but since they're not part of the API, they're under `Tests/` not `Sources/`. See https://github.com/carlbrown/HTTPSketch/tree/master/Tests/HTTPSketchTests/Helpers

I'm happy to move them to `Sources/`if that would be easier.

hh

-Carl

···

On May 30, 2017, at 5:53 AM, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

Hi, Johannes!

You're right. It helps performance by not having to go through all the
headers and lowercase then when they might not be needed during request
handling. This worked well in my own code because I used a dictionary as a
backing storage, having O(1) retrieval. Ideally the best backing storage
would be:

OrderedDictionary<Field, [String]>

- This way we have a single source of truth (another problem with the two
storages approach)
- We don't have to do lowercase on all headers even if they're not needed
later.
- The case insensitive comparison from Field is done when the user actually
needs the header.
- We maintain the original order of the http message in cases of proxying.
- Retrieval is done in O(1)

Cheers,
Paulo

···

On 30 May 2017 at 11:29, Johannes Weiss <johannesweiss@apple.com> wrote:

Hi,

> On 27 May 2017, at 2:18 pm, Paulo Faria via swift-server-dev < > swift-server-dev@swift.org> wrote:
>
> Michael, I narrowed the scope of the proposal and focused the rationale.
I answered your comments about switch for HTTPMethod and HTTPStatus.
Basically, we can switch on them because I implemented the pattern matching
operator ~=. I also removed the HTTPMessage as it is not *needed* and users
can define it and make HTTPRequest and HTTPResponse conform to it in
extensions in their own module.
>
> https://github.com/paulofaria/http-api-proposal#rationale
>
> Carl, Helge, Chris and Rien what do you think about the code? This is
pretty close to what Carl suggested.
>
> The main differences are..
>
> HTTPVersion as a struct.
> HTTPHeaders backed by a single array storage and case insensitive
checking is done with the Field type to improve performance.

not opposed to having a 'Field', but how does that improve performance? It
reduces memory usage but having a single list makes the lookup of a certain
header field with .filter O(n) instead of O(1).

> HTTPMethod as a struct instead of enum with pattern matching added
allowing switch cases. (pointed by Helge)
> HTTPStatus as a struct instead of enum with pattern matching added
allowing switch cases. (pointed by Helge)

happy with those. Except the proposed hashing functions

return method.hashValue ^ uri.hashValue ^ version.hashValue ^
headers.hashValue

is really not appropriate but I guess it's more a proof of concept.

-- Johannes

> Apart from that there's only naming differences. I added some tests to
showcase what I mentioned.
>
> https://github.com/paulofaria/http-api-proposal/blob/master/
Tests/HTTPTests/HTTPTests.swift
>
> On 27 May 2017 at 08:41, Michael Chiu <hatsuneyuji@icloud.com> wrote:
> The thing is, as I have mentioned before if we move on that route we
have to have a well defined structure before hand, for example, there’s no
such protocol “Message” in what Johannes proposed.
>
> As Chris and Carl mentioned before, this is only a prototype of the
discussed API, so at least we have something to test, experiment and
branchmark on.
>
> Also things like WebApp in Johannes’ proposal can easily remove without
huge changes, since WebApp is simply a typealias. I really don’t think
that’s a big issue, every other parts of the prototype is in fact pretty
http-specific.
>
> Yes the prototype might dependent on some other frameworks like blue
socket, but that’s how we can at least test and branchmark.
>
> Sincerely
> Michael
>
>
>> On May 27, 2017, at 4:06 AM, Paulo Faria <paulo@zewo.io> wrote:
>>
>> Sorry if I'm being annoying, but I really feel what we lack is process.
There's no formal proposal and proposal review. I really think we should
move incrementally with well defined scopes and deadlines for every round.
We didn't have that so far. Carl and others said that my suggestion is
counter productive. I think the opposite, of course, as what I'm proposing
is a well defined process where when we settle on a design for a particular
set of base APIs then we move on to a higher absctraction. This way we
won't be discussing the same things over and over again. I'll repeat, if we
can't agree on the base types how can we move on? I'm saying let's first
*settle* on Version, Headers, Message, Request and Response. Really
*define* the API so then we move on, incrementally, from lower abstraction
to higher abstraction.
>>
>>
>>
>> On May 27, 2017 07:53, "Paulo Faria" <paulo@zewo.io> wrote:
>> I'm just proposing we move the code incrementally. The "most
consentual" list I sent yesterday isn't radically different from the code
Johannes proposed. I really don't want to discuss this over and over, ad
infinitum. The most important in the messages I sent before is that we need
a well defined process. We don't have it. Just taking the first
implementation, with a lot of things that admitedly don't fit the scope,
and adding that so we can rework doesn't feel right to me. I'm actually
confused about the scope this project is taking. The code there explicitly
mentions a WebApp, which is a higher responsibility than HTTP. When we
started this project the scope as very clear. Crypto/TLS, Socket, HTTP. A
well designed HTTP module shouldn't depend *at all* on the socket
implementation. Providing an implementation would be just a matter of
injecting a dependency. Moving that code as is to the org really doesn't
feel right to me. All I'm saying is that we definitely should start having
code on the org. But I say we move first Version, Headers, Message,
Request, Response. And again, the "least controversial" I sent yesterday
isn't radically strange. It's an evolution of Johaness original proposal,
plus Carl's, plus Helge's suggestions, plus my suggestions. The only thing
I added that wasn't discussed before is HTTPHeader.Field which does a case
insensitive comparison in its equatable implementation, and the Message
protocol which holds the properties common to request and response (version
and headers). If we can't agree on that, which is the sum of everything
that was discussed about these particular types. How can we agree on that
full implementation?
>>
>> On May 27, 2017 05:13, "Michael Chiu" <hatsuneyuji@icloud.com> wrote:
>> Hi Carl
>>
>>
>> > This email thread isn’t about an API proposal. It’s about a
prototype implementation of an API that was already proposed and discussed
a month and a half ago. The prototype isn't a full-featured framework like
Vapor or Kitura, but it does actually work and it even has XCTests with
decent (>75%) code coverage.
>>
>> I see, I was confused by the email contents instead of reading the
subject and thought we are finally implementing some code. TBH, I don’t see
any reason why this should not move to swift-server on github, It sounds a
good start to me.
>> Thank you guys’ hard work for building it.
>>
>> > Also, please note that I didn’t play any part in proposing this
API back in March/April - it’s not “Carl’s proposal.” I just took the
existing API that the group had previously discussed and implemented enough
of it so that we could measure the utility and performance of the API as
proposed and so that we could have better informed discussions about
potential alternatives.
>>
>> You’re right. it was Johannes’ proposal, I’m so sorry for that.
>>
>> Michael.
>>
>>
>
>
> _______________________________________________
> swift-server-dev mailing list
> swift-server-dev@swift.org
> https://lists.swift.org/mailman/listinfo/swift-server-dev

oh.. scratch that.. the ordered dict won't help with proxying at all.. ouch.

···

On 30 May 2017 at 11:40, Paulo Faria <paulo@zewo.io> wrote:

Hi, Johannes!

You're right. It helps performance by not having to go through all the
headers and lowercase then when they might not be needed during request
handling. This worked well in my own code because I used a dictionary as a
backing storage, having O(1) retrieval. Ideally the best backing storage
would be:

OrderedDictionary<Field, [String]>

- This way we have a single source of truth (another problem with the two
storages approach)
- We don't have to do lowercase on all headers even if they're not needed
later.
- The case insensitive comparison from Field is done when the user
actually needs the header.
- We maintain the original order of the http message in cases of proxying.
- Retrieval is done in O(1)

Cheers,
Paulo

On 30 May 2017 at 11:29, Johannes Weiss <johannesweiss@apple.com> wrote:

Hi,

> On 27 May 2017, at 2:18 pm, Paulo Faria via swift-server-dev < >> swift-server-dev@swift.org> wrote:
>
> Michael, I narrowed the scope of the proposal and focused the
rationale. I answered your comments about switch for HTTPMethod and
HTTPStatus. Basically, we can switch on them because I implemented the
pattern matching operator ~=. I also removed the HTTPMessage as it is not
*needed* and users can define it and make HTTPRequest and HTTPResponse
conform to it in extensions in their own module.
>
> https://github.com/paulofaria/http-api-proposal#rationale
>
> Carl, Helge, Chris and Rien what do you think about the code? This is
pretty close to what Carl suggested.
>
> The main differences are..
>
> HTTPVersion as a struct.
> HTTPHeaders backed by a single array storage and case insensitive
checking is done with the Field type to improve performance.

not opposed to having a 'Field', but how does that improve performance?
It reduces memory usage but having a single list makes the lookup of a
certain header field with .filter O(n) instead of O(1).

> HTTPMethod as a struct instead of enum with pattern matching added
allowing switch cases. (pointed by Helge)
> HTTPStatus as a struct instead of enum with pattern matching added
allowing switch cases. (pointed by Helge)

happy with those. Except the proposed hashing functions

return method.hashValue ^ uri.hashValue ^ version.hashValue ^
headers.hashValue

is really not appropriate but I guess it's more a proof of concept.

-- Johannes

> Apart from that there's only naming differences. I added some tests to
showcase what I mentioned.
>
> https://github.com/paulofaria/http-api-proposal/blob/master/
Tests/HTTPTests/HTTPTests.swift
>
> On 27 May 2017 at 08:41, Michael Chiu <hatsuneyuji@icloud.com> wrote:
> The thing is, as I have mentioned before if we move on that route we
have to have a well defined structure before hand, for example, there’s no
such protocol “Message” in what Johannes proposed.
>
> As Chris and Carl mentioned before, this is only a prototype of the
discussed API, so at least we have something to test, experiment and
branchmark on.
>
> Also things like WebApp in Johannes’ proposal can easily remove without
huge changes, since WebApp is simply a typealias. I really don’t think
that’s a big issue, every other parts of the prototype is in fact pretty
http-specific.
>
> Yes the prototype might dependent on some other frameworks like blue
socket, but that’s how we can at least test and branchmark.
>
> Sincerely
> Michael
>
>
>> On May 27, 2017, at 4:06 AM, Paulo Faria <paulo@zewo.io> wrote:
>>
>> Sorry if I'm being annoying, but I really feel what we lack is
process. There's no formal proposal and proposal review. I really think we
should move incrementally with well defined scopes and deadlines for every
round. We didn't have that so far. Carl and others said that my suggestion
is counter productive. I think the opposite, of course, as what I'm
proposing is a well defined process where when we settle on a design for a
particular set of base APIs then we move on to a higher absctraction. This
way we won't be discussing the same things over and over again. I'll
repeat, if we can't agree on the base types how can we move on? I'm saying
let's first *settle* on Version, Headers, Message, Request and Response.
Really *define* the API so then we move on, incrementally, from lower
abstraction to higher abstraction.
>>
>>
>>
>> On May 27, 2017 07:53, "Paulo Faria" <paulo@zewo.io> wrote:
>> I'm just proposing we move the code incrementally. The "most
consentual" list I sent yesterday isn't radically different from the code
Johannes proposed. I really don't want to discuss this over and over, ad
infinitum. The most important in the messages I sent before is that we need
a well defined process. We don't have it. Just taking the first
implementation, with a lot of things that admitedly don't fit the scope,
and adding that so we can rework doesn't feel right to me. I'm actually
confused about the scope this project is taking. The code there explicitly
mentions a WebApp, which is a higher responsibility than HTTP. When we
started this project the scope as very clear. Crypto/TLS, Socket, HTTP. A
well designed HTTP module shouldn't depend *at all* on the socket
implementation. Providing an implementation would be just a matter of
injecting a dependency. Moving that code as is to the org really doesn't
feel right to me. All I'm saying is that we definitely should start having
code on the org. But I say we move first Version, Headers, Message,
Request, Response. And again, the "least controversial" I sent yesterday
isn't radically strange. It's an evolution of Johaness original proposal,
plus Carl's, plus Helge's suggestions, plus my suggestions. The only thing
I added that wasn't discussed before is HTTPHeader.Field which does a case
insensitive comparison in its equatable implementation, and the Message
protocol which holds the properties common to request and response (version
and headers). If we can't agree on that, which is the sum of everything
that was discussed about these particular types. How can we agree on that
full implementation?
>>
>> On May 27, 2017 05:13, "Michael Chiu" <hatsuneyuji@icloud.com> wrote:
>> Hi Carl
>>
>>
>> > This email thread isn’t about an API proposal. It’s about a
prototype implementation of an API that was already proposed and discussed
a month and a half ago. The prototype isn't a full-featured framework like
Vapor or Kitura, but it does actually work and it even has XCTests with
decent (>75%) code coverage.
>>
>> I see, I was confused by the email contents instead of reading the
subject and thought we are finally implementing some code. TBH, I don’t see
any reason why this should not move to swift-server on github, It sounds a
good start to me.
>> Thank you guys’ hard work for building it.
>>
>> > Also, please note that I didn’t play any part in proposing
this API back in March/April - it’s not “Carl’s proposal.” I just took the
existing API that the group had previously discussed and implemented enough
of it so that we could measure the utility and performance of the API as
proposed and so that we could have better informed discussions about
potential alternatives.
>>
>> You’re right. it was Johannes’ proposal, I’m so sorry for that.
>>
>> Michael.
>>
>>
>
>
> _______________________________________________
> swift-server-dev mailing list
> swift-server-dev@swift.org
> https://lists.swift.org/mailman/listinfo/swift-server-dev

I guess we need two storages after all.. but instead of lowercasing in the
dictionary storage we could keep Field.

public struct HTTPHeaders {
    public var original: [(String, String)]
    public var headers: [Field: [String]]
}

This way we have:

- We don't have to do lowercase on all headers even if they're not needed
later.
- The case insensitive comparison from Field is done when the user actually
needs the header.
- We maintain the original order of the http message in cases of proxying
by providing original.
- Retrieval is done in O(1) from headers.

But we don't have single source of truth.. which might not be that bad and
we also might get a penalty by adding to both storages.

···

On 30 May 2017 at 11:41, Paulo Faria <paulo@zewo.io> wrote:

oh.. scratch that.. the ordered dict won't help with proxying at all..
ouch.

On 30 May 2017 at 11:40, Paulo Faria <paulo@zewo.io> wrote:

Hi, Johannes!

You're right. It helps performance by not having to go through all the
headers and lowercase then when they might not be needed during request
handling. This worked well in my own code because I used a dictionary as a
backing storage, having O(1) retrieval. Ideally the best backing storage
would be:

OrderedDictionary<Field, [String]>

- This way we have a single source of truth (another problem with the two
storages approach)
- We don't have to do lowercase on all headers even if they're not needed
later.
- The case insensitive comparison from Field is done when the user
actually needs the header.
- We maintain the original order of the http message in cases of proxying.
- Retrieval is done in O(1)

Cheers,
Paulo

On 30 May 2017 at 11:29, Johannes Weiss <johannesweiss@apple.com> wrote:

Hi,

> On 27 May 2017, at 2:18 pm, Paulo Faria via swift-server-dev < >>> swift-server-dev@swift.org> wrote:
>
> Michael, I narrowed the scope of the proposal and focused the
rationale. I answered your comments about switch for HTTPMethod and
HTTPStatus. Basically, we can switch on them because I implemented the
pattern matching operator ~=. I also removed the HTTPMessage as it is not
*needed* and users can define it and make HTTPRequest and HTTPResponse
conform to it in extensions in their own module.
>
> https://github.com/paulofaria/http-api-proposal#rationale
>
> Carl, Helge, Chris and Rien what do you think about the code? This is
pretty close to what Carl suggested.
>
> The main differences are..
>
> HTTPVersion as a struct.
> HTTPHeaders backed by a single array storage and case insensitive
checking is done with the Field type to improve performance.

not opposed to having a 'Field', but how does that improve performance?
It reduces memory usage but having a single list makes the lookup of a
certain header field with .filter O(n) instead of O(1).

> HTTPMethod as a struct instead of enum with pattern matching added
allowing switch cases. (pointed by Helge)
> HTTPStatus as a struct instead of enum with pattern matching added
allowing switch cases. (pointed by Helge)

happy with those. Except the proposed hashing functions

return method.hashValue ^ uri.hashValue ^ version.hashValue ^
headers.hashValue

is really not appropriate but I guess it's more a proof of concept.

-- Johannes

> Apart from that there's only naming differences. I added some tests to
showcase what I mentioned.
>
> https://github.com/paulofaria/http-api-proposal/blob/master/
Tests/HTTPTests/HTTPTests.swift
>
> On 27 May 2017 at 08:41, Michael Chiu <hatsuneyuji@icloud.com> wrote:
> The thing is, as I have mentioned before if we move on that route we
have to have a well defined structure before hand, for example, there’s no
such protocol “Message” in what Johannes proposed.
>
> As Chris and Carl mentioned before, this is only a prototype of the
discussed API, so at least we have something to test, experiment and
branchmark on.
>
> Also things like WebApp in Johannes’ proposal can easily remove
without huge changes, since WebApp is simply a typealias. I really don’t
think that’s a big issue, every other parts of the prototype is in fact
pretty http-specific.
>
> Yes the prototype might dependent on some other frameworks like blue
socket, but that’s how we can at least test and branchmark.
>
> Sincerely
> Michael
>
>
>> On May 27, 2017, at 4:06 AM, Paulo Faria <paulo@zewo.io> wrote:
>>
>> Sorry if I'm being annoying, but I really feel what we lack is
process. There's no formal proposal and proposal review. I really think we
should move incrementally with well defined scopes and deadlines for every
round. We didn't have that so far. Carl and others said that my suggestion
is counter productive. I think the opposite, of course, as what I'm
proposing is a well defined process where when we settle on a design for a
particular set of base APIs then we move on to a higher absctraction. This
way we won't be discussing the same things over and over again. I'll
repeat, if we can't agree on the base types how can we move on? I'm saying
let's first *settle* on Version, Headers, Message, Request and Response.
Really *define* the API so then we move on, incrementally, from lower
abstraction to higher abstraction.
>>
>>
>>
>> On May 27, 2017 07:53, "Paulo Faria" <paulo@zewo.io> wrote:
>> I'm just proposing we move the code incrementally. The "most
consentual" list I sent yesterday isn't radically different from the code
Johannes proposed. I really don't want to discuss this over and over, ad
infinitum. The most important in the messages I sent before is that we need
a well defined process. We don't have it. Just taking the first
implementation, with a lot of things that admitedly don't fit the scope,
and adding that so we can rework doesn't feel right to me. I'm actually
confused about the scope this project is taking. The code there explicitly
mentions a WebApp, which is a higher responsibility than HTTP. When we
started this project the scope as very clear. Crypto/TLS, Socket, HTTP. A
well designed HTTP module shouldn't depend *at all* on the socket
implementation. Providing an implementation would be just a matter of
injecting a dependency. Moving that code as is to the org really doesn't
feel right to me. All I'm saying is that we definitely should start having
code on the org. But I say we move first Version, Headers, Message,
Request, Response. And again, the "least controversial" I sent yesterday
isn't radically strange. It's an evolution of Johaness original proposal,
plus Carl's, plus Helge's suggestions, plus my suggestions. The only thing
I added that wasn't discussed before is HTTPHeader.Field which does a case
insensitive comparison in its equatable implementation, and the Message
protocol which holds the properties common to request and response (version
and headers). If we can't agree on that, which is the sum of everything
that was discussed about these particular types. How can we agree on that
full implementation?
>>
>> On May 27, 2017 05:13, "Michael Chiu" <hatsuneyuji@icloud.com> wrote:
>> Hi Carl
>>
>>
>> > This email thread isn’t about an API proposal. It’s about a
prototype implementation of an API that was already proposed and discussed
a month and a half ago. The prototype isn't a full-featured framework like
Vapor or Kitura, but it does actually work and it even has XCTests with
decent (>75%) code coverage.
>>
>> I see, I was confused by the email contents instead of reading the
subject and thought we are finally implementing some code. TBH, I don’t see
any reason why this should not move to swift-server on github, It sounds a
good start to me.
>> Thank you guys’ hard work for building it.
>>
>> > Also, please note that I didn’t play any part in proposing
this API back in March/April - it’s not “Carl’s proposal.” I just took the
existing API that the group had previously discussed and implemented enough
of it so that we could measure the utility and performance of the API as
proposed and so that we could have better informed discussions about
potential alternatives.
>>
>> You’re right. it was Johannes’ proposal, I’m so sorry for that.
>>
>> Michael.
>>
>>
>
>
> _______________________________________________
> swift-server-dev mailing list
> swift-server-dev@swift.org
> https://lists.swift.org/mailman/listinfo/swift-server-dev

1)
One thing I would like to see is a separate item in the repository that

contains just the actual API. Instead of the API intermingled with the >
concrete implementation. Similar to what Johannes’ did. Could be a
document called API.md.

Update this when the API changes due to workgroup work.

Which would be more preferable, a full Jazzy-built API doc, just a .md
file or both? I'd prefer not to take the multi-branch approach if
possible, if nothing else because of the difficulty in keeping the
branches in sync. We should be able to propose implementation changes via
PRs that people can clone, test and compare, and we can merge in what
makes sense - removing blue-socket is a good example.

2)
The other thing that would be nice is a demo tool in the Sources

directory, again similar to what Johannes showed (his tiny echod sample).

It looks like Carl has done that already in SimpleResponseCreator.swift:
        
https://github.com/carlbrown/HTTPSketch/blob/master/Sources/HTTPSketch/SimpleResponseCreator.swift
Giving it a more obvious name makes sense - along with documenting it in
the README.md, etc.

Chris

···

From: Helge Heß via swift-server-dev <swift-server-dev@swift.org>
To: swift-server-dev <swift-server-dev@swift.org>
Date: 30/05/2017 11:54
Subject: Re: [swift-server-dev] Prototype of the discussed HTTP API
Spec
Sent by: swift-server-dev-bounces@swift.org

Hi,

+1 on Chris’ comment.

Two wishes:

1)
One thing I would like to see is a separate item in the repository that
contains just the actual API. Instead of the API intermingled with the
concrete implementation. Similar to what Johannes’ did. Could be a
document called API.md.
Update this when the API changes due to workgroup work.

Or more fancy: Put just the API (and no2 below) into the master branch.
The same .swift files, just w/o any implementation code. (Yes, this won’t
compile of course). Then have branches for concrete implementations, say
‘blue-socket’ for the current one.
This way the API can evolve in master and you can always see where the
concrete implementation is at, and other people can contribute different
implementations for comparison (e.g. the low hanging fruit would be a
simple one that doesn’t require a socket library but just libdispatch).

2)
The other thing that would be nice is a demo tool in the Sources
directory, again similar to what Johannes showed (his tiny echod sample).

hh

On 30. May 2017, at 12:08, Chris Bailey via swift-server-dev <swift-server-dev@swift.org> wrote:

Hi Paulo:

One of the things we're trying to avoid is a waterfall design approach,

requiring a fully settled API before any implementation work starts - its
much more preferable to have a reasonable starting point, make it work,
and then make it better. One of the advantages of this approach is that it
widens the funnel of participation out from "API designers" to include
users, who can try out the API and provide feedback.

The proposal that's been implemented was the result of a number of weeks

of debate on the mailing list, and as such gives us that starting point.
That doesn't mean that your proposal is ignored - in fact it gives us an
excellent list of areas that we can debate incrementally and see what the
effect would be via working use cases and performance tests.

That then gets us working and collaboration on on a single code base -

which is really the primary goal of the workgroup!

Chris

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number
741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU

> 1)
> One thing I would like to see is a separate item in the repository that contains just the actual API. Instead of the API intermingled with the > concrete implementation. Similar to what Johannes’ did. Could be a document called API.md.
> Update this when the API changes due to workgroup work.

Which would be more preferable, a full Jazzy-built API doc, just a .md file or both?

Just an .md file would be preferable by me.

I'd prefer not to take the multi-branch approach if possible, if nothing else because of the difficulty in keeping the branches in sync. We should be able to propose implementation changes via PRs that people can clone, test and compare, and we can merge in what makes sense - removing blue-socket is a good example.

I think it would be worthwhile to maintain multiple versions, especially during testing. E.g. the Blue one may make sense to checkout SSL setups, while a GCD one would be the default imp? Something like that, kinda relates to when/how we get a common socket API?

But anyways, not really important.

> 2)
> The other thing that would be nice is a demo tool in the Sources directory, again similar to what Johannes showed (his tiny echod sample).

It looks like Carl has done that already in SimpleResponseCreator.swift:

Ah, OK. What I meant is a tool target alongside the implementation, like:

  Sources/
    HTTPSketch/
      HTTPRequest.swift
      ...
    EchoDemo/
      main.swift - something which actually runs

hh

···

On 30 May 2017, at 13:09, Chris Bailey <BAILEYC@uk.ibm.com> wrote:

David here, working with Paulo on Zewo and more. There’s absolutely a lack of process, as Paulo mentioned. We have a very rough road ahead of us, with a ton of extra work, if we need to start writing implementation code at this level. Tests and dummy implementations should be well enough to allow us to gather feedback.

Personally I don’t think that tests and dummies are sufficient to the viability and performance of the API.

The way forward, as I see it, is agreeing on the API for HTTP Request/Response/Headers/Method/Status/Version based on a proposal structure and pull requests.

I would prefer the way it works/worked with Swift itself. An initial, working implementation is provided (Swift 1). Then we iterate over it using proposals/pull requests.

As mentioned I agree that it would be helpful to decouple the API setup from the implementation (my branch suggestion). Though it is so little code, I’m not sure it is worth the effort.

I should also mention that I strongly oppose bringing in dependencies from any actor in the Swift community to the work group org at this stage.

Presumably you are referring to the BlueSocket stuff? I can see your concerns, but I’m sure the idea was just to get forward.

What about a pure Dispatch/CHttp based implementation until the Swift Server Sockets are ready? (where is that effort?)
Would that work for you? It is probably a just a day of work to rip out the Blue stuff and replace it with a basic socket server just using Dispatch ...

hh

···

On 30 May 2017, at 13:23, David Ask via swift-server-dev <swift-server-dev@swift.org> wrote:

Hi,

···

On 27 May 2017, at 2:30 pm, Paulo Faria via swift-server-dev <swift-server-dev@swift.org> wrote:

By the way. I also think we should consider Helge's suggestions of naming for what is currently request and response. They are actually only the request and response head, as Helge pointed. So it *does* make sense to name it HTTPRequestHead and HTTPResponseHead.

I would also agree with HTTP{Request,Response}Head and I think having a shared HTTPMessageHead protocol makes sense too.

There were some other messages where the casing HTTPRequest(Head) vs HttpRequest(Head) was touched: I think in the Swift ecosystem HTTPRequestHead makes more sense as that is what Cocoa & Foundation use.

Regarding HTTPRequst(Head) vs just Request(Head) I think HTTPRequest(Head) is more appropriate because Swift's modules are currently a one-level hierarchy only and also most of the time all symbols are imported. Also again, Foundation sets a precedent of URLRequest/HTTPURLResponse.

--
  Johannes

On 27 May 2017 at 10:18, Paulo Faria <paulo@zewo.io> wrote:
Michael, I narrowed the scope of the proposal and focused the rationale. I answered your comments about switch for HTTPMethod and HTTPStatus. Basically, we can switch on them because I implemented the pattern matching operator ~=. I also removed the HTTPMessage as it is not *needed* and users can define it and make HTTPRequest and HTTPResponse conform to it in extensions in their own module.

https://github.com/paulofaria/http-api-proposal#rationale

Carl, Helge, Chris and Rien what do you think about the code? This is pretty close to what Carl suggested.

The main differences are..

HTTPVersion as a struct.
HTTPHeaders backed by a single array storage and case insensitive checking is done with the Field type to improve performance.
HTTPMethod as a struct instead of enum with pattern matching added allowing switch cases. (pointed by Helge)
HTTPStatus as a struct instead of enum with pattern matching added allowing switch cases. (pointed by Helge)

Apart from that there's only naming differences. I added some tests to showcase what I mentioned.

https://github.com/paulofaria/http-api-proposal/blob/master/Tests/HTTPTests/HTTPTests.swift

On 27 May 2017 at 08:41, Michael Chiu <hatsuneyuji@icloud.com> wrote:
The thing is, as I have mentioned before if we move on that route we have to have a well defined structure before hand, for example, there’s no such protocol “Message” in what Johannes proposed.

As Chris and Carl mentioned before, this is only a prototype of the discussed API, so at least we have something to test, experiment and branchmark on.

Also things like WebApp in Johannes’ proposal can easily remove without huge changes, since WebApp is simply a typealias. I really don’t think that’s a big issue, every other parts of the prototype is in fact pretty http-specific.

Yes the prototype might dependent on some other frameworks like blue socket, but that’s how we can at least test and branchmark.

Sincerely
Michael

On May 27, 2017, at 4:06 AM, Paulo Faria <paulo@zewo.io> wrote:

Sorry if I'm being annoying, but I really feel what we lack is process. There's no formal proposal and proposal review. I really think we should move incrementally with well defined scopes and deadlines for every round. We didn't have that so far. Carl and others said that my suggestion is counter productive. I think the opposite, of course, as what I'm proposing is a well defined process where when we settle on a design for a particular set of base APIs then we move on to a higher absctraction. This way we won't be discussing the same things over and over again. I'll repeat, if we can't agree on the base types how can we move on? I'm saying let's first *settle* on Version, Headers, Message, Request and Response. Really *define* the API so then we move on, incrementally, from lower abstraction to higher abstraction.

On May 27, 2017 07:53, "Paulo Faria" <paulo@zewo.io> wrote:
I'm just proposing we move the code incrementally. The "most consentual" list I sent yesterday isn't radically different from the code Johannes proposed. I really don't want to discuss this over and over, ad infinitum. The most important in the messages I sent before is that we need a well defined process. We don't have it. Just taking the first implementation, with a lot of things that admitedly don't fit the scope, and adding that so we can rework doesn't feel right to me. I'm actually confused about the scope this project is taking. The code there explicitly mentions a WebApp, which is a higher responsibility than HTTP. When we started this project the scope as very clear. Crypto/TLS, Socket, HTTP. A well designed HTTP module shouldn't depend *at all* on the socket implementation. Providing an implementation would be just a matter of injecting a dependency. Moving that code as is to the org really doesn't feel right to me. All I'm saying is that we definitely should start having code on the org. But I say we move first Version, Headers, Message, Request, Response. And again, the "least controversial" I sent yesterday isn't radically strange. It's an evolution of Johaness original proposal, plus Carl's, plus Helge's suggestions, plus my suggestions. The only thing I added that wasn't discussed before is HTTPHeader.Field which does a case insensitive comparison in its equatable implementation, and the Message protocol which holds the properties common to request and response (version and headers). If we can't agree on that, which is the sum of everything that was discussed about these particular types. How can we agree on that full implementation?

On May 27, 2017 05:13, "Michael Chiu" <hatsuneyuji@icloud.com> wrote:
Hi Carl

> This email thread isn’t about an API proposal. It’s about a prototype implementation of an API that was already proposed and discussed a month and a half ago. The prototype isn't a full-featured framework like Vapor or Kitura, but it does actually work and it even has XCTests with decent (>75%) code coverage.

I see, I was confused by the email contents instead of reading the subject and thought we are finally implementing some code. TBH, I don’t see any reason why this should not move to swift-server on github, It sounds a good start to me.
Thank you guys’ hard work for building it.

> Also, please note that I didn’t play any part in proposing this API back in March/April - it’s not “Carl’s proposal.” I just took the existing API that the group had previously discussed and implemented enough of it so that we could measure the utility and performance of the API as proposed and so that we could have better informed discussions about potential alternatives.

You’re right. it was Johannes’ proposal, I’m so sorry for that.

Michael.

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Hi,

I guess we need two storages after all.. but instead of lowercasing in the dictionary storage we could keep Field.

public struct HTTPHeaders {
    public var original: [(String, String)]
    public var headers: [Field: [String]]
}

This way we have:

- We don't have to do lowercase on all headers even if they're not needed later.
- The case insensitive comparison from Field is done when the user actually needs the header.
- We maintain the original order of the http message in cases of proxying by providing original.
- Retrieval is done in O(1) from headers.

fine by me but tbh that can be implementation defined, no? We should document that accessing header fields must be in constant time and everyone decides how they implement that.

But we don't have single source of truth.. which might not be that bad and we also might get a penalty by adding to both storages.

it's constant overhead is both time & space (we just duplicate storage), I'd argue that's fine.

-- Johannes

···

On 30 May 2017, at 3:47 pm, Paulo Faria <paulo@zewo.io> wrote:

On 30 May 2017 at 11:41, Paulo Faria <paulo@zewo.io> wrote:
oh.. scratch that.. the ordered dict won't help with proxying at all.. ouch.

On 30 May 2017 at 11:40, Paulo Faria <paulo@zewo.io> wrote:
Hi, Johannes!

You're right. It helps performance by not having to go through all the headers and lowercase then when they might not be needed during request handling. This worked well in my own code because I used a dictionary as a backing storage, having O(1) retrieval. Ideally the best backing storage would be:

OrderedDictionary<Field, [String]>

- This way we have a single source of truth (another problem with the two storages approach)
- We don't have to do lowercase on all headers even if they're not needed later.
- The case insensitive comparison from Field is done when the user actually needs the header.
- We maintain the original order of the http message in cases of proxying.
- Retrieval is done in O(1)

Cheers,
Paulo

On 30 May 2017 at 11:29, Johannes Weiss <johannesweiss@apple.com> wrote:
Hi,

> On 27 May 2017, at 2:18 pm, Paulo Faria via swift-server-dev <swift-server-dev@swift.org> wrote:
>
> Michael, I narrowed the scope of the proposal and focused the rationale. I answered your comments about switch for HTTPMethod and HTTPStatus. Basically, we can switch on them because I implemented the pattern matching operator ~=. I also removed the HTTPMessage as it is not *needed* and users can define it and make HTTPRequest and HTTPResponse conform to it in extensions in their own module.
>
> https://github.com/paulofaria/http-api-proposal#rationale
>
> Carl, Helge, Chris and Rien what do you think about the code? This is pretty close to what Carl suggested.
>
> The main differences are..
>
> HTTPVersion as a struct.
> HTTPHeaders backed by a single array storage and case insensitive checking is done with the Field type to improve performance.

not opposed to having a 'Field', but how does that improve performance? It reduces memory usage but having a single list makes the lookup of a certain header field with .filter O(n) instead of O(1).

> HTTPMethod as a struct instead of enum with pattern matching added allowing switch cases. (pointed by Helge)
> HTTPStatus as a struct instead of enum with pattern matching added allowing switch cases. (pointed by Helge)

happy with those. Except the proposed hashing functions

return method.hashValue ^ uri.hashValue ^ version.hashValue ^ headers.hashValue

is really not appropriate but I guess it's more a proof of concept.

-- Johannes

> Apart from that there's only naming differences. I added some tests to showcase what I mentioned.
>
> https://github.com/paulofaria/http-api-proposal/blob/master/Tests/HTTPTests/HTTPTests.swift
>
> On 27 May 2017 at 08:41, Michael Chiu <hatsuneyuji@icloud.com> wrote:
> The thing is, as I have mentioned before if we move on that route we have to have a well defined structure before hand, for example, there’s no such protocol “Message” in what Johannes proposed.
>
> As Chris and Carl mentioned before, this is only a prototype of the discussed API, so at least we have something to test, experiment and branchmark on.
>
> Also things like WebApp in Johannes’ proposal can easily remove without huge changes, since WebApp is simply a typealias. I really don’t think that’s a big issue, every other parts of the prototype is in fact pretty http-specific.
>
> Yes the prototype might dependent on some other frameworks like blue socket, but that’s how we can at least test and branchmark.
>
> Sincerely
> Michael
>
>
>> On May 27, 2017, at 4:06 AM, Paulo Faria <paulo@zewo.io> wrote:
>>
>> Sorry if I'm being annoying, but I really feel what we lack is process. There's no formal proposal and proposal review. I really think we should move incrementally with well defined scopes and deadlines for every round. We didn't have that so far. Carl and others said that my suggestion is counter productive. I think the opposite, of course, as what I'm proposing is a well defined process where when we settle on a design for a particular set of base APIs then we move on to a higher absctraction. This way we won't be discussing the same things over and over again. I'll repeat, if we can't agree on the base types how can we move on? I'm saying let's first *settle* on Version, Headers, Message, Request and Response. Really *define* the API so then we move on, incrementally, from lower abstraction to higher abstraction.
>>
>>
>>
>> On May 27, 2017 07:53, "Paulo Faria" <paulo@zewo.io> wrote:
>> I'm just proposing we move the code incrementally. The "most consentual" list I sent yesterday isn't radically different from the code Johannes proposed. I really don't want to discuss this over and over, ad infinitum. The most important in the messages I sent before is that we need a well defined process. We don't have it. Just taking the first implementation, with a lot of things that admitedly don't fit the scope, and adding that so we can rework doesn't feel right to me. I'm actually confused about the scope this project is taking. The code there explicitly mentions a WebApp, which is a higher responsibility than HTTP. When we started this project the scope as very clear. Crypto/TLS, Socket, HTTP. A well designed HTTP module shouldn't depend *at all* on the socket implementation. Providing an implementation would be just a matter of injecting a dependency. Moving that code as is to the org really doesn't feel right to me. All I'm saying is that we definitely should start having code on the org. But I say we move first Version, Headers, Message, Request, Response. And again, the "least controversial" I sent yesterday isn't radically strange. It's an evolution of Johaness original proposal, plus Carl's, plus Helge's suggestions, plus my suggestions. The only thing I added that wasn't discussed before is HTTPHeader.Field which does a case insensitive comparison in its equatable implementation, and the Message protocol which holds the properties common to request and response (version and headers). If we can't agree on that, which is the sum of everything that was discussed about these particular types. How can we agree on that full implementation?
>>
>> On May 27, 2017 05:13, "Michael Chiu" <hatsuneyuji@icloud.com> wrote:
>> Hi Carl
>>
>>
>> > This email thread isn’t about an API proposal. It’s about a prototype implementation of an API that was already proposed and discussed a month and a half ago. The prototype isn't a full-featured framework like Vapor or Kitura, but it does actually work and it even has XCTests with decent (>75%) code coverage.
>>
>> I see, I was confused by the email contents instead of reading the subject and thought we are finally implementing some code. TBH, I don’t see any reason why this should not move to swift-server on github, It sounds a good start to me.
>> Thank you guys’ hard work for building it.
>>
>> > Also, please note that I didn’t play any part in proposing this API back in March/April - it’s not “Carl’s proposal.” I just took the existing API that the group had previously discussed and implemented enough of it so that we could measure the utility and performance of the API as proposed and so that we could have better informed discussions about potential alternatives.
>>
>> You’re right. it was Johannes’ proposal, I’m so sorry for that.
>>
>> Michael.
>>
>>
>
>
> _______________________________________________
> swift-server-dev mailing list
> swift-server-dev@swift.org
> https://lists.swift.org/mailman/listinfo/swift-server-dev

I would recommend against that. The problem is that if you have such helpers/demos in the Sources, then they not only get built for your project but also in every project that depends on that project as well. So you can be building your production app, and you'll still get Echo, HelloWorld built -- and if you are building with an Xcode generated project, that will lead to other (unnecessary) targets being built for your project.

If you have demos, I would suggest moving them into a different repository and then have that depend on the implementation repository through Swift PM. You can then guarantee that the same examples compile and behave correctly across different versions of your libraries.

Alex

···

On 30 May 2017, at 16:20, Carl Brown via swift-server-dev <swift-server-dev@swift.org> wrote:

On May 30, 2017, at 5:53 AM, Helge Heß via swift-server-dev <swift-server-dev@swift.org <mailto:swift-server-dev@swift.org>> wrote:
2)
The other thing that would be nice is a demo tool in the Sources directory, again similar to what Johannes showed (his tiny echod sample).

I have several of those (Echo, HelloWorld, etc) that I use for end-to-end testing, but since they're not part of the API, they're under `Tests/` not `Sources/`. See https://github.com/carlbrown/HTTPSketch/tree/master/Tests/HTTPSketchTests/Helpers

I'm happy to move them to `Sources/`if that would be easier.

Hi,

Sorry if this is a little off-topic, but I just wanted to mention that I think neither is really true.

I’m not completely sure, but I think the Node.js HTTP implementation actually runs on multiple threads, it is just the user level JavaScript stack that is single threaded (I think it sparks CPU-count threads). May be wrong on that ;-)

https://www.quora.com/Is-Node-js-single-threaded

Well, yes. This doesn’t discuss what I was talking about. What I think (but I may be wrong) is that Node.js (the kernel, not the JS API) may run multiple HTTP threads where sockets are accepted, headers parsed etc, before those are handed over to the JavaScript thread. Or in other words, where those parts run multi-threaded (accepting, HTTP header parsing).

This makes some sense because doing all those things also takes time.

I said, I may be completely wrong here, it would make sense though :-)

you spawn multiple Node.js processes to utilise more than one core. But not important at all. Just wanted to mention that async API doesn't require more than one thread.

well, the basic API is similar I think

… yeah sorry, but no, the basic API is nothing like it … You completely ignore that IncomingMessage and ServerResponse themselves are full streams and generic event emitters with everything attached.

Even IncomingMessage is nothing like our HTTPRequestHead - our head is just an inactive struct whereas the IncomingMessage is an actual object. Representing the read stream, and the headers, and even state whether the headers has been sent or not.
I don’t think that this is necessarily a good design, but it is really different to what was proposed.

and the IncomingMessage (HTTPRequestHead for us) has headers, http version and stuff but not the body.

IncomingMessage *is* the body stream. It is like

class IncomingMessage : AsyncInputStream ( : ErrorEmitter : EventEmitter)

Instead of returning a closure, you register a handler in the request à la

--- SNIP ---
request.on('data',function(message){
  response.write(message);
});

request.on('end',function(){
  response.end();
});
--- SNAP —

This is the old style ‘push’ stream API. And even that has more features than callbacks. In ‘modern’ (years old) Node you would do:

request.on(‘readable’) {
   let block = request.read();
   … do stuff with it ...
}

but more usually you would pipe it somewhere,

let file = fs.createWriteStream(“/tmp/filedump”)
request.pipe(file)

or request.pipe(transform(func() { … })).pipe(file)

this is really the bit that we don't support. I fully agree that the proposed API is less powerful than Node.js . All I was trying to say is that Node.js has APIs similar to what is proposed (amongst others). But as soon as we need to compose streams (as you do in the example just above), we're out of luck with the API proposed.

But again, we're then getting into the territory of the Networking/Transport group. It is an absolutely valid opinion to say we shouldn't spend time on HTTP alone without composable streams/channels/pipes.

This is a great (and pretty quick) intro to streams:

https://github.com/substack/stream-handbook

which is reasonably close to .. return .processBody ..

Not at all, it is a huge superset of that.

don't you think? To me it looks quite straightforward to port a Node HTTP app to the proposal we're discussing.

Since it provides no streams nor piping I have a hard time seeing you port anything but the most trivial demo.

I was probably overstating that, you're right.

But yes, it can be used as a *basis* to layer a Node-like framework on top. I.e. I could write a Noze.io stream which reads from the Swift HTTP API.

This is what we should focus on: Make sure that the stuff can be used as a basis for more advanced stuff.

Sounds good to me. I was mainly trying to propose something that's not totally out of scope for the HTTP group and for which we have an actual implementation. But eventually we will hopefully achieve a more composable general IO/networking library. But as you (I think) mentioned in one of the previous mails, it'll be even harder to design something to make everybody happy (enough) as that is more general & abstract...

But I do agree that we don't have streams. I thought about something like that but I thought that's too much just for HTTP. If we want to have a more general streaming mechanism, we should probably discuss that in the Transport/Networking meetings, no?

Well, HTTP sits on top of two byte streams. Which can be plain TCP sockets, or TLS, or a local domain socket, even UDP, why not read from files for testing purposes? In short, HTTP should technically sit on top of arbitrary streams.
It’s not utterly important (nor necessarily better for performance), but it would make a nicer architecture.

agreed

That is why I said since the beginning that I’m more interested in how the API for the body stream will look like (as this affects sockets as well as HTTP). Now we essentially get separate APIs for the same thing ...

I do agree with you here. But the networking group basically hasn't happened yet so I was proposing something here as we happened to have an implementation that works.

Sure. In a way that really is something for Foundation anyways, IMO.

I thought that is what the Networking/Transport group is for. But as you say it's generally IO really not necessarily only networking.

-- Johannes

···

On 31 May 2017, at 3:39 pm, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:
On 31 May 2017, at 16:07, Johannes Weiss <johannesweiss@apple.com> wrote:

While I’d love to have a nice streaming framework like the one Node has built right into Swift, maybe just some extension of GCD channels would do (if you don’t want to go the full stream-everything approach Noze.io does), but I understand that this is a little out of scope and probably impossible to agree on ;-) Doesn’t matter too much as you can put adapters on top of the stuff.

:+1:

hh

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Hi Johannes

I think i need to clarify something: I’m ok with a asynchronous api that executes synchronously, for example if the api is something like [[ a. { b() } ; c() ]], executes as [[ a(); b(); c() ]], it is totally fine since it’s just synchronous api with syntactic sugar.

We actually have a synchronous implementation of the proposed API next to the DispatchIO one that we normally use. The synchronous one uses problem system calls and only services one request per thread. It's handy for unit testing and for specialised use-cases. The synchronous implementation only uses the following syscalls: open, close, read and write, that's it so nothing fancy.

I think even exposing these apis to user will be good. No need for fancy support just include it and it will be good enough.

I think i need to clarify something: I’m ok with a asynchronous api that executes synchronously, for example if the api is something like [[ a. { b() } ; c() ]], executes as [[ a(); b(); c() ]], it is totally fine since it’s just synchronous api

ie. you use write as a blocking system call because the file descriptor isn't set to be non-blocking.

Just as a side note: You won't be able to repro this issue by replacing the macOS `telnet` with the macOS `nc` (netcat) as netcat will only read more to the socket after it was able to write it. Ie. the implementation of standard macOS `nc` happens to make your implementation appear non-blocking. But the macOS provided telnet seems to do the right thing. You can use pbjnc (http://www.chiark.greenend.org.uk/~peterb/linux/pjbnc/\) if you prefer which also doesn't have the same bug as `nc`.

As I said both snippet of code are just sketches only for proof of concept. But I do missed on the kevent write one that’s for sure.

I'd guess that most programmers prefer an asynchronous API with callback (akin to Node.js/DispatchIO) to using the eventing mechanism directly and I was therefore assuming you wanted to build that from kevent() (which is what they're often used for). Nevertheless, kevent() won't make your programming model any nicer than asynchronous APIs and as I mentioned before you can build one from the other in a quite straightforward way. What we don't get from that is ordinary synchronous APIs that don't block kernel threads and that happens to be what most people would prefer eventually. Hence libdill/mill/venice and Zewo :).

Johannes, I totally agree with you. A asynchronous API is more intuitive and I agree with that. But since we are providing low level API for ppl like Zewo, Prefect, and Kitura, it is not right for us to assume their model of programming.

For libdill/mill/venice, even with green threads they will block when there’s nothing to do,

If you read in libdill/mill/venice, it will switch the user-level thread to _not_ block a kernel thread. That's the difference and that's what we can't achieve with Swift today (without using UB).

I’m quite confused on this one, since a green thread, if that’s what we think we were referring to, can not enter kernel (It can, but when it enters what happened is that the kernel thread associated with enters kernel).
So you can’t switch to another user-level thread to not block a kernel thread.
AFAIK all majority OS(Liunx, FreeBSD, Solaris….) adopted 1:1 threading model instead of n:m, not sure about Darwin but I think it applies to Darwin as well according to an old WWDC video (I could be wrong), hence any user threads (except for green threads) are in fact kernel threads. Since kevent and epoll are designed to block when they should, I don’t think anyone could avoid blocking something.

in fact all the example you listed above all uses events api internally. Hence I don’t think if an api will block a kernel thread is a good argument here.

kernel threads are a finite resource and most modern networking APIs try hard to only spawn a finite number of kernel threads way smaller than the number of connections handled concurrently. If you use Dispatch as your concurrency mechanism, your thread pool will have a maximum size of 64 threads by default on Darwin. (Sure you can spawn more using (NS)Thread from Foundation or pthreads or so)

Yes Kernel threads are finite resources especially in 1:1 model but I’m not sure how is it relevant. My concern on not include a synchronous API is that it make people impossible to write synchronous code, with server side swift tools, despite blocking or not, which they might want to. I’m not saying sync is better, I’m just saying we could give them a chance.

And even if such totally non-blocking programming model it will be expensive since the kernel is constantly scheduling a do-nothing-thread. (( if the io thread of a server-side application need to do something constantly despite there’s no user and no connection it sounds like a ghost story to me )).

what is the do-nothing-thread? The IO thread will only be scheduled if there's something to do and then normally the processing starts on that very thread. In systems like Netty they try very hard to reduce hopping between different threads and to only spawn a few. Node.js is the extreme which handles everything on one thread. It will be able to do thousands of connections with only one thread.

The kernel has no idea is a thread have anything to do unless it sleeps/enterKernel, unless a thread fits in these requirements, it will always scheduled by the kernel.

I’m saying, if there exists a real non-blocking programming model, defined that by “never call any ‘wait’ system calls’, than any IO threads of that model must constantly poll the kernel, hence such thread _cannot_be_scheduled_on_demand since the thread itself has no idea if it has anything to do. The only way to have an IO thread to do know they have to do something, they will either need

1) An external listener call blocking event api and poke the IO thread on demand
2) The IO thread has to constantly poll the kernel
3) An external listener polls the kernel constantly and poke the IO thread when ready.
  
2 and 3 are the do-nothing-thread I’m referring to, they are running, polling, wasting kernel resources but not actually being productive (when there’s no connection).

You should definitely check the return value of write(), it's very important. Even if positive you need to handle the case that it's less bytes than you wanted it to write. And if negative, the bytes are _lost_ which happens all the time with the current implementation.

Anyway, to fix the EAGAIN you'll need to ask kevent() when you can write next.

It was suppose to be a proof of concept sketch work. As mentioned in the comments of the code it was assuming to satisfy one single client. Now I’ve improved it so it handles multiple clients while remain synchronous and non blocking. EAGAIN is the only “error” will raise if you consider it as error but for me it’s part of the non blocking IO.

Foundation/Cocoa is I guess the Swift standard library and they abandon synchronous&blocking APIs completely. I don't think we should create something different (without it being better) than what people are used to.

Again, there are two options for IO at the moment:
1) synchronous & blocking kernel threads
2) asynchronous/inversion of control & not blocking kernel threads

Even though I would love a synchronous programming model, I'd chose option (2) because the drawbacks of (1) are just too big. The designers of Foundation/Cocoa/Netty/Node.js/many more have made the same decision. Not saying all other options aren't useful but I'd like the API to be implementable with high-performance and not requiring the implementors to block a kernel thread per connection.

To be honest I will choose 2 as well. But we are in not a 2 choose 1 situation. The main difference between we and netty/node.js is that ppl use them to, write a server, what we do is, writing something ppl use to write something like netty and node.js. So it is reasonable to think there’s demand on a lower-level, synchronous api, despite the possible “drawbacks” they might encounter.

Maybe we have some misunderstanding here. I’m not saying a synchronous api that happens to be able to handle a vector of sockets in single call without blocking anything, I’m saying a synchronous api that can just do one simple thing, which is, read/write in a synchronous way despite block or not, if it will block, just let them know by throwing an exception, the api call itself, will not block anything that way.

Cheers,
Michael.

I think I understand what Michael is trying to say, but I think we're not
in the realm of just HTTP anymore. This is more about the underlying IO
model. Strictly speaking the base API for POSIX non-blocking IO *IS*
synchronous. The only thing is that the IO call might return EAGAIN or
EWOULDBLOCK in case the call would block. Now, on a higher level how you
deal with EAGAIN or EWOULD is what will define if the higher-level API is
synchronous or asynchronous. I think what Michael is suggesting is that we
provide *for the base API* (Networking/IO) synchronous APIs with access to
the underlying file descriptor, which would allow higher level frameworks
to use either libdispatch/libdill/libuv/libwhatever to write their
scheduling mechanism and then define if their version of the higher level
APIs should be synchronous or asynchronous. This doesn't exclude the goal
of this group to provide a libdispatch based default implementation.

I agree with this. Although the overlap and reuse of code between these
different frameworks (each based on libdispatch or libdill or libuv, for
example) would not be that high, there's still value in having them use
what is possible to be common. For HTTP, separating the message heads from
the body is a great example. This way HTTPRequestHead and HTTPResponseHead
can be easily shared between synchronous and asynchronous frameworks since
this part doesn't involve IO. This means that these frameworks can share
for example authentication middleware which only depend on the
Authentication header.

This is not actually about sync/async. I think the takeaway is that
although we are providing a libdispatch based default implementation.
That's just a detail. I think what Michael means is that we must allow
other frameworks based on libdispatch/libuv/libdill/libwhatever to
implement their approach too. And this means providing synchronous APIs for
the base IO and exposing the file descriptor so people can choose how they
are going to poll/schedule for reads and writes. I agree with him a 100%.

···

On 1 June 2017 at 13:08, Michael Chiu <hatsuneyuji@icloud.com> wrote:

Hi Johannes

I think i need to clarify something: I’m ok with a asynchronous api that
executes synchronously, for example if the api is something like [[ a. {
b() } ; c() ]], executes as [[ a(); b(); c() ]], it is totally fine since
it’s just synchronous api with syntactic sugar.

We actually have a synchronous implementation of the proposed API next to
the DispatchIO one that we normally use. The synchronous one uses problem
system calls and only services one request per thread. It's handy for unit
testing and for specialised use-cases. The synchronous implementation only
uses the following syscalls: open, close, read and write, that's it so
nothing fancy.

I think even exposing these apis to user will be good. No need for fancy
support just include it and it will be good enough.

I think i need to clarify something: I’m ok with a asynchronous api that
executes synchronously, for example if the api is something like [[ a. {
b() } ; c() ]], executes as [[ a(); b(); c() ]], it is totally fine since
it’s just synchronous api

ie. you use write as a blocking system call because the file descriptor
isn't set to be non-blocking.

Just as a side note: You won't be able to repro this issue by replacing
the macOS `telnet` with the macOS `nc` (netcat) as netcat will only read
more to the socket after it was able to write it. Ie. the implementation of
standard macOS `nc` happens to make your implementation appear
non-blocking. But the macOS provided telnet seems to do the right thing.
You can use pbjnc (http://www.chiark.greenend.org.uk/~peterb/linux/pjbnc/\)
if you prefer which also doesn't have the same bug as `nc`.

As I said both snippet of code are just sketches only for proof of
concept. But I do missed on the kevent write one that’s for sure.

I'd guess that most programmers prefer an asynchronous API with callback
(akin to Node.js/DispatchIO) to using the eventing mechanism directly and I
was therefore assuming you wanted to build that from kevent() (which is
what they're often used for). Nevertheless, kevent() won't make your
programming model any nicer than asynchronous APIs and as I mentioned
before you can build one from the other in a quite straightforward way.
What we don't get from that is ordinary synchronous APIs that don't block
kernel threads and that happens to be what most people would prefer
eventually. Hence libdill/mill/venice and Zewo :).

Johannes, I totally agree with you. A asynchronous API is more intuitive
and I agree with that. But since we are providing low level API for ppl
like Zewo, Prefect, and Kitura, it is not right for us to assume their
model of programming.

For libdill/mill/venice, even with green threads they will block when
there’s nothing to do,

If you read in libdill/mill/venice, it will switch the user-level thread
to _not_ block a kernel thread. That's the difference and that's what we
can't achieve with Swift today (without using UB).

I’m quite confused on this one, since a green thread, if that’s what we
think we were referring to, can not enter kernel (It can, but when it
enters what happened is that the kernel thread associated with enters
kernel).
So you can’t switch to another user-level thread to not block a kernel
thread.
AFAIK all majority OS(Liunx, FreeBSD, Solaris….) adopted 1:1 threading
model instead of n:m, not sure about Darwin but I think it applies to
Darwin as well according to an old WWDC video (I could be wrong), hence any
user threads (except for green threads) are in fact kernel threads. Since
kevent and epoll are designed to block when they should, I don’t think
anyone could avoid blocking something.

in fact all the example you listed above all uses events api internally.
Hence I don’t think if an api will block a kernel thread is a good argument
here.

kernel threads are a finite resource and most modern networking APIs try
hard to only spawn a finite number of kernel threads way smaller than the
number of connections handled concurrently. If you use Dispatch as your
concurrency mechanism, your thread pool will have a maximum size of 64
threads by default on Darwin. (Sure you can spawn more using (NS)Thread
from Foundation or pthreads or so)

Yes Kernel threads are finite resources especially in 1:1 model but I’m
not sure how is it relevant. My concern on not include a synchronous API is
that it make people impossible to write synchronous code, with server side
swift tools, despite blocking or not, which they might want to. I’m not
saying sync is better, I’m just saying we could give them a chance.

And even if such totally non-blocking programming model it will be
expensive since the kernel is constantly scheduling a do-nothing-thread. ((
if the io thread of a server-side application need to do something
constantly despite there’s no user and no connection it sounds like a ghost
story to me )).

what is the do-nothing-thread? The IO thread will only be scheduled if
there's something to do and then normally the processing starts on that
very thread. In systems like Netty they try very hard to reduce hopping
between different threads and to only spawn a few. Node.js is the extreme
which handles everything on one thread. It will be able to do thousands of
connections with only one thread.

The kernel has no idea is a thread have anything to do unless it
sleeps/enterKernel, unless a thread fits in these requirements, it will
always scheduled by the kernel.

I’m saying, if there exists a real non-blocking programming model, defined
that by “never call any ‘wait’ system calls’, than any IO threads of that
model must constantly poll the kernel, hence such thread
_cannot_be_scheduled_on_demand since the thread itself has no idea if it
has anything to do. The only way to have an IO thread to do know they have
to do something, they will either need

1) An external listener call blocking event api and poke the IO thread on
demand
2) The IO thread has to constantly poll the kernel
3) An external listener polls the kernel constantly and poke the IO thread
when ready.

2 and 3 are the do-nothing-thread I’m referring to, they are running,
polling, wasting kernel resources but not actually being productive (when
there’s no connection).

You should definitely check the return value of write(), it's very
important. Even if positive you need to handle the case that it's less
bytes than you wanted it to write. And if negative, the bytes are _lost_
which happens all the time with the current implementation.

Anyway, to fix the EAGAIN you'll need to ask kevent() when you can write
next.

It was suppose to be a proof of concept sketch work. As mentioned in the
comments of the code it was assuming to satisfy one single client. Now I’ve
improved it so it handles multiple clients while remain synchronous and non
blocking. EAGAIN is the only “error” will raise if you consider it as error
but for me it’s part of the non blocking IO.

Foundation/Cocoa is I guess the Swift standard library and they abandon
synchronous&blocking APIs completely. I don't think we should create
something different (without it being better) than what people are used to.

Again, there are two options for IO at the moment:
1) synchronous & blocking kernel threads
2) asynchronous/inversion of control & not blocking kernel threads

Even though I would love a synchronous programming model, I'd chose option
(2) because the drawbacks of (1) are just too big. The designers of
Foundation/Cocoa/Netty/Node.js/many more have made the same decision. Not
saying all other options aren't useful but I'd like the API to be
implementable with high-performance and not requiring the implementors to
block a kernel thread per connection.

To be honest I will choose 2 as well. But we are in not a 2 choose 1
situation. The main difference between we and netty/node.js is that ppl use
them to, write a server, what we do is, writing something ppl use to write
something like netty and node.js. So it is reasonable to think there’s
demand on a lower-level, synchronous api, despite the possible “drawbacks”
they might encounter.

Maybe we have some misunderstanding here. I’m not saying a synchronous api
that happens to be able to handle a vector of sockets in single call
without blocking anything, I’m saying a synchronous api that can just do
one simple thing, which is, read/write in a synchronous way despite block
or not, if it will block, just let them know by throwing an exception, the
api call itself, will not block anything that way.

Cheers,
Michael.

Hi Michael,

Hi Johannes

I think i need to clarify something: I’m ok with a asynchronous api that executes synchronously, for example if the api is something like [[ a. { b() } ; c() ]], executes as [[ a(); b(); c() ]], it is totally fine since it’s just synchronous api with syntactic sugar.

We actually have a synchronous implementation of the proposed API next to the DispatchIO one that we normally use. The synchronous one uses problem system calls and only services one request per thread. It's handy for unit testing and for specialised use-cases. The synchronous implementation only uses the following syscalls: open, close, read and write, that's it so nothing fancy.

I think even exposing these apis to user will be good. No need for fancy support just include it and it will be good enough.

I think i need to clarify something: I’m ok with a asynchronous api that executes synchronously, for example if the api is something like [[ a. { b() } ; c() ]], executes as [[ a(); b(); c() ]], it is totally fine since it’s just synchronous api

ie. you use write as a blocking system call because the file descriptor isn't set to be non-blocking.

Just as a side note: You won't be able to repro this issue by replacing the macOS `telnet` with the macOS `nc` (netcat) as netcat will only read more to the socket after it was able to write it. Ie. the implementation of standard macOS `nc` happens to make your implementation appear non-blocking. But the macOS provided telnet seems to do the right thing. You can use pbjnc (http://www.chiark.greenend.org.uk/~peterb/linux/pjbnc/\) if you prefer which also doesn't have the same bug as `nc`.

As I said both snippet of code are just sketches only for proof of concept. But I do missed on the kevent write one that’s for sure.

I'd guess that most programmers prefer an asynchronous API with callback (akin to Node.js/DispatchIO) to using the eventing mechanism directly and I was therefore assuming you wanted to build that from kevent() (which is what they're often used for). Nevertheless, kevent() won't make your programming model any nicer than asynchronous APIs and as I mentioned before you can build one from the other in a quite straightforward way. What we don't get from that is ordinary synchronous APIs that don't block kernel threads and that happens to be what most people would prefer eventually. Hence libdill/mill/venice and Zewo :).

Johannes, I totally agree with you. A asynchronous API is more intuitive and I agree with that. But since we are providing low level API for ppl like Zewo, Prefect, and Kitura, it is not right for us to assume their model of programming.

For libdill/mill/venice, even with green threads they will block when there’s nothing to do,

If you read in libdill/mill/venice, it will switch the user-level thread to _not_ block a kernel thread. That's the difference and that's what we can't achieve with Swift today (without using UB).

I’m quite confused on this one, since a green thread, if that’s what we think we were referring to, can not enter kernel (It can, but when it enters what happened is that the kernel thread associated with enters kernel).
So you can’t switch to another user-level thread to not block a kernel thread.
AFAIK all majority OS(Liunx, FreeBSD, Solaris….) adopted 1:1 threading model instead of n:m, not sure about Darwin but I think it applies to Darwin as well according to an old WWDC video (I could be wrong), hence any user threads (except for green threads) are in fact kernel threads. Since kevent and epoll are designed to block when they should, I don’t think anyone could avoid blocking something.

Yes, Linux, macOS, FreeBSD and so on offer only a 1:1 threading model from the OS (Windows I think has Fibers). But libmill/dill/venice implement something akin to user-level threads themselves, you don't need kernel support for that at all (to schedule them cooperatively). Check out the man pages of setjmp and longjmp. Or check out Go (goroutines), Lua (coroutines), ... These are all basically cooperatively scheduled threads.

In other words: With setjmp() you can make a snapshot of the current environment and with longjmp() you can replace the current environment with one that you previously saved. That's like cooperatively switching something like a user-level thread/coroutine/green thread.

Run for example the code in this stackoverflow example: Practical usage of setjmp and longjmp in C - Stack Overflow

This document explains it pretty well too: libdill.org

in fact all the example you listed above all uses events api internally. Hence I don’t think if an api will block a kernel thread is a good argument here.

kernel threads are a finite resource and most modern networking APIs try hard to only spawn a finite number of kernel threads way smaller than the number of connections handled concurrently. If you use Dispatch as your concurrency mechanism, your thread pool will have a maximum size of 64 threads by default on Darwin. (Sure you can spawn more using (NS)Thread from Foundation or pthreads or so)

Yes Kernel threads are finite resources especially in 1:1 model but I’m not sure how is it relevant. My concern on not include a synchronous API is that it make people impossible to write synchronous code, with server side swift tools, despite blocking or not, which they might want to. I’m not saying sync is better, I’m just saying we could give them a chance.

No one's taking anything away from you. Everything you have today will still be available. But I believe the APIs (which is what we're designing here) a web app uses should in today's world in Swift be asynchronous.

Of course to implement the asynchronous API, synchronous system calls will be used (eg. kevent/epoll). But the user-facing API that is currently proposed is async-only in order for it to be implementable in a performant way. If we were to put synchronous functions in the user-facing API, then we'll struggle to implement them in a performant way).

Imagine the function to write a HTTP body chunk looked like this:

  func writeBodyChunk(_ data: Data) throws -> Void

then the user can expect this to only return when the data has been written successfully and that the connection was dropped if it throws.
But the implementation now has a problem: What to do if we can't write the bytes immediately? The only option we have is block this very thread and wait until we have written the bytes. Then we can return and let the user know if the write worked or not.

Comparing this to

  func writeBodyChunk(_ data: Data, completion: (Error?) -> Void) -> Void

we can now register the attempt to write the data and move on with the calling thread. When the data has been written we invoke the completion handler and everything's good.

And even if such totally non-blocking programming model it will be expensive since the kernel is constantly scheduling a do-nothing-thread. (( if the io thread of a server-side application need to do something constantly despite there’s no user and no connection it sounds like a ghost story to me )).

what is the do-nothing-thread? The IO thread will only be scheduled if there's something to do and then normally the processing starts on that very thread. In systems like Netty they try very hard to reduce hopping between different threads and to only spawn a few. Node.js is the extreme which handles everything on one thread. It will be able to do thousands of connections with only one thread.

The kernel has no idea is a thread have anything to do unless it sleeps/enterKernel, unless a thread fits in these requirements, it will always scheduled by the kernel.

but that's exactly what epoll/kevent do. They enter the kernel and tell the kernel what the user-space needs next.

The thread is now scheduled only if an event that kevent/epoll are waiting for turns up. And when kevent/epoll then return, most of the time the user space handler is submitted as a callback.

I’m saying, if there exists a real non-blocking programming model, defined that by “never call any ‘wait’ system calls’, than any IO threads of that model must constantly poll the kernel, hence such thread _cannot_be_scheduled_on_demand since the thread itself has no idea if it has anything to do. The only way to have an IO thread to do know they have to do something, they will either need

1) An external listener call blocking event api and poke the IO thread on demand
2) The IO thread has to constantly poll the kernel
3) An external listener polls the kernel constantly and poke the IO thread when ready.
  
2 and 3 are the do-nothing-thread I’m referring to, they are running, polling, wasting kernel resources but not actually being productive (when there’s no connection).

Nothing is in a tight polling loop. We run handlers as long as we can and when all handlers have run, kevent/epoll is entered again, done. Often you just spawn as many threads as you have CPUs and all is good. These threads are mostly sitting in kevent/epoll and as soon as some file descriptor becomes readable/writable the respective handler is invoked.

That's what DispatchSources do, what Node.js does, what Netty does, ...

You should definitely check the return value of write(), it's very important. Even if positive you need to handle the case that it's less bytes than you wanted it to write. And if negative, the bytes are _lost_ which happens all the time with the current implementation.

Anyway, to fix the EAGAIN you'll need to ask kevent() when you can write next.

It was suppose to be a proof of concept sketch work. As mentioned in the comments of the code it was assuming to satisfy one single client. Now I’ve improved it so it handles multiple clients while remain synchronous and non blocking. EAGAIN is the only “error” will raise if you consider it as error but for me it’s part of the non blocking IO.

well, it means the write hasn't happened. You will need to do the write again and that is normally done when kevent/epoll tell you to. And that's what I mean by inversion of control.

Foundation/Cocoa is I guess the Swift standard library and they abandon synchronous&blocking APIs completely. I don't think we should create something different (without it being better) than what people are used to.

Again, there are two options for IO at the moment:
1) synchronous & blocking kernel threads
2) asynchronous/inversion of control & not blocking kernel threads

Even though I would love a synchronous programming model, I'd chose option (2) because the drawbacks of (1) are just too big. The designers of Foundation/Cocoa/Netty/Node.js/many more have made the same decision. Not saying all other options aren't useful but I'd like the API to be implementable with high-performance and not requiring the implementors to block a kernel thread per connection.

To be honest I will choose 2 as well. But we are in not a 2 choose 1 situation. The main difference between we and netty/node.js is that ppl use them to, write a server, what we do is, writing something ppl use to write something like netty and node.js. So it is reasonable to think there’s demand on a lower-level, synchronous api, despite the possible “drawbacks” they might encounter.

This is the HTTP group so people will only write web servers with it, the API that was proposed it definitely not meant to implement anything netty or node like. It's to implement web apps in Swift.

There is however also a Networking/Transport group which will be more low-level than this (I assume) and there we do need to consider the lower-level APIs. And those will contain blocking system calls, namely kevent/epoll (if it won't be based on top of DispatchSources/DispatchIO which do the eventing out of the box, obviously also implemented with kevent/epoll).

Maybe we have some misunderstanding here. I’m not saying a synchronous api that happens to be able to handle a vector of sockets in single call without blocking anything, I’m saying a synchronous api that can just do one simple thing, which is, read/write in a synchronous way despite block or not, if it will block, just let them know by throwing an exception, the api call itself, will not block anything that way.

there may well be a misunderstanding here. No one wants to take all synchronous APIs away from you. They are available in Swift today and will remain there tomorrow.

But we're trying to design a HTTP API that is implementable with reasonable performance and that I believe should be done by only offering async APIs.

Thanks,
  Johannes

···

On 1 Jun 2017, at 5:08 pm, Michael Chiu <hatsuneyuji@icloud.com> wrote: