Skip to content

Commit 264abfc

Browse files
samikshya-dbclaude
andcommitted
[PECOBLR-1147] Implement client manager for per-host clients
Implemented per-host client management system with reference counting: Key Components: - clientManager: Singleton managing one telemetry client per host - clientHolder: Holds client and reference count - telemetryClient: Minimal stub implementation (Phase 4 placeholder) Core Features: - ✅ Singleton pattern for global client management - ✅ Per-host client creation and reuse - ✅ Reference counting tied to connection lifecycle - ✅ Thread-safe operations using sync.RWMutex - ✅ Automatic client cleanup when ref count reaches zero - ✅ Client start() called on creation - ✅ Client close() called on removal Methods Implemented: - getClientManager(): Returns singleton instance - getOrCreateClient(host, httpClient, cfg): Creates or reuses client, increments ref count - releaseClient(host): Decrements ref count, removes when zero Stub Implementation: - telemetryClient: Minimal stub with start() and close() methods - Will be fully implemented in Phase 4 (Export) Testing: - 11 comprehensive unit tests with 100% coverage - Tests for singleton, reference counting, concurrent access - Tests for multiple hosts, partial releases, lifecycle management - Thread-safety verified with 100+ concurrent goroutines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 126c10f commit 264abfc

File tree

4 files changed

+435
-1
lines changed

4 files changed

+435
-1
lines changed

telemetry/DESIGN.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1744,7 +1744,8 @@ func BenchmarkInterceptor_Disabled(b *testing.B) {
17441744

17451745
### Phase 2: Per-Host Management
17461746
- [x] Implement `featureflag.go` with caching and reference counting (PECOBLR-1146)
1747-
- [ ] Implement `manager.go` for client management
1747+
- [x] Implement `manager.go` for client management (PECOBLR-1147)
1748+
- [x] Implement `client.go` with minimal telemetryClient stub (PECOBLR-1147)
17481749
- [ ] Implement `circuitbreaker.go` with state machine
17491750
- [ ] Add unit tests for all components
17501751

telemetry/client.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package telemetry
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
// telemetryClient represents a client for sending telemetry data to Databricks.
8+
// This is a minimal stub implementation that will be fully implemented in Phase 4.
9+
type telemetryClient struct {
10+
host string
11+
httpClient *http.Client
12+
cfg *Config
13+
started bool
14+
closed bool
15+
}
16+
17+
// newTelemetryClient creates a new telemetry client for the given host.
18+
func newTelemetryClient(host string, httpClient *http.Client, cfg *Config) *telemetryClient {
19+
return &telemetryClient{
20+
host: host,
21+
httpClient: httpClient,
22+
cfg: cfg,
23+
}
24+
}
25+
26+
// start starts the telemetry client's background operations.
27+
// This is a stub implementation that will be fully implemented in Phase 4.
28+
func (c *telemetryClient) start() error {
29+
c.started = true
30+
return nil
31+
}
32+
33+
// close stops the telemetry client and flushes any pending data.
34+
// This is a stub implementation that will be fully implemented in Phase 4.
35+
func (c *telemetryClient) close() error {
36+
c.closed = true
37+
return nil
38+
}

telemetry/manager.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package telemetry
2+
3+
import (
4+
"net/http"
5+
"sync"
6+
)
7+
8+
// clientManager manages one telemetry client per host.
9+
// Prevents rate limiting by sharing clients across connections.
10+
type clientManager struct {
11+
mu sync.RWMutex
12+
clients map[string]*clientHolder
13+
}
14+
15+
// clientHolder holds a telemetry client and its reference count.
16+
type clientHolder struct {
17+
client *telemetryClient
18+
refCount int
19+
}
20+
21+
var (
22+
managerOnce sync.Once
23+
managerInstance *clientManager
24+
)
25+
26+
// getClientManager returns the singleton instance.
27+
func getClientManager() *clientManager {
28+
managerOnce.Do(func() {
29+
managerInstance = &clientManager{
30+
clients: make(map[string]*clientHolder),
31+
}
32+
})
33+
return managerInstance
34+
}
35+
36+
// getOrCreateClient gets or creates a telemetry client for the host.
37+
// Increments reference count.
38+
func (m *clientManager) getOrCreateClient(host string, httpClient *http.Client, cfg *Config) *telemetryClient {
39+
m.mu.Lock()
40+
defer m.mu.Unlock()
41+
42+
holder, exists := m.clients[host]
43+
if !exists {
44+
holder = &clientHolder{
45+
client: newTelemetryClient(host, httpClient, cfg),
46+
}
47+
m.clients[host] = holder
48+
_ = holder.client.start() // Start background flush goroutine
49+
}
50+
holder.refCount++
51+
return holder.client
52+
}
53+
54+
// releaseClient decrements reference count for the host.
55+
// Closes and removes client when ref count reaches zero.
56+
func (m *clientManager) releaseClient(host string) error {
57+
m.mu.Lock()
58+
holder, exists := m.clients[host]
59+
if !exists {
60+
m.mu.Unlock()
61+
return nil
62+
}
63+
64+
holder.refCount--
65+
if holder.refCount <= 0 {
66+
delete(m.clients, host)
67+
m.mu.Unlock()
68+
return holder.client.close() // Close and flush
69+
}
70+
71+
m.mu.Unlock()
72+
return nil
73+
}

0 commit comments

Comments
 (0)