-
Notifications
You must be signed in to change notification settings - Fork 205
Make FakeValuesService thread safe #770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report
📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more @@ Coverage Diff @@
## main #770 +/- ##
============================================
- Coverage 92.54% 92.27% -0.28%
- Complexity 2654 2662 +8
============================================
Files 286 287 +1
Lines 5274 5309 +35
Branches 553 552 -1
============================================
+ Hits 4881 4899 +18
- Misses 252 269 +17
Partials 141 141
... and 1 file with indirect coverage changes 📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible/practical to add tests?
The changes seem sane to me. I haven’t pulled and used them at all. Or, looked for any places updates might have been missed (I feel like other probably know the code base better than I do).
that's still a tricky part to create a 100% reproducible test for failing non thread safe FakeValuesService... |
Would this be something @rwong-gw could maybe help validate? |
For the past couple days, I have been using CI to run my Gatling tests every half hour with this version, and everything looks good, with no hanging or deadlocks. One suggestion: |
makes sense, added implementation |
@@ -48,26 +48,28 @@ public class FakeValuesService { | |||
private static final String[] EMPTY_ARRAY = new String[0]; | |||
private static final Logger LOG = Logger.getLogger("faker"); | |||
|
|||
private final Map<SingletonLocale, FakeValuesInterface> fakeValuesInterfaceMap = new IdentityHashMap<>(); | |||
private final StampedLockMap<SingletonLocale, FakeValuesInterface> fakeValuesInterfaceMap = new StampedLockMap<>(IdentityHashMap::new); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we don't use any StampedLockMap
specific functions, isn't it better to keep type unchanged (as Map
) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, for some of them it makes sense, for some not since there is updateNestedValue
method specific for StampedLockMap
} finally { | ||
lock.unlockRead(stamp); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you reuse doROOperation
function here? Looks like a duplicate of code...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in fact it was kind of test version...
As measurement shows here it's that rare case when code duplication wins. With doROOperation
it becomes 10% slower.... The reason is that it requires Supplier
object which is generated during runtime. Without this method and this object generation there is no need for that extra object.
Funny thing is that I finally adapted the test from #759 attached by @rwong-gw At the same time another option I had in mind was CopyOnWriteMaps ( So I replaced it with @kingthorin so at least one test is added |
} | ||
|
||
@Override | ||
public V put(K key, V value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function should be synchronized
otherwise it might produce incorrect result, don't you think?
For example 2 threads will access this function at the same time, one of the pair of key-value will be lost...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that's the main idea of COW approach: it is lock free.
Ok let's consider the case when 2 threads accessing this map.
Yes, in worst case one of the value will be lost. However it is ok.
We use this as a cache. It means that the lost value will be recalculated and put into map next time.
So there is no issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
} | ||
|
||
@Override | ||
public V remove(Object key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same, don't you think that all the modification functions should be synchronized
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see the answer above
} | ||
|
||
@Override | ||
public void putAll(Map<? extends K, ? extends V> m) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
synchronized
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see the answer above
} | ||
|
||
@Override | ||
public void clear() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
synchronized
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see the answer above
Map<K, V> newMap = mapSupplier.get(); | ||
newMap.putAll(map); | ||
final V result = newMap.remove(key); | ||
map = newMap; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it better to keep the internal map as unmodifiable? Just for safety.
Like:
map = Collections.unmodifiableMap(newMap);
@snuyanzin I'll give it a try. |
2883fcd
to
9616a24
Compare
06743ef
to
03675f4
Compare
merging since it contains a test provided at #759 |
The PR assumes that most of the time there is no writes to the maps and only reads.
In this case there could be used optimistic locks most of the time and cheapStampedLock
'sbased on measurements there is overhead only about 10% for some of methods, for others there is no noticeable differencee.g.
Datafaker_KotlinFakerBenchmark.kotlinFakerTest
before PR
after (10% slower)
DatafakerSimpleMethods.firstname
before
after (about 10% slower)
DatafakerSimpleMethods.fullname
before
after (about 10% slower)
UPD: a new approach passing added test is with
COWMap