Skip to content

Conversation

prestonvasquez
Copy link
Member

GODRIVER-3544, GODRIVER-3653

Summary

Extend the mongo.Client stable API to allow appending handshake client metadata on demand.

Background & Motivation

NA

@prestonvasquez prestonvasquez requested a review from a team as a code owner September 12, 2025 05:04
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR extends the mongo.Client API to support appending client metadata on-demand for handshake requests. The implementation allows drivers to dynamically update metadata information (name, version, platform) that is sent to servers during connection establishment.

  • Replaces static driver info configuration with atomic pointer-based dynamic metadata management
  • Adds AppendDriverInfo method to Client for runtime metadata updates
  • Refactors topology configuration to use functional options pattern with atomic driver info handling

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
mongo/client.go Adds AppendDriverInfo method and atomic driver info management to Client
x/mongo/driver/topology/topology_options.go Refactors config creation with functional options and atomic driver info
x/mongo/driver/topology/server_options.go Replaces individual driver info fields with atomic pointer
x/mongo/driver/topology/server.go Updates connection creation to use atomic driver info
internal/integration/handshake_test.go Comprehensive test coverage for dynamic metadata appending
internal/integration/unified/operation.go Adds appendMetadata operation support
internal/integration/unified/client_operation_execution.go Implements unified test operation execution
internal/test/client_metadata.go Test utility for encoding client metadata
internal/integration/mtest/proxy_*.go Enhanced proxy message capture for testing
internal/handshake/handshake.go Utility for parsing client metadata from wire messages
internal/assert/assertbsoncore/assertions_bsoncore.go Assertion helpers for comparing client metadata

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +318 to +323
// AppendsDriverInfo appends the provided [options.DriverInfo] to the metadata
// (e.g. name, version, platform) that will be sent to the server in handshake
// requests when establishing new connections. The provided info will overwrite
// any existing values.
//
// Repeated calls to appendMetadata with equivalent DriverInfo is a no-op.
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation contains inconsistencies. Line 317 refers to 'AppendsDriverInfo' (with an 's') but the method is named 'AppendDriverInfo'. Line 322 mentions 'appendMetadata' but should refer to 'AppendDriverInfo'.

Suggested change
// AppendsDriverInfo appends the provided [options.DriverInfo] to the metadata
// (e.g. name, version, platform) that will be sent to the server in handshake
// requests when establishing new connections. The provided info will overwrite
// any existing values.
//
// Repeated calls to appendMetadata with equivalent DriverInfo is a no-op.
// AppendDriverInfo appends the provided [options.DriverInfo] to the metadata
// (e.g. name, version, platform) that will be sent to the server in handshake
// requests when establishing new connections. The provided info will overwrite
// any existing values.
//
// Repeated calls to AppendDriverInfo with equivalent DriverInfo is a no-op.

Copilot uses AI. Check for mistakes.

start := time.Now()
time.Sleep(2 * time.Second)
messages := mt.GetProxiedMessages()
messages := mt.GetProxyCapture().Drain()
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from mt.GetProxiedMessages() to mt.GetProxyCapture().Drain() changes the API behavior from returning a copy of messages to draining the capture buffer. This could affect test isolation if other parts of the test expect to access the same messages.

Suggested change
messages := mt.GetProxyCapture().Drain()
messages := mt.GetProxiedMessages()

Copilot uses AI. Check for mistakes.

Comment on lines +296 to +298
handshakeOpts.OuterLibraryName = driverInfo.Load().Name
handshakeOpts.OuterLibraryVersion = driverInfo.Load().Version
handshakeOpts.OuterLibraryPlatform = driverInfo.Load().Platform
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The atomic pointer is being loaded multiple times (lines 295, 296, 297, 298). This should be optimized to load once and reuse the value to avoid potential race conditions and improve performance.

Suggested change
handshakeOpts.OuterLibraryName = driverInfo.Load().Name
handshakeOpts.OuterLibraryVersion = driverInfo.Load().Version
handshakeOpts.OuterLibraryPlatform = driverInfo.Load().Platform
di := driverInfo.Load()
handshakeOpts.OuterLibraryName = di.Name
handshakeOpts.OuterLibraryVersion = di.Version
handshakeOpts.OuterLibraryPlatform = di.Platform

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using simplified code:

handshaker = func(driver.Handshaker) driver.Handshaker {
	op := operation.NewHello().
		AppName(appName).
		Compressors(comps).
		ClusterClock(clock).
		ServerAPI(serverAPI).
		LoadBalanced(loadBalanced)

	if settings.driverInfo != nil {
		if di := settings.driverInfo.Load(); di != nil {
			op = op.OuterLibraryName(di.Name).
				OuterLibraryVersion(di.Version).
				OuterLibraryPlatform(di.Platform)
		}
	}

	return op
}

Comment on lines +813 to +819
if s.cfg.driverInfo != nil {
driverInfo := s.cfg.driverInfo.Load()
if driverInfo != nil {
handshaker = handshaker.OuterLibraryName(driverInfo.Name).OuterLibraryVersion(driverInfo.Version).
OuterLibraryPlatform(driverInfo.Platform)
}
}
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The nested nil checks for s.cfg.driverInfo and driverInfo.Load() could be simplified using a helper function or by consolidating the logic, improving code readability.

Copilot uses AI. Check for mistakes.

Copy link
Contributor

🧪 Performance Results

Commit SHA: 751104f

The following benchmark tests for version 68c3a9c6196e6200074df990 had statistically significant changes (i.e., |z-score| > 1.96):

Benchmark Measurement % Change Patch Value Stable Region H-Score Z-Score
BenchmarkBSONFlatDocumentDecoding ops_per_second_min -89.4945 204.1260 Avg: 1943.0314
Med: 2025.8252
Stdev: 359.7418
0.9122 -4.8338
BenchmarkMultiInsertSmallDocument total_time_seconds -17.4565 1.0066 Avg: 1.2195
Med: 1.2195
Stdev: 0.0463
0.9231 -4.5976
BenchmarkSmallDocInsertOne total_bytes_allocated 14.2584 35546136.0000 Avg: 31110306.6667
Med: 31874040.0000
Stdev: 1375871.3009
0.8791 3.2240
BenchmarkSmallDocInsertOne total_mem_allocs 14.2265 469142.0000 Avg: 410712.0000
Med: 420411.0000
Stdev: 18617.0503
0.8736 3.1385
BenchmarkSingleFindOneByID ops_per_second_max 10.0378 4628.9653 Avg: 4206.7060
Med: 4263.5014
Stdev: 125.7319
0.8782 3.3584
BenchmarkBSONFlatDocumentDecoding ns_per_op 5.5008 61760.0000 Avg: 58539.8158
Med: 58375.5000
Stdev: 1018.6144
0.8253 3.1613
BenchmarkBSONFlatDocumentDecoding ops_per_second_med -4.1389 17588.6026 Avg: 18347.9990
Med: 18325.0871
Stdev: 250.7477
0.8137 -3.0285
BenchmarkBSONDeepDocumentDecoding total_bytes_allocated -4.1384 240946064.0000 Avg: 251347937.2632
Med: 251754024.0000
Stdev: 3246569.4002
0.8245 -3.2040
BenchmarkBSONDeepDocumentDecoding ns_per_op 4.1187 75011.0000 Avg: 72043.7632
Med: 72032.5000
Stdev: 874.5642
0.8344 3.3928
BenchmarkBSONDeepDocumentDecoding total_mem_allocs -4.0868 11094007.0000 Avg: 11566719.6842
Med: 11584349.5000
Stdev: 148003.3723
0.8237 -3.1939
BenchmarkBSONDeepDocumentDecoding ops_per_second_med -3.7319 14160.2945 Avg: 14709.2334
Med: 14679.2224
Stdev: 165.5575
0.8299 -3.3157
BenchmarkBSONDeepDocumentDecoding ops_per_second_max -2.1791 14859.9450 Avg: 15190.9782
Med: 15236.1319
Stdev: 165.6540
0.7213 -1.9983
BenchmarkBSONFlatDocumentDecoding ops_per_second_max -2.0412 18588.3971 Avg: 18975.7258
Med: 19009.5998
Stdev: 174.4840
0.7559 -2.2199
BenchmarkSingleRunCommand allocated_bytes_per_op -0.1210 12183.0000 Avg: 12197.7632
Med: 12197.0000
Stdev: 4.9942
0.8218 -2.9560
BenchmarkBSONDeepDocumentDecoding allocated_bytes_per_op -0.0568 15094.0000 Avg: 15102.5789
Med: 15104.0000
Stdev: 3.2602
0.8568 -2.6314

For a comprehensive view of all microbenchmark results for this PR's commit, please check out the Evergreen perf task for this patch.

Copy link
Contributor

API Change Report

./v2/mongo

compatible changes

(*Client).AppendDriverInfo: added

./v2/x/mongo/driver/topology

incompatible changes

NewConfigFromOptionsWithAuthenticator: removed
WithOuterLibraryName: removed
WithOuterLibraryPlatform: removed
WithOuterLibraryVersion: removed

compatible changes

AuthConfigOption: added
NewAuthenticatorConfig: added
WithAuthConfigClientOptions: added
WithAuthConfigClock: added
WithAuthConfigDriverInfo: added
WithDriverInfo: added

@alcaeus alcaeus requested review from matthewdale and removed request for qingyang-hu September 15, 2025 17:54
Comment on lines +460 to +465
want := test.EncodeClientMetadata(mt, test.WithClientMetadataAppName("foo"))
for i := 0; i < 2; i++ {
message := mt.GetProxyCapture().TryNext()
require.NotNil(mt, message, "expected handshake message, got nil")

// First two messages should be connection handshakes: one for the heartbeat connection and the other for the
// application connection.
for idx, pair := range msgPairs[:2] {
helloCommand := handshake.LegacyHello
// Expect "hello" command name with API version.
if os.Getenv("REQUIRE_API_VERSION") == "true" {
helloCommand = "hello"
}
assert.Equal(mt, pair.CommandName, helloCommand, "expected command name %s at index %d, got %s", helloCommand, idx,
pair.CommandName)

sent := pair.Sent
appNameVal, err := sent.Command.LookupErr("client", "application", "name")
assert.Nil(mt, err, "expected command %s at index %d to contain app name", sent.Command, idx)
appName := appNameVal.StringValue()
assert.Equal(mt, testAppName, appName, "expected app name %v at index %d, got %v", testAppName, idx,
appName)
assertbsoncore.HandshakeClientMetadata(mt, want, message.Sent.Command)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using test.EncodeClientMetadata as the reference for the handshake metadata possibly obscures bugs in the handshake metadata logic that are also in test.EncodeClientMetadata. Is there a way we can make these assertions using something like BSON documents or bson.D literals?

E.g.

assertbsoncore.HandshakeClientMetadata(mt,
    bson.D{
        {"application", bson.D{
            {"name", "foo"},
        }},
        // ...
    },
    message.Sent.Command

// Repeated calls to appendMetadata with equivalent DriverInfo is a no-op.
//
// Metadata is limited to 512 bytes; any excess will be truncated.
func (c *Client) AppendDriverInfo(info options.DriverInfo) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a method on Client, can we make this an internal option on Collection?

E.g.

func blah(coll *mongo.Collection, ...) {
    opts := options.Collection()
    info := options.DriverInfo{
        // New driver info fields.
    }
    xoptions.SetInternalCollectionOptions(opts, "driverInfo", info)
    coll = coll.Clone(opts)
    // Use new collection.

Comment on lines +296 to +298
handshakeOpts.OuterLibraryName = driverInfo.Load().Name
handshakeOpts.OuterLibraryVersion = driverInfo.Load().Version
handshakeOpts.OuterLibraryPlatform = driverInfo.Load().Platform
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using simplified code:

handshaker = func(driver.Handshaker) driver.Handshaker {
	op := operation.NewHello().
		AppName(appName).
		Compressors(comps).
		ClusterClock(clock).
		ServerAPI(serverAPI).
		LoadBalanced(loadBalanced)

	if settings.driverInfo != nil {
		if di := settings.driverInfo.Load(); di != nil {
			op = op.OuterLibraryName(di.Name).
				OuterLibraryVersion(di.Version).
				OuterLibraryPlatform(di.Platform)
		}
	}

	return op
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants