New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GCD based, Asynchronous Implementation of the API #96

Closed
wants to merge 17 commits into
base: develop
from

Conversation

Projects
None yet
7 participants
@helje5
Contributor

helje5 commented Nov 10, 2017

This PR contains a GCD DispatchIO based implementation of the API and presumably should scale significantly better than the synchronous one. Throughput should be somewhat worse for obvious reasons.

I also setup a small test server demonstrating the API: http-testserver. Of special interest is the slow handler, which does the typical sleep() async demo.

Unlike Noze.io or Node.js it does not use a single network processing queue, but rather a set of base queues as suggested by Johannes. To support that, the handler API has been enhanced with the queue argument. All API calls of a handler need to be dispatched back to that (but the handler itself runs on the same queue, so the trivial cases require no extra work).

This implementation is supposed to properly support back pressure and pipelining.

Caveats:

  • tests don't work
  • little testing has been done, probably has tons of bugs ;-)

helje5 added some commits Nov 10, 2017

Drop PoCSocket, and the HTTPStreamingParser
The little socket stuff we need we can do directly in the
code. No need to wrap such basics.

The StreamingParser was some combination of
response writer and parser. Those are really
distinct things (the writer doesn't really
need to know about the parser, and the reverse).
Add `queue` argument to handler
Similar to PR #86. In this case it is non-optional,
that thing being a concrete implementation.

All API functions need to run on the given queue. But
since the handler itself also runs on that queue, it
only needs to be done if the handler actually dispatches
to a different queue.

This is an optimization to avoid excessive dispatching
in async environments. (if API calls could be issued
from arbitrary queues, they would always need to be
queued to the handler queue. which is possible but
pretty expensive).

P.S.: The argument is necessary because there is
nothing like `DispatchQueue.current` in GCD anymore.
Drop `HTTPResponseWriter` as a protocol
This is a concrete implementation. Unless we do anything as
protocols, there is no need to have this as one.
Add asynchronous, GCD based HTTP server
HTTP server which uses GCD to handle async I/O.
Add a very basic imp of connectionCount
... on Darwin. Use OSAtomicIncrement/Dec.
Make tests compile (but they won't work!)
Presumably tests would need to be done differently in an async
setup. Not sure how much we could reuse here.
Fix Linux compile
Import Dispatch explicitly, some SOCK_STREAM weirdness.
Drop `print` in start
... debug log, remove.
@carlbrown

carlbrown suggested changes Nov 10, 2017 edited

I like this approach, but we at least need to get the tests running first before we merge it. I can help with that, if you think more than one of us can work on it without stepping all over each other.

@helje5

This comment has been minimized.

Show comment
Hide comment
@helje5

helje5 Nov 10, 2017

Contributor

It would be awesome if you would get the tests working again. I don't plan to work on such any time soon, so feel free to do anything you want!

Adjusting the tests might be challenging, because, well, they need to be asynchronous. For Noze.io I have a helper class which tries to deal with the asynchronicity, not sure whether it might help you or not (or even whether this is the correct way to test that stuff): NozeIOTestCase.swift

The other hurdle is that I made the HTTPResponseWriter concrete (i.e. it is not a protocol anymore). I suppose that makes some mocking hard. We could change it back to a protocol, but I think it was suggested that we should avoid them for performance reasons.

Contributor

helje5 commented Nov 10, 2017

It would be awesome if you would get the tests working again. I don't plan to work on such any time soon, so feel free to do anything you want!

Adjusting the tests might be challenging, because, well, they need to be asynchronous. For Noze.io I have a helper class which tries to deal with the asynchronicity, not sure whether it might help you or not (or even whether this is the correct way to test that stuff): NozeIOTestCase.swift

The other hurdle is that I made the HTTPResponseWriter concrete (i.e. it is not a protocol anymore). I suppose that makes some mocking hard. We could change it back to a protocol, but I think it was suggested that we should avoid them for performance reasons.

@carlbrown carlbrown referenced this pull request Nov 10, 2017

Closed

TLS integration #93

@helje5

This comment has been minimized.

Show comment
Hide comment
@helje5

helje5 Nov 10, 2017

Contributor

BTW: I also still wonder whether we would want to allow multiple implementations, or whether that is just bad for a basic core module. E.g. I can imagine that we might be able to use generics to efficiently do something like Apache MPMs, for example:

let asyncServer = HTTPServer<DispatchMPM>(port: ...)

and

let syncServer = HTTPServer<ThreadedMPM>(port: ...)

Both have their unique advantages and disadvantages. But maybe this would make it too complex, not sure.

Contributor

helje5 commented Nov 10, 2017

BTW: I also still wonder whether we would want to allow multiple implementations, or whether that is just bad for a basic core module. E.g. I can imagine that we might be able to use generics to efficiently do something like Apache MPMs, for example:

let asyncServer = HTTPServer<DispatchMPM>(port: ...)

and

let syncServer = HTTPServer<ThreadedMPM>(port: ...)

Both have their unique advantages and disadvantages. But maybe this would make it too complex, not sure.

@carlbrown

This comment has been minimized.

Show comment
Hide comment
@carlbrown

carlbrown Nov 10, 2017

Contributor

Adjusting the tests might be challenging, because, well, they need to be asynchronous. For Noze.io I have a helper class which tries to deal with the asynchronicity, not sure whether it might help you or not

Ok. Let me go to work on that, and we'll see how far I get.

Contributor

carlbrown commented Nov 10, 2017

Adjusting the tests might be challenging, because, well, they need to be asynchronous. For Noze.io I have a helper class which tries to deal with the asynchronicity, not sure whether it might help you or not

Ok. Let me go to work on that, and we'll see how far I get.

//
// See http://swift.org/LICENSE.txt for license information
// Created by Helge Hess on 22.10.17.

This comment has been minimized.

@anayini

anayini Nov 10, 2017

Contributor

We probably want to keep the swift.org license stuff, right?

@anayini

anayini Nov 10, 2017

Contributor

We probably want to keep the swift.org license stuff, right?

This comment has been minimized.

@helje5

helje5 Nov 11, 2017

Contributor

Nah, I wrote it. It should have my name on it 😬 License is fine though.

@helje5

helje5 Nov 11, 2017

Contributor

Nah, I wrote it. It should have my name on it 😬 License is fine though.

This comment has been minimized.

@ogres

ogres Nov 16, 2017

This is probably not a good idea.
None of the other files in this repo have neither this nor "Copyright © 2017 SomeCompany..."
Do you think each time someone changes this file, they should add "Changed by..." to it as well? We have a git history for it, so your work won't be forgotten :)

@ogres

ogres Nov 16, 2017

This is probably not a good idea.
None of the other files in this repo have neither this nor "Copyright © 2017 SomeCompany..."
Do you think each time someone changes this file, they should add "Changed by..." to it as well? We have a git history for it, so your work won't be forgotten :)

Show outdated Hide outdated Sources/HTTP/HTTPServer.swift Outdated
Show outdated Hide outdated Sources/HTTP/HTTPServer.swift Outdated
Show outdated Hide outdated Sources/HTTP/HTTPServer.swift Outdated
Show outdated Hide outdated Sources/HTTP/HTTPServer.swift Outdated
guard !data.isEmpty else { return nil }
// TODO: reuse header values by matching the data via memcmp(), maybe first
// switch on length, compare c0
guard let s = String(data: data, encoding: .utf8) else { return nil }

This comment has been minimized.

@anayini

anayini Nov 10, 2017

Contributor

Can put these both in one guard if you like

@anayini

anayini Nov 10, 2017

Contributor

Can put these both in one guard if you like

This comment has been minimized.

@helje5

helje5 Nov 11, 2017

Contributor

Why would I do this? Those are separate things. This one doesn't make sense for me.

@helje5

helje5 Nov 11, 2017

Contributor

Why would I do this? Those are separate things. This one doesn't make sense for me.

private var writers = [ HTTPResponseWriter ]()
// FIXME: make this a linked list
internal init(fd : Int32,

This comment has been minimized.

@anayini

anayini Nov 10, 2017

Contributor

Nit: usually use newline before first arg when splitting args up on multiple lines

@anayini

anayini Nov 10, 2017

Contributor

Nit: usually use newline before first arg when splitting args up on multiple lines

This comment has been minimized.

@helje5

helje5 Nov 13, 2017

Contributor

This actually sounds a little weird, is there a style guide or standard lib example which does it like this?

@helje5

helje5 Nov 13, 2017

Contributor

This actually sounds a little weird, is there a style guide or standard lib example which does it like this?

This comment has been minimized.

@anayini

anayini Nov 13, 2017

Contributor

Hm - yeah maybe this is just a thing that we do in my company's internal style guide.

I actually don't even know, is there an Apple approved style guide? Couldn't find one with a quick search.

@anayini

anayini Nov 13, 2017

Contributor

Hm - yeah maybe this is just a thing that we do in my company's internal style guide.

I actually don't even know, is there an Apple approved style guide? Couldn't find one with a quick search.

This comment has been minimized.

@helje5

helje5 Nov 13, 2017

Contributor

You should find out if you propose such changes ;->

@helje5

helje5 Nov 13, 2017

Contributor

You should find out if you propose such changes ;->

This comment has been minimized.

@anayini

anayini Nov 13, 2017

Contributor

This comment has been minimized.

@anayini

anayini Nov 13, 2017

Contributor

I guess they do this only if the line would have been too long without, in which case they start the arguments on the next line.

I can go check to see if other projects are adhering to some style guide somewhere. Eventually we will probably run into a bunch of different tiny things like this.

@anayini

anayini Nov 13, 2017

Contributor

I guess they do this only if the line would have been too long without, in which case they start the arguments on the next line.

I can go check to see if other projects are adhering to some style guide somewhere. Eventually we will probably run into a bunch of different tiny things like this.

This comment has been minimized.

@helje5

helje5 Nov 13, 2017

Contributor

This looks really ugly, but fair enough. On the pro side, it uses a proper 2-space indent :-> Maybe we should have an issue to track what style we want to do?

Just to be clear, I do care a lot about style (and know very well that mine is the only correct one), but I think that in a project like this it should just match the mainline - please never argue with we do in my company's internal style guide but with Swift does it like this, check [here].

@helje5

helje5 Nov 13, 2017

Contributor

This looks really ugly, but fair enough. On the pro side, it uses a proper 2-space indent :-> Maybe we should have an issue to track what style we want to do?

Just to be clear, I do care a lot about style (and know very well that mine is the only correct one), but I think that in a project like this it should just match the mainline - please never argue with we do in my company's internal style guide but with Swift does it like this, check [here].

This comment has been minimized.

@anayini

anayini Nov 13, 2017

Contributor

👍 . Will file an issue.

@anayini

anayini Nov 13, 2017

Contributor

👍 . Will file an issue.

This comment has been minimized.

@anayini

anayini Nov 13, 2017

Contributor

Wasn't really arguing with we do this in my company's internal style guide. Only that I'm used to that style guide so I made the mistake of marking this as wrong here.

@anayini

anayini Nov 13, 2017

Contributor

Wasn't really arguing with we do this in my company's internal style guide. Only that I'm used to that style guide so I made the mistake of marking this as wrong here.

@helje5

This comment has been minimized.

Show comment
Hide comment
@helje5

helje5 Nov 12, 2017

Contributor

I enabled Issues on my fork https://github.com/ZeeZide/http/issues and marked those which may be necessary to merge this PR with a Required for PR label.

Contributor

helje5 commented Nov 12, 2017

I enabled Issues on my fork https://github.com/ZeeZide/http/issues and marked those which may be necessary to merge this PR with a Required for PR label.

Gets the simple end-to-end tests running on Mac
These tests pass on Linux if run one at a time,
but there's some issue so if you run them all,
the first test passes and the second one hangs. 

Still need to fix that.
@carlbrown

This comment has been minimized.

Show comment
Hide comment
@carlbrown

carlbrown Nov 15, 2017

Contributor

@helje5 I added a PR against your fork that has a batch of changes for getting tests to run on the Mac. (There will be another one once I find/fix a bug on Linux).

Contributor

carlbrown commented Nov 15, 2017

@helje5 I added a PR against your fork that has a batch of changes for getting tests to run on the Mac. (There will be another one once I find/fix a bug on Linux).

Merge pull request #13 from carlbrown/ZeeZide/feature/gcd-async-server
Gets the simple end-to-end tests running on Mac

@tanner0101 tanner0101 referenced this pull request Dec 6, 2017

Open

async server #106

helje5 added some commits Dec 7, 2017

Do not use seperate/concurrent queue for accept
... this was utter non-sense, no idea why I did this :-)
The listen socket is already non-blocking and it already
deals w/ nio accept. 🤦‍♀️
Move base-queue selection to optional closure
Remove the specific algorithm for selecting a base
queue and use an optional closure to return one, as
suggested by Johannes.

By default this will not assign a target queue and
let GCD deal with this. See related thread on
PR #96.
@GeorgeLyon

This comment has been minimized.

Show comment
Hide comment
@GeorgeLyon

GeorgeLyon Dec 11, 2017

Don't want to derail this PR, but I think leaking the DispatchQueue the server uses into the handler is mistake. Read and write operations on a descriptor don't need to be synchronized as far as I am aware. An example where the server and handler synchronization primitives are separate can be found here: https://github.com/GeorgeLyon/Server

GeorgeLyon commented Dec 11, 2017

Don't want to derail this PR, but I think leaking the DispatchQueue the server uses into the handler is mistake. Read and write operations on a descriptor don't need to be synchronized as far as I am aware. An example where the server and handler synchronization primitives are separate can be found here: https://github.com/GeorgeLyon/Server

@helje5

This comment has been minimized.

Show comment
Hide comment
@helje5

helje5 Dec 11, 2017

Contributor

Don't want to derail this PR, but I think leaking the DispatchQueue the server uses into the handler is mistake.

It is an optimisation. I don't really like it either, but the alternatives seem too expensive to me (require the response-writer to be free-threaded).

Read and write operations on a descriptor don't need to be synchronized as far as I am aware

Yeah, but I don't see how that helps us in any way. The goal here is to allow for asynchronous handlers which support back pressure. That implies more book-keeping. Which needs to be synchronised.

P.S.: Please lets continue the discussion on-list.

Contributor

helje5 commented Dec 11, 2017

Don't want to derail this PR, but I think leaking the DispatchQueue the server uses into the handler is mistake.

It is an optimisation. I don't really like it either, but the alternatives seem too expensive to me (require the response-writer to be free-threaded).

Read and write operations on a descriptor don't need to be synchronized as far as I am aware

Yeah, but I don't see how that helps us in any way. The goal here is to allow for asynchronous handlers which support back pressure. That implies more book-keeping. Which needs to be synchronised.

P.S.: Please lets continue the discussion on-list.

helje5 added some commits Dec 18, 2017

Replace ugly type dependency w/ closure
The connection doesn't need to know about the
server anymore. Instead we pass in a closure to
be called when the connection tears down.

Note: there is an intentional cycle between
server and connection. It will be broken when
the connection is done.
@helje5

This comment has been minimized.

Show comment
Hide comment
@helje5
Contributor

helje5 commented Mar 1, 2018

@helje5 helje5 closed this Mar 1, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment