Skip to content

fix: share single Telemetry instance per SDK client - #290

Merged
SoulPancake merged 2 commits into
mainfrom
fix/209-share-telemetry-instance
Mar 12, 2026
Merged

fix: share single Telemetry instance per SDK client#290
SoulPancake merged 2 commits into
mainfrom
fix/209-share-telemetry-instance

Conversation

@jimmyjames

@jimmyjamesjimmyjames commented Feb 15, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes#209

The SDK was creating new Telemetry, Metrics, and associated OpenTelemetry instruments for every HTTP request instead of sharing a single instance per SDK client. This caused:

  • Wasted object allocation on every request
  • Fragmented OTel instrument instances (potential metric aggregation issues)
  • Repeated GlobalOpenTelemetry meter lookups

Additionally, once a single Telemetry/Metrics instance is shared across concurrent async requests, the existing code had thread-safety bugs that are fixed here.

Changes

Thread safety (Telemetry.java, Metrics.java):

  • Telemetry.metrics(): volatile field + double-checked locking for safe lazy init
  • Metrics: HashMapConcurrentHashMap, check-then-put → computeIfAbsent() for counters and histograms

Shared instance wiring (OpenFgaClient.java, OpenFgaApi.java, ApiExecutor.java, OAuth2Client.java, HttpRequestAttempt.java):

  • OpenFgaClient owns the single Telemetry instance, passes it to OpenFgaApi and ApiExecutor
  • OpenFgaApi passes shared telemetry through to all HttpRequestAttempt calls
  • OAuth2Client passes its own Telemetry (one per client, not per request) to HttpRequestAttempt
  • New constructors validate telemetry parameter for null, consistent with existing parameter validation

Backward compatibility:

  • All existing public constructors are preserved as overloads that delegate to new versions
  • HttpRequestAttempt(5-param) delegates to new HttpRequestAttempt(6-param)
  • ApiExecutor(2-param) delegates to new ApiExecutor(3-param)
  • OpenFgaApi(1-param) and OpenFgaApi(2-param) preserved, new OpenFgaApi(3-param) added
  • No breaking changes

Test plan

  • Existing TelemetryTest.shouldBeASingletonMetricsInitialization validates singleton behavior
  • New concurrent access test verifies Telemetry.metrics() returns same instance across 10 threads
  • New test for backward-compatible ApiExecutor 2-param constructor
  • HttpRequestAttemptRetryTest updated to use shared telemetry instances
  • ./gradlew check passes (tests + spotless formatting)
  • CI passes on Java 17, 21, 25

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for injecting custom telemetry instances throughout the SDK, enabling greater flexibility in telemetry configuration.
  • Improvements

    • Enhanced thread-safety with concurrent data structures and double-checked locking for singleton initialization.
    • Strengthened input validation to ensure telemetry instances are non-null during initialization.
  • Tests

    • Added comprehensive test coverage for null telemetry validation and concurrent access scenarios.

@coderabbitai

coderabbitaiBot commented Feb 15, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR addresses issue #209 by introducing a single Telemetry instance per SDK instance instead of creating new ones for each HTTP request. New constructors accepting Telemetry are added to OpenFgaApi, ApiExecutor, and HttpRequestAttempt, while existing constructors delegate to these new ones. Thread-safety improvements are applied to Telemetry initialization and Metrics data structures.

Changes

Cohort / File(s)Summary
Telemetry Constructor Injection
src/main/java/dev/openfga/sdk/api/OpenFgaApi.java, src/main/java/dev/openfga/sdk/api/client/ApiExecutor.java, src/main/java/dev/openfga/sdk/api/client/HttpRequestAttempt.java, src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java, src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java
New constructors accepting Telemetry parameter added to key classes; existing constructors delegate to new ones passing new Telemetry(configuration). HttpRequestAttempt invocations updated to pass telemetry instance for propagation through HTTP request chain.
Thread-Safety Improvements
src/main/java/dev/openfga/sdk/telemetry/Telemetry.java, src/main/java/dev/openfga/sdk/telemetry/Metrics.java
ConcurrentHashMap replaces HashMap for thread-safe counter and histogram access; computeIfAbsent simplifies lazy initialization. Telemetry configuration field marked final; metrics field marked volatile with double-checked locking pattern for thread-safe singleton initialization.
Test Coverage
src/test/java/dev/openfga/sdk/api/OpenFgaApiTest.java, src/test/java/dev/openfga/sdk/api/client/ApiExecutorTest.java, src/test/java/dev/openfga/sdk/api/client/HttpRequestAttemptRetryTest.java, src/test/java/dev/openfga/sdk/telemetry/TelemetryTest.java
New tests validate null telemetry rejection in constructors; concurrent access test verifies singleton behavior under multithreaded conditions. HttpRequestAttemptRetryTest updated to pass telemetry in constructor calls.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • rhamzeh
  • evansims
🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 12.31% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main change: sharing a single Telemetry instance per SDK client instead of creating new instances for each request.
Linked Issues check✅ PassedThe PR fully addresses the requirements of issue #209 by eliminating per-request Telemetry creation and implementing a single shared instance per SDK client with proper thread-safety mechanisms.
Out of Scope Changes check✅ PassedAll changes directly support the objective of sharing a single Telemetry instance: constructor wiring, thread-safety improvements (ConcurrentHashMap, volatile fields, double-checked locking), and comprehensive testing.
Merge Conflict Detection✅ Passed✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/209-share-telemetry-instance

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

codecov-commenter commented Feb 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.24561% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 37.80%. Comparing base (d4b4f73) to head (34cfbe2).

Files with missing linesPatch %Lines
...main/java/dev/openfga/sdk/telemetry/Telemetry.java88.88%0 Missing and 1 partial ⚠️

❌ Your project status has failed because the head coverage (37.80%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@ Coverage Diff @@## main #290 +/- ##
============================================
+ Coverage 37.70% 37.80% +0.09% - Complexity 1236 1243 +7 
============================================
Files 197 197 Lines 7609 7621 +12 Branches 880 883 +3 ============================================
+ Hits 2869 2881 +12 
Misses 4601 4601 Partials 139 139 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/dev/openfga/sdk/api/client/HttpRequestAttemptRetryTest.java (1)

484-485: ⚠️ Potential issue | 🟡 Minor

Missing telemetry parameter - inconsistent with other tests.

The shouldRetryOnConnectionTimeout test uses the old 5-parameter HttpRequestAttempt constructor without passing Telemetry, while all other tests in this file were updated to use the new 6-parameter constructor with telemetry. This appears to be an oversight.

🔧 Proposed fix
 HttpRequestAttempt<Void> attempt =
- new HttpRequestAttempt<>(request, "test", Void.class, apiClient, timeoutConfig);+ new HttpRequestAttempt<>(request, "test", Void.class, apiClient, timeoutConfig, new Telemetry(timeoutConfig));
🤖 Fix all issues with AI agents
In `@src/main/java/dev/openfga/sdk/api/OpenFgaApi.java`:
- Around line 83-87: The OpenFgaApi constructor OpenFgaApi(Configuration,
ApiClient, Telemetry) lacks a null check for telemetry; add a validation at the
start of that constructor that throws IllegalArgumentException if telemetry is
null (matching ApiExecutor behavior) so subsequent uses of telemetry (e.g., in
HttpRequestAttempt calls) cannot cause a NullPointerException.
🧹 Nitpick comments (2)
src/test/java/dev/openfga/sdk/api/client/ApiExecutorTest.java (1)

385-391: Test verifies backward compatibility but could be more robust.

The test confirms the 2-parameter constructor works without throwing, which validates backward compatibility. However, consider expanding it to verify the internally-created Telemetry is functional:

💡 Optional: Add verification that the executor is functional
 `@Test`
public void twoParamConstructor_shouldCreateWithOwnTelemetry() throws Exception {
// Verifies the backward-compatible 2-param constructor works
ClientConfiguration config = new ClientConfiguration().apiUrl(fgaApiUrl).storeId(DEFAULT_STORE_ID);
ApiExecutor executor = new ApiExecutor(new ApiClient(), config);
assertNotNull(executor);
+ // Optionally verify it can build a request (doesn't require actual HTTP call)+ assertNotNull(executor.getClass().getDeclaredField("telemetry"));
}
src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java (1)

80-84: Consider thread-safety for concurrent configuration updates.

When setConfiguration is called, both telemetry and api fields are reassigned in two separate statements (lines 82-83). If OpenFgaClient is used concurrently from multiple threads, a thread could observe an inconsistent state where telemetry is updated but api still references the old OpenFgaApi (or vice versa).

If concurrent access to OpenFgaClient is expected, consider either:

  1. Making the updates atomic using synchronization
  2. Documenting that setConfiguration is not thread-safe and should not be called concurrently with API operations

Comment threadsrc/main/java/dev/openfga/sdk/api/OpenFgaApi.java
Instead of creating new Telemetry, Metrics, and OpenTelemetry instruments
for every HTTP request, share a single Telemetry instance per SDK client.
This avoids wasted allocation, fragmented OTel instrument instances, and
repeated GlobalOpenTelemetry meter lookups.
Also fixes thread-safety bugs that become relevant once the instance is
shared across concurrent async requests:
- Telemetry.metrics(): volatile field + double-checked locking
- Metrics counters/histograms: HashMap → ConcurrentHashMap with computeIfAbsent
All existing public constructors are preserved for backward compatibility.
Closes#209
@jimmyjames
jimmyjamesforce-pushed the fix/209-share-telemetry-instance branch from b5251de to d2058e8CompareFebruary 15, 2026 21:27
@jimmyjames
jimmyjames marked this pull request as ready for review February 15, 2026 21:32
@jimmyjames
jimmyjames requested a review from a team as a code ownerFebruary 15, 2026 21:32
CopilotAI review requested due to automatic review settings February 15, 2026 21:32
@dosubot

dosubotBot commented Feb 15, 2026

Copy link
Copy Markdown

Related Documentation

Checked 8 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?Join Discord

CopilotAI 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.

Pull request overview

This PR fixes SDK telemetry/metrics being re-created per HTTP request by introducing a shared Telemetry instance per SDK client and making Telemetry/Metrics safe for concurrent use, addressing issue #209.

Changes:

  • Share a single Telemetry instance across request execution paths (OpenFgaClientOpenFgaApi/ApiExecutorHttpRequestAttempt) and within OAuth2Client.
  • Make lazy Telemetry.metrics() initialization thread-safe (double-checked locking) and make Metrics instrument caches concurrent (ConcurrentHashMap + computeIfAbsent).
  • Add/adjust tests for singleton behavior, concurrent access, constructor compatibility, and null telemetry validation.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/main/java/dev/openfga/sdk/telemetry/Telemetry.javaThread-safe lazy initialization of a shared Metrics instance.
src/main/java/dev/openfga/sdk/telemetry/Metrics.javaMake counter/histogram caches concurrency-safe and atomically initialized.
src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.javaOwn and propagate a single Telemetry instance per client.
src/main/java/dev/openfga/sdk/api/OpenFgaApi.javaAccept and reuse injected Telemetry for all request attempts; add null validation.
src/main/java/dev/openfga/sdk/api/client/ApiExecutor.javaAccept and reuse injected Telemetry; preserve 2-param constructor via delegation.
src/main/java/dev/openfga/sdk/api/client/HttpRequestAttempt.javaAdd telemetry-injecting constructor and enforce non-null telemetry.
src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.javaReuse the client’s own telemetry when issuing token exchange requests.
src/test/java/dev/openfga/sdk/telemetry/TelemetryTest.javaAdd concurrent access test for Telemetry.metrics() singleton behavior.
src/test/java/dev/openfga/sdk/api/client/HttpRequestAttemptRetryTest.javaUpdate call sites to pass telemetry; add null telemetry rejection test.
src/test/java/dev/openfga/sdk/api/client/ApiExecutorTest.javaAdd tests for backward-compatible constructor and null telemetry validation.
src/test/java/dev/openfga/sdk/api/OpenFgaApiTest.javaAdd null telemetry rejection test for new constructor overload.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/test/java/dev/openfga/sdk/telemetry/TelemetryTest.java Outdated

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (1)
src/test/java/dev/openfga/sdk/telemetry/TelemetryTest.java (1)

49-49: Nit: import TimeUnit instead of using FQN.

Lines 6–10 already import other java.util.concurrent types. For consistency, import TimeUnit as well.

 import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;

Then on line 49:

- executor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS);+ executor.awaitTermination(5, TimeUnit.SECONDS);

@linux-foundation-easycla

linux-foundation-easyclaBot commented Feb 16, 2026

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

Assert awaitTermination returns true for clear timeout failures, and
wrap test body in try/finally with shutdownNow to prevent thread leaks.
@jimmyjames
jimmyjamesforce-pushed the fix/209-share-telemetry-instance branch from 289a4ae to 34cfbe2CompareFebruary 16, 2026 15:47
@SoulPancake

Copy link
Copy Markdown
Member

small thing I wanted to understand is why the OAuth2Client have its own Telemetry instance rather than receiving the shared one from OpenFgaClient?
Never mind, I realised it creates its own Configuration pointing at the token issuer, so a separate Telemetry instance makes complete sense here

@SoulPancake
SoulPancake added this pull request to the merge queueMar 12, 2026
Merged via the queue into main with commit 82aabf2Mar 12, 2026
24 checks passed
@SoulPancake
SoulPancake deleted the fix/209-share-telemetry-instance branch March 12, 2026 13:43
@coderabbitaicoderabbitaiBot mentioned this pull request Mar 27, 2026
4 tasks
Sign up for freeto 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.

Unnecessary Telemetry Objects Created

4 participants

@jimmyjames@codecov-commenter@SoulPancake