diff options
256 files changed, 3784 insertions, 1814 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index da9cb6da6a4..e1e3219a5d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,7 @@ add_subdirectory(container-messagebus) add_subdirectory(container-search) add_subdirectory(container-search-gui) add_subdirectory(container-search-and-docproc) +add_subdirectory(cloud-tenant-cd) add_subdirectory(clustercontroller-apps) add_subdirectory(clustercontroller-apputil) add_subdirectory(clustercontroller-utils) @@ -127,11 +128,13 @@ add_subdirectory(storageframework) add_subdirectory(storageserver) add_subdirectory(statistics) add_subdirectory(streamingvisitors) +add_subdirectory(tenant-cd-api) add_subdirectory(vbench) add_subdirectory(vdslib) add_subdirectory(vdstestlib) add_subdirectory(vespa-athenz) add_subdirectory(vespa-http-client) +add_subdirectory(vespa-osgi-testrunner) add_subdirectory(vespa-testrunner-components) add_subdirectory(vespa_feed_perf) add_subdirectory(vespa_jersey2) diff --git a/cloud-tenant-cd/CMakeLists.txt b/cloud-tenant-cd/CMakeLists.txt new file mode 100644 index 00000000000..2d30b9c4611 --- /dev/null +++ b/cloud-tenant-cd/CMakeLists.txt @@ -0,0 +1,2 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +install_fat_java_artifact(cloud-tenant-cd) diff --git a/cloud-tenant-cd/OWNERS b/cloud-tenant-cd/OWNERS new file mode 100644 index 00000000000..ff9741f2060 --- /dev/null +++ b/cloud-tenant-cd/OWNERS @@ -0,0 +1,2 @@ +mortent +bjorncs
\ No newline at end of file diff --git a/cloud-tenant-cd/pom.xml b/cloud-tenant-cd/pom.xml index c771e2dd1c3..d7e5a4a9642 100644 --- a/cloud-tenant-cd/pom.xml +++ b/cloud-tenant-cd/pom.xml @@ -53,19 +53,19 @@ <!-- compile scope --> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>tenant-auth</artifactId> + <artifactId>hosted-api</artifactId> <version>${project.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>hosted-api</artifactId> + <artifactId>config-provisioning</artifactId> <version>${project.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>config-provisioning</artifactId> + <artifactId>tenant-cd-commons</artifactId> <version>${project.version}</version> <scope>compile</scope> </dependency> @@ -78,9 +78,7 @@ <artifactId>bundle-plugin</artifactId> <extensions>true</extensions> <configuration> - <attachBundleArtifact>true</attachBundleArtifact> - <bundleClassifierName>deploy</bundleClassifierName> - <useCommonAssemblyIds>false</useCommonAssemblyIds> + <useCommonAssemblyIds>true</useCommonAssemblyIds> </configuration> </plugin> <plugin> diff --git a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/impl/VespaTestRuntime.java b/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/cloud/impl/VespaTestRuntime.java index 3a70a1ed531..5f6cc252d85 100644 --- a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/impl/VespaTestRuntime.java +++ b/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/cloud/impl/VespaTestRuntime.java @@ -1,13 +1,15 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.cd.impl; +package ai.vespa.hosted.cd.cloud.impl; import ai.vespa.cloud.Zone; import ai.vespa.hosted.api.ControllerHttpClient; +import ai.vespa.hosted.api.DefaultApiAuthenticator; +import ai.vespa.hosted.cd.commons.DefaultEndpointAuthenticator; import ai.vespa.hosted.api.Properties; import ai.vespa.hosted.api.TestConfig; import ai.vespa.hosted.cd.Deployment; import ai.vespa.hosted.cd.TestRuntime; -import ai.vespa.hosted.cd.impl.http.HttpDeployment; +import ai.vespa.hosted.cd.commons.HttpDeployment; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.zone.ZoneId; @@ -37,7 +39,7 @@ public class VespaTestRuntime implements TestRuntime { } private VespaTestRuntime(TestConfig config) { this.config = config; - this.deploymentToTest = new HttpDeployment(config.deployments().get(config.zone()), new ai.vespa.hosted.auth.EndpointAuthenticator(config.system())); + this.deploymentToTest = new HttpDeployment(config.deployments().get(config.zone()), new DefaultEndpointAuthenticator(config.system())); } @Override @@ -69,7 +71,7 @@ public class VespaTestRuntime implements TestRuntime { } private static TestConfig fromController() { - ControllerHttpClient controller = new ai.vespa.hosted.auth.ApiAuthenticator().controller(); + ControllerHttpClient controller = new DefaultApiAuthenticator().controller(); ApplicationId id = Properties.application(); Environment environment = Properties.environment().orElse(Environment.dev); ZoneId zone = Properties.region().map(region -> ZoneId.from(environment, region)) diff --git a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/impl/VespaTestRuntimeProvider.java b/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/cloud/impl/VespaTestRuntimeProvider.java index c27e1d4a0c9..d90a2d595c7 100644 --- a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/impl/VespaTestRuntimeProvider.java +++ b/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/cloud/impl/VespaTestRuntimeProvider.java @@ -1,5 +1,5 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.cd.impl; +package ai.vespa.hosted.cd.cloud.impl; import ai.vespa.hosted.cd.internal.TestRuntimeProvider; import com.yahoo.component.AbstractComponent; diff --git a/cloud-tenant-cd/src/main/resources/META-INF/services/ai.vespa.hosted.cd.TestRuntime b/cloud-tenant-cd/src/main/resources/META-INF/services/ai.vespa.hosted.cd.TestRuntime index 35cb2ed7c25..884d7d4b171 100644 --- a/cloud-tenant-cd/src/main/resources/META-INF/services/ai.vespa.hosted.cd.TestRuntime +++ b/cloud-tenant-cd/src/main/resources/META-INF/services/ai.vespa.hosted.cd.TestRuntime @@ -1,2 +1,2 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -ai.vespa.hosted.cd.impl.VespaTestRuntime
\ No newline at end of file +ai.vespa.hosted.cd.cloud.impl.VespaTestRuntime
\ No newline at end of file diff --git a/config-model/pom.xml b/config-model/pom.xml index 897236f78e7..95e79fd09fb 100644 --- a/config-model/pom.xml +++ b/config-model/pom.xml @@ -137,7 +137,7 @@ </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>documentapi</artifactId> + <artifactId>container-documentapi</artifactId> <version>${project.version}</version> <scope>provided</scope> </dependency> diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java index 71d16303573..04fe77d9e05 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java @@ -13,6 +13,7 @@ import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.container.handler.ThreadpoolConfig; import com.yahoo.container.handler.metrics.MetricsProxyApiConfig; import com.yahoo.container.handler.metrics.MetricsV2Handler; +import com.yahoo.container.handler.metrics.PrometheusV1Handler; import com.yahoo.container.jdisc.ContainerMbusConfig; import com.yahoo.container.jdisc.messagebus.MbusServerProvider; import com.yahoo.jdisc.http.ServletPathsConfig; @@ -58,6 +59,10 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat public static final String METRICS_V2_HANDLER_BINDING_1 = "http://*" + MetricsV2Handler.V2_PATH; public static final String METRICS_V2_HANDLER_BINDING_2 = METRICS_V2_HANDLER_BINDING_1 + "/*"; + public static final String PROMETHEUS_V1_HANDLER_CLASS = PrometheusV1Handler.class.getName(); + private static final String PROMETHEUS_V1_HANDLER_BINDING_1 = "http://*" + PrometheusV1Handler.V1_PATH; + private static final String PROMETHEUS_V1_HANDLER_BINDING_2 = PROMETHEUS_V1_HANDLER_BINDING_1 + "/*"; + public static final int heapSizePercentageOfTotalNodeMemory = 60; public static final int heapSizePercentageOfTotalNodeMemoryWhenCombinedCluster = 17; @@ -88,7 +93,7 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat addSimpleComponent("com.yahoo.container.jdisc.CertificateStoreProvider"); addSimpleComponent("com.yahoo.container.jdisc.AthenzIdentityProviderProvider"); addSimpleComponent("com.yahoo.container.jdisc.SystemInfoProvider"); - addMetricsV2Handler(); + addMetricsHandlers(); addTestrunnerComponentsIfTester(deployState); } @@ -116,16 +121,27 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat } } - public void addMetricsV2Handler() { + private void addMetricsHandlers() { + addMetricsHandler(METRICS_V2_HANDLER_CLASS, METRICS_V2_HANDLER_BINDING_1, METRICS_V2_HANDLER_BINDING_2); + addMetricsHandler(PROMETHEUS_V1_HANDLER_CLASS, PROMETHEUS_V1_HANDLER_BINDING_1, PROMETHEUS_V1_HANDLER_BINDING_2); + } + + private void addMetricsHandler(String handlerClass, String rootBinding, String innerBinding) { Handler<AbstractConfigProducer<?>> handler = new Handler<>( - new ComponentModel(METRICS_V2_HANDLER_CLASS, null, null, null)); - handler.addServerBindings(METRICS_V2_HANDLER_BINDING_1, METRICS_V2_HANDLER_BINDING_2); + new ComponentModel(handlerClass, null, null, null)); + handler.addServerBindings(rootBinding, innerBinding); addComponent(handler); } private void addTestrunnerComponentsIfTester(DeployState deployState) { - if (deployState.isHosted() && deployState.getProperties().applicationId().instance().isTester()) + if (deployState.isHosted() && deployState.getProperties().applicationId().instance().isTester()) { addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/vespa-testrunner-components-jar-with-dependencies.jar"))); + addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/vespa-osgi-testrunner-jar-with-dependencies.jar"))); + addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/tenant-cd-api-jar-with-dependencies.jar"))); + if(deployState.zone().system().isPublic()) { + addPlatformBundle(Paths.get(Defaults.getDefaults().underVespaHome("lib/jars/cloud-tenant-cd-jar-with-dependencies.jar"))); + } + } } public void setModelEvaluation(ContainerModelEvaluation modelEvaluation) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java index dd48e65c340..9676b8b1e4a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/AccessControl.java @@ -35,7 +35,8 @@ public final class AccessControl { ContainerCluster.BINDINGS_OVERVIEW_HANDLER_CLASS, ContainerCluster.STATE_HANDLER_CLASS, ContainerCluster.LOG_HANDLER_CLASS, - ApplicationContainerCluster.METRICS_V2_HANDLER_CLASS + ApplicationContainerCluster.METRICS_V2_HANDLER_CLASS, + ApplicationContainerCluster.PROMETHEUS_V1_HANDLER_CLASS ); public static final class Builder { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java b/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java index e618326eff5..943fcbf6c1d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java @@ -12,7 +12,6 @@ import com.yahoo.io.IOUtils; import com.yahoo.path.Path; import com.yahoo.search.query.profile.QueryProfileRegistry; import com.yahoo.searchdefinition.FeatureNames; -import com.yahoo.searchdefinition.MapEvaluationTypeContext; import com.yahoo.searchdefinition.RankProfile; import com.yahoo.searchdefinition.RankingConstant; import com.yahoo.searchdefinition.expressiontransforms.RankProfileTransformContext; @@ -24,19 +23,10 @@ import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue; import com.yahoo.searchlib.rankingexpression.evaluation.Value; import com.yahoo.searchlib.rankingexpression.parser.ParseException; import com.yahoo.searchlib.rankingexpression.rule.CompositeNode; -import com.yahoo.searchlib.rankingexpression.rule.ConstantNode; import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode; -import com.yahoo.searchlib.rankingexpression.rule.GeneratorLambdaFunctionNode; import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; -import com.yahoo.searchlib.rankingexpression.rule.TensorFunctionNode; import com.yahoo.tensor.Tensor; import com.yahoo.tensor.TensorType; -import com.yahoo.tensor.functions.Generate; -import com.yahoo.tensor.functions.Join; -import com.yahoo.tensor.functions.Reduce; -import com.yahoo.tensor.functions.Rename; -import com.yahoo.tensor.functions.ScalarFunctions; -import com.yahoo.tensor.functions.TensorFunction; import com.yahoo.tensor.serialization.TypedBinaryFormat; import java.io.BufferedReader; @@ -261,7 +251,6 @@ public class ConvertedModel { QueryProfileRegistry queryProfiles, Map<String, ExpressionFunction> expressions) { expression = expression.withBody(replaceConstantsByFunctions(expression.getBody(), constantsReplacedByFunctions)); - reduceBatchDimensions(expression.getBody(), model, profile, queryProfiles); store.writeExpression(expressionName, expression); expressions.put(expressionName, expression); } @@ -380,146 +369,6 @@ public class ConvertedModel { } /** - * Check if batch dimensions of inputs can be reduced out. If the input - * function specifies that a single exemplar should be evaluated, we can - * reduce the batch dimension out. - */ - private static void reduceBatchDimensions(RankingExpression expression, ImportedMlModel model, - RankProfile profile, QueryProfileRegistry queryProfiles) { - MapEvaluationTypeContext typeContext = profile.typeContext(queryProfiles); - - // Add any missing inputs for type resolution - Set<String> functionNames = new HashSet<>(); - addFunctionNamesIn(expression.getRoot(), functionNames, model); - for (String functionName : functionNames) { - Optional<TensorType> requiredType = model.inputTypeSpec(functionName).map(TensorType::fromSpec); - if (requiredType.isPresent()) { - Reference ref = Reference.fromIdentifier(functionName); - if (typeContext.getType(ref).equals(TensorType.empty)) { - typeContext.setType(ref, requiredType.get()); - } - } - } - typeContext.forgetResolvedTypes(); - - TensorType typeBeforeReducing = expression.getRoot().type(typeContext); - - // Check generated functions for inputs to reduce - for (String functionName : functionNames) { - if ( ! model.functions().containsKey(functionName)) continue; - - RankProfile.RankingExpressionFunction rankingExpressionFunction = profile.getFunctions().get(functionName); - if (rankingExpressionFunction == null) { - throw new IllegalArgumentException("Model refers to generated function '" + functionName + - "but this function is not present in " + profile); - } - RankingExpression functionExpression = rankingExpressionFunction.function().getBody(); - functionExpression.setRoot(reduceBatchDimensionsAtInput(functionExpression.getRoot(), model, typeContext)); - } - - // Check expression for inputs to reduce - ExpressionNode root = expression.getRoot(); - root = reduceBatchDimensionsAtInput(root, model, typeContext); - TensorType typeAfterReducing = root.type(typeContext); - root = expandBatchDimensionsAtOutput(root, typeBeforeReducing, typeAfterReducing); - expression.setRoot(root); - } - - private static ExpressionNode reduceBatchDimensionsAtInput(ExpressionNode node, ImportedMlModel model, - MapEvaluationTypeContext typeContext) { - if (node instanceof TensorFunctionNode) { - TensorFunction tensorFunction = ((TensorFunctionNode) node).function(); - if (tensorFunction instanceof Rename) { - List<ExpressionNode> children = ((TensorFunctionNode)node).children(); - if (children.size() == 1 && children.get(0) instanceof ReferenceNode) { - ReferenceNode referenceNode = (ReferenceNode) children.get(0); - if (model.inputTypeSpec(referenceNode.getName()).isPresent()) { - return reduceBatchDimensionExpression(tensorFunction, typeContext); - } - } - // Modify any renames in expression to disregard batch dimension - else if (children.size() == 1 && children.get(0) instanceof TensorFunctionNode) { - TensorFunction<Reference> childFunction = (((TensorFunctionNode) children.get(0)).function()); - TensorType childType = childFunction.type(typeContext); - Rename rename = (Rename) tensorFunction; - List<String> from = new ArrayList<>(); - List<String> to = new ArrayList<>(); - for (TensorType.Dimension dimension : childType.dimensions()) { - int i = rename.fromDimensions().indexOf(dimension.name()); - if (i < 0) { - throw new IllegalArgumentException("Rename does not contain dimension '" + - dimension + "' in child expression type: " + childType); - } - from.add((String)rename.fromDimensions().get(i)); - to.add((String)rename.toDimensions().get(i)); - } - return new TensorFunctionNode(new Rename<>(childFunction, from, to)); - } - } - } - if (node instanceof ReferenceNode) { - ReferenceNode referenceNode = (ReferenceNode) node; - if (model.inputTypeSpec(referenceNode.getName()).isPresent()) { - return reduceBatchDimensionExpression(TensorFunctionNode.wrap(node), typeContext); - } - } - if (node instanceof CompositeNode) { - List<ExpressionNode> children = ((CompositeNode)node).children(); - List<ExpressionNode> transformedChildren = new ArrayList<>(children.size()); - for (ExpressionNode child : children) { - transformedChildren.add(reduceBatchDimensionsAtInput(child, model, typeContext)); - } - return ((CompositeNode)node).setChildren(transformedChildren); - } - return node; - } - - private static ExpressionNode reduceBatchDimensionExpression(TensorFunction function, MapEvaluationTypeContext context) { - TensorFunction result = function; - TensorType type = function.type(context); - if (type.dimensions().size() > 1) { - List<String> reduceDimensions = new ArrayList<>(); - for (TensorType.Dimension dimension : type.dimensions()) { - if (dimension.size().orElse(-1L) == 1) { - reduceDimensions.add(dimension.name()); - } - } - if (reduceDimensions.size() > 0) { - result = new Reduce(function, Reduce.Aggregator.sum, reduceDimensions); - context.forgetResolvedTypes(); // We changed types - } - } - return new TensorFunctionNode(result); - } - - /** - * If batch dimensions have been reduced away above, bring them back here - * for any following computation of the tensor. - */ - // TODO: determine when this is not necessary! - private static ExpressionNode expandBatchDimensionsAtOutput(ExpressionNode node, TensorType before, TensorType after) { - if (after.equals(before)) return node; - - TensorType.Builder typeBuilder = new TensorType.Builder(after.valueType()); - for (TensorType.Dimension dimension : before.dimensions()) { - if (dimension.size().orElse(-1L) == 1 && !after.dimensionNames().contains(dimension.name())) { - typeBuilder.indexed(dimension.name(), 1); - } - } - TensorType expandDimensionsType = typeBuilder.build(); - if (expandDimensionsType.dimensions().size() > 0) { - ExpressionNode generatedExpression = new ConstantNode(new DoubleValue(1.0)); - Generate generatedFunction = new Generate(expandDimensionsType, - new GeneratorLambdaFunctionNode(expandDimensionsType, - generatedExpression) - .asLongListToDoubleOperator()); - Join expand = new Join(TensorFunctionNode.wrap(node), generatedFunction, ScalarFunctions.multiply()); - return new TensorFunctionNode(expand); - } - return node; - } - - /** * If a constant c is overridden by a function, we need to replace instances of "constant(c)" by "c" in expressions. * This method does that for the given expression and returns the result. */ diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithOnnxTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithOnnxTestCase.java index 1fe1ebf2bb3..dffdc3b4a34 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithOnnxTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithOnnxTestCase.java @@ -29,7 +29,6 @@ public class RankingExpressionWithOnnxTestCase { private final static String name = "mnist_softmax"; private final static String vespaExpression = "join(reduce(join(rename(Placeholder, (d0, d1), (d0, d2)), constant(" + name + "_Variable), f(a,b)(a * b)), sum, d2), constant(" + name + "_Variable_1), f(a,b)(a + b))"; - private final static String vespaExpressionWithBatchReduce = "join(join(reduce(join(reduce(rename(Placeholder, (d0, d1), (d0, d2)), sum, d0), constant(mnist_softmax_Variable), f(a,b)(a * b)), sum, d2), constant(mnist_softmax_Variable_1), f(a,b)(a + b)), tensor<float>(d0[1])(1.0), f(a,b)(a * b))"; @After public void removeGeneratedModelFiles() { @@ -97,7 +96,7 @@ public class RankingExpressionWithOnnxTestCase { "field mytensor type tensor<float>(d0[1],d1[784]) { indexing: attribute }", "Placeholder", application); - search.assertFirstPhaseExpression(vespaExpressionWithBatchReduce, "my_profile"); + search.assertFirstPhaseExpression(vespaExpression, "my_profile"); } @@ -115,7 +114,7 @@ public class RankingExpressionWithOnnxTestCase { "field mytensor type tensor<float>(d0[1],d1[784]) { indexing: attribute }", "Placeholder", application); - search.assertFirstPhaseExpression(vespaExpressionWithBatchReduce, "my_profile"); + search.assertFirstPhaseExpression(vespaExpression, "my_profile"); } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorFlowTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorFlowTestCase.java index 126a41e14ad..610d92144ad 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorFlowTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorFlowTestCase.java @@ -118,7 +118,7 @@ public class RankingExpressionWithTensorFlowTestCase { "field mytensor type tensor(d0[1],d1[784]) { indexing: attribute }", "Placeholder", application); - search.assertFirstPhaseExpression(vespaExpressionWithBatchReduce, "my_profile"); + search.assertFirstPhaseExpression(vespaExpression, "my_profile"); } @Test @@ -136,7 +136,7 @@ public class RankingExpressionWithTensorFlowTestCase { "field mytensor type tensor(d0[1],d1[784]) { indexing: attribute }", "Placeholder", application); - search.assertFirstPhaseExpression(vespaExpressionWithBatchReduce, "my_profile"); + search.assertFirstPhaseExpression(vespaExpression, "my_profile"); } @Test @@ -310,18 +310,10 @@ public class RankingExpressionWithTensorFlowTestCase { } @Test - public void testTensorFlowReduceBatchDimension() { - final String expression = "join(join(reduce(join(reduce(rename(Placeholder, (d0, d1), (d0, d2)), sum, d0), constant(" + name + "_layer_Variable_read), f(a,b)(a * b)), sum, d2), constant(" + name + "_layer_Variable_1_read), f(a,b)(a + b)), tensor(d0[1])(1.0), f(a,b)(a * b))"; - RankProfileSearchFixture search = fixtureWith("tensor(d0[1],d1[784])(0.0)", - "tensorflow('mnist_softmax/saved')"); - search.assertFirstPhaseExpression(expression, "my_profile"); - } - - @Test public void testFunctionGeneration() { final String name = "mnist_saved"; final String expression = "join(reduce(join(join(join(reduce(constant(" + name + "_dnn_hidden2_Const), sum, d2), imported_ml_function_" + name + "_dnn_hidden2_add, f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden2_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_outputs_weights_read), f(a,b)(a * b)), sum, d2), constant(" + name + "_dnn_outputs_bias_read), f(a,b)(a + b))"; - final String functionExpression1 = "join(reduce(join(reduce(rename(input, (d0, d1), (d0, d4)), sum, d0), constant(" + name + "_dnn_hidden1_weights_read), f(a,b)(a * b)), sum, d4), constant(" + name + "_dnn_hidden1_bias_read), f(a,b)(a + b))"; + final String functionExpression1 = "join(reduce(join(rename(input, (d0, d1), (d0, d4)), constant(" + name + "_dnn_hidden1_weights_read), f(a,b)(a * b)), sum, d4), constant(" + name + "_dnn_hidden1_bias_read), f(a,b)(a + b))"; final String functionExpression2 = "join(reduce(join(join(join(0.009999999776482582, imported_ml_function_" + name + "_dnn_hidden1_add, f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden1_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_hidden2_weights_read), f(a,b)(a * b)), sum, d3), constant(" + name + "_dnn_hidden2_bias_read), f(a,b)(a + b))"; RankProfileSearchFixture search = fixtureWith("tensor(d0[1],d1[784])(0.0)", @@ -351,7 +343,7 @@ public class RankingExpressionWithTensorFlowTestCase { " }"; final String expression = "join(reduce(join(join(join(reduce(constant(" + name + "_dnn_hidden2_Const), sum, d2), imported_ml_function_" + name + "_dnn_hidden2_add, f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden2_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_outputs_weights_read), f(a,b)(a * b)), sum, d2), constant(" + name + "_dnn_outputs_bias_read), f(a,b)(a + b))"; - final String functionExpression1 = "join(reduce(join(reduce(rename(input, (d0, d1), (d0, d4)), sum, d0), constant(" + name + "_dnn_hidden1_weights_read), f(a,b)(a * b)), sum, d4), constant(" + name + "_dnn_hidden1_bias_read), f(a,b)(a + b))"; + final String functionExpression1 = "join(reduce(join(rename(input, (d0, d1), (d0, d4)), constant(" + name + "_dnn_hidden1_weights_read), f(a,b)(a * b)), sum, d4), constant(" + name + "_dnn_hidden1_bias_read), f(a,b)(a + b))"; final String functionExpression2 = "join(reduce(join(join(join(0.009999999776482582, imported_ml_function_" + name + "_dnn_hidden1_add, f(a,b)(a * b)), imported_ml_function_" + name + "_dnn_hidden1_add, f(a,b)(max(a,b))), constant(" + name + "_dnn_hidden2_weights_read), f(a,b)(a * b)), sum, d3), constant(" + name + "_dnn_hidden2_bias_read), f(a,b)(a + b))"; RankProfileSearchFixture search = fixtureWithUncompiled(rankProfiles, new StoringApplicationPackage(applicationDir)); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java index 739ae055ec7..03c05af1145 100755 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java @@ -4,16 +4,19 @@ package com.yahoo.vespa.model.container; import com.yahoo.cloud.config.ClusterInfoConfig; import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.cloud.config.RoutingProviderConfig; +import com.yahoo.config.FileReference; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.config.model.test.MockRoot; +import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.Zone; import com.yahoo.config.provisioning.FlavorsConfig; +import com.yahoo.container.BundlesConfig; import com.yahoo.container.handler.ThreadpoolConfig; import com.yahoo.search.config.QrStartConfig; import com.yahoo.vespa.model.Host; @@ -24,13 +27,19 @@ import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.docproc.ContainerDocproc; import com.yahoo.vespa.model.container.search.ContainerSearch; import com.yahoo.vespa.model.container.search.searchchain.SearchChains; +import org.hamcrest.CoreMatchers; import org.junit.Test; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; /** * @author Simon Thoresen Hult @@ -259,6 +268,41 @@ public class ContainerClusterTest { assertFalse(config.enabled()); } + @Test + public void requireThatBundlesForTesterApplicationAreInstalled() { + List<String> expectedOnpremBundles = + List.of("vespa-testrunner-components-jar-with-dependencies.jar", + "vespa-osgi-testrunner-jar-with-dependencies.jar", + "tenant-cd-api-jar-with-dependencies.jar"); + verifyTesterApplicationInstalledBundles(Zone.defaultZone(), expectedOnpremBundles); + + List<String> expectedPublicBundles = new ArrayList<>(expectedOnpremBundles); + expectedPublicBundles.add("cloud-tenant-cd-jar-with-dependencies.jar"); + Zone publicZone = new Zone(SystemName.PublicCd, Environment.dev, RegionName.defaultName()); + verifyTesterApplicationInstalledBundles(publicZone, expectedPublicBundles); + + } + + private void verifyTesterApplicationInstalledBundles(Zone zone, List<String> expectedBundleNames) { + ApplicationId appId = ApplicationId.from("tenant", "application", "instance-t"); + DeployState state = new DeployState.Builder().properties( + new TestProperties() + .setHostedVespa(true) + .setApplicationId(appId)) + .zone(zone).build(); + MockRoot root = new MockRoot("foo", state); + ApplicationContainerCluster cluster = new ApplicationContainerCluster(root, "container0", "container1", state); + BundlesConfig.Builder bundleBuilder = new BundlesConfig.Builder(); + cluster.getConfig(bundleBuilder); + List<String> installedBundles = bundleBuilder.build().bundle().stream().map(FileReference::value).collect(Collectors.toList()); + + assertEquals(expectedBundleNames.size(), installedBundles.size()); + assertThat(installedBundles, containsInAnyOrder( + expectedBundleNames.stream().map(CoreMatchers::endsWith).collect(Collectors.toList()) + )); + } + + private static void addContainer(DeployLogger deployLogger, ApplicationContainerCluster cluster, String name, String hostName) { ApplicationContainer container = new ApplicationContainer(cluster, name, 0, cluster.isHostedVespa()); container.setHostResource(new HostResource(new Host(null, hostName))); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/ml/MlModelsTest.java b/config-model/src/test/java/com/yahoo/vespa/model/ml/MlModelsTest.java index 5b38e09537d..cad0ad7bae0 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/ml/MlModelsTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/ml/MlModelsTest.java @@ -64,12 +64,12 @@ public class MlModelsTest { private final String testProfile = "rankingExpression(input).rankingScript: attribute(argument)\n" + "rankingExpression(input).type: tensor<float>(d0[1],d1[784])\n" + - "rankingExpression(imported_ml_function_mnist_saved_dnn_hidden1_add).rankingScript: join(reduce(join(reduce(rename(rankingExpression(input), (d0, d1), (d0, d4)), sum, d0), constant(mnist_saved_dnn_hidden1_weights_read), f(a,b)(a * b)), sum, d4), constant(mnist_saved_dnn_hidden1_bias_read), f(a,b)(a + b))\n" + + "rankingExpression(imported_ml_function_mnist_saved_dnn_hidden1_add).rankingScript: join(reduce(join(rename(rankingExpression(input), (d0, d1), (d0, d4)), constant(mnist_saved_dnn_hidden1_weights_read), f(a,b)(a * b)), sum, d4), constant(mnist_saved_dnn_hidden1_bias_read), f(a,b)(a + b))\n" + "rankingExpression(mnist_tensorflow).rankingScript: join(reduce(join(map(join(reduce(join(join(join(0.009999999776482582, rankingExpression(imported_ml_function_mnist_saved_dnn_hidden1_add), f(a,b)(a * b)), rankingExpression(imported_ml_function_mnist_saved_dnn_hidden1_add), f(a,b)(max(a,b))), constant(mnist_saved_dnn_hidden2_weights_read), f(a,b)(a * b)), sum, d3), constant(mnist_saved_dnn_hidden2_bias_read), f(a,b)(a + b)), f(a)(1.0507009873554805 * if (a >= 0, a, 1.6732632423543772 * (exp(a) - 1)))), constant(mnist_saved_dnn_outputs_weights_read), f(a,b)(a * b)), sum, d2), constant(mnist_saved_dnn_outputs_bias_read), f(a,b)(a + b))\n" + "rankingExpression(Placeholder).rankingScript: attribute(argument)\n" + "rankingExpression(Placeholder).type: tensor<float>(d0[1],d1[784])\n" + - "rankingExpression(mnist_softmax_tensorflow).rankingScript: join(join(reduce(join(reduce(rename(rankingExpression(Placeholder), (d0, d1), (d0, d2)), sum, d0), constant(mnist_softmax_saved_layer_Variable_read), f(a,b)(a * b)), sum, d2), constant(mnist_softmax_saved_layer_Variable_1_read), f(a,b)(a + b)), tensor(d0[1])(1.0), f(a,b)(a * b))\n" + - "rankingExpression(mnist_softmax_onnx).rankingScript: join(join(reduce(join(reduce(rename(rankingExpression(Placeholder), (d0, d1), (d0, d2)), sum, d0), constant(mnist_softmax_Variable), f(a,b)(a * b)), sum, d2), constant(mnist_softmax_Variable_1), f(a,b)(a + b)), tensor<float>(d0[1])(1.0), f(a,b)(a * b))\n" + + "rankingExpression(mnist_softmax_tensorflow).rankingScript: join(reduce(join(rename(rankingExpression(Placeholder), (d0, d1), (d0, d2)), constant(mnist_softmax_saved_layer_Variable_read), f(a,b)(a * b)), sum, d2), constant(mnist_softmax_saved_layer_Variable_1_read), f(a,b)(a + b))\n" + + "rankingExpression(mnist_softmax_onnx).rankingScript: join(reduce(join(rename(rankingExpression(Placeholder), (d0, d1), (d0, d2)), constant(mnist_softmax_Variable), f(a,b)(a * b)), sum, d2), constant(mnist_softmax_Variable_1), f(a,b)(a + b))\n" + "rankingExpression(my_xgboost).rankingScript: if (f29 < -0.1234567, if (!(f56 >= -0.242398), 1.71218, -1.70044), if (f109 < 0.8723473, -1.94071, 1.85965)) + if (!(f60 >= -0.482947), if (f29 < -4.2387498, 0.784718, -0.96853), -6.23624)\n" + "rankingExpression(my_lightgbm).rankingScript: if (!(numerical_2 >= 0.46643291586559305), 2.1594397038037663, if (categorical_2 in [\"k\", \"l\", \"m\"], 2.235297305276056, 2.1792953471546546)) + if (categorical_1 in [\"d\", \"e\"], 0.03070842919354316, if (!(numerical_1 >= 0.5102250691730842), -0.04439151147520909, 0.005117411709368601)) + if (!(numerical_2 >= 0.668665477622446), if (!(numerical_2 >= 0.008118820676863816), -0.15361238490967524, -0.01192330846157292), 0.03499044894987518) + if (!(numerical_1 >= 0.5201391072644542), -0.02141000620783247, if (categorical_1 in [\"a\", \"b\"], -0.004121485787596721, 0.04534090904886873)) + if (categorical_2 in [\"k\", \"l\", \"m\"], if (!(numerical_2 >= 0.27283279016959255), -0.01924803254356527, 0.03643772842347651), -0.02701711918923075)\n" + "vespa.rank.firstphase: rankingExpression(firstphase)\n" + diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java index 56496b06c14..b0ca6ba67f5 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java @@ -52,13 +52,14 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer { @Override protected void maintain() { - log.fine(() -> "Running"); if (! distributeApplicationPackage.value()) return; try (var fileDownloader = new FileDownloader(createConnectionPool(configserverConfig), downloadDirectory)) { for (var applicationId : applicationRepository.listApplications()) { log.fine(() -> "Verifying application package for " + applicationId); RemoteSession session = applicationRepository.getActiveSession(applicationId); + if (session == null) continue; // App might be deleted after call to listApplications() + FileReference applicationPackage = session.getApplicationPackageReference(); long sessionId = session.getSessionId(); log.fine(() -> "Verifying application package file reference " + applicationPackage + " for session " + sessionId); @@ -85,7 +86,6 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer { sessionRepository.createLocalSessionUsingDistributedApplicationPackage(sessionId); } - private boolean missingOnDisk(FileReference applicationPackageReference) { Set<String> fileReferencesOnDisk = getFileReferencesOnDisk(downloadDirectory); return ! fileReferencesOnDisk.contains(applicationPackageReference.value()); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java index bb3a49f25da..f8299bdba1a 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenant.java @@ -28,11 +28,12 @@ public class Tenant { private final RequestHandler requestHandler; private final Instant created; - Tenant(TenantName name, - SessionRepository sessionRepository, - RequestHandler requestHandler, - TenantApplications applicationRepo, - Instant created) { + // Protected due to being subclassed in a system test + protected Tenant(TenantName name, + SessionRepository sessionRepository, + RequestHandler requestHandler, + TenantApplications applicationRepo, + Instant created) { this.name = name; this.path = TenantRepository.getTenantPath(name); this.requestHandler = requestHandler; diff --git a/container-core/pom.xml b/container-core/pom.xml index 64e5ebb00d3..0fbb590a1de 100644 --- a/container-core/pom.xml +++ b/container-core/pom.xml @@ -150,21 +150,18 @@ </exclusion> </exclusions> </dependency> + <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>documentapi</artifactId> + <artifactId>container-documentapi</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>vdslib</artifactId> <version>${project.version}</version> - <exclusions> - <exclusion> - <groupId>log4j</groupId> - <artifactId>log4j</artifactId> - </exclusion> - <exclusion> - <groupId>com.yahoo.vespa</groupId> - <artifactId>config</artifactId> - </exclusion> - </exclusions> </dependency> + <dependency> <groupId>com.yahoo.vespa</groupId> <artifactId>vespajlib</artifactId> diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java b/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java index 1d6e1a0893d..f3149ed4998 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java +++ b/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java @@ -11,6 +11,7 @@ import java.io.OutputStream; import java.time.Instant; import java.util.Optional; import java.util.concurrent.Executor; +import java.util.logging.Level; public class LogHandler extends ThreadedHttpRequestHandler { @@ -37,7 +38,12 @@ public class LogHandler extends ThreadedHttpRequestHandler { return new HttpResponse(200) { @Override public void render(OutputStream outputStream) { - logReader.writeLogs(outputStream, from, to); + try { + logReader.writeLogs(outputStream, from, to); + } + catch (Throwable t) { + log.log(Level.WARNING, "Failed reading logs from " + from + " to " + to, t); + } } }; } diff --git a/container-core/src/main/java/com/yahoo/container/handler/metrics/PrometheusV1Handler.java b/container-core/src/main/java/com/yahoo/container/handler/metrics/PrometheusV1Handler.java index 23b7a62ffa3..e33f2f47828 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/metrics/PrometheusV1Handler.java +++ b/container-core/src/main/java/com/yahoo/container/handler/metrics/PrometheusV1Handler.java @@ -1,6 +1,7 @@ package com.yahoo.container.handler.metrics; import ai.vespa.util.http.VespaHttpClientBuilder; +import com.google.inject.Inject; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.restapi.Path; import com.yahoo.restapi.StringResponse; @@ -30,8 +31,9 @@ public class PrometheusV1Handler extends HttpHandlerBase{ private final String metricsProxyUri; private final HttpClient httpClient = createHttpClient(); - protected PrometheusV1Handler(Executor executor, - MetricsProxyApiConfig config) { + @Inject + public PrometheusV1Handler(Executor executor, + MetricsProxyApiConfig config) { super(executor); metricsProxyUri = "http://localhost:" + config.metricsPort() + config.prometheusApiPath(); } diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java index e11622b1fa3..05c3b88b788 100644 --- a/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/metric/MetricUpdater.java @@ -97,7 +97,6 @@ public class MetricUpdater extends AbstractComponent { this.jrtMetrics = new JrtMetrics(metric); } - @SuppressWarnings("deprecation") @Override public void run() { long freeMemory = runtime.freeMemory(); diff --git a/container-documentapi/.gitignore b/container-documentapi/.gitignore new file mode 100644 index 00000000000..d054e91130e --- /dev/null +++ b/container-documentapi/.gitignore @@ -0,0 +1,3 @@ +container-documentapi.iml +target +/pom.xml.build diff --git a/container-documentapi/OWNERS b/container-documentapi/OWNERS new file mode 100644 index 00000000000..3b2ba1ede81 --- /dev/null +++ b/container-documentapi/OWNERS @@ -0,0 +1 @@ +gjoranv diff --git a/container-documentapi/README.md b/container-documentapi/README.md new file mode 100644 index 00000000000..a30257d5b49 --- /dev/null +++ b/container-documentapi/README.md @@ -0,0 +1,8 @@ +<!-- Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +# Documentapi + +Dependency artifact for building container bundles. +To build standalone document clients, use `documentapi` instead. + +The actual java code remains in `documentapi`, becuase it uses +common test files with the C++ code in the same module.
\ No newline at end of file diff --git a/container-documentapi/pom.xml b/container-documentapi/pom.xml new file mode 100644 index 00000000000..004ef75a4b6 --- /dev/null +++ b/container-documentapi/pom.xml @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.vespa</groupId> + <artifactId>parent</artifactId> + <version>7-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + <artifactId>container-documentapi</artifactId> + <packaging>container-plugin</packaging> + <version>7-SNAPSHOT</version> + + <dependencies> + + <dependency> + <!-- NOTE: this is instead of moving the java code in documentapi to this module (and turning the deps around), + which is made difficult by using common test files with the C++ code. --> + <groupId>com.yahoo.vespa</groupId> + <artifactId>documentapi</artifactId> + <version>${project.version}</version> + <exclusions> + <exclusion> + <groupId>*</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>documentapi-dependencies</artifactId> + <version>${project.version}</version> + <type>pom</type> + <scope>provided</scope> + </dependency> + + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>com.yahoo.vespa</groupId> + <artifactId>bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + </plugins> + </build> +</project> diff --git a/container-messagebus/pom.xml b/container-messagebus/pom.xml index fc7546d32c0..bdb308832ac 100644 --- a/container-messagebus/pom.xml +++ b/container-messagebus/pom.xml @@ -34,6 +34,12 @@ </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> + <artifactId>config</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> <artifactId>container-core</artifactId> <version>${project.version}</version> <scope>provided</scope> @@ -46,7 +52,7 @@ </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>documentapi</artifactId> + <artifactId>container-documentapi</artifactId> <version>${project.version}</version> <scope>provided</scope> </dependency> diff --git a/container-search-and-docproc/pom.xml b/container-search-and-docproc/pom.xml index cc66bd66922..04bf858ba3c 100644 --- a/container-search-and-docproc/pom.xml +++ b/container-search-and-docproc/pom.xml @@ -148,7 +148,7 @@ </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>documentapi</artifactId> + <artifactId>container-documentapi</artifactId> <version>${project.version}</version> <scope>provided</scope> </dependency> diff --git a/container-search/abi-spec.json b/container-search/abi-spec.json index ba52826cd3f..6a191248e13 100644 --- a/container-search/abi-spec.json +++ b/container-search/abi-spec.json @@ -5732,7 +5732,6 @@ "public final java.util.Map listValues(java.lang.String, java.util.Map)", "public final java.util.Map listValues(com.yahoo.processing.request.CompoundName, java.util.Map)", "public java.util.Map listValues(com.yahoo.processing.request.CompoundName, java.util.Map, com.yahoo.processing.request.Properties)", - "public java.util.Map listTypes(com.yahoo.processing.request.CompoundName, java.util.Map)", "public final java.lang.Object get(java.lang.String)", "public final java.lang.Object get(java.lang.String, java.util.Map)", "public final java.lang.Object get(java.lang.String, java.lang.String[])", @@ -6064,8 +6063,8 @@ "public" ], "methods": [ - "public void <init>()", "public void <init>(com.yahoo.search.query.profile.config.QueryProfilesConfig)", + "public void <init>()", "public void <init>(com.yahoo.search.query.profile.types.QueryProfileTypeRegistry)", "public final void register(com.yahoo.search.query.profile.compiled.CompiledQueryProfile)", "public com.yahoo.search.query.profile.types.QueryProfileTypeRegistry getTypeRegistry()", @@ -6137,9 +6136,12 @@ "public" ], "methods": [ - "public void <init>(java.lang.Object, java.lang.String, com.yahoo.search.query.profile.DimensionValues)", + "public void <init>(java.lang.Object, java.lang.String, boolean, boolean, com.yahoo.search.query.profile.types.QueryProfileType, com.yahoo.search.query.profile.DimensionValues)", "public java.lang.Object value()", "public java.lang.String source()", + "public boolean isUnoverridable()", + "public boolean isQueryProfile()", + "public com.yahoo.search.query.profile.types.QueryProfileType queryProfileType()", "public com.yahoo.search.query.profile.compiled.ValueWithSource withValue(java.lang.Object)", "public java.util.Optional variant()", "public java.lang.String toString()" diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java index e62848a7f9e..d8fb7b46440 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java @@ -74,7 +74,7 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM * will be adjusted accordingly. */ @Override - protected void sendSearchRequest(Query query) throws IOException { + protected Object sendSearchRequest(Query query, Object unusedContext) throws IOException { this.query = query; invokers.forEach(invoker -> invoker.setMonitor(this)); deadline = currentTime() + query.getTimeLeft(); @@ -89,13 +89,15 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM query.setHits(q); query.setOffset(0); + Object context = null; for (SearchInvoker invoker : invokers) { - invoker.sendSearchRequest(query); + context = invoker.sendSearchRequest(query, context); askedNodes++; } query.setHits(originalHits); query.setOffset(originalOffset); + return null; } @Override diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java index a32931c43c8..256759360f7 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java @@ -35,11 +35,12 @@ public class SearchErrorInvoker extends SearchInvoker { } @Override - protected void sendSearchRequest(Query query) throws IOException { + protected Object sendSearchRequest(Query query, Object context) throws IOException { this.query = query; - if(monitor != null) { + if (monitor != null) { monitor.responseAvailable(this); } + return context; } @Override diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java index 45ed1b87746..b33e91189cc 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java @@ -32,14 +32,21 @@ public abstract class SearchInvoker extends CloseableInvoker { * for correct result windowing. */ public Result search(Query query, Execution execution) throws IOException { - sendSearchRequest(query); + sendSearchRequest(query, null); InvokerResult result = getSearchResult(execution); setFinalStatus(result.getResult().hits().getError() == null); result.complete(); return result.getResult(); } - protected abstract void sendSearchRequest(Query query) throws IOException; + /** + * + * @param query the query to send + * @param context a context object that can be used to pass context among different + * invokers, e.g for reuse of preserialized data. + * @return an object that can be passed to the next invocation of sendSearchRequest + */ + protected abstract Object sendSearchRequest(Query query, Object context) throws IOException; protected abstract InvokerResult getSearchResult(Execution execution) throws IOException; diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java index bc0a38617ee..f4536a7aa4e 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java @@ -87,7 +87,7 @@ interface Client { RpcFillInvoker.GetDocsumsResponseReceiver responseReceiver, double timeoutSeconds); void request(String rpcMethod, CompressionType compression, int uncompressedLength, byte[] compressedPayload, - ResponseReceiver responseReceiver, double timeoutSeconds); + ResponseReceiver responseReceiver, double timeoutSeconds); /** Closes this connection */ void close(); diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java index 76240e55c98..4c0b77207d5 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java @@ -45,21 +45,36 @@ public class RpcSearchInvoker extends SearchInvoker implements Client.ResponseRe } @Override - protected void sendSearchRequest(Query query) { + protected Object sendSearchRequest(Query query, Object incomingContext) { this.query = query; Client.NodeConnection nodeConnection = resourcePool.getConnection(node.key()); if (nodeConnection == null) { responses.add(Client.ResponseOrError.fromError("Could not send search to unknown node " + node.key())); responseAvailable(); - return; + return incomingContext; } query.trace(false, 5, "Sending search request with jrt/protobuf to node with dist key ", node.key()); - var payload = ProtobufSerialization.serializeSearchRequest(query, Math.min(query.getHits(), maxHits), searcher.getServerId()); + RpcContext context = getContext(incomingContext); double timeoutSeconds = ((double) query.getTimeLeft() - 3.0) / 1000.0; - Compressor.Compression compressionResult = resourcePool.compress(query, payload); - nodeConnection.request(RPC_METHOD, compressionResult.type(), payload.length, compressionResult.data(), this, timeoutSeconds); + nodeConnection.request(RPC_METHOD, + context.compressedPayload.type(), + context.compressedPayload.uncompressedSize(), + context.compressedPayload.data(), + this, + timeoutSeconds); + return context; + } + + private RpcContext getContext(Object incomingContext) { + if (incomingContext instanceof RpcContext) + return (RpcContext)incomingContext; + + return new RpcContext(resourcePool, query, + ProtobufSerialization.serializeSearchRequest(query, + Math.min(query.getHits(), maxHits), + searcher.getServerId())); } @Override @@ -106,4 +121,14 @@ public class RpcSearchInvoker extends SearchInvoker implements Client.ResponseRe return searcher.getName(); } + static class RpcContext { + + final Compressor.Compression compressedPayload; + + RpcContext(RpcResourcePool resourcePool, Query query, byte[] payload) { + compressedPayload = resourcePool.compress(query, payload); + } + + } + } diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/AllReferencesQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/AllReferencesQueryProfileVisitor.java deleted file mode 100644 index eda8bf78b68..00000000000 --- a/container-search/src/main/java/com/yahoo/search/query/profile/AllReferencesQueryProfileVisitor.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.search.query.profile; - -import com.yahoo.processing.request.CompoundName; -import com.yahoo.search.query.profile.types.FieldDescription; -import com.yahoo.search.query.profile.types.QueryProfileFieldType; -import com.yahoo.search.query.profile.types.QueryProfileType; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * @author bratseth - */ -final class AllReferencesQueryProfileVisitor extends PrefixQueryProfileVisitor { - - /** A map of query profile types */ - private Set<CompoundName> references = new HashSet<>(); - - public AllReferencesQueryProfileVisitor(CompoundName prefix) { - super(prefix); - } - - @Override - public void onValue(String name, Object value, - DimensionBinding binding, - QueryProfile owner, - DimensionValues variant) {} - - @Override - public void onQueryProfileInsidePrefix(QueryProfile profile, - DimensionBinding binding, - QueryProfile owner, - DimensionValues variant) { - references.add(currentPrefix); - } - - /** Returns the values resulting from this visiting */ - public Set<CompoundName> getResult() { return references; } - - /** Returns false - we are not done until we have seen all */ - public boolean isDone() { return false; } - -} diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/AllTypesQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/AllTypesQueryProfileVisitor.java deleted file mode 100644 index 6bf17d70c70..00000000000 --- a/container-search/src/main/java/com/yahoo/search/query/profile/AllTypesQueryProfileVisitor.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.search.query.profile; - -import com.yahoo.processing.request.CompoundName; -import com.yahoo.search.query.profile.types.FieldDescription; -import com.yahoo.search.query.profile.types.QueryProfileFieldType; -import com.yahoo.search.query.profile.types.QueryProfileType; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author bratseth - */ -final class AllTypesQueryProfileVisitor extends PrefixQueryProfileVisitor { - - /** A map of query profile types */ - private Map<CompoundName, QueryProfileType> types = new HashMap<>(); - - public AllTypesQueryProfileVisitor(CompoundName prefix) { - super(prefix); - } - - @Override - public void onValue(String name, Object value, - DimensionBinding binding, - QueryProfile owner, - DimensionValues variant) {} - - - @Override - public void onQueryProfileInsidePrefix(QueryProfile profile, - DimensionBinding binding, - QueryProfile owner, - DimensionValues variant) { - if (profile.getType() != null) - addReachableTypes(currentPrefix, profile.getType()); - } - - private void addReachableTypes(CompoundName name, QueryProfileType type) { - types.putIfAbsent(name, type); // Types visited earlier has precedence: profile.type overrides profile.inherited.type - for (FieldDescription fieldDescription : type.fields().values()) { - if ( ! (fieldDescription.getType() instanceof QueryProfileFieldType)) continue; - QueryProfileFieldType fieldType = (QueryProfileFieldType)fieldDescription.getType(); - if (fieldType.getQueryProfileType() !=null) { - addReachableTypes(name.append(fieldDescription.getName()), fieldType.getQueryProfileType()); - } - } - } - - /** Returns the values resulting from this visiting */ - public Map<CompoundName, QueryProfileType> getResult() { return types; } - - /** Returns false - we are not done until we have seen all */ - public boolean isDone() { return false; } - -} diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/AllUnoverridableQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/AllUnoverridableQueryProfileVisitor.java deleted file mode 100644 index 4bae6823500..00000000000 --- a/container-search/src/main/java/com/yahoo/search/query/profile/AllUnoverridableQueryProfileVisitor.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.search.query.profile; - -import com.yahoo.processing.request.CompoundName; - -import java.util.HashSet; -import java.util.Set; - -/** - * @author bratseth - */ -final class AllUnoverridableQueryProfileVisitor extends PrefixQueryProfileVisitor { - - /** A map of query profile types */ - private Set<CompoundName> unoverridables = new HashSet<>(); - - public AllUnoverridableQueryProfileVisitor(CompoundName prefix) { - super(prefix); - } - - @Override - public void onValue(String name, Object value, - DimensionBinding binding, - QueryProfile owner, - DimensionValues variant) { - addUnoverridable(name, currentPrefix.append(name), binding, owner); - } - - @Override - public void onQueryProfileInsidePrefix(QueryProfile profile, - DimensionBinding binding, - QueryProfile owner, - DimensionValues variant) { - addUnoverridable(currentPrefix.last(), currentPrefix, binding, owner); - } - - private void addUnoverridable(String localName, - CompoundName fullName, - DimensionBinding binding, - QueryProfile owner) { - if (owner == null) return; - - Boolean isOverridable = owner.isLocalOverridable(localName, binding); - if (isOverridable != null && ! isOverridable) - unoverridables.add(fullName); - } - - /** Returns the values resulting from this visiting */ - public Set<CompoundName> getResult() { return unoverridables; } - - /** Returns false - we are not done until we have seen all */ - public boolean isDone() { return false; } - -} diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java index f27500085e1..2b61dc4c0a6 100644 --- a/container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java +++ b/container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java @@ -3,6 +3,9 @@ package com.yahoo.search.query.profile; import com.yahoo.processing.request.CompoundName; import com.yahoo.search.query.profile.compiled.ValueWithSource; +import com.yahoo.search.query.profile.types.FieldDescription; +import com.yahoo.search.query.profile.types.QueryProfileFieldType; +import com.yahoo.search.query.profile.types.QueryProfileType; import java.util.Collections; import java.util.HashMap; @@ -26,7 +29,7 @@ final class AllValuesQueryProfileVisitor extends PrefixQueryProfileVisitor { DimensionBinding binding, QueryProfile owner, DimensionValues variant) { - putValue(localName, value, owner, variant); + putValue(localName, value, null, owner, variant, binding); } @Override @@ -34,17 +37,25 @@ final class AllValuesQueryProfileVisitor extends PrefixQueryProfileVisitor { DimensionBinding binding, QueryProfile owner, DimensionValues variant) { - putValue("", profile.getValue(), owner, variant); + putValue("", profile.getValue(), profile, owner, variant, binding); } - private void putValue(String key, Object value, QueryProfile owner, DimensionValues variant) { - if (value == null) return; + private void putValue(String key, + Object value, + QueryProfile profile, + QueryProfile owner, + DimensionValues variant, + DimensionBinding binding) { CompoundName fullName = currentPrefix.append(key); - if (fullName.isEmpty()) return; // Avoid putting a non-leaf (subtree) root in the list if (values.containsKey(fullName.toString())) return; // The first value encountered has priority + Boolean isOverridable = owner != null ? owner.isLocalOverridable(key, binding) : null; + values.put(fullName.toString(), new ValueWithSource(value, owner == null ? "anonymous" : owner.getSource(), + isOverridable != null && ! isOverridable, + profile != null, + profile == null ? null : profile.getType(), variant)); } diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java b/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java index 0cbfdc5dca0..e0edf9f9894 100644 --- a/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java +++ b/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java @@ -162,7 +162,7 @@ public class DimensionBinding { combined.add(d2.get(d2Index++)); } else { - return null; // no independent and no agreement + return null; // not independent and no agreement } } if (d1Index < d1.size()) diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java index ab0f129f1e9..b6b03d37da8 100644 --- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java +++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java @@ -265,37 +265,6 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable } /** - * Lists types reachable from this, indexed by the prefix having that type. - * If this is itself typed, this' type will be included with an empty prefix - */ - public Map<CompoundName, QueryProfileType> listTypes(CompoundName prefix, Map<String, String> context) { - DimensionBinding dimensionBinding = DimensionBinding.createFrom(getDimensions(), context); - AllTypesQueryProfileVisitor visitor = new AllTypesQueryProfileVisitor(prefix); - accept(visitor, dimensionBinding, null); - return visitor.getResult(); - } - - /** - * Lists references reachable from this. - */ - Set<CompoundName> listReferences(CompoundName prefix, Map<String, String> context) { - DimensionBinding dimensionBinding = DimensionBinding.createFrom(getDimensions(),context); - AllReferencesQueryProfileVisitor visitor = new AllReferencesQueryProfileVisitor(prefix); - accept(visitor,dimensionBinding,null); - return visitor.getResult(); - } - - /** - * Lists every entry (value or reference) reachable from this which is not overridable - */ - Set<CompoundName> listUnoverridable(CompoundName prefix, Map<String, String> context) { - DimensionBinding dimensionBinding = DimensionBinding.createFrom(getDimensions(),context); - AllUnoverridableQueryProfileVisitor visitor = new AllUnoverridableQueryProfileVisitor(prefix); - accept(visitor, dimensionBinding, null); - return visitor.getResult(); - } - - /** * Returns a value from this query profile by resolving the given name: * <ul> * <li>The name up to the first dot is the value looked up in the value of this profile @@ -557,6 +526,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable QueryProfileVisitor visitor, DimensionBinding dimensionBinding, QueryProfile owner) { + //System.out.println(" visiting " + this); visitor.onQueryProfile(this, dimensionBinding, owner, null); if (visitor.isDone()) return; @@ -570,6 +540,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable if (visitor.visitInherited()) visitInherited(allowContent, visitor, dimensionBinding, owner); + //System.out.println(" done visiting " + this); } protected void visitVariants(boolean allowContent, QueryProfileVisitor visitor, DimensionBinding dimensionBinding) { @@ -759,7 +730,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable * Sets the overridability of a field in this profile, * this overrides the corresponding setting in the type (if any) */ - private void setOverridable(CompoundName fieldName,boolean overridable,DimensionBinding dimensionBinding) { + private void setOverridable(CompoundName fieldName, boolean overridable, DimensionBinding dimensionBinding) { QueryProfile parent = lookupParentExact(fieldName, true, dimensionBinding); if (parent.overridable == null) parent.overridable = new HashMap<>(); diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java index f1fc90dee09..5dacd347c2c 100644 --- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java +++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java @@ -12,6 +12,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * Compile a set of query profiles into compiled profiles. @@ -24,9 +25,8 @@ public class QueryProfileCompiler { public static CompiledQueryProfileRegistry compile(QueryProfileRegistry input) { CompiledQueryProfileRegistry output = new CompiledQueryProfileRegistry(input.getTypeRegistry()); - for (QueryProfile inputProfile : input.allComponents()) { + for (QueryProfile inputProfile : input.allComponents()) output.register(compile(inputProfile, output)); - } return output; } @@ -40,18 +40,20 @@ public class QueryProfileCompiler { // Resolve values for each existing variant and combine into a single data structure Set<DimensionBindingForPath> variants = collectVariants(CompoundName.empty, in, DimensionBinding.nullBinding); variants.add(new DimensionBindingForPath(DimensionBinding.nullBinding, CompoundName.empty)); // if this contains no variants - log.fine(() -> "Compiling " + in.toString() + " having " + variants.size() + " variants"); + log.fine(() -> "Compiling " + in + " having " + variants.size() + " variants"); + for (DimensionBindingForPath variant : variants) { log.finer(() -> " Compiling variant " + variant); for (Map.Entry<String, ValueWithSource> entry : in.visitValues(variant.path(), variant.binding().getContext()).valuesWithSource().entrySet()) { - values.put(variant.path().append(entry.getKey()), variant.binding(), entry.getValue()); + CompoundName fullName = variant.path().append(entry.getKey()); + values.put(fullName, variant.binding(), entry.getValue()); + if (entry.getValue().isUnoverridable()) + unoverridables.put(fullName, variant.binding(), Boolean.TRUE); + if (entry.getValue().isQueryProfile()) + references.put(fullName, variant.binding(), Boolean.TRUE); + if (entry.getValue().queryProfileType() != null) + types.put(fullName, variant.binding(), entry.getValue().queryProfileType()); } - for (Map.Entry<CompoundName, QueryProfileType> entry : in.listTypes(variant.path(), variant.binding().getContext()).entrySet()) - types.put(variant.path().append(entry.getKey()), variant.binding(), entry.getValue()); - for (CompoundName reference : in.listReferences(variant.path(), variant.binding().getContext())) - references.put(variant.path().append(reference), variant.binding(), Boolean.TRUE); // Used as a set; value is ignored - for (CompoundName name : in.listUnoverridable(variant.path(), variant.binding().getContext())) - unoverridables.put(variant.path().append(name), variant.binding(), Boolean.TRUE); // Used as a set; value is ignored } return new CompiledQueryProfile(in.getId(), in.getType(), @@ -100,6 +102,7 @@ public class QueryProfileCompiler { */ private static Set<DimensionBindingForPath> wildcardExpanded(Set<DimensionBindingForPath> variants) { Set<DimensionBindingForPath> expanded = new HashSet<>(); + for (var variant : variants) { if (hasWildcardBeforeEnd(variant.binding())) expanded.addAll(wildcardExpanded(variant, variants)); @@ -120,10 +123,10 @@ public class QueryProfileCompiler { Set<DimensionBindingForPath> expanded = new HashSet<>(); for (var variant : variants) { if (variant.binding().isNull()) continue; + if ( ! variant.path().hasPrefix(variantToExpand.path())) continue; DimensionBinding combined = variantToExpand.binding().combineWith(variant.binding()); - if ( ! combined.isInvalid() ) { + if ( ! combined.isInvalid() ) expanded.add(new DimensionBindingForPath(combined, variantToExpand.path())); - } } return expanded; } @@ -136,7 +139,7 @@ public class QueryProfileCompiler { for (DimensionBindingForPath v1 : v1s) { if (v1.binding().isNull()) continue; for (DimensionBindingForPath v2 : v2s) { - if (v1.binding().isNull()) continue; + if (v2.binding().isNull()) continue; DimensionBinding combined = v1.binding().combineWith(v2.binding()); if ( combined.isInvalid() ) continue; diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java index 0363b50815b..eb7a6d19d91 100644 --- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java +++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java @@ -62,7 +62,6 @@ public class QueryProfileRegistry extends ComponentRegistry<QueryProfile> { int slashIndex=id.getName().lastIndexOf("/"); if (slashIndex<1) return null; String parentName=id.getName().substring(0,slashIndex); - if (parentName.equals("")) return null; ComponentSpecification parentId=new ComponentSpecification(parentName,id.getVersionSpecification()); diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java index 8dedda800ea..4c4d6778d86 100644 --- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java +++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java @@ -30,7 +30,7 @@ public class QueryProfileVariants implements Freezable, Cloneable { private Map<String,FieldValues> fieldValuesByName = new HashMap<>(); /** The inherited profiles for various dimensions settings - a set of fieldvalues of List<QueryProfile> */ - private FieldValues inheritedProfiles =new FieldValues(); + private FieldValues inheritedProfiles = new FieldValues(); /** * Field and inherited profiles sorted by specificity used for all-value visiting. @@ -105,10 +105,14 @@ public class QueryProfileVariants implements Freezable, Cloneable { if (contentName != null) { if (type != null) contentName = type.unalias(contentName); + //System.out.println(" accepting single value in " + this + " for local key " + contentName); acceptSingleValue(contentName, allowContent, visitor, dimensionBinding); // Special cased for performance + //System.out.println(" done accepting single value in " + this + " for local key " + contentName); } else { + //System.out.println(" accepting all values in " + this); acceptAllValues(allowContent, visitor, type, dimensionBinding); + //System.out.println(" done accepting all values in " + this); } } @@ -144,7 +148,7 @@ public class QueryProfileVariants implements Freezable, Cloneable { if (visitor.isDone()) return; fieldIndex++; } - else if (inheritedProfileValue != null) { // Inherited is most specific at this point + else { // Inherited is most specific at this point if (inheritedProfileValue.matches(dimensionBinding.getValues())) { @SuppressWarnings("unchecked") List<QueryProfile> inheritedProfileList = (List<QueryProfile>)inheritedProfileValue.getValue(); diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java index 1c7a7cf3e97..9be459ceeab 100644 --- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java +++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java @@ -159,6 +159,7 @@ public class CompiledQueryProfile extends AbstractComponent implements Cloneable ValueWithSource valueWithSource = entry.getValue().get(context); if (valueWithSource == null) continue; + if (valueWithSource.value() == null) continue; valueWithSource = valueWithSource.withValue(substitute(valueWithSource.value(), context, substitution)); CompoundName suffixName = entry.getKey().rest(prefix.size()); diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java index 925d20903c6..bc49e116c6e 100644 --- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java +++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java @@ -2,6 +2,8 @@ package com.yahoo.search.query.profile.compiled; import com.yahoo.search.query.profile.DimensionValues; +import com.yahoo.search.query.profile.QueryProfile; +import com.yahoo.search.query.profile.types.QueryProfileType; import java.util.Optional; @@ -14,24 +16,52 @@ public class ValueWithSource { private final Object value; - /** The source of the query profile having a value */ private final String source; + private final boolean isUnoverridable; + + private final boolean isQueryProfile; + + private final QueryProfileType type; + /** The dimension values specifying a variant in that profile, or null if it is not in a variant */ private final DimensionValues variant; - public ValueWithSource(Object value, String source, DimensionValues variant) { + public ValueWithSource(Object value, + String source, + boolean isUnoverridable, boolean isQueryProfile, QueryProfileType type, + DimensionValues variant) { this.value = value; this.source = source; + this.isUnoverridable = isUnoverridable; + this.isQueryProfile = isQueryProfile; + this.type = type; this.variant = variant; } + /** + * Returns the value at this key, or null if none + * (in which case this is references a query profile which has no value set). + */ public Object value() { return value; } + /** Returns the source of the query profile having a value */ public String source() { return source; } + /** Returns true if this value cannot be overridden in queries */ + public boolean isUnoverridable() { return isUnoverridable; } + + /** + * Returns true if this key references a query profile (i.e a non-leaf). + * In this case the value may or may not be null, as non-leafs may have values. + */ + public boolean isQueryProfile() { return isQueryProfile; } + + /** Returns tye type of this if it refers to a query profile (not a leaf value), and it has a type */ + public QueryProfileType queryProfileType() { return type; } + public ValueWithSource withValue(Object value) { - return new ValueWithSource(value, source, variant); + return new ValueWithSource(value, source, isUnoverridable, isQueryProfile, type, variant); } /** Returns the variant having this value, or empty if it's not in a variant */ diff --git a/container-search/src/test/java/com/yahoo/search/dispatch/MockInvoker.java b/container-search/src/test/java/com/yahoo/search/dispatch/MockInvoker.java index c159293d7d9..459dcc83ab0 100644 --- a/container-search/src/test/java/com/yahoo/search/dispatch/MockInvoker.java +++ b/container-search/src/test/java/com/yahoo/search/dispatch/MockInvoker.java @@ -34,9 +34,10 @@ class MockInvoker extends SearchInvoker { } @Override - protected void sendSearchRequest(Query query) throws IOException { + protected Object sendSearchRequest(Query query, Object context) throws IOException { this.query = query; hitsRequested = query.getHits(); + return context; } @Override diff --git a/container-search/src/test/java/com/yahoo/search/dispatch/rpc/RpcSearchInvokerTest.java b/container-search/src/test/java/com/yahoo/search/dispatch/rpc/RpcSearchInvokerTest.java index ce19224b35f..c421e9523ed 100644 --- a/container-search/src/test/java/com/yahoo/search/dispatch/rpc/RpcSearchInvokerTest.java +++ b/container-search/src/test/java/com/yahoo/search/dispatch/rpc/RpcSearchInvokerTest.java @@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -38,7 +39,9 @@ public class RpcSearchInvokerTest { var invoker = new RpcSearchInvoker(mockSearcher(), new Node(7, "seven", 1), mockPool, 1000); Query q = new Query("search/?query=test&hits=10&offset=3"); - invoker.sendSearchRequest(q); + RpcSearchInvoker.RpcContext context = (RpcSearchInvoker.RpcContext) invoker.sendSearchRequest(q, null); + assertEquals(lengthHolder.get(), context.compressedPayload.uncompressedSize()); + assertSame(context.compressedPayload.data(), payloadHolder.get()); var bytes = mockPool.compressor().decompress(payloadHolder.get(), compressionTypeHolder.get(), lengthHolder.get()); var request = SearchProtocol.SearchRequest.newBuilder().mergeFrom(bytes).build(); @@ -46,6 +49,12 @@ public class RpcSearchInvokerTest { assertEquals(10, request.getHits()); assertEquals(3, request.getOffset()); assertTrue(request.getQueryTreeBlob().size() > 0); + + var invoker2 = new RpcSearchInvoker(mockSearcher(), new Node(8, "eight", 1), mockPool, 1000); + RpcSearchInvoker.RpcContext context2 = (RpcSearchInvoker.RpcContext)invoker2.sendSearchRequest(q, context); + assertSame(context, context2); + assertEquals(lengthHolder.get(), context.compressedPayload.uncompressedSize()); + assertSame(context.compressedPayload.data(), payloadHolder.get()); } @Test @@ -59,7 +68,7 @@ public class RpcSearchInvokerTest { var invoker = new RpcSearchInvoker(mockSearcher(), new Node(7, "seven", 1), mockPool, maxHits); Query q = new Query("search/?query=test&hits=10&offset=3"); - invoker.sendSearchRequest(q); + invoker.sendSearchRequest(q, null); var bytes = mockPool.compressor().decompress(payloadHolder.get(), compressionTypeHolder.get(), lengthHolder.get()); var request = SearchProtocol.SearchRequest.newBuilder().mergeFrom(bytes).build(); diff --git a/container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java b/container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java index 0485b4dbb62..06434da2478 100644 --- a/container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java +++ b/container-search/src/test/java/com/yahoo/search/query/profile/config/test/XmlReadingTestCase.java @@ -3,7 +3,6 @@ package com.yahoo.search.query.profile.config.test; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.container.jdisc.HttpRequest; -import com.yahoo.processing.execution.Execution; import com.yahoo.processing.request.CompoundName; import com.yahoo.yolean.Exceptions; import com.yahoo.search.Query; diff --git a/container-search/src/test/java/com/yahoo/search/searchchain/config/test/SearchChainConfigurerTestCase.java b/container-search/src/test/java/com/yahoo/search/searchchain/config/test/SearchChainConfigurerTestCase.java index a57ed07017f..5b16802a65e 100644 --- a/container-search/src/test/java/com/yahoo/search/searchchain/config/test/SearchChainConfigurerTestCase.java +++ b/container-search/src/test/java/com/yahoo/search/searchchain/config/test/SearchChainConfigurerTestCase.java @@ -18,13 +18,29 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import java.io.*; -import java.util.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThat; /** * @author bratseth @@ -32,8 +48,8 @@ import static org.junit.Assert.*; */ public class SearchChainConfigurerTestCase { - private static Random random = new Random(1); - private static String topCfgDir = System.getProperty("java.io.tmpdir") + File.separator + + private static final Random random = new Random(1); + private static final String topCfgDir = System.getProperty("java.io.tmpdir") + File.separator + "SearchChainConfigurerTestCase" + File.separator; private static final String testDir = "src/test/java/com/yahoo/search/searchchain/config/test/"; @@ -132,7 +148,7 @@ public class SearchChainConfigurerTestCase { * that does not contain any bootstrap configs. */ @Test - public void testSearcherConfigUpdate() throws IOException, InterruptedException { + public void testSearcherConfigUpdate() throws IOException { File cfgDir = getCfgDir(); copyFile(testDir + "handlers.cfg", cfgDir + "/handlers.cfg"); copyFile(testDir + "qr-search.cfg", cfgDir + "/qr-search.cfg"); @@ -171,9 +187,9 @@ public class SearchChainConfigurerTestCase { // Searchers with unchanged config (or that takes no config) are the same as before. Searcher s = searchers.getComponent(DeclaredTestSearcher.class.getName()); - assertThat((DeclaredTestSearcher)s, sameInstance(noConfigSearcher)); + assertThat(s, sameInstance(noConfigSearcher)); s = searchers.getComponent(StringSearcher.class.getName()); - assertThat((StringSearcher)s, sameInstance(stringSearcher)); + assertThat(s, sameInstance(stringSearcher)); configurer.shutdown(); cleanup(cfgDir); @@ -219,7 +235,7 @@ public class SearchChainConfigurerTestCase { assertThat(getSearchChainRegistryFrom(configurer).getSearcherRegistry(), not(searchers)); searchers = getSearchChainRegistryFrom(configurer).getSearcherRegistry(); assertThat(searchers.getComponentCount(), is(3)); - assertThat((IntSearcher)searchers.getComponent(IntSearcher.class.getName()), sameInstance(intSearcher)); + assertThat(searchers.getComponent(IntSearcher.class.getName()), sameInstance(intSearcher)); assertThat(searchers.getComponent(ConfigurableSearcher.class.getName()), instanceOf(ConfigurableSearcher.class)); assertThat(searchers.getComponent(DeclaredTestSearcher.class.getName()), instanceOf(DeclaredTestSearcher.class)); assertThat(searchers.getComponent(StringSearcher.class.getName()), nullValue()); @@ -326,7 +342,7 @@ public class SearchChainConfigurerTestCase { if (append) { Pattern p = Pattern.compile("^[a-z]+" + "\\[\\d+\\]\\.id (.+)"); BufferedReader reader = new BufferedReader(new InputStreamReader( - new FileInputStream(new File(componentsFile)), "UTF-8")); + new FileInputStream(new File(componentsFile)), StandardCharsets.UTF_8)); while ((line = reader.readLine()) != null) { Matcher m = p.matcher(line); if (m.matches() && !m.group(1).equals(HandlersConfigurerDi.RegistriesHack.class.getName())) { @@ -337,7 +353,7 @@ public class SearchChainConfigurerTestCase { reader.close(); } BufferedReader reader = new BufferedReader(new InputStreamReader( - new FileInputStream(new File(configFile)), "UTF-8")); + new FileInputStream(new File(configFile)), StandardCharsets.UTF_8)); Pattern component = Pattern.compile("^" + componentType + "\\[\\d+\\]\\.id (.+)"); while ((line = reader.readLine()) != null) { Matcher m = component.matcher(line); @@ -353,7 +369,7 @@ public class SearchChainConfigurerTestCase { buf.append("components[").append(i++).append("].id ").append(ExecutionFactory.class.getName()).append("\n"); buf.insert(0, "components["+i+"]\n"); - Writer writer = new OutputStreamWriter(new FileOutputStream(new File(componentsFile)), "UTF-8"); + Writer writer = new OutputStreamWriter(new FileOutputStream(new File(componentsFile)), StandardCharsets.UTF_8); writer.write(buf.toString()); writer.flush(); writer.close(); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java index a8dcaa5740c..245747a882f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RotationStatusUpdater.java @@ -1,7 +1,6 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.maintenance; -import com.yahoo.concurrent.maintenance.JobControl; import com.yahoo.vespa.hosted.controller.ApplicationController; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.Instance; @@ -11,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; import com.yahoo.vespa.hosted.controller.rotation.RotationId; import com.yahoo.vespa.hosted.controller.rotation.RotationState; import com.yahoo.vespa.hosted.controller.rotation.RotationStatus; +import com.yahoo.yolean.Exceptions; import java.time.Duration; import java.util.LinkedHashMap; @@ -69,11 +69,11 @@ public class RotationStatusUpdater extends ControllerMaintainer { try { pool.awaitTermination(30, TimeUnit.SECONDS); if (lastException.get() != null) { - log.log(Level.WARNING, String.format("Failed to get global routing status of %d/%d applications. Retrying in %s. Last error: ", + log.log(Level.WARNING, String.format("Failed to get global routing status of %d/%d applications. Retrying in %s. Last error: %s", failures.get(), attempts.get(), - interval()), - lastException.get()); + interval(), + Exceptions.toMessageString(lastException.get()))); } } catch (InterruptedException e) { throw new RuntimeException(e); diff --git a/dist/vespa.spec b/dist/vespa.spec index 1e1658d3d55..9353acf5c68 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -441,6 +441,7 @@ fi %{_prefix}/lib/jars/application-model-jar-with-dependencies.jar %{_prefix}/lib/jars/application-preprocessor-jar-with-dependencies.jar %{_prefix}/lib/jars/athenz-identity-provider-service-jar-with-dependencies.jar +%{_prefix}/lib/jars/cloud-tenant-cd-jar-with-dependencies.jar %{_prefix}/lib/jars/clustercontroller-apps-jar-with-dependencies.jar %{_prefix}/lib/jars/clustercontroller-apputil-jar-with-dependencies.jar %{_prefix}/lib/jars/clustercontroller-core-jar-with-dependencies.jar @@ -460,7 +461,9 @@ fi %{_prefix}/lib/jars/searchlib.jar %{_prefix}/lib/jars/searchlib-jar-with-dependencies.jar %{_prefix}/lib/jars/service-monitor-jar-with-dependencies.jar +%{_prefix}/lib/jars/tenant-cd-api-jar-with-dependencies.jar %{_prefix}/lib/jars/vespa_feed_perf-jar-with-dependencies.jar +%{_prefix}/lib/jars/vespa-osgi-testrunner-jar-with-dependencies.jar %{_prefix}/lib/jars/vespa-testrunner-components.jar %{_prefix}/lib/jars/vespa-testrunner-components-jar-with-dependencies.jar %{_prefix}/lib/jars/zookeeper-command-line-client-jar-with-dependencies.jar diff --git a/document/src/tests/CMakeLists.txt b/document/src/tests/CMakeLists.txt index 6458adccfd8..e203fc4b464 100644 --- a/document/src/tests/CMakeLists.txt +++ b/document/src/tests/CMakeLists.txt @@ -1,5 +1,7 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) + # Runner for unit tests written in gtest. # NOTE: All new test classes should be added here. vespa_add_executable(document_gtest_runner_app TEST @@ -32,7 +34,7 @@ vespa_add_executable(document_gtest_runner_app TEST weightedsetfieldvaluetest.cpp DEPENDS document - gtest + GTest::GTest AFTER document_documentconfig ) diff --git a/documentapi-dependencies/.gitignore b/documentapi-dependencies/.gitignore new file mode 100644 index 00000000000..35f8acd14ce --- /dev/null +++ b/documentapi-dependencies/.gitignore @@ -0,0 +1,3 @@ +documentapi-dependencies.iml +target +/pom.xml.build diff --git a/documentapi-dependencies/OWNERS b/documentapi-dependencies/OWNERS new file mode 100644 index 00000000000..3b2ba1ede81 --- /dev/null +++ b/documentapi-dependencies/OWNERS @@ -0,0 +1 @@ +gjoranv diff --git a/documentapi-dependencies/README.md b/documentapi-dependencies/README.md new file mode 100644 index 00000000000..35654dc24b2 --- /dev/null +++ b/documentapi-dependencies/README.md @@ -0,0 +1,6 @@ +<!-- Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +# Documentapi-dependencies + +Pom artifact that lists dependencies that are common between `documentapi` and +`container-documentapi`. These dependencies are provided by the Jdisc container, +but are needed in scope 'compile' for building standalone document clients. diff --git a/documentapi-dependencies/pom.xml b/documentapi-dependencies/pom.xml new file mode 100644 index 00000000000..6a48c7e9c71 --- /dev/null +++ b/documentapi-dependencies/pom.xml @@ -0,0 +1,75 @@ +<?xml version="1.0"?> +<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.vespa</groupId> + <artifactId>parent</artifactId> + <version>7-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + <artifactId>documentapi-dependencies</artifactId> + <packaging>pom</packaging> + <version>7-SNAPSHOT</version> + + <dependencies> + + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>annotations</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>component</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>config</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>config-lib</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>configdefinitions</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>document</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>jrt</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>messagebus</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>vdslib</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>vespajlib</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>yolean</artifactId> + <version>${project.version}</version> + </dependency> + + </dependencies> +</project> diff --git a/documentapi/OWNERS b/documentapi/OWNERS index e030acdbc5b..8e9a0415de5 100644 --- a/documentapi/OWNERS +++ b/documentapi/OWNERS @@ -1 +1,2 @@ -freva +vekterli +baldersheim diff --git a/documentapi/pom.xml b/documentapi/pom.xml index 245c20b3a46..5fb82e06d1b 100644 --- a/documentapi/pom.xml +++ b/documentapi/pom.xml @@ -10,79 +10,39 @@ <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>documentapi</artifactId> - <packaging>container-plugin</packaging> + <packaging>jar</packaging> <version>7-SNAPSHOT</version> <dependencies> + + <!-- WARNING: dependencies (apart from test scoped) must be added to documentapi-dependencies, not here! --> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>documentapi-dependencies</artifactId> + <version>${project.version}</version> + <type>pom</type> + </dependency> + + <dependency> + <!-- Needed because 'document' uses guava collections, and has guava only in provided scope --> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <scope>provided</scope> </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>component</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>messagebus</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>vdslib</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>vespajlib</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>config</artifactId> - <version>${project.version}</version> - <exclusions> - <exclusion> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-core</artifactId> - </exclusion> - </exclusions> - </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>document</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>configdefinitions</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>annotations</artifactId> - <version>${project.version}</version> - </dependency> </dependencies> <build> <plugins> <plugin> - <groupId>com.yahoo.vespa</groupId> - <artifactId>bundle-plugin</artifactId> - <extensions>true</extensions> - </plugin> - <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> @@ -107,6 +67,19 @@ </plugin> <plugin> <groupId>com.yahoo.vespa</groupId> + <artifactId>config-class-plugin</artifactId> + <version>${project.version}</version> + <executions> + <execution> + <id>config-gen</id> + <goals> + <goal>config-gen</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>com.yahoo.vespa</groupId> <artifactId>abi-check-plugin</artifactId> </plugin> </plugins> diff --git a/documentapi/src/tests/loadtypes/CMakeLists.txt b/documentapi/src/tests/loadtypes/CMakeLists.txt index 495c05c0e43..3e3e64310f8 100644 --- a/documentapi/src/tests/loadtypes/CMakeLists.txt +++ b/documentapi/src/tests/loadtypes/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(documentapi_loadtype_test_app TEST SOURCES loadtypetest.cpp DEPENDS documentapi vdstestlib - gtest + GTest::GTest ) vespa_add_test(NAME documentapi_loadtype_test_app COMMAND documentapi_loadtype_test_app) diff --git a/eval/src/tests/eval/inline_operation/CMakeLists.txt b/eval/src/tests/eval/inline_operation/CMakeLists.txt index 04cdbca3abf..050e9287c21 100644 --- a/eval/src/tests/eval/inline_operation/CMakeLists.txt +++ b/eval/src/tests/eval/inline_operation/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_inline_operation_test_app TEST SOURCES inline_operation_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_inline_operation_test_app COMMAND eval_inline_operation_test_app) diff --git a/eval/src/tests/eval/multiply_add/CMakeLists.txt b/eval/src/tests/eval/multiply_add/CMakeLists.txt index c50aa4f50a2..687c9b3b576 100644 --- a/eval/src/tests/eval/multiply_add/CMakeLists.txt +++ b/eval/src/tests/eval/multiply_add/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_multiply_add_test_app TEST SOURCES multiply_add_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_multiply_add_test_app COMMAND eval_multiply_add_test_app) diff --git a/eval/src/tests/tensor/dense_pow_as_map_optimizer/CMakeLists.txt b/eval/src/tests/tensor/dense_pow_as_map_optimizer/CMakeLists.txt index cf5103f8b4f..60cb72a8ed7 100644 --- a/eval/src/tests/tensor/dense_pow_as_map_optimizer/CMakeLists.txt +++ b/eval/src/tests/tensor/dense_pow_as_map_optimizer/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_dense_pow_as_map_optimizer_test_app TEST SOURCES dense_pow_as_map_optimizer_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_dense_pow_as_map_optimizer_test_app COMMAND eval_dense_pow_as_map_optimizer_test_app) diff --git a/eval/src/tests/tensor/dense_simple_expand_function/CMakeLists.txt b/eval/src/tests/tensor/dense_simple_expand_function/CMakeLists.txt index 4e7409a6139..42be6f4c613 100644 --- a/eval/src/tests/tensor/dense_simple_expand_function/CMakeLists.txt +++ b/eval/src/tests/tensor/dense_simple_expand_function/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_dense_simple_expand_function_test_app TEST SOURCES dense_simple_expand_function_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_dense_simple_expand_function_test_app COMMAND eval_dense_simple_expand_function_test_app) diff --git a/eval/src/tests/tensor/dense_simple_map_function/CMakeLists.txt b/eval/src/tests/tensor/dense_simple_map_function/CMakeLists.txt index 766986bd628..daa68665d2e 100644 --- a/eval/src/tests/tensor/dense_simple_map_function/CMakeLists.txt +++ b/eval/src/tests/tensor/dense_simple_map_function/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_dense_simple_map_function_test_app TEST SOURCES dense_simple_map_function_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_dense_simple_map_function_test_app COMMAND eval_dense_simple_map_function_test_app) diff --git a/eval/src/tests/tensor/index_lookup_table/CMakeLists.txt b/eval/src/tests/tensor/index_lookup_table/CMakeLists.txt index f2dc5d31045..3669c663896 100644 --- a/eval/src/tests/tensor/index_lookup_table/CMakeLists.txt +++ b/eval/src/tests/tensor/index_lookup_table/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_index_lookup_table_test_app TEST SOURCES index_lookup_table_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_index_lookup_table_test_app COMMAND eval_index_lookup_table_test_app) diff --git a/eval/src/tests/tensor/tensor_add_operation/CMakeLists.txt b/eval/src/tests/tensor/tensor_add_operation/CMakeLists.txt index e511b23c5ec..3dcd14a2189 100644 --- a/eval/src/tests/tensor/tensor_add_operation/CMakeLists.txt +++ b/eval/src/tests/tensor/tensor_add_operation/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_tensor_add_operation_test_app TEST SOURCES tensor_add_operation_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_tensor_add_operation_test_app COMMAND eval_tensor_add_operation_test_app) diff --git a/eval/src/tests/tensor/tensor_modify_operation/CMakeLists.txt b/eval/src/tests/tensor/tensor_modify_operation/CMakeLists.txt index 481a0aac7fb..9673c6d0c80 100644 --- a/eval/src/tests/tensor/tensor_modify_operation/CMakeLists.txt +++ b/eval/src/tests/tensor/tensor_modify_operation/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_tensor_modify_operation_test_app TEST SOURCES tensor_modify_operation_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_tensor_modify_operation_test_app COMMAND eval_tensor_modify_operation_test_app) diff --git a/eval/src/tests/tensor/tensor_remove_operation/CMakeLists.txt b/eval/src/tests/tensor/tensor_remove_operation/CMakeLists.txt index ffc71563107..0d0c9b17473 100644 --- a/eval/src/tests/tensor/tensor_remove_operation/CMakeLists.txt +++ b/eval/src/tests/tensor/tensor_remove_operation/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(eval_tensor_remove_operation_test_app TEST SOURCES tensor_remove_operation_test.cpp DEPENDS vespaeval - gtest + GTest::GTest ) vespa_add_test(NAME eval_tensor_remove_operation_test_app COMMAND eval_tensor_remove_operation_test_app) diff --git a/fat-model-dependencies/pom.xml b/fat-model-dependencies/pom.xml index 381035a75b0..6bf676dcba1 100644 --- a/fat-model-dependencies/pom.xml +++ b/fat-model-dependencies/pom.xml @@ -96,7 +96,7 @@ </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>documentapi</artifactId> + <artifactId>container-documentapi</artifactId> <version>${project.version}</version> </dependency> <dependency> diff --git a/fbench/src/test/authority/CMakeLists.txt b/fbench/src/test/authority/CMakeLists.txt index 00f804f43f6..09c1152383e 100644 --- a/fbench/src/test/authority/CMakeLists.txt +++ b/fbench/src/test/authority/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(fbench_authority_test_app TEST SOURCES authority_test.cpp DEPENDS fbench_util vespalib - gtest + GTest::GTest ) vespa_add_test(NAME fbench_authority_test_app COMMAND fbench_authority_test_app) diff --git a/tenant-auth/src/main/java/ai/vespa/hosted/auth/ApiAuthenticator.java b/hosted-api/src/main/java/ai/vespa/hosted/api/DefaultApiAuthenticator.java index 55b3af93050..cdd9a9a56dc 100644 --- a/tenant-auth/src/main/java/ai/vespa/hosted/auth/ApiAuthenticator.java +++ b/hosted-api/src/main/java/ai/vespa/hosted/api/DefaultApiAuthenticator.java @@ -1,10 +1,7 @@ // Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.auth; +package ai.vespa.hosted.api; -import ai.vespa.hosted.api.ControllerHttpClient; -import ai.vespa.hosted.api.Properties; - -public class ApiAuthenticator implements ai.vespa.hosted.api.ApiAuthenticator { +public class DefaultApiAuthenticator implements ai.vespa.hosted.api.ApiAuthenticator { /** Returns a controller client using mTLS if a key and certificate pair is provided, or signed requests otherwise. */ @Override diff --git a/logd/src/logd/proto_converter.cpp b/logd/src/logd/proto_converter.cpp index b3facd4ef4a..e4331e00480 100644 --- a/logd/src/logd/proto_converter.cpp +++ b/logd/src/logd/proto_converter.cpp @@ -1,6 +1,7 @@ // Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "proto_converter.h" +#include <vespa/vespalib/text/utf8.h> using ns_log::LogMessage; using ns_log::Logger; @@ -59,7 +60,7 @@ ProtoConverter::log_message_to_proto(const LogMessage& message, ProtoLogMessage& proto.set_service(message.service()); proto.set_component(message.component()); proto.set_level(convert_level(message.level())); - proto.set_payload(message.payload()); + proto.set_payload(vespalib::Utf8::filter_invalid_sequences(message.payload())); } } diff --git a/logd/src/tests/empty_forwarder/CMakeLists.txt b/logd/src/tests/empty_forwarder/CMakeLists.txt index 4c4ab8efa40..978a425a738 100644 --- a/logd/src/tests/empty_forwarder/CMakeLists.txt +++ b/logd/src/tests/empty_forwarder/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(logd_empty_forwarder_test_app TEST SOURCES empty_forwarder_test.cpp DEPENDS logd - gtest + GTest::GTest ) vespa_add_test(NAME logd_empty_forwarder_test_app COMMAND logd_empty_forwarder_test_app) diff --git a/logd/src/tests/proto_converter/CMakeLists.txt b/logd/src/tests/proto_converter/CMakeLists.txt index 5ca048ecd4e..ba4c7c2b26d 100644 --- a/logd/src/tests/proto_converter/CMakeLists.txt +++ b/logd/src/tests/proto_converter/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(logd_proto_converter_test_app TEST SOURCES proto_converter_test.cpp DEPENDS logd - gtest + GTest::GTest ) vespa_add_test(NAME logd_proto_converter_test_app COMMAND logd_proto_converter_test_app) diff --git a/logd/src/tests/proto_converter/proto_converter_test.cpp b/logd/src/tests/proto_converter/proto_converter_test.cpp index aa0b00e34d6..702752e8482 100644 --- a/logd/src/tests/proto_converter/proto_converter_test.cpp +++ b/logd/src/tests/proto_converter/proto_converter_test.cpp @@ -84,5 +84,21 @@ TEST_F(LogRequestTest, log_messages_are_converted_to_request) ProtoLogLevel::LogMessage_Level_EVENT, "bar_payload", proto.log_messages(1)); } +// UTF-8 encoding of \U+FFFD +#define FFFD "\xEF\xBF\xBD" + +TEST_F(LogRequestTest, invalid_utf8_is_filtered) +{ + messages.emplace_back(12345, "foo_host", 3, 5, "foo_service", "foo_component", Logger::info, + "valid: \xE2\x82\xAC and \xEF\xBF\xBA; semi-valid: \xED\xA0\xBD\xED\xB8\x80; invalid: \xCC surrogate \xED\xBF\xBF overlong \xC1\x81 end" + ); + convert(); + EXPECT_EQ(1, proto.log_messages_size()); + expect_proto_log_message_equal(12345, "foo_host", 3, 5, "foo_service", "foo_component", + ProtoLogLevel::LogMessage_Level_INFO, + "valid: \xE2\x82\xAC and \xEF\xBF\xBA; semi-valid: " FFFD FFFD "; invalid: " FFFD " surrogate " FFFD " overlong " FFFD FFFD " end", + proto.log_messages(0)); +} + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/logd/src/tests/rpc_forwarder/CMakeLists.txt b/logd/src/tests/rpc_forwarder/CMakeLists.txt index 66a30777b41..430647c9b82 100644 --- a/logd/src/tests/rpc_forwarder/CMakeLists.txt +++ b/logd/src/tests/rpc_forwarder/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(logd_rpc_forwarder_test_app TEST SOURCES rpc_forwarder_test.cpp DEPENDS logd - gtest + GTest::GTest ) vespa_add_test(NAME logd_rpc_forwarder_test_app COMMAND logd_rpc_forwarder_test_app) diff --git a/logd/src/tests/watcher/CMakeLists.txt b/logd/src/tests/watcher/CMakeLists.txt index 0bf0a574fa9..42fddae48b3 100644 --- a/logd/src/tests/watcher/CMakeLists.txt +++ b/logd/src/tests/watcher/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(logd_watcher_test_app TEST SOURCES watcher_test.cpp DEPENDS logd - gtest + GTest::GTest ) vespa_add_test(NAME logd_watcher_test_app COMMAND logd_watcher_test_app) diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java index 6b1376452fa..c910ac26833 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsHandler.java @@ -40,9 +40,7 @@ public class ApplicationMetricsHandler extends HttpHandlerBase { public static final String METRICS_V1_PATH = "/applicationmetrics/v1"; public static final String METRICS_VALUES_PATH = METRICS_V1_PATH + "/values"; - - public static final String PROMETHEUS_V1_PATH = "/applicationprometheus/v1"; - public static final String PROMETHEUS_VALUES_PATH = PROMETHEUS_V1_PATH + "/values"; + public static final String PROMETHEUS_VALUES_PATH = METRICS_V1_PATH + "/prometheus"; private final ApplicationMetricsRetriever metricsRetriever; private final MetricsConsumers metricsConsumers; @@ -60,8 +58,6 @@ public class ApplicationMetricsHandler extends HttpHandlerBase { public Optional<HttpResponse> doHandle(URI requestUri, Path apiPath, String consumer) { if (apiPath.matches(METRICS_V1_PATH)) return Optional.of(resourceListResponse(requestUri, List.of(METRICS_VALUES_PATH))); if (apiPath.matches(METRICS_VALUES_PATH)) return Optional.of(applicationMetricsResponse(consumer)); - - if (apiPath.matches(PROMETHEUS_V1_PATH)) return Optional.of(resourceListResponse(requestUri, List.of(PROMETHEUS_VALUES_PATH))); if (apiPath.matches(PROMETHEUS_VALUES_PATH)) return Optional.of(applicationPrometheusResponse(consumer)); return Optional.empty(); diff --git a/metrics/src/tests/CMakeLists.txt b/metrics/src/tests/CMakeLists.txt index c7ca296e7b5..435be3fd1dd 100644 --- a/metrics/src/tests/CMakeLists.txt +++ b/metrics/src/tests/CMakeLists.txt @@ -1,5 +1,7 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) + # Runner for unit tests written in gtest. vespa_add_executable(metrics_gtest_runner_app TEST SOURCES @@ -17,7 +19,7 @@ vespa_add_executable(metrics_gtest_runner_app TEST DEPENDS metrics vdstestlib - gtest + GTest::GTest ) vespa_add_test( diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java index 4e55e464fa7..6c1c1a48a9c 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java @@ -20,10 +20,8 @@ import java.nio.file.attribute.PosixFilePermissions; import java.nio.file.attribute.UserPrincipal; import java.nio.file.attribute.UserPrincipalLookupService; import java.time.Instant; -import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; import static com.yahoo.vespa.hosted.node.admin.task.util.file.IOExceptionUtil.ifExists; @@ -231,8 +229,8 @@ public class UnixPath { */ public boolean deleteRecursively() { if (!isSymbolicLink() && isDirectory()) { - for (UnixPath path : listContentsOfDirectory()) { - path.deleteRecursively(); + try (Stream<UnixPath> paths = listContentsOfDirectory()) { + paths.forEach(UnixPath::deleteRecursively); } } return uncheck(() -> Files.deleteIfExists(path)); @@ -243,13 +241,14 @@ public class UnixPath { return this; } - public List<UnixPath> listContentsOfDirectory() { - try (Stream<Path> stream = Files.list(path)){ - return stream - .map(UnixPath::new) - .collect(Collectors.toList()); + /** Lists the contents of this as a stream. Callers should use try-with to ensure that the stream is closed */ + public Stream<UnixPath> listContentsOfDirectory() { + try { + // Avoid the temptation to collect the stream here as collecting a directory with a high number of entries + // can quickly lead to out of memory conditions + return Files.list(path).map(UnixPath::new); } catch (NoSuchFileException ignored) { - return List.of(); + return Stream.empty(); } catch (IOException e) { throw new RuntimeException("Failed to list contents of directory " + path.toAbsolutePath(), e); } diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java index b420b6aac23..e5c2e35f2b2 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java @@ -27,6 +27,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.yahoo.yolean.Exceptions.uncheck; import static org.junit.Assert.assertEquals; @@ -247,10 +248,11 @@ public class CoredumpHandlerTest { private static void assertFolderContents(Path pathToFolder, String... filenames) { Set<String> expectedContentsOfFolder = Set.of(filenames); - Set<String> actualContentsOfFolder = new UnixPath(pathToFolder) - .listContentsOfDirectory().stream() - .map(unixPath -> unixPath.toPath().getFileName().toString()) - .collect(Collectors.toSet()); + Set<String> actualContentsOfFolder; + try (Stream<UnixPath> paths = new UnixPath(pathToFolder).listContentsOfDirectory()) { + actualContentsOfFolder = paths.map(unixPath -> unixPath.toPath().getFileName().toString()) + .collect(Collectors.toSet()); + } assertEquals(expectedContentsOfFolder, actualContentsOfFolder); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java index 06ecb1f4a01..3cb0f935888 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainer.java @@ -71,6 +71,7 @@ public class PeriodicApplicationMaintainer extends ApplicationMaintainer { private boolean shouldMaintain(ApplicationId id) { if (id.tenant().value().equals("stream") && id.application().value().equals("stream-ranking")) return false; if (id.tenant().value().equals("stream") && id.application().value().equals("stream-ranking-canary")) return false; + if (id.tenant().value().equals("stream") && id.application().value().equals("stream-ranking-rhel7")) return false; return true; } diff --git a/persistence/CMakeLists.txt b/persistence/CMakeLists.txt index e9fd742af27..3883929265c 100644 --- a/persistence/CMakeLists.txt +++ b/persistence/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_define_module( DEPENDS fastos @@ -20,7 +21,7 @@ vespa_define_module( src/vespa/persistence/spi TEST_DEPENDS - gtest + GTest::GTest TESTS src/tests diff --git a/persistence/src/vespa/persistence/CMakeLists.txt b/persistence/src/vespa/persistence/CMakeLists.txt index 77f3f865b6f..456c3f8f87f 100644 --- a/persistence/src/vespa/persistence/CMakeLists.txt +++ b/persistence/src/vespa/persistence/CMakeLists.txt @@ -11,5 +11,5 @@ vespa_add_library(persistence_persistence_conformancetest $<TARGET_OBJECTS:persistence_conformancetest_lib> DEPENDS persistence - gtest + GTest::GTest ) @@ -60,6 +60,7 @@ <module>container-dev</module> <module>container-di</module> <module>container-disc</module> + <module>container-documentapi</module> <module>container-integration-test</module> <module>container-jersey2</module> <module>container-messagebus</module> @@ -74,8 +75,9 @@ <module>docker-api</module> <module>docproc</module> <module>docprocs</module> - <module>documentapi</module> <module>document</module> + <module>documentapi</module> + <module>documentapi-dependencies</module> <module>documentgen-test</module> <module>fat-model-dependencies</module> <module>fileacquirer</module> @@ -126,9 +128,9 @@ <module>standalone-container</module> <module>statistics</module> <module>storage</module> - <module>tenant-auth</module> <module>tenant-base</module> <module>tenant-cd-api</module> + <module>tenant-cd-commons</module> <module>testutil</module> <module>vdslib</module> <module>vespaclient-core</module> diff --git a/processing/src/main/java/com/yahoo/processing/request/CompoundName.java b/processing/src/main/java/com/yahoo/processing/request/CompoundName.java index 976fb3e2796..09c0879fdbf 100644 --- a/processing/src/main/java/com/yahoo/processing/request/CompoundName.java +++ b/processing/src/main/java/com/yahoo/processing/request/CompoundName.java @@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import static com.yahoo.text.Lowercase.toLowerCase; @@ -141,9 +140,8 @@ public final class CompoundName { if (nameParts.length == 0) return this; if (isEmpty()) return fromComponents(nameParts); - List<String> newCompounds = new ArrayList<>(); - for (String namePart : nameParts) - newCompounds.add(namePart); + List<String> newCompounds = new ArrayList<>(nameParts.length+compounds.size()); + newCompounds.addAll(Arrays.asList(nameParts)); newCompounds.addAll(this.compounds); return new CompoundName(newCompounds); } diff --git a/searchcommon/src/tests/schema/CMakeLists.txt b/searchcommon/src/tests/schema/CMakeLists.txt index aafe015d9a1..76c8d3f8483 100644 --- a/searchcommon/src/tests/schema/CMakeLists.txt +++ b/searchcommon/src/tests/schema/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcommon_schema_test_app TEST SOURCES schema_test.cpp DEPENDS searchcommon - gtest + GTest::GTest ) vespa_add_test(NAME searchcommon_schema_test_app NO_VALGRIND COMMAND searchcommon_schema_test_app) diff --git a/searchcore/src/tests/proton/attribute/CMakeLists.txt b/searchcore/src/tests/proton/attribute/CMakeLists.txt index c23d97c6e88..95f55c28f18 100644 --- a/searchcore/src/tests/proton/attribute/CMakeLists.txt +++ b/searchcore/src/tests/proton/attribute/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_attribute_test_app TEST SOURCES attribute_test.cpp @@ -8,7 +9,7 @@ vespa_add_executable(searchcore_attribute_test_app TEST searchcore_flushengine searchcore_pcommon searchlib_test - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_attribute_test_app COMMAND searchcore_attribute_test_app) diff --git a/searchcore/src/tests/proton/attribute/attribute_aspect_delayer/CMakeLists.txt b/searchcore/src/tests/proton/attribute/attribute_aspect_delayer/CMakeLists.txt index de1ab5851f4..54c028fbfe8 100644 --- a/searchcore/src/tests/proton/attribute/attribute_aspect_delayer/CMakeLists.txt +++ b/searchcore/src/tests/proton/attribute/attribute_aspect_delayer/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_attribute_aspect_delayer_test_app TEST SOURCES attribute_aspect_delayer_test.cpp DEPENDS searchcore_attribute searchcore_pcommon - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_attribute_aspect_delayer_test_app COMMAND searchcore_attribute_aspect_delayer_test_app) diff --git a/searchcore/src/tests/proton/attribute/attribute_usage_sampler_functor/CMakeLists.txt b/searchcore/src/tests/proton/attribute/attribute_usage_sampler_functor/CMakeLists.txt index 2cb6bc65df3..a48d7f19abe 100644 --- a/searchcore/src/tests/proton/attribute/attribute_usage_sampler_functor/CMakeLists.txt +++ b/searchcore/src/tests/proton/attribute/attribute_usage_sampler_functor/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_attribute_usage_sampler_functor_test_app TEST SOURCES attribute_usage_sampler_functor_test.cpp DEPENDS searchcore_attribute searchcore_pcommon - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_attribute_usage_sampler_functor_test_app COMMAND searchcore_attribute_usage_sampler_functor_test_app) diff --git a/searchcore/src/tests/proton/common/operation_rate_tracker/CMakeLists.txt b/searchcore/src/tests/proton/common/operation_rate_tracker/CMakeLists.txt index f5e6a791124..3c5152935d7 100644 --- a/searchcore/src/tests/proton/common/operation_rate_tracker/CMakeLists.txt +++ b/searchcore/src/tests/proton/common/operation_rate_tracker/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_operation_rate_tracker_test_app TEST SOURCES operation_rate_tracker_test.cpp DEPENDS searchcore_pcommon - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_operation_rate_tracker_test_app COMMAND searchcore_operation_rate_tracker_test_app) diff --git a/searchcore/src/tests/proton/documentdb/lid_space_compaction/CMakeLists.txt b/searchcore/src/tests/proton/documentdb/lid_space_compaction/CMakeLists.txt index c4e69ace22c..3e008e3f5d0 100644 --- a/searchcore/src/tests/proton/documentdb/lid_space_compaction/CMakeLists.txt +++ b/searchcore/src/tests/proton/documentdb/lid_space_compaction/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_lid_space_compaction_test_app TEST SOURCES lid_space_compaction_test.cpp @@ -10,6 +11,6 @@ vespa_add_executable(searchcore_lid_space_compaction_test_app TEST searchcore_documentmetastore searchcore_bucketdb searchcore_pcommon - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_lid_space_compaction_test_app COMMAND searchcore_lid_space_compaction_test_app) diff --git a/searchcore/src/tests/proton/documentmetastore/CMakeLists.txt b/searchcore/src/tests/proton/documentmetastore/CMakeLists.txt index f31d82240e0..c35d736acd1 100644 --- a/searchcore/src/tests/proton/documentmetastore/CMakeLists.txt +++ b/searchcore/src/tests/proton/documentmetastore/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_documentmetastore_test_app TEST SOURCES documentmetastore_test.cpp @@ -9,7 +10,7 @@ vespa_add_executable(searchcore_documentmetastore_test_app TEST searchcore_attribute searchcore_feedoperation searchcore_fconfig - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_documentmetastore_test_app COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/documentmetastore_test.sh DEPENDS searchcore_documentmetastore_test_app) diff --git a/searchcore/src/tests/proton/index/CMakeLists.txt b/searchcore/src/tests/proton/index/CMakeLists.txt index 1ffad6cdbf8..b967bf304c3 100644 --- a/searchcore/src/tests/proton/index/CMakeLists.txt +++ b/searchcore/src/tests/proton/index/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_indexmanager_test_app TEST SOURCES indexmanager_test.cpp @@ -7,7 +8,7 @@ vespa_add_executable(searchcore_indexmanager_test_app TEST searchcore_index searchcore_flushengine searchcore_pcommon - gtest + GTest::GTest ) vespa_add_executable(searchcore_fusionrunner_test_app TEST SOURCES @@ -28,7 +29,7 @@ vespa_add_executable(searchcore_indexcollection_test_app TEST indexcollection_test.cpp DEPENDS searchcore_index - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_index_test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/index_test.sh DEPENDS searchcore_indexmanager_test_app searchcore_fusionrunner_test_app searchcore_diskindexcleaner_test_app searchcore_indexcollection_test_app) diff --git a/searchcore/src/tests/proton/matching/handle_recorder/CMakeLists.txt b/searchcore/src/tests/proton/matching/handle_recorder/CMakeLists.txt index a56d22ab154..3f4fd071e5c 100644 --- a/searchcore/src/tests/proton/matching/handle_recorder/CMakeLists.txt +++ b/searchcore/src/tests/proton/matching/handle_recorder/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_matching_handle_recorder_test_app TEST SOURCES handle_recorder_test.cpp DEPENDS searchcore_matching - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_matching_handle_recorder_test_app COMMAND searchcore_matching_handle_recorder_test_app) diff --git a/searchcore/src/tests/proton/matching/request_context/CMakeLists.txt b/searchcore/src/tests/proton/matching/request_context/CMakeLists.txt index 54696112302..c044a9d4869 100644 --- a/searchcore/src/tests/proton/matching/request_context/CMakeLists.txt +++ b/searchcore/src/tests/proton/matching/request_context/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_matching_request_context_test_app TEST SOURCES request_context_test.cpp DEPENDS searchcore_matching - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_matching_request_context_test_app COMMAND searchcore_matching_request_context_test_app) diff --git a/searchcore/src/tests/proton/matching/unpacking_iterators_optimizer/CMakeLists.txt b/searchcore/src/tests/proton/matching/unpacking_iterators_optimizer/CMakeLists.txt index 6fce7151664..764208c0eba 100644 --- a/searchcore/src/tests/proton/matching/unpacking_iterators_optimizer/CMakeLists.txt +++ b/searchcore/src/tests/proton/matching/unpacking_iterators_optimizer/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_unpacking_iterators_optimizer_test_app TEST SOURCES unpacking_iterators_optimizer_test.cpp DEPENDS searchcore_matching - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_unpacking_iterators_optimizer_test_app COMMAND searchcore_unpacking_iterators_optimizer_test_app) diff --git a/searchcore/src/tests/proton/reprocessing/attribute_reprocessing_initializer/CMakeLists.txt b/searchcore/src/tests/proton/reprocessing/attribute_reprocessing_initializer/CMakeLists.txt index 048ed1938e5..f96a31b7765 100644 --- a/searchcore/src/tests/proton/reprocessing/attribute_reprocessing_initializer/CMakeLists.txt +++ b/searchcore/src/tests/proton/reprocessing/attribute_reprocessing_initializer/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchcore_attribute_reprocessing_initializer_test_app TEST SOURCES attribute_reprocessing_initializer_test.cpp @@ -6,6 +7,6 @@ vespa_add_executable(searchcore_attribute_reprocessing_initializer_test_app TEST searchcore_reprocessing searchcore_attribute searchcore_pcommon - gtest + GTest::GTest ) vespa_add_test(NAME searchcore_attribute_reprocessing_initializer_test_app COMMAND searchcore_attribute_reprocessing_initializer_test_app) diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp b/searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp index fadea4b7962..19bece5ae9c 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/match_tools.cpp @@ -194,7 +194,8 @@ MatchToolsFactory(QueryLimiter & queryLimiter, trace.addEvent(4, "MTF: Fetch Postings"); _query.fetchPostings(); trace.addEvent(5, "MTF: Handle Global Filters"); - _query.handle_global_filters(searchContext.getDocIdLimit()); + double global_filter_limit = GlobalFilterLimit::lookup(rankProperties, rankSetup.get_global_filter_limit()); + _query.handle_global_filters(searchContext.getDocIdLimit(), global_filter_limit); _query.freeze(); trace.addEvent(5, "MTF: prepareSharedState"); _rankSetup.prepareSharedState(_queryEnv, _queryEnv.getObjectStore()); diff --git a/searchcore/src/vespa/searchcore/proton/matching/query.cpp b/searchcore/src/vespa/searchcore/proton/matching/query.cpp index 5213a2b9230..62a59ab7680 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/query.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/query.cpp @@ -199,10 +199,11 @@ Query::fetchPostings() } void -Query::handle_global_filters(uint32_t docid_limit) +Query::handle_global_filters(uint32_t docid_limit, double global_filter_limit) { using search::queryeval::GlobalFilter; - if (_blueprint->getState().want_global_filter()) { + double estimated_hit_ratio = _blueprint->getState().hit_ratio(docid_limit); + if (_blueprint->getState().want_global_filter() && estimated_hit_ratio >= global_filter_limit) { auto constraint = Blueprint::FilterConstraint::UPPER_BOUND; bool strict = true; auto filter_iterator = _blueprint->createFilterSearch(strict, constraint); diff --git a/searchcore/src/vespa/searchcore/proton/matching/query.h b/searchcore/src/vespa/searchcore/proton/matching/query.h index 3ed6229830d..60f40e24d1e 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/query.h +++ b/searchcore/src/vespa/searchcore/proton/matching/query.h @@ -89,7 +89,7 @@ public: **/ void optimize(); void fetchPostings(); - void handle_global_filters(uint32_t docidLimit); + void handle_global_filters(uint32_t docidLimit, double global_filter_limit); void freeze(); /** diff --git a/searchlib/CMakeLists.txt b/searchlib/CMakeLists.txt index 9b9f1a704cc..1db9018bd21 100644 --- a/searchlib/CMakeLists.txt +++ b/searchlib/CMakeLists.txt @@ -187,11 +187,9 @@ vespa_define_module( src/tests/query src/tests/queryeval src/tests/queryeval/blueprint - src/tests/queryeval/booleanmatchiteratorwrapper src/tests/queryeval/dot_product src/tests/queryeval/equiv src/tests/queryeval/fake_searchable - src/tests/queryeval/filter_wrapper src/tests/queryeval/getnodeweight src/tests/queryeval/monitoring_search_iterator src/tests/queryeval/multibitvectoriterator @@ -207,6 +205,7 @@ vespa_define_module( src/tests/queryeval/weak_and_heap src/tests/queryeval/weak_and_scorers src/tests/queryeval/weighted_set_term + src/tests/queryeval/wrappers src/tests/rankingexpression/feature_name_extractor src/tests/rankingexpression/intrinsic_blueprint_adapter src/tests/ranksetup diff --git a/searchlib/abi-spec.json b/searchlib/abi-spec.json index c22d906e2b2..0768999c52f 100644 --- a/searchlib/abi-spec.json +++ b/searchlib/abi-spec.json @@ -1614,6 +1614,7 @@ "public com.yahoo.tensor.functions.TensorFunction withArguments(java.util.List)", "public com.yahoo.tensor.functions.PrimitiveTensorFunction toPrimitive()", "public com.yahoo.tensor.TensorType type(com.yahoo.tensor.evaluation.TypeContext)", + "public java.util.Optional asScalarFunction()", "public com.yahoo.tensor.Tensor evaluate(com.yahoo.tensor.evaluation.EvaluationContext)", "public java.lang.String toString()", "public java.lang.String toString(com.yahoo.tensor.functions.ToStringContext)" diff --git a/searchlib/src/tests/attribute/attribute_header/CMakeLists.txt b/searchlib/src/tests/attribute/attribute_header/CMakeLists.txt index e72c0c6a528..2327cf5116f 100644 --- a/searchlib/src/tests/attribute/attribute_header/CMakeLists.txt +++ b/searchlib/src/tests/attribute/attribute_header/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_attribute_header_test_app TEST SOURCES attribute_header_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_attribute_header_test_app COMMAND searchlib_attribute_header_test_app) diff --git a/searchlib/src/tests/attribute/document_weight_or_filter_search/CMakeLists.txt b/searchlib/src/tests/attribute/document_weight_or_filter_search/CMakeLists.txt index 660cfbaefff..8f25f6b66b8 100644 --- a/searchlib/src/tests/attribute/document_weight_or_filter_search/CMakeLists.txt +++ b/searchlib/src/tests/attribute/document_weight_or_filter_search/CMakeLists.txt @@ -5,6 +5,7 @@ vespa_add_executable(searchlib_document_weight_or_filter_search_test_app TEST document_weight_or_filter_search_test.cpp DEPENDS searchlib + searchlib_test GTest::GTest ) vespa_add_test(NAME searchlib_document_weight_or_filter_search_test_app COMMAND searchlib_document_weight_or_filter_search_test_app) diff --git a/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp b/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp index c8e799a8f10..22f928ddf45 100644 --- a/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp +++ b/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp @@ -5,6 +5,8 @@ #include <vespa/searchlib/attribute/document_weight_or_filter_search.h> #include <vespa/searchlib/queryeval/searchiterator.h> #include <vespa/searchlib/common/bitvector.h> +#define ENABLE_GTEST_MIGRATION +#include <vespa/searchlib/test/searchiteratorverifier.h> using PostingList = search::attribute::PostingListTraits<int32_t>::PostingStoreBase; using Iterator = search::attribute::PostingListTraits<int32_t>::const_iterator; @@ -20,7 +22,7 @@ class DocumentWeightOrFilterSearchTest : public ::testing::Test { std::vector<EntryRef> _trees; uint32_t _range_start; uint32_t _range_end; -protected: +public: DocumentWeightOrFilterSearchTest(); ~DocumentWeightOrFilterSearchTest(); void inc_generation(); @@ -48,6 +50,13 @@ protected: _postings.apply(_trees[idx], &*adds.begin(), &*adds.end(), &*removes.begin(), &*removes.end()); } + void clear_tree(size_t idx) { + if (idx < _trees.size()) { + _postings.clear(_trees[idx]); + _trees[idx] = EntryRef(); + } + } + std::unique_ptr<SearchIterator> make_iterator() const { std::vector<Iterator> iterators; for (size_t i = 0; i < num_trees(); ++i) { @@ -200,4 +209,53 @@ TEST_F(DocumentWeightOrFilterSearchTest, taat_and_hits_into_ranged) expect_result(frombv(*bv), { 14 }); } +namespace { + +class Verifier : public search::test::SearchIteratorVerifier { + DocumentWeightOrFilterSearchTest &_test; +public: + Verifier(DocumentWeightOrFilterSearchTest &test, int num_trees) + : _test(test) + { + std::vector<std::vector<uint32_t>> trees(num_trees); + uint32_t tree_id = 0; + for (const auto doc_id : getExpectedDocIds()) { + trees[tree_id++ % trees.size()].emplace_back(doc_id); + } + tree_id = 0; + for (const auto &tree : trees) { + _test.add_tree(tree_id++, tree); + } + _test.inc_generation(); + } + ~Verifier() { + for (uint32_t tree_id = 0; tree_id < _test.num_trees(); ++tree_id) { + _test.clear_tree(tree_id); + } + _test.inc_generation(); + } + std::unique_ptr<SearchIterator> create(bool) const override { + return _test.make_iterator(); + } + +}; + +TEST_F(DocumentWeightOrFilterSearchTest, iterator_conformance) +{ + { + Verifier verifier(*this, 1); + verifier.verify(); + } + { + Verifier verifier(*this, 2); + verifier.verify(); + } + { + Verifier verifier(*this, 3); + verifier.verify(); + } +} + +} + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/searchlib/src/tests/attribute/enumstore/CMakeLists.txt b/searchlib/src/tests/attribute/enumstore/CMakeLists.txt index e8f6068cebe..b9a361d05fc 100644 --- a/searchlib/src/tests/attribute/enumstore/CMakeLists.txt +++ b/searchlib/src/tests/attribute/enumstore/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_enumstore_test_app TEST SOURCES enumstore_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_enumstore_test_app COMMAND searchlib_enumstore_test_app) diff --git a/searchlib/src/tests/attribute/multi_value_mapping/CMakeLists.txt b/searchlib/src/tests/attribute/multi_value_mapping/CMakeLists.txt index 6b1f5f3d656..53856bf7b88 100644 --- a/searchlib/src/tests/attribute/multi_value_mapping/CMakeLists.txt +++ b/searchlib/src/tests/attribute/multi_value_mapping/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_multi_value_mapping_test_app TEST SOURCES multi_value_mapping_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_multi_value_mapping_test_app COMMAND searchlib_multi_value_mapping_test_app) diff --git a/searchlib/src/tests/attribute/reference_attribute/CMakeLists.txt b/searchlib/src/tests/attribute/reference_attribute/CMakeLists.txt index 6638bf886b7..9473f9f7fbf 100644 --- a/searchlib/src/tests/attribute/reference_attribute/CMakeLists.txt +++ b/searchlib/src/tests/attribute/reference_attribute/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_reference_attribute_test_app TEST SOURCES reference_attribute_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_reference_attribute_test_app COMMAND searchlib_reference_attribute_test_app) diff --git a/searchlib/src/tests/attribute/save_target/CMakeLists.txt b/searchlib/src/tests/attribute/save_target/CMakeLists.txt index e127f66579e..dac326207da 100644 --- a/searchlib/src/tests/attribute/save_target/CMakeLists.txt +++ b/searchlib/src/tests/attribute/save_target/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_attribute_save_target_test_app TEST SOURCES attribute_save_target_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_attribute_save_target_test_app COMMAND searchlib_attribute_save_target_test_app) diff --git a/searchlib/src/tests/attribute/searchable/CMakeLists.txt b/searchlib/src/tests/attribute/searchable/CMakeLists.txt index e754253c34a..80c23e299f4 100644 --- a/searchlib/src/tests/attribute/searchable/CMakeLists.txt +++ b/searchlib/src/tests/attribute/searchable/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_attribute_searchable_adapter_test_app TEST SOURCES attribute_searchable_adapter_test.cpp @@ -19,6 +20,6 @@ vespa_add_executable(searchlib_attribute_blueprint_test_app TEST attributeblueprint_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_attribute_blueprint_test_app COMMAND searchlib_attribute_blueprint_test_app) diff --git a/searchlib/src/tests/attribute/searchcontextelementiterator/CMakeLists.txt b/searchlib/src/tests/attribute/searchcontextelementiterator/CMakeLists.txt index 1a27b446d30..6810d145124 100644 --- a/searchlib/src/tests/attribute/searchcontextelementiterator/CMakeLists.txt +++ b/searchlib/src/tests/attribute/searchcontextelementiterator/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_attribute_searchcontextelementiterator_test_app TEST SOURCES searchcontextelementiterator_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_attribute_searchcontextelementiterator_test_app COMMAND searchlib_attribute_searchcontextelementiterator_test_app) diff --git a/searchlib/src/tests/common/matching_elements/CMakeLists.txt b/searchlib/src/tests/common/matching_elements/CMakeLists.txt index cd1d3560c15..ef7aa316dfd 100644 --- a/searchlib/src/tests/common/matching_elements/CMakeLists.txt +++ b/searchlib/src/tests/common/matching_elements/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_common_matching_elements_test_app TEST SOURCES matching_elements_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_common_matching_elements_test_app COMMAND searchlib_common_matching_elements_test_app) diff --git a/searchlib/src/tests/common/matching_elements_fields/CMakeLists.txt b/searchlib/src/tests/common/matching_elements_fields/CMakeLists.txt index 3d6f338315a..f5a61787328 100644 --- a/searchlib/src/tests/common/matching_elements_fields/CMakeLists.txt +++ b/searchlib/src/tests/common/matching_elements_fields/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_common_matching_elements_fields_test_app TEST SOURCES matching_elements_fields_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_common_matching_elements_fields_test_app COMMAND searchlib_common_matching_elements_fields_test_app) diff --git a/searchlib/src/tests/engine/proto_converter/CMakeLists.txt b/searchlib/src/tests/engine/proto_converter/CMakeLists.txt index 718e8120c2c..82e8e1b8be7 100644 --- a/searchlib/src/tests/engine/proto_converter/CMakeLists.txt +++ b/searchlib/src/tests/engine/proto_converter/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_engine_proto_converter_test_app TEST SOURCES proto_converter_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_engine_proto_converter_test_app COMMAND searchlib_engine_proto_converter_test_app) diff --git a/searchlib/src/tests/engine/proto_rpc_adapter/CMakeLists.txt b/searchlib/src/tests/engine/proto_rpc_adapter/CMakeLists.txt index 3fa638f16e5..7d7259c40d8 100644 --- a/searchlib/src/tests/engine/proto_rpc_adapter/CMakeLists.txt +++ b/searchlib/src/tests/engine/proto_rpc_adapter/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_engine_proto_rpc_adapter_test_app TEST SOURCES proto_rpc_adapter_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_engine_proto_rpc_adapter_test_app COMMAND searchlib_engine_proto_rpc_adapter_test_app) diff --git a/searchlib/src/tests/features/bm25/CMakeLists.txt b/searchlib/src/tests/features/bm25/CMakeLists.txt index 3f9b92684f8..35ad629169f 100644 --- a/searchlib/src/tests/features/bm25/CMakeLists.txt +++ b/searchlib/src/tests/features/bm25/CMakeLists.txt @@ -1,11 +1,12 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_features_bm25_test_app TEST SOURCES bm25_test.cpp DEPENDS searchlib searchlib_test - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_features_bm25_test_app COMMAND searchlib_features_bm25_test_app) diff --git a/searchlib/src/tests/features/prod_features.cpp b/searchlib/src/tests/features/prod_features.cpp index c50c7a12698..f886ba59c1c 100644 --- a/searchlib/src/tests/features/prod_features.cpp +++ b/searchlib/src/tests/features/prod_features.cpp @@ -34,7 +34,7 @@ #include <vespa/searchlib/features/setup.h> #include <vespa/searchlib/features/termfeature.h> #include <vespa/searchlib/features/utils.h> -#include <vespa/searchlib/features/uniquefeature.h> +#include <vespa/searchlib/features/global_sequence_feature.h> #include <vespa/searchlib/features/weighted_set_parser.hpp> #include <vespa/searchlib/fef/featurenamebuilder.h> #include <vespa/searchlib/fef/indexproperties.h> @@ -1565,22 +1565,33 @@ Test::testMatchCount() } } +void verifySequence(uint64_t first, uint64_t second) { + ASSERT_GREATER(first, second); + ASSERT_GREATER(double(first), double(second)); +} + void Test::testUnique() { { - UniqueBlueprint bp; - EXPECT_TRUE(assertCreateInstance(bp, "unique")); + GlobalSequenceBlueprint bp; + EXPECT_TRUE(assertCreateInstance(bp, "globalSequence")); FtFeatureTest ft(_factory, ""); StringList params, in, out; FT_SETUP_OK(bp, ft.getIndexEnv(), params, in, out.add("out")); - FT_DUMP_EMPTY(_factory, "unique"); + FT_DUMP_EMPTY(_factory, "globalSequence"); } - FtFeatureTest ft(_factory, "unique"); + FtFeatureTest ft(_factory, "globalSequence"); ASSERT_TRUE(ft.setup()); - EXPECT_TRUE(ft.execute(0x10003,0, 1)); - EXPECT_TRUE(ft.execute(0x70003,0, 7)); - + TEST_DO(verifySequence(GlobalSequenceBlueprint::globalSequence(1, 0), GlobalSequenceBlueprint::globalSequence(1,1))); + TEST_DO(verifySequence(GlobalSequenceBlueprint::globalSequence(1, 1), GlobalSequenceBlueprint::globalSequence(1,2))); + TEST_DO(verifySequence(GlobalSequenceBlueprint::globalSequence(1, 1), GlobalSequenceBlueprint::globalSequence(2,1))); + TEST_DO(verifySequence(GlobalSequenceBlueprint::globalSequence(2, 1), GlobalSequenceBlueprint::globalSequence(2,2))); + TEST_DO(verifySequence(GlobalSequenceBlueprint::globalSequence(2, 2), GlobalSequenceBlueprint::globalSequence(2,3))); + TEST_DO(verifySequence(GlobalSequenceBlueprint::globalSequence(2, 2), GlobalSequenceBlueprint::globalSequence(3,0))); + ASSERT_EQUAL(0xfffffffefffdul, (1ul << 48) - 0x10003l); + EXPECT_TRUE(ft.execute(0xfffffffefffdul, 0, 1)); + EXPECT_TRUE(ft.execute(0xfffffff8fffdul, 0, 7)); } void diff --git a/searchlib/src/tests/index/field_length_calculator/CMakeLists.txt b/searchlib/src/tests/index/field_length_calculator/CMakeLists.txt index df09d0abaa7..e35927662e6 100644 --- a/searchlib/src/tests/index/field_length_calculator/CMakeLists.txt +++ b/searchlib/src/tests/index/field_length_calculator/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_field_length_calculator_test_app TEST SOURCES field_length_calculator_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_field_length_calculator_test_app COMMAND searchlib_field_length_calculator_test_app) diff --git a/searchlib/src/tests/memoryindex/compact_words_store/CMakeLists.txt b/searchlib/src/tests/memoryindex/compact_words_store/CMakeLists.txt index 754ff796690..71e13db15fa 100644 --- a/searchlib/src/tests/memoryindex/compact_words_store/CMakeLists.txt +++ b/searchlib/src/tests/memoryindex/compact_words_store/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_compact_words_store_test_app TEST SOURCES compact_words_store_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_compact_words_store_test_app COMMAND searchlib_compact_words_store_test_app) diff --git a/searchlib/src/tests/memoryindex/datastore/CMakeLists.txt b/searchlib/src/tests/memoryindex/datastore/CMakeLists.txt index be1a193cd3c..770118a65b4 100644 --- a/searchlib/src/tests/memoryindex/datastore/CMakeLists.txt +++ b/searchlib/src/tests/memoryindex/datastore/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_feature_store_test_app TEST SOURCES feature_store_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_feature_store_test_app COMMAND searchlib_feature_store_test_app) vespa_add_executable(searchlib_word_store_test_app TEST @@ -12,6 +13,6 @@ vespa_add_executable(searchlib_word_store_test_app TEST word_store_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_word_store_test_app COMMAND searchlib_word_store_test_app) diff --git a/searchlib/src/tests/memoryindex/document_inverter/CMakeLists.txt b/searchlib/src/tests/memoryindex/document_inverter/CMakeLists.txt index ecf33ee48fd..0831e87252f 100644 --- a/searchlib/src/tests/memoryindex/document_inverter/CMakeLists.txt +++ b/searchlib/src/tests/memoryindex/document_inverter/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_document_inverter_test_app TEST SOURCES document_inverter_test.cpp DEPENDS searchlib_test searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_document_inverter_test_app COMMAND searchlib_document_inverter_test_app) diff --git a/searchlib/src/tests/memoryindex/field_index/CMakeLists.txt b/searchlib/src/tests/memoryindex/field_index/CMakeLists.txt index a09d6baf1a5..2568249c2df 100644 --- a/searchlib/src/tests/memoryindex/field_index/CMakeLists.txt +++ b/searchlib/src/tests/memoryindex/field_index/CMakeLists.txt @@ -1,11 +1,12 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_field_index_test_app TEST SOURCES field_index_test.cpp DEPENDS searchlib searchlib_test - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_field_index_test_app COMMAND searchlib_field_index_test_app) diff --git a/searchlib/src/tests/memoryindex/field_index_remover/CMakeLists.txt b/searchlib/src/tests/memoryindex/field_index_remover/CMakeLists.txt index f18b4ba29cd..bc3a62c4f26 100644 --- a/searchlib/src/tests/memoryindex/field_index_remover/CMakeLists.txt +++ b/searchlib/src/tests/memoryindex/field_index_remover/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_field_index_remover_test_app TEST SOURCES field_index_remover_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_field_index_remover_test_app COMMAND searchlib_field_index_remover_test_app) diff --git a/searchlib/src/tests/memoryindex/field_inverter/CMakeLists.txt b/searchlib/src/tests/memoryindex/field_inverter/CMakeLists.txt index 6fefada6570..b670e0ccf57 100644 --- a/searchlib/src/tests/memoryindex/field_inverter/CMakeLists.txt +++ b/searchlib/src/tests/memoryindex/field_inverter/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_field_inverter_test_app TEST SOURCES field_inverter_test.cpp DEPENDS searchlib_test searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_field_inverter_test_app COMMAND searchlib_field_inverter_test_app) diff --git a/searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt b/searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt index 0dd42eacf30..611aa6e58c6 100644 --- a/searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt +++ b/searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_memory_index_test_app TEST SOURCES memory_index_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_memory_index_test_app COMMAND searchlib_memory_index_test_app) diff --git a/searchlib/src/tests/memoryindex/url_field_inverter/CMakeLists.txt b/searchlib/src/tests/memoryindex/url_field_inverter/CMakeLists.txt index db9418b7190..0378d209d5e 100644 --- a/searchlib/src/tests/memoryindex/url_field_inverter/CMakeLists.txt +++ b/searchlib/src/tests/memoryindex/url_field_inverter/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_url_field_inverter_test_app TEST SOURCES url_field_inverter_test.cpp DEPENDS searchlib_test searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_url_field_inverter_test_app COMMAND searchlib_url_field_inverter_test_app) diff --git a/searchlib/src/tests/postinglistbm/CMakeLists.txt b/searchlib/src/tests/postinglistbm/CMakeLists.txt index 6e90f44726a..004cff76efd 100644 --- a/searchlib/src/tests/postinglistbm/CMakeLists.txt +++ b/searchlib/src/tests/postinglistbm/CMakeLists.txt @@ -1,11 +1,12 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_posting_list_test_app TEST SOURCES posting_list_test.cpp DEPENDS searchlib_test searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_posting_list_test_app NO_VALGRIND COMMAND searchlib_posting_list_test_app) diff --git a/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/.cvsignore b/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/.cvsignore deleted file mode 100644 index 9e6565f9d16..00000000000 --- a/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/.cvsignore +++ /dev/null @@ -1,3 +0,0 @@ -.depend -Makefile -booleanmatchiteratorwrapper_test diff --git a/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/.gitignore b/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/.gitignore deleted file mode 100644 index b568b87514a..00000000000 --- a/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.depend -Makefile -booleanmatchiteratorwrapper_test -searchlib_booleanmatchiteratorwrapper_test_app diff --git a/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/CMakeLists.txt b/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/CMakeLists.txt deleted file mode 100644 index d97fe5b12d7..00000000000 --- a/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_executable(searchlib_booleanmatchiteratorwrapper_test_app TEST - SOURCES - booleanmatchiteratorwrapper_test.cpp - DEPENDS - searchlib - searchlib_test -) -vespa_add_test(NAME searchlib_booleanmatchiteratorwrapper_test_app COMMAND searchlib_booleanmatchiteratorwrapper_test_app) diff --git a/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/booleanmatchiteratorwrapper_test.cpp b/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/booleanmatchiteratorwrapper_test.cpp deleted file mode 100644 index 7d4551a5c7d..00000000000 --- a/searchlib/src/tests/queryeval/booleanmatchiteratorwrapper/booleanmatchiteratorwrapper_test.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include <vespa/vespalib/testkit/testapp.h> -#include <vespa/searchlib/queryeval/booleanmatchiteratorwrapper.h> -#include <vespa/searchlib/fef/termfieldmatchdata.h> -#include <vespa/searchlib/common/bitvectoriterator.h> -#include <vespa/searchlib/test/searchiteratorverifier.h> - -using namespace search::fef; -using namespace search::queryeval; -using search::BitVector; -using search::BitVectorIterator; - -struct DummyItr : public SearchIterator { - static uint32_t seekCnt; - static uint32_t unpackCnt; - static uint32_t dtorCnt; - static uint32_t _unpackedDocId; - TermFieldMatchData *match; - - DummyItr(TermFieldMatchData *m) { - match = m; - } - - ~DummyItr() { - ++dtorCnt; - } - - void doSeek(uint32_t docid) override { - ++seekCnt; - if (docid <= 10) { - setDocId(10); - } else if (docid <= 20) { - setDocId(20); - } else { - setAtEnd(); - } - } - - void doUnpack(uint32_t docid) override { - ++unpackCnt; - if (match != 0) { - _unpackedDocId = docid; - } - } -}; -uint32_t DummyItr::seekCnt = 0; -uint32_t DummyItr::unpackCnt = 0; -uint32_t DummyItr::dtorCnt = 0; -uint32_t DummyItr::_unpackedDocId = 0; - - -TEST("mostly everything") { - EXPECT_EQUAL(DummyItr::seekCnt, 0u); - EXPECT_EQUAL(DummyItr::unpackCnt, 0u); - EXPECT_EQUAL(DummyItr::dtorCnt, 0u); - { // without wrapper - TermFieldMatchData match; - DummyItr::_unpackedDocId = 0; - SearchIterator::UP search(new DummyItr(&match)); - search->initFullRange(); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 0u); - EXPECT_TRUE(!search->seek(1u)); - EXPECT_EQUAL(search->getDocId(), 10u); - EXPECT_TRUE(search->seek(10)); - search->unpack(10); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 10u); - EXPECT_TRUE(!search->seek(15)); - EXPECT_EQUAL(search->getDocId(), 20u); - EXPECT_TRUE(search->seek(20)); - search->unpack(20); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 20u); - EXPECT_TRUE(!search->seek(25)); - EXPECT_TRUE(search->isAtEnd()); - } - EXPECT_EQUAL(DummyItr::seekCnt, 3u); - EXPECT_EQUAL(DummyItr::unpackCnt, 2u); - EXPECT_EQUAL(DummyItr::dtorCnt, 1u); - { // with wrapper - TermFieldMatchData match; - TermFieldMatchDataArray tfmda; - tfmda.add(&match); - DummyItr::_unpackedDocId = 0; - SearchIterator::UP search(new BooleanMatchIteratorWrapper(SearchIterator::UP(new DummyItr(&match)), tfmda)); - search->initFullRange(); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 0u); - EXPECT_TRUE(!search->seek(1u)); - EXPECT_EQUAL(search->getDocId(), 10u); - EXPECT_TRUE(search->seek(10)); - search->unpack(10); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 0u); - EXPECT_TRUE(!search->seek(15)); - EXPECT_EQUAL(search->getDocId(), 20u); - EXPECT_TRUE(search->seek(20)); - search->unpack(20); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 0u); - EXPECT_TRUE(!search->seek(25)); - EXPECT_TRUE(search->isAtEnd()); - } - EXPECT_EQUAL(DummyItr::seekCnt, 6u); - EXPECT_EQUAL(DummyItr::unpackCnt, 2u); - EXPECT_EQUAL(DummyItr::dtorCnt, 2u); - { // with wrapper, without match data - SearchIterator::UP search(new BooleanMatchIteratorWrapper(SearchIterator::UP(new DummyItr(0)), TermFieldMatchDataArray())); - search->initFullRange(); - EXPECT_TRUE(!search->seek(1u)); - EXPECT_EQUAL(search->getDocId(), 10u); - EXPECT_TRUE(search->seek(10)); - search->unpack(10); - EXPECT_TRUE(!search->seek(15)); - EXPECT_EQUAL(search->getDocId(), 20u); - EXPECT_TRUE(search->seek(20)); - search->unpack(20); - EXPECT_TRUE(!search->seek(25)); - EXPECT_TRUE(search->isAtEnd()); - } - EXPECT_EQUAL(DummyItr::seekCnt, 9u); - EXPECT_EQUAL(DummyItr::unpackCnt, 2u); - EXPECT_EQUAL(DummyItr::dtorCnt, 3u); -} - -class Verifier : public search::test::SearchIteratorVerifier { -public: - ~Verifier(); - SearchIterator::UP create(bool strict) const override { - return std::make_unique<BooleanMatchIteratorWrapper>(createIterator(getExpectedDocIds(), strict), _tfmda);; - } -private: - mutable TermFieldMatchDataArray _tfmda; -}; - -Verifier::~Verifier() {} - -TEST("Test that boolean wrapper iterators adheres to SearchIterator requirements") { - Verifier searchIteratorVerifier; - searchIteratorVerifier.verify(); -} - -TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchlib/src/tests/queryeval/fake_searchable/CMakeLists.txt b/searchlib/src/tests/queryeval/fake_searchable/CMakeLists.txt index c98306aa179..d21524d158a 100644 --- a/searchlib/src/tests/queryeval/fake_searchable/CMakeLists.txt +++ b/searchlib/src/tests/queryeval/fake_searchable/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_fake_searchable_test_app TEST SOURCES fake_searchable_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_fake_searchable_test_app COMMAND searchlib_fake_searchable_test_app) diff --git a/searchlib/src/tests/queryeval/filter_wrapper/CMakeLists.txt b/searchlib/src/tests/queryeval/filter_wrapper/CMakeLists.txt deleted file mode 100644 index bab956e73bb..00000000000 --- a/searchlib/src/tests/queryeval/filter_wrapper/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -vespa_add_executable(searchlib_filter_wrapper_test_app TEST - SOURCES - filter_wrapper_test.cpp - DEPENDS - searchlib - searchlib_test -) -vespa_add_test(NAME searchlib_filter_wrapper_test_app COMMAND searchlib_filter_wrapper_test_app) diff --git a/searchlib/src/tests/queryeval/filter_wrapper/filter_wrapper_test.cpp b/searchlib/src/tests/queryeval/filter_wrapper/filter_wrapper_test.cpp deleted file mode 100644 index 707f51c551a..00000000000 --- a/searchlib/src/tests/queryeval/filter_wrapper/filter_wrapper_test.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include <vespa/vespalib/testkit/testapp.h> -#include <vespa/searchlib/queryeval/filter_wrapper.h> -#include <vespa/searchlib/fef/termfieldmatchdata.h> -#include <vespa/searchlib/common/bitvectoriterator.h> -#include <vespa/searchlib/test/searchiteratorverifier.h> - -using namespace search::fef; -using namespace search::queryeval; -using search::BitVector; -using search::BitVectorIterator; - -struct DummyItr : public SearchIterator { - static uint32_t seekCnt; - static uint32_t unpackCnt; - static uint32_t dtorCnt; - static uint32_t _unpackedDocId; - TermFieldMatchData *match; - - DummyItr(TermFieldMatchData *m) { - match = m; - } - - ~DummyItr() { - ++dtorCnt; - } - - void doSeek(uint32_t docid) override { - ++seekCnt; - if (docid <= 10) { - setDocId(10); - } else if (docid <= 20) { - setDocId(20); - } else { - setAtEnd(); - } - } - - void doUnpack(uint32_t docid) override { - ++unpackCnt; - if (match != 0) { - _unpackedDocId = docid; - } - } -}; -uint32_t DummyItr::seekCnt = 0; -uint32_t DummyItr::unpackCnt = 0; -uint32_t DummyItr::dtorCnt = 0; -uint32_t DummyItr::_unpackedDocId = 0; - - -TEST("filter wrapper forwards as expected") { - EXPECT_EQUAL(DummyItr::seekCnt, 0u); - EXPECT_EQUAL(DummyItr::unpackCnt, 0u); - EXPECT_EQUAL(DummyItr::dtorCnt, 0u); - { // without wrapper - TermFieldMatchData match; - DummyItr::_unpackedDocId = 0; - SearchIterator::UP search(new DummyItr(&match)); - search->initFullRange(); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 0u); - EXPECT_TRUE(!search->seek(1u)); - EXPECT_EQUAL(search->getDocId(), 10u); - EXPECT_TRUE(search->seek(10)); - search->unpack(10); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 10u); - EXPECT_TRUE(!search->seek(15)); - EXPECT_EQUAL(search->getDocId(), 20u); - EXPECT_TRUE(search->seek(20)); - search->unpack(20); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 20u); - EXPECT_TRUE(!search->seek(25)); - EXPECT_TRUE(search->isAtEnd()); - } - EXPECT_EQUAL(DummyItr::seekCnt, 3u); - EXPECT_EQUAL(DummyItr::unpackCnt, 2u); - EXPECT_EQUAL(DummyItr::dtorCnt, 1u); - { // with wrapper - TermFieldMatchData match; - TermFieldMatchDataArray tfmda; - tfmda.add(&match); - DummyItr::_unpackedDocId = 0; - auto search = std::make_unique<FilterWrapper>(1); - auto to_wrap = std::make_unique<DummyItr>(search->tfmda()[0]); - search->wrap(std::move(to_wrap)); - search->initFullRange(); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 0u); - EXPECT_TRUE(!search->seek(1u)); - EXPECT_EQUAL(search->getDocId(), 10u); - EXPECT_TRUE(search->seek(10)); - search->unpack(10); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 0u); - EXPECT_TRUE(!search->seek(15)); - EXPECT_EQUAL(search->getDocId(), 20u); - EXPECT_TRUE(search->seek(20)); - search->unpack(20); - EXPECT_EQUAL(DummyItr::_unpackedDocId, 0u); - EXPECT_TRUE(!search->seek(25)); - EXPECT_TRUE(search->isAtEnd()); - } - EXPECT_EQUAL(DummyItr::seekCnt, 6u); - EXPECT_EQUAL(DummyItr::unpackCnt, 2u); - EXPECT_EQUAL(DummyItr::dtorCnt, 2u); -} - -class Verifier : public search::test::SearchIteratorVerifier { -public: - ~Verifier(); - SearchIterator::UP create(bool strict) const override { - auto search = std::make_unique<FilterWrapper>(1); - search->wrap(createIterator(getExpectedDocIds(), strict)); - return search; - } -}; - -Verifier::~Verifier() {} - -TEST("Test that filter wrapper iterators adheres to SearchIterator requirements") { - Verifier searchIteratorVerifier; - searchIteratorVerifier.verify(); -} - -TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchlib/src/tests/queryeval/wrappers/CMakeLists.txt b/searchlib/src/tests/queryeval/wrappers/CMakeLists.txt new file mode 100644 index 00000000000..d1d566b1cab --- /dev/null +++ b/searchlib/src/tests/queryeval/wrappers/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +vespa_add_executable(searchlib_wrappers_test_app TEST + SOURCES + wrappers_test.cpp + DEPENDS + searchlib + searchlib_test + gtest +) +vespa_add_test(NAME searchlib_wrappers_test_app COMMAND searchlib_wrappers_test_app) diff --git a/searchlib/src/tests/queryeval/wrappers/wrappers_test.cpp b/searchlib/src/tests/queryeval/wrappers/wrappers_test.cpp new file mode 100644 index 00000000000..62bff93ab0e --- /dev/null +++ b/searchlib/src/tests/queryeval/wrappers/wrappers_test.cpp @@ -0,0 +1,197 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/searchlib/queryeval/filter_wrapper.h> +#include <vespa/searchlib/queryeval/booleanmatchiteratorwrapper.h> +#include <vespa/searchlib/fef/termfieldmatchdata.h> +#include <vespa/vespalib/gtest/gtest.h> + +#define ENABLE_GTEST_MIGRATION +#include <vespa/searchlib/test/searchiteratorverifier.h> + +using namespace search::fef; +using namespace search::queryeval; + +struct ObservedData { + uint32_t seekCnt; + uint32_t unpackCnt; + uint32_t dtorCnt; + uint32_t unpackedDocId; +}; + +class WrapperTest : public ::testing::Test { +public: + class DummyItr : public SearchIterator { + private: + ObservedData &_data; + TermFieldMatchData *_match; + public: + DummyItr(ObservedData &data, TermFieldMatchData *m) : _data(data), _match(m) {} + ~DummyItr() { + ++_data.dtorCnt; + } + void doSeek(uint32_t docid) override { + ++_data.seekCnt; + if (docid <= 10) { + setDocId(10); + } else if (docid <= 20) { + setDocId(20); + } else { + setAtEnd(); + } + } + void doUnpack(uint32_t docid) override { + ++_data.unpackCnt; + if (_match != 0) { + _data.unpackedDocId = docid; + } + } + }; + WrapperTest() : _data{0,0,0,0} {} +protected: + ObservedData _data; + + void verify_unwrapped() { + EXPECT_EQ(_data.seekCnt, 0u); + EXPECT_EQ(_data.unpackCnt, 0u); + EXPECT_EQ(_data.dtorCnt, 0u); + + // without wrapper + TermFieldMatchData match; + _data.unpackedDocId = 0; + auto search = std::make_unique<DummyItr>(_data, &match); + search->initFullRange(); + EXPECT_EQ(_data.unpackedDocId, 0u); + EXPECT_TRUE(!search->seek(1u)); + EXPECT_EQ(search->getDocId(), 10u); + EXPECT_TRUE(search->seek(10)); + search->unpack(10); + EXPECT_EQ(_data.unpackedDocId, 10u); + EXPECT_TRUE(!search->seek(15)); + EXPECT_EQ(search->getDocId(), 20u); + EXPECT_TRUE(search->seek(20)); + search->unpack(20); + EXPECT_EQ(_data.unpackedDocId, 20u); + EXPECT_TRUE(!search->seek(25)); + EXPECT_TRUE(search->isAtEnd()); + + search.reset(nullptr); + EXPECT_EQ(_data.seekCnt, 3u); + EXPECT_EQ(_data.unpackCnt, 2u); + EXPECT_EQ(_data.dtorCnt, 1u); + } +}; + +TEST_F(WrapperTest, filter_wrapper) +{ + verify_unwrapped(); + + // with FilterWrapper + TermFieldMatchData match; + TermFieldMatchDataArray tfmda; + tfmda.add(&match); + _data.unpackedDocId = 0; + auto search = std::make_unique<FilterWrapper>(1); +search->wrap(std::make_unique<DummyItr>(_data, search->tfmda()[0])); + search->initFullRange(); + EXPECT_EQ(_data.unpackedDocId, 0u); + EXPECT_TRUE(!search->seek(1u)); + EXPECT_EQ(search->getDocId(), 10u); + EXPECT_TRUE(search->seek(10)); + search->unpack(10); + EXPECT_EQ(_data.unpackedDocId, 0u); + EXPECT_TRUE(!search->seek(15)); + EXPECT_EQ(search->getDocId(), 20u); + EXPECT_TRUE(search->seek(20)); + search->unpack(20); + EXPECT_EQ(_data.unpackedDocId, 0u); + EXPECT_TRUE(!search->seek(25)); + EXPECT_TRUE(search->isAtEnd()); + + search.reset(nullptr); + EXPECT_EQ(_data.seekCnt, 6u); + EXPECT_EQ(_data.unpackCnt, 2u); + EXPECT_EQ(_data.dtorCnt, 2u); +} + +TEST_F(WrapperTest, boolean_match_iterator_wrapper) +{ + verify_unwrapped(); + { // with wrapper + TermFieldMatchData match; + TermFieldMatchDataArray tfmda; + tfmda.add(&match); + _data.unpackedDocId = 0; + auto to_wrap = std::make_unique<DummyItr>(_data, &match); + auto search = std::make_unique<BooleanMatchIteratorWrapper>(std::move(to_wrap), tfmda); + search->initFullRange(); + EXPECT_EQ(_data.unpackedDocId, 0u); + EXPECT_TRUE(!search->seek(1u)); + EXPECT_EQ(search->getDocId(), 10u); + EXPECT_TRUE(search->seek(10)); + search->unpack(10); + EXPECT_EQ(_data.unpackedDocId, 0u); + EXPECT_TRUE(!search->seek(15)); + EXPECT_EQ(search->getDocId(), 20u); + EXPECT_TRUE(search->seek(20)); + search->unpack(20); + EXPECT_EQ(_data.unpackedDocId, 0u); + EXPECT_TRUE(!search->seek(25)); + EXPECT_TRUE(search->isAtEnd()); + } + EXPECT_EQ(_data.seekCnt, 6u); + EXPECT_EQ(_data.unpackCnt, 2u); + EXPECT_EQ(_data.dtorCnt, 2u); + { // with wrapper, without match data + + auto to_wrap = std::make_unique<DummyItr>(_data, nullptr); + auto search = std::make_unique<BooleanMatchIteratorWrapper>(std::move(to_wrap), TermFieldMatchDataArray()); + search->initFullRange(); + EXPECT_TRUE(!search->seek(1u)); + EXPECT_EQ(search->getDocId(), 10u); + EXPECT_TRUE(search->seek(10)); + search->unpack(10); + EXPECT_TRUE(!search->seek(15)); + EXPECT_EQ(search->getDocId(), 20u); + EXPECT_TRUE(search->seek(20)); + search->unpack(20); + EXPECT_TRUE(!search->seek(25)); + EXPECT_TRUE(search->isAtEnd()); + } + EXPECT_EQ(_data.seekCnt, 9u); + EXPECT_EQ(_data.unpackCnt, 2u); + EXPECT_EQ(_data.dtorCnt, 3u); +} + +class FilterWrapperVerifier : public search::test::SearchIteratorVerifier { +public: + ~FilterWrapperVerifier() {} + SearchIterator::UP create(bool strict) const override { + auto search = std::make_unique<FilterWrapper>(1); + search->wrap(createIterator(getExpectedDocIds(), strict)); + return search; + } +}; + +TEST(FilterWrapperTest, adheres_to_search_iterator_requirements) +{ + FilterWrapperVerifier verifier; + verifier.verify(); +} + +class BooleanMatchIteratorWrapperVerifier : public search::test::SearchIteratorVerifier { +public: + SearchIterator::UP create(bool strict) const override { + return std::make_unique<BooleanMatchIteratorWrapper>(createIterator(getExpectedDocIds(), strict), _tfmda); + } + ~BooleanMatchIteratorWrapperVerifier() {} +private: + mutable TermFieldMatchDataArray _tfmda; +}; + +TEST(BooleanMatchIteratorWrapperWrapperTest, adheres_to_search_iterator_requirements) +{ + BooleanMatchIteratorWrapperVerifier verifier; + verifier.verify(); +} + +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/searchlib/src/tests/tensor/distance_functions/CMakeLists.txt b/searchlib/src/tests/tensor/distance_functions/CMakeLists.txt index 9356658f90e..6f7c71d4d54 100644 --- a/searchlib/src/tests/tensor/distance_functions/CMakeLists.txt +++ b/searchlib/src/tests/tensor/distance_functions/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_distance_functions_test_app TEST SOURCES distance_functions_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_distance_functions_test_app COMMAND searchlib_distance_functions_test_app) diff --git a/searchlib/src/tests/tensor/hnsw_index/CMakeLists.txt b/searchlib/src/tests/tensor/hnsw_index/CMakeLists.txt index b6a87502fdf..0fd2f9a205a 100644 --- a/searchlib/src/tests/tensor/hnsw_index/CMakeLists.txt +++ b/searchlib/src/tests/tensor/hnsw_index/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_hnsw_index_test_app TEST SOURCES hnsw_index_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_hnsw_index_test_app COMMAND searchlib_hnsw_index_test_app) @@ -13,5 +14,5 @@ vespa_add_executable(mt_stress_hnsw_app TEST stress_hnsw_mt.cpp DEPENDS searchlib - gtest + GTest::GTest ) diff --git a/searchlib/src/tests/tensor/hnsw_saver/CMakeLists.txt b/searchlib/src/tests/tensor/hnsw_saver/CMakeLists.txt index 90202e222a7..0dbd80c68d0 100644 --- a/searchlib/src/tests/tensor/hnsw_saver/CMakeLists.txt +++ b/searchlib/src/tests/tensor/hnsw_saver/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(searchlib_hnsw_save_load_test_app TEST SOURCES hnsw_save_load_test.cpp DEPENDS searchlib - gtest + GTest::GTest ) vespa_add_test(NAME searchlib_hnsw_save_load_test_app COMMAND searchlib_hnsw_save_load_test_app) diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp index c4467df5a1c..4a34a07a773 100644 --- a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp +++ b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp @@ -416,9 +416,25 @@ public: .setDocIdLimit(get_docid_limit()), _weights, _terms, _attr, strict); } + std::unique_ptr<SearchIterator> createFilterSearch(bool strict, FilterConstraint constraint) const override; bool always_needs_unpack() const override { return true; } }; +std::unique_ptr<SearchIterator> +DirectWandBlueprint::createFilterSearch(bool, FilterConstraint constraint) const +{ + if (constraint == Blueprint::FilterConstraint::UPPER_BOUND) { + std::vector<DocumentWeightIterator> iterators; + iterators.reserve(_terms.size()); + for (const IDocumentWeightAttribute::LookupResult &r : _terms) { + _attr.create(r.posting_idx, iterators); + } + return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators)); + } else { + return std::make_unique<queryeval::EmptySearch>(); + } +} + //----------------------------------------------------------------------------- class DirectAttributeBlueprint : public queryeval::SimpleLeafBlueprint diff --git a/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp b/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp index 35c5ce75f33..d9134417d27 100644 --- a/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp +++ b/searchlib/src/vespa/searchlib/attribute/document_weight_or_filter_search.cpp @@ -53,7 +53,7 @@ DocumentWeightOrFilterSearchImpl::doSeek(uint32_t docId) _children.seek(0, docId); } uint32_t min_doc_id = _children.get_docid(0); - for (uint16_t i = 1; min_doc_id > docId && i < _children.size(); ++i) { + for (uint16_t i = 1; i < _children.size(); ++i) { if (_children.get_docid(i) < docId) { _children.seek(i, docId); } diff --git a/searchlib/src/vespa/searchlib/features/CMakeLists.txt b/searchlib/src/vespa/searchlib/features/CMakeLists.txt index a3ce67c4bf6..215b6ade9fd 100644 --- a/searchlib/src/vespa/searchlib/features/CMakeLists.txt +++ b/searchlib/src/vespa/searchlib/features/CMakeLists.txt @@ -26,6 +26,7 @@ vespa_add_library(searchlib_features OBJECT flow_completeness_feature.cpp foreachfeature.cpp freshnessfeature.cpp + global_sequence_feature.cpp internal_max_reduce_prod_join_feature.cpp item_raw_score_feature.cpp jarowinklerdistancefeature.cpp @@ -63,7 +64,6 @@ vespa_add_library(searchlib_features OBJECT termfeature.cpp terminfofeature.cpp text_similarity_feature.cpp - uniquefeature.cpp utils.cpp valuefeature.cpp weighted_set_parser.cpp diff --git a/searchlib/src/vespa/searchlib/features/global_sequence_feature.cpp b/searchlib/src/vespa/searchlib/features/global_sequence_feature.cpp new file mode 100644 index 00000000000..255b033a592 --- /dev/null +++ b/searchlib/src/vespa/searchlib/features/global_sequence_feature.cpp @@ -0,0 +1,64 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "global_sequence_feature.h" +#include <vespa/vespalib/util/stash.h> + +using namespace search::fef; + +namespace search::features { + +namespace { + +/** + * Implements the executor for combining lid and distribution key to form a globally unique value. + */ +class GlobalSequenceExecutor : public fef::FeatureExecutor { +private: + uint32_t _distributionKey; + +public: + GlobalSequenceExecutor(uint32_t distributionKey) + : _distributionKey(distributionKey) + { + } + + void execute(uint32_t docId) override { + outputs().set_number(0, GlobalSequenceBlueprint::globalSequence(docId, _distributionKey)); + } +}; + +} + +GlobalSequenceBlueprint::GlobalSequenceBlueprint() : + Blueprint("globalSequence"), + _distributionKey(0) +{ +} + +void +GlobalSequenceBlueprint::visitDumpFeatures(const IIndexEnvironment &, IDumpFeatureVisitor &) const +{ +} + +bool +GlobalSequenceBlueprint::setup(const IIndexEnvironment & env, const ParameterList & ) +{ + _distributionKey = env.getDistributionKey(); + assert( _distributionKey < 0x10000); + describeOutput("out", "Returns (1 << 48) - ((lid << 16) | distributionKey)"); + return true; +} + +Blueprint::UP +GlobalSequenceBlueprint::createInstance() const +{ + return std::make_unique<GlobalSequenceBlueprint>(); +} + +FeatureExecutor & +GlobalSequenceBlueprint::createExecutor(const IQueryEnvironment &, vespalib::Stash &stash) const +{ + return stash.create<GlobalSequenceExecutor>(_distributionKey); +} + +} diff --git a/searchlib/src/vespa/searchlib/features/uniquefeature.h b/searchlib/src/vespa/searchlib/features/global_sequence_feature.h index f21a427762a..3678a260b00 100644 --- a/searchlib/src/vespa/searchlib/features/uniquefeature.h +++ b/searchlib/src/vespa/searchlib/features/global_sequence_feature.h @@ -14,12 +14,12 @@ namespace search::features { * It will change if documents change lid. */ -class UniqueBlueprint : public fef::Blueprint +class GlobalSequenceBlueprint : public fef::Blueprint { private: uint32_t _distributionKey; public: - UniqueBlueprint(); + GlobalSequenceBlueprint(); void visitDumpFeatures(const fef::IIndexEnvironment & env, fef::IDumpFeatureVisitor & visitor) const override; fef::Blueprint::UP createInstance() const override; fef::ParameterDescriptions getDescriptions() const override { @@ -27,6 +27,10 @@ public: } bool setup(const fef::IIndexEnvironment & env, const fef::ParameterList & params) override; fef::FeatureExecutor &createExecutor(const fef::IQueryEnvironment &env, vespalib::Stash &stash) const override; + + static uint64_t globalSequence(uint32_t docId, uint32_t distrKey) { + return (1ul << 48) - ((uint64_t(docId) << 16)| distrKey); + } }; } diff --git a/searchlib/src/vespa/searchlib/features/setup.cpp b/searchlib/src/vespa/searchlib/features/setup.cpp index ea6ec842a00..bd79f1d4fb5 100644 --- a/searchlib/src/vespa/searchlib/features/setup.cpp +++ b/searchlib/src/vespa/searchlib/features/setup.cpp @@ -53,7 +53,7 @@ #include "termfeature.h" #include "terminfofeature.h" #include "text_similarity_feature.h" -#include "uniquefeature.h" +#include "global_sequence_feature.h" #include "valuefeature.h" #include "max_reduce_prod_join_replacer.h" @@ -122,7 +122,7 @@ void setup_search_features(fef::IBlueprintRegistry & registry) registry.addPrototype(std::make_shared<TermEditDistanceBlueprint>()); registry.addPrototype(std::make_shared<TermFieldMdBlueprint>()); registry.addPrototype(std::make_shared<ConstantBlueprint>()); - registry.addPrototype(std::make_shared<UniqueBlueprint>()); + registry.addPrototype(std::make_shared<GlobalSequenceBlueprint>()); // Ranking Expression diff --git a/searchlib/src/vespa/searchlib/features/uniquefeature.cpp b/searchlib/src/vespa/searchlib/features/uniquefeature.cpp deleted file mode 100644 index 73ac4a1178e..00000000000 --- a/searchlib/src/vespa/searchlib/features/uniquefeature.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "uniquefeature.h" -#include <vespa/vespalib/util/stash.h> - -using namespace search::fef; - -namespace search::features { - -namespace { - -/** - * Implements the executor for combining lid and distribution key to form a globally unique value. - */ -class UniqueLidAndDistributionKeyExecutor : public fef::FeatureExecutor { -private: - uint32_t _distributionKey; - -public: - UniqueLidAndDistributionKeyExecutor(uint32_t distributionKey) - : _distributionKey(distributionKey) - { - assert( _distributionKey < 0x10000); - } - - void execute(uint32_t docId) override { - outputs().set_number(0, (uint64_t(docId) << 16u) | _distributionKey); - } -}; - -} - -UniqueBlueprint::UniqueBlueprint() : - Blueprint("unique"), - _distributionKey(0) -{ -} - -void -UniqueBlueprint::visitDumpFeatures(const IIndexEnvironment &, IDumpFeatureVisitor &) const -{ -} - -bool -UniqueBlueprint::setup(const IIndexEnvironment & env, const ParameterList & ) -{ - _distributionKey = env.getDistributionKey(); - describeOutput("out", "Returns (lid << 16) | distributionKey"); - return true; -} - -Blueprint::UP -UniqueBlueprint::createInstance() const -{ - return std::make_unique<UniqueBlueprint>(); -} - -FeatureExecutor & -UniqueBlueprint::createExecutor(const IQueryEnvironment &, vespalib::Stash &stash) const -{ - return stash.create<UniqueLidAndDistributionKeyExecutor>(_distributionKey); -} - -} diff --git a/searchlib/src/vespa/searchlib/fef/indexproperties.cpp b/searchlib/src/vespa/searchlib/fef/indexproperties.cpp index fb44b986301..622e437692a 100644 --- a/searchlib/src/vespa/searchlib/fef/indexproperties.cpp +++ b/searchlib/src/vespa/searchlib/fef/indexproperties.cpp @@ -290,6 +290,22 @@ NearestNeighborBruteForceLimit::lookup(const Properties &props, double defaultVa return lookupDouble(props, NAME, defaultValue); } +const vespalib::string GlobalFilterLimit::NAME("vespa.matching.global_filter_limit"); + +const double GlobalFilterLimit::DEFAULT_VALUE(0.0); + +double +GlobalFilterLimit::lookup(const Properties &props) +{ + return lookup(props, DEFAULT_VALUE); +} + +double +GlobalFilterLimit::lookup(const Properties &props, double defaultValue) +{ + return lookupDouble(props, NAME, defaultValue); +} + } // namespace matching namespace softtimeout { diff --git a/searchlib/src/vespa/searchlib/fef/indexproperties.h b/searchlib/src/vespa/searchlib/fef/indexproperties.h index 30c726caeba..1b4c2e92d8d 100644 --- a/searchlib/src/vespa/searchlib/fef/indexproperties.h +++ b/searchlib/src/vespa/searchlib/fef/indexproperties.h @@ -218,6 +218,19 @@ namespace matching { static double lookup(const Properties &props); static double lookup(const Properties &props, double defaultValue); }; + + /** + * Property to control fallback to not building a global filter + * for a query with a blueprint that wants a global filter. If the + * estimated ratio of matching documents is less than this limit + * then don't build a global filter. + **/ + struct GlobalFilterLimit { + static const vespalib::string NAME; + static const double DEFAULT_VALUE; + static double lookup(const Properties &props); + static double lookup(const Properties &props, double defaultValue); + }; } namespace softtimeout { diff --git a/searchlib/src/vespa/searchlib/fef/ranksetup.cpp b/searchlib/src/vespa/searchlib/fef/ranksetup.cpp index e197f095852..249351a4fe5 100644 --- a/searchlib/src/vespa/searchlib/fef/ranksetup.cpp +++ b/searchlib/src/vespa/searchlib/fef/ranksetup.cpp @@ -62,7 +62,8 @@ RankSetup::RankSetup(const BlueprintFactory &factory, const IIndexEnvironment &i _softTimeoutEnabled(false), _softTimeoutTailCost(0.1), _softTimeoutFactor(0.5), - _nearest_neighbor_brute_force_limit(0.05) + _nearest_neighbor_brute_force_limit(0.05), + _global_filter_limit(0.0) { } RankSetup::~RankSetup() = default; @@ -106,6 +107,7 @@ RankSetup::configure() setSoftTimeoutTailCost(softtimeout::TailCost::lookup(_indexEnv.getProperties())); setSoftTimeoutFactor(softtimeout::Factor::lookup(_indexEnv.getProperties())); set_nearest_neighbor_brute_force_limit(matching::NearestNeighborBruteForceLimit::lookup(_indexEnv.getProperties())); + set_global_filter_limit(matching::GlobalFilterLimit::lookup(_indexEnv.getProperties())); } void diff --git a/searchlib/src/vespa/searchlib/fef/ranksetup.h b/searchlib/src/vespa/searchlib/fef/ranksetup.h index ad793eeaceb..3e127a1e8b5 100644 --- a/searchlib/src/vespa/searchlib/fef/ranksetup.h +++ b/searchlib/src/vespa/searchlib/fef/ranksetup.h @@ -60,6 +60,7 @@ private: double _softTimeoutTailCost; double _softTimeoutFactor; double _nearest_neighbor_brute_force_limit; + double _global_filter_limit; public: @@ -369,6 +370,9 @@ public: void set_nearest_neighbor_brute_force_limit(double v) { _nearest_neighbor_brute_force_limit = v; } double get_nearest_neighbor_brute_force_limit() const { return _nearest_neighbor_brute_force_limit; } + void set_global_filter_limit(double v) { _global_filter_limit = v; } + double get_global_filter_limit() const { return _global_filter_limit; } + /** * This method may be used to indicate that certain features * should be dumped during a full feature dump. diff --git a/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp index 454db4e820a..799755042a6 100644 --- a/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.cpp @@ -65,6 +65,12 @@ DotProductBlueprint::createLeafSearch(const search::fef::TermFieldMatchDataArray return DotProductSearch::create(children, *tfmda[0], childMatch, _weights, std::move(md)); } +SearchIterator::UP +DotProductBlueprint::createFilterSearch(bool strict, FilterConstraint constraint) const +{ + return create_or_filter(_terms, strict, constraint); +} + void DotProductBlueprint::fetchPostings(const ExecuteInfo &execInfo) { diff --git a/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.h index 86c8e90300a..a6d33731e9c 100644 --- a/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/dot_product_blueprint.h @@ -32,6 +32,7 @@ public: SearchIteratorUP createLeafSearch(const search::fef::TermFieldMatchDataArray &tfmda, bool strict) const override; + SearchIteratorUP createFilterSearch(bool strict, FilterConstraint constraint) const override; void visitMembers(vespalib::ObjectVisitor &visitor) const override; void fetchPostings(const ExecuteInfo &execInfo) override; diff --git a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp index aa65342c114..3f632cfc377 100644 --- a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp @@ -434,6 +434,16 @@ WeakAndBlueprint::createIntermediateSearch(MultiSearch::Children sub_searches, return WeakAndSearch::create(terms, _n, strict); } +SearchIterator::UP +WeakAndBlueprint::createFilterSearch(bool strict, FilterConstraint constraint) const +{ + if (constraint == Blueprint::FilterConstraint::UPPER_BOUND) { + return create_or_filter(get_children(), strict, constraint); + } else { + return std::make_unique<EmptySearch>(); + } +} + //----------------------------------------------------------------------------- Blueprint::HitEstimate diff --git a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h index 6bbe4562641..bc635952d55 100644 --- a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h +++ b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h @@ -92,6 +92,7 @@ public: SearchIterator::UP createIntermediateSearch(MultiSearch::Children subSearches, bool strict, fef::MatchData &md) const override; + SearchIterator::UP createFilterSearch(bool strict, FilterConstraint constraint) const override; WeakAndBlueprint(uint32_t n) : _n(n) {} ~WeakAndBlueprint(); diff --git a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp index ac4f164b09f..4de52af102b 100644 --- a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.cpp @@ -3,6 +3,8 @@ #include "same_element_blueprint.h" #include "same_element_search.h" #include "field_spec.hpp" +#include "andsearch.h" +#include "emptysearch.h" #include <vespa/searchlib/fef/termfieldmatchdata.h> #include <vespa/searchlib/attribute/searchcontextelementiterator.h> #include <vespa/vespalib/objects/visit.hpp> @@ -88,6 +90,24 @@ SameElementBlueprint::createLeafSearch(const search::fef::TermFieldMatchDataArra return create_same_element_search(strict); } +SearchIterator::UP +SameElementBlueprint::createFilterSearch(bool strict, FilterConstraint constraint) const +{ + if (constraint == Blueprint::FilterConstraint::UPPER_BOUND) { + MultiSearch::Children sub_searches; + sub_searches.reserve(_terms.size()); + for (size_t i = 0; i < _terms.size(); ++i) { + auto search = _terms[i]->createFilterSearch(strict && (i == 0), constraint); + sub_searches.push_back(std::move(search)); + } + UnpackInfo unpack_info; + auto search = AndSearch::create(std::move(sub_searches), strict, unpack_info); + return search; + } else { + return std::make_unique<EmptySearch>(); + } +} + void SameElementBlueprint::visitMembers(vespalib::ObjectVisitor &visitor) const { diff --git a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h index 8d647ac3a32..be04203fc6d 100644 --- a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h @@ -40,6 +40,7 @@ public: std::unique_ptr<SameElementSearch> create_same_element_search(bool strict) const; SearchIteratorUP createLeafSearch(const search::fef::TermFieldMatchDataArray &tfmda, bool strict) const override; + SearchIteratorUP createFilterSearch(bool strict, FilterConstraint constraint) const override; void visitMembers(vespalib::ObjectVisitor &visitor) const override; const std::vector<Blueprint::UP> &terms() const { return _terms; } const vespalib::string &field_name() const { return _field_name; } diff --git a/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp index d4f16e2f91c..4e83c45cbc0 100644 --- a/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.cpp @@ -3,6 +3,7 @@ #include "parallel_weak_and_blueprint.h" #include "parallel_weak_and_search.h" #include <vespa/searchlib/queryeval/field_spec.hpp> +#include <vespa/searchlib/queryeval/emptysearch.h> #include <vespa/searchlib/queryeval/searchiterator.h> #include <vespa/searchlib/fef/termfieldmatchdata.h> #include <vespa/vespalib/objects/visit.hpp> @@ -102,6 +103,16 @@ ParallelWeakAndBlueprint::createLeafSearch(const search::fef::TermFieldMatchData std::move(childrenMatchData)), strict)); } +std::unique_ptr<SearchIterator> +ParallelWeakAndBlueprint::createFilterSearch(bool strict, FilterConstraint constraint) const +{ + if (constraint == Blueprint::FilterConstraint::UPPER_BOUND) { + return create_or_filter(_terms, strict, constraint); + } else { + return std::make_unique<EmptySearch>(); + } +} + void ParallelWeakAndBlueprint::fetchPostings(const ExecuteInfo & execInfo) { diff --git a/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.h index 842067f9849..5b034b6de0a 100644 --- a/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_blueprint.h @@ -59,6 +59,7 @@ public: void addTerm(Blueprint::UP term, int32_t weight); SearchIterator::UP createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool strict) const override; + std::unique_ptr<SearchIterator> createFilterSearch(bool strict, FilterConstraint constraint) const override; void visitMembers(vespalib::ObjectVisitor &visitor) const override; void fetchPostings(const ExecuteInfo &execInfo) override; bool always_needs_unpack() const override; diff --git a/searchlib/src/vespa/searchlib/test/CMakeLists.txt b/searchlib/src/vespa/searchlib/test/CMakeLists.txt index 41084148c87..4d722e687ef 100644 --- a/searchlib/src/vespa/searchlib/test/CMakeLists.txt +++ b/searchlib/src/vespa/searchlib/test/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(searchlib_test SOURCES document_weight_attribute_helper.cpp @@ -12,7 +13,17 @@ vespa_add_library(searchlib_test statestring.cpp $<TARGET_OBJECTS:searchlib_test_fakedata> $<TARGET_OBJECTS:searchlib_searchlib_test_diskindex> + $<TARGET_OBJECTS:searchlib_test_gtest_migration> DEPENDS searchlib searchlib_searchlib_test_memoryindex + GTest::GTest ) + +vespa_add_library(searchlib_test_gtest_migration OBJECT + SOURCES + initrange.cpp + searchiteratorverifier.cpp +) + +target_compile_definitions(searchlib_test_gtest_migration PRIVATE ENABLE_GTEST_MIGRATION) diff --git a/searchlib/src/vespa/searchlib/test/initrange.cpp b/searchlib/src/vespa/searchlib/test/initrange.cpp index 2292a8e775e..1e23f7c5b8c 100644 --- a/searchlib/src/vespa/searchlib/test/initrange.cpp +++ b/searchlib/src/vespa/searchlib/test/initrange.cpp @@ -1,6 +1,12 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "initrange.h" +#ifdef ENABLE_GTEST_MIGRATION +#include <vespa/vespalib/gtest/gtest.h> +#define ASSERT_EQUAL ASSERT_EQ +#define EXPECT_EQUAL EXPECT_EQ +#else #include <vespa/vespalib/testkit/test_kit.h> +#endif #include <vespa/searchlib/queryeval/emptysearch.h> #include <vespa/searchlib/queryeval/truesearch.h> #include <algorithm> diff --git a/searchlib/src/vespa/searchlib/test/initrange.h b/searchlib/src/vespa/searchlib/test/initrange.h index a143dfdb119..4fbb851779a 100644 --- a/searchlib/src/vespa/searchlib/test/initrange.h +++ b/searchlib/src/vespa/searchlib/test/initrange.h @@ -7,6 +7,10 @@ namespace search::test { +#ifdef ENABLE_GTEST_MIGRATION +#define InitRangeVerifier InitRangeVerifierForGTest +#endif + class InitRangeVerifier { public: typedef queryeval::SearchIterator SearchIterator; diff --git a/searchlib/src/vespa/searchlib/test/searchiteratorverifier.cpp b/searchlib/src/vespa/searchlib/test/searchiteratorverifier.cpp index ec53d6d9d00..276f8fbc08d 100644 --- a/searchlib/src/vespa/searchlib/test/searchiteratorverifier.cpp +++ b/searchlib/src/vespa/searchlib/test/searchiteratorverifier.cpp @@ -2,7 +2,14 @@ #include "searchiteratorverifier.h" #include "initrange.h" +#ifdef ENABLE_GTEST_MIGRATION +#include <vespa/vespalib/gtest/gtest.h> +#define TEST_DO(x) x +#define EXPECT_EQUAL EXPECT_EQ +#define ASSERT_EQUAL ASSERT_EQ +#else #include <vespa/vespalib/testkit/test_kit.h> +#endif #include <vespa/searchlib/queryeval/emptysearch.h> #include <vespa/searchlib/queryeval/truesearch.h> #include <vespa/searchlib/queryeval/termwise_search.h> diff --git a/searchlib/src/vespa/searchlib/test/searchiteratorverifier.h b/searchlib/src/vespa/searchlib/test/searchiteratorverifier.h index 3d35731dab1..afeb46f0c16 100644 --- a/searchlib/src/vespa/searchlib/test/searchiteratorverifier.h +++ b/searchlib/src/vespa/searchlib/test/searchiteratorverifier.h @@ -7,6 +7,10 @@ namespace search::test { +#ifdef ENABLE_GTEST_MIGRATION +#define SearchIteratorVerifier SearchIteratorVerifierForGTest +#endif + class SearchIteratorVerifier { public: typedef queryeval::SearchIterator SearchIterator; diff --git a/searchlib/src/vespa/searchlib/uca/CMakeLists.txt b/searchlib/src/vespa/searchlib/uca/CMakeLists.txt index a04add4bfee..c3f469e59ac 100644 --- a/searchlib/src/vespa/searchlib/uca/CMakeLists.txt +++ b/searchlib/src/vespa/searchlib/uca/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(ICU 60.0 REQUIRED COMPONENTS uc i18n) vespa_add_library(searchlib_searchlib_uca SOURCES ucaconverter.cpp @@ -6,6 +7,6 @@ vespa_add_library(searchlib_searchlib_uca INSTALL lib64 DEPENDS searchlib - icui18n - icuuc + ICU::i18n + ICU::uc ) diff --git a/simplemetrics/pom.xml b/simplemetrics/pom.xml index f65aeaabef1..6ca02febefd 100644 --- a/simplemetrics/pom.xml +++ b/simplemetrics/pom.xml @@ -25,6 +25,12 @@ </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> + <artifactId>component</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> <artifactId>vespajlib</artifactId> <version>${project.version}</version> <scope>provided</scope> diff --git a/storage/src/tests/bucketdb/CMakeLists.txt b/storage/src/tests/bucketdb/CMakeLists.txt index 2468e587aff..5dc269e54f9 100644 --- a/storage/src/tests/bucketdb/CMakeLists.txt +++ b/storage/src/tests/bucketdb/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(storage_bucketdb_gtest_runner_app TEST SOURCES bucketinfotest.cpp @@ -12,7 +13,7 @@ vespa_add_executable(storage_bucketdb_gtest_runner_app TEST DEPENDS storage storage_testcommon - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/tests/bucketdb/initializertest.cpp b/storage/src/tests/bucketdb/initializertest.cpp index 63f990f7cc1..7d3d2c185da 100644 --- a/storage/src/tests/bucketdb/initializertest.cpp +++ b/storage/src/tests/bucketdb/initializertest.cpp @@ -139,11 +139,11 @@ typedef std::map<document::BucketId, BucketData> DiskData; struct BucketInfoLogger { std::map<PartitionId, DiskData>& map; - BucketInfoLogger(std::map<PartitionId, DiskData>& m) + explicit BucketInfoLogger(std::map<PartitionId, DiskData>& m) : map(m) {} StorBucketDatabase::Decision operator()( - uint64_t revBucket, StorBucketDatabase::Entry& entry) + uint64_t revBucket, const StorBucketDatabase::Entry& entry) { document::BucketId bucket( document::BucketId::keyToBucketId(revBucket)); @@ -152,14 +152,14 @@ struct BucketInfoLogger { DiskData& ddata(map[entry.disk]); BucketData& bdata(ddata[bucket]); bdata.info = entry.getBucketInfo(); - return StorBucketDatabase::CONTINUE; + return StorBucketDatabase::Decision::CONTINUE; } }; std::map<PartitionId, DiskData> createMapFromBucketDatabase(StorBucketDatabase& db) { std::map<PartitionId, DiskData> result; BucketInfoLogger infoLogger(result); - db.all(infoLogger, "createmap"); + db.for_each(std::ref(infoLogger), "createmap"); return result; } // Create data we want to have in this test @@ -551,8 +551,8 @@ struct DatabaseInsertCallback : MessageCallback BucketData d; StorBucketDatabase::WrappedEntry entry( _database.get(bid, "DatabaseInsertCallback::onMessage", - StorBucketDatabase::LOCK_IF_NONEXISTING_AND_NOT_CREATING)); - if (entry.exist()) { + StorBucketDatabase::CREATE_IF_NONEXISTING)); + if (entry.preExisted()) { _errors << "db entry for " << bid << " already existed"; } if (i < 5) { diff --git a/storage/src/tests/bucketdb/lockablemaptest.cpp b/storage/src/tests/bucketdb/lockablemaptest.cpp index 101b9d014fa..c5d22b6e6b5 100644 --- a/storage/src/tests/bucketdb/lockablemaptest.cpp +++ b/storage/src/tests/bucketdb/lockablemaptest.cpp @@ -4,8 +4,8 @@ #include <vespa/storage/bucketdb/judymultimap.h> #include <vespa/storage/bucketdb/judymultimap.hpp> #include <vespa/storage/bucketdb/lockablemap.hpp> +#include <vespa/storage/bucketdb/btree_lockable_map.hpp> #include <vespa/vespalib/gtest/gtest.h> -#include <boost/operators.hpp> #include <vespa/log/log.h> LOG_SETUP(".lockable_map_test"); @@ -13,41 +13,62 @@ LOG_SETUP(".lockable_map_test"); // FIXME these old tests may have the least obvious semantics and worst naming in the entire storage module using namespace ::testing; +using document::BucketId; namespace storage { namespace { - struct A : public boost::operators<A> { - int _val1; - int _val2; - int _val3; - A() : _val1(0), _val2(0), _val3(0) {} - A(int val1, int val2, int val3) - : _val1(val1), _val2(val2), _val3(val3) {} +struct A { + int _val1; + int _val2; + int _val3; - static bool mayContain(const A&) { return true; } + A() : _val1(0), _val2(0), _val3(0) {} + A(int val1, int val2, int val3) + : _val1(val1), _val2(val2), _val3(val3) {} - bool operator==(const A& a) const { - return (_val1 == a._val1 && _val2 == a._val2 && _val3 == a._val3); - } - bool operator<(const A& a) const { - if (_val1 != a._val1) return (_val1 < a._val1); - if (_val2 != a._val2) return (_val2 < a._val2); - return (_val3 < a._val3); - } - }; + static bool mayContain(const A&) { return true; } + // Make this type smell more like a proper bucket DB value type. + constexpr bool verifyLegal() const noexcept { return true; } + constexpr bool valid() const noexcept { return true; } - std::ostream& operator<<(std::ostream& out, const A& a) { - return out << "A(" << a._val1 << ", " << a._val2 << ", " << a._val3 << ")"; + bool operator==(const A& a) const noexcept { + return (_val1 == a._val1 && _val2 == a._val2 && _val3 == a._val3); + } + bool operator!=(const A& a) const noexcept { + return !(*this == a); } + bool operator<(const A& a) const noexcept { + if (_val1 != a._val1) return (_val1 < a._val1); + if (_val2 != a._val2) return (_val2 < a._val2); + return (_val3 < a._val3); + } +}; + +std::ostream& operator<<(std::ostream& out, const A& a) { + return out << "A(" << a._val1 << ", " << a._val2 << ", " << a._val3 << ")"; +} - typedef LockableMap<JudyMultiMap<A> > Map; } -TEST(LockableMapTest, simple_usage) { +template <typename MapT> +struct LockableMapTest : ::testing::Test { + using Map = MapT; +}; + +using MapTypes = ::testing::Types<LockableMap<JudyMultiMap<A>>, bucketdb::BTreeLockableMap<A>>; +VESPA_GTEST_TYPED_TEST_SUITE(LockableMapTest, MapTypes); + +// Disable warnings emitted by gtest generated files when using typed tests +#pragma GCC diagnostic push +#ifndef __clang__ +#pragma GCC diagnostic ignored "-Wsuggest-override" +#endif + +TYPED_TEST(LockableMapTest, simple_usage) { // Tests insert, erase, size, empty, operator[] - Map map; + TypeParam map; // Do some insertions EXPECT_TRUE(map.empty()); bool preExisted; @@ -57,11 +78,11 @@ TEST(LockableMapTest, simple_usage) { EXPECT_EQ(false, preExisted); map.insert(14, A(42, 0, 0), "foo", preExisted); EXPECT_EQ(false, preExisted); - EXPECT_EQ((Map::size_type) 3, map.size()) << map.toString(); + EXPECT_EQ(map.size(), 3); map.insert(11, A(4, 7, 0), "foo", preExisted); EXPECT_EQ(true, preExisted); - EXPECT_EQ((Map::size_type) 3, map.size()); + EXPECT_EQ(map.size(), 3); EXPECT_FALSE(map.empty()); // Access some elements @@ -71,20 +92,20 @@ TEST(LockableMapTest, simple_usage) { // Do removes EXPECT_EQ(map.erase(12, "foo"), 0); - EXPECT_EQ((Map::size_type) 3, map.size()); + EXPECT_EQ(map.size(), 3); EXPECT_EQ(map.erase(14, "foo"), 1); - EXPECT_EQ((Map::size_type) 2, map.size()); + EXPECT_EQ(map.size(), 2); EXPECT_EQ(map.erase(11, "foo"), 1); EXPECT_EQ(map.erase(16, "foo"), 1); - EXPECT_EQ((Map::size_type) 0, map.size()); + EXPECT_EQ(map.size(), 0); EXPECT_TRUE(map.empty()); } -TEST(LockableMapTest, comparison) { - Map map1; - Map map2; +TYPED_TEST(LockableMapTest, comparison) { + TypeParam map1; + TypeParam map2; bool preExisted; // Check empty state is correct @@ -123,154 +144,194 @@ TEST(LockableMapTest, comparison) { } namespace { - struct NonConstProcessor { - Map::Decision operator()(int key, A& a) { - (void) key; - ++a._val2; - return Map::UPDATE; + +template <typename Map> +struct NonConstProcessor { + typename Map::Decision operator()(int key, A& a) { + (void) key; + ++a._val2; + return Map::UPDATE; + } +}; + +template <typename Map> +struct EntryProcessor { + mutable uint32_t count; + mutable std::vector<std::string> log; + mutable std::vector<typename Map::Decision> behaviour; + + EntryProcessor(); + explicit EntryProcessor(const std::vector<typename Map::Decision>& decisions); + ~EntryProcessor(); + + typename Map::Decision operator()(uint64_t key, A& a) const { + std::ostringstream ost; + ost << key << " - " << a; + log.push_back(ost.str()); + typename Map::Decision d = Map::CONTINUE; + if (behaviour.size() > count) { + d = behaviour[count++]; } - }; - struct EntryProcessor { - mutable uint32_t count; - mutable std::vector<std::string> log; - mutable std::vector<Map::Decision> behaviour; - - EntryProcessor(); - EntryProcessor(const std::vector<Map::Decision>& decisions); - ~EntryProcessor(); - - Map::Decision operator()(uint64_t key, A& a) const { - std::ostringstream ost; - ost << key << " - " << a; - log.push_back(ost.str()); - Map::Decision d = Map::CONTINUE; - if (behaviour.size() > count) { - d = behaviour[count++]; - } - if (d == Map::UPDATE) { - ++a._val3; - } - return d; + if (d == Map::UPDATE) { + ++a._val3; } + return d; + } - std::string toString() { - std::ostringstream ost; - for (uint32_t i=0; i<log.size(); ++i) { - ost << log[i] << "\n"; - } - return ost.str(); + std::string toString() { + std::ostringstream ost; + for (uint32_t i=0; i<log.size(); ++i) { + ost << log[i] << "\n"; } - }; -} + return ost.str(); + } +}; + +template <typename Map> +EntryProcessor<Map>::EntryProcessor() + : count(0), log(), behaviour() {} + +template <typename Map> +EntryProcessor<Map>::EntryProcessor(const std::vector<typename Map::Decision>& decisions) + : count(0), log(), behaviour(decisions) {} + +template <typename Map> +EntryProcessor<Map>::~EntryProcessor() = default; + +template <typename Map> +struct ConstProcessor { + mutable std::vector<std::string> log; + + typename Map::Decision operator()(uint64_t key, const A& a) const { + std::ostringstream ost; + ost << key << " - " << a; + log.push_back(ost.str()); + return Map::CONTINUE; + } -EntryProcessor::EntryProcessor() : count(0), log(), behaviour() {} -EntryProcessor::EntryProcessor(const std::vector<Map::Decision>& decisions) - : count(0), log(), behaviour(decisions) {} -EntryProcessor::~EntryProcessor() = default; + std::string toString() { + std::ostringstream ost; + for (const auto& entry : log) { + ost << entry << "\n"; + } + return ost.str(); + } +}; + +} -TEST(LockableMapTest, iterating) { - Map map; +TYPED_TEST(LockableMapTest, iterating) { + TypeParam map; bool preExisted; map.insert(16, A(1, 2, 3), "foo", preExisted); map.insert(11, A(4, 6, 0), "foo", preExisted); map.insert(14, A(42, 0, 0), "foo", preExisted); - // Test that we can use functor with non-const function + // Test that we can use functor with non-const function { - NonConstProcessor ncproc; - map.each(ncproc, "foo"); // Locking both for each element + NonConstProcessor<TypeParam> ncproc; + map.for_each_mutable(std::ref(ncproc), "foo"); // First round of mutating functor for `all` EXPECT_EQ(A(4, 7, 0), *map.get(11, "foo")); EXPECT_EQ(A(42,1, 0), *map.get(14, "foo")); EXPECT_EQ(A(1, 3, 3), *map.get(16, "foo")); - map.all(ncproc, "foo"); // And for all + map.for_each_mutable(std::ref(ncproc), "foo"); // Once more, with feeling. EXPECT_EQ(A(4, 8, 0), *map.get(11, "foo")); EXPECT_EQ(A(42,2, 0), *map.get(14, "foo")); EXPECT_EQ(A(1, 4, 3), *map.get(16, "foo")); } - // Test that we can use const functors directly.. - map.each(EntryProcessor(), "foo"); - // Test iterator bounds { - EntryProcessor proc; - map.each(proc, "foo", 11, 16); + ConstProcessor<TypeParam> cproc; + map.for_each(std::ref(cproc), "foo"); + std::string expected("11 - A(4, 8, 0)\n" + "14 - A(42, 2, 0)\n" + "16 - A(1, 4, 3)\n"); + EXPECT_EQ(expected, cproc.toString()); + } + // Test that we can use const functors directly.. + map.for_each(ConstProcessor<TypeParam>(), "foo"); + + // Test iterator bounds + { + EntryProcessor<TypeParam> proc; + map.for_each_mutable(std::ref(proc), "foo", 11, 16); std::string expected("11 - A(4, 8, 0)\n" "14 - A(42, 2, 0)\n" "16 - A(1, 4, 3)\n"); EXPECT_EQ(expected, proc.toString()); - EntryProcessor proc2; - map.each(proc2, "foo", 12, 15); + EntryProcessor<TypeParam> proc2; + map.for_each_mutable(std::ref(proc2), "foo", 12, 15); expected = "14 - A(42, 2, 0)\n"; EXPECT_EQ(expected, proc2.toString()); } // Test that we can abort iterating { - std::vector<Map::Decision> decisions; - decisions.push_back(Map::CONTINUE); - decisions.push_back(Map::ABORT); - EntryProcessor proc(decisions); - map.each(proc, "foo"); + std::vector<typename TypeParam::Decision> decisions; + decisions.push_back(TypeParam::CONTINUE); + decisions.push_back(TypeParam::ABORT); + EntryProcessor<TypeParam> proc(decisions); + map.for_each_mutable(std::ref(proc), "foo"); std::string expected("11 - A(4, 8, 0)\n" "14 - A(42, 2, 0)\n"); EXPECT_EQ(expected, proc.toString()); } - // Test that we can remove during iteration + // Test that we can remove during iteration { - std::vector<Map::Decision> decisions; - decisions.push_back(Map::CONTINUE); - decisions.push_back(Map::REMOVE); - EntryProcessor proc(decisions); - map.each(proc, "foo"); + std::vector<typename TypeParam::Decision> decisions; + decisions.push_back(TypeParam::CONTINUE); + decisions.push_back(TypeParam::REMOVE); // TODO consider removing; not used + EntryProcessor<TypeParam> proc(decisions); + map.for_each_mutable(std::ref(proc), "foo"); std::string expected("11 - A(4, 8, 0)\n" "14 - A(42, 2, 0)\n" "16 - A(1, 4, 3)\n"); EXPECT_EQ(expected, proc.toString()); - EXPECT_EQ((Map::size_type) 2, map.size()) << map.toString(); + EXPECT_EQ(2u, map.size()); EXPECT_EQ(A(4, 8, 0), *map.get(11, "foo")); EXPECT_EQ(A(1, 4, 3), *map.get(16, "foo")); - Map::WrappedEntry entry = map.get(14, "foo"); + auto entry = map.get(14, "foo"); EXPECT_FALSE(entry.exist()); } } -TEST(LockableMapTest, chunked_iteration_is_transparent_across_chunk_sizes) { - Map map; +TYPED_TEST(LockableMapTest, chunked_iteration_is_transparent_across_chunk_sizes) { + TypeParam map; bool preExisted; map.insert(16, A(1, 2, 3), "foo", preExisted); map.insert(11, A(4, 6, 0), "foo", preExisted); map.insert(14, A(42, 0, 0), "foo", preExisted); - NonConstProcessor ncproc; // Increments 2nd value in all entries. - // chunkedAll with chunk size of 1 - map.chunkedAll(ncproc, "foo", 1us, 1); + NonConstProcessor<TypeParam> ncproc; // Increments 2nd value in all entries. + // for_each_chunked with chunk size of 1 + map.for_each_chunked(std::ref(ncproc), "foo", 1us, 1); EXPECT_EQ(A(4, 7, 0), *map.get(11, "foo")); EXPECT_EQ(A(42, 1, 0), *map.get(14, "foo")); EXPECT_EQ(A(1, 3, 3), *map.get(16, "foo")); - // chunkedAll with chunk size larger than db size - map.chunkedAll(ncproc, "foo", 1us, 100); + // for_each_chunked with chunk size larger than db size + map.for_each_chunked(std::ref(ncproc), "foo", 1us, 100); EXPECT_EQ(A(4, 8, 0), *map.get(11, "foo")); EXPECT_EQ(A(42, 2, 0), *map.get(14, "foo")); EXPECT_EQ(A(1, 4, 3), *map.get(16, "foo")); } -TEST(LockableMapTest, can_abort_during_chunked_iteration) { - Map map; +TYPED_TEST(LockableMapTest, can_abort_during_chunked_iteration) { + TypeParam map; bool preExisted; map.insert(16, A(1, 2, 3), "foo", preExisted); map.insert(11, A(4, 6, 0), "foo", preExisted); map.insert(14, A(42, 0, 0), "foo", preExisted); - std::vector<Map::Decision> decisions; - decisions.push_back(Map::CONTINUE); - decisions.push_back(Map::ABORT); - EntryProcessor proc(decisions); - map.chunkedAll(proc, "foo", 1us, 100); + std::vector<typename TypeParam::Decision> decisions; + decisions.push_back(TypeParam::CONTINUE); + decisions.push_back(TypeParam::ABORT); + EntryProcessor<TypeParam> proc(decisions); + map.for_each_chunked(std::ref(proc), "foo", 1us, 100); std::string expected("11 - A(4, 6, 0)\n" "14 - A(42, 0, 0)\n"); EXPECT_EQ(expected, proc.toString()); } -TEST(LockableMapTest, find_buckets_simple) { - Map map; +TYPED_TEST(LockableMapTest, find_buckets_simple) { + TypeParam map; document::BucketId id1(17, 0x0ffff); id1 = id1.stripUnused(); @@ -293,8 +354,8 @@ TEST(LockableMapTest, find_buckets_simple) { EXPECT_EQ(A(3,4,5), *results[id3]); } -TEST(LockableMapTest, find_buckets) { - Map map; +TYPED_TEST(LockableMapTest, find_buckets) { + TypeParam map; document::BucketId id1(16, 0x0ffff); document::BucketId id2(17, 0x0ffff); @@ -317,8 +378,8 @@ TEST(LockableMapTest, find_buckets) { EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); } -TEST(LockableMapTest, find_buckets_2) { // ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_buckets_2) { // ticket 3121525 + TypeParam map; document::BucketId id1(16, 0x0ffff); document::BucketId id2(17, 0x0ffff); @@ -341,8 +402,8 @@ TEST(LockableMapTest, find_buckets_2) { // ticket 3121525 EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); } -TEST(LockableMapTest, find_buckets_3) { // ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_buckets_3) { // ticket 3121525 + TypeParam map; document::BucketId id1(16, 0x0ffff); document::BucketId id2(17, 0x0ffff); @@ -359,8 +420,8 @@ TEST(LockableMapTest, find_buckets_3) { // ticket 3121525 EXPECT_EQ(A(1,2,3), *results[id1.stripUnused()]); } -TEST(LockableMapTest, find_buckets_4) { // ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_buckets_4) { // ticket 3121525 + TypeParam map; document::BucketId id1(16, 0x0ffff); document::BucketId id2(17, 0x0ffff); @@ -379,8 +440,8 @@ TEST(LockableMapTest, find_buckets_4) { // ticket 3121525 EXPECT_EQ(A(1,2,3), *results[id1.stripUnused()]); } -TEST(LockableMapTest, find_buckets_5) { // ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_buckets_5) { // ticket 3121525 + TypeParam map; document::BucketId id1(16, 0x0ffff); document::BucketId id2(17, 0x0ffff); @@ -399,8 +460,8 @@ TEST(LockableMapTest, find_buckets_5) { // ticket 3121525 EXPECT_EQ(A(1,2,3), *results[id1.stripUnused()]); } -TEST(LockableMapTest, find_no_buckets) { - Map map; +TYPED_TEST(LockableMapTest, find_no_buckets) { + TypeParam map; document::BucketId id(16, 0x0ffff); auto results = map.getAll(id, "foo"); @@ -408,8 +469,8 @@ TEST(LockableMapTest, find_no_buckets) { EXPECT_EQ(0, results.size()); } -TEST(LockableMapTest, find_all) { - Map map; +TYPED_TEST(LockableMapTest, find_all) { + TypeParam map; document::BucketId id1(16, 0x0aaaa); // contains id2-id7 document::BucketId id2(17, 0x0aaaa); // contains id3-id4 @@ -450,8 +511,8 @@ TEST(LockableMapTest, find_all) { EXPECT_EQ(A(9,10,11), *results[id9.stripUnused()]); // sub bucket } -TEST(LockableMapTest, find_all_2) { // Ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_all_2) { // Ticket 3121525 + TypeParam map; document::BucketId id1(17, 0x00001); document::BucketId id2(17, 0x10001); @@ -469,8 +530,8 @@ TEST(LockableMapTest, find_all_2) { // Ticket 3121525 EXPECT_EQ(A(2,3,4), *results[id2.stripUnused()]); // sub bucket } -TEST(LockableMapTest, find_all_unused_bit_is_set) { // ticket 2938896 - Map map; +TYPED_TEST(LockableMapTest, find_all_unused_bit_is_set) { // ticket 2938896 + TypeParam map; document::BucketId id1(24, 0x000dc7089); document::BucketId id2(33, 0x0053c7089); @@ -493,8 +554,8 @@ TEST(LockableMapTest, find_all_unused_bit_is_set) { // ticket 2938896 EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); // sub bucket } -TEST(LockableMapTest, find_all_inconsistently_split) { // Ticket 2938896 - Map map; +TYPED_TEST(LockableMapTest, find_all_inconsistently_split) { // Ticket 2938896 + TypeParam map; document::BucketId id1(16, 0x00001); // contains id2-id3 document::BucketId id2(17, 0x00001); @@ -515,8 +576,8 @@ TEST(LockableMapTest, find_all_inconsistently_split) { // Ticket 2938896 EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); // sub bucket } -TEST(LockableMapTest, find_all_inconsistently_split_2) { // ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_all_inconsistently_split_2) { // ticket 3121525 + TypeParam map; document::BucketId id1(17, 0x10000); document::BucketId id2(27, 0x007228034); // contains id3 @@ -538,8 +599,8 @@ TEST(LockableMapTest, find_all_inconsistently_split_2) { // ticket 3121525 EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); // most specific match (super bucket) } -TEST(LockableMapTest, find_all_inconsistently_split_3) { // ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_all_inconsistently_split_3) { // ticket 3121525 + TypeParam map; document::BucketId id1(16, 0x0ffff); // contains id2 document::BucketId id2(17, 0x0ffff); @@ -556,8 +617,8 @@ TEST(LockableMapTest, find_all_inconsistently_split_3) { // ticket 3121525 EXPECT_EQ(A(1,2,3), *results[id1.stripUnused()]); // super bucket } -TEST(LockableMapTest, find_all_inconsistently_split_4) { // ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_all_inconsistently_split_4) { // ticket 3121525 + TypeParam map; document::BucketId id1(16, 0x0ffff); // contains id2-id3 document::BucketId id2(17, 0x0ffff); @@ -577,8 +638,8 @@ TEST(LockableMapTest, find_all_inconsistently_split_4) { // ticket 3121525 EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); // sub bucket } -TEST(LockableMapTest, find_all_inconsistently_split_5) { // ticket 3121525 - Map map; +TYPED_TEST(LockableMapTest, find_all_inconsistently_split_5) { // ticket 3121525 + TypeParam map; document::BucketId id1(16, 0x0ffff); // contains id2-id3 document::BucketId id2(17, 0x0ffff); @@ -598,8 +659,8 @@ TEST(LockableMapTest, find_all_inconsistently_split_5) { // ticket 3121525 EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); // sub bucket } -TEST(LockableMapTest, find_all_inconsistently_split_6) { - Map map; +TYPED_TEST(LockableMapTest, find_all_inconsistently_split_6) { + TypeParam map; document::BucketId id1(16, 0x0ffff); // contains id2-id3 document::BucketId id2(18, 0x1ffff); @@ -619,8 +680,8 @@ TEST(LockableMapTest, find_all_inconsistently_split_6) { EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); // sub bucket } -TEST(LockableMapTest, find_all_inconsistent_below_16_bits) { - Map map; +TYPED_TEST(LockableMapTest, find_all_inconsistent_below_16_bits) { + TypeParam map; document::BucketId id1(1, 0x1); // contains id2-id3 document::BucketId id2(3, 0x1); @@ -641,24 +702,64 @@ TEST(LockableMapTest, find_all_inconsistent_below_16_bits) { EXPECT_EQ(A(3,4,5), *results[id3.stripUnused()]); // sub bucket } -TEST(LockableMapTest, is_consistent) { - Map map; +TYPED_TEST(LockableMapTest, is_consistent) { + TypeParam map; document::BucketId id1(16, 0x00001); // contains id2-id3 document::BucketId id2(17, 0x00001); bool preExisted; map.insert(id1.stripUnused().toKey(), A(1,2,3), "foo", preExisted); { - Map::WrappedEntry entry( - map.get(id1.stripUnused().toKey(), "foo", true)); + auto entry = map.get(id1.stripUnused().toKey(), "foo", true); EXPECT_TRUE(map.isConsistent(entry)); } map.insert(id2.stripUnused().toKey(), A(1,2,3), "foo", preExisted); { - Map::WrappedEntry entry( - map.get(id1.stripUnused().toKey(), "foo", true)); + auto entry = map.get(id1.stripUnused().toKey(), "foo", true); EXPECT_FALSE(map.isConsistent(entry)); } } +TYPED_TEST(LockableMapTest, get_without_auto_create_does_not_implicitly_lock_bucket) { + TypeParam map; + BucketId id(16, 0x00001); + + auto entry = map.get(id.toKey(), "foo", false); + EXPECT_FALSE(entry.exist()); + EXPECT_FALSE(entry.preExisted()); + EXPECT_FALSE(entry.locked()); +} + +TYPED_TEST(LockableMapTest, get_with_auto_create_returns_default_constructed_entry_if_missing) { + TypeParam map; + BucketId id(16, 0x00001); + + auto entry = map.get(id.toKey(), "foo", true); + EXPECT_TRUE(entry.exist()); + EXPECT_FALSE(entry.preExisted()); + EXPECT_TRUE(entry.locked()); + EXPECT_EQ(*entry, A()); + *entry = A(1, 2, 3); + entry.write(); // Implicit unlock (!) + + // Should now exist + entry = map.get(id.toKey(), "foo", true); + EXPECT_TRUE(entry.exist()); + EXPECT_TRUE(entry.preExisted()); + EXPECT_TRUE(entry.locked()); + EXPECT_EQ(*entry, A(1, 2, 3)); +} + +TYPED_TEST(LockableMapTest, entry_changes_not_visible_if_write_not_invoked_on_guard) { + TypeParam map; + BucketId id(16, 0x00001); + auto entry = map.get(id.toKey(), "foo", true); + *entry = A(1, 2, 3); + // No write() call on guard + entry.unlock(); + + entry = map.get(id.toKey(), "foo", true); + EXPECT_EQ(*entry, A()); +} + } // storage diff --git a/storage/src/tests/common/CMakeLists.txt b/storage/src/tests/common/CMakeLists.txt index 1922d13ca61..5d5b04d8095 100644 --- a/storage/src/tests/common/CMakeLists.txt +++ b/storage/src/tests/common/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(storage_testcommon TEST SOURCES dummystoragelink.cpp @@ -19,7 +20,7 @@ vespa_add_executable(storage_common_gtest_runner_app TEST DEPENDS storage_testcommon storage - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/tests/common/hostreporter/CMakeLists.txt b/storage/src/tests/common/hostreporter/CMakeLists.txt index 2fcb159cb08..4328b8156e6 100644 --- a/storage/src/tests/common/hostreporter/CMakeLists.txt +++ b/storage/src/tests/common/hostreporter/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(storage_testhostreporter TEST SOURCES util.cpp @@ -14,7 +15,7 @@ vespa_add_executable(storage_hostreporter_gtest_runner_app TEST DEPENDS storage storage_testhostreporter - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/tests/common/teststorageapp.cpp b/storage/src/tests/common/teststorageapp.cpp index 082af954871..9fcf1049e1b 100644 --- a/storage/src/tests/common/teststorageapp.cpp +++ b/storage/src/tests/common/teststorageapp.cpp @@ -140,10 +140,8 @@ namespace { } TestServiceLayerApp::TestServiceLayerApp(vespalib::stringref configId) - : TestStorageApp( - StorageComponentRegisterImpl::UP( - new ServiceLayerComponentRegisterImpl), - lib::NodeType::STORAGE, getIndexFromConfig(configId), configId), + : TestStorageApp(std::make_unique<ServiceLayerComponentRegisterImpl>(true), // TODO remove B-tree flag once default + lib::NodeType::STORAGE, getIndexFromConfig(configId), configId), _compReg(dynamic_cast<ServiceLayerComponentRegisterImpl&>( TestStorageApp::getComponentRegister())), _persistenceProvider(), @@ -157,10 +155,8 @@ TestServiceLayerApp::TestServiceLayerApp(vespalib::stringref configId) TestServiceLayerApp::TestServiceLayerApp(DiskCount dc, NodeIndex index, vespalib::stringref configId) - : TestStorageApp( - StorageComponentRegisterImpl::UP( - new ServiceLayerComponentRegisterImpl), - lib::NodeType::STORAGE, index, configId), + : TestStorageApp(std::make_unique<ServiceLayerComponentRegisterImpl>(true), // TODO remove B-tree flag once default + lib::NodeType::STORAGE, index, configId), _compReg(dynamic_cast<ServiceLayerComponentRegisterImpl&>( TestStorageApp::getComponentRegister())), _persistenceProvider(), diff --git a/storage/src/tests/distributor/CMakeLists.txt b/storage/src/tests/distributor/CMakeLists.txt index 3148540d86d..1403021a9c3 100644 --- a/storage/src/tests/distributor/CMakeLists.txt +++ b/storage/src/tests/distributor/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(storage_distributor_gtest_runner_app TEST SOURCES blockingoperationstartertest.cpp @@ -49,7 +50,7 @@ vespa_add_executable(storage_distributor_gtest_runner_app TEST storage_testcommon storage_testhostreporter storage_distributor - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/tests/frameworkimpl/status/CMakeLists.txt b/storage/src/tests/frameworkimpl/status/CMakeLists.txt index 1b49b1bac45..cf2ee5fd51b 100644 --- a/storage/src/tests/frameworkimpl/status/CMakeLists.txt +++ b/storage/src/tests/frameworkimpl/status/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(storage_status_gtest_runner_app TEST SOURCES gtest_runner.cpp @@ -8,7 +9,7 @@ vespa_add_executable(storage_status_gtest_runner_app TEST DEPENDS storage storage_testcommon - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/tests/persistence/CMakeLists.txt b/storage/src/tests/persistence/CMakeLists.txt index e8095109806..ae96748ff9d 100644 --- a/storage/src/tests/persistence/CMakeLists.txt +++ b/storage/src/tests/persistence/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(storage_persistence_gtest_runner_app TEST SOURCES bucketownershipnotifiertest.cpp @@ -15,7 +16,7 @@ vespa_add_executable(storage_persistence_gtest_runner_app TEST DEPENDS storage storage_testpersistence_common - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/tests/persistence/common/CMakeLists.txt b/storage/src/tests/persistence/common/CMakeLists.txt index 53ec3fd7c0c..29b826c0c9b 100644 --- a/storage/src/tests/persistence/common/CMakeLists.txt +++ b/storage/src/tests/persistence/common/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(storage_testpersistence_common TEST SOURCES filestortestfixture.cpp persistenceproviderwrapper.cpp DEPENDS - gtest + GTest::GTest persistence storage_testcommon ) diff --git a/storage/src/tests/persistence/filestorage/CMakeLists.txt b/storage/src/tests/persistence/filestorage/CMakeLists.txt index 5209bcce73d..8cabfb865cd 100644 --- a/storage/src/tests/persistence/filestorage/CMakeLists.txt +++ b/storage/src/tests/persistence/filestorage/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(storage_filestorage_gtest_runner_app TEST SOURCES deactivatebucketstest.cpp @@ -16,7 +17,7 @@ vespa_add_executable(storage_filestorage_gtest_runner_app TEST storage storageapi storage_testpersistence_common - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/tests/storageserver/CMakeLists.txt b/storage/src/tests/storageserver/CMakeLists.txt index 1d759a534f6..95ce08265ad 100644 --- a/storage/src/tests/storageserver/CMakeLists.txt +++ b/storage/src/tests/storageserver/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(storage_teststorageserver TEST SOURCES testvisitormessagesession.cpp @@ -25,7 +26,7 @@ vespa_add_executable(storage_storageserver_gtest_runner_app TEST storage_storageserver storage_testcommon storage_teststorageserver - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/tests/visiting/CMakeLists.txt b/storage/src/tests/visiting/CMakeLists.txt index c1b19960cea..8bfc28e7eb9 100644 --- a/storage/src/tests/visiting/CMakeLists.txt +++ b/storage/src/tests/visiting/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(storage_visiting_gtest_runner_app TEST SOURCES commandqueuetest.cpp @@ -10,7 +11,7 @@ vespa_add_executable(storage_visiting_gtest_runner_app TEST DEPENDS storage storage_teststorageserver - gtest + GTest::GTest ) vespa_add_test( diff --git a/storage/src/vespa/storage/bucketdb/CMakeLists.txt b/storage/src/vespa/storage/bucketdb/CMakeLists.txt index a99d16d9f0f..5bd966ae0e1 100644 --- a/storage/src/vespa/storage/bucketdb/CMakeLists.txt +++ b/storage/src/vespa/storage/bucketdb/CMakeLists.txt @@ -2,11 +2,13 @@ vespa_add_library(storage_bucketdb OBJECT SOURCES btree_bucket_database.cpp + btree_lockable_map.cpp bucketcopy.cpp bucketdatabase.cpp bucketinfo.cpp bucketmanager.cpp bucketmanagermetrics.cpp + generic_btree_bucket_database.cpp judyarray.cpp lockablemap.cpp mapbucketdatabase.cpp diff --git a/storage/src/vespa/storage/bucketdb/abstract_bucket_map.h b/storage/src/vespa/storage/bucketdb/abstract_bucket_map.h new file mode 100644 index 00000000000..6c669beab1c --- /dev/null +++ b/storage/src/vespa/storage/bucketdb/abstract_bucket_map.h @@ -0,0 +1,244 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/document/bucket/bucketid.h> +#include <vespa/vespalib/stllike/hash_map.h> +#include <vespa/vespalib/stllike/hash_set.h> +#include <vespa/vespalib/util/time.h> +#include <cassert> +#include <functional> +#include <iosfwd> +#include <map> + +namespace storage::bucketdb { + +/* + * Interface for content node bucket database implementations. + * + * Allows for multiple divergent implementations to exist of the + * bucket database in a transition period. + */ +template <typename ValueT> +class AbstractBucketMap { +public: + using key_type = uint64_t; // Always a raw u64 bucket key. + using mapped_type = ValueT; + using size_type = size_t; + using BucketId = document::BucketId; + struct WrappedEntry; + + // Responsible for releasing lock in map when out of scope. + class LockKeeper { + friend struct WrappedEntry; + AbstractBucketMap& _map; + key_type _key; + bool _locked; + + LockKeeper(AbstractBucketMap& map, key_type key) + : _map(map), _key(key), _locked(true) {} + void unlock() { _map.unlock(_key); _locked = false; } + public: + ~LockKeeper() { if (_locked) unlock(); } + }; + + struct WrappedEntry { + WrappedEntry() + : _exists(false), + _preExisted(false), + _lockKeeper(), + _value(), + _clientId(nullptr) + {} + WrappedEntry(AbstractBucketMap& map, + const key_type& key, const mapped_type& val, + const char* clientId, bool preExisted_) + : _exists(true), + _preExisted(preExisted_), + _lockKeeper(new LockKeeper(map, key)), + _value(val), + _clientId(clientId) {} + WrappedEntry(AbstractBucketMap& map, const key_type& key, + const char* clientId) + : _exists(false), + _preExisted(false), + _lockKeeper(new LockKeeper(map, key)), + _value(), + _clientId(clientId) {} + // TODO noexcept on these: + WrappedEntry(WrappedEntry&&) = default; + WrappedEntry& operator=(WrappedEntry&&) = default; + ~WrappedEntry(); + + mapped_type* operator->() { return &_value; } + const mapped_type* operator->() const { return &_value; } + mapped_type& operator*() { return _value; } + const mapped_type& operator*() const { return _value; } + + const mapped_type *get() const { return &_value; } + mapped_type *get() { return &_value; } + + void write(); + void remove(); + void unlock(); + [[nodiscard]] bool exist() const { return _exists; } // TODO rename to exists() + [[nodiscard]] bool preExisted() const { return _preExisted; } + [[nodiscard]] bool locked() const { return _lockKeeper.get(); } + const key_type& getKey() const { return _lockKeeper->_key; }; + + BucketId getBucketId() const { + return BucketId(BucketId::keyToBucketId(getKey())); + } + protected: + bool _exists; + bool _preExisted; + std::unique_ptr<LockKeeper> _lockKeeper; + mapped_type _value; + const char* _clientId; + friend class AbstractLockableMap; + }; + + struct LockId { + key_type _key; + const char* _owner; + + LockId() : _key(0), _owner("none - empty token") {} + LockId(key_type key, const char* owner) + : _key(key), _owner(owner) + { + assert(_owner); + } + + size_t hash() const { return _key; } + size_t operator%(size_t val) const { return _key % val; } + bool operator==(const LockId& id) const { return (_key == id._key); } + operator key_type() const { return _key; } + }; + + using EntryMap = std::map<BucketId, WrappedEntry>; // TODO ordered std::vector instead? map interface needed? + + enum Decision { ABORT, UPDATE, REMOVE, CONTINUE, DECISION_COUNT }; + + AbstractBucketMap() = default; + virtual ~AbstractBucketMap() = default; + + virtual void insert(const key_type& key, const mapped_type& value, + const char* client_id, bool has_lock, + bool& pre_existed) = 0; + virtual bool erase(const key_type& key, const char* clientId, bool has_lock) = 0; + + virtual WrappedEntry get(const key_type& key, const char* clientId, bool createIfNonExisting) = 0; + WrappedEntry get(const key_type& key, const char* clientId) { + return get(key, clientId, false); + } + /** + * Returns all buckets in the bucket database that can contain the given + * bucket, and all buckets that that bucket contains. + */ + virtual EntryMap getAll(const BucketId& bucketId, const char* clientId) = 0; + /** + * Returns all buckets in the bucket database that can contain the given + * bucket. Usually, there should be only one such bucket, but in the case + * of inconsistent splitting, there may be more than one. + */ + virtual EntryMap getContained(const BucketId& bucketId, const char* clientId) = 0; + /** + * Returns true iff bucket has no superbuckets or sub-buckets in the + * database. Usage assumption is that any operation that can cause the + * bucket to become inconsistent will require taking its lock, so by + * requiring the lock to be provided here we avoid race conditions. + */ + virtual bool isConsistent(const WrappedEntry& entry) = 0; // TODO const + + static constexpr uint32_t DEFAULT_CHUNK_SIZE = 1000; + + + /** + * Iterate over the entire database contents, holding the global database + * mutex for `chunkSize` processed entries at a time, yielding the current + * thread between each chunk to allow other threads to get a chance at + * acquiring a bucket lock. + * + * TODO deprecate in favor of snapshotting once fully on B-tree DB + * + * Type erasure of functor needed due to virtual indirection. + */ + void for_each_chunked(std::function<Decision(uint64_t, ValueT&)> func, + const char* clientId, + vespalib::duration yieldTime = 10us, + uint32_t chunkSize = DEFAULT_CHUNK_SIZE) + { + do_for_each_chunked(std::move(func), clientId, yieldTime, chunkSize); + } + + void for_each_mutable(std::function<Decision(uint64_t, ValueT&)> func, + const char* clientId, + const key_type& first = 0, + const key_type& last = UINT64_MAX) + { + do_for_each_mutable(std::move(func), clientId, first, last); + } + + void for_each(std::function<Decision(uint64_t, const ValueT&)> func, + const char* clientId, + const key_type& first = 0, + const key_type& last = UINT64_MAX) + { + do_for_each(std::move(func), clientId, first, last); + } + + [[nodiscard]] virtual size_type size() const noexcept = 0; + [[nodiscard]] virtual size_type getMemoryUsage() const noexcept = 0; + [[nodiscard]] virtual bool empty() const noexcept = 0; + + virtual void showLockClients(vespalib::asciistream& out) const = 0; + + virtual void print(std::ostream& out, bool verbose, const std::string& indent) const = 0; +private: + virtual void unlock(const key_type& key) = 0; // Only for bucket lock guards + virtual void do_for_each_chunked(std::function<Decision(uint64_t, ValueT&)> func, + const char* clientId, + vespalib::duration yieldTime, + uint32_t chunkSize) = 0; + virtual void do_for_each_mutable(std::function<Decision(uint64_t, ValueT&)> func, + const char* clientId, + const key_type& first, + const key_type& last) = 0; + virtual void do_for_each(std::function<Decision(uint64_t, const ValueT&)> func, + const char* clientId, + const key_type& first, + const key_type& last) = 0; +}; + +template <typename ValueT> +std::ostream& operator<<(std::ostream& os, const AbstractBucketMap<ValueT>& map) { + map.print(os, false, ""); + return os; +} + +template <typename ValueT> +AbstractBucketMap<ValueT>::WrappedEntry::~WrappedEntry() = default; + +template <typename ValueT> +void AbstractBucketMap<ValueT>::WrappedEntry::write() { + assert(_lockKeeper->_locked); + assert(_value.verifyLegal()); + bool b; + _lockKeeper->_map.insert(_lockKeeper->_key, _value, _clientId, true, b); + _lockKeeper->unlock(); +} + +template <typename ValueT> +void AbstractBucketMap<ValueT>::WrappedEntry::remove() { + assert(_lockKeeper->_locked); + assert(_exists); + _lockKeeper->_map.erase(_lockKeeper->_key, _clientId, true); + _lockKeeper->unlock(); +} + +template <typename ValueT> +void AbstractBucketMap<ValueT>::WrappedEntry::unlock() { + assert(_lockKeeper->_locked); + _lockKeeper->unlock(); +} + +} diff --git a/storage/src/vespa/storage/bucketdb/btree_bucket_database.cpp b/storage/src/vespa/storage/bucketdb/btree_bucket_database.cpp index 30fa8bb7543..42bd3a247bb 100644 --- a/storage/src/vespa/storage/bucketdb/btree_bucket_database.cpp +++ b/storage/src/vespa/storage/bucketdb/btree_bucket_database.cpp @@ -13,6 +13,10 @@ #include <vespa/vespalib/datastore/array_store.hpp> #include <iostream> +// TODO remove once this impl uses the generic bucket B-tree code! +#include "generic_btree_bucket_database.h" +#include <vespa/vespalib/datastore/datastore.h> + /* * Buckets in our tree are represented by their 64-bit numeric key, in what's known as * "reversed bit order with appended used-bits" form. I.e. a bucket ID (16, 0xcafe), which @@ -40,16 +44,8 @@ using vespalib::datastore::EntryRef; using vespalib::ConstArrayRef; using document::BucketId; -BTreeBucketDatabase::BTreeBucketDatabase() - : _tree(), - _store(make_default_array_store_config()), - _generation_handler() -{ -} - -BTreeBucketDatabase::~BTreeBucketDatabase() = default; - -vespalib::datastore::ArrayStoreConfig BTreeBucketDatabase::make_default_array_store_config() { +template <typename ReplicaStore> +vespalib::datastore::ArrayStoreConfig make_default_array_store_config() { return ReplicaStore::optimizedConfigForHugePage(1023, vespalib::alloc::MemoryAllocator::HUGEPAGE_SIZE, 4 * 1024, 8 * 1024, 0.2).enable_free_lists(true); } @@ -121,6 +117,15 @@ uint8_t next_parent_bit_seek_level(uint8_t minBits, const document::BucketId& a, } +BTreeBucketDatabase::BTreeBucketDatabase() + : _tree(), + _store(make_default_array_store_config<ReplicaStore>()), + _generation_handler() +{ +} + +BTreeBucketDatabase::~BTreeBucketDatabase() = default; + void BTreeBucketDatabase::commit_tree_changes() { // TODO break up and refactor // TODO verify semantics and usage @@ -574,4 +579,45 @@ uint64_t BTreeBucketDatabase::ReadGuardImpl::generation() const noexcept { return _guard.getGeneration(); } +// TODO replace existing distributor DB code with generic impl. +// This is to ensure the generic implementation compiles with an ArrayStore backing in +// the meantime. +struct BTreeBucketDatabase2 { + struct ReplicaValueTraits { + using ValueType = Entry; + using ConstValueRef = ConstEntryRef; + using DataStoreType = vespalib::datastore::ArrayStore<BucketCopy>; + + static ValueType make_invalid_value() { + return Entry::createInvalid(); + } + static uint64_t wrap_and_store_value(DataStoreType& store, const Entry& entry) noexcept { + auto replicas_ref = store.add(entry.getBucketInfo().getRawNodes()); + return value_from(entry.getBucketInfo().getLastGarbageCollectionTime(), replicas_ref); + } + static void remove_by_wrapped_value(DataStoreType& store, uint64_t value) noexcept { + store.remove(entry_ref_from_value(value)); + } + static ValueType unwrap_from_key_value(const DataStoreType& store, uint64_t key, uint64_t value) { + const auto replicas_ref = store.get(entry_ref_from_value(value)); + const auto bucket = BucketId(BucketId::keyToBucketId(key)); + return entry_from_replica_array_ref(bucket, gc_timestamp_from_value(value), replicas_ref); + } + static ConstValueRef unwrap_const_ref_from_key_value(const DataStoreType& store, uint64_t key, uint64_t value) { + const auto replicas_ref = store.get(entry_ref_from_value(value)); + const auto bucket = BucketId(BucketId::keyToBucketId(key)); + return const_entry_ref_from_replica_array_ref(bucket, gc_timestamp_from_value(value), replicas_ref); + } + }; + + using BTreeImpl = bucketdb::GenericBTreeBucketDatabase<ReplicaValueTraits>; + BTreeImpl _impl; + + BTreeBucketDatabase2() + : _impl(make_default_array_store_config<ReplicaValueTraits::DataStoreType>()) + {} +}; + +template class bucketdb::GenericBTreeBucketDatabase<BTreeBucketDatabase2::ReplicaValueTraits>; + } diff --git a/storage/src/vespa/storage/bucketdb/btree_bucket_database.h b/storage/src/vespa/storage/bucketdb/btree_bucket_database.h index 35e248cfc76..0dfa2b07b8a 100644 --- a/storage/src/vespa/storage/bucketdb/btree_bucket_database.h +++ b/storage/src/vespa/storage/bucketdb/btree_bucket_database.h @@ -84,8 +84,6 @@ private: const document::BucketId& bucket, std::vector<Entry>& entries) const; - static vespalib::datastore::ArrayStoreConfig make_default_array_store_config(); - class ReadGuardImpl : public ReadGuard { const BTreeBucketDatabase* _db; GenerationHandler::Guard _guard; diff --git a/storage/src/vespa/storage/bucketdb/btree_lockable_map.cpp b/storage/src/vespa/storage/bucketdb/btree_lockable_map.cpp new file mode 100644 index 00000000000..a76f50d41ab --- /dev/null +++ b/storage/src/vespa/storage/bucketdb/btree_lockable_map.cpp @@ -0,0 +1,8 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "btree_lockable_map.hpp" + +namespace storage::bucketdb { + +template class BTreeLockableMap<StorageBucketInfo>; // Forced instantiation. + +} diff --git a/storage/src/vespa/storage/bucketdb/btree_lockable_map.h b/storage/src/vespa/storage/bucketdb/btree_lockable_map.h new file mode 100644 index 00000000000..136baefb615 --- /dev/null +++ b/storage/src/vespa/storage/bucketdb/btree_lockable_map.h @@ -0,0 +1,163 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include "abstract_bucket_map.h" +#include "storagebucketinfo.h" +#include <vespa/document/bucket/bucketid.h> +#include <vespa/vespalib/util/time.h> +#include <vespa/vespalib/stllike/hash_map.h> +#include <vespa/vespalib/stllike/hash_set.h> +#include <map> +#include <memory> +#include <mutex> +#include <condition_variable> +#include <cassert> +#include <iosfwd> + +namespace storage::bucketdb { + +template <typename DataStoreTraitsT> class GenericBTreeBucketDatabase; + +/* + * AbstractBucketMap implementation that uses a B-tree bucket database backing structure. + * + * Identical global and per-bucket locking semantics as LockableMap. + */ +template <typename T> +class BTreeLockableMap : public AbstractBucketMap<T> { + struct ValueTraits; +public: + using ParentType = AbstractBucketMap<T>; + using WrappedEntry = typename ParentType::WrappedEntry; + using key_type = typename ParentType::key_type; + using mapped_type = typename ParentType::mapped_type; + using LockId = typename ParentType::LockId; + using EntryMap = typename ParentType::EntryMap; + using Decision = typename ParentType::Decision; + using BucketId = document::BucketId; + + BTreeLockableMap(); + ~BTreeLockableMap(); + + bool operator==(const BTreeLockableMap& other) const; + bool operator!=(const BTreeLockableMap& other) const { + return ! (*this == other); + } + bool operator<(const BTreeLockableMap& other) const; + size_t size() const noexcept override; + size_t getMemoryUsage() const noexcept override; + bool empty() const noexcept override; + void swap(BTreeLockableMap&); + + WrappedEntry get(const key_type& key, const char* clientId, bool createIfNonExisting) override; + WrappedEntry get(const key_type& key, const char* clientId) { + return get(key, clientId, false); + } + bool erase(const key_type& key, const char* clientId, bool has_lock) override; + void insert(const key_type& key, const mapped_type& value, + const char* client_id, bool has_lock, bool& pre_existed) override; + + bool erase(const key_type& key, const char* client_id) { + return erase(key, client_id, false); + } + void insert(const key_type& key, const mapped_type& value, + const char* client_id, bool& pre_existed) { + return insert(key, value, client_id, false, pre_existed); + } + void clear(); + void print(std::ostream& out, bool verbose, const std::string& indent) const override; + EntryMap getContained(const BucketId& bucketId, const char* clientId) override; + EntryMap getAll(const BucketId& bucketId, const char* clientId) override; + bool isConsistent(const WrappedEntry& entry) override; + void showLockClients(vespalib::asciistream & out) const override; + +private: + struct hasher { + size_t operator () (const LockId & lid) const { return lid.hash(); } + }; + class LockIdSet : public vespalib::hash_set<LockId, hasher> { + typedef vespalib::hash_set<LockId, hasher> Hash; + public: + LockIdSet(); + ~LockIdSet(); + void print(std::ostream& out, bool verbose, const std::string& indent) const; + bool exists(const LockId & lid) const { return this->find(lid) != Hash::end(); } + size_t getMemoryUsage() const; + }; + + class LockWaiters { + typedef vespalib::hash_map<size_t, LockId> WaiterMap; + public: + typedef size_t Key; + typedef typename WaiterMap::const_iterator const_iterator; + LockWaiters(); + ~LockWaiters(); + Key insert(const LockId & lid); + void erase(Key id) { _map.erase(id); } + const_iterator begin() const { return _map.begin(); } + const_iterator end() const { return _map.end(); } + private: + Key _id; + WaiterMap _map; + }; + + mutable std::mutex _lock; + std::condition_variable _cond; + std::unique_ptr<GenericBTreeBucketDatabase<ValueTraits>> _impl; + LockIdSet _lockedKeys; + LockWaiters _lockWaiters; + + void unlock(const key_type& key) override; + bool findNextKey(key_type& key, mapped_type& val, const char* clientId, + std::unique_lock<std::mutex> &guard); + bool handleDecision(key_type& key, mapped_type& val, Decision decision); + void acquireKey(const LockId & lid, std::unique_lock<std::mutex> &guard); + + void do_for_each_mutable(std::function<Decision(uint64_t, mapped_type&)> func, + const char* clientId, + const key_type& first, + const key_type& last) override; + + void do_for_each(std::function<Decision(uint64_t, const mapped_type&)> func, + const char* clientId, + const key_type& first, + const key_type& last) override; + + void do_for_each_chunked(std::function<Decision(uint64_t, mapped_type&)> func, + const char* client_id, + vespalib::duration yield_time, + uint32_t chunk_size) override; + + /** + * Process up to `chunk_size` bucket database entries from--and possibly + * including--the bucket pointed to by `key`. + * + * Returns true if additional chunks may be processed after the call to + * this function has returned, false if iteration has completed or if + * `func` returned an abort-decision. + * + * Modifies `key` in-place to point to the next key to process for the next + * invocation of this function. + */ + bool processNextChunk(std::function<Decision(uint64_t, mapped_type&)>& func, + key_type& key, + const char* client_id, + uint32_t chunk_size); + + /** + * Returns the given bucket, its super buckets and its sub buckets. + */ + void getAllWithoutLocking(const BucketId& bucket, + std::vector<BucketId::Type>& keys); + + /** + * Find the given list of keys in the map and add them to the map of + * results, locking them in the process. + */ + void addAndLockResults(const std::vector<BucketId::Type>& keys, + const char* clientId, + std::map<BucketId, WrappedEntry>& results, + std::unique_lock<std::mutex> &guard); +}; + +} diff --git a/storage/src/vespa/storage/bucketdb/btree_lockable_map.hpp b/storage/src/vespa/storage/bucketdb/btree_lockable_map.hpp new file mode 100644 index 00000000000..9c7228ae21d --- /dev/null +++ b/storage/src/vespa/storage/bucketdb/btree_lockable_map.hpp @@ -0,0 +1,507 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "btree_lockable_map.h" +#include "generic_btree_bucket_database.hpp" +#include <vespa/vespalib/btree/btreebuilder.h> +#include <vespa/vespalib/btree/btreenodeallocator.hpp> +#include <vespa/vespalib/btree/btreenode.hpp> +#include <vespa/vespalib/btree/btreenodestore.hpp> +#include <vespa/vespalib/btree/btreeiterator.hpp> +#include <vespa/vespalib/btree/btreeroot.hpp> +#include <vespa/vespalib/btree/btreebuilder.hpp> +#include <vespa/vespalib/btree/btree.hpp> +#include <vespa/vespalib/btree/btreestore.hpp> +#include <vespa/vespalib/datastore/datastore.h> +#include <vespa/vespalib/stllike/hash_map.hpp> +#include <vespa/vespalib/stllike/hash_set.hpp> +#include <thread> +#include <sstream> + +// Major TODOs in the short term: +// - Introduce snapshotting for readers +// - Greatly improve performance for DB iteration for readers by avoiding +// requirement to lock individual buckets and perform O(n) lbound seeks +// just to do a sweep. + +namespace storage::bucketdb { + +using vespalib::datastore::EntryRef; +using vespalib::ConstArrayRef; +using document::BucketId; + +template <typename T> +struct BTreeLockableMap<T>::ValueTraits { + using ValueType = T; + using ConstValueRef = const T&; + using DataStoreType = vespalib::datastore::DataStore<ValueType>; + + static EntryRef entry_ref_from_value(uint64_t value) { + return EntryRef(value & 0xffffffffULL); + } + static ValueType make_invalid_value() { + return ValueType(); + } + static uint64_t wrap_and_store_value(DataStoreType& store, const ValueType& value) noexcept { + return store.addEntry(value).ref(); + } + static void remove_by_wrapped_value(DataStoreType& store, uint64_t value) noexcept { + store.holdElem(entry_ref_from_value(value), 1); + } + static ValueType unwrap_from_key_value(const DataStoreType& store, [[maybe_unused]] uint64_t key, uint64_t value) { + return store.getEntry(entry_ref_from_value(value)); + } + static ConstValueRef unwrap_const_ref_from_key_value(const DataStoreType& store, [[maybe_unused]] uint64_t key, uint64_t value) { + return store.getEntry(entry_ref_from_value(value)); + } +}; + +template <typename T> +BTreeLockableMap<T>::BTreeLockableMap() + : _impl(std::make_unique<GenericBTreeBucketDatabase<ValueTraits>>()) +{} + +template <typename T> +BTreeLockableMap<T>::~BTreeLockableMap() = default; + +template <typename T> +BTreeLockableMap<T>::LockIdSet::LockIdSet() : Hash() {} + +template <typename T> +BTreeLockableMap<T>::LockIdSet::~LockIdSet() = default; + +template <typename T> +size_t BTreeLockableMap<T>::LockIdSet::getMemoryUsage() const { + return Hash::getMemoryConsumption(); +} + +template <typename T> +BTreeLockableMap<T>::LockWaiters::LockWaiters() : _id(0), _map() {} + +template <typename T> +BTreeLockableMap<T>::LockWaiters::~LockWaiters() = default; + +template <typename T> +size_t BTreeLockableMap<T>::LockWaiters::insert(const LockId & lid) { + Key id(_id++); + _map.insert(typename WaiterMap::value_type(id, lid)); + return id; +} + +template <typename T> +bool BTreeLockableMap<T>::operator==(const BTreeLockableMap& other) const { + std::lock_guard guard(_lock); + std::lock_guard guard2(other._lock); + if (_impl->size() != other._impl->size()) { + return false; + } + auto lhs = _impl->begin(); + auto rhs = other._impl->begin(); + for (; lhs.valid(); ++lhs, ++rhs) { + assert(rhs.valid()); + if (lhs.getKey() != rhs.getKey()) { + return false; + } + if (_impl->const_value_ref_from_valid_iterator(lhs) + != other._impl->const_value_ref_from_valid_iterator(rhs)) + { + return false; + } + } + return true; +} + +template <typename T> +bool BTreeLockableMap<T>::operator<(const BTreeLockableMap& other) const { + std::lock_guard guard(_lock); + std::lock_guard guard2(other._lock); + auto lhs = _impl->begin(); + auto rhs = other._impl->begin(); + for (; lhs.valid() && rhs.valid(); ++lhs, ++rhs) { + if (lhs.getKey() != rhs.getKey()) { + return (lhs.getKey() < rhs.getKey()); + } + if (_impl->const_value_ref_from_valid_iterator(lhs) + != other._impl->const_value_ref_from_valid_iterator(rhs)) + { + return (_impl->const_value_ref_from_valid_iterator(lhs) + < other._impl->const_value_ref_from_valid_iterator(rhs)); + } + } + if (lhs.valid() == rhs.valid()) { + return false; // All keys are equal in maps of equal size. + } + return rhs.valid(); // Rhs still valid, lhs is not; ergo lhs is "less". +} + +template <typename T> +size_t BTreeLockableMap<T>::size() const noexcept { + std::lock_guard guard(_lock); + return _impl->size(); +} + +template <typename T> +size_t BTreeLockableMap<T>::getMemoryUsage() const noexcept { + std::lock_guard guard(_lock); + const auto impl_usage = _impl->memory_usage(); + return (impl_usage.allocatedBytes() + _lockedKeys.getMemoryUsage() + + sizeof(std::mutex) + sizeof(std::condition_variable)); +} + +template <typename T> +bool BTreeLockableMap<T>::empty() const noexcept { + std::lock_guard guard(_lock); + return _impl->empty(); +} + +template <typename T> +void BTreeLockableMap<T>::swap(BTreeLockableMap& other) { + std::lock_guard guard(_lock); + std::lock_guard guard2(other._lock); + _impl.swap(other._impl); +} + +template <typename T> +void BTreeLockableMap<T>::acquireKey(const LockId& lid, std::unique_lock<std::mutex>& guard) { + if (_lockedKeys.exists(lid)) { + auto waitId = _lockWaiters.insert(lid); + while (_lockedKeys.exists(lid)) { + _cond.wait(guard); + } + _lockWaiters.erase(waitId); + } +} + +template <typename T> +typename BTreeLockableMap<T>::WrappedEntry +BTreeLockableMap<T>::get(const key_type& key, const char* clientId, bool createIfNonExisting) { + LockId lid(key, clientId); + std::unique_lock guard(_lock); + acquireKey(lid, guard); + auto iter = _impl->find(key); + bool preExisted = iter.valid(); + + if (!preExisted && createIfNonExisting) { + _impl->update_by_raw_key(key, mapped_type()); + // TODO avoid double lookup, though this is in an unlikely branch so shouldn't matter much. + iter = _impl->find(key); + assert(iter.valid()); + } + if (!iter.valid()) { + return WrappedEntry(); + } + _lockedKeys.insert(lid); + return WrappedEntry(*this, key, _impl->entry_from_iterator(iter), clientId, preExisted); +} + +template <typename T> +bool BTreeLockableMap<T>::erase(const key_type& key, const char* client_id, bool has_lock) { + LockId lid(key, client_id); + std::unique_lock guard(_lock); + if (!has_lock) { + acquireKey(lid, guard); + } + return _impl->remove_by_raw_key(key); +} + +template <typename T> +void BTreeLockableMap<T>::insert(const key_type& key, const mapped_type& value, + const char* clientId, bool has_lock, bool& pre_existed) +{ + LockId lid(key, clientId); + std::unique_lock guard(_lock); + if (!has_lock) { + acquireKey(lid, guard); + } + pre_existed = _impl->update_by_raw_key(key, value); +} + +template <typename T> +void BTreeLockableMap<T>::clear() { + std::lock_guard guard(_lock); + _impl->clear(); +} + +template <typename T> +bool BTreeLockableMap<T>::findNextKey(key_type& key, mapped_type& val, + const char* clientId, + std::unique_lock<std::mutex> &guard) +{ + // Wait for next value to unlock. + auto it = _impl->lower_bound(key); + while (it.valid() && _lockedKeys.exists(LockId(it.getKey(), ""))) { + auto wait_id = _lockWaiters.insert(LockId(it.getKey(), clientId)); + _cond.wait(guard); + _lockWaiters.erase(wait_id); + it = _impl->lower_bound(key); + } + if (!it.valid()) { + return true; + } + key = it.getKey(); + val = _impl->entry_from_iterator(it); + return false; +} + +template <typename T> +bool BTreeLockableMap<T>::handleDecision(key_type& key, mapped_type& val, + Decision decision) +{ + switch (decision) { + case Decision::UPDATE: + _impl->update_by_raw_key(key, val); + break; + case Decision::REMOVE: + // Invalidating is fine, since the caller doesn't hold long-lived iterators. + _impl->remove_by_raw_key(key); + break; + case Decision::ABORT: + return true; + case Decision::CONTINUE: + break; + default: + HDR_ABORT("should not be reached"); + } + return false; +} + +template <typename T> +void BTreeLockableMap<T>::do_for_each_mutable(std::function<Decision(uint64_t, mapped_type&)> func, + const char* clientId, + const key_type& first, + const key_type& last) +{ + key_type key = first; + mapped_type val; + std::unique_lock guard(_lock); + while (true) { + if (findNextKey(key, val, clientId, guard) || key > last) { + return; + } + Decision d(func(key, val)); + if (handleDecision(key, val, d)) { + return; + } + ++key; + } +} + +template <typename T> +void BTreeLockableMap<T>::do_for_each(std::function<Decision(uint64_t, const mapped_type&)> func, + const char* clientId, + const key_type& first, + const key_type& last) +{ + key_type key = first; + mapped_type val; + std::unique_lock guard(_lock); + while (true) { + if (findNextKey(key, val, clientId, guard) || key > last) { + return; + } + Decision d(func(key, val)); + assert(d == Decision::ABORT || d == Decision::CONTINUE); + if (handleDecision(key, val, d)) { + return; + } + ++key; + } +} + +template <typename T> +bool BTreeLockableMap<T>::processNextChunk(std::function<Decision(uint64_t, mapped_type&)>& func, + key_type& key, + const char* client_id, + const uint32_t chunk_size) +{ + mapped_type val; + std::unique_lock guard(_lock); + for (uint32_t processed = 0; processed < chunk_size; ++processed) { + if (findNextKey(key, val, client_id, guard)) { + return false; + } + Decision d(func(const_cast<const key_type&>(key), val)); + if (handleDecision(key, val, d)) { + return false; + } + ++key; + } + return true; +} + +template <typename T> +void BTreeLockableMap<T>::do_for_each_chunked(std::function<Decision(uint64_t, mapped_type&)> func, + const char* client_id, + vespalib::duration yield_time, + uint32_t chunk_size) +{ + key_type key{}; + while (processNextChunk(func, key, client_id, chunk_size)) { + // Rationale: delay iteration for as short a time as possible while + // allowing another thread blocked on the main DB mutex to acquire it + // in the meantime. Simply yielding the thread does not have the + // intended effect with the Linux scheduler. + // This is a pragmatic stop-gap solution; a more robust change requires + // the redesign of bucket DB locking and signalling semantics in the + // face of blocked point lookups. + std::this_thread::sleep_for(yield_time); + } +} + +template <typename T> +void BTreeLockableMap<T>::print(std::ostream& out, bool verbose, + const std::string& indent) const +{ + std::lock_guard guard(_lock); + out << "BTreeLockableMap {\n" << indent << " "; + + if (verbose) { + for (auto it = _impl->begin(); it.valid(); ++it) { + out << "Key: " << BucketId(BucketId::keyToBucketId(it.getKey())) + << " Value: " << _impl->entry_from_iterator(it) << "\n" << indent << " "; + } + out << "\n" << indent << " Locked keys: "; + _lockedKeys.print(out, verbose, indent + " "); + } + out << "} : "; +} + +template <typename T> +void BTreeLockableMap<T>::LockIdSet::print(std::ostream& out, bool verbose, + const std::string& indent) const +{ + out << "hash {"; + for (const auto& entry : *this) { + if (verbose) { + out << "\n" << indent << " "; + } else { + out << " "; + } + out << entry; + } + if (verbose) { + out << "\n" << indent; + } + out << " }"; +} + + + +template <typename T> +void BTreeLockableMap<T>::unlock(const key_type& key) { + std::lock_guard guard(_lock); + _lockedKeys.erase(LockId(key, "")); + _cond.notify_all(); +} + +template <typename T> +void BTreeLockableMap<T>::addAndLockResults( + const std::vector<BucketId::Type>& keys, + const char* clientId, + std::map<BucketId, WrappedEntry>& results, + std::unique_lock<std::mutex> &guard) +{ + // Wait until all buckets are free to be added, then add them all. + while (true) { + bool allOk = true; + key_type waitingFor(0); + + for (const auto key : keys) { + if (_lockedKeys.exists(LockId(key, clientId))) { + waitingFor = key; + allOk = false; + break; + } + } + + if (!allOk) { + auto waitId = _lockWaiters.insert(LockId(waitingFor, clientId)); + _cond.wait(guard); + _lockWaiters.erase(waitId); + } else { + for (const auto key : keys) { + auto iter = _impl->find(key); + if (iter.valid()) { + _lockedKeys.insert(LockId(key, clientId)); + results[BucketId(BucketId::keyToBucketId(key))] = WrappedEntry( + *this, key, _impl->entry_from_iterator(iter), clientId, true); + } + } + break; + } + } +} + +template <typename T> +typename BTreeLockableMap<T>::EntryMap +BTreeLockableMap<T>::getContained(const BucketId& bucket, + const char* clientId) +{ + std::unique_lock guard(_lock); + std::map<BucketId, WrappedEntry> results; + + std::vector<BucketId::Type> keys; + _impl->find_parents_and_self(bucket, [&keys](uint64_t key, [[maybe_unused]]const auto& value){ + keys.emplace_back(key); + }); + + if (!keys.empty()) { + addAndLockResults(keys, clientId, results, guard); + } + + return results; +} + +template <typename T> +void BTreeLockableMap<T>::getAllWithoutLocking(const BucketId& bucket, + std::vector<BucketId::Type>& keys) +{ + _impl->find_parents_self_and_children(bucket, [&keys](uint64_t key, [[maybe_unused]]const auto& value){ + keys.emplace_back(key); + }); +} + +/** + * Returns the given bucket, its super buckets and its sub buckets. + */ +template <typename T> +typename BTreeLockableMap<T>::EntryMap +BTreeLockableMap<T>::getAll(const BucketId& bucket, const char* clientId) { + std::unique_lock guard(_lock); + + std::map<BucketId, WrappedEntry> results; + std::vector<BucketId::Type> keys; + + getAllWithoutLocking(bucket, keys); + addAndLockResults(keys, clientId, results, guard); + + return results; +} + +template <typename T> +bool BTreeLockableMap<T>::isConsistent(const BTreeLockableMap::WrappedEntry& entry) { + std::lock_guard guard(_lock); + uint64_t n_buckets = 0; + _impl->find_parents_self_and_children(entry.getBucketId(), + [&n_buckets]([[maybe_unused]] uint64_t key, [[maybe_unused]] const auto& value) { + ++n_buckets; + }); + return (n_buckets == 1); +} + +template <typename T> +void BTreeLockableMap<T>::showLockClients(vespalib::asciistream& out) const { + std::lock_guard guard(_lock); + out << "Currently grabbed locks:"; + for (const auto& locked : _lockedKeys) { + out << "\n " + << BucketId(BucketId::keyToBucketId(locked._key)) + << " - " << locked._owner; + } + out << "\nClients waiting for keys:"; + for (const auto& waiter : _lockWaiters) { + out << "\n " + << BucketId(BucketId::keyToBucketId(waiter.second._key)) + << " - " << waiter.second._owner; + } +} + +} diff --git a/storage/src/vespa/storage/bucketdb/bucketmanager.cpp b/storage/src/vespa/storage/bucketdb/bucketmanager.cpp index c8cdaaaaa23..4dbeb5a9a22 100644 --- a/storage/src/vespa/storage/bucketdb/bucketmanager.cpp +++ b/storage/src/vespa/storage/bucketdb/bucketmanager.cpp @@ -116,12 +116,12 @@ namespace { : _state(*distribution, systemState), _result(result), _factory(factory), - _storageDistribution(distribution) + _storageDistribution(std::move(distribution)) { } StorBucketDatabase::Decision operator()(uint64_t bucketId, - StorBucketDatabase::Entry& data) + const StorBucketDatabase::Entry& data) { document::BucketId b(document::BucketId::keyToBucketId(bucketId)); try{ @@ -155,7 +155,7 @@ namespace { .getDistributionConfigHash().c_str(), _state.getClusterState().toString().c_str()); } - return StorBucketDatabase::CONTINUE; + return StorBucketDatabase::Decision::CONTINUE; } }; @@ -180,7 +180,7 @@ namespace { StorBucketDatabase::Decision operator()( document::BucketId::Type bucketId, - StorBucketDatabase::Entry& data) + const StorBucketDatabase::Entry& data) { document::BucketId bucket( document::BucketId::keyToBucketId(bucketId)); @@ -202,7 +202,7 @@ namespace { } } - return StorBucketDatabase::CONTINUE; + return StorBucketDatabase::Decision::CONTINUE; }; void add(const MetricsUpdater& rhs) { @@ -242,7 +242,7 @@ BucketManager::updateMetrics(bool updateDocCount) MetricsUpdater total(diskCount); for (auto& space : _component.getBucketSpaceRepo()) { MetricsUpdater m(diskCount); - space.second->bucketDatabase().chunkedAll(m, "BucketManager::updateMetrics"); + space.second->bucketDatabase().for_each_chunked(std::ref(m), "BucketManager::updateMetrics"); total.add(m); if (updateDocCount) { auto bm = _metrics->bucket_spaces.find(space.first); @@ -338,10 +338,10 @@ namespace { class BucketDBDumper { vespalib::XmlOutputStream& _xos; public: - BucketDBDumper(vespalib::XmlOutputStream& xos) : _xos(xos) {} + explicit BucketDBDumper(vespalib::XmlOutputStream& xos) : _xos(xos) {} StorBucketDatabase::Decision operator()( - uint64_t bucketId, StorBucketDatabase::Entry& info) + uint64_t bucketId, const StorBucketDatabase::Entry& info) { using namespace vespalib::xml; document::BucketId bucket( @@ -356,7 +356,7 @@ namespace { info.getBucketInfo().printXml(_xos); _xos << XmlAttribute("disk", info.disk); _xos << XmlEndTag(); - return StorBucketDatabase::CONTINUE; + return StorBucketDatabase::Decision::CONTINUE; }; }; } @@ -378,8 +378,8 @@ BucketManager::reportStatus(std::ostream& out, xmlReporter << XmlTag("bucket-space") << XmlAttribute("name", document::FixedBucketSpaces::to_string(space.first)); BucketDBDumper dumper(xmlReporter.getStream()); - _component.getBucketSpaceRepo().get(space.first).bucketDatabase().chunkedAll( - dumper, "BucketManager::reportStatus"); + _component.getBucketSpaceRepo().get(space.first).bucketDatabase().for_each_chunked( + std::ref(dumper), "BucketManager::reportStatus"); xmlReporter << XmlEndTag(); } xmlReporter << XmlEndTag(); @@ -655,12 +655,12 @@ BucketManager::processRequestBucketInfoCommands(document::BucketSpace bucketSpac if (LOG_WOULD_LOG(spam)) { DistributorInfoGatherer<true> builder( *clusterState, result, idFac, distribution); - _component.getBucketDatabase(bucketSpace).chunkedAll(builder, + _component.getBucketDatabase(bucketSpace).for_each_chunked(std::ref(builder), "BucketManager::processRequestBucketInfoCommands-1"); } else { DistributorInfoGatherer<false> builder( *clusterState, result, idFac, distribution); - _component.getBucketDatabase(bucketSpace).chunkedAll(builder, + _component.getBucketDatabase(bucketSpace).for_each_chunked(std::ref(builder), "BucketManager::processRequestBucketInfoCommands-2"); } _metrics->fullBucketInfoLatency.addValue( diff --git a/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.cpp b/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.cpp new file mode 100644 index 00000000000..bcc471cc903 --- /dev/null +++ b/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.cpp @@ -0,0 +1,36 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "generic_btree_bucket_database.h" + +namespace storage::bucketdb { + +using document::BucketId; + +// TODO dedupe and unify common code +uint8_t +getMinDiffBits(uint16_t minBits, const BucketId& a, const BucketId& b) { + for (uint32_t i = minBits; i <= std::min(a.getUsedBits(), b.getUsedBits()); i++) { + BucketId a1(i, a.getRawId()); + BucketId b1(i, b.getRawId()); + if (b1.getId() != a1.getId()) { + return i; + } + } + return minBits; +} + +uint8_t next_parent_bit_seek_level(uint8_t minBits, const BucketId& a, const BucketId& b) { + const uint8_t min_used = std::min(a.getUsedBits(), b.getUsedBits()); + assert(min_used >= minBits); // Always monotonically descending towards leaves + for (uint32_t i = minBits; i <= min_used; i++) { + BucketId a1(i, a.getRawId()); + BucketId b1(i, b.getRawId()); + if (b1.getId() != a1.getId()) { + return i; + } + } + // The bit prefix is equal, which means that one node is a parent of the other. In this + // case we have to force the seek to continue from the next level in the tree. + return std::max(min_used, minBits) + 1; +} + +} diff --git a/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.h b/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.h new file mode 100644 index 00000000000..15de7f3525b --- /dev/null +++ b/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.h @@ -0,0 +1,242 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/document/bucket/bucketid.h> +#include <vespa/vespalib/btree/btree.h> +#include <vespa/vespalib/btree/minmaxaggregated.h> +#include <vespa/vespalib/btree/minmaxaggrcalc.h> + +namespace storage::bucketdb { + +/** + * Database implementation-specific interface for appending entries + * during a merge() operation. + */ +template <typename ValueT> +struct TrailingInserter { + virtual ~TrailingInserter() = default; + /** + * Insert a new database entry at the end of the current bucket space. + * + * Precondition: the bucket ID must sort after all entries that + * have already been iterated over or inserted via insert_at_end(). + */ + virtual void insert_at_end(const document::BucketId& bucket_id, const ValueT&) = 0; +}; + +/** + * Database implementation-specific interface for accessing bucket + * entries and prepending entries during a merge() operation. + */ +template <typename ValueT> +struct Merger { + virtual ~Merger() = default; + + // TODO this should ideally be separated into read/write functions, but this + // will suffice for now to avoid too many changes. + + /** + * Bucket key/ID of the currently iterated entry. Unless the information stored + * in the DB Entry is needed, using one of these methods should be preferred to + * getting the bucket ID via current_entry(). The underlying DB is expected to + * have cheap access to the ID but _may_ have expensive access to the entry itself. + */ + [[nodiscard]] virtual uint64_t bucket_key() const noexcept = 0; + [[nodiscard]] virtual document::BucketId bucket_id() const noexcept = 0; + /** + * Returns a mutable representation of the currently iterated database + * entry. If changes are made to this object, Result::Update must be + * returned from merge(). Otherwise, mutation visibility is undefined. + */ + [[nodiscard]] virtual ValueT& current_entry() = 0; + /** + * Insert a new entry into the bucket database that is ordered before the + * currently iterated entry. + * + * Preconditions: + * - The bucket ID must sort _before_ the currently iterated + * entry's bucket ID, in "reversed bits" bucket key order. + * - The bucket ID must sort _after_ any entries previously + * inserted with insert_before_current(). + * - The bucket ID must not be the same as a bucket that was + * already iterated over as part of the DB merge() call or inserted + * via a previous call to insert_before_current(). + * Such buckets must be handled by explicitly updating the provided + * entry for the iterated bucket and returning Result::Update. + */ + virtual void insert_before_current(const document::BucketId& bucket_id, const ValueT&) = 0; +}; + +/** + * Interface to be implemented by callers that wish to receive callbacks + * during a bucket merge() operation. + */ +template <typename ValueT> +struct MergingProcessor { + // See merge() for semantics on enum values. + enum class Result { + Update, + KeepUnchanged, + Skip + }; + + virtual ~MergingProcessor() = default; + /** + * Invoked for each existing bucket in the database, in bucket key order. + * The provided Merge instance may be used to access the current entry + * and prepend entries to the DB. + * + * Return value semantics: + * - Result::Update: + * when merge() returns, the changes made to the current entry will + * become visible in the bucket database. + * - Result::KeepUnchanged: + * when merge() returns, the entry will remain in the same state as + * it was when merge() was originally called. + * - Result::Skip: + * when merge() returns, the entry will no longer be part of the DB. + * Any entries added via insert_before_current() _will_ be present. + * + */ + virtual Result merge(Merger<ValueT>&) = 0; + /** + * Invoked once after all existing buckets have been iterated over. + * The provided TrailingInserter instance may be used to append + * an arbitrary number of entries to the database. + * + * This is used to handle elements remaining at the end of a linear + * merge operation. + */ + virtual void insert_remaining_at_end(TrailingInserter<ValueT>&) {} +}; + +/* + * Bucket database implementation built around lock-free single-writer/multiple-readers B+tree. + * + * Key is always treated as a 64-bit uint bucket ID key. + * Value is a 64-bit uint whose semantics are handled by the provided DataStoreTraitsT. + * All DataStore access and value type (un)marshalling is deferred to the traits type, + * allowing this class to be used for both fixed-sized and dynamic-sized value types. + * + * Buckets in our tree are represented by their 64-bit numeric key, in what's known as + * "reversed bit order with appended used-bits" form. I.e. a bucket ID (16, 0xcafe), which + * in its canonical representation has 16 (the used-bits) in its 6 MSBs and 0xcafe in its + * LSBs is transformed into 0x7f53000000000010. This key is logically comprised of two parts: + * - the reversed bucket ID itself (0xcafe - 0x7f53) with all trailing zeroes for unset bits + * - the _non-reversed_ used-bits appended as the LSBs + * + * This particular transformation gives us keys with the following invariants: + * - all distinct bucket IDs map to exactly 1 key + * - buckets with the same ID but different used-bits are ordered in such a way that buckets + * with higher used-bits sort after buckets with lower used-bits + * - the key ordering represents an implicit in-order traversal of the binary bucket tree + * - consequently, all parent buckets are ordered before their child buckets + * + * The in-order traversal invariant is fundamental to many of the algorithms that operate + * on the bucket tree. + */ +template <typename DataStoreTraitsT> +class GenericBTreeBucketDatabase { +public: + using DataStoreType = typename DataStoreTraitsT::DataStoreType; + using ValueType = typename DataStoreTraitsT::ValueType; + using ConstValueRef = typename DataStoreTraitsT::ConstValueRef; + using GenerationHandler = vespalib::GenerationHandler; + + struct KeyUsedBitsMinMaxAggrCalc : vespalib::btree::MinMaxAggrCalc { + constexpr static bool aggregate_over_values() { return false; } + constexpr static int32_t getVal(uint64_t key) noexcept { + static_assert(document::BucketId::CountBits == 6u); + return static_cast<int32_t>(key & 0b11'1111U); // 6 LSB of key contains used-bits + } + }; + + using BTree = vespalib::btree::BTree<uint64_t, uint64_t, + vespalib::btree::MinMaxAggregated, + std::less<>, + vespalib::btree::BTreeDefaultTraits, + KeyUsedBitsMinMaxAggrCalc>; + using BTreeConstIterator = typename BTree::ConstIterator; + + BTree _tree; + DataStoreType _store; + GenerationHandler _generation_handler; + + template <typename... DataStoreArgs> + explicit GenericBTreeBucketDatabase(DataStoreArgs&&... data_store_args) + : _store(std::forward<DataStoreArgs>(data_store_args)...) + {} + + GenericBTreeBucketDatabase(const GenericBTreeBucketDatabase&) = delete; + GenericBTreeBucketDatabase& operator=(const GenericBTreeBucketDatabase&) = delete; + GenericBTreeBucketDatabase(GenericBTreeBucketDatabase&&) = delete; + GenericBTreeBucketDatabase& operator=(GenericBTreeBucketDatabase&&) = delete; + + // TODO move + struct EntryProcessor { + virtual ~EntryProcessor() = default; + /** Return false to stop iterating. */ + virtual bool process(const typename DataStoreTraitsT::ConstValueRef& e) = 0; + }; + + ValueType entry_from_iterator(const BTreeConstIterator& iter) const; + ConstValueRef const_value_ref_from_valid_iterator(const BTreeConstIterator& iter) const; + + static document::BucketId bucket_from_valid_iterator(const BTreeConstIterator& iter); + + BTreeConstIterator find(uint64_t key) const noexcept; + BTreeConstIterator lower_bound(uint64_t key) const noexcept; + BTreeConstIterator begin() const noexcept; + + void clear() noexcept; + [[nodiscard]] size_t size() const noexcept; + [[nodiscard]] bool empty() const noexcept; + [[nodiscard]] vespalib::MemoryUsage memory_usage() const noexcept; + + ValueType get(const document::BucketId& bucket) const; + ValueType get_by_raw_key(uint64_t key) const; + // Return true if bucket existed in DB, false otherwise. + bool remove(const document::BucketId& bucket); + bool remove_by_raw_key(uint64_t key); + // Returns true if bucket pre-existed in the DB, false otherwise + bool update(const document::BucketId& bucket, const ValueType& new_entry); + bool update_by_raw_key(uint64_t bucket_key, const ValueType& new_entry); + + template <typename Func> + void find_parents_and_self(const document::BucketId& bucket, + Func func) const; + + template <typename Func> + void find_parents_self_and_children(const document::BucketId& bucket, + Func func) const; + + void for_each(EntryProcessor& proc, const document::BucketId& after) const; + + document::BucketId getAppropriateBucket(uint16_t minBits, const document::BucketId& bid) const; + + [[nodiscard]] uint32_t child_subtree_count(const document::BucketId& bucket) const; + + const DataStoreType& store() const noexcept { return _store; } + DataStoreType& store() noexcept { return _store; } + + void merge(MergingProcessor<ValueType>& proc); +private: + // Functor is called for each found element in key order, with raw u64 keys and values. + template <typename Func> + BTreeConstIterator find_parents_internal(const typename BTree::FrozenView& frozen_view, + const document::BucketId& bucket, + Func func) const; + template <typename Func> + void find_parents_and_self_internal(const typename BTree::FrozenView& frozen_view, + const document::BucketId& bucket, + Func func) const; + void commit_tree_changes(); + + template <typename DataStoreTraitsT2> friend struct BTreeBuilderMerger; + template <typename DataStoreTraitsT2> friend struct BTreeTrailingInserter; +}; + +uint8_t getMinDiffBits(uint16_t minBits, const document::BucketId& a, const document::BucketId& b); +uint8_t next_parent_bit_seek_level(uint8_t minBits, const document::BucketId& a, const document::BucketId& b); + +} diff --git a/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.hpp b/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.hpp new file mode 100644 index 00000000000..4b1b507d95a --- /dev/null +++ b/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.hpp @@ -0,0 +1,494 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include "generic_btree_bucket_database.h" + +namespace storage::bucketdb { + +using document::BucketId; + +template <typename DataStoreTraitsT> +BucketId GenericBTreeBucketDatabase<DataStoreTraitsT>::bucket_from_valid_iterator(const BTreeConstIterator& iter) { + return BucketId(BucketId::keyToBucketId(iter.getKey())); +} + +template <typename DataStoreTraitsT> +void GenericBTreeBucketDatabase<DataStoreTraitsT>::commit_tree_changes() { + // TODO break up and refactor + // TODO verify semantics and usage + // TODO make BTree wrapping API which abstracts away all this stuff via reader/writer interfaces + _tree.getAllocator().freeze(); + + auto current_gen = _generation_handler.getCurrentGeneration(); + _store.transferHoldLists(current_gen); + _tree.getAllocator().transferHoldLists(current_gen); + + _generation_handler.incGeneration(); + + auto used_gen = _generation_handler.getFirstUsedGeneration(); + _store.trimHoldLists(used_gen); + _tree.getAllocator().trimHoldLists(used_gen); +} + +template <typename DataStoreTraitsT> +void GenericBTreeBucketDatabase<DataStoreTraitsT>::clear() noexcept { + _tree.clear(); + commit_tree_changes(); +} + +template <typename DataStoreTraitsT> +size_t GenericBTreeBucketDatabase<DataStoreTraitsT>::size() const noexcept { + return _tree.size(); +} + +template <typename DataStoreTraitsT> +bool GenericBTreeBucketDatabase<DataStoreTraitsT>::empty() const noexcept { + return !_tree.begin().valid(); +} + +template <typename DataStoreTraitsT> +vespalib::MemoryUsage GenericBTreeBucketDatabase<DataStoreTraitsT>::memory_usage() const noexcept { + auto mem_usage = _tree.getMemoryUsage(); + mem_usage.merge(_store.getMemoryUsage()); + return mem_usage; +} + +template <typename DataStoreTraitsT> +typename GenericBTreeBucketDatabase<DataStoreTraitsT>::ValueType +GenericBTreeBucketDatabase<DataStoreTraitsT>::entry_from_iterator(const BTreeConstIterator& iter) const { + if (!iter.valid()) { + return DataStoreTraitsT::make_invalid_value(); + } + const auto value = iter.getData(); + std::atomic_thread_fence(std::memory_order_acquire); + return DataStoreTraitsT::unwrap_from_key_value(_store, iter.getKey(), value); +} + +template <typename DataStoreTraitsT> +typename GenericBTreeBucketDatabase<DataStoreTraitsT>::ConstValueRef +GenericBTreeBucketDatabase<DataStoreTraitsT>::const_value_ref_from_valid_iterator(const BTreeConstIterator& iter) const { + const auto value = iter.getData(); + std::atomic_thread_fence(std::memory_order_acquire); + return DataStoreTraitsT::unwrap_const_ref_from_key_value(_store, iter.getKey(), value); +} + +template <typename DataStoreTraitsT> +typename GenericBTreeBucketDatabase<DataStoreTraitsT>::BTreeConstIterator +GenericBTreeBucketDatabase<DataStoreTraitsT>::lower_bound(uint64_t key) const noexcept { + return _tree.lowerBound(key); +} + +template <typename DataStoreTraitsT> +typename GenericBTreeBucketDatabase<DataStoreTraitsT>::BTreeConstIterator +GenericBTreeBucketDatabase<DataStoreTraitsT>::find(uint64_t key) const noexcept { + return _tree.find(key); +} + +template <typename DataStoreTraitsT> +typename GenericBTreeBucketDatabase<DataStoreTraitsT>::BTreeConstIterator +GenericBTreeBucketDatabase<DataStoreTraitsT>::begin() const noexcept { + return _tree.begin(); +} + +/* + * Finding the complete set of parents of a given bucket is not obvious how to + * do efficiently, as we only know that the parents are ordered before their + * children, but we do not a-priori know if any exist at all. The Judy DB impl + * does O(b) explicit point lookups (where b is the number of used bits in the + * bucket), starting at the leaf bit and working towards the root. To avoid + * having to re-create iterators and perform a full tree search every time, we + * turn this on its head and start from the root, progressing towards the leaf. + * This allows us to reuse a single iterator and to continue seeking forwards + * from its current position. + * + * To speed up the process of converging on the target bucket without needing + * to check many unrelated subtrees, we let the underlying B-tree automatically + * aggregate the min/max range of the used-bits of all contained bucket keys. + * If we e.g. know that the minimum number of used bits in the DB is 16, we can + * immediately seek to this level in the tree instead of working our way down + * one bit at a time. By definition, no parents can exist above this level. + * This is a very important optimization, as bucket trees are usually very well + * balanced due to randomized distribution of data (combined with a cluster-wide + * minimum tree level imposed by distribution bits). It is common that the minimum + * number of used bits == max number of used bits, i.e. a totally even split. + * This means that for a system without inconsistently split buckets (i.e. no + * parents) we're highly likely to converge on the target bucket in a single seek. + * + * Algorithm: + * + * Core invariant: every subsequent iterator seek performed in this algorithm + * is for a key that is strictly higher than the one the iterator is currently at. + * + * 1. Lbound seek to the lowest key that is known to exclude all already visited + * parents. On the first iteration we use a bit count equal to the minimum number + * of key used-bits in the entire DB, allowing us to potentially skip most subtrees. + * 2. If the current node's key is greater than that of the requested bucket's key, + * we've either descended to--or beyond--it in its own subtree or we've entered + * a disjoint subtree. Since we know that all parents must sort before any given + * child bucket, no more parents may be found at this point. Algorithm terminates. + * 3. As the main body of the loop is entered, we know one of following must hold: + * 3.1 The current node is an explicitly present parent of our bucket. + * 3.2 The current node is contained in a left subtree branch of a parent that + * does not have a bucket explicitly present in the tree. It cannot be in + * a right subtree of any parent, as that would imply the node is ordered + * _after_ our own bucket in an in-order traversal, which would contradict + * the check in step 2 above. + * 4. If the current node contains the requested bucket, we're at a parent + * node of the bucket; add it to the result set. + * If this is _not_ the case, we're in a different subtree. Example: the + * requested bucket has a key whose MSB is 1 but the first bucket in the + * tree has a key with an MSB of 0. Either way we need to update our search + * key to home in on the target subtree where more parents may be found; + * 5. Update the seek key to find the next possible parent. To ensure this key is + * strictly greater than the iterator's current key we find the largest shared + * prefix of bits in common between the current node's key and the requested + * bucket's key. The prefix length + 1 is then the depth in the tree at which the + * two subtrees branch off and diverge. + * The new key is then the MSB prefix length + 1 requested bucket's key with a + * matching number of used-bits set. Forward lbound-seek the iterator to this key. + * `--> TODO elaborate on prefix semantics when they are equal wrt. min used bits + * 6. Iff iterator is still valid, go to step 2 + * + * This algorithm is able to skip through large parts of the tree in a sparsely populated + * tree, but the number of seeks will trend towards O(b - min_bits) as with the legacy + * implementation when a tree is densely populated (where `b` is the used-bits count of the + * most specific node in the tree for the target bucket, and min_bits is the minimum number + * of used-bits for any key in the database). This because all logical inner nodes in the tree + * will have subtrees under them. Even in the worst case we should be more efficient than the + * legacy Judy-based implementation since we've cut any dense search space in half for each + * invocation of seek() on the iterator. + */ +template <typename DataStoreTraitsT> +template <typename Func> +typename GenericBTreeBucketDatabase<DataStoreTraitsT>::BTreeConstIterator +GenericBTreeBucketDatabase<DataStoreTraitsT>::find_parents_internal( + const typename BTree::FrozenView& frozen_view, + const BucketId& bucket, + Func func) const +{ + const uint64_t bucket_key = bucket.toKey(); + if (frozen_view.empty()) { + return frozen_view.begin(); // Will be invalid. + } + const auto min_db_bits = frozen_view.getAggregated().getMin(); + assert(min_db_bits >= static_cast<int32_t>(BucketId::minNumBits)); + assert(min_db_bits <= static_cast<int32_t>(BucketId::maxNumBits)); + // Start at the lowest possible tree level no parents can exist above, + // descending towards the bucket itself. + // Note: important to use getId() rather than getRawId(), as min_db_bits may be + // greater than the used bits of the queried bucket. If we used the raw ID, we'd + // end up looking at undefined bits. + const auto first_key = BucketId(min_db_bits, bucket.getId()).toKey(); + auto iter = frozen_view.lowerBound(first_key); + // Try skipping as many levels of the tree as possible as we go. + uint32_t bits = min_db_bits; + while (iter.valid() && (iter.getKey() < bucket_key)) { + auto candidate = BucketId(BucketId::keyToBucketId(iter.getKey())); + if (candidate.contains(bucket)) { + assert(candidate.getUsedBits() >= bits); + func(iter.getKey(), const_value_ref_from_valid_iterator(iter)); + } + bits = next_parent_bit_seek_level(bits, candidate, bucket); + const auto parent_key = BucketId(bits, bucket.getRawId()).toKey(); + assert(parent_key > iter.getKey()); + iter.seek(parent_key); + } + return iter; +} + +template <typename DataStoreTraitsT> +template <typename Func> +void GenericBTreeBucketDatabase<DataStoreTraitsT>::find_parents_and_self_internal( + const typename BTree::FrozenView& frozen_view, + const BucketId& bucket, + Func func) const +{ + auto iter = find_parents_internal(frozen_view, bucket, func); + if (iter.valid() && iter.getKey() == bucket.toKey()) { + func(iter.getKey(), entry_from_iterator(iter)); + } +} + +template <typename DataStoreTraitsT> +template <typename Func> +void GenericBTreeBucketDatabase<DataStoreTraitsT>::find_parents_and_self( + const document::BucketId& bucket, + Func func) const +{ + auto view = _tree.getFrozenView(); + find_parents_and_self_internal(view, bucket, std::move(func)); +} + +template <typename DataStoreTraitsT> +template <typename Func> +void GenericBTreeBucketDatabase<DataStoreTraitsT>::find_parents_self_and_children( + const BucketId& bucket, + Func func) const +{ + auto view = _tree.getFrozenView(); + auto iter = find_parents_internal(view, bucket, func); + // `iter` is already pointing at, or beyond, one of the bucket's subtrees. + for (; iter.valid(); ++iter) { + auto candidate = BucketId(BucketId::keyToBucketId(iter.getKey())); + if (bucket.contains(candidate)) { + func(iter.getKey(), entry_from_iterator(iter)); + } else { + break; + } + } +} + +template <typename DataStoreTraitsT> +typename GenericBTreeBucketDatabase<DataStoreTraitsT>::ValueType +GenericBTreeBucketDatabase<DataStoreTraitsT>::get(const BucketId& bucket) const { + return entry_from_iterator(_tree.find(bucket.toKey())); +} + +template <typename DataStoreTraitsT> +typename GenericBTreeBucketDatabase<DataStoreTraitsT>::ValueType +GenericBTreeBucketDatabase<DataStoreTraitsT>::get_by_raw_key(uint64_t key) const { + return entry_from_iterator(_tree.find(key)); +} + +template <typename DataStoreTraitsT> +bool GenericBTreeBucketDatabase<DataStoreTraitsT>::remove_by_raw_key(uint64_t key) { + auto iter = _tree.find(key); + if (!iter.valid()) { + return false; + } + const auto value = iter.getData(); + DataStoreTraitsT::remove_by_wrapped_value(_store, value); + _tree.remove(iter); + commit_tree_changes(); + return true; +} + +template <typename DataStoreTraitsT> +bool GenericBTreeBucketDatabase<DataStoreTraitsT>::remove(const BucketId& bucket) { + return remove_by_raw_key(bucket.toKey()); +} + +template <typename DataStoreTraitsT> +bool GenericBTreeBucketDatabase<DataStoreTraitsT>::update_by_raw_key(uint64_t bucket_key, + const ValueType& new_entry) +{ + const auto new_value = DataStoreTraitsT::wrap_and_store_value(_store, new_entry); + auto iter = _tree.lowerBound(bucket_key); + const bool pre_existed = (iter.valid() && (iter.getKey() == bucket_key)); + if (pre_existed) { + DataStoreTraitsT::remove_by_wrapped_value(_store, iter.getData()); + // In-place update of value; does not require tree structure modification + std::atomic_thread_fence(std::memory_order_release); // Must ensure visibility when new array ref is observed + iter.writeData(new_value); + } else { + _tree.insert(iter, bucket_key, new_value); + } + commit_tree_changes(); // TODO does publishing a new root imply an implicit memory fence? + return pre_existed; +} + +template <typename DataStoreTraitsT> +bool GenericBTreeBucketDatabase<DataStoreTraitsT>::update(const BucketId& bucket, + const ValueType& new_entry) +{ + return update_by_raw_key(bucket.toKey(), new_entry); +} + +// TODO need snapshot read with guarding +// FIXME semantics of for-each in judy and bit tree DBs differ, former expects lbound, latter ubound..! +// FIXME but bit-tree code says "lowerBound" in impl and "after" in declaration??? +template <typename DataStoreTraitsT> +void GenericBTreeBucketDatabase<DataStoreTraitsT>::for_each(EntryProcessor& proc, const BucketId& after) const { + for (auto iter = _tree.upperBound(after.toKey()); iter.valid(); ++iter) { + // TODO memory fencing once we use snapshots! + if (!proc.process(DataStoreTraitsT::unwrap_const_ref_from_key_value(_store, iter.getKey(), iter.getData()))) { + break; + } + } +} + +/* + * Returns the bucket ID which, based on the buckets already existing in the DB, + * is the most specific location in the tree in which it should reside. This may + * or may not be a bucket that already exists. + * + * Example: if there is a single bucket (1, 1) in the tree, a query for (1, 1) or + * (1, 3) will return (1, 1) as that is the most specific leaf in that subtree. + * A query for (1, 0) will return (1, 0) even though this doesn't currently exist, + * as there is no existing bucket that can contain the queried bucket. It is up to + * the caller to create this bucket according to its needs. + * + * Usually this function will be called with an ID whose used-bits is at max (58), in + * order to find a leaf bucket to route an incoming document operation to. + * + * TODO rename this function, it's very much _not_ obvious what an "appropriate" bucket is..! + * TODO this should be possible to do concurrently + */ +template <typename DataStoreTraitsT> +BucketId GenericBTreeBucketDatabase<DataStoreTraitsT>::getAppropriateBucket(uint16_t minBits, const BucketId& bid) const { + // The bucket tree is ordered in such a way that it represents a + // natural in-order traversal of all buckets, with inner nodes being + // visited before leaf nodes. This means that a lower bound seek will + // never return a parent of a seeked bucket. The iterator will be pointing + // to a bucket that is either the actual bucket given as the argument to + // lowerBound() or the next in-order bucket (or end() if none exists). + // TODO snapshot + auto iter = _tree.lowerBound(bid.toKey()); + if (iter.valid()) { + // Find the first level in the tree where the paths through the bucket tree + // diverge for the target bucket and the current bucket. + minBits = getMinDiffBits(minBits, bucket_from_valid_iterator(iter), bid); + } + // TODO is it better to copy original iterator and do begin() on the copy? + auto first_iter = _tree.begin(); + // Original iterator might be in a different subtree than that of our + // target bucket. If possible, rewind one node to discover any parent or + // leftmost sibling of our node. If there's no such node, we'll still + // discover the greatest equal bit prefix. + if (iter != first_iter) { + --iter; + minBits = getMinDiffBits(minBits, bucket_from_valid_iterator(iter), bid); + } + return BucketId(minBits, bid.getRawId()); +} + +/* + * Enumerate the number of child subtrees under `bucket`. The value returned is in the + * range [0, 2] regardless of how many subtrees are present further down in the tree. + * + * Finding this number is reasonably straight forward; we construct two buckets that + * represent the key ranges for the left and right subtrees under `bucket` and check + * if there are any ranges in the tree's keyspace that are contained in these. + */ +template <typename DataStoreTraitsT> +uint32_t GenericBTreeBucketDatabase<DataStoreTraitsT>::child_subtree_count(const BucketId& bucket) const { + assert(bucket.getUsedBits() < BucketId::maxNumBits); + BucketId lhs_bucket(bucket.getUsedBits() + 1, bucket.getId()); + BucketId rhs_bucket(bucket.getUsedBits() + 1, (1ULL << bucket.getUsedBits()) | bucket.getId()); + + auto iter = _tree.lowerBound(lhs_bucket.toKey()); + if (!iter.valid()) { + return 0; + } + if (lhs_bucket.contains(bucket_from_valid_iterator(iter))) { + iter.seek(rhs_bucket.toKey()); + if (!iter.valid()) { + return 1; // lhs subtree only + } + return (rhs_bucket.contains(bucket_from_valid_iterator(iter)) ? 2 : 1); + } else if (rhs_bucket.contains(bucket_from_valid_iterator(iter))) { + return 1; // rhs subtree only + } + return 0; +} + +template <typename DataStoreTraitsT> +struct BTreeBuilderMerger final : Merger<typename DataStoreTraitsT::ValueType> { + using DBType = GenericBTreeBucketDatabase<DataStoreTraitsT>; + using ValueType = typename DataStoreTraitsT::ValueType; + using BTreeBuilderType = typename DBType::BTree::Builder; + + DBType& _db; + BTreeBuilderType& _builder; + uint64_t _current_key; + uint64_t _current_value; + ValueType _cached_value; + bool _valid_cached_value; + + BTreeBuilderMerger(DBType& db, BTreeBuilderType& builder) + : _db(db), + _builder(builder), + _current_key(0), + _current_value(0), + _cached_value(), + _valid_cached_value(false) + {} + ~BTreeBuilderMerger() override = default; + + uint64_t bucket_key() const noexcept override { + return _current_key; + } + BucketId bucket_id() const noexcept override { + return BucketId(BucketId::keyToBucketId(_current_key)); + } + ValueType& current_entry() override { + if (!_valid_cached_value) { + _cached_value = DataStoreTraitsT::unwrap_from_key_value(_db.store(), _current_key, _current_value); + _valid_cached_value = true; + } + return _cached_value; + } + void insert_before_current(const BucketId& bucket_id, const ValueType& e) override { + const uint64_t bucket_key = bucket_id.toKey(); + assert(bucket_key < _current_key); + const auto new_value = DataStoreTraitsT::wrap_and_store_value(_db.store(), e); + _builder.insert(bucket_key, new_value); + } + + void update_iteration_state(uint64_t key, uint64_t value) { + _current_key = key; + _current_value = value; + _valid_cached_value = false; + } +}; + +template <typename DataStoreTraitsT> +struct BTreeTrailingInserter final : TrailingInserter<typename DataStoreTraitsT::ValueType> { + using DBType = GenericBTreeBucketDatabase<DataStoreTraitsT>; + using ValueType = typename DataStoreTraitsT::ValueType; + using BTreeBuilderType = typename DBType::BTree::Builder; + + DBType& _db; + BTreeBuilderType& _builder; + + BTreeTrailingInserter(DBType& db, BTreeBuilderType& builder) + : _db(db), + _builder(builder) + {} + + ~BTreeTrailingInserter() override = default; + + void insert_at_end(const BucketId& bucket_id, const ValueType& e) override { + const uint64_t bucket_key = bucket_id.toKey(); + const auto new_value = DataStoreTraitsT::wrap_and_store_value(_db.store(), e); + _builder.insert(bucket_key, new_value); + } +}; + +// TODO lbound arg? +template <typename DataStoreTraitsT> +void GenericBTreeBucketDatabase<DataStoreTraitsT>::merge(MergingProcessor<ValueType>& proc) { + typename BTree::Builder builder(_tree.getAllocator()); + BTreeBuilderMerger<DataStoreTraitsT> merger(*this, builder); + + // TODO for_each instead? + for (auto iter = _tree.begin(); iter.valid(); ++iter) { + const uint64_t key = iter.getKey(); + const uint64_t value = iter.getData(); + merger.update_iteration_state(key, value); + + auto result = proc.merge(merger); + + if (result == MergingProcessor<ValueType>::Result::KeepUnchanged) { + builder.insert(key, value); // Reuse array store ref with no changes + } else if (result == MergingProcessor<ValueType>::Result::Update) { + assert(merger._valid_cached_value); // Must actually have been touched + assert(merger._cached_value.valid()); + DataStoreTraitsT::remove_by_wrapped_value(_store, value); + const auto new_value = DataStoreTraitsT::wrap_and_store_value(_store, merger._cached_value); + builder.insert(key, new_value); + } else if (result == MergingProcessor<ValueType>::Result::Skip) { + DataStoreTraitsT::remove_by_wrapped_value(_store, value); + } else { + abort(); + } + } + BTreeTrailingInserter<DataStoreTraitsT> inserter(*this, builder); + proc.insert_remaining_at_end(inserter); + + _tree.assign(builder); + commit_tree_changes(); +} + + +} diff --git a/storage/src/vespa/storage/bucketdb/lockablemap.h b/storage/src/vespa/storage/bucketdb/lockablemap.h index 8b4e403b899..83ecac0a94f 100644 --- a/storage/src/vespa/storage/bucketdb/lockablemap.h +++ b/storage/src/vespa/storage/bucketdb/lockablemap.h @@ -14,6 +14,7 @@ */ #pragma once +#include "abstract_bucket_map.h" #include <map> #include <vespa/vespalib/util/printable.h> #include <vespa/vespalib/stllike/hash_map.h> @@ -26,98 +27,19 @@ namespace storage { -template<typename Map> -class LockableMap : public vespalib::Printable +template <typename Map> +class LockableMap + : public bucketdb::AbstractBucketMap<typename Map::mapped_type> { public: - typedef typename Map::key_type key_type; - typedef typename Map::mapped_type mapped_type; - typedef typename Map::value_type value_type; - typedef typename Map::size_type size_type; - using BucketId = document::BucketId; - struct WrappedEntry; - - /** Responsible for releasing lock in map when out of scope. */ - class LockKeeper { - friend struct WrappedEntry; - LockableMap<Map>& _map; - key_type _key; - bool _locked; - - LockKeeper(LockableMap<Map>& map, key_type key) - : _map(map), _key(key), _locked(true) {} - void unlock() { _map.unlock(_key); _locked = false;} - public: - ~LockKeeper() { if (_locked) unlock(); } - }; - - struct WrappedEntry { - WrappedEntry() : _exists(false), _lockKeeper(), _value() {} - WrappedEntry(WrappedEntry &&) = default; - WrappedEntry & operator = (WrappedEntry &&) = default; - ~WrappedEntry() { } - - mapped_type* operator->() { return &_value; } - const mapped_type* operator->() const { return &_value; } - mapped_type& operator*() { return _value; } - const mapped_type& operator*() const { return _value; } - - const mapped_type *get() const { return &_value; } - mapped_type *get() { return &_value; } - - void write(); - void remove(); - void unlock(); - bool exist() const { return _exists; } - bool preExisted() const { return _preExisted; } - bool locked() const { return _lockKeeper.get(); } - const key_type& getKey() const { return _lockKeeper->_key; }; - - BucketId getBucketId() const { - return BucketId(BucketId::keyToBucketId(getKey())); - } - - protected: - WrappedEntry(LockableMap<Map>& map, - const key_type& key, const mapped_type& val, - const char* clientId, bool preExisted_) - : _exists(true), - _preExisted(preExisted_), - _lockKeeper(new LockKeeper(map, key)), - _value(val), - _clientId(clientId) {} - WrappedEntry(LockableMap<Map>& map, const key_type& key, - const char* clientId) - : _exists(false), - _preExisted(false), - _lockKeeper(new LockKeeper(map, key)), - _value(), - _clientId(clientId) {} - - bool _exists; - bool _preExisted; - std::unique_ptr<LockKeeper> _lockKeeper; - mapped_type _value; - const char* _clientId; - friend class LockableMap<Map>; - }; - - struct LockId { - key_type _key; - const char* _owner; - - LockId() : _key(0), _owner("none - empty token") {} - LockId(key_type key, const char* owner) - : _key(key), _owner(owner) - { - assert(_owner != 0); - } - - size_t hash() const { return _key; } - size_t operator%(size_t val) const { return _key % val; } - bool operator==(const LockId& id) const { return (_key == id._key); } - operator key_type() const { return _key; } - }; + using ParentType = bucketdb::AbstractBucketMap<typename Map::mapped_type>; + using WrappedEntry = typename ParentType::WrappedEntry; + using key_type = typename ParentType::key_type; + using mapped_type = typename ParentType::mapped_type; + using LockId = typename ParentType::LockId; + using EntryMap = typename ParentType::EntryMap; + using Decision = typename ParentType::Decision; + using BucketId = document::BucketId; LockableMap(); ~LockableMap(); @@ -126,14 +48,20 @@ public: return ! (*this == other); } bool operator<(const LockableMap& other) const; - typename Map::size_type size() const; - size_type getMemoryUsage() const; - bool empty() const; + size_t size() const noexcept override; + size_t getMemoryUsage() const noexcept override; + bool empty() const noexcept override; void swap(LockableMap&); - WrappedEntry get(const key_type& key, const char* clientId, - bool createIfNonExisting = false, - bool lockIfNonExistingAndNotCreating = false); + WrappedEntry get(const key_type& key, const char* clientId, bool createIfNonExisting) override; + WrappedEntry get(const key_type& key, const char* clientId) { + return get(key, clientId, false); + } + + bool erase(const key_type& key, const char* clientId, bool haslock) override; + void insert(const key_type& key, const mapped_type& value, + const char* clientId, bool haslock, bool& preExisted) override; + bool erase(const key_type& key, const char* clientId) { return erase(key, clientId, false); } void insert(const key_type& key, const mapped_type& value, @@ -141,42 +69,6 @@ public: { return insert(key, value, clientId, false, preExisted); } void clear(); - enum Decision { ABORT, UPDATE, REMOVE, CONTINUE, DECISION_COUNT }; - - template<typename Functor> - void each(Functor& functor, const char* clientId, - const key_type& first = key_type(), - const key_type& last = key_type() - 1 ); - - template<typename Functor> - void each(const Functor& functor, const char* clientId, - const key_type& first = key_type(), - const key_type& last = key_type() - 1 ); - - template<typename Functor> - void all(Functor& functor, const char* clientId, - const key_type& first = key_type(), - const key_type& last = key_type()-1); - - template<typename Functor> - void all(const Functor& functor, const char* clientId, - const key_type& first = key_type(), - const key_type& last = key_type() - 1 ); - - static constexpr uint32_t DEFAULT_CHUNK_SIZE = 1000; - - /** - * Iterate over the entire database contents, holding the global database - * mutex for `chunkSize` processed entries at a time, yielding the current - * thread between each such such to allow other threads to get a chance - * at acquiring a bucket lock. - */ - template <typename Functor> - void chunkedAll(Functor& functor, - const char* clientId, - vespalib::duration yieldTime = 10us, - uint32_t chunkSize = DEFAULT_CHUNK_SIZE); - void print(std::ostream& out, bool verbose, const std::string& indent) const override; /** @@ -184,18 +76,13 @@ public: * bucket. Usually, there should be only one such bucket, but in the case * of inconsistent splitting, there may be more than one. */ - std::map<BucketId, WrappedEntry> - getContained(const BucketId& bucketId, const char* clientId); - - typedef std::map<BucketId, WrappedEntry> EntryMap; + EntryMap getContained(const BucketId& bucketId, const char* clientId) override; /** * Returns all buckets in the bucket database that can contain the given * bucket, and all buckets that that bucket contains. - * - * If sibling is != 0, also fetch that bucket if possible. */ - EntryMap getAll(const BucketId& bucketId, const char* clientId, const BucketId& sibling = BucketId(0)); + EntryMap getAll(const BucketId& bucketId, const char* clientId) override; /** * Returns true iff bucket has no superbuckets or sub-buckets in the @@ -203,9 +90,9 @@ public: * bucket to become inconsistent will require taking its lock, so by * requiring the lock to be provided here we avoid race conditions. */ - bool isConsistent(const WrappedEntry& entry); + bool isConsistent(const WrappedEntry& entry) override; - void showLockClients(vespalib::asciistream & out) const; + void showLockClients(vespalib::asciistream & out) const override; private: struct hasher { @@ -217,7 +104,7 @@ private: LockIdSet(); ~LockIdSet(); void print(std::ostream& out, bool verbose, const std::string& indent) const; - bool exist(const LockId & lid) const { return this->find(lid) != Hash::end(); } + bool exist(const LockId& lid) const { return this->find(lid) != Hash::end(); } size_t getMemoryUsage() const; }; @@ -243,15 +130,27 @@ private: LockIdSet _lockedKeys; LockWaiters _lockWaiters; - bool erase(const key_type& key, const char* clientId, bool haslock); - void insert(const key_type& key, const mapped_type& value, - const char* clientId, bool haslock, bool& preExisted); - void unlock(const key_type& key); + void unlock(const key_type& key) override; bool findNextKey(key_type& key, mapped_type& val, const char* clientId, std::unique_lock<std::mutex> &guard); bool handleDecision(key_type& key, mapped_type& val, Decision decision); void acquireKey(const LockId & lid, std::unique_lock<std::mutex> &guard); + void do_for_each_mutable(std::function<Decision(uint64_t, mapped_type&)> func, + const char* clientId, + const key_type& first, + const key_type& last) override; + + void do_for_each(std::function<Decision(uint64_t, const mapped_type&)> func, + const char* clientId, + const key_type& first, + const key_type& last) override; + + void do_for_each_chunked(std::function<Decision(uint64_t, mapped_type&)> func, + const char* clientId, + vespalib::duration yieldTime, + uint32_t chunkSize) override; + /** * Process up to `chunkSize` bucket database entries from--and possibly * including--the bucket pointed to by `key`. @@ -263,17 +162,15 @@ private: * Modifies `key` in-place to point to the next key to process for the next * invocation of this function. */ - template <typename Functor> - bool processNextChunk(Functor& functor, + bool processNextChunk(std::function<Decision(uint64_t, mapped_type&)>& func, key_type& key, const char* clientId, - const uint32_t chunkSize); + uint32_t chunkSize); /** * Returns the given bucket, its super buckets and its sub buckets. */ void getAllWithoutLocking(const BucketId& bucket, - const BucketId& sibling, std::vector<BucketId::Type>& keys); /** diff --git a/storage/src/vespa/storage/bucketdb/lockablemap.hpp b/storage/src/vespa/storage/bucketdb/lockablemap.hpp index 2ca2183ae26..fcdde0a810c 100644 --- a/storage/src/vespa/storage/bucketdb/lockablemap.hpp +++ b/storage/src/vespa/storage/bucketdb/lockablemap.hpp @@ -16,7 +16,7 @@ template<typename Map> LockableMap<Map>::LockIdSet::LockIdSet() : Hash() { } template<typename Map> -LockableMap<Map>::LockIdSet::~LockIdSet() { } +LockableMap<Map>::LockIdSet::~LockIdSet() = default; template<typename Map> size_t @@ -28,7 +28,7 @@ template<typename Map> LockableMap<Map>::LockWaiters::LockWaiters() : _id(0), _map() { } template<typename Map> -LockableMap<Map>::LockWaiters::~LockWaiters() { } +LockableMap<Map>::LockWaiters::~LockWaiters() = default; template<typename Map> size_t @@ -39,35 +39,6 @@ LockableMap<Map>::LockWaiters::insert(const LockId & lid) { } template<typename Map> -void -LockableMap<Map>::WrappedEntry::write() -{ - assert(_lockKeeper->_locked); - assert(_value.verifyLegal()); - bool b; - _lockKeeper->_map.insert(_lockKeeper->_key, _value, _clientId, true, b); - _lockKeeper->unlock(); -} - -template<typename Map> -void -LockableMap<Map>::WrappedEntry::remove() -{ - assert(_lockKeeper->_locked); - assert(_exists); - _lockKeeper->_map.erase(_lockKeeper->_key, _clientId, true); - _lockKeeper->unlock(); -} - -template<typename Map> -void -LockableMap<Map>::WrappedEntry::unlock() -{ - assert(_lockKeeper->_locked); - _lockKeeper->unlock(); -} - -template<typename Map> LockableMap<Map>::LockableMap() : _map(), _lock(), @@ -77,7 +48,7 @@ LockableMap<Map>::LockableMap() {} template<typename Map> -LockableMap<Map>::~LockableMap() {} +LockableMap<Map>::~LockableMap() = default; template<typename Map> bool @@ -98,16 +69,16 @@ LockableMap<Map>::operator<(const LockableMap<Map>& other) const } template<typename Map> -typename Map::size_type -LockableMap<Map>::size() const +size_t +LockableMap<Map>::size() const noexcept { std::lock_guard<std::mutex> guard(_lock); return _map.size(); } template<typename Map> -typename Map::size_type -LockableMap<Map>::getMemoryUsage() const +size_t +LockableMap<Map>::getMemoryUsage() const noexcept { std::lock_guard<std::mutex> guard(_lock); return _map.getMemoryUsage() + _lockedKeys.getMemoryUsage() + @@ -116,7 +87,7 @@ LockableMap<Map>::getMemoryUsage() const template<typename Map> bool -LockableMap<Map>::empty() const +LockableMap<Map>::empty() const noexcept { std::lock_guard<std::mutex> guard(_lock); return _map.empty(); @@ -145,9 +116,7 @@ void LockableMap<Map>::acquireKey(const LockId & lid, std::unique_lock<std::mute template<typename Map> typename LockableMap<Map>::WrappedEntry -LockableMap<Map>::get(const key_type& key, const char* clientId, - bool createIfNonExisting, - bool lockIfNonExistingAndNotCreating) +LockableMap<Map>::get(const key_type& key, const char* clientId, bool createIfNonExisting) { LockId lid(key, clientId); std::unique_lock<std::mutex> guard(_lock); @@ -157,71 +126,34 @@ LockableMap<Map>::get(const key_type& key, const char* clientId, _map.find(key, createIfNonExisting, preExisted); if (it == _map.end()) { - if (lockIfNonExistingAndNotCreating) { - return WrappedEntry(*this, key, clientId); - } else { - return WrappedEntry(); - } + return WrappedEntry(); } _lockedKeys.insert(lid); return WrappedEntry(*this, key, it->second, clientId, preExisted); } -#ifdef ENABLE_BUCKET_OPERATION_LOGGING - -namespace bucketdb { -struct StorageBucketInfo; -struct BucketInfo; -} - -namespace debug { - -template <typename T> struct TypeTag {}; -// Storage -void logBucketDbInsert(uint64_t key, const bucketdb::StorageBucketInfo& entry); -void logBucketDbErase(uint64_t key, const TypeTag<bucketdb::StorageBucketInfo>&); - -// Distributor -void logBucketDbInsert(uint64_t key, const bucketdb::BucketInfo& entry); -void logBucketDbErase(uint64_t key, const TypeTag<bucketdb::BucketInfo>&); - -template <typename DummyValue> -inline void logBucketDbErase(uint64_t, const TypeTag<DummyValue>&) {} -template <typename DummyKey, typename DummyValue> -inline void logBucketDbInsert(const DummyKey&, const DummyValue&) {} - -} - -#endif // ENABLE_BUCKET_OPERATION_LOGGING - template<typename Map> bool -LockableMap<Map>::erase(const key_type& key, const char* clientId, bool haslock) +LockableMap<Map>::erase(const key_type& key, const char* client_id, bool has_lock) { - LockId lid(key, clientId); + LockId lid(key, client_id); std::unique_lock<std::mutex> guard(_lock); - if (!haslock) { + if (!has_lock) { acquireKey(lid, guard); } -#ifdef ENABLE_BUCKET_OPERATION_LOGGING - debug::logBucketDbErase(key, debug::TypeTag<mapped_type>()); -#endif return _map.erase(key); } template<typename Map> void LockableMap<Map>::insert(const key_type& key, const mapped_type& value, - const char* clientId, bool haslock, bool& preExisted) + const char* client_id, bool has_lock, bool& preExisted) { - LockId lid(key, clientId); + LockId lid(key, client_id); std::unique_lock<std::mutex> guard(_lock); - if (!haslock) { + if (!has_lock) { acquireKey(lid, guard); } -#ifdef ENABLE_BUCKET_OPERATION_LOGGING - debug::logBucketDbInsert(key, value); -#endif _map.insert(key, value, preExisted); } @@ -242,12 +174,14 @@ LockableMap<Map>::findNextKey(key_type& key, mapped_type& val, // Wait for next value to unlock. typename Map::iterator it(_map.lower_bound(key)); while (it != _map.end() && _lockedKeys.exist(LockId(it->first, ""))) { - typename LockWaiters::Key waitId(_lockWaiters.insert(LockId(it->first, clientId))); + auto wait_id = _lockWaiters.insert(LockId(it->first, clientId)); _cond.wait(guard); - _lockWaiters.erase(waitId); + _lockWaiters.erase(wait_id); it = _map.lower_bound(key); } - if (it == _map.end()) return true; + if (it == _map.end()) { + return true; + } key = it->first; val = it->second; return false; @@ -260,127 +194,60 @@ LockableMap<Map>::handleDecision(key_type& key, mapped_type& val, { bool b; switch (decision) { - case UPDATE: _map.insert(key, val, b); - break; - case REMOVE: _map.erase(key); - break; - case ABORT: return true; - case CONTINUE: break; - default: - HDR_ABORT("should not be reached"); + case Decision::UPDATE: + _map.insert(key, val, b); + break; + case Decision::REMOVE: + _map.erase(key); + break; + case Decision::ABORT: + return true; + case Decision::CONTINUE: + break; + default: + HDR_ABORT("should not be reached"); } return false; } template<typename Map> -template<typename Functor> -void -LockableMap<Map>::each(Functor& functor, const char* clientId, - const key_type& first, const key_type& last) -{ - key_type key = first; - mapped_type val; - Decision decision; - { - std::unique_lock<std::mutex> guard(_lock); - if (findNextKey(key, val, clientId, guard) || key > last) return; - _lockedKeys.insert(LockId(key, clientId)); - } - try{ - while (true) { - decision = functor(const_cast<const key_type&>(key), val); - std::unique_lock<std::mutex> guard(_lock); - _lockedKeys.erase(LockId(key, clientId)); - _cond.notify_all(); - if (handleDecision(key, val, decision)) return; - ++key; - if (findNextKey(key, val, clientId, guard) || key > last) return; - _lockedKeys.insert(LockId(key, clientId)); - } - } catch (...) { - // Assuming only the functor call can throw exceptions, we need - // to unlock the current key before exiting - std::lock_guard<std::mutex> guard(_lock); - _lockedKeys.erase(LockId(key, clientId)); - _cond.notify_all(); - throw; - } -} - -template<typename Map> -template<typename Functor> -void -LockableMap<Map>::each(const Functor& functor, const char* clientId, - const key_type& first, const key_type& last) -{ - key_type key = first; - mapped_type val; - Decision decision; - { - std::unique_lock<std::mutex> guard(_lock); - if (findNextKey(key, val, clientId, guard) || key > last) return; - _lockedKeys.insert(LockId(key, clientId)); - } - try{ - while (true) { - decision = functor(const_cast<const key_type&>(key), val); - std::unique_lock<std::mutex> guard(_lock); - _lockedKeys.erase(LockId(key, clientId)); - _cond.notify_all(); - if (handleDecision(key, val, decision)) return; - ++key; - if (findNextKey(key, val, clientId, guard) || key > last) return; - _lockedKeys.insert(LockId(key, clientId)); - } - } catch (...) { - // Assuming only the functor call can throw exceptions, we need - // to unlock the current key before exiting - std::lock_guard<std::mutex> guard(_lock); - _lockedKeys.erase(LockId(key, clientId)); - _cond.notify_all(); - throw; - } -} - -template<typename Map> -template<typename Functor> -void -LockableMap<Map>::all(Functor& functor, const char* clientId, - const key_type& first, const key_type& last) +void LockableMap<Map>::do_for_each_mutable(std::function<Decision(uint64_t, mapped_type&)> func, + const char* clientId, + const key_type& first, + const key_type& last) { key_type key = first; mapped_type val; std::unique_lock<std::mutex> guard(_lock); while (true) { if (findNextKey(key, val, clientId, guard) || key > last) return; - Decision d(functor(const_cast<const key_type&>(key), val)); + Decision d(func(const_cast<const key_type&>(key), val)); if (handleDecision(key, val, d)) return; ++key; } } template<typename Map> -template<typename Functor> -void -LockableMap<Map>::all(const Functor& functor, const char* clientId, - const key_type& first, const key_type& last) +void LockableMap<Map>::do_for_each(std::function<Decision(uint64_t, const mapped_type&)> func, + const char* clientId, + const key_type& first, + const key_type& last) { key_type key = first; mapped_type val; std::unique_lock<std::mutex> guard(_lock); while (true) { if (findNextKey(key, val, clientId, guard) || key > last) return; - Decision d(functor(const_cast<const key_type&>(key), val)); - assert(d == ABORT || d == CONTINUE); + Decision d(func(const_cast<const key_type&>(key), val)); + assert(d == Decision::ABORT || d == Decision::CONTINUE); if (handleDecision(key, val, d)) return; ++key; } } template <typename Map> -template <typename Functor> bool -LockableMap<Map>::processNextChunk(Functor& functor, +LockableMap<Map>::processNextChunk(std::function<Decision(uint64_t, mapped_type&)>& func, key_type& key, const char* clientId, const uint32_t chunkSize) @@ -391,7 +258,7 @@ LockableMap<Map>::processNextChunk(Functor& functor, if (findNextKey(key, val, clientId, guard)) { return false; } - Decision d(functor(const_cast<const key_type&>(key), val)); + Decision d(func(const_cast<const key_type&>(key), val)); if (handleDecision(key, val, d)) { return false; } @@ -401,15 +268,13 @@ LockableMap<Map>::processNextChunk(Functor& functor, } template <typename Map> -template <typename Functor> -void -LockableMap<Map>::chunkedAll(Functor& functor, - const char* clientId, - vespalib::duration yieldTime, - uint32_t chunkSize) +void LockableMap<Map>::do_for_each_chunked(std::function<Decision(uint64_t, mapped_type&)> func, + const char* clientId, + vespalib::duration yieldTime, + uint32_t chunkSize) { key_type key{}; - while (processNextChunk(functor, key, clientId, chunkSize)) { + while (processNextChunk(func, key, clientId, chunkSize)) { // Rationale: delay iteration for as short a time as possible while // allowing another thread blocked on the main DB mutex to acquire it // in the meantime. Simply yielding the thread does not have the @@ -591,7 +456,7 @@ LockableMap<Map>::addAndLockResults( uint8_t getMinDiffBits(uint16_t minBits, const document::BucketId& a, const document::BucketId& b); template<typename Map> -std::map<document::BucketId, typename LockableMap<Map>::WrappedEntry> +typename LockableMap<Map>::EntryMap LockableMap<Map>::getContained(const BucketId& bucket, const char* clientId) { @@ -626,7 +491,6 @@ LockableMap<Map>::getContained(const BucketId& bucket, template<typename Map> void LockableMap<Map>::getAllWithoutLocking(const BucketId& bucket, - const BucketId& sibling, std::vector<BucketId::Type>& keys) { BucketId result; @@ -674,26 +538,21 @@ LockableMap<Map>::getAllWithoutLocking(const BucketId& bucket, break; } } - - if (sibling.getRawId() != 0) { - keys.push_back(sibling.toKey()); - } } /** * Returns the given bucket, its super buckets and its sub buckets. */ template<typename Map> -std::map<document::BucketId, typename LockableMap<Map>::WrappedEntry> -LockableMap<Map>::getAll(const BucketId& bucket, const char* clientId, - const BucketId& sibling) +typename LockableMap<Map>::EntryMap +LockableMap<Map>::getAll(const BucketId& bucket, const char* clientId) { std::unique_lock<std::mutex> guard(_lock); std::map<BucketId, WrappedEntry> results; std::vector<BucketId::Type> keys; - getAllWithoutLocking(bucket, sibling, keys); + getAllWithoutLocking(bucket, keys); addAndLockResults(keys, clientId, results, guard); @@ -706,10 +565,9 @@ LockableMap<Map>::isConsistent(const typename LockableMap<Map>::WrappedEntry& en { std::lock_guard<std::mutex> guard(_lock); - BucketId sibling(0); std::vector<BucketId::Type> keys; - getAllWithoutLocking(entry.getBucketId(), sibling, keys); + getAllWithoutLocking(entry.getBucketId(), keys); assert(keys.size() >= 1); assert(keys.size() != 1 || keys[0] == entry.getKey()); diff --git a/storage/src/vespa/storage/bucketdb/stdmapwrapper.h b/storage/src/vespa/storage/bucketdb/stdmapwrapper.h deleted file mode 100644 index 889227f1747..00000000000 --- a/storage/src/vespa/storage/bucketdb/stdmapwrapper.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @class StdMapWrapper - * @ingroup bucketdb - * - * @brief Wrapper for std::map to add functionality in JudyMultiMap. - * - * To remove the need for partial template specialization in lockablemap - */ - -#pragma once - -#include <map> -#include <vespa/vespalib/util/printable.h> -#include <ostream> - -namespace storage { - -template<typename Key, typename Value> -class StdMapWrapper : public std::map<Key, Value>, - public vespalib::Printable -{ -public: - StdMapWrapper() {} - - virtual void print(std::ostream& out, bool verbose, - const std::string& indent) const; - - typename std::map<Key, Value>::iterator find(Key key); - - typename std::map<Key, Value>::iterator find(Key key, bool insert, bool&); - - void insert(Key key, const Value& val, bool&); - - uint32_t getMemoryUsage() const; -}; - -template<class Key, class Value> -uint32_t -StdMapWrapper<Key, Value>::getMemoryUsage() const -{ - Value val; - - return (32 + sizeof(val)) * this->size(); -} - -template<class Key, class Value> -void -StdMapWrapper<Key, Value>::print(std::ostream& out, - bool, - const std::string& indent) const -{ - out << "StdMapWrapper("; - for (typename std::map<Key, Value>::const_iterator i = this->begin(); - i != this->end(); ++i) - { - out << "\n" << indent << " " << "Key: " << i->first << ", Value: " - << i->second; - } - out << ")"; -} - -template<class Key, class Value> -inline typename std::map<Key, Value>::iterator -StdMapWrapper<Key, Value>:: -find(Key key) -{ - bool tmp; - return find(key, false, tmp); -} - -template<class Key, class Value> -inline typename std::map<Key, Value>::iterator -StdMapWrapper<Key, Value>:: -find(Key key, bool insertIfNonExisting, bool&) -{ - if (insertIfNonExisting) { - std::pair<typename std::map<Key, Value>::iterator, bool> result - = std::map<Key, Value>::insert(std::pair<Key, Value>(key, Value())); - return result.first; - } else { - return std::map<Key, Value>::find(key); - } -} - -template<class Key, class Value> -void -StdMapWrapper<Key, Value>:: -insert(Key key, const Value& val, bool&) -{ - this->operator[](key) = val; -} - -} - diff --git a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp index 84352df5ec9..ed83ad268e5 100644 --- a/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp +++ b/storage/src/vespa/storage/bucketdb/storagebucketdbinitializer.cpp @@ -462,13 +462,13 @@ namespace { _next(), _alreadySet(0) {} StorBucketDatabase::Decision operator()( - uint64_t revBucket, StorBucketDatabase::Entry& entry) + uint64_t revBucket, const StorBucketDatabase::Entry& entry) { BucketId bucket(BucketId::keyToBucketId(revBucket)); if (bucket == _iterator) { //LOG(spam, "Ignoring bucket %s as it has value of current " // "iterator", bucket.toString().c_str()); - return StorBucketDatabase::CONTINUE; + return StorBucketDatabase::Decision::CONTINUE; } _iterator = bucket; if (entry.disk != _disk) { @@ -487,10 +487,10 @@ namespace { LOG(spam, "Aborting iterating for disk %u as we have " "enough results. Leaving iterator at %s", uint32_t(_disk), _iterator.toString().c_str()); - return StorBucketDatabase::ABORT; + return StorBucketDatabase::Decision::ABORT; } } - return StorBucketDatabase::CONTINUE; + return StorBucketDatabase::Decision::CONTINUE; } }; } @@ -513,9 +513,10 @@ StorageBucketDBInitializer::sendReadBucketInfo(spi::PartitionId disk, document:: NextBucketOnDiskFinder finder(disk, state._databaseIterator, count); LOG(spam, "Iterating bucket db further. Starting at iterator %s", state._databaseIterator.toString().c_str()); - _system.getBucketDatabase(bucketSpace).all(finder, - "StorageBucketDBInitializer::readBucketInfo", - state._databaseIterator.stripUnused().toKey()); + _system.getBucketDatabase(bucketSpace).for_each( + std::ref(finder), + "StorageBucketDBInitializer::readBucketInfo", + state._databaseIterator.stripUnused().toKey()); if (finder._alreadySet > 0) { _metrics._infoSetByLoad.inc(finder._alreadySet); _state._infoSetByLoad += finder._alreadySet; diff --git a/storage/src/vespa/storage/bucketdb/storbucketdb.cpp b/storage/src/vespa/storage/bucketdb/storbucketdb.cpp index df9e8e4e5b5..7066ea115fd 100644 --- a/storage/src/vespa/storage/bucketdb/storbucketdb.cpp +++ b/storage/src/vespa/storage/bucketdb/storbucketdb.cpp @@ -1,11 +1,15 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "storbucketdb.h" +#include "btree_lockable_map.h" #include "judymultimap.hpp" +#include "lockablemap.h" #include <vespa/log/log.h> LOG_SETUP(".storage.bucketdb.stor_bucket_db"); +using document::BucketId; + namespace storage { namespace bucketdb { @@ -16,15 +20,15 @@ print(std::ostream& out, bool, const std::string&) const out << info << ", disk " << disk; } -bool StorageBucketInfo::operator == (const StorageBucketInfo & b) const { +bool StorageBucketInfo::operator==(const StorageBucketInfo& b) const { return disk == b.disk; } -bool StorageBucketInfo::operator != (const StorageBucketInfo & b) const { +bool StorageBucketInfo::operator!=(const StorageBucketInfo& b) const { return !(*this == b); } -bool StorageBucketInfo::operator < (const StorageBucketInfo & b) const { +bool StorageBucketInfo::operator<(const StorageBucketInfo& b) const { return disk < b.disk; } @@ -34,8 +38,25 @@ operator<<(std::ostream& out, const StorageBucketInfo& info) { return out; } +namespace { + +std::unique_ptr<AbstractBucketMap<StorageBucketInfo>> make_legacy_db_impl() { + return std::make_unique<LockableMap<JudyMultiMap<StorageBucketInfo>>>(); +} + +std::unique_ptr<AbstractBucketMap<StorageBucketInfo>> make_btree_db_impl() { + return std::make_unique<BTreeLockableMap<StorageBucketInfo>>(); +} + +} + } // bucketdb +StorBucketDatabase::StorBucketDatabase(bool use_btree_db) + : _impl(use_btree_db ? bucketdb::make_btree_db_impl() + : bucketdb::make_legacy_db_impl()) +{} + void StorBucketDatabase::insert(const document::BucketId& bucket, const bucketdb::StorageBucketInfo& entry, @@ -43,26 +64,14 @@ StorBucketDatabase::insert(const document::BucketId& bucket, { assert(entry.disk != 0xff); bool preExisted; -#if __WORDSIZE == 64 - return LockableMap<JudyMultiMap<Entry> >::insert( - bucket.toKey(), entry, clientId, preExisted); -#else - return LockableMap<StdMapWrapper<document::BucketId::Type, Entry> >::insert( - bucket.toKey(), entry, clientId, preExisted); -#endif + return _impl->insert(bucket.toKey(), entry, clientId, false, preExisted); } bool StorBucketDatabase::erase(const document::BucketId& bucket, const char* clientId) { -#if __WORDSIZE == 64 - return LockableMap<JudyMultiMap<Entry> >::erase( - bucket.stripUnused().toKey(), clientId); -#else - return LockableMap<StdMapWrapper<document::BucketId::Type, Entry> >::erase( - bucket.stripUnused().toKey(), clientId); -#endif + return _impl->erase(bucket.stripUnused().toKey(), clientId, false); } StorBucketDatabase::WrappedEntry @@ -71,16 +80,60 @@ StorBucketDatabase::get(const document::BucketId& bucket, Flag flags) { bool createIfNonExisting = (flags & CREATE_IF_NONEXISTING); - bool lockIfNonExisting = (flags & LOCK_IF_NONEXISTING_AND_NOT_CREATING); -#if __WORDSIZE == 64 - return LockableMap<JudyMultiMap<Entry> >::get( - bucket.stripUnused().toKey(), clientId, createIfNonExisting, - lockIfNonExisting); -#else - return LockableMap<StdMapWrapper<document::BucketId::Type, Entry> >::get( - bucket.stripUnused().toKey(), clientId, - createIfNonExisting, lockIfNonExisting); -#endif + return _impl->get(bucket.stripUnused().toKey(), clientId, createIfNonExisting); +} + +size_t StorBucketDatabase::size() const { + return _impl->size(); +} + +size_t StorBucketDatabase::getMemoryUsage() const { + return _impl->getMemoryUsage(); +} + +void StorBucketDatabase::showLockClients(vespalib::asciistream& out) const { + _impl->showLockClients(out); +} + +StorBucketDatabase::EntryMap +StorBucketDatabase::getAll(const BucketId& bucketId, const char* clientId) { + return _impl->getAll(bucketId, clientId); +} + +StorBucketDatabase::EntryMap +StorBucketDatabase::getContained(const BucketId& bucketId, const char* clientId) { + return _impl->getContained(bucketId, clientId); +} + +bool StorBucketDatabase::isConsistent(const WrappedEntry& entry) { + return _impl->isConsistent(entry); +} + +void StorBucketDatabase::for_each_chunked( + std::function<Decision(uint64_t, const bucketdb::StorageBucketInfo&)> func, + const char* clientId, + vespalib::duration yieldTime, + uint32_t chunkSize) +{ + _impl->for_each_chunked(std::move(func), clientId, yieldTime, chunkSize); +} + +void StorBucketDatabase::for_each_mutable( + std::function<Decision(uint64_t, bucketdb::StorageBucketInfo&)> func, + const char* clientId, + const key_type& first, + const key_type& last) +{ + _impl->for_each_mutable(std::move(func), clientId, first, last); +} + +void StorBucketDatabase::for_each( + std::function<Decision(uint64_t, const bucketdb::StorageBucketInfo&)> func, + const char* clientId, + const key_type& first, + const key_type& last) +{ + _impl->for_each(std::move(func), clientId, first, last); } template class JudyMultiMap<bucketdb::StorageBucketInfo>; diff --git a/storage/src/vespa/storage/bucketdb/storbucketdb.h b/storage/src/vespa/storage/bucketdb/storbucketdb.h index 15d1004a00d..87e5d80c01b 100644 --- a/storage/src/vespa/storage/bucketdb/storbucketdb.h +++ b/storage/src/vespa/storage/bucketdb/storbucketdb.h @@ -1,51 +1,84 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * \class StorageBucketInfo - * \ingroup bucketdb - * - * \brief An entry in the storage bucket database. - * - * \class StorBucketDatabase - * \ingroup bucketdb - * - * \brief The storage bucket database. - */ #pragma once -#include "judymultimap.h" -#include "lockablemap.h" -#include "stdmapwrapper.h" +#include "abstract_bucket_map.h" #include "storagebucketinfo.h" #include <vespa/storageapi/defs.h> +#include <memory> namespace storage { - -class StorBucketDatabase -#if __WORDSIZE == 64 - : public LockableMap<JudyMultiMap<bucketdb::StorageBucketInfo> > -#else -# warning Bucket database cannot use Judy on non-64 bit platforms - : public LockableMap<StdMapWrapper<document::BucketId::Type, bucketdb::StorageBucketInfo> > -#endif -{ +class StorBucketDatabase { + std::unique_ptr<bucketdb::AbstractBucketMap<bucketdb::StorageBucketInfo>> _impl; public: + using Entry = bucketdb::StorageBucketInfo; + using key_type = bucketdb::AbstractBucketMap<Entry>::key_type; + using Decision = bucketdb::AbstractBucketMap<Entry>::Decision; + using WrappedEntry = bucketdb::AbstractBucketMap<Entry>::WrappedEntry; + using EntryMap = bucketdb::AbstractBucketMap<Entry>::EntryMap; + using BucketId = document::BucketId; + enum Flag { NONE = 0, - CREATE_IF_NONEXISTING = 1, - LOCK_IF_NONEXISTING_AND_NOT_CREATING = 2 + CREATE_IF_NONEXISTING = 1 }; - typedef bucketdb::StorageBucketInfo Entry; - StorBucketDatabase() {}; + explicit StorBucketDatabase(bool use_btree_db = false); void insert(const document::BucketId&, const bucketdb::StorageBucketInfo&, const char* clientId); bool erase(const document::BucketId&, const char* clientId); - WrappedEntry get(const document::BucketId& bucket, const char* clientId, - Flag flags = NONE); + WrappedEntry get(const document::BucketId& bucket, const char* clientId, Flag flags = NONE); + + size_t size() const; + + /** + * Returns all buckets in the bucket database that can contain the given + * bucket, and all buckets that that bucket contains. + */ + EntryMap getAll(const BucketId& bucketId, const char* clientId); + + /** + * Returns all buckets in the bucket database that can contain the given + * bucket. Usually, there should be only one such bucket, but in the case + * of inconsistent splitting, there may be more than one. + */ + EntryMap getContained(const BucketId& bucketId, const char* clientId); + + /** + * Iterate over the entire database contents, holding the global database + * mutex for `chunkSize` processed entries at a time, yielding the current + * thread between each such such to allow other threads to get a chance + * at acquiring a bucket lock. + */ + void for_each_chunked(std::function<Decision(uint64_t, const bucketdb::StorageBucketInfo&)> func, + const char* clientId, + vespalib::duration yieldTime = 10us, + uint32_t chunkSize = bucketdb::AbstractBucketMap<bucketdb::StorageBucketInfo>::DEFAULT_CHUNK_SIZE); + + void for_each_mutable(std::function<Decision(uint64_t, bucketdb::StorageBucketInfo&)> func, + const char* clientId, + const key_type& first = key_type(), + const key_type& last = key_type() - 1); + + void for_each(std::function<Decision(uint64_t, const bucketdb::StorageBucketInfo&)> func, + const char* clientId, + const key_type& first = key_type(), + const key_type& last = key_type() - 1); + + /** + * Returns true iff bucket has no superbuckets or sub-buckets in the + * database. Usage assumption is that any operation that can cause the + * bucket to become inconsistent will require taking its lock, so by + * requiring the lock to be provided here we avoid race conditions. + */ + bool isConsistent(const WrappedEntry& entry); + + size_t getMemoryUsage() const; + void showLockClients(vespalib::asciistream & out) const; + }; } // storage diff --git a/storage/src/vespa/storage/common/content_bucket_space.cpp b/storage/src/vespa/storage/common/content_bucket_space.cpp index 0827c721100..e293c4bc336 100644 --- a/storage/src/vespa/storage/common/content_bucket_space.cpp +++ b/storage/src/vespa/storage/common/content_bucket_space.cpp @@ -4,9 +4,9 @@ namespace storage { -ContentBucketSpace::ContentBucketSpace(document::BucketSpace bucketSpace) +ContentBucketSpace::ContentBucketSpace(document::BucketSpace bucketSpace, bool use_btree_db) : _bucketSpace(bucketSpace), - _bucketDatabase(), + _bucketDatabase(use_btree_db), _lock(), _clusterState(), _distribution(), diff --git a/storage/src/vespa/storage/common/content_bucket_space.h b/storage/src/vespa/storage/common/content_bucket_space.h index 81ce6234879..bf001f8b6f2 100644 --- a/storage/src/vespa/storage/common/content_bucket_space.h +++ b/storage/src/vespa/storage/common/content_bucket_space.h @@ -26,7 +26,7 @@ private: public: using UP = std::unique_ptr<ContentBucketSpace>; - ContentBucketSpace(document::BucketSpace bucketSpace); + explicit ContentBucketSpace(document::BucketSpace bucketSpace, bool use_btree_db = false); document::BucketSpace bucketSpace() const noexcept { return _bucketSpace; } StorBucketDatabase &bucketDatabase() { return _bucketDatabase; } diff --git a/storage/src/vespa/storage/common/content_bucket_space_repo.cpp b/storage/src/vespa/storage/common/content_bucket_space_repo.cpp index 774ddb81578..2cc03e4d1e5 100644 --- a/storage/src/vespa/storage/common/content_bucket_space_repo.cpp +++ b/storage/src/vespa/storage/common/content_bucket_space_repo.cpp @@ -7,11 +7,13 @@ using document::BucketSpace; namespace storage { -ContentBucketSpaceRepo::ContentBucketSpaceRepo() +ContentBucketSpaceRepo::ContentBucketSpaceRepo(bool use_btree_db) : _map() { - _map.emplace(document::FixedBucketSpaces::default_space(), std::make_unique<ContentBucketSpace>(document::FixedBucketSpaces::default_space())); - _map.emplace(document::FixedBucketSpaces::global_space(), std::make_unique<ContentBucketSpace>(document::FixedBucketSpaces::global_space())); + _map.emplace(document::FixedBucketSpaces::default_space(), + std::make_unique<ContentBucketSpace>(document::FixedBucketSpaces::default_space(), use_btree_db)); + _map.emplace(document::FixedBucketSpaces::global_space(), + std::make_unique<ContentBucketSpace>(document::FixedBucketSpaces::global_space(), use_btree_db)); } ContentBucketSpace & diff --git a/storage/src/vespa/storage/common/content_bucket_space_repo.h b/storage/src/vespa/storage/common/content_bucket_space_repo.h index 0d4ddb86bcf..142bb5ea1d5 100644 --- a/storage/src/vespa/storage/common/content_bucket_space_repo.h +++ b/storage/src/vespa/storage/common/content_bucket_space_repo.h @@ -19,7 +19,7 @@ private: BucketSpaceMap _map; public: - ContentBucketSpaceRepo(); + explicit ContentBucketSpaceRepo(bool use_btree_db = false); ContentBucketSpace &get(document::BucketSpace bucketSpace) const; BucketSpaceMap::const_iterator begin() const { return _map.begin(); } BucketSpaceMap::const_iterator end() const { return _map.end(); } @@ -31,7 +31,7 @@ public: void forEachBucket(Functor &functor, const char *clientId) const { for (const auto &elem : _map) { - elem.second->bucketDatabase().all(functor, clientId); + elem.second->bucketDatabase().for_each(std::ref(functor), clientId); } } @@ -39,7 +39,7 @@ public: void forEachBucketChunked(Functor &functor, const char *clientId) const { for (const auto &elem : _map) { - elem.second->bucketDatabase().chunkedAll(functor, clientId); + elem.second->bucketDatabase().for_each_chunked(std::ref(functor), clientId); } } diff --git a/storage/src/vespa/storage/config/stor-distributormanager.def b/storage/src/vespa/storage/config/stor-distributormanager.def index 71059d717a6..db2bfb61376 100644 --- a/storage/src/vespa/storage/config/stor-distributormanager.def +++ b/storage/src/vespa/storage/config/stor-distributormanager.def @@ -203,7 +203,7 @@ simulated_db_merging_latency_msec int default=0 ## Whether to use a B-tree data structure for the distributor bucket database instead ## of the legacy database. Setting this option may trigger alternate code paths for ## read only operations, as the B-tree database is thread safe for concurrent reads. -use_btree_database bool default=false restart +use_btree_database bool default=true restart ## If a bucket is inconsistent and an Update operation is received, a two-phase ## write-repair path is triggered in which a Get is sent to all diverging replicas. @@ -243,4 +243,4 @@ enable_metadata_only_fetch_phase_for_inconsistent_updates bool default=false ## maintenance scans from being done as part of the tick for up to N rounds of ticking. ## This is to reduce the amount of CPU spent on ideal state calculations and bucket DB ## accesses when the distributor is heavily loaded with feed operations. -max_consecutively_inhibited_maintenance_ticks int default=20
\ No newline at end of file +max_consecutively_inhibited_maintenance_ticks int default=20 diff --git a/storage/src/vespa/storage/config/stor-server.def b/storage/src/vespa/storage/config/stor-server.def index 5d303b59d81..e1446aa8ed1 100644 --- a/storage/src/vespa/storage/config/stor-server.def +++ b/storage/src/vespa/storage/config/stor-server.def @@ -84,3 +84,7 @@ bucket_rechecking_chunk_size int default=100 ## full bucket info requests. The latency is added per batch of operations processed. ## Only useful for testing! simulated_bucket_request_latency_msec int default=0 + +## If set, content node processes will use a B-tree backed bucket database implementation +## instead of the legacy Judy-based implementation. +use_content_node_btree_bucket_db bool default=false restart diff --git a/storage/src/vespa/storage/distributor/distributor.cpp b/storage/src/vespa/storage/distributor/distributor.cpp index 5dade47c2a6..5080b16c727 100644 --- a/storage/src/vespa/storage/distributor/distributor.cpp +++ b/storage/src/vespa/storage/distributor/distributor.cpp @@ -109,10 +109,6 @@ Distributor::Distributor(DistributorComponentRegister& compReg, _must_send_updated_host_info(false), _use_btree_database(use_btree_database) { - if (use_btree_database) { - LOG(info, "Using new B-tree bucket database implementation instead of legacy implementation"); // TODO remove this once default is swapped - } - _component.registerMetric(*_metrics); _component.registerMetricUpdateHook(_metricUpdateHook, framework::SecondTime(0)); diff --git a/storage/src/vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.cpp b/storage/src/vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.cpp index 888f1e816a1..da45b61701e 100644 --- a/storage/src/vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.cpp +++ b/storage/src/vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.cpp @@ -9,9 +9,9 @@ namespace storage { using vespalib::IllegalStateException; -ServiceLayerComponentRegisterImpl::ServiceLayerComponentRegisterImpl() +ServiceLayerComponentRegisterImpl::ServiceLayerComponentRegisterImpl(bool use_btree_db) : _diskCount(0), - _bucketSpaceRepo() + _bucketSpaceRepo(use_btree_db) { } void diff --git a/storage/src/vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.h b/storage/src/vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.h index deb3b2c0767..9988b861422 100644 --- a/storage/src/vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.h +++ b/storage/src/vespa/storage/frameworkimpl/component/servicelayercomponentregisterimpl.h @@ -27,7 +27,7 @@ class ServiceLayerComponentRegisterImpl public: typedef std::unique_ptr<ServiceLayerComponentRegisterImpl> UP; - ServiceLayerComponentRegisterImpl(); + ServiceLayerComponentRegisterImpl(bool use_btree_db = false); uint16_t getDiskCount() const { return _diskCount; } ContentBucketSpaceRepo& getBucketSpaceRepo() { return _bucketSpaceRepo; } diff --git a/storage/src/vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.cpp b/storage/src/vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.cpp index 2f66027ccab..654fcbd1380 100644 --- a/storage/src/vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.cpp +++ b/storage/src/vespa/storage/frameworkimpl/component/storagecomponentregisterimpl.cpp @@ -25,7 +25,7 @@ StorageComponentRegisterImpl::StorageComponentRegisterImpl() { } -StorageComponentRegisterImpl::~StorageComponentRegisterImpl() { } +StorageComponentRegisterImpl::~StorageComponentRegisterImpl() = default; void StorageComponentRegisterImpl::registerStorageComponent(StorageComponent& smc) diff --git a/storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp b/storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp index eec8ac3e327..021c94464df 100644 --- a/storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp +++ b/storage/src/vespa/storage/persistence/filestorage/filestormanager.cpp @@ -848,7 +848,7 @@ namespace { StorBucketDatabase::Decision operator()(document::BucketId::Type, StorBucketDatabase::Entry& data) { data.info.setActive(false); - return StorBucketDatabase::UPDATE; + return StorBucketDatabase::Decision::UPDATE; } }; } @@ -870,7 +870,8 @@ FileStorManager::updateState() if (contentBucketSpace.getNodeUpInLastNodeStateSeenByProvider() && !nodeUp) { LOG(debug, "Received cluster state where this node is down; de-activating all buckets in database for bucket space %s", bucketSpace.toString().c_str()); Deactivator deactivator; - contentBucketSpace.bucketDatabase().all(deactivator, "FileStorManager::updateState"); + contentBucketSpace.bucketDatabase().for_each_mutable( + std::ref(deactivator), "FileStorManager::updateState"); } contentBucketSpace.setNodeUpInLastNodeStateSeenByProvider(nodeUp); spi::ClusterState spiState(*derivedClusterState, _component.getIndex(), *contentBucketSpace.getDistribution()); diff --git a/storage/src/vespa/storage/storageserver/servicelayernodecontext.cpp b/storage/src/vespa/storage/storageserver/servicelayernodecontext.cpp index ecd52295d38..bbfc9afc7aa 100644 --- a/storage/src/vespa/storage/storageserver/servicelayernodecontext.cpp +++ b/storage/src/vespa/storage/storageserver/servicelayernodecontext.cpp @@ -4,9 +4,8 @@ namespace storage { -ServiceLayerNodeContext::ServiceLayerNodeContext( - framework::Clock::UP clock) - : StorageNodeContext(StorageComponentRegisterImpl::UP(new ServiceLayerComponentRegisterImpl), +ServiceLayerNodeContext::ServiceLayerNodeContext(framework::Clock::UP clock, bool use_btree_db) + : StorageNodeContext(std::make_unique<ServiceLayerComponentRegisterImpl>(use_btree_db), std::move(clock)), _componentRegister(dynamic_cast<ComponentRegister&>(StorageNodeContext::getComponentRegister())) { diff --git a/storage/src/vespa/storage/storageserver/servicelayernodecontext.h b/storage/src/vespa/storage/storageserver/servicelayernodecontext.h index 9266dc2c7e9..0d7ac5bff69 100644 --- a/storage/src/vespa/storage/storageserver/servicelayernodecontext.h +++ b/storage/src/vespa/storage/storageserver/servicelayernodecontext.h @@ -29,8 +29,7 @@ struct ServiceLayerNodeContext : public StorageNodeContext { * You can provide your own clock implementation. Useful in testing where * you want to fake the clock. */ - ServiceLayerNodeContext( - framework::Clock::UP clock = framework::Clock::UP(new RealClock)); + ServiceLayerNodeContext(framework::Clock::UP clock, bool use_btree_db); /** * Get the actual component register. Available as the actual type as the diff --git a/storageapi/src/tests/CMakeLists.txt b/storageapi/src/tests/CMakeLists.txt index 8b820adb467..f07b9d0a2bc 100644 --- a/storageapi/src/tests/CMakeLists.txt +++ b/storageapi/src/tests/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(storageapi_gtest_runner_app TEST SOURCES gtest_runner.cpp @@ -8,7 +9,7 @@ vespa_add_executable(storageapi_gtest_runner_app TEST storageapi_testmbusprot storageapi_testmessageapi storageapi - gtest + GTest::GTest ) vespa_add_test( diff --git a/storageapi/src/tests/buckets/CMakeLists.txt b/storageapi/src/tests/buckets/CMakeLists.txt index 42310be56f0..ea2cc109986 100644 --- a/storageapi/src/tests/buckets/CMakeLists.txt +++ b/storageapi/src/tests/buckets/CMakeLists.txt @@ -1,8 +1,9 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(storageapi_testbuckets SOURCES bucketinfotest.cpp DEPENDS storageapi - gtest + GTest::GTest ) diff --git a/storageapi/src/tests/mbusprot/CMakeLists.txt b/storageapi/src/tests/mbusprot/CMakeLists.txt index 2801c9a91dd..84b8ff0f1b0 100644 --- a/storageapi/src/tests/mbusprot/CMakeLists.txt +++ b/storageapi/src/tests/mbusprot/CMakeLists.txt @@ -1,8 +1,9 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(storageapi_testmbusprot SOURCES storageprotocoltest.cpp DEPENDS storageapi - gtest + GTest::GTest ) diff --git a/storageframework/src/tests/CMakeLists.txt b/storageframework/src/tests/CMakeLists.txt index d658605a911..1cf40b8a2ef 100644 --- a/storageframework/src/tests/CMakeLists.txt +++ b/storageframework/src/tests/CMakeLists.txt @@ -1,5 +1,7 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) + # Runner for unit tests written in gtest. # NOTE: All new test classes should be added here. vespa_add_executable(storageframework_gtest_runner_app TEST @@ -8,7 +10,7 @@ vespa_add_executable(storageframework_gtest_runner_app TEST DEPENDS storageframework_testclock storageframework_testthread - gtest + GTest::GTest ) vespa_add_test( diff --git a/storageframework/src/tests/clock/CMakeLists.txt b/storageframework/src/tests/clock/CMakeLists.txt index f887de61239..62bb6eeedc1 100644 --- a/storageframework/src/tests/clock/CMakeLists.txt +++ b/storageframework/src/tests/clock/CMakeLists.txt @@ -1,8 +1,9 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(storageframework_testclock SOURCES timetest.cpp DEPENDS storageframework - gtest + GTest::GTest ) diff --git a/storageframework/src/tests/thread/CMakeLists.txt b/storageframework/src/tests/thread/CMakeLists.txt index 3e6db8c9b57..810b0c59912 100644 --- a/storageframework/src/tests/thread/CMakeLists.txt +++ b/storageframework/src/tests/thread/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(storageframework_testthread SOURCES tickingthreadtest.cpp taskthreadtest.cpp DEPENDS storageframework - gtest + GTest::GTest ) diff --git a/storageserver/src/tests/CMakeLists.txt b/storageserver/src/tests/CMakeLists.txt index 9c475543b81..2981d452d5f 100644 --- a/storageserver/src/tests/CMakeLists.txt +++ b/storageserver/src/tests/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(storageserver_gtest_runner_app TEST SOURCES storageservertest.cpp @@ -8,7 +9,7 @@ vespa_add_executable(storageserver_gtest_runner_app TEST DEPENDS storageserver_storageapp vdstestlib - gtest + GTest::GTest ) vespa_add_test( diff --git a/storageserver/src/vespa/storageserver/app/distributorprocess.h b/storageserver/src/vespa/storageserver/app/distributorprocess.h index 48fa331ba54..e416f285268 100644 --- a/storageserver/src/vespa/storageserver/app/distributorprocess.h +++ b/storageserver/src/vespa/storageserver/app/distributorprocess.h @@ -12,7 +12,7 @@ namespace storage { -class DistributorProcess : public Process { +class DistributorProcess final : public Process { DistributorNodeContext _context; DistributorNode::NeedActiveState _activeFlag; bool _use_btree_database; @@ -23,7 +23,7 @@ class DistributorProcess : public Process { _visitDispatcherConfigHandler; public: - DistributorProcess(const config::ConfigUri & configUri); + explicit DistributorProcess(const config::ConfigUri & configUri); ~DistributorProcess() override; void shutdown() override; diff --git a/storageserver/src/vespa/storageserver/app/servicelayerprocess.cpp b/storageserver/src/vespa/storageserver/app/servicelayerprocess.cpp index bde93b9e4fb..4ff3810d85f 100644 --- a/storageserver/src/vespa/storageserver/app/servicelayerprocess.cpp +++ b/storageserver/src/vespa/storageserver/app/servicelayerprocess.cpp @@ -1,6 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "servicelayerprocess.h" +#include <vespa/config/helper/configgetter.hpp> +#include <vespa/storage/config/config-stor-server.h> #include <vespa/storage/storageserver/servicelayernode.h> #include <vespa/searchvisitor/searchvisitor.h> @@ -9,8 +11,21 @@ LOG_SETUP(".storageserver.service_layer_process"); namespace storage { -ServiceLayerProcess::ServiceLayerProcess(const config::ConfigUri & configUri) - : Process(configUri) +namespace { + +bool configured_to_use_btree_db(const config::ConfigUri& config_uri) { + using vespa::config::content::core::StorServerConfig; + auto server_config = config::ConfigGetter<StorServerConfig>::getConfig( + config_uri.getConfigId(), config_uri.getContext()); + return server_config->useContentNodeBtreeBucketDb; +} + +} + +ServiceLayerProcess::ServiceLayerProcess(const config::ConfigUri& configUri) + : Process(configUri), + _context(std::make_unique<framework::defaultimplementation::RealClock>(), + configured_to_use_btree_db(configUri)) { } diff --git a/storageserver/src/vespa/storageserver/app/servicelayerprocess.h b/storageserver/src/vespa/storageserver/app/servicelayerprocess.h index 27c2db8ed6f..b24640cbbd7 100644 --- a/storageserver/src/vespa/storageserver/app/servicelayerprocess.h +++ b/storageserver/src/vespa/storageserver/app/servicelayerprocess.h @@ -36,8 +36,8 @@ protected: ServiceLayerNodeContext _context; public: - ServiceLayerProcess(const config::ConfigUri & configUri); - ~ServiceLayerProcess(); + explicit ServiceLayerProcess(const config::ConfigUri & configUri); + ~ServiceLayerProcess() override; void shutdown() override; diff --git a/tenant-auth/OWNERS b/tenant-auth/OWNERS deleted file mode 100644 index d0a102ecbf4..00000000000 --- a/tenant-auth/OWNERS +++ /dev/null @@ -1 +0,0 @@ -jonmv diff --git a/tenant-auth/README.md b/tenant-auth/README.md deleted file mode 100644 index c7552bd987c..00000000000 --- a/tenant-auth/README.md +++ /dev/null @@ -1,2 +0,0 @@ -<!-- Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> -# Utilities that authenticate users to the hosted Vespa API, or to hosted Vespa applications. diff --git a/tenant-auth/src/test/java/ai/vespa/hosted/auth/AuthenticatorTest.java b/tenant-auth/src/test/java/ai/vespa/hosted/auth/AuthenticatorTest.java deleted file mode 100644 index 64282c26849..00000000000 --- a/tenant-auth/src/test/java/ai/vespa/hosted/auth/AuthenticatorTest.java +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.auth; - -public class AuthenticatorTest { - -} diff --git a/tenant-cd-api/CMakeLists.txt b/tenant-cd-api/CMakeLists.txt new file mode 100644 index 00000000000..971aa974aa2 --- /dev/null +++ b/tenant-cd-api/CMakeLists.txt @@ -0,0 +1,2 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +install_fat_java_artifact(tenant-cd-api) diff --git a/tenant-cd-api/pom.xml b/tenant-cd-api/pom.xml index b19d42d094f..23e5f3ec3f4 100644 --- a/tenant-cd-api/pom.xml +++ b/tenant-cd-api/pom.xml @@ -57,9 +57,7 @@ <artifactId>bundle-plugin</artifactId> <extensions>true</extensions> <configuration> - <attachBundleArtifact>true</attachBundleArtifact> - <bundleClassifierName>deploy</bundleClassifierName> - <useCommonAssemblyIds>false</useCommonAssemblyIds> + <useCommonAssemblyIds>true</useCommonAssemblyIds> </configuration> </plugin> <plugin> diff --git a/tenant-cd-commons/OWNERS b/tenant-cd-commons/OWNERS new file mode 100644 index 00000000000..0a0d219e4eb --- /dev/null +++ b/tenant-cd-commons/OWNERS @@ -0,0 +1,2 @@ +bjorncs +mortent diff --git a/tenant-cd-commons/README.md b/tenant-cd-commons/README.md new file mode 100644 index 00000000000..b1cd95606c8 --- /dev/null +++ b/tenant-cd-commons/README.md @@ -0,0 +1,3 @@ +# tenant-cd-commons + +Contains shared implementations of APIs/interfaces of `tenant-cd-api`.
\ No newline at end of file diff --git a/tenant-auth/pom.xml b/tenant-cd-commons/pom.xml index be8b42dd6c2..4d92be95c0b 100644 --- a/tenant-auth/pom.xml +++ b/tenant-cd-commons/pom.xml @@ -1,40 +1,56 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<!-- Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> + + <groupId>com.yahoo.vespa</groupId> + <artifactId>tenant-cd-commons</artifactId> + <packaging>jar</packaging> + <parent> <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> <version>7-SNAPSHOT</version> - <relativePath>../parent/pom.xml</relativePath> + <relativePath>../parent</relativePath> </parent> - <artifactId>tenant-auth</artifactId> - <description>Provides resources for authenticating with the hosted Vespa API or application containers</description> <dependencies> + <!-- compile --> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>hosted-api</artifactId> + <artifactId>tenant-cd-api</artifactId> <version>${project.version}</version> + <scope>provided</scope> </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>config-provisioning</artifactId> + <artifactId>security-utils</artifactId> <version>${project.version}</version> + <scope>provided</scope> </dependency> + + <!-- compile --> <dependency> <groupId>com.yahoo.vespa</groupId> - <artifactId>security-utils</artifactId> + <artifactId>hosted-api</artifactId> <version>${project.version}</version> </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> + <groupId>com.yahoo.vespa</groupId> + <artifactId>config-provisioning</artifactId> + <version>${project.version}</version> </dependency> </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + </plugin> + </plugins> + </build> + </project> diff --git a/tenant-auth/src/main/java/ai/vespa/hosted/auth/EndpointAuthenticator.java b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/DefaultEndpointAuthenticator.java index a13e2d8ee56..89414cc069a 100644 --- a/tenant-auth/src/main/java/ai/vespa/hosted/auth/EndpointAuthenticator.java +++ b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/DefaultEndpointAuthenticator.java @@ -1,5 +1,5 @@ // Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.auth; +package ai.vespa.hosted.cd.commons; import ai.vespa.hosted.api.Properties; import com.yahoo.config.provision.SystemName; @@ -26,12 +26,12 @@ import static ai.vespa.hosted.api.Properties.getNonBlankProperty; * * @author jonmv */ -public class EndpointAuthenticator implements ai.vespa.hosted.api.EndpointAuthenticator { +public class DefaultEndpointAuthenticator implements EndpointAuthenticator { - private static final Logger logger = Logger.getLogger(EndpointAuthenticator.class.getName()); + private static final Logger logger = Logger.getLogger(DefaultEndpointAuthenticator.class.getName()); /** Don't touch. */ - public EndpointAuthenticator(@SuppressWarnings("unused") SystemName __) { } + public DefaultEndpointAuthenticator(@SuppressWarnings("unused") SystemName __) { } /** * If {@code System.getProperty("vespa.test.credentials.root")} is set, key and certificate files diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/EndpointAuthenticator.java b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/EndpointAuthenticator.java index 81813335a63..e7936ddea7a 100644 --- a/hosted-api/src/main/java/ai/vespa/hosted/api/EndpointAuthenticator.java +++ b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/EndpointAuthenticator.java @@ -1,10 +1,9 @@ // Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.api; +package ai.vespa.hosted.cd.commons; import javax.net.ssl.SSLContext; import java.net.http.HttpRequest; import java.security.NoSuchAlgorithmException; -import java.util.Optional; /** * Adds environment dependent authentication to HTTP request against Vespa deployments. diff --git a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/impl/http/HttpDeployment.java b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/HttpDeployment.java index 65210455b85..90a33bcb513 100644 --- a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/impl/http/HttpDeployment.java +++ b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/HttpDeployment.java @@ -1,7 +1,6 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.cd.impl.http; +package ai.vespa.hosted.cd.commons; -import ai.vespa.hosted.api.EndpointAuthenticator; import ai.vespa.hosted.cd.Deployment; import ai.vespa.hosted.cd.Endpoint; diff --git a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/impl/http/HttpEndpoint.java b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/HttpEndpoint.java index f48973b7382..cf8865df878 100644 --- a/cloud-tenant-cd/src/main/java/ai/vespa/hosted/cd/impl/http/HttpEndpoint.java +++ b/tenant-cd-commons/src/main/java/ai/vespa/hosted/cd/commons/HttpEndpoint.java @@ -1,7 +1,6 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.cd.impl.http; +package ai.vespa.hosted.cd.commons; -import ai.vespa.hosted.api.EndpointAuthenticator; import ai.vespa.hosted.cd.Endpoint; import javax.net.ssl.SSLParameters; diff --git a/vdslib/src/tests/CMakeLists.txt b/vdslib/src/tests/CMakeLists.txt index f552808f97c..458f3bda01d 100644 --- a/vdslib/src/tests/CMakeLists.txt +++ b/vdslib/src/tests/CMakeLists.txt @@ -1,5 +1,7 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) + # Runner for unit tests written in gtest. # NOTE: All new test classes should be added here. vespa_add_executable(vdslib_gtest_runner_app TEST @@ -11,7 +13,7 @@ vespa_add_executable(vdslib_gtest_runner_app TEST vdslib_testdistribution vdslib_teststate vdslib_testthread - gtest + GTest::GTest ) vespa_add_test( diff --git a/vdslib/src/tests/bucketdistribution/CMakeLists.txt b/vdslib/src/tests/bucketdistribution/CMakeLists.txt index ecb29e9b12d..986a7ff26ff 100644 --- a/vdslib/src/tests/bucketdistribution/CMakeLists.txt +++ b/vdslib/src/tests/bucketdistribution/CMakeLists.txt @@ -1,8 +1,9 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(vdslib_bucketdistributiontest SOURCES bucketdistributiontest.cpp DEPENDS vdslib - gtest + GTest::GTest ) diff --git a/vdslib/src/tests/container/CMakeLists.txt b/vdslib/src/tests/container/CMakeLists.txt index 9f7b2e33efa..47ffbcf7b82 100644 --- a/vdslib/src/tests/container/CMakeLists.txt +++ b/vdslib/src/tests/container/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(vdslib_containertest SOURCES parameterstest.cpp @@ -6,5 +7,5 @@ vespa_add_library(vdslib_containertest documentsummarytest.cpp DEPENDS vdslib - gtest + GTest::GTest ) diff --git a/vdslib/src/tests/distribution/CMakeLists.txt b/vdslib/src/tests/distribution/CMakeLists.txt index cdcfcfce5f9..61563c4c448 100644 --- a/vdslib/src/tests/distribution/CMakeLists.txt +++ b/vdslib/src/tests/distribution/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(vdslib_testdistribution SOURCES distributiontest.cpp @@ -6,5 +7,5 @@ vespa_add_library(vdslib_testdistribution idealnodecalculatorimpltest.cpp DEPENDS vdslib - gtest + GTest::GTest ) diff --git a/vdslib/src/tests/distribution/distributiontest.cpp b/vdslib/src/tests/distribution/distributiontest.cpp index f0b48faebef..5a337433bdb 100644 --- a/vdslib/src/tests/distribution/distributiontest.cpp +++ b/vdslib/src/tests/distribution/distributiontest.cpp @@ -13,10 +13,14 @@ #include <vespa/vespalib/io/fileutil.h> #include <vespa/vespalib/stllike/lexical_cast.h> #include <vespa/vespalib/text/stringtokenizer.h> +#include <vespa/vespalib/util/benchmark_timer.h> +#include <gmock/gmock.h> #include <chrono> #include <thread> #include <fstream> +using namespace ::testing; + namespace storage::lib { template <typename T> @@ -1143,4 +1147,106 @@ TEST(DistributionTest, test_hierarchical_distribute_less_than_redundancy) } } +TEST(DistributionTest, wildcard_top_level_distribution_gives_expected_node_results) { + std::string raw_config = R"(redundancy 2 +initial_redundancy 2 +ensure_primary_persisted true +ready_copies 2 +active_per_leaf_group false +distributor_auto_ownership_transfer_on_whole_group_down true +group[0].index "invalid" +group[0].name "invalid" +group[0].capacity 5 +group[0].partitions "*" +group[1].index "0" +group[1].name "switch0" +group[1].capacity 3 +group[1].partitions "" +group[1].nodes[0].index 0 +group[1].nodes[0].retired false +group[1].nodes[1].index 1 +group[1].nodes[1].retired false +group[1].nodes[2].index 2 +group[1].nodes[2].retired false +group[2].index "1" +group[2].name "switch1" +group[2].capacity 2 +group[2].partitions "" +group[2].nodes[0].index 3 +group[2].nodes[0].retired false +group[2].nodes[1].index 4 +group[2].nodes[1].retired false +disk_distribution "MODULO_BID" +)"; + Distribution distr(raw_config); + ClusterState state("version:1 distributor:5 storage:5"); + + auto nodes_of = [&](uint32_t bucket){ + std::vector<uint16_t> actual; + distr.getIdealNodes(NodeType::STORAGE, state, document::BucketId(16, bucket), actual); + return actual; + }; + + EXPECT_THAT(nodes_of(1), ElementsAre(0, 2)); + EXPECT_THAT(nodes_of(2), ElementsAre(2, 0)); + EXPECT_THAT(nodes_of(3), ElementsAre(4, 3)); + EXPECT_THAT(nodes_of(4), ElementsAre(3, 4)); + EXPECT_THAT(nodes_of(5), ElementsAre(4, 3)); + EXPECT_THAT(nodes_of(6), ElementsAre(1, 0)); + EXPECT_THAT(nodes_of(7), ElementsAre(2, 0)); +} + +namespace { + +std::string generate_config_with_n_1node_groups(int n_groups) { + std::ostringstream config_os; + std::ostringstream partition_os; + for (int i = 0; i < n_groups - 1; ++i) { + partition_os << "1|"; + } + partition_os << '*'; + config_os << "redundancy " << n_groups << "\n" + << "initial_redundancy " << n_groups << "\n" + << "ensure_primary_persisted true\n" + << "ready_copies " << n_groups << "\n" + << "active_per_leaf_group true\n" + << "distributor_auto_ownership_transfer_on_whole_group_down true\n" + << "group[0].index \"invalid\"\n" + << "group[0].name \"invalid\"\n" + << "group[0].capacity " << n_groups << "\n" + << "group[0].partitions \"" << partition_os.str() << "\"\n"; + + for (int i = 0; i < n_groups; ++i) { + int g = i + 1; + config_os << "group[" << g << "].index \"" << i << "\"\n" + << "group[" << g << "].name \"group" << g << "\"\n" + << "group[" << g << "].capacity 1\n" + << "group[" << g << "].partitions \"\"\n" + << "group[" << g << "].nodes[0].index \"" << i << "\"\n" + << "group[" << g << "].nodes[0].retired false\n"; + } + return config_os.str(); +} + +std::string generate_state_with_n_nodes_up(int n_nodes) { + std::ostringstream state_os; + state_os << "version:1 bits:8 distributor:" << n_nodes << " storage:" << n_nodes; + return state_os.str(); +} + +} + +TEST(DistributionTest, DISABLED_benchmark_ideal_state_for_many_groups) { + const int n_groups = 150; + Distribution distr(generate_config_with_n_1node_groups(n_groups)); + ClusterState state(generate_state_with_n_nodes_up(n_groups)); + + std::vector<uint16_t> actual; + uint32_t bucket = 0; + auto min_time = vespalib::BenchmarkTimer::benchmark([&]{ + distr.getIdealNodes(NodeType::STORAGE, state, document::BucketId(16, (bucket++ & 0xffffU)), actual); + }, 5.0); + fprintf(stderr, "%.10f seconds\n", min_time); +} + } diff --git a/vdslib/src/tests/state/CMakeLists.txt b/vdslib/src/tests/state/CMakeLists.txt index 0e057d12226..d28ff47798a 100644 --- a/vdslib/src/tests/state/CMakeLists.txt +++ b/vdslib/src/tests/state/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(vdslib_teststate SOURCES cluster_state_bundle_test.cpp @@ -7,5 +8,5 @@ vespa_add_library(vdslib_teststate nodestatetest.cpp DEPENDS vdslib - gtest + GTest::GTest ) diff --git a/vdslib/src/tests/thread/CMakeLists.txt b/vdslib/src/tests/thread/CMakeLists.txt index 4d1e753a8f6..df2e8bf43f9 100644 --- a/vdslib/src/tests/thread/CMakeLists.txt +++ b/vdslib/src/tests/thread/CMakeLists.txt @@ -1,8 +1,9 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_library(vdslib_testthread SOURCES taskschedulertest.cpp DEPENDS vdslib - gtest + GTest::GTest ) diff --git a/vdslib/src/vespa/vdslib/distribution/distribution.cpp b/vdslib/src/vespa/vdslib/distribution/distribution.cpp index 0abb99e17d2..4c74923c58b 100644 --- a/vdslib/src/vespa/vdslib/distribution/distribution.cpp +++ b/vdslib/src/vespa/vdslib/distribution/distribution.cpp @@ -351,6 +351,7 @@ namespace { const Group* _group; double _score; + ScoredGroup() : _group(nullptr), _score(0) {} ScoredGroup(const Group* group, double score) : _group(group), _score(score) {} @@ -430,40 +431,36 @@ Distribution::getIdealGroups(const document::BucketId& bucket, std::vector<ResultGroup>& results) const { if (parent.isLeafGroup()) { - results.push_back(ResultGroup(parent, redundancy)); + results.emplace_back(parent, redundancy); return; } - const Group::Distribution& redundancyArray( - parent.getDistribution(redundancy)); - std::vector<ScoredGroup> tmpResults(redundancyArray.size(), - ScoredGroup(0, 0)); - uint32_t seed(getGroupSeed(bucket, clusterState, parent)); + const Group::Distribution& redundancyArray = parent.getDistribution(redundancy); + uint32_t seed = getGroupSeed(bucket, clusterState, parent); RandomGen random(seed); uint32_t currentIndex = 0; - const std::map<uint16_t, Group*>& subGroups(parent.getSubGroups()); - for (std::map<uint16_t, Group*>::const_iterator it = subGroups.begin(); - it != subGroups.end(); ++it) - { - while (it->first < currentIndex++) random.nextDouble(); - double score = random.nextDouble(); - if (it->second->getCapacity() != 1) { - // Capacity shouldn't possibly be 0. - // Verified in Group::setCapacity() - score = std::pow(score, 1.0 / it->second->getCapacity().getValue()); + const auto& subGroups = parent.getSubGroups(); + std::vector<ScoredGroup> tmpResults; + tmpResults.reserve(subGroups.size()); + for (const auto& g : subGroups) { + while (g.first < currentIndex++) { + random.nextDouble(); } - if (score > tmpResults.back()._score) { - tmpResults.push_back(ScoredGroup(it->second, score)); - std::sort(tmpResults.begin(), tmpResults.end()); - tmpResults.pop_back(); + double score = random.nextDouble(); + if (g.second->getCapacity() != 1) { + // Capacity shouldn't possibly be 0. + // Verified in Group::setCapacity() + score = std::pow(score, 1.0 / g.second->getCapacity().getValue()); } + tmpResults.emplace_back(g.second, score); } - while (tmpResults.back()._group == nullptr) { - tmpResults.pop_back(); + std::sort(tmpResults.begin(), tmpResults.end()); + if (tmpResults.size() > redundancyArray.size()) { + tmpResults.resize(redundancyArray.size()); } for (uint32_t i=0, n=tmpResults.size(); i<n; ++i) { ScoredGroup& group(tmpResults[i]); - // This should never happen. Config should verify that each group - // has enough groups beneath them. + // This should never happen. Config should verify that each group + // has enough groups beneath them. assert(group._group != nullptr); getIdealGroups(bucket, clusterState, *group._group, redundancyArray[i], results); diff --git a/vespa-osgi-testrunner/CMakeLists.txt b/vespa-osgi-testrunner/CMakeLists.txt new file mode 100644 index 00000000000..58aba186710 --- /dev/null +++ b/vespa-osgi-testrunner/CMakeLists.txt @@ -0,0 +1,2 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +install_fat_java_artifact(vespa-osgi-testrunner) diff --git a/vespa-osgi-testrunner/pom.xml b/vespa-osgi-testrunner/pom.xml index 5ed9f75c9eb..62ea578f14f 100644 --- a/vespa-osgi-testrunner/pom.xml +++ b/vespa-osgi-testrunner/pom.xml @@ -79,9 +79,7 @@ <version>${project.version}</version> <extensions>true</extensions> <configuration> - <attachBundleArtifact>true</attachBundleArtifact> - <bundleClassifierName>deploy</bundleClassifierName> - <useCommonAssemblyIds>false</useCommonAssemblyIds> + <useCommonAssemblyIds>true</useCommonAssemblyIds> </configuration> </plugin> <plugin> diff --git a/vespajlib/abi-spec.json b/vespajlib/abi-spec.json index 154b6871392..7e7e376a8df 100644 --- a/vespajlib/abi-spec.json +++ b/vespajlib/abi-spec.json @@ -1904,6 +1904,7 @@ ], "methods": [ "public abstract java.lang.Double apply(com.yahoo.tensor.evaluation.EvaluationContext)", + "public java.util.Optional asTensorFunction()", "public java.lang.String toString(com.yahoo.tensor.functions.ToStringContext)", "public bridge synthetic java.lang.Object apply(java.lang.Object)" ], @@ -2609,6 +2610,7 @@ "public abstract com.yahoo.tensor.TensorType type(com.yahoo.tensor.evaluation.TypeContext)", "public final com.yahoo.tensor.Tensor evaluate()", "public abstract java.lang.String toString(com.yahoo.tensor.functions.ToStringContext)", + "public java.util.Optional asScalarFunction()", "public java.lang.String toString()" ], "fields": [] diff --git a/vespalib/src/tests/alloc/alloc_test.cpp b/vespalib/src/tests/alloc/alloc_test.cpp index d46d2374dfc..4dbeba62ee1 100644 --- a/vespalib/src/tests/alloc/alloc_test.cpp +++ b/vespalib/src/tests/alloc/alloc_test.cpp @@ -8,6 +8,14 @@ using namespace vespalib; using namespace vespalib::alloc; +#ifndef __SANITIZE_ADDRESS__ +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define __SANITIZE_ADDRESS__ +#endif +#endif +#endif + template <typename T> void testSwap(T & a, T & b) @@ -192,6 +200,13 @@ TEST("auto alloced mmap alloc can not be extended if no room") { TEST_DO(verifyNoExtensionWhenNoRoom(buf, reserved, SZ)); } +/* + * The two following tests are disabled when address sanitizer is + * enabled since extra instrumentation code might trigger extra mmap + * or munmap calls, breaking some of the assumptions in the disabled + * tests. + */ +#ifndef __SANITIZE_ADDRESS__ TEST("mmap alloc can be extended if room") { Alloc dummy = Alloc::allocMMap(100); Alloc reserved = Alloc::allocMMap(100); @@ -209,6 +224,7 @@ TEST("mmap alloc can not be extended if no room") { TEST_DO(verifyNoExtensionWhenNoRoom(buf, reserved, 4096)); } #endif +#endif TEST("heap alloc can not be shrinked") { Alloc buf = Alloc::allocHeap(101); diff --git a/vespalib/src/tests/crypto/CMakeLists.txt b/vespalib/src/tests/crypto/CMakeLists.txt index b930b5715b5..d0661461dd4 100644 --- a/vespalib/src/tests/crypto/CMakeLists.txt +++ b/vespalib/src/tests/crypto/CMakeLists.txt @@ -1,10 +1,11 @@ # Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_crypto_crypto_test_app TEST SOURCES crypto_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_crypto_crypto_test_app COMMAND vespalib_crypto_crypto_test_app) diff --git a/vespalib/src/tests/datastore/datastore/CMakeLists.txt b/vespalib/src/tests/datastore/datastore/CMakeLists.txt index eb3e0a4576a..631c1812a7f 100644 --- a/vespalib/src/tests/datastore/datastore/CMakeLists.txt +++ b/vespalib/src/tests/datastore/datastore/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_datastore_test_app TEST SOURCES datastore_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_datastore_test_app COMMAND vespalib_datastore_test_app) diff --git a/vespalib/src/tests/datastore/unique_store/CMakeLists.txt b/vespalib/src/tests/datastore/unique_store/CMakeLists.txt index d72e8c10ad5..29b34e93247 100644 --- a/vespalib/src/tests/datastore/unique_store/CMakeLists.txt +++ b/vespalib/src/tests/datastore/unique_store/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_unique_store_test_app TEST SOURCES unique_store_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_unique_store_test_app COMMAND vespalib_unique_store_test_app) diff --git a/vespalib/src/tests/datastore/unique_store_dictionary/CMakeLists.txt b/vespalib/src/tests/datastore/unique_store_dictionary/CMakeLists.txt index b1478dea22c..2208902b5bb 100644 --- a/vespalib/src/tests/datastore/unique_store_dictionary/CMakeLists.txt +++ b/vespalib/src/tests/datastore/unique_store_dictionary/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_unique_store_dictionary_test_app TEST SOURCES unique_store_dictionary_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_unique_store_dictionary_test_app COMMAND vespalib_unique_store_dictionary_test_app) diff --git a/vespalib/src/tests/datastore/unique_store_string_allocator/CMakeLists.txt b/vespalib/src/tests/datastore/unique_store_string_allocator/CMakeLists.txt index c2a9999d545..9984877ab21 100644 --- a/vespalib/src/tests/datastore/unique_store_string_allocator/CMakeLists.txt +++ b/vespalib/src/tests/datastore/unique_store_string_allocator/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_unique_store_string_allocator_test_app TEST SOURCES unique_store_string_allocator_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_unique_store_string_allocator_test_app COMMAND vespalib_unique_store_string_allocator_test_app) diff --git a/vespalib/src/tests/overload/CMakeLists.txt b/vespalib/src/tests/overload/CMakeLists.txt index 67aa6230225..a03f6fbdc8d 100644 --- a/vespalib/src/tests/overload/CMakeLists.txt +++ b/vespalib/src/tests/overload/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_overload_test_app TEST SOURCES overload_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_overload_test_app COMMAND vespalib_overload_test_app) diff --git a/vespalib/src/tests/stllike/CMakeLists.txt b/vespalib/src/tests/stllike/CMakeLists.txt index ebf7de9c747..41e0b9e8507 100644 --- a/vespalib/src/tests/stllike/CMakeLists.txt +++ b/vespalib/src/tests/stllike/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_hash_test_app TEST SOURCES hash_test.cpp @@ -52,6 +53,6 @@ vespa_add_executable(vespalib_replace_variable_test_app TEST replace_variable_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_replace_variable_test_app COMMAND vespalib_replace_variable_test_app) diff --git a/vespalib/src/tests/time/CMakeLists.txt b/vespalib/src/tests/time/CMakeLists.txt index e43bd9097e5..ba639f2392e 100644 --- a/vespalib/src/tests/time/CMakeLists.txt +++ b/vespalib/src/tests/time/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_time_box_test_app TEST SOURCES time_box_test.cpp @@ -11,6 +12,6 @@ vespa_add_executable(vespalib_time_test_app TEST time_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_time_test_app COMMAND vespalib_time_test_app) diff --git a/vespalib/src/tests/typify/CMakeLists.txt b/vespalib/src/tests/typify/CMakeLists.txt index 29e95af1988..c8e53d6baca 100644 --- a/vespalib/src/tests/typify/CMakeLists.txt +++ b/vespalib/src/tests/typify/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_typify_test_app TEST SOURCES typify_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_typify_test_app COMMAND vespalib_typify_test_app) diff --git a/vespalib/src/tests/util/reusable_set/CMakeLists.txt b/vespalib/src/tests/util/reusable_set/CMakeLists.txt index 9c46b5ba61e..edbbc2ff11f 100644 --- a/vespalib/src/tests/util/reusable_set/CMakeLists.txt +++ b/vespalib/src/tests/util/reusable_set/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_reusable_set_test_app TEST SOURCES reusable_set_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_reusable_set_test_app COMMAND vespalib_reusable_set_test_app) diff --git a/vespalib/src/tests/visit_ranges/CMakeLists.txt b/vespalib/src/tests/visit_ranges/CMakeLists.txt index de94b2ebb1e..3c51d7c1e34 100644 --- a/vespalib/src/tests/visit_ranges/CMakeLists.txt +++ b/vespalib/src/tests/visit_ranges/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalib_visit_ranges_test_app TEST SOURCES visit_ranges_test.cpp DEPENDS vespalib - gtest + GTest::GTest ) vespa_add_test(NAME vespalib_visit_ranges_test_app COMMAND vespalib_visit_ranges_test_app) diff --git a/vespalib/src/vespa/vespalib/text/utf8.cpp b/vespalib/src/vespa/vespalib/text/utf8.cpp index 660178aaaa1..58b587d45b5 100644 --- a/vespalib/src/vespa/vespalib/text/utf8.cpp +++ b/vespalib/src/vespa/vespalib/text/utf8.cpp @@ -175,8 +175,9 @@ Utf8ReaderForZTS::getComplexChar(unsigned char firstbyte, uint32_t fallback) } -Utf8Writer& -Utf8Writer::putChar(uint32_t codepoint) +template <typename Target> +Utf8Writer<Target>& +Utf8Writer<Target>::putChar(uint32_t codepoint) { if (codepoint < 0x80) { _target.push_back((char)codepoint); @@ -229,5 +230,23 @@ Utf8Writer::putChar(uint32_t codepoint) return *this; } +template class Utf8Writer<vespalib::string>; +template class Utf8Writer<std::string>; -} // namespace vespalib +template <typename T> +T Utf8::filter_invalid_sequences(const T& input) +{ + T retval; + Utf8Reader reader(input.c_str(), input.size()); + Utf8Writer writer(retval); + while (reader.hasMore()) { + uint32_t ch = reader.getChar(); + writer.putChar(ch); + } + return retval; +} + +template vespalib::string Utf8::filter_invalid_sequences(const vespalib::string&); +template std::string Utf8::filter_invalid_sequences(const std::string&); + +} // namespace diff --git a/vespalib/src/vespa/vespalib/text/utf8.h b/vespalib/src/vespa/vespalib/text/utf8.h index 0c75203fbbe..e65aaee9708 100644 --- a/vespalib/src/vespa/vespalib/text/utf8.h +++ b/vespalib/src/vespa/vespalib/text/utf8.h @@ -28,6 +28,15 @@ public: }; /** + * Filter a string (std::string or vespalib::string) + * and replace any invalid UTF8 sequences with the + * standard replacement char U+FFFD; note that any + * UTF-8 encoded surrogates are also considered invalid. + **/ + template <typename T> + static T filter_invalid_sequences(const T& input); + + /** * check if a byte is valid as the first byte of an UTF-8 character. * @param c the byte to be checked * @return true if a valid UTF-8 character can start with this byte @@ -155,7 +164,7 @@ protected: first_high_surrogate = 0xD800, last_high_surrogate = 0xDBFF, first_low_surrogate = 0xDC00, - last_low_surrogate = 0xDCFF + last_low_surrogate = 0xDFFF }; }; @@ -321,9 +330,10 @@ public: /** * @brief Writer class that appends UTF-8 characters to a string **/ +template <typename Target> class Utf8Writer : public Utf8 { - string &_target; + Target &_target; public: /** * construct a writer appending to the given string @@ -331,7 +341,7 @@ public: * that the writer will append to. Must be writable * and must be kept alive while the writer is active. **/ - Utf8Writer(string &target) : _target(target) {} + Utf8Writer(Target &target) : _target(target) {} /** * append the given character to the target string. diff --git a/vespalog/src/test/log_message/CMakeLists.txt b/vespalog/src/test/log_message/CMakeLists.txt index 51c888f67f3..a42e7f24bf3 100644 --- a/vespalog/src/test/log_message/CMakeLists.txt +++ b/vespalog/src/test/log_message/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +find_package(GTest REQUIRED) vespa_add_executable(vespalog_log_message_test_app TEST SOURCES log_message_test.cpp DEPENDS vespalog - gtest + GTest::GTest ) vespa_add_test(NAME vespalog_log_message_test_app COMMAND vespalog_log_message_test_app) |