Skip to content

feat(bigtable): add NewSessionClient constructor with session-dedicated channel pool - #20183

Open
sushanb wants to merge 1 commit into
googleapis:mainfrom
sushanb:feat/bigtable-session-client-constructor
Open

feat(bigtable): add NewSessionClient constructor with session-dedicated channel pool#20183
sushanb wants to merge 1 commit into
googleapis:mainfrom
sushanb:feat/bigtable-session-client-constructor

Conversation

@sushanb

@sushanb sushanb commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds bigtable.SessionClient — a standalone, session-only handle that owns its own gRPC channel pool, stub, and ClientConfigurationManager, independent of any classic *Client the caller may also have open.

This is the first slice of the larger session-table-api plan: constructor + channel pool + config manager wiring only. Follow-ups will add per-resource SessionTable factory methods, cross-client caching on *Client, mixed-mode integration inside *Client, and sessionz debug forwarding.

Usage

sc, err := bigtable.NewSessionClient(ctx, "my-project", "my-instance",
    bigtable.ClientConfig{AppProfile: "default"})
if err != nil { … }
defer sc.Close()

mgr := sc.ConfigurationManager()  // subscribe to server-driven config

Notes for reviewers

  • Option plumbing mirrors NewClientWithConfig verbatim so any behavioural drift between the two constructors stays visible in diff. When retry env vars, direct-access options, or feature-flag construction change in NewClientWithConfig, they should mirror here.
  • First production caller of NewClientConfigurationManager. Every prior caller is in tests.
  • Manager polling ctxconfigManager.Start(context.Background()), not the constructor's ctx. The polling loop must outlive NewSessionClient's caller context; teardown flows through Close, not ctx cancellation.
  • Close is idempotent via sync.Once. Ordering: manager → metrics tracer → channel pool, so no in-flight GetClientConfiguration lands on a closed pool.
  • Reuses pingAndWarmChannelPrimer and pingAndWarmDirectAccessChecker for now. A session-specific noop primer and getClientConfigDirectAccessChecker (already anticipated in direct_access_checker.go:43-65) will land as follow-ups.

Explicitly out of scope (follow-ups)

  • SessionClient.NewSessionTable* factory + SessionTable type
  • sessionTables cache on *Client
  • Mixed-mode composition (Client.sessionClient field, OpenTable routing)
  • Session-specific noop ChannelPrimer
  • getClientConfigDirectAccessChecker
  • SessionDebug / ChannelDebug / ConfigDebug forwarding (waits on debugview package landing upstream)

Test plan

  • go build ./... clean
  • go vet ./... clean
  • go test -run '^(TestNewSessionClient|TestSessionClient)' -count=1 passes
  • go test -short ./internal/transport/ passes (no regression in the underlying pool machinery)
  • Live smoke against vRPC sandbox — optional, for reviewer

/cc @mutianf

…ed channel pool

Adds bigtable.SessionClient — a standalone session-only handle that
owns its own gRPC channel pool, stub, and ClientConfigurationManager,
independent of any classic *Client the caller may also have open.

This is the first slice of the larger session-table-api plan:
constructor + channel pool + config manager wiring. Follow-ups will
add per-resource SessionTable factory methods, cross-client caching,
mixed-mode integration inside *Client, and sessionz debug forwarding.

Notes:
- Option plumbing mirrors NewClientWithConfig verbatim so behavioural
  drift between the two constructors stays visible in diff.
- NewSessionClient is the first production caller of
  NewClientConfigurationManager (all prior callers are in tests).
- The config manager's polling loop is started on a background parent
  ctx so it outlives NewSessionClient's caller context; Close is the
  only teardown path. Close is idempotent via sync.Once.
- Reuses the existing pingAndWarmChannelPrimer and
  pingAndWarmDirectAccessChecker for now; a session-specific noop
  primer and getClientConfigDirectAccessChecker will land as
  follow-ups.

Tests cover accessors, the emulator NoopMetricsProvider override, and
Close idempotency; all use an in-memory bttest server.
@sushanb
sushanb requested review from a team as code owners July 21, 2026 19:43
@product-auto-label product-auto-label Bot added the api: bigtable Issues related to the Bigtable API. label Jul 21, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces SessionClient, a session-only handle to a Bigtable instance that manages its own gRPC channel pool, stub, and configuration manager, along with corresponding unit tests. The review feedback highlights several important improvements: ensuring proper cleanup of metricsTracerFactory if the constructor fails, making the background logger configurable instead of hardcoding log.Default(), adding a nil check for mPool in Close() to prevent potential panics, and deferring the cleanup function in the idempotency test to avoid leaking the in-memory test server.

Comment on lines +84 to +86
if err != nil {
return nil, err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

When initializing resources that require cleanup (such as metrics) inside a constructor, use named return errors and a defer block to clean up the resources if the constructor fails. This ensures that if any subsequent step returns an error, the newly created metricsTracerFactory is properly cleaned up.

defer func() {
	if err != nil {
		metricsTracerFactory.Shutdown()
	}
}()
References
  1. When initializing resources that require cleanup (such as metrics) inside a constructor, use named return errors and a defer block to clean up the resources if the constructor fails.

// context, so start it on a background parent. Close teardown flows
// through configManager.Close(), not ctx cancellation.
instanceName := fmt.Sprintf("projects/%s/instances/%s", project, instance)
configManager := btransport.NewClientConfigurationManager(stub, instanceName, config.AppProfile, featureFlagsMD, log.Default())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Hardcoding log.Default() in production library code makes it difficult for library users to suppress, redirect, or customize background configuration polling logs (which can be noisy during transient network issues). Consider making the logger configurable via ClientConfig or using a quieter/discard logger by default if the user does not specify one.

if c.metricsTracerFactory != nil {
c.metricsTracerFactory.Shutdown()
}
c.closeErr = c.mPool.Close()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To prevent a potential nil pointer dereference panic if Close() is called on an uninitialized or partially initialized SessionClient (where mPool is nil), add a nil check before calling c.mPool.Close(), similar to the guards used for configManager and metricsTracerFactory.

if c.mPool != nil {
	c.closeErr = c.mPool.Close()
}

Comment on lines +82 to +90
sc, _ := newTestSessionClient(t) // don't defer cleanup — we drive Close manually below

if err := sc.Close(); err != nil {
t.Errorf("first Close: %v", err)
}
// Second Close must not panic and must not error.
if err := sc.Close(); err != nil {
t.Errorf("second Close: %v", err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

In TestSessionClient_Close_Idempotent, discarding the cleanup function returned by newTestSessionClient leaks the in-memory bttest.Server because srv.Close() is never called. Since Close() is idempotent, you can safely defer cleanup() at the start of the test to ensure the server is properly shut down, while still manually testing multiple Close() calls on the client.

Suggested change
sc, _ := newTestSessionClient(t) // don't defer cleanup — we drive Close manually below
if err := sc.Close(); err != nil {
t.Errorf("first Close: %v", err)
}
// Second Close must not panic and must not error.
if err := sc.Close(); err != nil {
t.Errorf("second Close: %v", err)
}
sc, cleanup := newTestSessionClient(t)
defer cleanup()
if err := sc.Close(); err != nil {
t.Errorf("first Close: %v", err)
}
if err := sc.Close(); err != nil {
t.Errorf("second Close: %v", err)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: bigtable Issues related to the Bigtable API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant