Skip to content
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

Provide a deep config copy from ConfigHelper.use() #701

Merged
merged 3 commits into from
May 15, 2019

Conversation

marcusholl
Copy link
Member

The config map prepared by ConfigHelper is a mix from several configuration levels. The lowest config level (DefaultValueCache) is shared between several ConfigHelper/step invocations. In case a Map or Collection which is inherited from the DefaultValueCache level gets modified, this is also visible for all subsequent steps. This causes trouble and situations which are hard to debug (See e.g. SAP-archive/jenkins-pipelines#16).

With this change here each invocation of ConfigHelper.use() provides a deep defensive copy. With that we can ensure that there is no configuration update from one step to another.

There is another related pull request making the DefaultValueCache immutable (#642). This PR is not superseded by thisone. This two PRs complements each other.

Currently #642 fails since there is an update performed the the config map from one of the steps (hitting finally an unmodifiable Map). With is resolved with the PR here since the immutable map is replaced by another (mutable) map when creating the defensive copy.

* with the other signatures
*/
static deepCopy(def original) {
original
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which types do we here in practice?
I would expect numbers and Strings. Both are to my knownledge immutable (so no problem).
However, can we codify this assumption and fail if it is violated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the types there is a description available on that site: http://groovy-lang.org/json.html.

We have to deal with:

  • java.lang.String
  • java.lang.BigDecimal, java.lang.Integer
  • java.lang.Boolean, maybe the corresponding primitive
  • java.util.Date
  • and of course java.util.LinkedHashMap and java.util.ArrayList

The only issue we have wrt immutability is the Date. Currently we do not use this type in our config.

However, can we codify this assumption and fail if it is violated?

That a good approach. In the "sister" pull request #642 we have already such a type check. Since Date is not used up to now this type is omitted there. I will introduce this check also here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, can we codify this assumption and fail if it is violated?

First I though that would in principle a good idea. In the meantime I played around with it and now I think we are in a better shape when we do not enforce immutability here.

Another question is: should the default config be immutable. IMO the answer to that question is: it should be immutable. But for that we have PR #642, which is not superseded by this PR here.

With this PR we ensure that the collections and the maps are copied (... deep). These collections are not imutable (... if they would some code manipulating the config map would not not work anymore), so we do not focus on immutability here for the maps and the collections.

In the test cases we have also examples where we hand-over instances into the steps which are mutable (basically mocks for e.g. utils). But I can also imagine use-cases where this is reasonable for real-world use cases.

@benhei : having this in mind you might re-think about the proposal of ensuring immutability for the types contained in the maps/collections. Again: the default configs immutability is addressed by #642.

An example how dealing with immutable values contained somewhere in the config might look like is provided by marcusholl#4. But as outlined above I would like to suggest not to go this way.

Other question (should be IMO handled in another PR in case we would like to have this): How should we deal with the parameters handed over via step-signature?

Copy link
Contributor

@benhei benhei May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have a miss-understanding here. My question was whether we can ensure the contract "either a object is mutable and is copied or its immutable". Lets discuss tomorrow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benhei : I checked the details.

we have to deal with the following mutable classes when running the tests (each occurance decorated by me with a comment explaining more details):

 ArtifactSetVersionTest // anonymous class or closure
 CheckChangeInDevelopmentTest // anonymous class or closure
 CloudFoundryDeployTest // anonymous class or closure
 SOLMAN/com.sap.piper.cm.BackendType. // real issue, the enum is expected to be provided also in real life from pipelines (or config
 TransportRequestCreateTest // anonymous class or closure
 TransportRequestReleaseTest// anonymous class or closure
 TransportRequestUploadFileTest// anonymous class or closure
 artifactSetVersion// anonymous class or closure
 com.sap.piper.DescriptorUtils // looks like mocking
 com.sap.piper.JenkinsUtils // looks like mocking
 com.sap.piper.Utils // looks like mocking
 com.sap.piper.integration.WhitesourceOrgAdminRepository // real issue. This type is expected to be provided from a pipeline
 com.sap.piper.integration.WhitesourceRepository // real issue. This type is expected to be provided from a pipeline

So we have real use cases where we can hand over (via signature) mutable instances. I cannot say much about the whitesource, but in general I can think about reasonable cases. Regarding the enums: to me it looks like there is nothing wrong with providing enums via signature. This is better than strings. And enums are not immutabe by default. I mean most enums in reality will be imutable, but there is no contract requiring that ...

I will add comments to the corresponding methods creating defensive copies or the Maps/Collections saying that only the Maps/Collections are copied.

That is not a perfect solution, but it helps us for the problems we see currently.

nextLevel: [
list: ['x', 'y'],
duplicate: l,
set: (Set)[9, 8, 7]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would [9, 8, 7] as Set work here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this would also work here. So we have several options to get a Set here rather than a list.

Copy link
Member

@OliverNocon OliverNocon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

The config map prepared by ConfigHelper is a mix from several configuration levels. The lowest config level
(DefaultValueCache) is shared between several ConfigHelper invocations. In case a Map or Collection which is
inherited from the DefaultValueCache level gets modified, this is also visible for all subsequent steps. This
causes trouble and situation which are hard to debug.

With this change here each invocation of ConfigHelper.use() provides a deep defensive copy. With that we can
ensure that there is no configuration update from one step to another.
@marcusholl marcusholl merged commit ed328b7 into SAP:master May 15, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants