[MJARSIGNER-41] Retry and delay feature when signing#13
Conversation
|
Amazing work, thank you. |
elharo
left a comment
There was a problem hiding this comment.
Looks good overall. If the fix for the CI can be separated out and landed first, that would be great.
| * if any network operations are used during signing, for example using a Time Stamp Authority or network based | ||
| * PKCS11 HSM solution for storing code signing keys. | ||
| * | ||
| * The default value of 1 indicate that no retries should be made. |
There was a problem hiding this comment.
indicates
What if someone sets this to zero or a negative number?
There was a problem hiding this comment.
Added method AbstractJarsignerMojo.validateParameters() and overridden in JarsignerSignMojo. Added maxTries and maxRetryDelaySeconds contraint check that will reset to default values and log if checks fail. Also added test cases to verify correct behaviour (see JarsignerSignMojoRetryTest and methods testInvalidMaxTries_zero(), testInvalidMaxTries_negative(), testMaxRetryDelaySeconds(), testInvalidMaxRetryDelaySeconds_negative()).
| throws JavaToolException, MojoExecutionException { | ||
| Commandline commandLine = null; | ||
| int resultCode = 0; | ||
| for (int attempt = 0; attempt < maxTries; attempt++) { |
There was a problem hiding this comment.
this doesn't try even once if the value is 0 or less.
There was a problem hiding this comment.
Fixed via JarsignerSignMojo.validateParameters().
| waitStrategy.waitAfterFailure(attempt, Duration.ofSeconds(maxRetryDelaySeconds)); | ||
| } | ||
| } | ||
| throw new MojoExecutionException(getMessage("failure", getCommandlineInfo(commandLine), resultCode)); |
There was a problem hiding this comment.
This returns the last failure only. Perhaps good enough but there could be multiple different failures
There was a problem hiding this comment.
I have made a minor refactor of the for-loop so that it is clearer in the source code that it is the last failure that is returned. I have also added test case JarsignerSignMojoRetryTest.testLastErrorReturned() to verify this behavior.
| } | ||
|
|
||
| private void defaultWaitStrategy(int attempt, Duration maxRetryDelay) throws MojoExecutionException { | ||
| long delayMillis = (long) (Duration.ofSeconds(1).toMillis() * Math.pow(2, attempt)); |
There was a problem hiding this comment.
possible overflow and wraparound here.
There should likely be a maximum retry length.
There was a problem hiding this comment.
Refactored method to make it testable. Added test case JarsignerSignMojoRetryTest.testDefaultWaitStrategy() that tests/proves that there are no unexpected overflow/wraparound problems (at least not visible from the outside).
There was a problem hiding this comment.
Maybe. Math.pow(2, attempt) still worries me. That can get very big very fast. Perhaps set a maximum on attempts here. That is, don't go above a few minutes. attempt <= 20. You can retry more, though I wouldn't wait more than a few minutes between attempts, no matter how many times.
One or two retries might be OK. But if it fails three times, do we really want to just keep trying?
1c46d0a to
d35a8aa
Compare
|
Thanks for the feedback @elharo! I have fixed the easy things, and also created a dedicated pull request #15 for MJARSIGNER-68! I will look at and fix the rest of the feedback soon (1 to 2 working days)! |
25f8e98 to
8e786d4
Compare
8e786d4 to
fd1fb4f
Compare
| if (this.skip) { | ||
| getLog().info(getMessage("disabled")); | ||
| return; | ||
| } |
There was a problem hiding this comment.
I did an indentation change for this method that my IDE showed nicely, but GitHub diff goes bananas over it. I could change it back. However in the scope of https://issues.apache.org/jira/projects/MJARSIGNER/issues/MJARSIGNER-72 I still plan to refactor this method. I'm not sure if MJARSIGNER-72 will be accepted by the community, but I will still implement it because I have a direct need for it.
fd1fb4f to
2c8b205
Compare
|
I have resolved/fixed all code review comments (at least from my perspective) and have now set pull request back to "ready for review". |
| } | ||
|
|
||
| private void defaultWaitStrategy(int attempt, Duration maxRetryDelay) throws MojoExecutionException { | ||
| long delayMillis = (long) (Duration.ofSeconds(1).toMillis() * Math.pow(2, attempt)); |
There was a problem hiding this comment.
Maybe. Math.pow(2, attempt) still worries me. That can get very big very fast. Perhaps set a maximum on attempts here. That is, don't go above a few minutes. attempt <= 20. You can retry more, though I wouldn't wait more than a few minutes between attempts, no matter how many times.
One or two retries might be OK. But if it fails three times, do we really want to just keep trying?
Implementation of retry and delay feature when signing and test cases
2c8b205 to
529d2cf
Compare
|
I have fixed JUnit version. I don't agree there is any problem in However I have anyway added limit on what value (20) the exponent should have in the function and updated the test cases to reflect this. |
|
I also think it would be good to release a new version of the plugin. For what it is worth I have been running a custom build from the state of master branch the 19th of December in my company. We have probably built over 1000 builds since then. The settings I use:
(also tagging @slachiewicz) |
|
@elharo and @slachiewicz: Is there any plan on making a new release for maven-jarsigner-plugin? I think the features I have implemented would be beneficial for others as well. |
|
@elharo @slachiewicz @hboutemy |
|
Ok I will do it 😄 in a few days |
|
Resolve #66 |
This pull request implements https://issues.apache.org/jira/projects/MJARSIGNER/issues/MJARSIGNER-41 that introduces a retry feature for when jar signing fails (typically for network issues). It also implements a delay feature between retries.
In this pull request I have:
I thought a lot about whether both signing and verification should have the retry feature. In the end I felt the code would be better if only the signing had the retry feature. I’m unsure if any verification may fail due to external circumstances. Perhaps if external Certificate Revocation Lists (CRLs) are used? But I’m unsure if jarsigner performs such a check to an external network resource?
I opted to use
@since 3.1.0declaration(s) for the added parameters. Feel free to change/suggest other values for this!I spent some time thinking about if a linear or exponential backoff algorithm for sleeping should be used. In the end I made a simple exponential backoff implementation with only one configurable parameter (maxRetryDelay). It would be possible to also add configuration for initalDelay (hard coded to 1 second) and multiplier (hard coded to 2), but I choose not to do this to keep things simple.
I considered implementing support for multiple tsa lists. It was suggested by Thorsten Meinl as a patch to https://issues.apache.org/jira/projects/MJARSIGNER/issues/MJARSIGNER-59 and also by @jcompagner in #1 (comment). I think this could be a good idea, but I did not want to “pollute” my pull request. If it was to be implemented I think the way forward is to change type of JarsignerSignMojo.tsa from a String to String[] and let Maven default array handling handle this (it will then handle both using comma separation, or nested XML tags). But my guess is that an adding of tsa list would “force” to also use a list for -tsacert and also the non-existent parameter -tsapolicyid? Because tsa, tsacert and tsapolicyid all belong together as a triplet.
I implemented varargs for the
getMessage()methods to make them cleaner.With the combination of what bdt-stru and lasselindqvist added I ended up adding one log and one obscure exception (that will not happen often) that does not have internationalization (see
JarsignerSignMojo.defaultWaitStrategy()). My own feeling is that if the Mojo configuration site documentation does not have internationalization, is it that important that some corner case log messages also does not have it?I spend a lot of time writing test cases. My motivation for this is that I like tests and that I plan to continue working to implement https://issues.apache.org/jira/projects/MJARSIGNER/issues/MJARSIGNER-72. I suspect I will need some refactoring and a safety net when I begin with that.
While writing test case I saw that maven-jarsigner-plugin was missing support for the
-signedjarparameter. But I did not have a direct need to have this feature, so I did not implement it. I cannot find any ticket for this on https://issues.apache.org/jira/projects/MJARSIGNER/issues so I guess there is no high need for it.I tried using maven Maven Plugin Testing Harness (https://maven.apache.org/plugin-testing/maven-plugin-testing-harness/). I spent many hours on it. I felt that the documentation/implementation was not mature enough to use. I did not find any good way of injecting custom instances of JarSigner/MavenSession/MavenProject. It is possible to make Maven Plugin Testing Harness instantiate a class (that is runing the default constructor). But that was not enough for me: I needed to tailor the instances. The way to make it instantiate custom classes was too cumbersome in my opinion (that I had to go via a pom.xml file on disk). It was possible to create via Java methods only, but those methods were not documented. All in all, I decided to create my own mini-test hardness framework (see MojoTestCreator.java and PluginXmlParser.java).
Since slachiewicz, in the integration branch https://github.com/apache/maven-jarsigner-plugin/tree/MJARSIGNER-41, tried to use jacoco for code coverage I completed the configuration so that Jacoco will output HTML reports in target/site/jacoco/index.html. Unfortunately, I don’t have access or don’t fully understand how the CI (Jenkins?) server https://ci-maven.apache.org/job/Maven/job/maven-box/job/maven-jarsigner-plugin/ works to know what good values are for a code coverage report to be shown on a job build page.
Just as Elliotte Rusty has noted in https://issues.apache.org/jira/projects/MJARSIGNER/issues/MJARSIGNER-68 the integration test /src/it/verify-fail/pom.xml fails when I executed it (mvn clean install -P+run-its). I have made a fix for this and documented how I did to generate the tampered.jar in src/it/verify-fail/pom.xml.