-
Notifications
You must be signed in to change notification settings - Fork 122
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
#2827: Solved issue with thread-unsafety of CommitHashesText #3114
#2827: Solved issue with thread-unsafety of CommitHashesText #3114
Conversation
eo-maven-plugin/src/test/java/org/eolang/maven/hash/CommitHashesTextTest.java
Show resolved
Hide resolved
@volodya-lombrozo please review |
@@ -45,4 +49,23 @@ void downloadsDefaultList() throws Exception { | |||
Matchers.containsString("master") | |||
); | |||
} | |||
|
|||
@Test | |||
void isThreadSafe() { |
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.
@levBagryansky Can we implement it simpler?
@Test
void isThreadSafe() {
MatcherAssert.assertThat(
"Can be used in different threads without NPE",
IntStream.range(0, 200)
.parallel()
.mapToObj(i -> new CommitHashesText())
.map(UncheckedText::new)
.map(UncheckedText::asString)
.allMatch(Objects::nonNull),
Matchers.equalTo(true)
);
}
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.
@volodya-lombrozo We need CountDownLatch to make test better. See https://www.yegor256.com/2018/03/27/how-to-test-thread-safety.html
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.
@levBagryansky It's a good point, actually, thank you. However, do we really need to make this test better? If my test fails without Synced
it means, it is sufficient enough. I believe we can sacrifice this accuracy in favour of simplicity. What do you think?
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.
@volodya-lombrozo It does not fail without Synced
sometimes even now.
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.
@volodya-lombrozo We also want have different 200 threads but .parallel
does not give it
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.
@levBagryansky BTW, I tried to run your test and it doesn't fail without Synced
, as well, as my solution. How do you reproduce the problem?
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.
@levBagryansky And maybe we don't need all of these 200 threads?
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.
@volodya-lombrozo run again
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.
@volodya-lombrozo I did initially this way , but Pqulice didn't like it.
@volodya-lombrozo BTW I think there is a problem with testing of such thing. Note, |
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.
@levBagryansky Maybe I miss something. So, please, correct me, if I'm wrong somewhere:
- I pulled your changes
- I reverted all the changes from
CommitHashesText.java
(removedSynced
) - Run your test
isThreadSafe
(1000 times) - All the tests pass.
So, I don't understand the intention of this test and this changes since everything works just well without them.
@volodya-lombrozo This test must be started first because of
If other test already computed |
@yegor256 We have a problem with testing thread-safety of private static final Text CACHE = new Sticky(
CommitHashesText.asText(CommitHashesText.HOME)
) Here
|
@levBagryansky maybe the |
@yegor256 according to documentation other tests are not running while this test is executing.
This is necessary for the test, but the problem is that usually previous tests already compute the |
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.
@volodya-lombrozo please check
private static final Text CACHE = new Sticky( | ||
CommitHashesText.asText(CommitHashesText.HOME) | ||
); | ||
private static final String CACHE = CommitHashesText.asText(CommitHashesText.HOME); |
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.
@volodya-lombrozo please take a look. I think we do not need here Sticky
at all. Note, that
CommitHashesText.asText(CommitHashesText.HOME)
is calling here anyway, why do we need to wrap it to Sticky
. Also, in CommitHashesText.asText(CommitHashesText.HOME)
String
is already calulated but is wrapping to Text
.
@levBagryansky maybe we should create a new annotation for JUnit: |
@yegor256 according to https://stackoverflow.com/questions/3921322/how-to-fork-jvm, Looks like we would do |
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.
Context
I have found that the original problem occurred in CommitHashesMap
, not in CommitHashesText
directly. Here is the log from the last failed pipelines:
2024-04-25T02:12:28.4282253Z Caused by: java.lang.NullPointerException
2024-04-25T02:12:28.4283068Z at org.cactoos.text.TextOfScalar.asString(TextOfScalar.java:58)
2024-04-25T02:12:28.4284055Z at org.cactoos.text.TextEnvelope.asString(TextEnvelope.java:51)
2024-04-25T02:12:28.4285043Z at org.cactoos.text.TextEnvelope.asString(TextEnvelope.java:51)
2024-04-25T02:12:28.4285950Z at org.cactoos.text.Split.lambda$new$0(Split.java:127)
2024-04-25T02:12:28.4286757Z at org.cactoos.scalar.Checked.value(Checked.java:76)
2024-04-25T02:12:28.4287580Z at org.cactoos.scalar.IoChecked.value(IoChecked.java:63)
2024-04-25T02:12:28.4288427Z at org.cactoos.scalar.Unchecked.value(Unchecked.java:56)
2024-04-25T02:12:28.4289336Z at org.cactoos.iterable.IterableOf.iterator(IterableOf.java:83)
2024-04-25T02:12:28.4290277Z at org.cactoos.iterable.Mapped.lambda$new$0(Mapped.java:61)
2024-04-25T02:12:28.4291120Z at org.cactoos.scalar.Checked.value(Checked.java:76)
2024-04-25T02:12:28.4292141Z at org.cactoos.scalar.IoChecked.value(IoChecked.java:63)
2024-04-25T02:12:28.4293001Z at org.cactoos.scalar.Unchecked.value(Unchecked.java:56)
2024-04-25T02:12:28.4293949Z at org.cactoos.iterable.IterableOf.iterator(IterableOf.java:83)
2024-04-25T02:12:28.4295104Z at org.cactoos.iterable.IterableEnvelope.iterator(IterableEnvelope.java:53)
2024-04-25T02:12:28.4296364Z at org.cactoos.iterable.IterableEnvelope.iterator(IterableEnvelope.java:53)
2024-04-25T02:12:28.4297728Z at org.cactoos.iterable.Mapped.lambda$new$0(Mapped.java:61)
2024-04-25T02:12:28.4298639Z at org.cactoos.scalar.Checked.value(Checked.java:76)
2024-04-25T02:12:28.4299499Z at org.cactoos.scalar.IoChecked.value(IoChecked.java:63)
2024-04-25T02:12:28.4300385Z at org.cactoos.scalar.Unchecked.value(Unchecked.java:56)
2024-04-25T02:12:28.4301344Z at org.cactoos.iterable.IterableOf.iterator(IterableOf.java:83)
2024-04-25T02:12:28.4302498Z at org.cactoos.iterable.IterableEnvelope.iterator(IterableEnvelope.java:53)
2024-04-25T02:12:28.4303514Z at org.cactoos.map.MapOf.make(MapOf.java:189)
2024-04-25T02:12:28.4304257Z at org.cactoos.map.MapOf.<init>(MapOf.java:176)
2024-04-25T02:12:28.4305245Z at org.eolang.maven.hash.CommitHashesMap.fromTable(CommitHashesMap.java:93)
2024-04-25T02:12:28.4306638Z at org.eolang.maven.hash.CommitHashesMap.<init>(CommitHashesMap.java:69)
2024-04-25T02:12:28.4307813Z at org.eolang.maven.hash.CommitHashesMap.<init>(CommitHashesMap.java:53)
2024-04-25T02:12:28.4308998Z at org.eolang.maven.SafeMojo.<init>(SafeMojo.java:219)
2024-04-25T02:12:28.4309893Z at org.eolang.maven.PrintMojo.<init>(PrintMojo.java:57)
2024-04-25T02:12:28.4310580Z ... 61 more
Of course, these classes are connected, but mentioning this in the issue or in the PR would help to investigate the problem faster and it would add some sort of context. This information usually helps to create an appropriate unit test. And what is the most important, it will free a reviewer from searching for these logs.
Please add more helpful information and a human-readable description for your PRs or issues. It will significantly speed up the review process.
Sticky
Now, let's return to the problem. Actually, by removing the Sticky
usage, you solved the issue. Why? Just run this test:
@Test
void revealsProblemWithSticky() {
for (int j = 0; j < 10_000; ++j) {
final Text shared = new Sticky(() -> "oops");
MatcherAssert.assertThat(
"Shared text should be thread-safe, but it wasn't",
IntStream.range(0, 200)
.parallel()
.mapToObj(i -> shared)
.map(UncheckedText::new)
.map(UncheckedText::asString)
.allMatch(Objects::nonNull),
Matchers.equalTo(true)
);
}
}
And you will see that it fails, since the Sticky
doesn’t guarantee thread-safety.
What about our case?
I would suggest writing something as follows:
@Test
void revealsProblemWithCache() {
for (int j = 0; j < 10_000; ++j) {
final Sticky cache = new Sticky(
() -> "15c85d7f8cffe15b0deba96e90bdac98a76293bb 0.23.17"
);
MatcherAssert.assertThat(
"Should calculate the hash of the commit without an NPE, but it didn't",
IntStream.range(0, 200)
.parallel()
.mapToObj(i -> new CommitHashesMap(new CommitHashesText(cache)::asString))
.map(chm -> chm.get("0.23.17"))
.allMatch(Objects::nonNull),
Matchers.equalTo(true)
);
}
}
I would recommend removing the isThreadSafe
test. Regarding #3141 , I don’t think we need to go so far; the test above should be sufficient.
|
The best solution I see is to remove the static field |
|
@yegor256 Let's just merge this |
@rultor merge |
Closes #2827
PR-Codex overview
The focus of this PR is to refactor the
CommitHashesText
class by replacingorg.cactoos.Text
withjava.lang.String
for simplicity and efficiency.Detailed summary
org.cactoos.Text
withjava.lang.String
inCommitHashesText
classCACHE
asText
method return type tojava.lang.String
isThreadSafe
to check thread safety ofCommitHashesText