-
Notifications
You must be signed in to change notification settings - Fork 42
Remove BlueSocket dependency #39
Remove BlueSocket dependency #39
Conversation
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
+1 to the big picture, a few comments inline. |
@ianpartridge I don't see comments in line? |
Er, they vanished. Weird. I'll go through again... |
There was a problem hiding this 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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just return recv(...)
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use Data
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the _ =
necessary?
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 throw
ing 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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call this socket
?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ignores failure...
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public class BlueSocketSimpleServer: CurrentConnectionCounting { | ||
/// Socket to listen on for connections | ||
private let serverSocket: Socket | ||
public class SimpleSocketSimpleServer: CurrentConnectionCounting { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too many simples? 🙂
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this sensible?
There was a problem hiding this comment.
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.
There was a problem hiding this 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)) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code can be a bit cleaner if you use getaddrinfo
https://github.com/vapor/vapor/blob/beta/Sources/TCP/Socket/Socket%2BBind.swift#L7-L40
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
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.
-Carl |
@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) |
Ok - I'm going to merge this as-is for now, and we can move further work to another PR. |
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 usingDispatchData
as discussed in #37 - specifically the following part of the conversation: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