bitflicker64 commented on code in PR #805: URL: https://github.com/apache/hugegraph-toolchain/pull/805#discussion_r4278148873
########## hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/unit/BatchInsertRetryTest.java: ########## @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.loader.test.unit; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.driver.HugeClient; +import org.apache.hugegraph.exception.ServerException; +import org.apache.hugegraph.loader.builder.Record; +import org.apache.hugegraph.loader.executor.LoadContext; +import org.apache.hugegraph.loader.executor.LoadOptions; +import org.apache.hugegraph.loader.mapping.EdgeMapping; +import org.apache.hugegraph.loader.mapping.InputStruct; +import org.apache.hugegraph.loader.metrics.LoadMetrics; +import org.apache.hugegraph.loader.metrics.LoadSummary; +import org.apache.hugegraph.loader.progress.LoadProgress; +import org.apache.hugegraph.loader.source.file.FileSource; +import org.apache.hugegraph.loader.task.BatchInsertTask; +import org.apache.hugegraph.structure.graph.Edge; +import org.apache.hugegraph.testutil.Assert; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; + +/** + * Batch insert retries against a local fake server + */ +public class BatchInsertRetryTest { + + private static final String VERSIONS = + "{\"versions\":{\"version\":\"v1\",\"core\":\"1.8.0\"," + + "\"gremlin\":\"3.8.1\",\"api\":\"0.72.0.0\"}}"; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private HttpServer server; + private HugeClient client; + private final AtomicInteger batchRequests = new AtomicInteger(); + private final List<Handler> batchHandlers = Collections.synchronizedList(new ArrayList<>()); + + @Before + public void setup() throws IOException { + this.server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + this.server.createContext("/", exchange -> { + String path = exchange.getRequestURI().getPath(); + if (path.endsWith("/versions")) { + reply(exchange, 200, VERSIONS); + } else if (path.endsWith("/graph/edges/batch")) { + int index = this.batchRequests.getAndIncrement(); + Handler handler = index < this.batchHandlers.size() ? + this.batchHandlers.get(index) : + e -> reply(e, 201, "[\"e1\"]"); + handler.handle(exchange); + } else { + reply(exchange, 404, "{\"exception\":\"NotFoundException\"," + + "\"message\":\"" + path + "\"}"); + } + }); + this.server.start(); + this.client = HugeClient.builder("http://127.0.0.1:" + + this.server.getAddress().getPort(), + "DEFAULT", "hugegraph") + .configTimeout(1) + .build(); + } + + @After + public void teardown() { + if (this.client != null) { + this.client.close(); + } + this.server.stop(0); + } + + @Test + public void testServerErrorIsRetriedUntilSuccess() throws Exception { + this.batchHandlers.add(e -> reply(e, 500, serverError("java.lang.IllegalStateException", + "backend busy"))); + this.batchHandlers.add(e -> reply(e, 503, "<html>Service Unavailable</html>")); + LoadContext context = this.context(3); + InputStruct struct = edgeStruct(context); + EdgeMapping mapping = struct.edges().get(0); + + new BatchInsertTask(context, struct, mapping, batch()).execute(); + + Assert.assertEquals(3, this.batchRequests.get()); + Assert.assertEquals(1L, context.summary().metrics(struct).insertSuccess(mapping)); + } + + @Test + public void testServerErrorFailsAfterRetries() throws Exception { + for (int i = 0; i < 3; i++) { + this.batchHandlers.add(e -> reply(e, 500, serverError( + "java.lang.IllegalStateException", "backend busy"))); + } + LoadContext context = this.context(2); + InputStruct struct = edgeStruct(context); + EdgeMapping mapping = struct.edges().get(0); + + Assert.assertThrows(ServerException.class, () -> { + new BatchInsertTask(context, struct, mapping, batch()).execute(); + }, e -> { + Assert.assertEquals(500, ((ServerException) e).status()); + }); + // The first request and two retries + Assert.assertEquals(3, this.batchRequests.get()); + Assert.assertEquals(0L, context.summary().metrics(struct).insertSuccess(mapping)); + } + + @Test + public void testIllegalArgumentIsNotRetried() throws Exception { + this.batchHandlers.add(e -> reply(e, 400, serverError( + "class java.lang.IllegalArgumentException", + "Invalid edge label 'knows'"))); + LoadContext context = this.context(3); + InputStruct struct = edgeStruct(context); + EdgeMapping mapping = struct.edges().get(0); + + Assert.assertThrows(ServerException.class, () -> { + new BatchInsertTask(context, struct, mapping, batch()).execute(); + }, e -> { + Assert.assertEquals("Invalid edge label 'knows'", e.getMessage()); + }); + Assert.assertEquals(1, this.batchRequests.get()); + } + + @Test + public void testUnacceptableMessageIsNotRetried() throws Exception { + this.batchHandlers.add(e -> reply(e, 500, serverError( + "java.lang.IllegalStateException", + "The server is being shutting down"))); + LoadContext context = this.context(3); + InputStruct struct = edgeStruct(context); + EdgeMapping mapping = struct.edges().get(0); + + Assert.assertThrows(ServerException.class, () -> { + new BatchInsertTask(context, struct, mapping, batch()).execute(); + }); + Assert.assertEquals(1, this.batchRequests.get()); + } + + private LoadContext context(int retryTimes) throws Exception { + LoadOptions options = new LoadOptions(); + options.retryTimes = retryTimes; + // No sleep between retries + options.retryInterval = 0; + options.file = this.folder.newFile("mapping.json").getAbsolutePath(); + LoadContext context = (LoadContext) allocateInstance(LoadContext.class); Review Comment: Minor: `context()` builds `LoadContext` with `sun.misc.Unsafe.allocateInstance` and then sets 13 private fields by name, so the test bypasses the constructor and silently diverges from a real context. `LoadContext` has 15 instance fields; `loadingModeSet` and `failure` are left at defaults, and `schemaCache` and `parseGroup` are set to `null` although the constructor always makes them non-null. A later change that renames a field fails here with `NoSuchFieldException`, and one that adds a constructor-initialised field or reads `context.schemaCache()` on the insert path fails with an NPE that has nothing to do with retries. None of this is needed: the public `LoadContext(LoadOptions)` constructor only calls `HugeClientHolder.create(options)`, which builds the client and requests `/versions`, the one endpoint the fake server already serves besides `/graph/edges/batch`. Please set `options.host = "127.0.0.1"`, `options.port` to the fake server's port and `options.timeout = 1`, use `n ew LoadContext(options)`, close `context.client()` in `teardown()`, and drop `allocateInstance` and `setField`. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
