Skip to content

Make backend key data optional #296

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

Merged
merged 10 commits into from
Jun 3, 2022
Merged

Make backend key data optional #296

merged 10 commits into from
Jun 3, 2022

Conversation

rausnitz
Copy link
Contributor

@rausnitz rausnitz commented Jun 2, 2022

This allows the ConnectionStateMachine to be in state readyForQuery even if the BackendKeyData has not been received.

The use case that prompted this change is integration with Amazon RDS Proxy. When connecting to Postgres via RDS Proxy, any query fails with error PSQLError(base: PostgresNIO.PSQLError.Base.unexpectedBackendMessage(.readyForQuery(.idle))).

According to the RDS Proxy docs, one limitation of RDS Proxy is "For PostgreSQL, RDS Proxy doesn't currently support canceling a query from a client." The Postgres docs explain BackendKeyData's purpose as: "This message provides secret-key data that the frontend must save if it wants to be able to issue cancel requests later. The frontend should not respond to this message, but should continue listening for a ReadyForQuery message."

So, it seems that RDS Proxy for Postgres has no use for BackendKeyData and does not provide it.

Allowing nil for BackendKeyData in this function resolves the PSQLError error we were seeing.

@0xTim 0xTim requested a review from fabianfett June 3, 2022 07:22
Copy link
Collaborator

@fabianfett fabianfett left a comment

Choose a reason for hiding this comment

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

Thanks for bringing this up! I think this is something that we should definitely support. However, I think, we should make all of this way more explicit than currently proposed:

  1. We should add a setting to PostgresConnection.Configuration.Conntection that allow connections to not provide BackendKeyData. A possible name should be: allowNotProvidingBackendKeyData. By default this property should be set to false.
  2. We need to inject the value of the property into the StateMachine.
  3. If we receive readyForQuery without having received backendKeyData before, we should check, if the property allowNotProvidingBackendKeyData is true and only progress in this case.
  4. We need unit test cases that cover both flows.

Is this something you feel comfortable tackling? I'm here to help you at any step on the way.

@fabianfett fabianfett added the enhancement New feature or request label Jun 3, 2022
@rausnitz
Copy link
Contributor Author

rausnitz commented Jun 3, 2022

Can do. I'll work on those changes. Thanks!

@rausnitz
Copy link
Contributor Author

rausnitz commented Jun 3, 2022

I made some changes so that connections must provide BackendKeyData by default. If this approach is acceptable, I'll add unit tests.

@rausnitz rausnitz requested a review from fabianfett June 3, 2022 13:02
@rausnitz
Copy link
Contributor Author

rausnitz commented Jun 3, 2022

Actually, handling this in the unit tests was straightforward, so I went ahead and updated it.

Copy link
Collaborator

@fabianfett fabianfett left a comment

Choose a reason for hiding this comment

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

This already looks really good. Don't get distracted by the amount of code comments... This is awesome work!

Would you mind addressing the comments and add a test case for the new code path in ConnectionStateMachineTests.swift?

Two examples tests are here:

    func testBackendKeyAndParameterStatusReceivedAfterAuthenticated() {
        var state = ConnectionStateMachine(.authenticated(nil, [:]))
        
        XCTAssertEqual(state.backendKeyDataReceived(.init(processID: 2730, secretKey: 882037977)), .wait)
        
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "DateStyle", value: "ISO, MDY")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "application_name", value: "")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "server_encoding", value: "UTF8")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "integer_datetimes", value: "on")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "client_encoding", value: "UTF8")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "TimeZone", value: "Etc/UTC")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "is_superuser", value: "on")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "server_version", value: "13.1 (Debian 13.1-1.pgdg100+1)")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "session_authorization", value: "postgres")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "IntervalStyle", value: "postgres")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "standard_conforming_strings", value: "on")), .wait)
        
        XCTAssertEqual(state.readyForQueryReceived(.idle), .fireEventReadyForQuery)
    }
    
    func testReadyForQueryReceivedWithoutBackendKeyAfterAuthenticated() {
        var state = ConnectionStateMachine(.authenticated(nil, [:]))
        
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "DateStyle", value: "ISO, MDY")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "application_name", value: "")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "server_encoding", value: "UTF8")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "integer_datetimes", value: "on")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "client_encoding", value: "UTF8")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "TimeZone", value: "Etc/UTC")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "is_superuser", value: "on")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "server_version", value: "13.1 (Debian 13.1-1.pgdg100+1)")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "session_authorization", value: "postgres")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "IntervalStyle", value: "postgres")), .wait)
        XCTAssertEqual(state.parameterStatusReceived(.init(parameter: "standard_conforming_strings", value: "on")), .wait)
        
        XCTAssertEqual(state.readyForQueryReceived(.idle),
                       .closeConnectionAndCleanup(.init(action: .close, tasks: [], error: PSQLError.unexpectedBackendMessage(.readyForQuery(.idle)), closePromise: nil)))
    }

Again, thanks a lot for driving this forward...

@codecov-commenter
Copy link

codecov-commenter commented Jun 3, 2022

Codecov Report

Merging #296 (d2ee24c) into main (2825829) will increase coverage by 0.00%.
The diff coverage is 66.66%.

@@           Coverage Diff           @@
##             main     #296   +/-   ##
=======================================
  Coverage   43.91%   43.91%           
=======================================
  Files         115      115           
  Lines        9678     9680    +2     
=======================================
+ Hits         4250     4251    +1     
- Misses       5428     5429    +1     
Flag Coverage Δ
unittests 43.91% <66.66%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...es/PostgresNIO/Connection/PostgresConnection.swift 17.14% <33.33%> (+0.14%) ⬆️
...nection State Machine/ConnectionStateMachine.swift 57.53% <75.00%> (ø)
...urces/PostgresNIO/New/PostgresChannelHandler.swift 61.14% <100.00%> (ø)

@fabianfett
Copy link
Collaborator

This looks really good now. Only thing left is merge of main into your branch!

@rausnitz rausnitz requested a review from fabianfett June 3, 2022 17:25
@rausnitz
Copy link
Contributor Author

rausnitz commented Jun 3, 2022

This looks really good now. Only thing left is merge of main into your branch!

Done!

Copy link
Collaborator

@fabianfett fabianfett left a comment

Choose a reason for hiding this comment

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

One Nit remaining...

@rausnitz rausnitz requested a review from fabianfett June 3, 2022 21:50
Copy link
Collaborator

@fabianfett fabianfett left a comment

Choose a reason for hiding this comment

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

Awesome patch @rausnitz! Thanks a ton!

@fabianfett fabianfett merged commit d648c5b into vapor:main Jun 3, 2022
@rausnitz
Copy link
Contributor Author

rausnitz commented Jun 4, 2022

Thanks for the thorough and fast reviews @fabianfett!

@fabianfett fabianfett added this to the 1.11.0 milestone Jun 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants