YTDB-165: Ytdb 165 add non throwing get to cas array clean rebased#671
YTDB-165: Ytdb 165 add non throwing get to cas array clean rebased#671artem (logart) wants to merge 11 commits into
Conversation
Summary of ChangesHello artem (@logart), I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request refines the Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request modifies CASObjectArray to allow non-throwing get operations and introduces new test dependencies along with a multi-threaded test class. The primary modification involves adding a defaultPlaceholder field and a getOrEmpty method, along with an overloaded get method that accepts a throwOnOutOfBounds boolean, enhancing flexibility in element retrieval. However, the new get and set methods lack proper validation for negative indices, potentially leading to infinite loops and Denial of Service. Additionally, the default constructor introduces a regression where the set(int, T) method reliably crashes with a NullPointerException due to an uninitialized placeholder. Ensure explicit bounds checking is added and the default placeholder is safely handled.
| private T get(int index, boolean throwOnOutOfBounds) { | ||
| final var size = this.size.get(); | ||
|
|
||
| if (size <= index) { | ||
| throw new ArrayIndexOutOfBoundsException("Requested " + index + ", size is " + size); | ||
| if (throwOnOutOfBounds) { | ||
| throw new ArrayIndexOutOfBoundsException("Requested " + index + ", size is " + size); | ||
| } | ||
| return defaultPlaceholder; | ||
| } |
There was a problem hiding this comment.
The get method (and by extension getOrEmpty) does not properly validate that the index parameter is non-negative, potentially leading to a Denial of Service. The existing check if (size <= index) fails for negative indices because size is always non-negative. For index < -1, the code calculates a containerIndex of 31 and enters an infinite loop of Thread.yield() while waiting for a container that will never be created. A check for index < 0 should be added to ensure the method fails safely for all out-of-bounds indices.
There was a problem hiding this comment.
This is fixed right in the beginning of the method.
| public T getOrEmpty(int index) { | ||
| return get(index, false); |
| public T get(int index) { | ||
| return get(index, true); |
| @Test | ||
| public void shouldThrowIfIndexIsOutOfBounds() { | ||
| final var array = new CASObjectArray<Integer>(); | ||
|
|
||
| Assert.assertThrows(IndexOutOfBoundsException.class, () -> array.get(0)); |
| @Test | ||
| public void shouldReturnNullIfElementDoesNotExistsLikeAMap() { | ||
| final var array = new CASObjectArray<Integer>(); | ||
|
|
||
| Assert.assertNull(array.getOrEmpty(0)); |
| @Test | ||
| public void shouldReturnDefaultPlaceholderIfElementDoesNotExists() { | ||
| Integer defaultPlaceholder = (int) (Math.random() * Integer.MAX_VALUE); | ||
| final var array = new CASObjectArray<>(defaultPlaceholder); | ||
|
|
||
| Assert.assertSame(defaultPlaceholder, array.getOrEmpty(0)); |
Qodana for JVM46 new problems were found
💡 Qodana analysis was run in the pull request mode: only the changed files were checked Contact Qodana teamContact us at qodana-support@jetbrains.com
|
| public void set(int index, T value) { | ||
| if (defaultPlaceholder == null) { | ||
| throw new UnsupportedOperationException( | ||
| "set without placeholder only works with new CASObjectArray(defaultPlaceholder) constructed object." |
There was a problem hiding this comment.
String concatenation produces ...object.Please use...
There was a problem hiding this comment.
Sorry, I don't get what should use instead.
| return container.compareAndSet(indexInsideContainer, oldValue, value); | ||
| } | ||
|
|
||
| public T getOrEmpty(int index) { |
There was a problem hiding this comment.
getOrDefault is more appropriate
|
|
||
| @Test | ||
| public void shouldReturnDefaultPlaceholderIfElementDoesNotExists() { | ||
| Integer defaultPlaceholder = (int) (Math.random() * Integer.MAX_VALUE); |
There was a problem hiding this comment.
That is non-reproducable test as seed is not printed.
| private final T defaultPlaceholder; | ||
|
|
||
| public CASObjectArray() { | ||
| this(null); |
There was a problem hiding this comment.
seems like null can not be passed, does not it ?
There was a problem hiding this comment.
It could, by default we are setting null for the new element(legacy behaviour which I want to keep).
That's why when you use set(index, value, placholder) method the placeholder is mandatory and non null.
With the new api I've added you either need to:
- Use non default constructor with placeholder, this way you are free to use both set(index, value) and set(index, value, placeholder) methods, or
- Use default constructor like it used to be, but then set(index, value) will throw an exception since null placeholder is indeed not allowed.
I've thought about this design for a while, and I don't really like it, but I don't know how to make this change backward compatible in other way.
| </configuration> | ||
| </plugin> | ||
| <plugin> | ||
| <groupId>org.pastalab.fray.maven</groupId> |
There was a problem hiding this comment.
The core module's surefire is configured with surefire-junit47 , are fray tests really executed
068af4e to
df30def
Compare
Coverage Gate ResultsThresholds: 85% line, 70% branch Line Coverage: ✅ 89.5% (17/19 lines)
Branch Coverage: ✅ 83.3% (10/12 branches)
|
Mutation Testing Gate ResultsThreshold: 85% ✅ No mutations found — mutation gate skipped. |
|
artem (@logart) do we need this one ? |
df30def to
4008d6d
Compare
|
Andrii Lomakin (@andrii0lomakin) not sure how to reply to specific comment, so adding a link: #671 (comment) If you mean PR, yes, I am integrating your vmlense configuration from develop and checking if all my tests pass. |
4008d6d to
322da95
Compare
e35566c to
fa5faad
Compare
Test Count Gate ResultsTolerance: 5% drop allowed per module Overall: ✅ 19959 tests (baseline: 19944, +15)
|
Motivation:
While implementing #YTDB-165, I have a place where I do casArray.get(pageIndex) to check if the page is in the readCache already.
This operation fails for all new pages since they are not in the array yet.
Based on the logic of the method, I will then cache those pages and add them to the array.
Solution:
Add a flag (I know this is bad, but I feel like copying the method is even worse...) to the get method of CASObjectArray, so it will define either throw on out of bound (array behaviour) or return null (map behaviour).
An alternative solution would be to create an atomic operation. Something along the lines like getOrSet which will get the existing value or (if it is missing) set a computed one.
https://youtrack.jetbrains.com/issue/YTDB-495/Make-CASObjectArray-behave-like-a-map