Skip to content

Commit

Permalink
Merge pull request #785 from amvanbaren/bugfix/issue-779
Browse files Browse the repository at this point in the history
Use job instead of @async
  • Loading branch information
amvanbaren authored Jul 18, 2023
2 parents 19336be + e3f00dd commit f925747
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,12 @@ public FileResource getBinary(ExtensionVersion extVersion, String binaryName) {
binary.setExtension(extVersion);
binary.setName(binaryName);
binary.setType(FileResource.DOWNLOAD);
binary.setContent(null);
try {
binary.setContent(Files.readAllBytes(extensionFile.getPath()));
} catch (IOException e) {
throw new RuntimeException(e);
}

return binary;
}

Expand Down
17 changes: 13 additions & 4 deletions server/src/main/java/org/eclipse/openvsx/ExtensionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
import org.eclipse.openvsx.cache.CacheService;
import org.eclipse.openvsx.entities.*;
import org.eclipse.openvsx.publish.PublishExtensionVersionHandler;
import org.eclipse.openvsx.publish.PublishExtensionVersionJobRequest;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.TempFile;
import org.eclipse.openvsx.util.TimeUtil;
import org.jobrunr.scheduling.JobRequestScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
Expand All @@ -50,6 +52,9 @@ public class ExtensionService {
@Autowired
PublishExtensionVersionHandler publishHandler;

@Autowired
JobRequestScheduler scheduler;

@Value("${ovsx.publishing.require-license:false}")
boolean requireLicense;

Expand All @@ -61,10 +66,14 @@ public ExtensionVersion mirrorVersion(TempFile extensionFile, String signatureNa
}

public ExtensionVersion publishVersion(InputStream content, PersonalAccessToken token) {
var extensionFile = createExtensionFile(content);
var download = doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);
publishHandler.publishAsync(download, extensionFile, this);
return download.getExtension();
try(var extensionFile = createExtensionFile(content)) {
var download = doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);
publishHandler.persistDownload(download);
scheduler.enqueue(new PublishExtensionVersionJobRequest(download.getId()));
return download.getExtension();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private FileResource doPublish(TempFile extensionFile, String binaryName, PersonalAccessToken token, LocalDateTime timestamp, boolean checkDependencies) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
package org.eclipse.openvsx.publish;

import com.google.common.base.Joiner;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import org.eclipse.openvsx.ExtensionProcessor;
import org.eclipse.openvsx.ExtensionService;
import org.eclipse.openvsx.ExtensionValidator;
import org.eclipse.openvsx.UserService;
import org.eclipse.openvsx.adapter.VSCodeIdService;
Expand All @@ -23,18 +24,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@Component
Expand Down Expand Up @@ -201,50 +196,6 @@ private List<Extension> updateExistingPublicIds(Extension extension) {
return updatedExtensions;
}

@Async
@Retryable
public void publishAsync(FileResource download, TempFile extensionFile, ExtensionService extensionService) {
var extVersion = download.getExtension();

// Delete file resources in case publishAsync is retried
service.deleteFileResources(extVersion);
download.setId(0L);

service.storeDownload(download, extensionFile);
service.persistResource(download);
try(var processor = new ExtensionProcessor(extensionFile)) {
Consumer<FileResource> consumer = resource -> {
service.storeResource(resource);
service.persistResource(resource);
};

if(integrityService.isEnabled()) {
var keyPair = extVersion.getSignatureKeyPair();
if(keyPair != null) {
var signature = integrityService.generateSignature(download, extensionFile, keyPair);
consumer.accept(signature);
} else {
// Can happen when GenerateKeyPairJobRequestHandler hasn't run yet and there is no active SignatureKeyPair.
// This extension version should be assigned a SignatureKeyPair and a signature FileResource should be created
// by the ExtensionVersionSignatureJobRequestHandler migration.
logger.warn("Integrity service is enabled, but {} did not have an active key pair", NamingUtil.toLogFormat(extVersion));
}
}

processor.processEachResource(extVersion, consumer);
processor.getFileResources(extVersion).forEach(consumer);
consumer.accept(processor.generateSha256Checksum(extVersion));
}

// Update whether extension is active, the search index and evict cache
service.activateExtension(extVersion, extensionService);
try {
extensionFile.close();
} catch (IOException e) {
logger.error("failed to delete temp file", e);
}
}

public void mirror(FileResource download, TempFile extensionFile, String signatureName) {
var extVersion = download.getExtension();
service.mirrorResource(download);
Expand All @@ -265,4 +216,10 @@ private FileResource getSignatureResource(String signatureName, ExtensionVersion
resource.setType(FileResource.DOWNLOAD_SIG);
return resource;
}

@Transactional
public void persistDownload(FileResource download) {
download.setStorageType(FileResource.STORAGE_DB);
entityManager.persist(download);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/** ******************************************************************************
* Copyright (c) 2023 Precies. Software Ltd and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
* ****************************************************************************** */
package org.eclipse.openvsx.publish;

import org.eclipse.openvsx.ExtensionProcessor;
import org.eclipse.openvsx.entities.FileResource;
import org.eclipse.openvsx.migration.MigrationService;
import org.eclipse.openvsx.util.NamingUtil;
import org.jobrunr.jobs.lambdas.JobRequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.AbstractMap;
import java.util.function.Consumer;

@Component
public class PublishExtensionVersionJob implements JobRequestHandler<PublishExtensionVersionJobRequest> {

protected final Logger logger = LoggerFactory.getLogger(PublishExtensionVersionJob.class);

@Autowired
ExtensionVersionIntegrityService integrityService;

@Autowired
PublishExtensionVersionJobService service;

@Autowired
MigrationService migrations;

@Override
public void run(PublishExtensionVersionJobRequest jobRequest) throws Exception {
var download = service.getFileResource(jobRequest.getDownloadId());
var extVersion = download.getExtension();
logger.info("Processing files for {}", NamingUtil.toLogFormat(extVersion));

// Delete file resources in case job is retried
service.deleteFileResources(extVersion);

var content = migrations.getContent(download);
var extensionFile = migrations.getExtensionFile(new AbstractMap.SimpleEntry<>(download, content));
try(var processor = new ExtensionProcessor(extensionFile)) {
Consumer<FileResource> consumer = resource -> {
service.storeResource(resource);
service.persistResource(resource);
};

if(integrityService.isEnabled()) {
var keyPair = extVersion.getSignatureKeyPair();
if(keyPair != null) {
var signature = integrityService.generateSignature(download, extensionFile, keyPair);
consumer.accept(signature);
} else {
// Can happen when GenerateKeyPairJobRequestHandler hasn't run yet and there is no active SignatureKeyPair.
// This extension version should be assigned a SignatureKeyPair and a signature FileResource should be created
// by the ExtensionVersionSignatureJobRequestHandler migration.
logger.warn("Integrity service is enabled, but {} did not have an active key pair", NamingUtil.toLogFormat(extVersion));
}
}

processor.processEachResource(extVersion, consumer);
processor.getFileResources(extVersion).forEach(consumer);
consumer.accept(processor.generateSha256Checksum(extVersion));
}

service.storeDownload(download);

// Update whether extension is active, the search index and evict cache
service.activateExtension(extVersion);
if(!download.getStorageType().equals(FileResource.STORAGE_DB)) {
// Don't store the binary content in the DB - it's now stored externally
download.setContent(null);
}

service.updateResource(download);
logger.info("Published {}", NamingUtil.toLogFormat(extVersion));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/** ******************************************************************************
* Copyright (c) 2023 Precies. Software Ltd and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
* ****************************************************************************** */
package org.eclipse.openvsx.publish;

import org.jobrunr.jobs.lambdas.JobRequest;
import org.jobrunr.jobs.lambdas.JobRequestHandler;

public class PublishExtensionVersionJobRequest implements JobRequest {

private long downloadId;

public PublishExtensionVersionJobRequest() {}

public PublishExtensionVersionJobRequest(long downloadId) {
this.downloadId = downloadId;
}

public long getDownloadId() {
return downloadId;
}

public void setDownloadId(long downloadId) {
this.downloadId = downloadId;
}

@Override
public Class<? extends JobRequestHandler> getJobRequestHandler() {
return PublishExtensionVersionJob.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/** ******************************************************************************
* Copyright (c) 2023 Precies. Software Ltd and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
* ****************************************************************************** */
package org.eclipse.openvsx.publish;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import org.eclipse.openvsx.ExtensionService;
import org.eclipse.openvsx.entities.ExtensionVersion;
import org.eclipse.openvsx.entities.FileResource;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.storage.StorageUtilService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

@Component
public class PublishExtensionVersionJobService {

@Autowired
EntityManager entityManager;

@Autowired
RepositoryService repositories;

@Autowired
StorageUtilService storageUtil;

@Autowired
ExtensionService extensions;

public FileResource getFileResource(long id) {
return entityManager.find(FileResource.class, id);
}

@Transactional
public void deleteFileResources(ExtensionVersion extVersion) {
repositories.findFiles(extVersion)
.filter(f -> !f.getType().equals(FileResource.DOWNLOAD))
.forEach(entityManager::remove);
}

@Retryable
public void storeDownload(FileResource resource) {
// Store file resource in the DB or external storage
if (storageUtil.shouldStoreExternally(resource)) {
storageUtil.uploadFile(resource);
} else {
resource.setStorageType(FileResource.STORAGE_DB);
}
}

@Retryable
public void storeResource(FileResource resource) {
// Store file resource in the DB or external storage
if (storageUtil.shouldStoreExternally(resource)) {
storageUtil.uploadFile(resource);
// Don't store the binary content in the DB - it's now stored externally
resource.setContent(null);
} else {
resource.setStorageType(FileResource.STORAGE_DB);
}
}

@Transactional
public void persistResource(FileResource resource) {
entityManager.persist(resource);
}

@Transactional
public void activateExtension(ExtensionVersion extVersion) {
extVersion.setActive(true);
extVersion = entityManager.merge(extVersion);
extensions.updateExtension(extVersion.getExtension());
}

@Transactional
public void updateResource(FileResource resource) {
entityManager.merge(resource);
}
}
Loading

0 comments on commit f925747

Please sign in to comment.