Memory exhaustion reading Go coverage file

  • ALM used: GitHub
  • CI system used: GitHub Actions
  • Scanner command used when applicable: sonarsource/sonarqube-scan-action v6.0.0
  • Languages of the repository: Go
  • Error observed: java.lang.OutOfMemoryError: Java heap space

We have a big Go repository that generates a huge coverage file (>3Gb). There are more than 3600 files in this repository. When the go plugin reads the coverage file it consumes more than 24 Gb of memory and crashes with java.lang.OutOfMemoryError: Java heap space.

We’ve tried splitting the coverage file in chunks to check if it’s able to read it this way but the same happens. Here is the log when it tries to read these multiple coverage chunks:

07:31:59.303 INFO  Sensor Code Quality and Security for Go [go] (done) | time=9268ms
07:31:59.303 INFO  Sensor Go Cover sensor for Go coverage [go]
07:31:59.625 INFO  Load coverage report from '/home/runner/_work/<project>/coverage_split/coverage_00'
07:32:25.298 INFO  Load coverage report from '/home/runner/_work/<project>/coverage_split/coverage_01'
07:32:50.993 INFO  Load coverage report from '/home/runner/_work/<project>/coverage_split/coverage_02'
07:33:17.590 INFO  Load coverage report from '/home/runner/_work/<project>/coverage_split/coverage_03'
07:33:44.163 INFO  Load coverage report from '/home/runner/_work/<project>/coverage_split/coverage_04'
07:35:07.377 ERROR Error during SonarScanner Engine execution
java.lang.OutOfMemoryError: Java heap space
	at java.base/java.util.Arrays.copyOfRange(Unknown Source)
	at java.base/java.lang.StringLatin1.newString(Unknown Source)
	at java.base/java.lang.String.substring(Unknown Source)
	at java.base/java.util.Scanner.hasNextLine(Unknown Source)
	at org.sonar.go.coverage.GoCoverSensor.parse(GoCoverSensor.java:195)
	at org.sonar.go.coverage.GoCoverSensor.lambda$execute$1(GoCoverSensor.java:77)
	at org.sonar.go.coverage.GoCoverSensor$$Lambda$992/0x00007f69f46aa450.accept(Unknown Source)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
	at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(Unknown Source)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(Unknown Source)
	at java.base/java.util.stream.ReferencePipeline$7$1.accept(Unknown Source)
	at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Unknown Source)
	at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source)
	at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
	at java.base/java.util.stream.ReferencePipeline.forEach(Unknown Source)
	at org.sonar.go.coverage.GoCoverSensor.execute(GoCoverSensor.java:77)
	at org.sonar.go.coverage.GoCoverSensor.execute(GoCoverSensor.java:71)
	at org.sonar.scanner.sensor.AbstractSensorWrapper.analyse(AbstractSensorWrapper.java:68)
	at org.sonar.scanner.sensor.ModuleSensorsExecutor.execute(ModuleSensorsExecutor.java:75)
	at org.sonar.scanner.sensor.ModuleSensorsExecutor.lambda$execute$1(ModuleSensorsExecutor.java:48)
	at org.sonar.scanner.sensor.ModuleSensorsExecutor$$Lambda$749/0x00007f69f45c9580.run(Unknown Source)
	at org.sonar.scanner.sensor.ModuleSensorsExecutor.withModuleStrategy(ModuleSensorsExecutor.java:66)
	at org.sonar.scanner.sensor.ModuleSensorsExecutor.execute(ModuleSensorsExecutor.java:48)
	at org.sonar.scanner.scan.SpringModuleScanContainer.doAfterStart(SpringModuleScanContainer.java:66)
	at org.sonar.core.platform.SpringComponentContainer.startComponents(SpringComponentContainer.java:200)
	at org.sonar.core.platform.SpringComponentContainer.execute(SpringComponentContainer.java:179)
	at org.sonar.scanner.scan.SpringProjectScanContainer.scan(SpringProjectScanContainer.java:216)
	at org.sonar.scanner.scan.SpringProjectScanContainer.scanRecursively(SpringProjectScanContainer.java:212)
	at org.sonar.scanner.scan.SpringProjectScanContainer.doAfterStart(SpringProjectScanContainer.java:175)

I’ve been checking the go plugin that reads the coverage file and it seems that it first parses all the coverage chunks, stores them in memory and then calls saveCoverage. Is it possible that this plugin writes each chunk file after it is parsed instead of having all the coverage in memory?

https://github.com/SonarSource/sonar-go/blob/master/sonar-go-plugin/src/main/java/org/sonar/go/coverage/GoCoverSensor.java:

  void execute(SensorContext context, GoPathContext goContext) {
    try {
      Coverage coverage = new Coverage(goContext);
      getReportPaths(context).forEach(reportPath -> parse(reportPath, coverage));
      coverage.fileMap.forEach((filePath, coverageStats) -> {
        try {
          saveFileCoverage(context, filePath, coverageStats);
        } catch (Exception e) {
          LOG.warn("Failed saving coverage info for file: {}", filePath);
        }
      });
    } catch (Exception e) {
      LOG.warn("Coverage import failed: {}", e.getMessage(), e);
    }
  }

Thanks!

Hey @jfontan

There may be something we need to do here to fix this, but in the meantime can you try increasing the amount of memory available to the scanner? You can do this by setting the anlaysis parameter sonar.scanner.javaOpts to a value like -Xmx8G.

- uses: SonarSource/sonarqube-scan-action@6
  with:
    args: >
      -Dsonar.scanner.javaOpts=-Xmx8G

We already have it configured to allow 24Gb but sometimes the nodes don’t have that amount available and fails.

      - name: SonarCloud Scan
        uses: sonarsource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_SCANNER_OPTS: "-Xmx24g"
          SONAR_SCANNER_JAVA_OPTS: "-Xmx24g"

Is there anything we can do to help fixing this issue? I can provide more information if needed.

Our checks are failing and using machines with more than 24Gb of memory is not on the table. We’ve been increasing memory until we reached this value.

Just having a bit of patience for the right team to be able to take a look. :slight_smile:

OK, thank you!

Hi @jfontan

Thank you for reporting this issue. Indeed the code coverage import needs to be improved to properly handle such big report(s). I created ticket: Jira to fix this issue.

Best

Marcin Stachniuk