Skip to content

Commit

Permalink
DRIVERS-2530: Defer checking for session support until after connecti…
Browse files Browse the repository at this point in the history
…on checkout (#1379)
  • Loading branch information
dariakp committed Feb 28, 2023
1 parent ef669cd commit 90cfaef
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 73 deletions.
91 changes: 28 additions & 63 deletions source/sessions/driver-sessions.rst
Expand Up @@ -203,10 +203,9 @@ instances at the same time (see the Server Session Pool section). Additionally,
a ``ClientSession`` may only ever be associated with one ``ServerSession`` for
its lifetime.

Drivers MUST report an error if sessions are not supported by the deployment
(see How to Check Whether a Deployment Supports Sessions). This error MUST either
be reported by ``startSession``, or be reported the first time the session is used
for an operation.
Drivers MUST NOT check for session support in `startSession`. Instead, if sessions
are not supported, the error MUST be reported the first time the session is used
for an operation (See `How to Tell Whether a Connection Supports Sessions`_).

Explicit vs implicit sessions
-----------------------------
Expand Down Expand Up @@ -433,14 +432,14 @@ Existing database methods that start an implicit session
--------------------------------------------------------

When an existing ``MongoDatabase`` method that does not take a session is called,
the driver MUST check whether the deployment supports sessions (See How to
Check Whether a Deployment Supports Session). If sessions are supported, the
driver MUST behave as if a new ``ClientSession`` was started just for this one
the driver MUST behave as if a new ``ClientSession`` was started just for this one
operation and ended immediately after this operation completes. The actual
implementation will likely involve calling ``client.startSession``, but that is not
required by this spec. Regardless, please consult the startSession section to
replicate the required steps for creating a session.
Drivers MUST NOT consume a server session id until after the connection is checked out.
The driver MUST NOT use the session if the checked out connection does not support sessions
(see `How to Tell Whether a Connection Supports Sessions`_) and, in all cases, MUST NOT consume a server
session id until after the connection is checked out and session support is confirmed.

MongoCollection changes
=======================
Expand Down Expand Up @@ -475,13 +474,14 @@ Existing collection methods that start an implicit session
----------------------------------------------------------

When an existing ``MongoCollection`` method that does not take a session is called,
the driver MUST check whether the deployment supports sessions (See How to
Check Whether a Deployment Supports Session). If sessions are supported, the
driver MUST behave as if a new ``ClientSession`` was started just for this one
the driver MUST behave as if a new ``ClientSession`` was started just for this one
operation and ended immediately after this operation completes. The actual
implementation will likely involve calling ``client.startSession``, but that is not
required by this spec.
Drivers MUST create an implicit session only after successfully checking out a connection.
required by this spec. Regardless, please consult the startSession section to
replicate the required steps for creating a session.
The driver MUST NOT use the session if the checked out connection does not support sessions
(see `How to Tell Whether a Connection Supports Sessions`_) and, in all cases, MUST NOT consume a server
session id until after the connection is checked out and session support is confirmed.

Sessions and Cursors
====================
Expand All @@ -502,68 +502,32 @@ A driver SHOULD NOT attempt to release the acquired session before connection ch

Explicit sessions MAY be changed to allocate a server session similarly.

How to Check Whether a Deployment Supports Sessions
How to Tell Whether a Connection Supports Sessions
===================================================

A driver can determine whether a deployment supports sessions by checking whether
the ``logicalSessionTimeoutMinutes`` property of the ``TopologyDescription`` has
a value or not. If it has a value the deployment supports sessions. However, in
order for this determination to be valid, the driver MUST be connected to at least
one server of a type that is `data-bearing
<https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#data-bearing-server-type>`_.
Therefore, the detailed steps to determine whether sessions are supported are:

1. If the ``TopologyDescription`` and connection type indicate that

* the driver is not connected to any servers, OR
* is not a direct connection AND is not connected to a data-bearing server

then a driver must do a server selection for any server whose type is data-bearing.
Server selection will either time out or result in a ``TopologyDescription`` that
includes at least one connected, data-bearing server.
A driver can determine whether a connection supports sessions by checking whether
the ``logicalSessionTimeoutMinutes`` property of the establishing handshake response has
a value or not. If it has a value, sessions are supported.

2. Having verified in step 1 that the ``TopologyDescription`` includes at least
one connected server a driver can now determine whether sessions are supported
by inspecting the ``TopologyType`` and ``logicalSessionTimeoutMinutes`` property.
When the ``TopologyType`` is ``LoadBalanced``, sessions are always supported.
In the case of an explicit session, if sessions are not supported, the driver MUST raise an error.
In the case of an implicit session, if sessions are not supported, the driver MUST ignore the session.

Possible race conditions when checking whether a deployment supports sessions
-----------------------------------------------------------------------------
Possible race condition when checking for session support
---------------------------------------------------------

There are some possible race conditions that can happen between the time the
There is a possible race condition that can happen between the time the
driver checks whether sessions are supported and subsequently sends a command
to the server:

* The TopologyDescription might be stale and no longer be accurate because it
has been a few seconds since the last heartbeat.

* The TopologyDescription might be accurate at the time the driver checks
whether sessions are supported, but by the time the driver sends a command to
the server it might no longer be accurate.

* The TopologyDescription might be based on connections to a subset of the
servers and it is possible that as the driver connects to more servers the
driver might discover that sessions aren't supported after all.

* The server might have supported sessions at the time the connection was first
opened (and reported a value for logicalSessionTimeoutMinutes in the initial
response to the `handshake <https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst>`_),
but have subsequently been downgraded to not support sessions. The server does
not close the socket in this scenario, and the driver will forever conclude that
the server at the other end of this connection supports sessions. This scenario
will only be a problem until the next heartbeat against that server.

These race conditions are particularly insidious when the driver decides to
start an implicit session based on the conclusion that sessions are supported.
We don't want existing applications that don't use explicit sessions to fail
when using implicit sessions.

To handle these race conditions, the driver MUST ignore any implicit session if
at the point it is sending a command to a specific server it turns out that
that particular server doesn't support sessions after all. This handles the
first three race conditions. There is nothing that the driver can do about the
final race condition, and the server will just return an error in this
scenario.
not close the socket in this scenario, so the driver will conclude that
the server at the other end of this connection supports sessions.

There is nothing that the driver can do about this race condition, and the server
will just return an error in this scenario.

Sending the session ID to the server on all commands
====================================================
Expand Down Expand Up @@ -1187,3 +1151,4 @@ Changelog
:2022-03-24: ServerSession Pooling is required and clarifies session acquisition bounding
:2022-06-13: Move prose tests to test README and apply new ordering
:2022-10-05: Remove spec front matter
:2023-02-24: Defer checking for session support until after connection checkout
52 changes: 42 additions & 10 deletions source/sessions/tests/README.rst
Expand Up @@ -13,26 +13,34 @@ The YAML and JSON files in this directory are platform-independent tests
meant to exercise a driver's implementation of sessions. These tests utilize the
`Unified Test Format <../../unified-test-format/unified-test-format.rst>`__.

Several prose tests, which are not easily expressed in YAML, are also presented
in the Driver Sessions Spec. Those tests will need to be manually implemented
by each driver.

Snapshot session tests
======================
Snapshot sessions tests require server of version 5.0 or higher and
replica set or a sharded cluster deployment.
Default snapshot history window on the server is 5 minutes. Running the test in debug mode, or in any other slow configuration
~~~~~~~~~~~~~~~~~~~~~~
The default snapshot history window on the server is 5 minutes. Running the test in debug mode, or in any other slow configuration
may lead to `SnapshotTooOld` errors. Drivers can work around this issue by increasing the server's `minSnapshotHistoryWindowInSeconds` parameter, for example:

.. code:: python
client.admin.command('setParameter', 1, minSnapshotHistoryWindowInSeconds=60)
client.admin.command('setParameter', 1, minSnapshotHistoryWindowInSeconds=600)
Testing against servers that do not support sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since all regular 3.6+ servers support sessions, the prose tests which test for session non-support SHOULD
use a mongocryptd server as the test server (available with server versions 4.2+); however, if future versions of mongocryptd
support sessions or if mongocryptd is not a viable option for the driver implementing these tests, another server MAY be
substituted as long as it does not return a non-null value for ``logicalSessionTimeoutMinutes``;
in the event that no such server is readily available, a mock server may be used as a last resort.

As part of the test setup for these cases, create a ``MongoClient`` pointed at the test server with the options
specified in the test case and verify that the test server does NOT define a value for ``logicalSessionTimeoutMinutes``
by sending a hello command and checking the response.

Prose tests
```````````
===========

1. Setting both ``snapshot`` and ``causalConsistency`` to true is not allowed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Snapshot sessions tests require server of version 5.0 or higher and
replica set or a sharded cluster deployment.

* ``client.startSession(snapshot = true, causalConsistency = true)``
* Assert that an error was raised by driver
Expand Down Expand Up @@ -234,6 +242,29 @@ This test only applies to drivers that allow authentication to be changed on the
* Call ``findOne`` using the session as an explicit session
* Assert that the driver returned an error because the session is owned by a different user

18. Implicit session is ignored if connection does not support sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Refer to `Testing against servers that do not support sessions`_ and configure a ``MongoClient``
with command monitoring enabled.

* Send a read command to the server (e.g., ``findOne``), ignoring any errors from the server response
* Check the corresponding ``commandStarted`` event: verify that ``lsid`` is not set
* Send a write command to the server (e.g., ``insertOne``), ignoring any errors from the server response
* Check the corresponding ``commandStarted`` event: verify that lsid is not set

19. Explicit session raises an error if connection does not support sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Refer to `Testing against servers that do not support sessions`_ and configure a ``MongoClient``
with default options.

* Create a new explicit session by calling ``startSession`` (this MUST NOT error)
* Attempt to send a read command to the server (e.g., ``findOne``) with the explicit session passed in
* Assert that a client-side error is generated indicating that sessions are not supported
* Attempt to send a write command to the server (e.g., ``insertOne``) with the explicit session passed in
* Assert that a client-side error is generated indicating that sessions are not supported

Changelog
=========

Expand All @@ -242,3 +273,4 @@ Changelog
:2021-07-30: Use numbering for prose test
:2022-02-11: Convert legacy tests to unified format
:2022-06-13: Relocate prose test from spec document and apply new ordering
:2023-02-24: Fix formatting and add new prose tests 18 and 19

0 comments on commit 90cfaef

Please sign in to comment.