55import java .util .LinkedHashMap ;
66import java .util .List ;
77import java .util .Map ;
8+ import java .util .concurrent .locks .ReadWriteLock ;
9+ import java .util .concurrent .locks .ReentrantReadWriteLock ;
810
911/**
1012 * Internal storage for feature flags that maintains insertion order.
1315class FeatureFlagStore {
1416 // LinkedHashMap maintains insertion order
1517 private final Map <String , String > flags = new LinkedHashMap <String , String >();
18+ private final ReadWriteLock lock = new ReentrantReadWriteLock ();
1619
1720 /**
1821 * Add a feature flag with the specified name and variant.
1922 * If the name already exists, the variant will be updated without changing position.
2023 *
21- * @param name the feature flag name
24+ * @param name the feature flag name
2225 * @param variant the feature flag variant (can be null)
2326 */
24- synchronized void addFeatureFlag (String name , String variant ) {
27+ void addFeatureFlag (String name , String variant ) {
2528 if (name == null || name .isEmpty ()) {
2629 return ;
2730 }
28- flags .put (name , variant );
31+ lock .writeLock ().lock ();
32+ try {
33+ flags .put (name , variant );
34+ } finally {
35+ lock .writeLock ().unlock ();
36+ }
2937 }
3038
3139 /**
@@ -34,14 +42,20 @@ synchronized void addFeatureFlag(String name, String variant) {
3442 *
3543 * @param featureFlags the feature flags to add
3644 */
37- synchronized void addFeatureFlags (Collection <FeatureFlag > featureFlags ) {
38- if (featureFlags == null ) {
45+ void addFeatureFlags (Collection <FeatureFlag > featureFlags ) {
46+ if (featureFlags == null || featureFlags . isEmpty () ) {
3947 return ;
4048 }
41- for (FeatureFlag flag : featureFlags ) {
42- if (flag != null ) {
43- addFeatureFlag (flag .getName (), flag .getVariant ());
49+
50+ lock .writeLock ().lock ();
51+ try {
52+ for (FeatureFlag flag : featureFlags ) {
53+ if (flag != null ) {
54+ flags .put (flag .getName (), flag .getVariant ());
55+ }
4456 }
57+ } finally {
58+ lock .writeLock ().unlock ();
4559 }
4660 }
4761
@@ -50,40 +64,63 @@ synchronized void addFeatureFlags(Collection<FeatureFlag> featureFlags) {
5064 *
5165 * @param name the feature flag name to remove
5266 */
53- synchronized void clearFeatureFlag (String name ) {
67+ void clearFeatureFlag (String name ) {
5468 if (name != null ) {
55- flags .remove (name );
69+ lock .writeLock ().lock ();
70+ try {
71+ flags .remove (name );
72+ } finally {
73+ lock .writeLock ().unlock ();
74+ }
5675 }
5776 }
5877
5978 /**
6079 * Remove all feature flags.
6180 */
62- synchronized void clearFeatureFlags () {
63- flags .clear ();
81+ void clearFeatureFlags () {
82+ lock .writeLock ().lock ();
83+ try {
84+ flags .clear ();
85+ } finally {
86+ lock .writeLock ().unlock ();
87+ }
6488 }
6589
6690 /**
6791 * Get a list of all feature flags in insertion order.
6892 *
6993 * @return an unmodifiable list of feature flags
7094 */
71- synchronized List <FeatureFlag > toList () {
72- List <FeatureFlag > result = new ArrayList <FeatureFlag >(flags .size ());
73- for (Map .Entry <String , String > entry : flags .entrySet ()) {
74- result .add (new FeatureFlag (entry .getKey (), entry .getValue ()));
95+ List <FeatureFlag > toList () {
96+ lock .readLock ().lock ();
97+ try {
98+ List <FeatureFlag > result = new ArrayList <>(flags .size ());
99+ for (Map .Entry <String , String > entry : flags .entrySet ()) {
100+ FeatureFlag flag = FeatureFlag .of (entry .getKey (), entry .getValue ());
101+ if (flag != null ) {
102+ result .add (flag );
103+ }
104+ }
105+ return result ;
106+ } finally {
107+ lock .readLock ().unlock ();
75108 }
76- return result ;
77109 }
78110
79111 /**
80112 * Create a copy of this store with all the same flags.
81113 *
82114 * @return a new FeatureFlagStore with the same flags
83115 */
84- synchronized FeatureFlagStore copy () {
116+ FeatureFlagStore copy () {
85117 FeatureFlagStore copy = new FeatureFlagStore ();
86- copy .flags .putAll (this .flags );
118+ lock .readLock ().lock ();
119+ try {
120+ copy .flags .putAll (this .flags );
121+ } finally {
122+ lock .readLock ().unlock ();
123+ }
87124 return copy ;
88125 }
89126
@@ -94,14 +131,29 @@ synchronized FeatureFlagStore copy() {
94131 *
95132 * @param other the other store to merge from
96133 */
97- synchronized void merge (FeatureFlagStore other ) {
98- if (other == null ) {
134+ void merge (FeatureFlagStore other ) {
135+ if (other == null || other == this ) {
99136 return ;
100137 }
101- synchronized (other ) {
102- for (Map .Entry <String , String > entry : other .flags .entrySet ()) {
103- flags .put (entry .getKey (), entry .getValue ());
138+
139+ // Warning: this *looks* like a classic deadlock pattern, but because this method is only ever called
140+ // with isolated copies of FeatureFlagStore, the locks will never actually be contended.
141+ // If this method were to be called with two live stores, then it would be possible for a deadlock to occur.
142+ other .lock .readLock ().lock ();
143+ try {
144+ // we don't use other.size() because it grabs a lock.readLock() again
145+ if (other .flags .isEmpty ()) {
146+ return ;
104147 }
148+
149+ lock .writeLock ().lock ();
150+ try {
151+ flags .putAll (other .flags );
152+ } finally {
153+ lock .writeLock ().unlock ();
154+ }
155+ } finally {
156+ other .lock .readLock ().unlock ();
105157 }
106158 }
107159
@@ -110,7 +162,12 @@ synchronized void merge(FeatureFlagStore other) {
110162 *
111163 * @return the number of feature flags
112164 */
113- synchronized int size () {
114- return flags .size ();
165+ int size () {
166+ lock .readLock ().lock ();
167+ try {
168+ return flags .size ();
169+ } finally {
170+ lock .readLock ().unlock ();
171+ }
115172 }
116173}
0 commit comments