Skip to content

Commit e0df257

Browse files
authored
Reduce CPU usage of gradle run (#49055)
The RunTask is responsible for logging output from nodes to the console and also stays active since we want the cluster to keep running. However, the implementation of the logging and waiting resulted in a spin loop that continually polls for data to have been written to one of the nodes' output files. On my laptop, this causes an idle invocation of `gradle run` to consume an entire core. The JDK provides a method to be notified of changes to files through the use of a WatchService. While a WatchService based implementation for logging and waiting works, a delay of up to ten seconds is encountered when running on macOS. This is due to the lack of a native WatchService implementation that uses kqueue or FSEvents; the current WatchService implementation in the JDK uses polling with a default interval of ten seconds. While the interval can be changed programmatically it is not an acceptable solution due to the need to access the com.sun.nio.file.SensitivityWatchEventModifier enum, which is in an internal package. The change in this commit instead introduces a check to see if any data was available to read and log. If no data is available in any of the node output files, the thread sleeps for 100ms. This is enough time to prevent consuming large amounts of cpu while still providing output to the console in a timely fashion.
1 parent 630aac2 commit e0df257

File tree

1 file changed

+49
-11
lines changed
  • buildSrc/src/main/java/org/elasticsearch/gradle/testclusters

1 file changed

+49
-11
lines changed

buildSrc/src/main/java/org/elasticsearch/gradle/testclusters/RunTask.java

+49-11
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
import org.gradle.api.tasks.options.Option;
88

99
import java.io.BufferedReader;
10+
import java.io.Closeable;
1011
import java.io.IOException;
1112
import java.nio.file.Files;
12-
import java.util.HashSet;
13+
import java.util.ArrayList;
14+
import java.util.List;
1315
import java.util.Map;
14-
import java.util.Set;
1516
import java.util.stream.Collectors;
1617

1718
public class RunTask extends DefaultTestClustersTask {
@@ -66,17 +67,54 @@ public void beforeStart() {
6667

6768
@TaskAction
6869
public void runAndWait() throws IOException {
69-
Set<BufferedReader> toRead = new HashSet<>();
70-
for (ElasticsearchCluster cluster : getClusters()) {
71-
for (ElasticsearchNode node : cluster.getNodes()) {
72-
toRead.add(Files.newBufferedReader(node.getEsStdoutFile()));
70+
List<BufferedReader> toRead = new ArrayList<>();
71+
try {
72+
for (ElasticsearchCluster cluster : getClusters()) {
73+
for (ElasticsearchNode node : cluster.getNodes()) {
74+
BufferedReader reader = Files.newBufferedReader(node.getEsStdoutFile());
75+
toRead.add(reader);
76+
}
7377
}
74-
}
75-
while (Thread.currentThread().isInterrupted() == false) {
76-
for (BufferedReader bufferedReader : toRead) {
77-
if (bufferedReader.ready()) {
78-
logger.lifecycle(bufferedReader.readLine());
78+
79+
while (Thread.currentThread().isInterrupted() == false) {
80+
boolean readData = false;
81+
for (BufferedReader bufferedReader : toRead) {
82+
if (bufferedReader.ready()) {
83+
readData = true;
84+
logger.lifecycle(bufferedReader.readLine());
85+
}
7986
}
87+
88+
if (readData == false) {
89+
// no data was ready to be consumed and rather than continuously spinning, pause
90+
// for some time to avoid excessive CPU usage. Ideally we would use the JDK
91+
// WatchService to receive change notifications but the WatchService does not have
92+
// a native MacOS implementation and instead relies upon polling with possible
93+
// delays up to 10s before a notification is received. See JDK-7133447.
94+
try {
95+
Thread.sleep(100L);
96+
} catch (InterruptedException e) {
97+
Thread.currentThread().interrupt();
98+
return;
99+
}
100+
}
101+
}
102+
} finally {
103+
Exception thrown = null;
104+
for (Closeable closeable : toRead) {
105+
try {
106+
closeable.close();
107+
} catch (Exception e) {
108+
if (thrown == null) {
109+
thrown = e;
110+
} else {
111+
thrown.addSuppressed(e);
112+
}
113+
}
114+
}
115+
116+
if (thrown != null) {
117+
logger.debug("exception occurred during close of stdout file readers", thrown);
80118
}
81119
}
82120
}

0 commit comments

Comments
 (0)