Skip to content
This repository has been archived by the owner on Nov 23, 2021. It is now read-only.

Remove BlueSocket dependency #39

Merged
merged 4 commits into from
Sep 8, 2017
Merged

Remove BlueSocket dependency #39

merged 4 commits into from
Sep 8, 2017

Conversation

carlbrown
Copy link
Contributor

@carlbrown carlbrown commented Aug 31, 2017

I put together a simple (less than 150 line) replacement for BlueSocket for use in Proof of Concepts and Unit Tests that's much easier to understand (since it only needs to do IPv4 TCP server-side bound to all IP addresses on the host). This will let @tanner0101 (and anyone else who needs to) use the repo without needing to mess with any dependencies.

Note that there are features that BlueSocket had that this doesn't support (like listening only to one network interface and leaving other ones alone). I largely consider that a feature. If there are specific things that people need/want, I'll be happy to discuss implementing them in the simpler model.

This PR also enables the ability to change the API for this repo from using (NS)Data to using DispatchData as discussed in #37 - specifically the following part of the conversation:

Part of this is because BlueSocket (that I really want to replace with something much simpler) explicitly reads/writes all socket data in NSData format.

That should be a non-reason. I think very few people care what BlueSocket does. Just drop it and use something else.

Obviously, if we're going to change the HTTP API to use DispatchData, we need to discuss it, so that's not included here. This just makes it possible.

This (hopefully) resolves the root problem with #25 and removes the confusing (but unfortunately expected) errors discussed here: https://lists.swift.org/pipermail/swift-server-dev/Week-of-Mon-20170529/000537.html

call `activate()` to force cleanup

fix typo in getsockname on Linux

working on Linux

don't let broken socket kill the server

Split off SimpleServerSocket to its own file

pass port along
@carlbrown carlbrown mentioned this pull request Aug 31, 2017
4 tasks
@ianpartridge
Copy link
Contributor

+1 to the big picture, a few comments inline.

@carlbrown
Copy link
Contributor Author

@ianpartridge I don't see comments in line?

@ianpartridge
Copy link
Contributor

Er, they vanished. Weird. I'll go through again...

Copy link
Contributor

@ianpartridge ianpartridge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments inline - this also needs running through SwiftLint.

internal func socketRead(into readBuffer: inout UnsafeMutablePointer<Int8>, maxLength:Int) throws -> Int {
//let readBuffer: UnsafeMutablePointer<CChar> = UnsafeMutablePointer<CChar>.allocate(capacity: maxLength)
readBuffer.initialize(to: 0x0)
let read = recv(self.socketfd, readBuffer, maxLength, Int32(0))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just return recv(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like putting a breakpoint on the return while troubleshooting and being able to see/print the value. Harder to do if there's no local var (and the optimizer should make them perform equivalently).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can set a breakpoint then step in/out a few times until recv() returns, but OK.


internal private(set) var isConnected = false

internal func socketRead(into readBuffer: inout UnsafeMutablePointer<Int8>, maxLength:Int) throws -> Int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use Data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #37 - This makes it possible to measure Data vs DispatchData, which was part of the goal of this exercise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK so it's a temporary thing - cool.

return read
}

@discardableResult internal func socketWrite(from buffer: UnsafeRawPointer, bufSize: Int) throws -> Int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this throws it would be better to check the return from send() and throw if <1.

//print("Shutting down socket \(self.socketfd)")
if self.isListening || self.isConnected {
//print("Shutting down socket")
_ = shutdown(self.socketfd, Int32(SHUT_RDWR))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the _ = necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It generates a warning if you ignore the result. It's not marked @discarable in Glibc (or Darwin or both)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to discard it then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should throw if the return != 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I had been avoiding throwing if shutdown(2) fails is because it shouldn't be an error if shutdownAndClose gets called on an already-closed-and-invalidated socket. Does that seem reasonable?

}

internal func acceptClientConnection() throws -> SimpleServerSocket {
let retVal = SimpleServerSocket()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call this socket?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not, though. It only implements a small subset of the socket(2) APIs. Which is all we need for this application, but it's not a general purpose Socket object.

}

//In a perfect world, we wouldn't have to clean this all up explicitly,
// but KDE/heaptrack informs us we're in far from a perfect world

if !(self.readerSource?.isCancelled ?? true) {
self.readerSource?.cancel()
#if os(Linux)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mysterious... Why all the #if? Needs a comment at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'l comment it. Basically, macOS >= 10.12 needs one implementation, and everything else needs a different one, but I couldn't figure out how to combine the #if os() check with the #available one.

} catch {
fatalError("Failed to set socket as non-blocking")
}
socket?.setBlocking(mode: true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ignores failure...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I'll do another pass with throwing.

let data = Data(bytes: readBuffer.bytes.assumingMemoryBound(to: Int8.self), count: readBuffer.length)
if strongSelf.socket?.socketfd ?? -1 > 0 {
let maxLength: Int = Int(strongSelf.readerSource?.data ?? 10000)
var readBuffer: UnsafeMutablePointer<Int8> = UnsafeMutablePointer<Int8>.allocate(capacity: maxLength)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we using Data any more?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See conversation with @helje5 in #37

public class BlueSocketSimpleServer: CurrentConnectionCounting {
/// Socket to listen on for connections
private let serverSocket: Socket
public class SimpleSocketSimpleServer: CurrentConnectionCounting {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many simples? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair. I'll fix in the next commit


/// The port we're listening on. Used primarily to query a randomly assigned port during XCTests
public var port: Int {
return Int(serverSocket.listeningPort)
}

/// Tuning parameter to set the number of queues
private var queueMax: Int
private var queueMax: Int = 4 //sensible default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this sensible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because experimentally (with BlueSocket, so I might need to change it once I run this implementation through load testing), that's the smallest number I could use and still pull a webpage in a browser while a https://github.com/carlbrown/SwiftServerComparison run was in progress.

Copy link
Member

@tanner0101 tanner0101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work :)

//print("Shutting down socket \(self.socketfd)")
if self.isListening || self.isConnected {
//print("Shutting down socket")
_ = shutdown(self.socketfd, Int32(SHUT_RDWR))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should throw if the return != 0


internal func bindAndListen(on port: Int, maxBacklogSize: Int32 = 10000) throws {
#if os(Linux)
self.socketfd = socket(Int32(AF_INET), Int32(SOCK_STREAM.rawValue), Int32(IPPROTO_TCP))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we set some internal constants in vapor to simplify areas like this https://github.com/vapor/vapor/blob/beta/Sources/TCP/Utilties/Utilities.swift#L28-L34

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wow. I love the touchup of SOCK_STREAM. @ianpartridge, is it okay with you if I mimic https://github.com/vapor/vapor/blob/beta/Sources/TCP/Utilties/Utilities.swift#L32 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, anything that makes things more readable is good.

}

#if os(Linux)
var addr = sockaddr_in(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So with getaddrinfo() you don't have to break out #if OS() there? Is that right? That's handy.


/// Called by the parser to let us know that a response has started being created
public func responseBeginning() {
self.socketWriterQueue?.async { [weak self] in
self.socketWriterQueue.async { [weak self] in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all the calls to async here will add quite a bit of overhead. Can we not assume/require that all interaction done w/ the socket be done on the same serial queue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is to make sure that we can't get deadlocks. I've had a situation in testing where a read pushed a chunk of data into the pipeline, and it called something that wanted to write, but the write couldn't happen until the read completed, and so it all hung (this was on a future implementation in the context of SSL where OpenSSL's need to keep global state forces a lock around read/write). Maybe I'm just being paranoid. What do you think?

@carlbrown
Copy link
Contributor Author

Ugh @tanner0101 - I pushed a patch to address Ian's comments, and now yours show as outdated and my inline replies aren't appearing above. I wasn't ignoring you. I'm replying both here and inline.

  1. the reason I had been avoiding throwing if shutdown(2) fails is because it shouldn't be an error if shutdownAndClose gets called on an already-closed-and-invalidated socket. Does that seem reasonable?
  2. Oh, wow. I love the touchup of SOCK_STREAM. @ianpartridge, is it okay with you if I mimic https://github.com/vapor/vapor/blob/beta/Sources/TCP/Utilties/Utilities.swift#L32 ?
  3. So with getaddrinfo() you don't have to break out #if OS() there? Is that right?
  4. My concern here is to make sure that we can't get deadlocks. I've had a situation in testing where a read pushed a chunk of data into the pipeline, and it called something that wanted to write, but the write couldn't happen until the read completed, and so it all hung (this was on a future implementation in the context of SSL where OpenSSL's need to keep global state forces a lock around read/write). Maybe I'm just being paranoid. What do you think?

-Carl

@seabaylea
Copy link
Member

@carlbrown as this touches quite a few files, is this ready to merge as-is with the SOCK_STREAM etc. constants added later? (so it doesn't block changes to the rest of the codebase)

@seabaylea
Copy link
Member

Ok - I'm going to merge this as-is for now, and we can move further work to another PR.

@seabaylea seabaylea merged commit 6b5b27e into swift-server:develop Sep 8, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants