Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;

using Microsoft.ComponentDetection.Common.Telemetry.Attributes;

/// <summary>
/// Telemetry record for tracking Maven CLI file cleanup operations.
/// </summary>
public class MavenCliCleanupTelemetryRecord : BaseDetectionTelemetryRecord
{
/// <inheritdoc/>
public override string RecordName => "MavenCliCleanup";

/// <summary>
/// Gets or sets the number of files successfully cleaned up.
/// </summary>
[Metric]
public int FilesCleanedCount { get; set; }

/// <summary>
/// Gets or sets the number of files that failed to be cleaned up.
/// </summary>
[Metric]
public int FilesFailedCount { get; set; }

/// <summary>
/// Gets or sets the source directory that was scanned for cleanup.
/// </summary>
public string? SourceDirectory { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,4 @@ public interface IMavenCommandService
Task<MavenCliResult> GenerateDependenciesFileAsync(ProcessRequest processRequest, CancellationToken cancellationToken = default);

void ParseDependenciesFile(ProcessRequest processRequest);

/// <summary>
/// Registers that a detector is actively reading a dependency file.
/// This prevents premature deletion by other detectors.
/// </summary>
/// <param name="dependencyFilePath">The path to the dependency file being read.</param>
void RegisterFileReader(string dependencyFilePath);

/// <summary>
/// Unregisters a detector's active reading of a dependency file and attempts cleanup.
/// If no other detectors are reading the file, it will be safely deleted.
/// </summary>
/// <param name="dependencyFilePath">The path to the dependency file that was being read.</param>
/// <param name="detectorId">The identifier of the detector unregistering the file reader.</param>
void UnregisterFileReader(string dependencyFilePath, string detectorId = null);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ internal class MavenCommandService : IMavenCommandService
/// </summary>
private readonly ConcurrentDictionary<string, MavenCliResult> completedLocations = new();

/// <summary>
/// Tracks the number of active readers for each dependency file.
/// Used for safe file cleanup coordination between detectors.
/// </summary>
private readonly ConcurrentDictionary<string, int> fileReaderCounts = new();

private readonly ICommandLineInvocationService commandLineInvocationService;
private readonly IMavenStyleDependencyGraphParserService parserService;
private readonly IEnvironmentVariableService envVarService;
Expand All @@ -56,7 +50,7 @@ public MavenCommandService(
this.logger = logger;
}

public string BcdeMvnDependencyFileName => "bcde.mvndeps";
public string BcdeMvnDependencyFileName => MavenConstants.BcdeMvnDependencyFileName;

public async Task<bool> MavenCLIExistsAsync()
{
Expand All @@ -69,9 +63,6 @@ public async Task<MavenCliResult> GenerateDependenciesFileAsync(ProcessRequest p
var pomDir = Path.GetDirectoryName(pomFile.Location);
var depsFilePath = Path.Combine(pomDir, this.BcdeMvnDependencyFileName);

// Register as file reader immediately to prevent premature cleanup
this.RegisterFileReader(depsFilePath);

// Check the cache before acquiring the semaphore to allow fast-path returns
// even when cancellation has been requested.
if (this.completedLocations.TryGetValue(pomFile.Location, out var cachedResult)
Expand Down Expand Up @@ -169,68 +160,4 @@ public void ParseDependenciesFile(ProcessRequest processRequest)
var lines = sr.ReadToEnd().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
this.parserService.Parse(lines, processRequest.SingleFileComponentRecorder);
}

/// <summary>
/// Registers that a detector is actively reading a dependency file.
/// This prevents premature deletion by other detectors.
/// </summary>
/// <param name="dependencyFilePath">The path to the dependency file being read.</param>
public void RegisterFileReader(string dependencyFilePath)
{
this.fileReaderCounts.AddOrUpdate(dependencyFilePath, 1, (key, count) => count + 1);
this.logger.LogDebug(
"Registered file reader for {DependencyFilePath}, count: {Count}",
dependencyFilePath,
this.fileReaderCounts[dependencyFilePath]);
}

/// <summary>
/// Unregisters a detector's active reading of a dependency file and attempts cleanup.
/// If no other detectors are reading the file, it will be safely deleted.
/// </summary>
/// <param name="dependencyFilePath">The path to the dependency file that was being read.</param>
/// <param name="detectorId">The identifier of the detector unregistering the file reader.</param>
public void UnregisterFileReader(string dependencyFilePath, string detectorId = null)
{
var newCount = this.fileReaderCounts.AddOrUpdate(dependencyFilePath, 0, (key, count) => Math.Max(0, count - 1));
this.logger.LogDebug(
"{DetectorId}: Unregistered file reader for {DependencyFilePath}, count: {Count}",
detectorId ?? "Unknown",
dependencyFilePath,
newCount);

// If no readers remain, attempt cleanup
if (newCount == 0)
{
this.TryDeleteDependencyFileIfNotInUse(dependencyFilePath, detectorId);
}
}

/// <summary>
/// Attempts to delete a dependency file if no detectors are currently using it.
/// </summary>
/// <param name="dependencyFilePath">The path to the dependency file to delete.</param>
/// <param name="detectorId">The identifier of the detector requesting the deletion.</param>
private void TryDeleteDependencyFileIfNotInUse(string dependencyFilePath, string detectorId = null)
{
var detector = detectorId ?? "Unknown";

// Safe to delete - no readers are using the file (count was already verified to be 0)
try
{
if (File.Exists(dependencyFilePath))
{
File.Delete(dependencyFilePath);
this.logger.LogDebug("{DetectorId}: Successfully deleted dependency file {DependencyFilePath}", detector, dependencyFilePath);
}
else
{
this.logger.LogDebug("{DetectorId}: Dependency file {DependencyFilePath} was already deleted", detector, dependencyFilePath);
}
}
catch (Exception ex)
{
this.logger.LogWarning(ex, "{DetectorId}: Failed to delete dependency file {DependencyFilePath}", detector, dependencyFilePath);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Microsoft.ComponentDetection.Detectors.Maven;

/// <summary>
/// Shared constants for Maven detectors.
/// </summary>
public static class MavenConstants
{
/// <summary>
/// The filename for Maven CLI dependency output files.
/// This file is generated by MavenCommandService and consumed by Maven detectors.
/// </summary>
public const string BcdeMvnDependencyFileName = "bcde.mvndeps";

/// <summary>
/// Detector ID for MvnCliComponentDetector.
/// </summary>
public const string MvnCliDetectorId = "MvnCli";

/// <summary>
/// Detector ID for MavenWithFallbackDetector.
/// </summary>
public const string MavenWithFallbackDetectorId = "MavenWithFallback";
}
Loading
Loading