From fcc143a24facaeaf149b29f545f21e6e44ab6ba3 Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Thu, 8 Oct 2020 13:42:51 +0800 Subject: [PATCH] Port async catcher --- README.md | 2 +- .../java/io/izzel/arclight/api/Unsafe.java | 22 +- .../common/asm/ArclightImplementer.java | 17 ++ .../arclight/common/asm/AsyncCatcher.java | 255 ++++++++++++++++++ .../core/server/MinecraftServerMixin.java | 3 + .../common/mod/server/ArclightServer.java | 18 ++ .../src/main/resources/async_catcher.json | 22 ++ .../arclight/i18n/conf/AsyncCatcherSpec.java | 42 +++ .../izzel/arclight/i18n/conf/ConfigSpec.java | 7 + .../src/main/resources/META-INF/arclight.conf | 7 + .../main/resources/META-INF/i18n/zh_cn.conf | 9 + 11 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 arclight-common/src/main/java/io/izzel/arclight/common/asm/AsyncCatcher.java create mode 100644 arclight-common/src/main/resources/async_catcher.json create mode 100644 i18n-config/src/main/java/io/izzel/arclight/i18n/conf/AsyncCatcherSpec.java diff --git a/README.md b/README.md index 7147d43c..0e309333 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A Bukkit server implementation utilizing Mixin. | Minecraft | Forge | Status | Build | | :----: | :----: | :---: | :---: | | 1.16.x | 34.1.7 | ACTIVE | [![1.16 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight-16?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight-16) | -| 1.15.x | 31.2.37 | ACTIVE | [![1.15 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight-15?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight-15) | +| 1.15.x | 31.2.45 | ACTIVE | [![1.15 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight-15?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight-15) | | 1.14.x | 28.2.0 | [LEGACY](https://github.com/IzzelAliz/Arclight/releases/tag/1.0.6) | [![1.14 Status](https://img.shields.io/appveyor/build/IzzelAliz/arclight?style=flat-square)](https://ci.appveyor.com/project/IzzelAliz/arclight) | * Legacy version still accepts pull requests. diff --git a/arclight-api/src/main/java/io/izzel/arclight/api/Unsafe.java b/arclight-api/src/main/java/io/izzel/arclight/api/Unsafe.java index b2c0c90b..f4c1a06f 100644 --- a/arclight-api/src/main/java/io/izzel/arclight/api/Unsafe.java +++ b/arclight-api/src/main/java/io/izzel/arclight/api/Unsafe.java @@ -2,6 +2,7 @@ package io.izzel.arclight.api; import sun.reflect.CallerSensitive; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; import java.security.ProtectionDomain; @@ -11,6 +12,7 @@ public class Unsafe { private static final sun.misc.Unsafe unsafe; private static final MethodHandles.Lookup lookup; + private static final MethodHandle defineClass; static { try { @@ -22,11 +24,24 @@ public class Unsafe { Object base = unsafe.staticFieldBase(field); long offset = unsafe.staticFieldOffset(field); lookup = (MethodHandles.Lookup) unsafe.getObject(base, offset); + defineClass = lookup.unreflect(ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class)); } catch (Exception e) { throw new RuntimeException(e); } } + public static T getStatic(Class cl, String name) { + try { + Unsafe.ensureClassInitialized(cl); + Field field = cl.getDeclaredField(name); + Object materialByNameBase = Unsafe.staticFieldBase(field); + long materialByNameOffset = Unsafe.staticFieldOffset(field); + return (T) Unsafe.getObject(materialByNameBase, materialByNameOffset); + } catch (Exception e) { + return null; + } + } + public static MethodHandles.Lookup lookup() { return lookup; } @@ -337,7 +352,12 @@ public class Unsafe { } public static Class defineClass(String s, byte[] bytes, int i, int i1, ClassLoader classLoader, ProtectionDomain protectionDomain) { - return unsafe.defineClass(s, bytes, i, i1, classLoader, protectionDomain); + try { + return (Class) defineClass.bindTo(classLoader).invoke(s, bytes, i , i1, protectionDomain); + } catch (Throwable throwable) { + throwException(throwable); + return null; + } } public static Class defineAnonymousClass(Class aClass, byte[] bytes, Object[] objects) { diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/asm/ArclightImplementer.java b/arclight-common/src/main/java/io/izzel/arclight/common/asm/ArclightImplementer.java index 3fa3d726..5aaf2acc 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/asm/ArclightImplementer.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/asm/ArclightImplementer.java @@ -3,10 +3,15 @@ package io.izzel.arclight.common.asm; import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; import io.izzel.arclight.common.mod.util.log.ArclightI18nLogger; import org.apache.logging.log4j.Logger; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; import org.spongepowered.asm.launch.MixinLaunchPlugin; +import java.lang.reflect.Modifier; import java.nio.file.Path; import java.util.ArrayList; import java.util.EnumSet; @@ -36,6 +41,7 @@ public class ArclightImplementer implements ILaunchPluginService { this.transformerLoader = transformerLoader; this.implementers.put("inventory", new InventoryImplementer()); this.implementers.put("switch", SwitchTableFixer.INSTANCE); + this.implementers.put("async", AsyncCatcher.INSTANCE); } @Override @@ -79,4 +85,15 @@ public class ArclightImplementer implements ILaunchPluginService { public boolean processClass(Phase phase, ClassNode classNode, Type classType) { throw new IllegalStateException("Outdated ModLauncher"); } + + public static void loadArgs(InsnList list, MethodNode methodNode, Type[] types, int i) { + if (!Modifier.isStatic(methodNode.access)) { + list.add(new VarInsnNode(Opcodes.ALOAD, i)); + i += 1; + } + for (Type type : types) { + list.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), i)); + i += type.getSize(); + } + } } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/asm/AsyncCatcher.java b/arclight-common/src/main/java/io/izzel/arclight/common/asm/AsyncCatcher.java new file mode 100644 index 00000000..5ce99ad4 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/asm/AsyncCatcher.java @@ -0,0 +1,255 @@ +package io.izzel.arclight.common.asm; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import io.izzel.arclight.api.Unsafe; +import io.izzel.arclight.common.mod.server.ArclightServer; +import io.izzel.arclight.i18n.ArclightConfig; +import io.izzel.arclight.i18n.conf.AsyncCatcherSpec; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.GeneratorAdapter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.util.Constants; + +import java.io.InputStreamReader; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +public class AsyncCatcher implements Implementer { + + public static final AsyncCatcher INSTANCE = new AsyncCatcher(); + private static final Marker MARKER = MarkerManager.getMarker("ASYNC_CATCHER"); + private static final CallbackInfoReturnable NOOP = new CallbackInfoReturnable<>("noop", false); + private static final AtomicInteger COUNTER = new AtomicInteger(0); + + private final boolean dump; + private final boolean warn; + private final AsyncCatcherSpec.Operation defaultOp; + private final Map> reasons; + private final ClassLoader classLoader; + + public AsyncCatcher() { + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + this.reasons = gson.fromJson( + new InputStreamReader(AsyncCatcher.class.getResourceAsStream("/async_catcher.json")), + new TypeToken>>() {}.getType() + ); + this.defaultOp = ArclightConfig.spec().getAsyncCatcher().getDefaultOp(); + this.dump = ArclightConfig.spec().getAsyncCatcher().isDump(); + this.warn = ArclightConfig.spec().getAsyncCatcher().isWarn(); + this.classLoader = Thread.currentThread().getContextClassLoader(); + } + + @Override + public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) { + Map map = reasons.get(node.name); + if (map != null) { + boolean found = false; + List methods = node.methods; + for (int i = 0, methodsSize = methods.size(); i < methodsSize; i++) { + MethodNode method = methods.get(i); + String reason = map.get(method.name + method.desc); + if (reason != null) { + found = true; + injectCheck(node, method, reason); + } + } + return found; + } + return false; + } + + private void injectCheck(ClassNode node, MethodNode methodNode, String reason) { + ArclightImplementer.LOGGER.debug(MARKER, "Injecting {}/{}{} for reason {}", node.name, methodNode.name, methodNode.desc, reason); + AsyncCatcherSpec.Operation operation = ArclightConfig.spec().getAsyncCatcher().getOverrides().getOrDefault(reason, defaultOp); + InsnList insnList = new InsnList(); + LabelNode labelNode = new LabelNode(new Label()); + LabelNode labelNode1 = new LabelNode(new Label()); + insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/izzel/arclight/common/mod/server/ArclightServer", "isPrimaryThread", "()Z")); + insnList.add(new JumpInsnNode(Opcodes.IFNE, labelNode)); + instantiateCallback(node, methodNode, insnList); + insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getType(AsyncCatcherSpec.Operation.class).getInternalName(), operation.name(), Type.getType(AsyncCatcherSpec.Operation.class).getDescriptor())); + insnList.add(new LdcInsnNode(reason)); + insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getType(AsyncCatcher.class).getInternalName(), "checkOp", "(Ljava/util/function/Supplier;Lio/izzel/arclight/i18n/conf/AsyncCatcherSpec$Operation;Ljava/lang/String;)Lorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable;")); + Type returnType = Type.getMethodType(methodNode.desc).getReturnType(); + boolean hasReturn = !returnType.equals(Type.VOID_TYPE); + if (hasReturn) { + insnList.add(new InsnNode(Opcodes.DUP)); + } + insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getType(CallbackInfoReturnable.class).getInternalName(), "isCancelled", "()Z")); + insnList.add(new JumpInsnNode(Opcodes.IFEQ, hasReturn ? labelNode1 : labelNode)); + if (hasReturn) { + insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getType(CallbackInfoReturnable.class).getInternalName(), getReturnAccessor(returnType), getReturnDescriptor(returnType))); + if (returnType.getSort() > Type.DOUBLE) { + insnList.add(new TypeInsnNode(Opcodes.CHECKCAST, returnType.getInternalName())); + } + insnList.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN))); + } else { + insnList.add(new InsnNode(Opcodes.RETURN)); + } + insnList.add(labelNode1); + insnList.add(new InsnNode(Opcodes.POP)); + insnList.add(labelNode); + methodNode.instructions.insert(insnList); + } + + private void instantiateCallback(ClassNode node, MethodNode methodNode, InsnList insnList) { + MethodNode bridge; + if (Modifier.isPrivate(methodNode.access)) { + bridge = createBridge(node, methodNode); + } else { + bridge = methodNode; + } + + ClassNode classNode = new ClassNode(); + String desc = createImplType(node, methodNode, classNode, bridge); + + insnList.add(new TypeInsnNode(Opcodes.NEW, classNode.name)); + insnList.add(new InsnNode(Opcodes.DUP)); + Type methodType = Type.getMethodType(methodNode.desc); + ArclightImplementer.loadArgs(insnList, methodNode, methodType.getArgumentTypes(), 0); + insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.name, "", desc)); + } + + private String createImplType(ClassNode node, MethodNode methodNode, ClassNode classNode, MethodNode bridge) { + classNode.version = Opcodes.V1_8; + classNode.name = node.name + "$AsyncCatcher$" + COUNTER.getAndIncrement(); + classNode.access = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL; + classNode.superName = "java/lang/Object"; + classNode.interfaces.add(Type.getType(Supplier.class).getInternalName()); + List types = new ArrayList<>(); + if (!Modifier.isStatic(methodNode.access)) { + types.add(Type.getObjectType(node.name)); + } + types.addAll(Arrays.asList(Type.getArgumentTypes(methodNode.desc))); + + for (int i = 0; i < types.size(); i++) { + FieldNode fieldNode = new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "x" + i, types.get(i).getDescriptor(), null, null); + classNode.fields.add(fieldNode); + } + + MethodNode init = new MethodNode(); + init.name = ""; + init.desc = Type.getMethodType(Type.VOID_TYPE, types.toArray(new Type[0])).getDescriptor(); + init.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + init.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V")); + + int offset = 1; + for (int i = 0; i < types.size(); i++) { + init.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + init.instructions.add(new VarInsnNode(types.get(i).getOpcode(Opcodes.ILOAD), offset)); + init.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, "x" + i, types.get(i).getDescriptor())); + offset += types.get(i).getSize(); + } + init.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(init); + + MethodNode get = new MethodNode(); + get.name = "get"; + get.desc = "()Ljava/lang/Object;"; + GeneratorAdapter adapter = new GeneratorAdapter(get, Opcodes.ACC_PUBLIC, get.name, get.desc); + for (int i = 0; i < types.size(); i++) { + adapter.loadThis(); + adapter.getField(Type.getObjectType(classNode.name), "x" + i, types.get(i)); + } + get.instructions.add(new MethodInsnNode( + Modifier.isStatic(methodNode.access) ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL, + node.name, bridge.name, bridge.desc + )); + adapter.valueOf(Type.getReturnType(bridge.desc)); + adapter.returnValue(); + classNode.methods.add(get); + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + byte[] bytes = writer.toByteArray(); + Unsafe.defineClass(Type.getObjectType(classNode.name).getClassName(), bytes, 0, bytes.length, this.classLoader, AsyncCatcher.class.getProtectionDomain()); + ArclightImplementer.LOGGER.debug(MARKER, "Defined impl callback class {}", classNode.name); + return init.desc; + } + + private MethodNode createBridge(ClassNode node, MethodNode methodNode) { + MethodNode ret = new MethodNode(); + ret.name = methodNode.name + "$asyncCatcher$" + COUNTER.getAndIncrement(); + ret.desc = methodNode.desc; + ret.access = Opcodes.ACC_PUBLIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC; + if (Modifier.isStatic(methodNode.access)) { + ret.access = ret.access | Opcodes.ACC_STATIC; + } + Type methodType = Type.getMethodType(methodNode.desc); + ArclightImplementer.loadArgs(ret.instructions, methodNode, methodType.getArgumentTypes(), 0); + int invokeCode = Modifier.isStatic(methodNode.access) ? Opcodes.INVOKESTATIC : Opcodes.INVOKESPECIAL; + ret.instructions.add(new MethodInsnNode(invokeCode, node.name, methodNode.name, methodNode.desc)); + ret.instructions.add(new InsnNode(methodType.getReturnType().getOpcode(Opcodes.IRETURN))); + node.methods.add(ret); + ArclightImplementer.LOGGER.debug(MARKER, "Bridge method {}/{}{} created", node.name, ret.name, ret.desc); + return ret; + } + + static String getReturnAccessor(org.objectweb.asm.Type returnType) { + if (returnType.getSort() == org.objectweb.asm.Type.OBJECT || returnType.getSort() == org.objectweb.asm.Type.ARRAY) { + return "getReturnValue"; + } + return String.format("getReturnValue%s", returnType.getDescriptor()); + } + + static String getReturnDescriptor(org.objectweb.asm.Type returnType) { + if (returnType.getSort() == org.objectweb.asm.Type.OBJECT || returnType.getSort() == org.objectweb.asm.Type.ARRAY) { + return String.format("()%s", Constants.OBJECT_DESC); + } + return String.format("()%s", returnType.getDescriptor()); + } + + @SuppressWarnings("unchecked") + public static CallbackInfoReturnable checkOp(Supplier method, AsyncCatcherSpec.Operation operation, String reason) throws Throwable { + if (INSTANCE.warn) { + ArclightImplementer.LOGGER.warn(MARKER, "Async " + reason); + } + IllegalStateException exception = new IllegalStateException("Asynchronous " + reason + "!"); + if (INSTANCE.dump) { + ArclightImplementer.LOGGER.debug(MARKER, "Async " + reason, exception); + } + switch (operation) { + case NONE: return (CallbackInfoReturnable) NOOP; + case EXCEPTION: throw exception; + case BLOCK: { + CallbackInfoReturnable cir = new CallbackInfoReturnable<>(reason, true); + CompletableFuture future = CompletableFuture.supplyAsync(method, ArclightServer.getMainThreadExecutor()); + cir.setReturnValue(future.get(5, TimeUnit.SECONDS)); + return cir; + } + case DISPATCH: { + ArclightServer.executeOnMainThread(method::get); + CallbackInfoReturnable cir = new CallbackInfoReturnable<>(reason, true); + cir.cancel(); + return cir; + } + } + throw new IllegalStateException("how this can happen?"); + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/MinecraftServerMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/MinecraftServerMixin.java index d3b91407..35d6342d 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/MinecraftServerMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/MinecraftServerMixin.java @@ -295,6 +295,9 @@ public abstract class MinecraftServerMixin extends RecursiveEventLoop key) { return BukkitRegistry.DIM_MAP.get(key); } diff --git a/arclight-common/src/main/resources/async_catcher.json b/arclight-common/src/main/resources/async_catcher.json new file mode 100644 index 00000000..84b100ef --- /dev/null +++ b/arclight-common/src/main/resources/async_catcher.json @@ -0,0 +1,22 @@ +{ + "net/minecraft/world/server/ServerWorld": { + "func_72838_d(Lnet/minecraft/entity/Entity;)Z": "entity add", + "func_217465_m(Lnet/minecraft/entity/Entity;)V": "entity register", + "removeEntityComplete(Lnet/minecraft/entity/Entity;Z)V": "entity unregister" + }, + "net/minecraft/world/server/ChunkManager$EntityTracker": { + "func_219399_a(Lnet/minecraft/entity/player/ServerPlayerEntity;)V": "player tracker clear", + "func_219400_b(Lnet/minecraft/entity/player/ServerPlayerEntity;)V": "player tracker update" + }, + "net/minecraft/block/Block": { + "func_220082_b(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Z)V": "block place", + "func_196243_a(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Z)V": "block remove" + }, + "net/minecraft/entity/LivingEntity": { + "func_195064_c(Lnet/minecraft/potion/EffectInstance;)Z": "effect add" + }, + "net/minecraft/world/server/ChunkManager": { + "func_219210_a(Lnet/minecraft/entity/Entity;)V": "entity track", + "func_219231_b(Lnet/minecraft/entity/Entity;)V": "entity untrack" + } +} \ No newline at end of file diff --git a/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/AsyncCatcherSpec.java b/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/AsyncCatcherSpec.java new file mode 100644 index 00000000..3e11c180 --- /dev/null +++ b/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/AsyncCatcherSpec.java @@ -0,0 +1,42 @@ +package io.izzel.arclight.i18n.conf; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.util.Map; + +@ConfigSerializable +public class AsyncCatcherSpec { + + @Setting("dump") + private boolean dump; + + @Setting("warn") + private boolean warn; + + @Setting("defaultOperation") + private Operation defaultOp; + + @Setting("overrides") + private Map overrides; + + public boolean isDump() { + return dump; + } + + public boolean isWarn() { + return warn; + } + + public Operation getDefaultOp() { + return defaultOp; + } + + public Map getOverrides() { + return overrides; + } + + public enum Operation { + NONE, DISPATCH, BLOCK, EXCEPTION + } +} diff --git a/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/ConfigSpec.java b/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/ConfigSpec.java index 308270c8..93bedf48 100644 --- a/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/ConfigSpec.java +++ b/i18n-config/src/main/java/io/izzel/arclight/i18n/conf/ConfigSpec.java @@ -18,6 +18,9 @@ public class ConfigSpec { @Setting("compatibility") private CompatSpec compatSpec; + @Setting("async-catcher") + private AsyncCatcherSpec asyncCatcherSpec; + public int getVersion() { return version; } @@ -33,4 +36,8 @@ public class ConfigSpec { public CompatSpec getCompat() { return compatSpec; } + + public AsyncCatcherSpec getAsyncCatcher() { + return asyncCatcherSpec; + } } diff --git a/i18n-config/src/main/resources/META-INF/arclight.conf b/i18n-config/src/main/resources/META-INF/arclight.conf index a0988091..26425bcd 100644 --- a/i18n-config/src/main/resources/META-INF/arclight.conf +++ b/i18n-config/src/main/resources/META-INF/arclight.conf @@ -11,4 +11,11 @@ compatibility { } entity-property-overrides { } +} +async-catcher { + dump = true + warn = true + defaultOperation = block + overrides { + } } \ No newline at end of file diff --git a/i18n-config/src/main/resources/META-INF/i18n/zh_cn.conf b/i18n-config/src/main/resources/META-INF/i18n/zh_cn.conf index 21b3c1a7..70c35bea 100644 --- a/i18n-config/src/main/resources/META-INF/i18n/zh_cn.conf +++ b/i18n-config/src/main/resources/META-INF/i18n/zh_cn.conf @@ -81,4 +81,13 @@ comments { ] locale.comment = "语言/国际化相关设置" optimization.comment = "服务端优化相关设置" + async-catcher.comment = [ + "异步捕获相关设置" + "Async Catcher 共有四种模式" + "NONE - 保持在当前线程执行" + "DISPATCH - 不阻塞地发布到主线程" + "BLOCK - 发不到主线程并等待结果" + "EXCEPTION - 抛出异常" + ] + async-catcher.dump.comment = "是否在 debug 日志中打印堆栈信息" }