@Beta public class DefaultDriverConfigReporter extends Object implements DriverConfigReporter
DriverConfigReporter: serializes the driver configuration to the cross-driver
DRIVER_CONFIG JSON shape, which Connection.Factory then sends in the control
connection's STARTUP options.
The report is built afresh for every control connection, from inside its STARTUP frame
assembly, and never cached: it describes the objects that are in force at that handshake rather
than the configuration the Cluster was constructed from. That distinction is not
theoretical. Cluster.Manager.init() builds the Connection.Factory before it calls
LoadBalancingPolicy.init(com.datastax.driver.core.Cluster, java.util.Collection<com.datastax.driver.core.Host>), and the first control connection's STARTUP goes out in
between — so a datacenter or rack the policy infers from the node it reaches cannot be known on
the first report and is known on every later one. A reconnect therefore costs one report build
and may legitimately report more than the connection before it did.
The report follows the normative cross-driver schema (report version 1): kebab-case
keys, nested objects, and omission of any key or group that has no value (nothing is ever
emitted as null). The same applies where a configured value falls outside what the schema
can express but the key is optional: a disabled connect timeout, a disabled read
timeout, a disabled SO_LINGER, an unbounded page size and a non-serial default serial
consistency level are omitted rather than emitted as a value the schema rejects. Two keys are
omitted for a third reason — the schema admits only a boolean, and 3.x cannot observe which one
applies: query.defaults.client-timestamps for a timestamp generator that is none of the
driver's own, and connection.tls.hostname-verification for any SSLOptions other
than SniSSLOptions. Both keys are documented as absent when the behavior is unknown,
which is exactly the case here: a custom generator decides per call whether to assign a timestamp
at all, and every other SSLOptions builds its engine from a user-supplied SSLContext (or hands the whole handler over to Netty), so nothing the reporter can read says
whether the hostname is checked. Two more optional bounds are omitted for the opposite reason —
3.x simply has no such bound to report: connection.reconnection.policy.max-attempts,
since its reconnection policies retry forever, and query.retry.policy.max-retries, since
no single number describes a 3.x retry policy: RetryPolicy.onRequestError(com.datastax.driver.core.Statement, com.datastax.driver.core.ConsistencyLevel, com.datastax.driver.core.exceptions.DriverException, int) is the one path
that does not stop after a single retry, and it is only reached for an idempotent statement, so
the same policy bounds a non-idempotent request at one retry and an idempotent one at the length
of the query plan. Which of the two a statement gets is not configuration — Statement.setIdempotent(boolean) overrides the reported query.defaults.idempotence per statement.
Only a token-aware chain has a built-in shape in the schema, whose query.load-balancing.policy.type admits token-aware and custom and nothing else.
That shape carries no name, so it is claimed only for a chain every element of which the group
can describe; any other chain is custom, named after every policy in it — WhiteListPolicy(TokenAwarePolicy(DCAwareRoundRobinPolicy)) — and keeps the capability keys it
can still state, which the custom branch admits as additional properties. See loadBalancingPolicy(java.util.List<com.datastax.driver.core.policies.LoadBalancingPolicy>, com.datastax.driver.core.policies.LoadBalancingPolicy, com.datastax.driver.core.DefaultDriverConfigReporter.NodeLocation). A datacenter and rack are reported in query.load-balancing.node-preference whenever the chain prefers one: from a DC- or rack-aware
policy, or from a filter restricting the session to a single datacenter. A bare RoundRobinPolicy, or a WhiteListPolicy over one, prefers neither and both preference
keys are omitted.
The schema reports that preference in a second, optional place, connection.node-preference, which describes the part of the cluster the driver holds connections
to rather than how a query is routed. One LoadBalancingPolicy decides both in 3.x, since
LoadBalancingPolicy.distance(Host) is what governs whether a host is pooled at all, so
that key is derived from the same chain — but from its datacenter half alone. A rack-aware
policy's distance() returns REMOTE, never IGNORED, for a local-datacenter
host in another rack, so those hosts are still pooled and the rack does not scope what the driver
connects to. The datacenter does: a host outside the preferred one is IGNORED unless the
policy is configured to use hosts there, and an ignored host gets no pool at all.
That last claim is exact only for the policy the preference was read from. A chainable policy
above it can narrow pooling further: HostFilterPolicy.distance(Host) — and
therefore WhiteListPolicy's — returns IGNORED for any host failing its predicate,
including one inside the reported datacenter, and a custom policy computes distance()
itself and need honor nothing the chain below it prefers. The reported datacenter is thus
necessary but not sufficient: it is what the located policy prefers, not the whole of what gets a
pool. Reported all the same, deliberately, on the grounds that hiding a datacenter the operator
really did configure is the worse failure mode — note the asymmetry with the inferred values
above, in that nothing is inferred on a third party's behalf while what was explicitly configured
is passed through even where a wrapper may override it.
A filter that restricts the session to exactly one datacenter is read the other way round:
HostFilterPolicy.fromDCWhiteList(LoadBalancingPolicy, Iterable) retains the datacenters
it was given, so when nothing in the chain prefers a datacenter of its own — fromDCWhiteList(new RoundRobinPolicy(), ["dc1"]) — the restriction is what decides the
preference and is reported as one. Anything a filter can express that names no single datacenter
keeps the group omitted: a blacklist, several allowed datacenters, a blank name, a WhiteListPolicy (which filters on addresses), or a caller-supplied Predicate<Host>,
which stays opaque. A filter the preference was not read from — because a location-aware
policy below it won — narrows the chain without being stated anywhere, so it costs the chain its
built-in shape and is named instead, exactly like any other restricting wrapper.
Known limitations: omission is not always available, because these fields are required by the schema.
connection.requests.in-flight.max must be positive, while PoolingOptions.setMaxRequestsPerConnection(HostDistance, int) also accepts 0.
query.speculative-execution.policy.percentile is bounded to 0..100 exclusive by the
schema, while PercentileSpeculativeExecutionPolicy accepts a percentile of 0.
connection.requests.orphaned.max used to belong to that list and no longer does: 3.x
has no orphaned-request setting to report it from — a request the driver stopped waiting for
keeps its stream identifier until the response arrives, with no configurable bound and no
connection replacement (only 4.x has advanced.connection.max-orphan-requests) — and the
schema now makes the group optional for exactly that case, so omission is the schema-valid
answer.
Such a value is reported as-is, and a key with no equivalent stays omitted: the
reporter deliberately neither fabricates an in-range value — which would misreport a setting an
operator may have chosen on purpose, or a policy 3.x does not implement — nor drops the whole
report over one field, so the document is accurate but fails schema validation. Tracked as a
cross-driver schema gap: the fix is to let those fields express the value, the way control-plane.schema.agreement.timeout-ms already admits 0.
The read timeout feeds three places, all of them optional, so disabling it omits all three:
connection.read, control-plane.queries.system.timeout.client-side-ms and query.defaults.request.
| Modifier and Type | Field and Description |
|---|---|
protected Configuration |
configuration |
static String |
DRIVER_CONFIG_KEY
STARTUP option key under which the config JSON is sent.
|
| Constructor and Description |
|---|
DefaultDriverConfigReporter(Configuration configuration) |
| Modifier and Type | Method and Description |
|---|---|
protected String |
buildJson()
Builds the compact, single-line JSON configuration report.
|
String |
buildReport()
Builds the configuration report, or returns
null if it could not be built (in which
case no DRIVER_CONFIG option is sent). |
protected void |
populateConfig(ObjectNode root)
Populates the three top-level configuration groups onto the report root from
configuration and its policies. |
public static final String DRIVER_CONFIG_KEY
protected final Configuration configuration
public DefaultDriverConfigReporter(Configuration configuration)
public String buildReport()
DriverConfigReporternull if it could not be built (in which
case no DRIVER_CONFIG option is sent).
Called once per control connection, from its STARTUP frame assembly, and not cached:
the report describes the objects in force at the handshake that sends it, rather than the
configuration the Cluster was constructed from. Implementations must therefore tolerate
being called on a Netty event loop, and repeatedly over a cluster's lifetime.
Implementations must not throw: a failure to build the report must be swallowed (and
logged) rather than propagated, so that a diagnostic aid can never break cluster
initialization. For the same reason they must bound the report's size and return null
rather than an oversized one: STARTUP option values carry an unchecked 16-bit length
prefix, so a value over 65535 encoded bytes corrupts the frame instead of merely being useless.
The built-in reporter caps the report at 32KiB of UTF-8, the limit the other ScyllaDB drivers
apply.
An implementation that cannot even be loaded is handled one level up rather than
here: DefaultDriverConfigReporter needs Jackson, and on a classpath without it the
failure is a LinkageError raised while initializing the class, which no method of this
interface could catch. Connection.Factory.buildDriverConfigReport contains that and
reports nothing, so the connection is still established.
buildReport in interface DriverConfigReporterDRIVER_CONFIG startup option, or null to
send nothing.protected String buildJson()
protected void populateConfig(ObjectNode root)
configuration and its policies. Keys the driver has no equivalent for (or cannot introspect
in 3.x) are omitted rather than emitted as null.Copyright © 2012–2026. All rights reserved.