Skip to content
This repository has been archived by the owner on Mar 30, 2024. It is now read-only.

Added support for Notifications that use DispatchSource #59

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

mlilback
Copy link

Using a DispatchSource is less wasteful of CPU power as per PostgreSQL documentation.

A parametrized init for Connection.Notification is needed so an application using Notifications does not need to import CPostgreSQL.

Fixed a compiler warning about an unused variable.

@codecov
Copy link

codecov bot commented Sep 16, 2017

Codecov Report

Merging #59 into master will decrease coverage by 2.04%.
The diff coverage is 76.92%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #59      +/-   ##
==========================================
- Coverage   82.33%   80.29%   -2.05%     
==========================================
  Files          10       10              
  Lines        1325     1142     -183     
  Branches      107        0     -107     
==========================================
- Hits         1091      917     -174     
+ Misses        229      225       -4     
+ Partials        5        0       -5
Impacted Files Coverage Δ
Sources/PostgreSQL/Error.swift 28.81% <100%> (-5.4%) ⬇️
Sources/PostgreSQL/Connection.swift 91.76% <76.31%> (-4.94%) ⬇️
Sources/PostgreSQL/Result.swift 74.25% <0%> (-9.86%) ⬇️
Sources/PostgreSQL/Bind/FieldType.swift 25.24% <0%> (-5.01%) ⬇️
Sources/PostgreSQL/Bind/Bind.swift 77.24% <0%> (-2.17%) ⬇️
Sources/PostgreSQL/Database.swift 100% <0%> (ø) ⬆️
Sources/PostgreSQL/ConnectionInfo.swift 100% <0%> (ø) ⬆️
Sources/PostgreSQL/Bind/Bind+Node.swift 94.79% <0%> (+0.14%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d57aa99...b127f46. Read the comment docs.

@vzsg
Copy link
Member

vzsg commented Sep 16, 2017

This is really nice, thank you! Personally I wouldn't mind eliminating the old solution for notifications altogether, in favor of using Dispatch.

What do you think, @sroebert?

@vzsg vzsg requested a review from sroebert September 16, 2017 12:43
@Joannis
Copy link

Joannis commented Sep 16, 2017

@tanner0101 I think this would remove the need for a pure swift driver.

/// - Returns: the dispatch socket to activate
/// - Throws: if fails to get the socket for the connection
public func makeListenDispatchSource(toChannel channel: String, queue: DispatchQueue, callback: @escaping (_ note: Notification?, _ err: Error?) -> Void) throws -> DispatchSourceRead {
guard let sock = Optional.some(PQsocket(self.cConnection)), sock >= 0
Copy link
Member

Choose a reason for hiding this comment

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

This seems a bit strange, why create an optional out of something not optional. Also, there is a callback, I would use this callback in case of an error. This way you can remove throws from this function. So it would change to:

let sock = PQsocket(cConnection)
guard sock >= 0 else {
    let error = PostgreSQLError(code: .ioError, reason: "failed to get socket for connection")
    callback(nil, error)
    return
}

Copy link
Author

Choose a reason for hiding this comment

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

The optional allows using it in a guard statement. I try to put all precondition guards at the top of the function. I'll change.

It can't just return because it returns a non-optional DispatchSource. That's why it throws. Better to explicitly generate an error instead of returning nil.

guard let sock = Optional.some(PQsocket(self.cConnection)), sock >= 0
else { throw PostgreSQLError(code: .ioError, reason: "failed to get socket for connection") }
let src = DispatchSource.makeReadSource(fileDescriptor: sock, queue: queue)
src.setEventHandler { [unowned 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.

Using unowned self can cause a crash. It might be better not to retain self here, so I would change it to:

src.setEventHandler { [weak self] in
    guard let strongSelf = self else {
        return
    }

    // Use strongSelf instead of self
}

Copy link
Author

Choose a reason for hiding this comment

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

Will do.

try self.execute("LISTEN \(channel)")
return src
}

/// Registers as a listener on a specific notification channel.
Copy link
Member

Choose a reason for hiding this comment

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

Considering the new method should be the way to go, I would mark the old function as deprecated.

/// - Parameter err: Any error while reading the notification. If not nil, the source will have been canceled
/// - Returns: the dispatch socket to activate
/// - Throws: if fails to get the socket for the connection
public func makeListenDispatchSource(toChannel channel: String, queue: DispatchQueue, callback: @escaping (_ note: Notification?, _ err: Error?) -> Void) throws -> DispatchSourceRead {
Copy link
Member

Choose a reason for hiding this comment

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

I would simply call this function listen(toChannel:queue:callback:)

Copy link
Author

Choose a reason for hiding this comment

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

Will do. I'd just copied Apple's naming convention. Better to use a swifty one.

@@ -152,6 +152,15 @@ public final class Connection: ConnInfoInitializable {
public let channel: String
public let payload: String?

/// initializer usable without knowledge of CPostgreSQL
/// required to allow unit testing of classes using Notifications
public init(pid: Int, channel: String, payload: String?) {
Copy link
Member

Choose a reason for hiding this comment

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

You do not need to make this init public. If you do @testable import, it should just work. This struct should not be initializable outside of this library.

Copy link
Author

Choose a reason for hiding this comment

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

I'll remove that change

/// - Parameter channel: the channel to register for
/// - Parameter queue: the queue to create the DispatchSource on
/// - Parameter callback: the callback
/// - Parameter note: The notification received from the database
Copy link
Member

Choose a reason for hiding this comment

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

The note and err parameters cannot be commented like this. It should be part of the callback comments. Also I would write out their full names instead of abbreviations.

Copy link
Author

Choose a reason for hiding this comment

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

Why? If you comment them that way, option clicking on the listen function shows them in a nested table underneath the callback parameter.

Copy link
Member

Choose a reason for hiding this comment

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

Ok sorry, didn’t know it worked like that.

@sroebert
Copy link
Member

sroebert commented Sep 18, 2017

Nice work, this is much better than the existing function with sleeping. This should simply replace the old one. I added some review comments.

Also have a look at the tests, as the changes are only covered 66%

@mlilback
Copy link
Author

mlilback commented Sep 27, 2017

To test the error handling code at 206-207 the test needs to close the connection. Then it crashes in the deinit because that tries to close it, too.

This will always crash because cConnection can't be set to nil. Any ideas on how to fix this so it doesn't crash? I see two options:

  1. make public private(set) var cConnection: CConnection and set to nil in close and check for nil before calling PQfinish.

  2. Add a Bool flag to know if it was closed.

@sroebert
Copy link
Member

sroebert commented Sep 28, 2017

I actually think the close function is not correct in this case. Close should check if it is connected and only close in that case, otherwise it should simply do nothing. So the validateConnection should not be there in my opinion.

And probably the same goes for the reset function.

@mlilback
Copy link
Author

PQfinish is documented as freeing the memory and "must not be used again". That means there is no way to query if the connection is open without a crash. So it comes back to my previous question. Make cConnection nullable or add a flag for the connection state?

Generally I'd want to make it optional, but that is an API breaking change.

@sroebert
Copy link
Member

Ok, yeah that is a tricky one. I would also go for making it optional. I know it is a breaking change, but at the same time, I doubt there will be many (if any) libraries out there, making direct use of this property.

What about making this property deprecated, changing it to a var that points to a new optional property, throwing a fatal error if it is nil.

@mlilback
Copy link
Author

mlilback commented Oct 2, 2017

reset() and close() were marked as throws because they called validateConnection(). The connection status isn't checked after the call to PQreset(). Therefore, an error is thrown if the connection is invalid (isn't that the reason reset() is called?) but not if the reset fails.

I think reset() should only throw an error if PQStatus() is CONNECTION_BAD after the call to PQreset(). Throwing an error if already closed will fail current tests. Thoughts?

Along the same line, I think close should not through an error if already closed. It is throwing an error if isConnection is false. That would mean it would not throw errors anymore, so should close() no longer be marked as throws, or keep it for compatibility?

@sroebert
Copy link
Member

sroebert commented Oct 3, 2017

Yes, that is exactly what I meant. I like the idea of throwing an error is reset does not properly reset.

About compatibility, I find it a bit hard, as it is a breaking change, but again, I doubt anyone is using this directly currently. And I don't like the idea of keeping it a throwing function when it does not. So in my opinion we should change it to not throwing.

…lid. reset() behaves no longer calls validateConnection(), now throws if connection was not properly reset. close() no longer marked throws as it doesn't actually throw anymore. listen() version using sleep deprecated.
@sroebert
Copy link
Member

sroebert commented Oct 9, 2017

Any idea what the problem with travis is? I see some message about brew and vapor.

@mlilback
Copy link
Author

Here are a couple of possible fixes: PowerShell/PowerShell#5062

Either add a homebrew environment variable, or move the location of brew update. Seems like an issue with a recent brew upgrade.

@mlilback
Copy link
Author

can someone merge this if it all good?

@natebird
Copy link
Collaborator

@mlilback - Can you update the .travis.yml file to move the brew update line above other brew commands? That should solve the failing tests. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants