From cc221d31c7e36f5279d7766f82b4ce2bf87d545b Mon Sep 17 00:00:00 2001 From: John Niang Date: Wed, 12 Oct 2022 12:26:15 +0800 Subject: [PATCH] Continue extension updation if modification conflict occurs (#2536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind bug /area core /milestone 2.0 #### What this PR does / why we need it: When we initialize default extensions at Halo startup, updation of one extension is very likely to be failed due to modification conflict. Then, remaining extensions won't be updated anymore. Therefore, this PR resolve this problem by retrying updation with 3 times with interval 100ms and continue extension updation if modification conflict occurs. #### How to test 1. Fully initialize system(e.g.: Clear `~/halo-next` before starting Halo) 2. Update Role `authenticated` as you wish via Extension API 3. Restart Halo and check it again 4. Retry multiple times #### Does this PR introduce a user-facing change? ```release-note 修复系统默认数据无法正常更新的问题 ``` --- .../infra/ExtensionResourceInitializer.java | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/run/halo/app/infra/ExtensionResourceInitializer.java b/src/main/java/run/halo/app/infra/ExtensionResourceInitializer.java index f5a5753993..03b5bfcbda 100644 --- a/src/main/java/run/halo/app/infra/ExtensionResourceInitializer.java +++ b/src/main/java/run/halo/app/infra/ExtensionResourceInitializer.java @@ -1,6 +1,7 @@ package run.halo.app.infra; import java.io.IOException; +import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -9,10 +10,12 @@ import org.springframework.context.event.EventListener; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Unstructured; import run.halo.app.infra.properties.HaloProperties; @@ -60,18 +63,17 @@ public Mono initialize(ApplicationReadyEvent readyEvent) { .map(this::listResources) .distinct() .flatMapIterable(resources -> resources) - .doOnNext(resource -> log.debug("Initializing extension resource: {}", resource)) + .doOnNext(resource -> log.debug("Initializing extension resource from location: {}", + resource)) .map(resource -> new YamlUnstructuredLoader(resource).load()) .flatMapIterable(extensions -> extensions) - .flatMap(extension -> extensionClient.fetch(extension.groupVersionKind(), - extension.getMetadata().getName()) - .flatMap(createdExtension -> { - extension.getMetadata() - .setVersion(createdExtension.getMetadata().getVersion()); - return extensionClient.update(extension); - }) - .switchIfEmpty(Mono.defer(() -> extensionClient.create(extension))) - ) + .doOnNext(extension -> { + if (log.isDebugEnabled()) { + log.debug("Initializing extension resource: {}/{}", + extension.groupVersionKind(), extension.getMetadata().getName()); + } + }) + .flatMap(this::createOrUpdate) .doOnNext(extension -> { if (log.isDebugEnabled()) { log.debug("Initialized extension resource: {}/{}", extension.groupVersionKind(), @@ -81,6 +83,24 @@ public Mono initialize(ApplicationReadyEvent readyEvent) { .then(); } + private Mono createOrUpdate(Unstructured extension) { + return Mono.just(extension) + .flatMap(ext -> extensionClient.fetch(extension.groupVersionKind(), + extension.getMetadata().getName())) + .flatMap(existingExt -> { + extension.getMetadata().setVersion(existingExt.getMetadata().getVersion()); + return extensionClient.update(extension); + }) + .switchIfEmpty(Mono.defer(() -> extensionClient.create(extension))) + .retryWhen(Retry.fixedDelay(3, Duration.ofMillis(100)) + .filter(t -> t instanceof OptimisticLockingFailureException)) + .onErrorContinue(OptimisticLockingFailureException.class, (throwable, o) -> { + log.warn("Failed to create or update extension resource: {}/{} due to modification " + + "conflict", + extension.groupVersionKind(), extension.getMetadata().getName()); + }); + } + private List listResources(String location) { var resolver = new PathMatchingResourcePatternResolver(); try {