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
ZOOKEEPER-3495: fix SnapshotDigestTest to work with JDK12+ #1056
Conversation
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 approach looks good to me.
I left a comment.
I have spent a few cycles on this problem during jdk12 early access stage.
There would be other tricks to tweak constant values in tests, like https://github.com/eolivelli/tweakjava
But in this case it is better to use your approach that will be valid for the long term
} | ||
|
||
|
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.
This is not final, why don't you add a setDigestEnabled method?
Make it 'volatile'
We won't lose much in performances
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.
This patch still uses reflection, so it seems to me that we're shooting our other leg. I can be convinced though, but I think there must be a very cool design pattern for this which we can test easily without using reflection. See my comments first and let me think about it.
zookeeper-server/src/test/java/org/apache/zookeeper/server/DigestCalculatorTestUtil.java
Outdated
Show resolved
Hide resolved
|
||
public static final DigestCalculator DIGEST_CALCULATOR; |
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.
What if we make DigestCalculator
interface, remove final from here and mock it easily in tests?
What's the benefit of making it final?
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 that would work too, and mocking is more elegant than using plain reflection in the tests. (I think with Mockito you can even mock the class and you don't even need an intreface)
The only issue with the removal of the final
modifier is that from that point anywhere from the production code you can easily change this singleton. But we can write a comment that it is to be changed only from tests...
@anmolnar I suggested to add a setter method, so mo more need for reflection |
if we want to avoid both reflection and mocking, then we actually need two setter methods, one for the setEnable and one for the setVersion. Both of the fields can be made volatile in this case I think. we can add |
For VisibleForTesting follow the examples you found, we don't have a rule yet |
I think this code is not perfectly organised:
|
good idea, I will try this way |
The DIGEST_VERSION is made to be static final to make sure it won't be changed in code with things like setVersion. It seems to be safer and cleaner to me with the current code implementation, for the test if we want to avoid reflection we add a pre-defined snapshot file with different digest version (like -1), and test the difference version case. Also DigestCalculator is not only used in DataTree, it will also being used in PrepRequestProcessor when adding real time digest on txn, so this class is more about a util which could be replaced with different digest method. I'm preparing the PR in the last few days, will send the PR probably today. |
Thanks for pointing this out! Let's wait for your PR to be submitted / merged, before finalizing this one. Can you link the jira ticket / PR here?
I think with the solution proposed by @anmolnar it is still possible to keep the DIGEST_VERSION to be static final and make it impossible to change it from the code accidentally. We only create a default constructor for I will submit a new commit here, trying out @anmolnar's proposal, then we can see if everyone likes it or not :) - but let's wait with the merge until I merged with @lvfangmin's change... |
Would you please be a little bit more specific here? I don't see what code changes are u trying to protect DIGEST_VERSION from and why would my proposal brake that? I see the opposite: making it package-private is as safe as static final while it makes the code cleaner and testable at the same time.
Why? Could you please be more specific?
No problem with that. It can still use the interface to be injected as constructor dependency same way as I'm looking forward to your patch and see how unit testing is carried out. I really want to avoid reflection everywhere, because we can clearly see now how fragile it could be. Additionally your proposal about preparing suitable snapshot files could be working, but that also breaks unit testing principles. Let's see @symat 's updated patch and talk about which way to go forward. |
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.
Getting better. I still think using interface would make the code even more cleaner.
zookeeper-server/src/main/java/org/apache/zookeeper/server/NodeHashMapImpl.java
Outdated
Show resolved
Hide resolved
} | ||
|
||
private boolean digestEnabled; | ||
|
||
public DigestCalculator() { |
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.
Move it to the same package as DataTree
lives and make package-private everything.
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.
@lvfangmin mentioned that he will use the DigestCalculator from other places, so I would wait for his PR. If it turns out still to be used only from this package, then I will move 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.
He mentioned PrepRequestProcessor
which is also in server
package, so it should be even better there.
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.
Let's make the class 'final' it will slightly help the JVM for optimizations.
We had 'static' methods that were not overridable.
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.
This is a good idea. I tried it, but in SnapshotDigestTest.testDifferentDigestVersion
we create a Mockito Spy on the class and apparently you can not create Mockito Spy on a final class.
So instead of this I take @anmolnar 's original idea and moved this class to the server
package and made it packet private, so at least it can not be overwritten from non-ZK code. Let's hope it will work also for @lvfangmin. If not, then we can move back...
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.
Besides PrepRequestProcessor, SerializeUtils will also reference to DigestCalculator when deserialize digest from txn:
https://github.com/apache/zookeeper/pull/1059/files#diff-d21faf00209dfd3c78b50788f07474ffR127.
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.
@lvfangmin I think that DigestCalculator
is an internal utility of DataTree
and should not be exposed to anywhere else. PrepRequestProcessor
could do better to access properties like digestEnabled
and DIGEST_VERSION
through DataTree
getters. Like:
zks.getZKDatabase().getDataTree().getDigestEnabled();
It can cache the 2 values in the constructor like it's doing currently with digestEnabled
.
Unfortunately SerializeUtils
is a different story. It's another piece of logic which I think should be organised differently. It breaks both encapsulation and responsibility principles. Let me think about if there's a way to get round that.
Did you have time to send your PR? What do you think about the current proposal in general? |
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.
+1 LGTM.
@eolivelli @lvfangmin PTAL. |
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.
} | ||
|
||
private boolean digestEnabled; | ||
|
||
public DigestCalculator() { |
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.
Let's make the class 'final' it will slightly help the JVM for optimizations.
We had 'static' methods that were not overridable.
@@ -219,7 +219,7 @@ public void testPzxidUpdatedWhenDeletingNonExistNode() throws Exception { | |||
@Test | |||
public void testDigestUpdatedWhenReplayCreateTxnForExistNode() { | |||
try { | |||
DigestCalculator.setDigestEnabled(true); | |||
System.setProperty(DigestCalculator.ZOOKEEPER_DIGEST_ENABLED, "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.
This won't have effect of you don't instantiate a new DataTree and the internal DigestCalculator
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.
Nice catch, thanks! I modified the test to create a new DataTree after setting the system property.
(I also made the test cleaner by creating the DataTree now only in the test cases and removed the field from the setUp
method - previously half the tests used the field, half of them not)
@symat Sorry for the lately reply, the other PR has been sent out for review a week ago, you can take a look at here: #1059. |
This version with mockito.spy looks good to me, as long as we don't use things like setVersion. The only downside of this is we'll need to pass the DigestCalculator around after changing it to non-static, probably I'll move the digestEnabled to some which could be shared across different classes. |
private boolean digestEnabled; | ||
|
||
DigestCalculator() { | ||
this.digestEnabled = Boolean.parseBoolean(System.getProperty(ZOOKEEPER_DIGEST_ENABLED, "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.
Probably we should move this setting to ZooKeeperServer, so that it can be shared and used in static classes like SerializeUtil.
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.
That could work.
@symat @eolivelli @lvfangmin Can we move on with this patch? I'd really like to see this test fixed and builds are green. Enrico you're still having -1 on 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.
LGTM
but we should wait for @lvfangmin
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.
+1
Thanks @symat for making this work on JDK 12+, it seems better to move digestEnabled to ZooKeeperServer class.
Thanks @lvfangmin for the comments! I moved the In this way the |
Merged to master. Thanks @symat ! |
The problem with the test `SnapshotDigestTest.testDifferentDigestVersion` was that it used reflection to change a final static value in `DigestCalculator`, what is no longer supported with JDK 12+ (see [JDK-8210522](https://bugs.openjdk.java.net/browse/JDK-8210522)) I think there are still some hacky solutions to go behind these limitations (with [PowerMock](https://github.com/powermock/powermock) maybe or using [VarHandle as suggested here](https://stackoverflow.com/questions/56039341/get-declared-fields-of-java-lang-reflect-fields-in-jdk12)), but I think the best is to avoid the situation when we need to poke the static final field. I changed the fully static `DigestCalculator`, converting it to a static singleton object with non-static methods / fields. The fields of the singleton object can not be modified from production code, but we can change them from the tests even in JDK 12 / 13 (as they are not static final fields). I tested it locally using openjdk, I hope it will also work in jenkins. Author: Mate Szalay-Beko <szalay.beko.mate@gmail.com> Author: Mate Szalay-Beko <mszalay@cloudera.com> Reviewers: fangmin@apache.org, eolivelli@apache.org, andor@apache.org Closes apache#1056 from symat/ZOOKEEPER-3495 and squashes the following commits: fa2a3b3 [Mate Szalay-Beko] Merge remote-tracking branch 'apache/master' into HEAD 67c5532 [Mate Szalay-Beko] ZOOKEEPER-3495 move digestEnabled to the ZooKeeperServer class c58a88c [Mate Szalay-Beko] ZOOKEEPER-3495: implement code-review comments 1a8cc2b [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 90da4c4 [Mate Szalay-Beko] ZOOKEEPER-3495: NodeHashMap use the same DigestCalculator instance as DataTree e1f659c [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 dbbf38f [Mate Szalay-Beko] ZOOKEEPER-3495: fix checkstyle errors 07741ae [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 d0d886d [Mate Szalay-Beko] ZOOKEEPER-3495: change implementation according to code review comments 2a80c32 [Mate Szalay-Beko] ZOOKEEPER-3495: fix SnapshotDigestTest to work with JDK12+
The problem with the test `SnapshotDigestTest.testDifferentDigestVersion` was that it used reflection to change a final static value in `DigestCalculator`, what is no longer supported with JDK 12+ (see [JDK-8210522](https://bugs.openjdk.java.net/browse/JDK-8210522)) I think there are still some hacky solutions to go behind these limitations (with [PowerMock](https://github.com/powermock/powermock) maybe or using [VarHandle as suggested here](https://stackoverflow.com/questions/56039341/get-declared-fields-of-java-lang-reflect-fields-in-jdk12)), but I think the best is to avoid the situation when we need to poke the static final field. I changed the fully static `DigestCalculator`, converting it to a static singleton object with non-static methods / fields. The fields of the singleton object can not be modified from production code, but we can change them from the tests even in JDK 12 / 13 (as they are not static final fields). I tested it locally using openjdk, I hope it will also work in jenkins. Author: Mate Szalay-Beko <szalay.beko.mate@gmail.com> Author: Mate Szalay-Beko <mszalay@cloudera.com> Reviewers: fangmin@apache.org, eolivelli@apache.org, andor@apache.org Closes apache#1056 from symat/ZOOKEEPER-3495 and squashes the following commits: fa2a3b3 [Mate Szalay-Beko] Merge remote-tracking branch 'apache/master' into HEAD 67c5532 [Mate Szalay-Beko] ZOOKEEPER-3495 move digestEnabled to the ZooKeeperServer class c58a88c [Mate Szalay-Beko] ZOOKEEPER-3495: implement code-review comments 1a8cc2b [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 90da4c4 [Mate Szalay-Beko] ZOOKEEPER-3495: NodeHashMap use the same DigestCalculator instance as DataTree e1f659c [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 dbbf38f [Mate Szalay-Beko] ZOOKEEPER-3495: fix checkstyle errors 07741ae [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 d0d886d [Mate Szalay-Beko] ZOOKEEPER-3495: change implementation according to code review comments 2a80c32 [Mate Szalay-Beko] ZOOKEEPER-3495: fix SnapshotDigestTest to work with JDK12+
The problem with the test `SnapshotDigestTest.testDifferentDigestVersion` was that it used reflection to change a final static value in `DigestCalculator`, what is no longer supported with JDK 12+ (see [JDK-8210522](https://bugs.openjdk.java.net/browse/JDK-8210522)) I think there are still some hacky solutions to go behind these limitations (with [PowerMock](https://github.com/powermock/powermock) maybe or using [VarHandle as suggested here](https://stackoverflow.com/questions/56039341/get-declared-fields-of-java-lang-reflect-fields-in-jdk12)), but I think the best is to avoid the situation when we need to poke the static final field. I changed the fully static `DigestCalculator`, converting it to a static singleton object with non-static methods / fields. The fields of the singleton object can not be modified from production code, but we can change them from the tests even in JDK 12 / 13 (as they are not static final fields). I tested it locally using openjdk, I hope it will also work in jenkins. Author: Mate Szalay-Beko <szalay.beko.mate@gmail.com> Author: Mate Szalay-Beko <mszalay@cloudera.com> Reviewers: fangmin@apache.org, eolivelli@apache.org, andor@apache.org Closes apache#1056 from symat/ZOOKEEPER-3495 and squashes the following commits: fa2a3b3 [Mate Szalay-Beko] Merge remote-tracking branch 'apache/master' into HEAD 67c5532 [Mate Szalay-Beko] ZOOKEEPER-3495 move digestEnabled to the ZooKeeperServer class c58a88c [Mate Szalay-Beko] ZOOKEEPER-3495: implement code-review comments 1a8cc2b [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 90da4c4 [Mate Szalay-Beko] ZOOKEEPER-3495: NodeHashMap use the same DigestCalculator instance as DataTree e1f659c [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 dbbf38f [Mate Szalay-Beko] ZOOKEEPER-3495: fix checkstyle errors 07741ae [Mate Szalay-Beko] Merge remote-tracking branch 'origin/master' into ZOOKEEPER-3495 d0d886d [Mate Szalay-Beko] ZOOKEEPER-3495: change implementation according to code review comments 2a80c32 [Mate Szalay-Beko] ZOOKEEPER-3495: fix SnapshotDigestTest to work with JDK12+
The problem with the test
SnapshotDigestTest.testDifferentDigestVersion
was that it used reflection to change a final static value inDigestCalculator
, what is no longer supported with JDK 12+ (see JDK-8210522)I think there are still some hacky solutions to go behind these limitations (with PowerMock maybe or using VarHandle as suggested here), but I think the best is to avoid the situation when we need to poke the static final field.
I changed the fully static
DigestCalculator
, converting it to a static singleton object with non-static methods / fields. The fields of the singleton object can not be modified from production code, but we can change them from the tests even in JDK 12 / 13 (as they are not static final fields).I tested it locally using openjdk, I hope it will also work in jenkins.