Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,38 @@ Features
``Cluster(driver_config_reporting_enabled=False)``; ``SESSION_ID`` is unaffected by
that setting. Reporting is best effort and never prevents a connection from being
established.
* ``DRIVER_CONFIG`` now describes the configuration itself rather than only the schema
version it follows (DRIVER-379). The report covers connection settings (timeouts,
request capacity, shard awareness, socket options, reconnection policy, TLS hostname
verification), the driver's own control-plane query timeouts, and the query defaults
and policies a statement gets when it overrides none of them. It follows the JSON
schema shared with the other ScyllaDB drivers, so the same document describes a
client whichever driver wrote it. Custom policies are reported by type name only and
never by their attributes, so a policy holding a credential does not leak it into the
clients table.
* ``Cluster.sockopts`` is now materialized at construction, so a one-shot iterable is
applied to every connection the cluster opens rather than only to the first one.
* Negotiate and implement the ``SCYLLA_USE_METADATA_ID`` protocol extension: prepared
statements skip re-sending result metadata on EXECUTE, and the driver automatically
refreshes cached metadata when the server detects a schema change (DRIVER-153)

Others
------
* ``DCAwareRoundRobinPolicy.local_dc`` is now read-only. It is set by the constructor,
and filled in by the policy itself when the constructor was given none, from the first
host to come up. Assigning it afterwards was indistinguishable from that inference,
and the two mean different things: a datacenter the application chose against one the
driver guessed. Code that assigned it should pass ``local_dc`` to the constructor
instead.
* ``Connection.max_request_id`` and ``Connection.orphaned_threshold`` are now derived
again for a subclass that lowers ``max_in_flight``. Both are computed from it in the
class body, which runs once, so a subclass previously inherited values derived from the
base class -- leaving, for example, a ``max_in_flight`` of 256 with a threshold of
24576, which a connection holding at most 256 orphaned stream ids can never reach.
Orphan-based connection replacement therefore never happened for such a subclass. A
subclass that sets either itself keeps it. ``max_request_id`` also moves from the
instance to the class, so that it can be read before a connection exists; its value is
unchanged.
* The ``STARTUP`` options that describe the driver itself are no longer the
application's to set. An ``ApplicationInfoBase.add_startup_options`` that sets
``DRIVER_NAME``, ``DRIVER_VERSION``, ``SESSION_ID`` or ``DRIVER_CONFIG`` now has that
Expand Down
7 changes: 5 additions & 2 deletions cassandra/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -1468,7 +1468,10 @@ def __init__(self,

self.ssl_options = ssl_options
self.ssl_context = ssl_context
self.sockopts = sockopts
# Materialized once: these are applied to every socket the cluster opens
# and are read again to build the configuration report, so a one-shot
# iterable would leave whichever consumer ran second with nothing at all.
self.sockopts = list(sockopts) if sockopts is not None else None
self.cql_version = cql_version
self.max_schema_agreement_wait = max_schema_agreement_wait
self.control_connection_timeout = control_connection_timeout
Expand Down Expand Up @@ -1520,7 +1523,7 @@ def __init__(self,
# Built whatever the flag says, so that the flag is the only thing that
# decides whether a connection reports: see _make_connection_kwargs. The
# reporter holds no state, so an unused one costs nothing.
self._driver_config_reporter = DriverConfigReporter()
self._driver_config_reporter = DriverConfigReporter(self)

self.control_connection = ControlConnection(
self, self.control_connection_timeout,
Expand Down
37 changes: 35 additions & 2 deletions cassandra/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,30 @@ class Connection(object):
# owning pool (currently, only HostConnection supports this)
orphaned_threshold = 3 * max_in_flight // 4

# The highest request id a connection will hand out. Request ids run from
# zero to this inclusive, and borrow_connection admits a request only while
# in_flight is below it. Capped at the CQL stream id range, which is all the
# protocol can address however high max_in_flight is set.
max_request_id = min(max_in_flight - 1, (2 ** 15) - 1)

def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# Both of these are derived from max_in_flight, so both have to be
# derived again for a subclass that lowers it. The expressions above run
# once, when this class body is executed, and a subclass would otherwise
# inherit values computed from *this* class's max_in_flight -- leaving,
# say, a max_in_flight of 256 with a threshold of 24576, which a
# connection holding at most 256 orphans can never reach. Orphan-based
# replacement would then never happen for it at all.
#
# A subclass that sets either itself keeps it: that is a deliberate
# choice, not something to derive over.
if 'max_in_flight' in cls.__dict__:
if 'orphaned_threshold' not in cls.__dict__:
cls.orphaned_threshold = 3 * cls.max_in_flight // 4
if 'max_request_id' not in cls.__dict__:
cls.max_request_id = min(cls.max_in_flight - 1, (2 ** 15) - 1)

is_defunct = False
is_closed = False
lock = None
Expand Down Expand Up @@ -944,7 +968,10 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None,
if not self.ssl_context and self.ssl_options:
self.ssl_context = self._build_ssl_context_from_options()

self.max_request_id = min(self.max_in_flight - 1, (2 ** 15) - 1)
# max_request_id is derived on the class, so that the configuration
# report can describe the limit before any connection exists without
# restating how it is computed.
#
# Don't fill the deque with 2**15 items right away. Start with some and add
# more if needed.
initial_size = min(300, self.max_in_flight)
Expand Down Expand Up @@ -1563,7 +1590,13 @@ def _handle_options_response(self, options_response):
# only the control connection reports it. A reporter left as None means
# the cluster has configuration reporting disabled.
if self.is_control_connection and self._driver_config_reporter is not None:
self._driver_config_reporter.add_startup_options(options)
# Whether this is a ScyllaDB node is already known: the features
# above were parsed from the SUPPORTED response, and sharding info
# is what the driver itself keys ScyllaDB-only behaviour off (see
# ControlConnection._try_connect), so the report describes what the
# driver will actually do rather than only what it was configured to.
self._driver_config_reporter.add_startup_options(
options, is_scylla=self.features.sharding_info is not None)

if self.cql_version:
if self.cql_version not in supported_cql_versions:
Expand Down
Loading
Loading