feat(launcher): enhance flow-mds with support for retries with increasing delays to prevent log spam
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/push/docs Pipeline was successful

This commit is contained in:
Johannes Frohnmeyer 2024-06-29 18:26:20 +02:00
parent a67a0b4b4f
commit fabe5afed0
Signed by: Johannes
GPG Key ID: E76429612C2929F4
5 changed files with 84 additions and 20 deletions

View File

@ -150,7 +150,11 @@ public class FlowMds implements ModsDirScanner {
try { try {
performScanTask(Map.of(ScanStage.ALL, R::nop)); performScanTask(Map.of(ScanStage.ALL, R::nop));
Thread.sleep(1000); Thread.sleep(1000);
} catch (IOException | InterruptedException e) { } catch (IOException e) {
for (SavedTask task : tasks.values()) {
task.future.completeExceptionally(e);
}
} catch (InterruptedException e) {
for (SavedTask task : tasks.values()) { for (SavedTask task : tasks.values()) {
task.future.completeExceptionally(e); task.future.completeExceptionally(e);
} }
@ -159,6 +163,7 @@ public class FlowMds implements ModsDirScanner {
} }
} }
private final HashMap<String, MdsRetryState> timeouts = new HashMap<>();
private void performScanTask(Map<ScanStage, ThrowingBiConsumer<Path, Mod, IOException>> discovered) throws IOException { private void performScanTask(Map<ScanStage, ThrowingBiConsumer<Path, Mod, IOException>> discovered) throws IOException {
if (!Files.isDirectory(instance.modsDir())) { if (!Files.isDirectory(instance.modsDir())) {
return; return;
@ -171,22 +176,22 @@ public class FlowMds implements ModsDirScanner {
ScanStage targetStage = discovered.keySet().stream() ScanStage targetStage = discovered.keySet().stream()
.max(Comparator.naturalOrder()) .max(Comparator.naturalOrder())
.orElse(ScanStage.DISCOVER); .orElse(ScanStage.DISCOVER);
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.NONE)); pipeline.addFeedbackTask(discovered.get(ScanStage.NONE));
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.DISCOVER)); pipeline.addFeedbackTask(discovered.get(ScanStage.DISCOVER));
if (targetStage.contains(ScanStage.DOWNLOAD)) { if (targetStage.contains(ScanStage.DOWNLOAD)) {
pipeline.addTask(ScanStage.DOWNLOAD, new MdsDownloadTask(instance)); pipeline.addTask(ScanStage.DOWNLOAD, new MdsTaskRetryWrapper(timeouts, new MdsDownloadTask(instance)));
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.DOWNLOAD)); pipeline.addFeedbackTask(discovered.get(ScanStage.DOWNLOAD));
} }
if (targetStage.contains(ScanStage.CROSSREFERENCE)) { if (targetStage.contains(ScanStage.CROSSREFERENCE)) {
pipeline.addTask(ScanStage.CROSSREFERENCE, new MdsCrossReferenceTask(instance)); pipeline.addTask(ScanStage.CROSSREFERENCE, new MdsTaskRetryWrapper(timeouts, new MdsCrossReferenceTask(instance)));
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.CROSSREFERENCE)); pipeline.addFeedbackTask(discovered.get(ScanStage.CROSSREFERENCE));
} }
if (targetStage.contains(ScanStage.UPDATECHECK)) { if (targetStage.contains(ScanStage.UPDATECHECK)) {
pipeline.addTask(ScanStage.UPDATECHECK, new MdsUpdateTask(instance, getGameVersion())); pipeline.addTask(ScanStage.UPDATECHECK, new MdsTaskRetryWrapper(timeouts, new MdsUpdateTask(instance, getGameVersion())));
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.UPDATECHECK)); pipeline.addFeedbackTask(discovered.get(ScanStage.UPDATECHECK));
} }
pipeline.addTask(ScanStage.NONE, discovered.get(ScanStage.ALL)); pipeline.addFeedbackTask(discovered.get(ScanStage.ALL));
var futures1 = pipeline.run(factory, getToScan(), new MdsDiscoverTask(instance, getGameVersion())); var futures1 = pipeline.run(factory, getToScan(), new MdsDiscoverTask(timeouts, instance, getGameVersion()));
var futures2 = pipeline.run(factory, descriptions.entrySet().stream().map(Tuple::from).collect(Collectors.toSet())); var futures2 = pipeline.run(factory, descriptions.entrySet().stream().map(Tuple::from).collect(Collectors.toSet()));
for (CompletableFuture<Void> future : Stream.concat(futures1.stream(), futures2.stream()).toList()) { for (CompletableFuture<Void> future : Stream.concat(futures1.stream(), futures2.stream()).toList()) {
try { try {

View File

@ -11,8 +11,9 @@ import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMod;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap;
public record MdsDiscoverTask(ProtoInstance instance, String gameVersion) implements ThrowingFunction<Path, Mod, IOException> { public record MdsDiscoverTask(HashMap<String, MdsRetryState> timeouts, ProtoInstance instance, String gameVersion) implements ThrowingFunction<Path, Mod, IOException> {
@Override @Override
public Mod apply(Path file) throws IOException { public Mod apply(Path file) throws IOException {
if (!Files.exists(file)) return null; if (!Files.exists(file)) return null;
@ -23,12 +24,22 @@ public record MdsDiscoverTask(ProtoInstance instance, String gameVersion) implem
} }
private Mod discover(Path jarPath, Path imodPath) throws IOException { private Mod discover(Path jarPath, Path imodPath) throws IOException {
String key = imodPath.getFileName().toString() + "/" + MdsDiscoverTask.class.getTypeName();
MdsRetryState state = timeouts.computeIfAbsent(key, _ -> new MdsRetryState());
if (!state.shouldRetry()) return null;
try {
ModMeta meta; ModMeta meta;
if (Files.exists(imodPath)) meta = GC_ModMeta.deserialize(imodPath, GsonPreset.CONFIG); if (Files.exists(imodPath)) meta = GC_ModMeta.deserialize(imodPath, GsonPreset.CONFIG);
else { else {
meta = ModMeta.fromJar(jarPath); meta = ModMeta.fromJar(jarPath);
GC_ModMeta.serialize(meta, imodPath, GsonPreset.CONFIG); GC_ModMeta.serialize(meta, imodPath, GsonPreset.CONFIG);
} }
return new MdsMod(instance, imodPath, jarPath, meta); Mod result = new MdsMod(instance, imodPath, jarPath, meta);
state.success();
return result;
} catch (RuntimeException | IOException e) {
state.fail();
throw e;
}
} }
} }

View File

@ -9,7 +9,6 @@ import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
public class MdsPipeline { public class MdsPipeline {
@ -22,7 +21,14 @@ public class MdsPipeline {
})); }));
} }
public void addTask(ScanStage stage, @Nullable ThrowingBiConsumer<Path, ? super MdsMod, IOException> task) { public void addFeedbackTask(@Nullable ThrowingBiConsumer<Path, Mod, IOException> task) {
if (task == null) return;
functions.add(Tuple.of(ScanStage.NONE, tuple -> {
if (tuple.right() != null) task.accept(tuple.left(), tuple.right());
}));
}
public void addTask(ScanStage stage, @Nullable ThrowingBiConsumer<Path, MdsMod, IOException> task) {
if (task == null) return; if (task == null) return;
functions.add(Tuple.of(stage, tuple -> { functions.add(Tuple.of(stage, tuple -> {
if (tuple.right() instanceof MdsMod mmod) task.accept(tuple.left(), mmod); if (tuple.right() instanceof MdsMod mmod) task.accept(tuple.left(), mmod);

View File

@ -0,0 +1,19 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
public class MdsRetryState {
private int retries = 0;
private long nextRetry = 0;
public boolean shouldRetry() {
return retries == 0 || System.currentTimeMillis() > nextRetry;
}
public void success() {
retries = 0;
}
public void fail() {
retries++;
nextRetry = System.currentTimeMillis() + (1L << retries) * 1000;
}
}

View File

@ -0,0 +1,23 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import io.gitlab.jfronny.inceptum.launcher.system.mds.MdsMod;
import java.io.IOException;
import java.util.Map;
public record MdsTaskRetryWrapper(Map<String, MdsRetryState> timeouts, ThrowingConsumer<MdsMod, IOException> inner) implements ThrowingConsumer<MdsMod, IOException> {
@Override
public void accept(MdsMod mod) throws IOException {
String key = mod.getMetadataPath().getFileName().toString() + "/" + inner.getClass().getTypeName();
MdsRetryState state = timeouts.computeIfAbsent(key, _ -> new MdsRetryState());
if (!state.shouldRetry()) return;
try {
inner.accept(mod);
state.success();
} catch (IOException e) {
state.fail();
throw e;
}
}
}