Skip to content

Resource leak in GHService.getDiffStats() - JGit resources not closed #1660

@Fikri-20

Description

@Fikri-20

Plugin Modernizer version

999999-SNAPSHOT (latest main branch, built locally)

What Operating System are you using

Linux (GitHub Actions CI environment)
Observation: The bug exists in the source code regardless of OS, as JGit Resource leaks are a cross-platform issue affecting both Linux and Windows builds.

Reproduction steps

Reproduction steps:

  1. Clone the repository: git clone https://github.com/jenkins-infra/plugin-modernizer-tool.git
  2. Checkout latest main branch
  3. Build the project: mvn package -DskipTests
  4. Set environment variables:
    • export GH_TOKEN=your_github_token
    • export GH_OWNER=your_ithub_username
  5. Run the tool with dry-run mode on any plugin:
    plugin-modernizer dry-run --plugins PLUGIN_NAME --recipe RECIPE_NAME --debug
  6. Monitor open file handles during execution
  7. Observe resources accumulate with each plugin processed

Expected Results

JGit resources (ObjectReader, DiffFormatter, RevWalk) should be properly closed
after use in the getDiffStats() method.
All resources should be closed via try-with-resources or explicit close() calls:

  • ObjectReader should be closed after diff calculation
  • DiffFormatter should be closed after diff calculation
  • RevWalk instances should be closed after use
    Expected: Stable resource count regardless of number of plugins processed.

Actual Results:
RESOURCE LEAK CONFIRMED - Bug identified via code analysis
The getDiffStats() method in GHService.java has resource leaks:

LEAK #1 - dryRun=true path (lines 1254-1286):

ObjectReader reader = repository.newObjectReader();      // line 1254 - CREATED
DiffFormatter formatter = new DiffFormatter(...);          // line 1255 - CREATED
  if (dryRun) {
      return new DiffStats(...);  // line 1286 - RETURNS EARLY!
      // BUG: reader and formatter are NEVER closed in this path!
  }

LEAK #2 - dryRun=false path (lines 1288-1317):

reader.close();  // line 1316 - Only reader is closed

LEAK #3 - RevWalk instances (lines 1299-1300):

  oldTree.reset(reader, new RevWalk(repository).parseTree(defaultBranch));  // RevWalk #1
  newTree.reset(reader, new RevWalk(repository).parseTree(head));            // RevWalk #2

I created a standalone Java program (ResourceLeakDemo.java) that mimics the buggy code pattern from GHService.getDiffStats(). Running it proves the resource leak:

AFTER 5 RUNS with dryRun=true:
Resources still open: 10
Maximum ever open: 10

Each run creates 2 resources (reader + formatter) that are NEVER closed.
IMPACT:

  • Each call to getDiffStats() leaks 2-4 JGit resources
  • Batch processing of 50 plugins = potential 200+ leaked resources
  • Can cause OutOfMemoryError or "too many open files" error

Affected Component: plugin-modernizer-core
Affected File: GHService.java lines 1254-1317

Suggested Fix:

try (ObjectReader reader = repository.newObjectReader();
     DiffFormatter formatter = new DiffFormatter(new ByteArrayOutputStream())) {
    formatter.setRepository(repository);
    formatter.setDiffComparator(RawTextComparator.DEFAULT);
    formatter.setDetectRenames(true);
    // ... existing code ...
} // Both reader and formatter automatically closed
// For RevWalk:
try (RevWalk oldWalk = new RevWalk(repository);
     RevWalk newWalk = new RevWalk(repository)) {
    // ... use oldWalk and newWalk ...
} // Both RevWalk automatically closed

Screenshots attached:

Image Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Priority

    None yet

    Target date

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions