Skip to content

Commit c1de89a

Browse files
authored
Make sure correct error is thrown, if server closes connection (#397)
1 parent ef3a00f commit c1de89a

File tree

2 files changed

+43
-13
lines changed

2 files changed

+43
-13
lines changed

Sources/PostgresNIO/New/Connection State Machine/ConnectionStateMachine.swift

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ struct ConnectionStateMachine {
203203
preconditionFailure("How can a connection be closed, if it was never connected.")
204204

205205
case .closed:
206-
preconditionFailure("How can a connection be closed, if it is already closed.")
206+
return .wait
207207

208208
case .authenticated,
209209
.sslRequestSent,
@@ -214,8 +214,8 @@ struct ConnectionStateMachine {
214214
.readyForQuery,
215215
.extendedQuery,
216216
.closeCommand:
217-
return self.errorHappened(.uncleanShutdown)
218-
217+
return self.errorHappened(.serverClosedConnection(underlying: nil))
218+
219219
case .closing(let error):
220220
self.state = .closed(clientInitiated: true, error: error)
221221
self.quiescingState = .notQuiescing
@@ -910,7 +910,7 @@ struct ConnectionStateMachine {
910910
// the error state and will try to close the connection. However the server might have
911911
// send further follow up messages. In those cases we will run into this method again
912912
// and again. We should just ignore those events.
913-
return .wait
913+
return .closeConnection(closePromise)
914914

915915
case .modifying:
916916
preconditionFailure("Invalid state: \(self.state)")
@@ -1034,16 +1034,16 @@ extension ConnectionStateMachine {
10341034
case .clientClosesConnection, .clientClosedConnection:
10351035
preconditionFailure("Pure client error, that is thrown directly in PostgresConnection")
10361036
case .serverClosedConnection:
1037-
preconditionFailure("Pure client error, that is thrown directly and should never ")
1037+
return true
10381038
}
10391039
}
10401040

10411041
mutating func setErrorAndCreateCleanupContextIfNeeded(_ error: PSQLError) -> ConnectionAction.CleanUpContext? {
1042-
guard self.shouldCloseConnection(reason: error) else {
1043-
return nil
1042+
if self.shouldCloseConnection(reason: error) {
1043+
return self.setErrorAndCreateCleanupContext(error)
10441044
}
10451045

1046-
return self.setErrorAndCreateCleanupContext(error)
1046+
return nil
10471047
}
10481048

10491049
mutating func setErrorAndCreateCleanupContext(_ error: PSQLError, closePromise: EventLoopPromise<Void>? = nil) -> ConnectionAction.CleanUpContext {
@@ -1060,13 +1060,15 @@ extension ConnectionStateMachine {
10601060
forwardedPromise = closePromise
10611061
}
10621062

1063-
self.state = .closing(error)
1064-
1065-
var action = ConnectionAction.CleanUpContext.Action.close
1066-
if case .uncleanShutdown = error.code.base {
1063+
let action: ConnectionAction.CleanUpContext.Action
1064+
if case .serverClosedConnection = error.code.base {
1065+
self.state = .closed(clientInitiated: false, error: error)
10671066
action = .fireChannelInactive
1067+
} else {
1068+
self.state = .closing(error)
1069+
action = .close
10681070
}
1069-
1071+
10701072
return .init(action: action, tasks: tasks, error: error, closePromise: forwardedPromise)
10711073
}
10721074
}

Tests/PostgresNIOTests/New/PostgresConnectionTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,34 @@ class PostgresConnectionTests: XCTestCase {
275275
}
276276
}
277277

278+
func testIfServerJustClosesTheErrorReflectsThat() async throws {
279+
let (connection, channel) = try await self.makeTestConnectionWithAsyncTestingChannel()
280+
281+
async let response = try await connection.query("SELECT 1;", logger: self.logger)
282+
283+
let listenMessage = try await channel.waitForUnpreparedRequest()
284+
XCTAssertEqual(listenMessage.parse.query, "SELECT 1;")
285+
286+
try await channel.testingEventLoop.executeInContext { channel.pipeline.fireChannelInactive() }
287+
try await channel.testingEventLoop.executeInContext { channel.pipeline.fireChannelUnregistered() }
288+
289+
do {
290+
_ = try await response
291+
XCTFail("Expected to throw")
292+
} catch {
293+
XCTAssertEqual((error as? PSQLError)?.code, .serverClosedConnection)
294+
}
295+
296+
// retry on same connection
297+
298+
do {
299+
_ = try await connection.query("SELECT 1;", logger: self.logger)
300+
XCTFail("Expected to throw")
301+
} catch {
302+
XCTAssertEqual((error as? PSQLError)?.code, .serverClosedConnection)
303+
}
304+
}
305+
278306
struct TestPrepareStatement: PostgresPreparedStatement {
279307
static var sql = "SELECT datname FROM pg_stat_activity WHERE state = $1"
280308
typealias Row = String

0 commit comments

Comments
 (0)