Repository navigation
Commit ebe9513
fix: Synchronize provider state access on the state lock (#54)
`getState()` synchronized on the `state` field's value instead of the
`stateLock` monitor, so reads were never mutually exclusive with writes.
- `getState()` now locks `stateLock`, matching `setState`
- `initialize` sets the ready state through `setState` and reads it
through `getState` instead of touching the field unlocked
- No behavior change for callers beyond correct visibility of state
transitions
@cursor review
<details>
<summary>Implementation details</summary>
**Root cause**
`synchronized (state)` locks on the `ProviderState` enum constant
currently referenced by the field. A reader holding the monitor of
`ProviderState.NOT_READY` does not exclude a writer holding `stateLock`,
so state transitions had no happens-before relationship with reads.
Because enum constants are JVM-wide singletons, it also meant contending
on a monitor shared with any other code that happens to lock the same
constant.
`initialize` also wrote `state = ProviderState.READY` and later read
`state` without any lock, on a different thread than the data source
status listener that mutates it.
**Alternatives considered**
Making the field `volatile` would fix visibility, but the `VALID` branch
of `handleDataSourceStatus` needs a compare-and-set over the field, so
the lock is still required; keeping a single lock is simpler than mixing
both.
**Testing**
`./gradlew test` — all 44 tests pass. The existing `LifeCycleTest` cases
already assert the state transitions this lock protects (`NOT_READY` →
`READY`, and → `ERROR` on a failed data source); a test cannot
deterministically observe the previous incorrect locking, so no new test
is added.
No visual preview applies — this is a server-side provider change.
</details>
Link to Devin session:
https://app.devin.ai/sessions/0c452d209ec54b068ba120b4c92b8f6c
Requested by: @kinyoklion
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Overview**
> Fixes incorrect locking around the OpenFeature provider lifecycle
state so reads and writes use the same monitor.
>
> `getState()` now synchronizes on `stateLock` instead of on the
`ProviderState` enum value held in the field, which did not exclude
concurrent updates from `setState` and could contend on JVM-wide enum
monitors. **`initialize`** routes the early-ready path through
`setState(READY)` and the readiness check through `getState()` instead
of assigning or reading the `state` field without the lock.
>
> Callers should see the same transitions, with correct visibility and
mutual exclusion when the data source listener updates state on another
thread.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
dfc2c89. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>1 parent 1f71ef3 commit ebe9513
1 file changed
Lines changed: 3 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
142 | | - | |
| 142 | + | |
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
| |||
149 | 149 | | |
150 | 150 | | |
151 | 151 | | |
152 | | - | |
| 152 | + | |
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
| |||
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
166 | | - | |
| 166 | + | |
167 | 167 | | |
168 | 168 | | |
169 | 169 | | |
| |||
0 commit comments