Skip to content

Commit 4e9f88b

Browse files
Scope aliveness checks to undrained connections
1 parent 2f1f041 commit 4e9f88b

File tree

2 files changed

+76
-69
lines changed

2 files changed

+76
-69
lines changed

source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.md

Lines changed: 75 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ interface Connection {
220220
* - "pending": The Connection has been created but has not yet been established. Contributes to
221221
* totalConnectionCount and pendingConnectionCount.
222222
*
223-
* - "pending response": The Connection is attempting to discard a response for an operation where the socket timed
223+
* - "pending response": The Connection is attempting to discard a response for an operation where the socket timed
224224
* out. This state is only used when CSOT is enabled and maxTimeMS was added to the command.
225225
*
226226
* - "available": The Connection has been established and is waiting in the pool to be checked
@@ -358,7 +358,7 @@ interface ConnectionPool {
358358
/**
359359
* Mark all current Connections as stale, clear the WaitQueue, and mark the pool as "paused".
360360
* No connections may be checked out or created in this pool until ready() is called again.
361-
* interruptInUseConnections specifies whether the pool will force interrupt "in use" connections as part of the clear.
361+
* interruptInUseConnections specifies whether the pool will force interrupt "in use" connections as part of the clear.
362362
* Default false.
363363
*/
364364
clear(interruptInUseConnections: Optional<Boolean>): void;
@@ -579,7 +579,7 @@ other threads from checking out [Connections](#connection) while establishing a
579579
Before a given [Connection](#connection) is returned from checkOut, it must be marked as "in use", and the pool's
580580
availableConnectionCount MUST be decremented.
581581

582-
##### Awaiting Pending Read (CSOT-only)
582+
##### Awaiting Pending Read (drivers that support CSOT)
583583

584584
If an operation times out the socket while awaiting a server response and CSOT is enabled and `maxTimeMS` was added to
585585
the command:
@@ -597,12 +597,12 @@ consuming buffered data.
597597
1. **Persist and update timestamp**: The connection must record the current time immediately after the original socket
598598
timeout. This timestamp MUST be updated to the current time whenever any bytes are successfully read, received, or
599599
consumed while explicitly awaiting the pending response as part of checking out the connection.
600-
2. **Aliveness check**: If the connection remains idle (i.e. no data is read or received) for more than 3 seconds since
601-
the start of the "pending response" state or since the last successful read/receive, the driver MUST attempt to
602-
verify the connection’s health by either performing a non-blocking read or using the minimal possible timeout to
603-
check if at least one byte can be read/received. If at least one byte can be read the connection should be returned
604-
to the pool for reuse and a retryable error should be propagated to the operation layer. If no bytes can be read,
605-
the connection MUST be closed.
600+
2. **Aliveness check**: If the undrained connection remains idle (i.e. no data is read or received) for more than 3
601+
seconds since the start of the "pending response" state or since the last successful read/receive, the driver MUST
602+
attempt to verify the connection’s health by either performing a non-blocking read or using the minimal possible
603+
timeout to check if at least one byte can be read/received. If at least one byte can be read the connection should
604+
be returned to the pool for reuse and a retryable error should be propagated to the operation layer. If no bytes
605+
can be read, the connection MUST be closed.
606606
3. **User-provided timeout**: If a user-provided timeout is specified for the "pending response" drain, the driver MUST
607607
use the minimum of (a) the remaining time before the 3 second "pending response" window elapses and (b) the
608608
user-provided timeout as the effective timeout for the read/drain operation.
@@ -618,76 +618,76 @@ consuming buffered data.
618618
```mermaid
619619
sequenceDiagram
620620
participant Driver
621-
participant Pool
622-
participant Conn as Connection (*)
623-
participant Server
624-
621+
participant Pool
622+
participant Conn as Connection (*)
623+
participant Server
624+
625625
Driver->>Pool: Checkout Connection (*)
626626
Pool->>Driver: Return connection (*)
627-
Driver->>Conn: Send operation (1) (CSOT enabled, maxTimeMS > 0, exhaustAllowed = false)
628-
Conn->>Server: Send command
627+
Driver->>Conn: Send operation (1) (CSOT enabled, maxTimeMS > 0, exhaustAllowed = false)
628+
Conn->>Server: Send command
629629
Server-->>Conn: (No response, socket times out)
630630
631-
Conn->>Conn: Transition connection to "pending response" state, record current time
632-
Conn-->>Driver: Error
631+
Conn->>Conn: Transition connection to "pending response" state, record current time
632+
Conn-->>Driver: Error
633633
634634
Driver->>Pool: Checkout Connection (*)
635635
Pool->>Driver: Return connection (*)
636636
Driver->>Conn: Send operation (2)
637-
637+
638638
Conn->>Conn: Attempt to drain pending response from operation (1)
639-
Conn->>Conn: Update pending read timestamp if bytes read
640-
alt Timeout window exceeded or non-timeout error
641-
Conn->>Conn: Close connection
642-
Conn-->>Driver: Error
643-
else Timeout window not exceeded
644-
alt Error
639+
Conn->>Conn: Update pending read timestamp if bytes read
640+
alt Timeout window exceeded or non-timeout error
641+
Conn->>Conn: Close connection
642+
Conn-->>Driver: Error
643+
else Timeout window not exceeded
644+
alt Error
645645
Conn->>Conn: Clear pending response state
646-
Conn->>Conn: Reset to current time
647-
Conn->>Pool: Check connection back into pool
648-
Conn-->>Driver: Error
649-
else No error
650-
Conn->>Conn: Clear pending response state
651-
Conn->>Driver: Return connection to execute operation
652-
end
653-
end
646+
Conn->>Conn: Reset to current time
647+
Conn->>Pool: Check connection back into pool
648+
Conn-->>Driver: Error
649+
else No error
650+
Conn->>Conn: Clear pending response state
651+
Conn->>Driver: Return connection to execute operation
652+
end
653+
end
654654
```
655655

656656
```python
657-
PENDING_RESPONSE_TIMEOUT_MS = 3000 # static timeout
658-
659-
def await_pending_response(timeout, conn):
660-
# Note: conn.pending_start is initialized after the original socket timeout
661-
# and not in this function since the connection will sit in the pool for some
662-
# non-deterministic amount of time after the socket timeout.
663-
664-
remaining_time = (conn.pending_start + PENDING_RESPONSE_TIMEOUT_MS) - current_time()
665-
if remaining_time <= 0:
666-
# Use the smallest timeout (or enable non-blocking read).
667-
remaining_time = 0.001
668-
669-
if timeout is None:
670-
timeout = min(remaining_time, conn.socket_timeout_ms)
671-
else:
672-
timeout = min(remaining_time, timeout)
673-
674-
data, error = execute_pending_response(timeout, conn)
675-
end_time = current_time()
676-
677-
if error is not None and error is not timeout:
678-
close_connection(conn)
679-
raise
680-
681-
if len(data) > 0:
682-
# Refresh the remaining time upon a successful read
683-
conn.pending_start = end_time
684-
685-
# Check if the remaining time has been exceeded
686-
if end_time - conn.pending_start >= PENDING_RESPONSE_TIMEOUT_MS:
687-
close_connection(conn)
688-
689-
if error is not None:
690-
raise error
657+
PENDING_RESPONSE_TIMEOUT_MS = 3000 # static timeout
658+
659+
def await_pending_response(timeout, conn):
660+
# Note: conn.pending_start is initialized after the original socket timeout
661+
# and not in this function since the connection will sit in the pool for some
662+
# non-deterministic amount of time after the socket timeout.
663+
664+
remaining_time = (conn.pending_start + PENDING_RESPONSE_TIMEOUT_MS) - current_time()
665+
if remaining_time <= 0:
666+
# Use the smallest timeout (or enable non-blocking read).
667+
remaining_time = 0.001
668+
669+
if timeout is None:
670+
timeout = min(remaining_time, conn.socket_timeout_ms)
671+
else:
672+
timeout = min(remaining_time, timeout)
673+
674+
data, error = execute_pending_response(timeout, conn)
675+
end_time = current_time()
676+
677+
if error is not None and error is not timeout:
678+
close_connection(conn)
679+
raise
680+
681+
if len(data) > 0:
682+
# Refresh the remaining time upon a successful read
683+
conn.pending_start = end_time
684+
685+
# Check if the remaining time has been exceeded
686+
if end_time - conn.pending_start >= PENDING_RESPONSE_TIMEOUT_MS:
687+
close_connection(conn)
688+
689+
if error is not None:
690+
raise error
691691
```
692692

693693
##### Pseudocode
@@ -966,7 +966,7 @@ interface PoolClosedEvent {
966966
* Emitted when a Connection Pool creates a Connection object.
967967
* NOTE: This does not mean that the Connection is ready for use.
968968
*/
969-
interface ConnectionCreatedEvent {
969+
interface ConnectionCreatedEvent {
970970
/**
971971
* The ServerAddress of the Endpoint the pool is attempting to connect to.
972972
*/
@@ -1122,7 +1122,7 @@ interface ConnectionCheckedInEvent {
11221122
}
11231123

11241124
/**
1125-
* Emitted when the connection being checked out is attempting to read and
1125+
* Emitted when the connection being checked out is attempting to read and
11261126
* discard a pending server response.
11271127
*/
11281128
interface PendingResponseStarted {
@@ -1143,7 +1143,7 @@ interface PendingResponseStarted {
11431143
}
11441144

11451145
/**
1146-
* Emitted when the connection successfully read the pending read and is ready
1146+
* Emitted when the connection successfully read the pending read and is ready
11471147
* to be checked out.
11481148
*/
11491149
interface PendingResponseSucceeded {
@@ -1638,6 +1638,12 @@ Exhaust cursors are incompatible with the "pending response" connection state du
16381638
connection's completion, which occurs only when `moreToCome=0` is received. Consequently, discarding one of these
16391639
responses does not restore the connection to a reusable state.
16401640

1641+
### Async IO Considerations for Awaiting a Pending Response
1642+
1643+
Several drivers (e.g., event-loop or background-read designs) perform socket I/O asynchronously. After a socket times
1644+
out, the server's reply may be drained while the connection is idle in the pool. Therefore, the aliveness check only
1645+
applies to an undrained connection that has exceeded the pending-response window without progress.
1646+
16411647
## Backwards Compatibility
16421648

16431649
As mentioned in [Deprecated Options](#deprecated-options), some drivers currently implement the options `waitQueueSize`

source/unified-test-format/unified-test-format.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,7 @@ The structure of each object is as follows:
12741274
When `failureIsRedacted` is present and its value is `true`, the test runner MUST assert that a failure is present and
12751275
that the failure has been redacted according to the rules defined for error redaction in the
12761276
[command logging and monitoring specification](../command-logging-and-monitoring/command-logging-and-monitoring.md#security).
1277+
12771278

12781279
When `false`, the test runner MUST assert that a failure is present and that the failure has NOT been redacted.
12791280

0 commit comments

Comments
 (0)