Repository navigation
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05fcdef98b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| val snapshots = executor.invokeAll((0 until 64).map { Callable { JSONObject(owner.accept()!!) } }) | ||
| .map { it.get().getJSONObject("fetch") } |
There was a problem hiding this comment.
Bound the concurrency test's waits
If accept() develops a deadlock, invokeAll and the subsequent Future.get() calls wait indefinitely, so this concurrency regression test can hang the Gradle worker instead of failing. Use timed task submission or bounded get/latch waits so the test terminates with a useful failure.
AGENTS.md reference: AGENTS.md:L197-L197
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
+1 to bounding this wait. We would classify this as P3 rather than P1: no current production deadlock is demonstrated; the impact is a regression test hanging a developer or test worker if accept() stops completing. We would use the timed invokeAll overload, fail explicitly if any tasks are cancelled or unfinished, and retain the finally cleanup. A timeout only on the later get() calls would not bound the preceding invokeAll wait.
There was a problem hiding this comment.
More details
The diagnostics preserve existing assignment data while maintaining a stable per-client identity and synchronized fetch sequence. The completed static review identified no actionable defect, but focused tests could not execute because a required aapt2 dependency was unavailable in the offline cache.
🤖 Bits Code Review · Commit 05fcdef
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: 61f6dce | Docs | View more details | Give us feedback! |
vjfridge
left a comment
There was a problem hiding this comment.
Looks like we have merge conflict to resolve and I left a couple non-blocking comments but otherwise looks good to me!
| @Test | ||
| fun `M emit only three fields with UUIDv7 identity W accepting logical fetches`() { | ||
| val owner = FlagsRequestDiagnostics(1_700_000_000_000) | ||
| val first = JSONObject(owner.accept()!!) |
There was a problem hiding this comment.
Non-blocking nitpick: could we follow the test conventions here and in the other added tests—Given/When/Then sections, checkNotNull with a useful failure message instead of !!, and imports instead of fully qualified names? That would make the setup/action/assertion boundaries and unexpected-null failures easier to read.
There was a problem hiding this comment.
val first = JSONObject(checkNotNull(wner.accept())There was a problem hiding this comment.
Btw would it be prettier to generate Json here, and compare strings instead?
the assertions is almost impossible to understand in such form:
val expected = JSONObject(
mapOf(
"sdk" to mapOf("instance_id" to instanceId),
"fetch" to mapOf(
"sequence" to sequence,
"trigger" to trigger.wireValue
)
)
)
.toString()
assertThat(actual).isEqual(expected) WDYT?
| val snapshots = executor.invokeAll((0 until 64).map { Callable { JSONObject(owner.accept()!!) } }) | ||
| .map { it.get().getJSONObject("fetch") } |
There was a problem hiding this comment.
+1 to bounding this wait. We would classify this as P3 rather than P1: no current production deadlock is demonstrated; the impact is a regression test hanging a developer or test worker if accept() stops completing. We would use the timed invokeAll overload, fail explicitly if any tasks are cancelled or unfinished, and retain the finally cleanup. A timeout only on the later get() calls would not bound the preceding invokeAll wait.
| // JSON operations are contained by the catch; no caller-supplied values enter the envelope. | ||
| @Suppress("UnsafeThirdPartyFunctionCall") | ||
| try { | ||
| JSONObject() |
There was a problem hiding this comment.
nit: this one looks bit more readable
try {
JSONObject(
mapOf(
"sdk" to mapOf("instance_id" to instanceId),
"fetch" to mapOf(
"sequence" to sequence,
"trigger" to trigger.wireValue
)
)
)
.toString()
.takeIf { it.toByteArray(Charsets.UTF_8).size <= MAX_ENVELOPE_BYTES }
} catch (_: Exception) {
null
}| datadogContext: DatadogContext, | ||
| diagnostics: String? | ||
| ): String? { | ||
| val request = requestFactory.create(context, datadogContext, diagnostics) ?: return null |
There was a problem hiding this comment.
nit:
return requestFactory.create(context, datadogContext, diagnostics)
?.let(::executeDownloadRequest)| @Test | ||
| fun `M emit only three fields with UUIDv7 identity W accepting logical fetches`() { | ||
| val owner = FlagsRequestDiagnostics(1_700_000_000_000) | ||
| val first = JSONObject(owner.accept()!!) |
There was a problem hiding this comment.
val first = JSONObject(checkNotNull(wner.accept())| @Test | ||
| fun `M emit only three fields with UUIDv7 identity W accepting logical fetches`() { | ||
| val owner = FlagsRequestDiagnostics(1_700_000_000_000) | ||
| val first = JSONObject(owner.accept()!!) |
There was a problem hiding this comment.
Btw would it be prettier to generate Json here, and compare strings instead?
the assertions is almost impossible to understand in such form:
val expected = JSONObject(
mapOf(
"sdk" to mapOf("instance_id" to instanceId),
"fetch" to mapOf(
"sequence" to sequence,
"trigger" to trigger.wireValue
)
)
)
.toString()
assertThat(actual).isEqual(expected) WDYT?
| ) | ||
|
|
||
| whenever(mockAssignmentsDownloader.readPrecomputedFlags(context, fakeDatadogContext)) | ||
| whenever(mockAssignmentsDownloader.readPrecomputedFlags(eq(context), eq(fakeDatadogContext), any())) |
There was a problem hiding this comment.
here and below: is it possible to use expected value instead of any() ?
05fcdef to
5b892de
Compare
Capture typed diagnostics with each logical fetch and serialize them directly in the precomputed request body for edge request-log correlation.
5b892de to
61f6dce
Compare
What does this PR do?
References FFLSDK-272.
Send 3 new fields in the precomputed assignments POST:
sdk.instance_id- uniquely identifies the in-memory configuration-owning client instancefetch.sequence- a logical fetch operation in the SDK's lifecyclefetch.trigger- the SDK's internal accounting of what triggered the fetchMotivation
Distinguish configuration-owning SDK instances and logical fetches when investigating repeated assignment requests in edge request logs.
Implementation
EvaluationsManagerowns the UUIDv7 instance ID and fetch sequence. Each accepted fetch captures an immutable diagnostics value, which travels with that fetch to the existing request factory. The factory adds its fields directly to the structured request body and serializes the complete body once.The in-package
UUIDv7Generatorsupplies the instance ID. A diagnostics trigger enum usesinitializationandcontext_change, consistently across SDKs.Additional Notes
Local validation: all 613 Flags unit tests, Detekt, ktlint and API checks passed. The API check used the unchanged generated reference with apiDump excluded to avoid the existing Gradle task-ordering conflict. Tests cover instance identity, logical sequencing, concurrent acceptance and the complete request body. Verify deployed edge/proxy acceptance and the EVP schema prerequisite before SDK release.
Review checklist (to be filled by reviewers)