Skip to content

Commit 49099f3

Browse files
authored
Fix memory leaks in gradle daemon (#1198)
2 parents 2e9b09c + 628c5fc commit 49099f3

File tree

11 files changed

+101
-49
lines changed

11 files changed

+101
-49
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1212
## [Unreleased]
1313
### Fixed
1414
* Update the `black` version regex to fix `19.10b0` and earlier. (fixes [#1195](https://github.com/diffplug/spotless/issues/1195), regression introduced in `2.25.0`)
15+
* `GitAttributesLineEndings$RelocatablePolicy` and `FormatterStepImpl` now null-out their initialization lambdas after their state has been calculated, which allows GC to collect variables which were incidentally captured but not needed in the calculated state. ([#1198](https://github.com/diffplug/spotless/pull/1198))
1516

1617
## [2.25.2] - 2022-05-03
1718
### Changes

lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package com.diffplug.spotless.extra;
1717

18-
import static com.diffplug.spotless.extra.LibExtraPreconditions.requireElementsNonNull;
19-
2018
import java.io.File;
2119
import java.io.FileInputStream;
2220
import java.io.IOException;
@@ -78,8 +76,8 @@ public static LineEnding.Policy create(File projectDir, Supplier<Iterable<File>>
7876
static class RelocatablePolicy extends LazyForwardingEquality<CachedEndings> implements LineEnding.Policy {
7977
private static final long serialVersionUID = 5868522122123693015L;
8078

81-
final transient File projectDir;
82-
final transient Supplier<Iterable<File>> toFormat;
79+
transient File projectDir;
80+
transient Supplier<Iterable<File>> toFormat;
8381

8482
RelocatablePolicy(File projectDir, Supplier<Iterable<File>> toFormat) {
8583
this.projectDir = Objects.requireNonNull(projectDir, "projectDir");
@@ -88,8 +86,13 @@ static class RelocatablePolicy extends LazyForwardingEquality<CachedEndings> imp
8886

8987
@Override
9088
protected CachedEndings calculateState() throws Exception {
91-
Runtime runtime = new RuntimeInit(projectDir, toFormat.get()).atRuntime();
92-
return new CachedEndings(projectDir, runtime, toFormat.get());
89+
Runtime runtime = new RuntimeInit(projectDir).atRuntime();
90+
// LazyForwardingEquality guarantees that this will only be called once, and keeping toFormat
91+
// causes a memory leak, see https://github.com/diffplug/spotless/issues/1194
92+
CachedEndings state = new CachedEndings(projectDir, runtime, toFormat.get());
93+
projectDir = null;
94+
toFormat = null;
95+
return state;
9396
}
9497

9598
@Override
@@ -146,8 +149,7 @@ static class RuntimeInit {
146149
final @Nullable File workTree;
147150

148151
@SuppressFBWarnings("SIC_INNER_SHOULD_BE_STATIC_ANON")
149-
RuntimeInit(File projectDir, Iterable<File> toFormat) {
150-
requireElementsNonNull(toFormat);
152+
RuntimeInit(File projectDir) {
151153
/////////////////////////////////
152154
// USER AND SYSTEM-WIDE VALUES //
153155
/////////////////////////////////
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2022 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless;
17+
18+
import java.util.Objects;
19+
20+
/** Superclass of all compound FormatterSteps necessary for {@link com.diffplug.spotless.LazyForwardingEquality#unlazy(java.lang.Object)}. */
21+
abstract class DelegateFormatterStep implements FormatterStep {
22+
protected final FormatterStep delegateStep;
23+
24+
DelegateFormatterStep(FormatterStep delegateStep) {
25+
this.delegateStep = Objects.requireNonNull(delegateStep);
26+
}
27+
28+
@Override
29+
public final String getName() {
30+
return delegateStep.getName();
31+
}
32+
}

lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java

+3-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 DiffPlug
2+
* Copyright 2016-2022 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,27 +22,19 @@
2222

2323
import javax.annotation.Nullable;
2424

25-
final class FilterByContentPatternFormatterStep implements FormatterStep {
26-
private final FormatterStep delegateStep;
25+
final class FilterByContentPatternFormatterStep extends DelegateFormatterStep {
2726
final Pattern contentPattern;
2827

2928
FilterByContentPatternFormatterStep(FormatterStep delegateStep, String contentPattern) {
30-
this.delegateStep = Objects.requireNonNull(delegateStep);
29+
super(delegateStep);
3130
this.contentPattern = Pattern.compile(Objects.requireNonNull(contentPattern));
3231
}
3332

34-
@Override
35-
public String getName() {
36-
return delegateStep.getName();
37-
}
38-
3933
@Override
4034
public @Nullable String format(String raw, File file) throws Exception {
4135
Objects.requireNonNull(raw, "raw");
4236
Objects.requireNonNull(file, "file");
43-
4437
Matcher matcher = contentPattern.matcher(raw);
45-
4638
if (matcher.find()) {
4739
return delegateStep.format(raw, file);
4840
} else {

lib/src/main/java/com/diffplug/spotless/FilterByFileFormatterStep.java

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 DiffPlug
2+
* Copyright 2016-2022 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,20 +20,14 @@
2020

2121
import javax.annotation.Nullable;
2222

23-
final class FilterByFileFormatterStep implements FormatterStep {
24-
private final FormatterStep delegateStep;
23+
final class FilterByFileFormatterStep extends DelegateFormatterStep {
2524
private final SerializableFileFilter filter;
2625

2726
FilterByFileFormatterStep(FormatterStep delegateStep, SerializableFileFilter filter) {
28-
this.delegateStep = Objects.requireNonNull(delegateStep);
27+
super(delegateStep);
2928
this.filter = Objects.requireNonNull(filter);
3029
}
3130

32-
@Override
33-
public String getName() {
34-
return delegateStep.getName();
35-
}
36-
3731
@Override
3832
public @Nullable String format(String raw, File file) throws Exception {
3933
Objects.requireNonNull(raw, "raw");

lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 DiffPlug
2+
* Copyright 2016-2022 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ abstract class FormatterStepImpl<State extends Serializable> extends Strict<Stat
3939
final transient String name;
4040

4141
/** Transient because only the state matters. */
42-
final transient ThrowingEx.Supplier<State> stateSupplier;
42+
transient ThrowingEx.Supplier<State> stateSupplier;
4343

4444
FormatterStepImpl(String name, ThrowingEx.Supplier<State> stateSupplier) {
4545
this.name = Objects.requireNonNull(name);
@@ -53,7 +53,11 @@ public String getName() {
5353

5454
@Override
5555
protected State calculateState() throws Exception {
56-
return stateSupplier.get();
56+
// LazyForwardingEquality guarantees that this will only be called once, and keeping toFormat
57+
// causes a memory leak, see https://github.com/diffplug/spotless/issues/1194
58+
State state = stateSupplier.get();
59+
stateSupplier = null;
60+
return state;
5761
}
5862

5963
static final class Standard<State extends Serializable> extends FormatterStepImpl<State> {

lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 DiffPlug
2+
* Copyright 2016-2022 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -111,4 +111,18 @@ static byte[] toBytes(Serializable obj) {
111111
}
112112
return byteOutput.toByteArray();
113113
}
114+
115+
/** Ensures that the lazy state has been evaluated. */
116+
public static void unlazy(Object in) {
117+
if (in instanceof LazyForwardingEquality) {
118+
((LazyForwardingEquality<?>) in).state();
119+
} else if (in instanceof DelegateFormatterStep) {
120+
unlazy(((DelegateFormatterStep) in).delegateStep);
121+
} else if (in instanceof Iterable) {
122+
Iterable<Object> cast = (Iterable<Object>) in;
123+
for (Object c : cast) {
124+
unlazy(c);
125+
}
126+
}
127+
}
114128
}

plugin-gradle/CHANGES.md

+3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).
44

55
## [Unreleased]
6+
### Added
7+
* `FormatExtension.createIndependentApplyTaskLazy`, with same functionality as `createIndependentApplyTaskLazy` but returning `TaskProvider` ([#1198](https://github.com/diffplug/spotless/pull/1198))
68
### Fixed
79
* Update the `black` version regex to fix `19.10b0` and earlier. (fixes [#1195](https://github.com/diffplug/spotless/issues/1195), regression introduced in `6.5.0`)
10+
* Improved daemon memory consumption ([#1198](https://github.com/diffplug/spotless/pull/1198) fixes [#1194](https://github.com/diffplug/spotless/issues/1194))
811

912
## [6.5.2] - 2022-05-03
1013
### Changes

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java

+17-11
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.gradle.api.file.ConfigurableFileTree;
3939
import org.gradle.api.file.FileCollection;
4040
import org.gradle.api.plugins.BasePlugin;
41+
import org.gradle.api.tasks.TaskProvider;
4142

4243
import com.diffplug.common.base.Preconditions;
4344
import com.diffplug.spotless.FormatExceptionPolicyStrict;
@@ -769,6 +770,11 @@ protected Project getProject() {
769770
return spotless.project;
770771
}
771772

773+
/** Eager version of {@link #createIndependentApplyTaskLazy(String)} */
774+
public SpotlessApply createIndependentApplyTask(String taskName) {
775+
return createIndependentApplyTaskLazy(taskName).get();
776+
}
777+
772778
/**
773779
* Creates an independent {@link SpotlessApply} for (very) unusual circumstances.
774780
*
@@ -782,19 +788,19 @@ protected Project getProject() {
782788
*
783789
* NOTE: does not respect the rarely-used <a href="https://github.com/diffplug/spotless/blob/b7f8c551a97dcb92cc4b0ee665448da5013b30a3/plugin-gradle/README.md#can-i-apply-spotless-to-specific-files">{@code spotlessFiles} property</a>.
784790
*/
785-
public SpotlessApply createIndependentApplyTask(String taskName) {
791+
public TaskProvider<SpotlessApply> createIndependentApplyTaskLazy(String taskName) {
786792
Preconditions.checkArgument(!taskName.endsWith(SpotlessExtension.APPLY), "Task name must not end with " + SpotlessExtension.APPLY);
787-
// create and setup the task
788-
SpotlessTaskImpl spotlessTask = spotless.project.getTasks().create(taskName + SpotlessTaskService.INDEPENDENT_HELPER, SpotlessTaskImpl.class);
789-
spotlessTask.init(spotless.getRegisterDependenciesTask().getTaskService());
790-
setupTask(spotlessTask);
791-
// clean removes the SpotlessCache, so we have to run after clean
792-
spotlessTask.mustRunAfter(BasePlugin.CLEAN_TASK_NAME);
793+
TaskProvider<SpotlessTaskImpl> spotlessTask = spotless.project.getTasks().register(taskName + SpotlessTaskService.INDEPENDENT_HELPER, SpotlessTaskImpl.class, task -> {
794+
task.init(spotless.getRegisterDependenciesTask().getTaskService());
795+
setupTask(task);
796+
// clean removes the SpotlessCache, so we have to run after clean
797+
task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME);
798+
});
793799
// create the apply task
794-
SpotlessApply applyTask = spotless.project.getTasks().create(taskName, SpotlessApply.class);
795-
applyTask.init(spotlessTask);
796-
applyTask.dependsOn(spotlessTask);
797-
800+
TaskProvider<SpotlessApply> applyTask = spotless.project.getTasks().register(taskName, SpotlessApply.class, task -> {
801+
task.dependsOn(spotlessTask);
802+
task.init(spotlessTask.get());
803+
});
798804
return applyTask;
799805
}
800806

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,10 @@ private static Provisioner forConfigurationContainer(Project project, Configurat
127127
if (!projName.isEmpty()) {
128128
projName = projName + "/";
129129
}
130-
logger.error(
131-
"You need to add a repository containing the '{}' artifact in '{}build.gradle'.\n" +
130+
throw new GradleException(String.format(
131+
"You need to add a repository containing the '%s' artifact in '%sbuild.gradle'.%n" +
132132
"E.g.: 'repositories { mavenCentral() }'",
133-
mavenCoords, projName,
134-
e);
135-
throw e;
133+
mavenCoords, projName), e);
136134
}
137135
};
138136
}

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JvmLocalCache.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 DiffPlug
2+
* Copyright 2021-2022 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.gradle.api.Task;
2727

2828
import com.diffplug.spotless.FileSignature;
29+
import com.diffplug.spotless.LazyForwardingEquality;
2930

3031
class JvmLocalCache {
3132
private static GradleException cacheIsStale() {
@@ -53,6 +54,11 @@ static class LiveCacheKeyImpl<T> implements LiveCache<T>, Serializable {
5354

5455
@Override
5556
public void set(T value) {
57+
if (value instanceof LazyForwardingEquality) {
58+
// whenever we cache an instance of LazyForwardingEquality, we want to make sure that we give it
59+
// a chance to null-out its initialization lambda (see https://github.com/diffplug/spotless/issues/1194#issuecomment-1120744842)
60+
LazyForwardingEquality.unlazy((LazyForwardingEquality<?>) value);
61+
}
5662
daemonState.put(internalKey, value);
5763
}
5864

0 commit comments

Comments
 (0)