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
Expand Up @@ -92,11 +92,12 @@ public ExtensionScanCompletionService(

/**
* Check completion for a single scan, catching and logging any errors.
*
* <p>
* Called inline after scanner jobs finish (within existing transaction).
* Errors are logged but don't fail the caller - the recovery service
* will catch any missed completions on next restart.
*/
@Transactional
public void checkCompletionSafely(String scanId) {
try {
checkSingleScanCompletion(scanId);
Expand All @@ -107,10 +108,10 @@ public void checkCompletionSafely(String scanId) {

/**
* Check completion for a single scan (event-driven).
*
* <p>
* Called by checkCompletionSafely after scanner jobs finish.
* Also called directly by the recovery service.
*
* <p>
* Note: @Transactional needed for direct calls from recovery service.
* When called via checkCompletionSafely, participates in existing transaction.
*/
Expand Down Expand Up @@ -150,19 +151,19 @@ public void checkSingleScanCompletion(String scanId) {

/**
* Process completed scans and activate extensions when all scans pass.
*
* <p>
* FALLBACK: Runs every 5 minutes using JobRunr distributed scheduling.
* Only one pod executes this at a time (distributed lock).
*
* <p>
* IMPORTANT: Primary completion checking is EVENT-DRIVEN via checkSingleScanCompletion()
* which is called immediately when each scanner job finishes. This polling job is only
* a safety net for edge cases (missed events, server crashes, etc.).
*
* <p>
* To avoid blocking workers during high load:
* - Runs less frequently (every 5 minutes instead of 1)
* - Processes at most MAX_SCANS_PER_CYCLE scans per run
* - Prioritizes oldest scans first (FIFO)
*
* <p>
* Strategy:
* 1. Find all ExtensionScanResult records in SCANNING status (oldest first)
* 2. For each one (up to limit), check if all associated ScanJobs are complete
Expand Down Expand Up @@ -266,18 +267,18 @@ private Boolean processSingleScan(ExtensionScan scanResult) {

/**
* Determine if we should proceed with scan completion or wait for more jobs.
*
* <p>
* This method dynamically checks which scanners are CURRENTLY active and ensures
* all active scanners have jobs (terminal or not).
*
* <p>
* Strategy:
* 1. Get list of currently active scanners from ScannerRegistry
* 2. Check which scanners have jobs for this extension
* 3. If missing scanners AND > 5 minutes since scan started → create jobs for new scanners
* 4. If all active scanners have terminal jobs → proceed with completion
*
* <p>
* Edge cases handled:
*
* <p>
* 1. Scanner Removed: Jobs for removed scanners are ignored (not in active list)
* 2. Scanner Added: After 5 minutes, create jobs for new scanners
* 3. Jobs Still Being Created: Wait < 5 minutes for initial job creation
Expand Down Expand Up @@ -366,7 +367,7 @@ private boolean shouldProceedWithCompletion(ExtensionScan scanResult, List<Scann

/**
* Create scan jobs for scanners that were added after publishing started.
*
* <p>
* This allows new scanners to retroactively scan extensions that are still
* in the scanning phase.
*/
Expand Down Expand Up @@ -421,7 +422,7 @@ private boolean allJobsTerminal(List<ScannerJob> jobs) {

/**
* Process a completed scan group and activate extension if scans passed.
*
* <p>
* Logic:
* 1. Check if any jobs FAILED
* 2. Load all threats from all jobs
Expand Down Expand Up @@ -626,7 +627,7 @@ private boolean completeExtensionScan(String scanId, List<ScannerJob> jobs) {

/**
* Find the ExtensionVersion associated with an ExtensionScan.
*
* <p>
* Uses the namespace/extension/version/platform from the scan record
* to look up the actual ExtensionVersion entity.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,14 @@ public Scanner.Invocation startScan(@Nonnull Command command) throws ScannerExce

try (var extensionFile = scanFileService.getExtensionFile(command.extensionVersionId())) {
File file = extensionFile.getPath().toFile();
String fileName = extensionFile.getResource() != null ? extensionFile.getResource().getName() : file.getName();

// Copy operation to avoid mutating shared config (thread safety)
RemoteScannerProperties.HttpOperation startOp = configOp.copy();

// Process URL and headers with placeholders
Map<String, String> placeholders = new HashMap<>();
placeholders.put("fileName", file.getName());
placeholders.put("fileName", fileName);

processOperation(startOp, placeholders);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ public ScannerConcurrencyDispatcher(
*/
@Job(name = "Scanner concurrency dispatcher", retries = 0)
@Recurring(id = "scanner-concurrency-dispatcher", interval = "PT15S")
@Transactional
public void dispatch() {
boolean anyLimited = scannerRegistry.getAllScanners().stream()
.anyMatch(s -> s.getMaxConcurrency() > 0);
Expand Down
24 changes: 1 addition & 23 deletions server/src/main/java/org/eclipse/openvsx/util/TempFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,13 @@ public class TempFile implements Closeable {
private FileResource resource;
private Namespace namespace;

/** Original name of the file (e.g., path within archive) */
private String originalName;

/** SHA256 hash of the file content */
private String sha256Hash;

public TempFile(String prefix, String suffix) throws IOException {
path = Files.createTempFile(prefix, suffix);
}

/**
* Create a TempFile from an existing path.
*
* <p>
* Used when extracting files to a pre-created temp location.
* The file will be deleted when close() is called.
*
Expand Down Expand Up @@ -65,22 +59,6 @@ public void setNamespace(Namespace namespace) {
this.namespace = namespace;
}

public String getOriginalName() {
return originalName;
}

public void setOriginalName(String originalName) {
this.originalName = originalName;
}

public String getSha256Hash() {
return sha256Hash;
}

public void setSha256Hash(String sha256Hash) {
this.sha256Hash = sha256Hash;
}

@Override
public void close() throws IOException {
Files.delete(path);
Expand Down
Loading