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

Use map.putIfAbsent() or map.computeIfAbsent() as appropriate instead of containsKey() + put() #7764

Merged
merged 11 commits into from
Jun 14, 2019

Conversation

sashidhar
Copy link
Contributor

@sashidhar sashidhar commented May 26, 2019

Fix #7316.

  1. if(!map.containsKey(some_key)) {
    key.put(some_key, some_value);
    }
    The above map usage pattern has been replaced with map.putIfAbsent()

  2. A new search configuration got updated in .idea/inspectionProfiles/Druid.xml.

sashidhar and others added 2 commits May 26, 2019 16:27
@sashidhar
Copy link
Contributor Author

Checking the failures.

@gianm
Copy link
Contributor

gianm commented May 26, 2019

The test error looks potentially legitimate; please check this test and any others referenced in the Travis CI report:

org.apache.druid.indexing.firehose.IngestSegmentFirehoseFactoryTest.testGetUniqueDimensionsAndMetrics[DimNames[dims]MetricNames[metrics]ParserDimNames[dims]WrapInCombining[false]](org.apache.druid.indexing.firehose.IngestSegmentFirehoseFactoryTest)
  Run 1: IngestSegmentFirehoseFactoryTest.testGetUniqueDimensionsAndMetrics:539 expected:<[met9, met10, met11, met12, met13, met8, met7, met6, met5, met4, met3, met2, met1, met0]> but was:<[met9, met10, met11, met12, met13, met8, null, null, null, null, met7, null, null, null]>

@@ -306,6 +303,11 @@
<constraint name="x" maxCount="2147483647" within="" contains="" />
<constraint name="ImmutableMap" regexp="Immutable.*" within="" contains="" />
</searchConfiguration>
<searchConfiguration name="Use Map.putIfAbsent() instead of containsKey() + put()" created="1558868694225" text="if (!$m$.containsKey($k$)) {&#10; $m$.put($k$, $v$);&#10;}" recursive="false" caseInsensitive="true" type="JAVA">
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't always good advice: in particular, if v is expensive to compute then computeIfAbsent should be used instead of putIfAbsent. This is because putIfAbsent computes v eagerly, whereas containsKey + put (or computeIfAbsent) computes v lazily. This can also have a deeper effect on behavior, if computation of v has side effects.

Copy link
Contributor

Choose a reason for hiding this comment

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

After reviewing the rest of the patch, it seems like computeIfAbsent is appropriate more often than putIfAbsent, so the recommendation here should reflect that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure @gianm , I'll make the necessary changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've made the changes. Some of the changes are straight forward. I'll comment on those respective changes which are notable.

if (!hyperLogLogs.containsKey(interval)) {
hyperLogLogs.put(interval, HyperLogLogCollector.makeLatestCollector());
}
hyperLogLogs.putIfAbsent(interval, HyperLogLogCollector.makeLatestCollector());
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good candidate for computeIfAbsent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if (!identifiersByDataSource.containsKey(identifier.getDataSource())) {
identifiersByDataSource.put(identifier.getDataSource(), new HashSet<>());
}
identifiersByDataSource.putIfAbsent(identifier.getDataSource(), new HashSet<>());
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good candidate for computeIfAbsent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if (!hllCollectors.containsKey(interval)) {
hllCollectors.put(interval, Optional.of(HyperLogLogCollector.makeLatestCollector()));
}
hllCollectors.putIfAbsent(interval, Optional.of(HyperLogLogCollector.makeLatestCollector()));
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good candidate for computeIfAbsent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if (!segmentFileMap.containsKey(segment)) {
segmentFileMap.put(segment, segmentLoader.getSegmentFiles(segment));
}
segmentFileMap.putIfAbsent(segment, segmentLoader.getSegmentFiles(segment));
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be computeIfAbsent, since there are side effects.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if (!serverExpectations.containsKey(lastServer)) {
serverExpectations.put(lastServer, new ServerExpectations(lastServer, makeMock(mocks, QueryRunner.class)));
}
serverExpectations.putIfAbsent(lastServer, new ServerExpectations(lastServer, makeMock(mocks, QueryRunner.class)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Another good candidate for computeIfAbsent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if (!newContext.containsKey(PlannerContext.CTX_SQL_QUERY_ID)) {
newContext.put(PlannerContext.CTX_SQL_QUERY_ID, UUID.randomUUID().toString());
}
newContext.putIfAbsent(PlannerContext.CTX_SQL_QUERY_ID, UUID.randomUUID().toString());
Copy link
Contributor

Choose a reason for hiding this comment

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

Another good candidate for computeIfAbsent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if (!timelines.containsKey(descriptor.getDataSource())) {
timelines.put(descriptor.getDataSource(), new VersionedIntervalTimeline<>(Ordering.natural()));
}
timelines.putIfAbsent(descriptor.getDataSource(), new VersionedIntervalTimeline<>(Ordering.natural()));
Copy link
Contributor

Choose a reason for hiding this comment

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

Another good candidate for computeIfAbsent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -88,9 +88,6 @@
<inspection_tool class="JSRedeclarationOfBlockScope" enabled="true" level="Non-TeamCity Error" enabled_by_default="true" />
<inspection_tool class="JSReferencingArgumentsOutsideOfFunction" enabled="true" level="Non-TeamCity Error" enabled_by_default="true" />
<inspection_tool class="Java8MapForEach" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="JavadocReference" enabled="true" level="ERROR" enabled_by_default="true">
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted this change.

@@ -161,7 +158,7 @@
<searchConfiguration name="Suboptimal IndexedInts iteration" text="$x$ &lt; $y$.size()" recursive="false" caseInsensitive="true" type="JAVA">
<constraint name="__context__" target="true" within="" contains="" />
<constraint name="x" within="" contains="" />
<constraint name="y" nameOfExprType="IndexedInts" exprTypeWithinHierarchy="true" within="" contains="" />
<constraint name="y" nameOfExprType="IndexedInts" expressionTypes="IndexedInts" exprTypeWithinHierarchy="true" within="" contains="" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did this change?

Copy link
Member

Choose a reason for hiding this comment

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

When using different versions of IntelliJ IDEA, it makes some seemingly arbitrary changes to the config by itself. As long as it doesn't break the TeamCity build (and if the PR author uses the latest version of IntelliJ), it should be fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@leventov, thanks for the info. I've reverted the changes auto made by the IDE.


segmentFileMap.computeIfAbsent(segment, k -> {
try {
return segmentLoader.getSegmentFiles(segment);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

segmentLoader.getSegmentFiles(segment) throws SegmentLoadingException, which needs to be caught and re thrown as RuntimeException. This makes the outer try catch redundant (compilation error) as the SegmentLoadingException has already been caught.

}
catch (SegmentLoadingException e) {
throw new RuntimeException(e);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Outer try/catch block removed.

if (!uniqueMetrics.containsKey(metric)) {
uniqueMetrics.put(metric, index++);
uniqueMetrics.computeIfAbsent(metric, k -> {
return index[0]++;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using index variable as int as is, the compiler complains "Variables used in lambda should be final or effectively final". The fix is to use an integer array with one element. Let me know if this is right.

@sashidhar
Copy link
Contributor Author

sashidhar commented May 28, 2019

@gianm , @leventov
This is the failure. Can you help me understand this recommendation.

Selected: Structural Search Inspection (1)
indexing-service/src/main/java/org/apache/druid/indexing/overlord
ForkingTaskRunner.java (1)
217:  A ConcurrentHashMap on which computeIfAbsent() is called should be assigned into variables of ConcurrentHashMap type, not ConcurrentMap

This is the line. Why should ConcurrentMap changed to ConcurrentHashMap type here ?
private final ConcurrentMap<String, ForkingTaskRunnerWorkItem> tasks = new ConcurrentHashMap<>();

@clintropolis
Copy link
Member

This is the line. Why should ConcurrentMap changed to ConcurrentHashMap type here ?

This check was added in #6898, I think maybe this comment is relevant to what it is trying to enforce.

…sent() is called should be assigned into variables of ConcurrentHashMap type, not ConcurrentMap
@sashidhar
Copy link
Contributor Author

Thanks @clintropolis this helps.

@leventov
Copy link
Member

@clintropolis the reference place with the explanation is now here: https://github.com/code-review-checklists/java-concurrency#chm-type

@sashidhar sashidhar changed the title #7316 Use Map.putIfAbsent() instead of containsKey() + put() #7316 Use map.putIfAbsent() or map.computeIfAbsent() as appropriate instead of containsKey() + put() May 28, 2019
@sashidhar
Copy link
Contributor Author

@gianm, @leventov All checks have passed. Please review.

@sashidhar
Copy link
Contributor Author

@gianm, @leventov Gentle reminder.

Copy link
Contributor

@gianm gianm left a comment

Choose a reason for hiding this comment

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

LGTM!

@sashidhar
Copy link
Contributor Author

@leventov Please review.

@leventov leventov changed the title #7316 Use map.putIfAbsent() or map.computeIfAbsent() as appropriate instead of containsKey() + put() Use map.putIfAbsent() or map.computeIfAbsent() as appropriate instead of containsKey() + put() Jun 14, 2019
@leventov
Copy link
Member

The PR doesn't require my review because it has one approval from a committer and it's not tagged Design Review.

@leventov leventov merged commit 3bee6ad into apache:master Jun 14, 2019
@sashidhar
Copy link
Contributor Author

Thanks @leventov.

@sashidhar sashidhar deleted the map_usage_pattern branch July 30, 2019 19:37
@clintropolis clintropolis added this to the 0.16.0 milestone Aug 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Prohibit a Map usage pattern replaceable with putIfAbsent()
5 participants