summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--application/abi-spec.json2
-rw-r--r--application/src/main/java/com/yahoo/application/container/Processing.java12
-rw-r--r--application/src/main/java/com/yahoo/application/container/ProcessingBase.java16
-rw-r--r--application/src/main/java/com/yahoo/application/container/Search.java13
-rw-r--r--client/go/cmd/prod.go31
-rw-r--r--client/go/cmd/test.go109
-rw-r--r--client/go/cmd/test_test.go23
-rw-r--r--client/go/cmd/testdata/empty.json1
-rw-r--r--client/go/cmd/testdata/tests/expected-suite.out129
-rw-r--r--client/go/cmd/testdata/tests/expected.out2
-rw-r--r--client/go/cmd/testdata/tests/production-test/illegal-reference.json14
-rw-r--r--client/go/cmd/testdata/tests/system-test/test.json2
-rw-r--r--client/go/cmd/testdata/tests/system-test/wrong-code.json14
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandler.java39
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandlerTest.java75
-rw-r--r--container-core/abi-spec.json9
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java2
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java7
-rw-r--r--container-core/src/main/java/com/yahoo/processing/handler/AbstractProcessingHandler.java3
-rw-r--r--container-core/src/main/java/com/yahoo/processing/handler/ProcessingResponse.java20
-rw-r--r--container-core/src/main/java/com/yahoo/processing/rendering/AsynchronousSectionedRenderer.java36
-rw-r--r--container-core/src/main/java/com/yahoo/processing/rendering/Renderer.java22
-rw-r--r--container-core/src/test/java/com/yahoo/processing/rendering/AsynchronousSectionedRendererTest.java10
-rw-r--r--container-search/abi-spec.json6
-rw-r--r--container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java26
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/Renderer.java13
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java2
-rw-r--r--container-search/src/test/java/com/yahoo/search/rendering/AsyncGroupPopulationTestCase.java26
-rw-r--r--container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java4
-rw-r--r--container-search/src/test/java/com/yahoo/search/rendering/SyncDefaultRendererTestCase.java22
-rw-r--r--container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java32
-rw-r--r--default_build_settings.cmake20
-rw-r--r--dist/vespa.spec15
-rw-r--r--eval/src/vespa/eval/eval/typed_cells.h4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java7
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java4
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp2
-rw-r--r--searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp2
-rw-r--r--searchlib/src/tests/attribute/enumstore/enumstore_test.cpp204
-rw-r--r--searchlib/src/tests/transactionlog/CMakeLists.txt3
-rw-r--r--searchlib/src/tests/transactionlog/translogclient_test.cpp101
-rwxr-xr-xsearchlib/src/tests/transactionlog/translogclient_test.sh6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_header.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_header.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.cpp159
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.h3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/i_enum_store_dictionary.h19
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multienumattribute.cpp8
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postingstore.cpp195
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postingstore.h9
-rw-r--r--searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singleenumattribute.cpp13
-rw-r--r--searchlib/src/vespa/searchlib/docstore/compacter.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp15
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h12
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp9
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hamming_distance.h10
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/domain.cpp31
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/domainpart.cpp65
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/domainpart.h10
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/ichunk.cpp45
-rw-r--r--searchlib/src/vespa/searchlib/transactionlog/ichunk.h16
-rw-r--r--staging_vespalib/src/tests/singleexecutor/singleexecutor_test.cpp34
-rw-r--r--staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp16
-rw-r--r--staging_vespalib/src/vespa/vespalib/util/singleexecutor.h5
-rw-r--r--storage/src/tests/distributor/putoperationtest.cpp32
-rw-r--r--storage/src/vespa/storage/distributor/persistencemessagetracker.cpp7
-rw-r--r--vespajlib/src/main/java/com/yahoo/concurrent/CompletableFutures.java45
-rw-r--r--vespalib/src/tests/btree/btree_store/btree_store_test.cpp91
-rw-r--r--vespalib/src/tests/datastore/sharded_hash_map/sharded_hash_map_test.cpp186
-rw-r--r--vespalib/src/tests/util/rcuvector/rcuvector_test.cpp16
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreeiterator.h6
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenode.h5
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreestore.h7
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreestore.hpp44
-rw-r--r--vespalib/src/vespa/vespalib/datastore/CMakeLists.txt1
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store.hpp44
-rw-r--r--vespalib/src/vespa/vespalib/datastore/entry_ref_filter.cpp28
-rw-r--r--vespalib/src/vespa/vespalib/datastore/entry_ref_filter.h35
-rw-r--r--vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.cpp106
-rw-r--r--vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.h21
-rw-r--r--vespalib/src/vespa/vespalib/datastore/i_unique_store_dictionary.h3
-rw-r--r--vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp29
-rw-r--r--vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h5
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store.hpp5
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.hpp8
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_remapper.h33
-rw-r--r--vespalib/src/vespa/vespalib/hwaccelrated/iaccelrated.cpp60
-rw-r--r--vespalib/src/vespa/vespalib/util/rcuvector.h6
-rw-r--r--vespalib/src/vespa/vespalib/util/rcuvector.hpp32
94 files changed, 1794 insertions, 843 deletions
diff --git a/application/abi-spec.json b/application/abi-spec.json
index 5c298471b9c..2138f12854c 100644
--- a/application/abi-spec.json
+++ b/application/abi-spec.json
@@ -347,7 +347,7 @@
"public final com.yahoo.processing.Response process(com.yahoo.component.ComponentSpecification, com.yahoo.processing.Request)",
"protected abstract com.yahoo.processing.Response doProcess(com.yahoo.component.chain.Chain, com.yahoo.processing.Request)",
"public final byte[] processAndRender(com.yahoo.component.ComponentSpecification, com.yahoo.component.ComponentSpecification, com.yahoo.processing.Request)",
- "protected abstract com.google.common.util.concurrent.ListenableFuture doProcessAndRender(com.yahoo.component.ComponentSpecification, com.yahoo.processing.Request, com.yahoo.processing.rendering.Renderer, java.io.ByteArrayOutputStream)",
+ "protected abstract java.util.concurrent.CompletableFuture doProcessAndRender(com.yahoo.component.ComponentSpecification, com.yahoo.processing.Request, com.yahoo.processing.rendering.Renderer, java.io.ByteArrayOutputStream)",
"protected com.yahoo.component.chain.Chain getChain(com.yahoo.component.ComponentSpecification)",
"protected final com.yahoo.processing.rendering.Renderer getRenderer(com.yahoo.component.ComponentSpecification)",
"protected abstract com.yahoo.processing.rendering.Renderer doGetRenderer(com.yahoo.component.ComponentSpecification)"
diff --git a/application/src/main/java/com/yahoo/application/container/Processing.java b/application/src/main/java/com/yahoo/application/container/Processing.java
index 1f96fe2294b..4ca367ea720 100644
--- a/application/src/main/java/com/yahoo/application/container/Processing.java
+++ b/application/src/main/java/com/yahoo/application/container/Processing.java
@@ -2,7 +2,6 @@
package com.yahoo.application.container;
import com.yahoo.api.annotations.Beta;
-import com.google.common.util.concurrent.ListenableFuture;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.chain.Chain;
import com.yahoo.processing.Processor;
@@ -15,6 +14,7 @@ import com.yahoo.processing.rendering.Renderer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
/**
* @author Einar M R Rosenvinge
@@ -41,14 +41,14 @@ public final class Processing extends ProcessingBase<Request, Response, Processo
}
@Override
- protected ListenableFuture<Boolean> doProcessAndRender(ComponentSpecification chainSpec,
- Request request,
- Renderer<Response> renderer,
- ByteArrayOutputStream stream) throws IOException {
+ protected CompletableFuture<Boolean> doProcessAndRender(ComponentSpecification chainSpec,
+ Request request,
+ Renderer<Response> renderer,
+ ByteArrayOutputStream stream) throws IOException {
Execution execution = handler.createExecution(getChain(chainSpec), request);
Response response = execution.process(request);
- return renderer.render(stream, response, execution, request);
+ return renderer.renderResponse(stream, response, execution, request);
}
@Override
diff --git a/application/src/main/java/com/yahoo/application/container/ProcessingBase.java b/application/src/main/java/com/yahoo/application/container/ProcessingBase.java
index 2b4ea822d03..96866b94e29 100644
--- a/application/src/main/java/com/yahoo/application/container/ProcessingBase.java
+++ b/application/src/main/java/com/yahoo/application/container/ProcessingBase.java
@@ -2,20 +2,18 @@
package com.yahoo.application.container;
import com.yahoo.api.annotations.Beta;
-import com.google.common.util.concurrent.ListenableFuture;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.chain.Chain;
import com.yahoo.processing.Processor;
import com.yahoo.processing.Request;
import com.yahoo.processing.Response;
import com.yahoo.processing.execution.chain.ChainRegistry;
-import com.yahoo.processing.rendering.AsynchronousRenderer;
import com.yahoo.processing.rendering.Renderer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
/**
* @author gjoranv
@@ -45,13 +43,13 @@ public abstract class ProcessingBase<REQUEST extends Request, RESPONSE extends R
REQUEST request) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
Renderer<RESPONSE> renderer = getRenderer(rendererSpec);
- ListenableFuture<Boolean> renderTask = doProcessAndRender(chainSpec, request, renderer, stream);
+ CompletableFuture<Boolean> renderTask = doProcessAndRender(chainSpec, request, renderer, stream);
awaitFuture(renderTask);
return stream.toByteArray();
}
- private void awaitFuture(ListenableFuture<Boolean> renderTask) {
+ private void awaitFuture(CompletableFuture<Boolean> renderTask) {
try {
renderTask.get();
} catch (InterruptedException | ExecutionException e) {
@@ -59,10 +57,10 @@ public abstract class ProcessingBase<REQUEST extends Request, RESPONSE extends R
}
}
- protected abstract ListenableFuture<Boolean> doProcessAndRender(ComponentSpecification chainSpec,
- REQUEST request,
- Renderer<RESPONSE> renderer,
- ByteArrayOutputStream stream) throws IOException ;
+ protected abstract CompletableFuture<Boolean> doProcessAndRender(ComponentSpecification chainSpec,
+ REQUEST request,
+ Renderer<RESPONSE> renderer,
+ ByteArrayOutputStream stream) throws IOException ;
protected Chain<PROCESSOR> getChain(ComponentSpecification chainSpec) {
Chain<PROCESSOR> chain = getChains().getComponent(chainSpec);
diff --git a/application/src/main/java/com/yahoo/application/container/Search.java b/application/src/main/java/com/yahoo/application/container/Search.java
index 3535b660b78..6a2f728fbcc 100644
--- a/application/src/main/java/com/yahoo/application/container/Search.java
+++ b/application/src/main/java/com/yahoo/application/container/Search.java
@@ -2,7 +2,6 @@
package com.yahoo.application.container;
import com.yahoo.api.annotations.Beta;
-import com.google.common.util.concurrent.ListenableFuture;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.chain.Chain;
import com.yahoo.processing.execution.chain.ChainRegistry;
@@ -12,10 +11,10 @@ import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
import com.yahoo.search.handler.HttpSearchResponse;
import com.yahoo.search.handler.SearchHandler;
-import com.yahoo.search.searchchain.SearchChainRegistry;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
/**
* @author Einar M R Rosenvinge
@@ -41,12 +40,12 @@ public final class Search extends ProcessingBase<Query, Result, Searcher> {
}
@Override
- protected ListenableFuture<Boolean> doProcessAndRender(ComponentSpecification chainSpec,
- Query request,
- Renderer<Result> renderer,
- ByteArrayOutputStream stream) throws IOException {
+ protected CompletableFuture<Boolean> doProcessAndRender(ComponentSpecification chainSpec,
+ Query request,
+ Renderer<Result> renderer,
+ ByteArrayOutputStream stream) throws IOException {
Result result = process(chainSpec, request);
- return HttpSearchResponse.waitableRender(result, result.getQuery(), renderer, stream);
+ return HttpSearchResponse.asyncRender(result, result.getQuery(), renderer, stream);
}
@Override
diff --git a/client/go/cmd/prod.go b/client/go/cmd/prod.go
index 89dc4cb6094..4ca4d20d105 100644
--- a/client/go/cmd/prod.go
+++ b/client/go/cmd/prod.go
@@ -142,11 +142,10 @@ $ vespa prod submit`,
if pkg.TestPath == "" {
fatalErrHint(fmt.Errorf("No tests found"),
"The application must be a Java maven project, or include basic HTTP tests under src/test/application/",
- "See https://cloud.vespa.ai/en/reference/getting-to-production")
+ "See https://cloud.vespa.ai/en/getting-to-production")
return
- } else {
- verifyTests(pkg.TestPath, target)
}
+ verifyTests(pkg.TestPath, target)
isCI := os.Getenv("CI") != ""
if !isCI {
fmt.Fprintln(stderr, color.Yellow("Warning:"), "We recommend doing this only from a CD job")
@@ -352,10 +351,26 @@ func prompt(r *bufio.Reader, question, defaultAnswer string, validator func(inpu
}
func verifyTests(testsParent string, target vespa.Target) {
- runTests(filepath.Join(testsParent, "tests", "system-test"), target, true)
- runTests(filepath.Join(testsParent, "tests", "staging-setup"), target, true)
- runTests(filepath.Join(testsParent, "tests", "staging-test"), target, true)
- if util.PathExists(filepath.Join(testsParent, "tests", "production-test")) {
- runTests(filepath.Join(testsParent, "tests", "production-test"), target, true)
+ verifyTest(testsParent, "system-test", target, true)
+ verifyTest(testsParent, "staging-setup", target, true)
+ verifyTest(testsParent, "staging-test", target, true)
+ verifyTest(testsParent, "production-test", target, false)
+}
+
+func verifyTest(testsParent string, suite string, target vespa.Target, required bool) {
+ testDirectory := filepath.Join(testsParent, "tests", suite)
+ _, err := os.Stat(testDirectory)
+ if err != nil {
+ if required {
+ if errors.Is(err, os.ErrNotExist) {
+ fatalErrHint(fmt.Errorf("No %s tests found", suite),
+ fmt.Sprintf("No such directory: %s", testDirectory),
+ "See https://cloud.vespa.ai/en/reference/testing")
+ }
+ fatalErrHint(err, "See https://cloud.vespa.ai/en/reference/testing")
+ }
+ return
}
+
+ runTests(testDirectory, target, true)
}
diff --git a/client/go/cmd/test.go b/client/go/cmd/test.go
index 7ba7a19b235..b8e028ee763 100644
--- a/client/go/cmd/test.go
+++ b/client/go/cmd/test.go
@@ -17,7 +17,6 @@ import (
"net/http"
"net/url"
"os"
- "path"
"path/filepath"
"strings"
"time"
@@ -29,24 +28,20 @@ func init() {
}
var testCmd = &cobra.Command{
- Use: "test [tests directory or test file]",
+ Use: "test <tests directory or test file>",
Short: "Run a test suite, or a single test",
Long: `Run a test suite, or a single test
-Runs all JSON test files in the specified directory (the working
-directory by default), or the single JSON test file specified.
+Runs all JSON test files in the specified directory, or the single JSON test file specified.
See https://cloud.vespa.ai/en/reference/testing.html for details.`,
Example: `$ vespa test src/test/application/tests/system-test
$ vespa test src/test/application/tests/system-test/feed-and-query.json`,
- Args: cobra.MaximumNArgs(1),
+ Args: cobra.ExactArgs(1),
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
target := getTarget()
- testPath := "."
- if len(args) > 0 {
- testPath = args[0]
- }
+ testPath := args[0]
if count, failed := runTests(testPath, target, false); len(failed) != 0 {
plural := "s"
if count == 1 {
@@ -77,10 +72,11 @@ func runTests(rootPath string, target vespa.Target, dryRun bool) (int, []string)
if err != nil {
fatalErrHint(err, "See https://cloud.vespa.ai/en/reference/testing")
}
+
previousFailed := false
for _, test := range tests {
if !test.IsDir() && filepath.Ext(test.Name()) == ".json" {
- testPath := path.Join(rootPath, test.Name())
+ testPath := filepath.Join(rootPath, test.Name())
if previousFailed {
fmt.Fprintln(stdout, "")
previousFailed = false
@@ -122,10 +118,10 @@ func runTest(testPath string, target vespa.Target, dryRun bool) string {
testName = filepath.Base(testPath)
}
if !dryRun {
- fmt.Fprintf(stdout, "Running %s:", color.Cyan(testName))
+ fmt.Fprintf(stdout, "%s:", testName)
}
- defaultParameters, err := getParameters(test.Defaults.ParametersRaw, path.Dir(testPath))
+ defaultParameters, err := getParameters(test.Defaults.ParametersRaw, filepath.Dir(testPath))
if err != nil {
fmt.Fprintln(stderr)
fatalErrHint(err, fmt.Sprintf("Invalid default parameters for %s", testName), "See https://cloud.vespa.ai/en/reference/testing")
@@ -136,24 +132,24 @@ func runTest(testPath string, target vespa.Target, dryRun bool) string {
fatalErrHint(fmt.Errorf("a test must have at least one step, but none were found in %s", testPath), "See https://cloud.vespa.ai/en/reference/testing")
}
for i, step := range test.Steps {
- stepName := step.Name
- if stepName == "" {
- stepName = fmt.Sprintf("step %d", i+1)
+ stepName := fmt.Sprintf("Step %d", i+1)
+ if step.Name != "" {
+ stepName += ": " + step.Name
}
- failure, longFailure, err := verify(step, path.Dir(testPath), test.Defaults.Cluster, defaultParameters, target, dryRun)
+ failure, longFailure, err := verify(step, filepath.Dir(testPath), test.Defaults.Cluster, defaultParameters, target, dryRun)
if err != nil {
fmt.Fprintln(stderr)
fatalErrHint(err, fmt.Sprintf("Error in %s", stepName), "See https://cloud.vespa.ai/en/reference/testing")
}
if !dryRun {
if failure != "" {
- fmt.Fprintf(stdout, " %s %s:\n%s\n", color.Red("Failed"), color.Cyan(stepName), longFailure)
+ fmt.Fprintf(stdout, " %s\n%s:\n%s\n", color.Red("failed"), stepName, longFailure)
return fmt.Sprintf("%s: %s: %s", testName, stepName, failure)
}
if i == 0 {
fmt.Fprintf(stdout, " ")
}
- fmt.Fprint(stdout, color.Green("."))
+ fmt.Fprint(stdout, ".")
}
}
if !dryRun {
@@ -207,7 +203,7 @@ func verify(step step, testsPath string, defaultCluster string, defaultParameter
}
externalEndpoint := requestUrl.IsAbs()
if !externalEndpoint {
- baseURL := "http://dummy/"
+ baseURL := ""
if service != nil {
baseURL = service.BaseURL
}
@@ -267,8 +263,13 @@ func verify(step step, testsPath string, defaultCluster string, defaultParameter
defer response.Body.Close()
if statusCode != response.StatusCode {
- failure := fmt.Sprintf("Unexpected %s: %s", "status code", color.Red(response.StatusCode))
- return failure, fmt.Sprintf("%s\nExpected: %s\nActual response:\n%s", failure, color.Cyan(statusCode), util.ReaderToJSON(response.Body)), nil
+ return fmt.Sprintf("Unexpected status code: %d", color.Red(response.StatusCode)),
+ fmt.Sprintf("Unexpected status code\nExpected: %d\nActual: %d\nRequested: %s at %s\nResponse:\n%s",
+ color.Cyan(statusCode),
+ color.Red(response.StatusCode),
+ color.Cyan(method),
+ color.Cyan(requestUrl),
+ util.ReaderToJSON(response.Body)), nil
}
if responseBodySpec == nil {
@@ -285,20 +286,24 @@ func verify(step step, testsPath string, defaultCluster string, defaultParameter
return "", "", fmt.Errorf("got non-JSON response; %w:\n%s", err, string(responseBodyBytes))
}
- failure, expected, err := compare(responseBodySpec, responseBody, "")
+ failure, expected, actual, err := compare(responseBodySpec, responseBody, "")
if failure != "" {
responsePretty, _ := json.MarshalIndent(responseBody, "", " ")
longFailure := failure
if expected != "" {
- longFailure += "\n" + expected
+ longFailure += "\nExpected: " + expected
+ }
+ if actual != "" {
+ failure += ": " + actual
+ longFailure += "\nActual: " + actual
}
- longFailure += "\nActual response:\n" + string(responsePretty)
+ longFailure += fmt.Sprintf("\nRequested: %s at %s\nResponse:\n%s", color.Cyan(method), color.Cyan(requestUrl), string(responsePretty))
return failure, longFailure, err
}
return "", "", err
}
-func compare(expected interface{}, actual interface{}, path string) (string, string, error) {
+func compare(expected interface{}, actual interface{}, path string) (string, string, string, error) {
typeMatch := false
valueMatch := false
switch u := expected.(type) {
@@ -323,18 +328,15 @@ func compare(expected interface{}, actual interface{}, path string) (string, str
if ok {
if len(u) == len(v) {
for i, e := range u {
- failure, expected, err := compare(e, v[i], fmt.Sprintf("%s/%d", path, i))
- if failure != "" || err != nil {
- return failure, expected, err
+ if failure, expected, actual, err := compare(e, v[i], fmt.Sprintf("%s/%d", path, i)); failure != "" || err != nil {
+ return failure, expected, actual, err
}
}
valueMatch = true
} else {
- return fmt.Sprintf("Unexpected %s at %s: %d",
- "number of elements",
- color.Cyan(path),
- color.Red(len(v))),
- fmt.Sprintf("Expected: %d", color.Cyan(len(u))),
+ return fmt.Sprintf("Unexpected number of elements at %s", color.Cyan(path)),
+ fmt.Sprintf("%d", color.Cyan(len(u))),
+ fmt.Sprintf("%d", color.Red(len(v))),
nil
}
}
@@ -346,17 +348,16 @@ func compare(expected interface{}, actual interface{}, path string) (string, str
childPath := fmt.Sprintf("%s/%s", path, strings.ReplaceAll(strings.ReplaceAll(n, "~", "~0"), "/", "~1"))
f, ok := v[n]
if !ok {
- return fmt.Sprintf("Missing expected field at %s", color.Red(childPath)), "", nil
+ return fmt.Sprintf("Missing expected field at %s", color.Red(childPath)), "", "", nil
}
- failure, expected, err := compare(e, f, childPath)
- if failure != "" || err != nil {
- return failure, expected, err
+ if failure, expected, actual, err := compare(e, f, childPath); failure != "" || err != nil {
+ return failure, expected, actual, err
}
}
valueMatch = true
}
default:
- return "", "", fmt.Errorf("unexpected JSON type for value '%v'", expected)
+ return "", "", "", fmt.Errorf("unexpected JSON type for value '%v'", expected)
}
if !valueMatch {
@@ -369,21 +370,22 @@ func compare(expected interface{}, actual interface{}, path string) (string, str
}
expectedJson, _ := json.Marshal(expected)
actualJson, _ := json.Marshal(actual)
- return fmt.Sprintf("Unexpected %s at %s: %s",
- mismatched,
- color.Cyan(path),
- color.Red(actualJson)),
- fmt.Sprintf("Expected: %s", color.Cyan(expectedJson)),
+ return fmt.Sprintf("Unexpected %s at %s", mismatched, color.Cyan(path)),
+ fmt.Sprintf("%s", color.Cyan(expectedJson)),
+ fmt.Sprintf("%s", color.Red(actualJson)),
nil
}
- return "", "", nil
+ return "", "", "", nil
}
func getParameters(parametersRaw []byte, testsPath string) (map[string]string, error) {
if parametersRaw != nil {
var parametersPath string
if err := json.Unmarshal(parametersRaw, &parametersPath); err == nil {
- resolvedParametersPath := path.Join(testsPath, parametersPath)
+ if err = validateRelativePath(parametersPath); err != nil {
+ return nil, err
+ }
+ resolvedParametersPath := filepath.Join(testsPath, parametersPath)
parametersRaw, err = ioutil.ReadFile(resolvedParametersPath)
if err != nil {
return nil, fmt.Errorf("failed to read request parameters at %s: %w", resolvedParametersPath, err)
@@ -401,7 +403,10 @@ func getParameters(parametersRaw []byte, testsPath string) (map[string]string, e
func getBody(bodyRaw []byte, testsPath string) ([]byte, error) {
var bodyPath string
if err := json.Unmarshal(bodyRaw, &bodyPath); err == nil {
- resolvedBodyPath := path.Join(testsPath, bodyPath)
+ if err = validateRelativePath(bodyPath); err != nil {
+ return nil, err
+ }
+ resolvedBodyPath := filepath.Join(testsPath, bodyPath)
bodyRaw, err = ioutil.ReadFile(resolvedBodyPath)
if err != nil {
return nil, fmt.Errorf("failed to read body file at %s: %w", resolvedBodyPath, err)
@@ -410,6 +415,18 @@ func getBody(bodyRaw []byte, testsPath string) ([]byte, error) {
return bodyRaw, nil
}
+func validateRelativePath(relPath string) error {
+ if filepath.IsAbs(relPath) {
+ return fmt.Errorf("path must be relative, but was '%s'", relPath)
+ }
+ cleanPath := filepath.Clean(relPath)
+ fmt.Println(cleanPath)
+ if strings.HasPrefix(cleanPath, "../../../") {
+ return fmt.Errorf("path may not point outside src/test/application, but '%s' does", relPath)
+ }
+ return nil
+}
+
type test struct {
Name string `json:"name"`
Defaults defaults `json:"defaults"`
diff --git a/client/go/cmd/test_test.go b/client/go/cmd/test_test.go
index 4c5e4c3f1e5..6649353df77 100644
--- a/client/go/cmd/test_test.go
+++ b/client/go/cmd/test_test.go
@@ -5,6 +5,7 @@
package cmd
import (
+ "fmt"
"github.com/vespa-engine/vespa/client/go/util"
"github.com/vespa-engine/vespa/client/go/vespa"
"io/ioutil"
@@ -23,29 +24,41 @@ func TestSuite(t *testing.T) {
searchResponse, _ := ioutil.ReadFile("testdata/tests/response.json")
client.NextStatus(200)
client.NextStatus(200)
- for i := 0; i < 10; i++ {
+ for i := 0; i < 11; i++ {
client.NextResponse(200, string(searchResponse))
}
expectedBytes, _ := ioutil.ReadFile("testdata/tests/expected-suite.out")
outBytes, errBytes := execute(command{args: []string{"test", "testdata/tests/system-test"}}, t, client)
- assert.Equal(t, string(expectedBytes), outBytes)
- assert.Equal(t, "", errBytes)
baseUrl := "http://127.0.0.1:8080"
urlWithQuery := baseUrl + "/search/?presentation.timing=true&query=artist%3A+foo&timeout=3.4s"
requests := []*http.Request{createFeedRequest(baseUrl), createFeedRequest(baseUrl), createSearchRequest(urlWithQuery), createSearchRequest(urlWithQuery)}
- for i := 0; i < 8; i++ {
+ requests = append(requests, createSearchRequest(baseUrl+"/search/"))
+ requests = append(requests, createSearchRequest(baseUrl+"/search/?foo=%2F"))
+ for i := 0; i < 7; i++ {
requests = append(requests, createSearchRequest(baseUrl+"/search/"))
}
assertRequests(requests, client, t)
+ fmt.Println(outBytes)
+ assert.Equal(t, string(expectedBytes), outBytes)
+ assert.Equal(t, "", errBytes)
+}
+
+func TestIllegalFileReference(t *testing.T) {
+ client := &mockHttpClient{}
+ client.NextStatus(200)
+ client.NextStatus(200)
+ _, errBytes := execute(command{args: []string{"test", "testdata/tests/production-test/illegal-reference.json"}}, t, client)
+ assertRequests([]*http.Request{createRequest("GET", "http://127.0.0.1:8080/search/", "{}")}, client, t)
+ assert.Equal(t, "\nError: path may not point outside src/test/application, but 'foo/../../../../this-is-not-ok.json' does\nHint: Error in Step 2\nHint: See https://cloud.vespa.ai/en/reference/testing\n", errBytes)
}
func TestProductionTest(t *testing.T) {
client := &mockHttpClient{}
client.NextStatus(200)
outBytes, errBytes := execute(command{args: []string{"test", "testdata/tests/production-test/external.json"}}, t, client)
- assert.Equal(t, "Running external.json: . OK\n\nSuccess: 1 test OK\n", outBytes)
+ assert.Equal(t, "external.json: . OK\n\nSuccess: 1 test OK\n", outBytes)
assert.Equal(t, "", errBytes)
assertRequests([]*http.Request{createRequest("GET", "https://my.service:123/path?query=wohoo", "")}, client, t)
}
diff --git a/client/go/cmd/testdata/empty.json b/client/go/cmd/testdata/empty.json
new file mode 100644
index 00000000000..9e26dfeeb6e
--- /dev/null
+++ b/client/go/cmd/testdata/empty.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/client/go/cmd/testdata/tests/expected-suite.out b/client/go/cmd/testdata/tests/expected-suite.out
index 963889b8019..df916f50a95 100644
--- a/client/go/cmd/testdata/tests/expected-suite.out
+++ b/client/go/cmd/testdata/tests/expected-suite.out
@@ -1,8 +1,11 @@
-Running my test: .... OK
-Running wrong-bool-value.json: Failed step 1:
-Unexpected value at /root/coverage/full: true
+My test: .... OK
+wrong-bool-value.json: failed
+Step 1:
+Unexpected value at /root/coverage/full
Expected: false
-Actual response:
+Actual: true
+Requested: GET at http://127.0.0.1:8080/search/
+Response:
{
"root": {
"children": [
@@ -38,10 +41,55 @@ Actual response:
}
}
-Running wrong-element-count.json: Failed step 1:
-Unexpected number of elements at /root/children: 1
+wrong-code.json: failed
+Step 1:
+Unexpected status code
+Expected: 123
+Actual: 200
+Requested: GET at http://127.0.0.1:8080/search/?foo=%2F
+Response:
+{
+ "root": {
+ "children": [
+ {
+ "fields": {
+ "artist": "Foo Fighters",
+ "documentid": "id:test:music::doc",
+ "sddocname": "music"
+ },
+ "id": "id:test:music::doc",
+ "relevance": 0.38186238359951247,
+ "source": "music"
+ }
+ ],
+ "coverage": {
+ "coverage": 100,
+ "documents": 1,
+ "full": true,
+ "nodes": 1,
+ "results": 1,
+ "resultsFull": 1
+ },
+ "fields": {
+ "totalCount": 1
+ },
+ "id": "toplevel",
+ "relevance": 1
+ },
+ "timing": {
+ "querytime": 0.003,
+ "searchtime": 0.004,
+ "summaryfetchtime": 0
+ }
+}
+
+wrong-element-count.json: failed
+Step 1:
+Unexpected number of elements at /root/children
Expected: 0
-Actual response:
+Actual: 1
+Requested: GET at http://127.0.0.1:8080/search/
+Response:
{
"root": {
"children": [
@@ -77,9 +125,11 @@ Actual response:
}
}
-Running wrong-field-name.json: Failed step 1:
+wrong-field-name.json: failed
+Step 1:
Missing expected field at /root/fields/totalCountDracula
-Actual response:
+Requested: GET at http://127.0.0.1:8080/search/
+Response:
{
"root": {
"children": [
@@ -115,10 +165,13 @@ Actual response:
}
}
-Running wrong-float-value.json: Failed step 1:
-Unexpected value at /root/children/0/relevance: 0.38186238359951247
+wrong-float-value.json: failed
+Step 1:
+Unexpected value at /root/children/0/relevance
Expected: 0.381862373599
-Actual response:
+Actual: 0.38186238359951247
+Requested: GET at http://127.0.0.1:8080/search/
+Response:
{
"root": {
"children": [
@@ -154,10 +207,13 @@ Actual response:
}
}
-Running wrong-int-value.json: Failed step 1:
-Unexpected value at /root/fields/totalCount: 1
+wrong-int-value.json: failed
+Step 1:
+Unexpected value at /root/fields/totalCount
Expected: 2
-Actual response:
+Actual: 1
+Requested: GET at http://127.0.0.1:8080/search/
+Response:
{
"root": {
"children": [
@@ -193,9 +249,11 @@ Actual response:
}
}
-Running wrong-null-value.json: Failed step 1:
+wrong-null-value.json: failed
+Step 1:
Missing expected field at /boot
-Actual response:
+Requested: GET at http://127.0.0.1:8080/search/
+Response:
{
"root": {
"children": [
@@ -231,10 +289,13 @@ Actual response:
}
}
-Running wrong-string-value.json: Failed step 1:
-Unexpected value at /root/children/0/fields/artist: "Foo Fighters"
+wrong-string-value.json: failed
+Step 1:
+Unexpected value at /root/children/0/fields/artist
Expected: "Boo Fighters"
-Actual response:
+Actual: "Foo Fighters"
+Requested: GET at http://127.0.0.1:8080/search/
+Response:
{
"root": {
"children": [
@@ -270,10 +331,13 @@ Actual response:
}
}
-Running wrong-type.json: Failed step 1:
-Unexpected type at /root/fields/totalCount: 1
+wrong-type.json: failed
+Step 1:
+Unexpected type at /root/fields/totalCount
Expected: "1"
-Actual response:
+Actual: 1
+Requested: GET at http://127.0.0.1:8080/search/
+Response:
{
"root": {
"children": [
@@ -309,12 +373,13 @@ Actual response:
}
}
-Failure: 8 of 9 tests failed:
-wrong-bool-value.json: step 1: Unexpected value at /root/coverage/full: true
-wrong-element-count.json: step 1: Unexpected number of elements at /root/children: 1
-wrong-field-name.json: step 1: Missing expected field at /root/fields/totalCountDracula
-wrong-float-value.json: step 1: Unexpected value at /root/children/0/relevance: 0.38186238359951247
-wrong-int-value.json: step 1: Unexpected value at /root/fields/totalCount: 1
-wrong-null-value.json: step 1: Missing expected field at /boot
-wrong-string-value.json: step 1: Unexpected value at /root/children/0/fields/artist: "Foo Fighters"
-wrong-type.json: step 1: Unexpected type at /root/fields/totalCount: 1
+Failure: 9 of 10 tests failed:
+wrong-bool-value.json: Step 1: Unexpected value at /root/coverage/full: true
+wrong-code.json: Step 1: Unexpected status code: 200
+wrong-element-count.json: Step 1: Unexpected number of elements at /root/children: 1
+wrong-field-name.json: Step 1: Missing expected field at /root/fields/totalCountDracula
+wrong-float-value.json: Step 1: Unexpected value at /root/children/0/relevance: 0.38186238359951247
+wrong-int-value.json: Step 1: Unexpected value at /root/fields/totalCount: 1
+wrong-null-value.json: Step 1: Missing expected field at /boot
+wrong-string-value.json: Step 1: Unexpected value at /root/children/0/fields/artist: "Foo Fighters"
+wrong-type.json: Step 1: Unexpected type at /root/fields/totalCount: 1
diff --git a/client/go/cmd/testdata/tests/expected.out b/client/go/cmd/testdata/tests/expected.out
index 084fb10f72a..2ca35fe6a37 100644
--- a/client/go/cmd/testdata/tests/expected.out
+++ b/client/go/cmd/testdata/tests/expected.out
@@ -1,3 +1,3 @@
-Running my test: .... OK
+My test: .... OK
Success: 1 test OK
diff --git a/client/go/cmd/testdata/tests/production-test/illegal-reference.json b/client/go/cmd/testdata/tests/production-test/illegal-reference.json
new file mode 100644
index 00000000000..edd8a2fafeb
--- /dev/null
+++ b/client/go/cmd/testdata/tests/production-test/illegal-reference.json
@@ -0,0 +1,14 @@
+{
+ "steps": [
+ {
+ "request": {
+ "body": "foo/../../../empty.json"
+ }
+ },
+ {
+ "request": {
+ "body": "foo/../../../../this-is-not-ok.json"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/go/cmd/testdata/tests/system-test/test.json b/client/go/cmd/testdata/tests/system-test/test.json
index f53df929dbd..2e327b5e5df 100644
--- a/client/go/cmd/testdata/tests/system-test/test.json
+++ b/client/go/cmd/testdata/tests/system-test/test.json
@@ -1,5 +1,5 @@
{
- "name": "my test",
+ "name": "My test",
"defaults": {
"cluster": "container",
"parameters": {
diff --git a/client/go/cmd/testdata/tests/system-test/wrong-code.json b/client/go/cmd/testdata/tests/system-test/wrong-code.json
new file mode 100644
index 00000000000..c325054faa1
--- /dev/null
+++ b/client/go/cmd/testdata/tests/system-test/wrong-code.json
@@ -0,0 +1,14 @@
+{
+ "steps": [
+ {
+ "request": {
+ "parameters": {
+ "foo": "/"
+ }
+ },
+ "response": {
+ "code": 123
+ }
+ }
+ ]
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
index 73138d15559..562ccc44a37 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
@@ -313,7 +313,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
if (deploymentSpec.isEmpty()) return;
for (var deprecatedElement : deploymentSpec.get().deprecatedElements()) {
- deployLogger.log(WARNING, deprecatedElement.humanReadableString());
+ deployLogger.logApplicationPackage(WARNING, deprecatedElement.humanReadableString());
}
addIdentityProvider(cluster,
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandler.java
index 0dc7dbda9a1..3aa69c6cedf 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandler.java
@@ -3,8 +3,6 @@ package com.yahoo.vespa.config.server.http.v1;
import com.google.inject.Inject;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Deployer;
-import com.yahoo.config.provision.Deployment;
import com.yahoo.jdisc.http.HttpRequest;
import com.yahoo.path.Path;
import com.yahoo.restapi.RestApi;
@@ -20,7 +18,6 @@ import com.yahoo.vespa.curator.transaction.CuratorTransaction;
import com.yahoo.yolean.Exceptions;
import java.time.Clock;
-import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
@@ -50,18 +47,16 @@ public class RoutingStatusApiHandler extends RestApiRequestHandler<RoutingStatus
private final Curator curator;
private final Clock clock;
- private final Deployer deployer;
@Inject
- public RoutingStatusApiHandler(Context context, Curator curator, Deployer deployer) {
- this(context, curator, Clock.systemUTC(), deployer);
+ public RoutingStatusApiHandler(Context context, Curator curator) {
+ this(context, curator, Clock.systemUTC());
}
- RoutingStatusApiHandler(Context context, Curator curator, Clock clock, Deployer deployer) {
+ RoutingStatusApiHandler(Context context, Curator curator, Clock clock) {
super(context, RoutingStatusApiHandler::createRestApiDefinition);
this.curator = Objects.requireNonNull(curator);
this.clock = Objects.requireNonNull(clock);
- this.deployer = Objects.requireNonNull(deployer);
curator.create(DEPLOYMENT_STATUS_ROOT);
}
@@ -113,29 +108,19 @@ public class RoutingStatusApiHandler extends RestApiRequestHandler<RoutingStatus
RestApi.RequestContext.RequestContent requestContent = context.requestContentOrThrow();
Slime requestBody = Exceptions.uncheck(() -> SlimeUtils.jsonToSlime(requestContent.content().readAllBytes()));
DeploymentRoutingStatus wantedStatus = deploymentRoutingStatusFromSlime(requestBody, clock.instant());
- DeploymentRoutingStatus currentStatus = deploymentStatus(upstreamNames.iterator().next());
-
+ List<DeploymentRoutingStatus> currentStatuses = upstreamNames.stream()
+ .map(this::deploymentStatus)
+ .collect(Collectors.toList());
+ DeploymentRoutingStatus currentStatus = currentStatuses.get(0);
// Redeploy application so that a new LbServicesConfig containing the updated status is generated and consumed
- // by routing layer. This is required to update weights for application endpoints when routing status for a
- // deployment is changed
+ // by routing layer. This is required to update status of upstreams in application endpoints
log.log(Level.INFO, "Changing routing status of " + instance + " from " +
currentStatus.status() + " to " + wantedStatus.status());
- changeStatus(upstreamNames, wantedStatus);
- try {
- Optional<Deployment> deployment = deployer.deployFromLocalActive(instance, Duration.ofMinutes(1));
- if (deployment.isEmpty()) throw new IllegalArgumentException("No deployment of " + instance + " found");
- deployment.get().activate();
- } catch (Exception e) {
- log.log(Level.SEVERE, "Failed to redeploy " + instance + ". Reverting routing status to " +
- currentStatus.status(), e);
- changeStatus(upstreamNames, currentStatus);
- throw new RestApiException.InternalServerError("Failed to change status to " +
- wantedStatus.status() + ", reverting to "
- + currentStatus.status() +
- " because redeployment of " +
- instance + " failed: " +
- Exceptions.toMessageString(e));
+ boolean needsChange = currentStatuses.stream().anyMatch(status -> status.status() != wantedStatus.status());
+ if (!needsChange) {
+ return new SlimeJsonResponse(toSlime(wantedStatus));
}
+ changeStatus(upstreamNames, wantedStatus);
return new SlimeJsonResponse(toSlime(wantedStatus));
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandlerTest.java
index 8dd7cf4d6fc..e2b45d33cbc 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v1/RoutingStatusApiHandlerTest.java
@@ -2,9 +2,6 @@
package com.yahoo.vespa.config.server.http.v1;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Deployer;
-import com.yahoo.config.provision.Deployment;
-import com.yahoo.config.provision.HostFilter;
import com.yahoo.container.jdisc.HttpRequestBuilder;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.jdisc.http.HttpRequest.Method;
@@ -19,17 +16,11 @@ import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
-import java.time.Clock;
-import java.time.Duration;
import java.time.Instant;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
import static com.yahoo.yolean.Exceptions.uncheck;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
/**
* @author bjorncs
@@ -42,7 +33,6 @@ public class RoutingStatusApiHandlerTest {
private final Curator curator = new MockCurator();
private final ManualClock clock = new ManualClock();
- private final MockDeployer deployer = new MockDeployer(clock);
private RestApiTestDriver testDriver;
@@ -50,8 +40,7 @@ public class RoutingStatusApiHandlerTest {
public void before() {
RoutingStatusApiHandler requestHandler = new RoutingStatusApiHandler(RestApiTestDriver.createHandlerTestContext(),
curator,
- clock,
- deployer);
+ clock);
testDriver = RestApiTestDriver.newBuilder(requestHandler).build();
}
@@ -77,14 +66,6 @@ public class RoutingStatusApiHandlerTest {
String response = responseAsString(executeRequest(Method.PUT, "/routing/v1/status/" + upstreamName + "?application=" + instance.serializedForm(),
statusOut()));
assertEquals(response("OUT", "issue-XXX", "operator", clock.instant()), response);
- assertTrue("Re-deployed " + instance, deployer.lastDeployed.containsKey(instance));
-
- // Status is reverted if redeployment fails
- deployer.failNextDeployment(true);
- response = responseAsString(executeRequest(Method.PUT, "/routing/v1/status/" + upstreamName + "?application=" + instance.serializedForm(),
- requestContent("IN", "all good")));
- assertEquals("{\"error-code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Failed to change status to in, reverting to out because redeployment of t1.a1.i1 failed: Deployment failed\"}",
- response);
// Read status stored in old format (path exists, but without content)
curator.set(Path.fromString("/routing/v1/status/" + upstreamName), new byte[0]);
@@ -92,7 +73,6 @@ public class RoutingStatusApiHandlerTest {
assertEquals(response("OUT", "", "", clock.instant()), response);
// Change status of multiple upstreams
- deployer.failNextDeployment(false);
String upstreamName2 = "upstream2";
String upstreams = upstreamName + "," + upstreamName2 + "," + upstreamName2;
response = responseAsString(executeRequest(Method.PUT, "/routing/v1/status/" + upstreams + "?application=" + instance.serializedForm(),
@@ -172,57 +152,4 @@ public class RoutingStatusApiHandlerTest {
return "{\"status\":\"" + status + "\",\"cause\":\"" + reason + "\",\"agent\":\"" + agent + "\",\"lastUpdate\":" + instant.getEpochSecond() + "}";
}
- private static class MockDeployer implements Deployer {
-
- private final Map<ApplicationId, Instant> lastDeployed = new HashMap<>();
- private final Clock clock;
-
- private boolean failNextDeployment = false;
-
- public MockDeployer(Clock clock) {
- this.clock = clock;
- }
-
- public MockDeployer failNextDeployment(boolean fail) {
- this.failNextDeployment = fail;
- return this;
- }
-
- @Override
- public Optional<Deployment> deployFromLocalActive(ApplicationId application, boolean bootstrap) {
- return deployFromLocalActive(application, Duration.ZERO, false);
- }
-
- @Override
- public Optional<Deployment> deployFromLocalActive(ApplicationId application, Duration timeout, boolean bootstrap) {
- if (failNextDeployment) {
- throw new RuntimeException("Deployment failed");
- }
- return Optional.of(new Deployment() {
- @Override
- public void prepare() {}
-
- @Override
- public long activate() {
- lastDeployed.put(application, clock.instant());
- return 1L;
- }
-
- @Override
- public void restart(HostFilter filter) {}
- });
- }
-
- @Override
- public Optional<Instant> lastDeployTime(ApplicationId application) {
- return Optional.ofNullable(lastDeployed.get(application));
- }
-
- @Override
- public Duration serverDeployTimeout() {
- return Duration.ZERO;
- }
-
- }
-
}
diff --git a/container-core/abi-spec.json b/container-core/abi-spec.json
index 6bee1f2b4fb..cdd6da944c5 100644
--- a/container-core/abi-spec.json
+++ b/container-core/abi-spec.json
@@ -2321,8 +2321,7 @@
"public"
],
"methods": [
- "public void <init>(com.yahoo.jdisc.http.HttpRequest)",
- "public com.yahoo.jdisc.http.HttpRequest getParentRequest()"
+ "public void <init>(com.yahoo.jdisc.http.HttpRequest)"
],
"fields": []
},
@@ -3138,8 +3137,9 @@
"public abstract void endResponse()",
"public void <init>()",
"public void <init>(java.util.concurrent.Executor)",
- "public final com.google.common.util.concurrent.ListenableFuture render(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
+ "public final java.util.concurrent.CompletableFuture renderResponse(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
"public void deconstruct()",
+ "public final java.util.concurrent.CompletableFuture renderResponseBeforeHandover(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
"public final com.google.common.util.concurrent.ListenableFuture renderBeforeHandover(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
"public com.yahoo.processing.execution.Execution getExecution()",
"public com.yahoo.processing.Response getResponse()",
@@ -3185,7 +3185,8 @@
"public void <init>()",
"public com.yahoo.processing.rendering.Renderer clone()",
"public void init()",
- "public abstract com.google.common.util.concurrent.ListenableFuture render(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
+ "public com.google.common.util.concurrent.ListenableFuture render(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
+ "public java.util.concurrent.CompletableFuture renderResponse(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
"public abstract java.lang.String getEncoding()",
"public abstract java.lang.String getMimeType()",
"public bridge synthetic com.yahoo.component.AbstractComponent clone()",
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java
index 8ac2305f5df..2580b4a6ac0 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java
@@ -73,7 +73,7 @@ public class DiscFilterRequest {
*/
@Deprecated(forRemoval = true, since = "7.511")
public HttpRequest getParentRequest() {
- throw new UnsupportedOperationException("getParentRequest is not supported for " + parent.getClass().getName());
+ return parent;
}
/**
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java
index aa4050dd963..74c3b8adc7d 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java
@@ -10,15 +10,8 @@ import com.yahoo.jdisc.http.HttpRequest;
@Deprecated(forRemoval = true, since = "7.511")
public class JdiscFilterRequest extends DiscFilterRequest {
- private final HttpRequest parent;
-
public JdiscFilterRequest(HttpRequest parent) {
super(parent);
- this.parent = parent;
}
- @SuppressWarnings("removal")
- @Override
- public HttpRequest getParentRequest() { return parent; }
-
}
diff --git a/container-core/src/main/java/com/yahoo/processing/handler/AbstractProcessingHandler.java b/container-core/src/main/java/com/yahoo/processing/handler/AbstractProcessingHandler.java
index 5119e69f72e..9b9224e70ef 100644
--- a/container-core/src/main/java/com/yahoo/processing/handler/AbstractProcessingHandler.java
+++ b/container-core/src/main/java/com/yahoo/processing/handler/AbstractProcessingHandler.java
@@ -244,7 +244,8 @@ public abstract class AbstractProcessingHandler<COMPONENT extends Processor> ext
// Render if we have a renderer capable of it
if (getRenderer() instanceof AsynchronousSectionedRenderer) {
- ((AsynchronousSectionedRenderer) getRenderer()).renderBeforeHandover(new ContentChannelOutputStream(channel), response, execution, request);
+ ((AsynchronousSectionedRenderer) getRenderer()).renderResponseBeforeHandover(
+ new ContentChannelOutputStream(channel), response, execution, request);
}
}
diff --git a/container-core/src/main/java/com/yahoo/processing/handler/ProcessingResponse.java b/container-core/src/main/java/com/yahoo/processing/handler/ProcessingResponse.java
index 54fbce9e177..28645b4bde0 100644
--- a/container-core/src/main/java/com/yahoo/processing/handler/ProcessingResponse.java
+++ b/container-core/src/main/java/com/yahoo/processing/handler/ProcessingResponse.java
@@ -1,19 +1,9 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.processing.handler;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
import com.google.common.collect.ImmutableList;
import com.yahoo.container.jdisc.AsyncHttpResponse;
-import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.VespaHeaders;
-import com.yahoo.container.logging.AccessLogEntry;
import com.yahoo.jdisc.handler.CompletionHandler;
import com.yahoo.jdisc.handler.ContentChannel;
import com.yahoo.processing.Request;
@@ -26,6 +16,14 @@ import com.yahoo.processing.request.ErrorMessage;
import com.yahoo.processing.response.Data;
import com.yahoo.processing.response.DataList;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
/**
* A response from running a request through processing. This response is just a
* wrapper of the knowhow needed to render the Response from processing.
@@ -62,7 +60,7 @@ public class ProcessingResponse extends AsyncHttpResponse {
AsynchronousRenderer asyncRenderer = (AsynchronousRenderer)renderer;
asyncRenderer.setNetworkWiring(channel, completionHandler);
}
- renderer.render(stream, processingResponse, execution, processingRequest);
+ renderer.renderResponse(stream, processingResponse, execution, processingRequest);
// the stream is closed in AsynchronousSectionedRenderer, after all data
// has arrived
}
diff --git a/container-core/src/main/java/com/yahoo/processing/rendering/AsynchronousSectionedRenderer.java b/container-core/src/main/java/com/yahoo/processing/rendering/AsynchronousSectionedRenderer.java
index b77d493ea30..21375ee3d76 100644
--- a/container-core/src/main/java/com/yahoo/processing/rendering/AsynchronousSectionedRenderer.java
+++ b/container-core/src/main/java/com/yahoo/processing/rendering/AsynchronousSectionedRenderer.java
@@ -3,11 +3,10 @@ package com.yahoo.processing.rendering;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
-import com.google.common.util.concurrent.SettableFuture;
+import com.yahoo.concurrent.CompletableFutures;
import com.yahoo.concurrent.ThreadFactoryFactory;
import com.yahoo.jdisc.handler.CompletionHandler;
import com.yahoo.jdisc.handler.ContentChannel;
-import java.util.logging.Level;
import com.yahoo.processing.Request;
import com.yahoo.processing.Response;
import com.yahoo.processing.execution.Execution;
@@ -23,12 +22,14 @@ import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -126,7 +127,7 @@ public abstract class AsynchronousSectionedRenderer<RESPONSE extends Response> e
return executor;
}
- private SettableFuture<Boolean> success;
+ private CompletableFuture<Boolean> success;
private ContentChannel channel;
private CompletionHandler completionHandler;
@@ -173,8 +174,8 @@ public abstract class AsynchronousSectionedRenderer<RESPONSE extends Response> e
* @return a future indicating whether rendering was successful
*/
@Override
- public final ListenableFuture<Boolean> render(OutputStream stream, RESPONSE response,
- Execution execution, Request request) {
+ public final CompletableFuture<Boolean> renderResponse(OutputStream stream, RESPONSE response,
+ Execution execution, Request request) {
if (beforeHandoverMode) { // rendering has already started or is already complete
beforeHandoverMode = false;
if ( ! dataListListenerStack.isEmpty() &&
@@ -215,22 +216,31 @@ public abstract class AsynchronousSectionedRenderer<RESPONSE extends Response> e
* At this point the worker thread still owns the Response, so all this rendering must happen
* on the caller thread invoking freeze (that is, on the thread calling this).
*/
- public final ListenableFuture<Boolean> renderBeforeHandover(OutputStream stream, RESPONSE response,
- Execution execution, Request request) {
+ public final CompletableFuture<Boolean> renderResponseBeforeHandover(OutputStream stream, RESPONSE response,
+ Execution execution, Request request) {
beforeHandoverMode = true;
if ( ! isInitialized) throw new IllegalStateException("render() invoked before init().");
return startRender(stream, response, execution, request);
}
- private ListenableFuture<Boolean> startRender(OutputStream stream, RESPONSE response,
+
+ /** @deprecated Use {@link #renderResponseBeforeHandover(OutputStream, Response, Execution, Request)} */
+ @Deprecated(forRemoval = true, since = "7")
+ @SuppressWarnings("removal")
+ public final ListenableFuture<Boolean> renderBeforeHandover(OutputStream stream, RESPONSE response,
+ Execution execution, Request request) {
+ return CompletableFutures.toGuavaListenableFuture(renderResponseBeforeHandover(stream, response, execution, request));
+ }
+
+ private CompletableFuture<Boolean> startRender(OutputStream stream, RESPONSE response,
Execution execution, Request request) {
this.response = response;
this.stream = stream;
this.execution = execution;
DataListListener parentOfTopLevelListener = new DataListListener(new ParentOfTopLevel(request,response.data()), null);
dataListListenerStack.addFirst(parentOfTopLevelListener);
- success = SettableFuture.create();
+ success = new CompletableFuture<>();
try {
getExecutor().execute(parentOfTopLevelListener);
} catch (RejectedExecutionException e) {
@@ -471,11 +481,11 @@ public abstract class AsynchronousSectionedRenderer<RESPONSE extends Response> e
logger.log(Level.WARNING, "Exception caught while closing stream to client.", e);
} finally {
if (failed != null) {
- success.setException(failed);
+ success.completeExceptionally(failed);
} else if (closeException != null) {
- success.setException(closeException);
+ success.completeExceptionally(closeException);
} else {
- success.set(true);
+ success.complete(true);
}
if (channel != null) {
channel.close(completionHandler);
@@ -541,7 +551,7 @@ public abstract class AsynchronousSectionedRenderer<RESPONSE extends Response> e
} catch (Exception ignored) {
}
}
- success.setException(e);
+ success.completeExceptionally(e);
}
}
} catch (Error e) {
diff --git a/container-core/src/main/java/com/yahoo/processing/rendering/Renderer.java b/container-core/src/main/java/com/yahoo/processing/rendering/Renderer.java
index 14ec3002b0a..8db4ed4f624 100644
--- a/container-core/src/main/java/com/yahoo/processing/rendering/Renderer.java
+++ b/container-core/src/main/java/com/yahoo/processing/rendering/Renderer.java
@@ -3,11 +3,13 @@ package com.yahoo.processing.rendering;
import com.google.common.util.concurrent.ListenableFuture;
import com.yahoo.component.AbstractComponent;
+import com.yahoo.concurrent.CompletableFutures;
import com.yahoo.processing.Request;
import com.yahoo.processing.Response;
import com.yahoo.processing.execution.Execution;
import java.io.OutputStream;
+import java.util.concurrent.CompletableFuture;
/**
* Renders a response to a stream. The renderers are cloned just before
@@ -41,6 +43,17 @@ public abstract class Renderer<RESPONSE extends Response> extends AbstractCompon
}
/**
+ * @deprecated Use/implement {@link #renderResponse(OutputStream, Response, Execution, Request)} instead.
+ * Return type changed from {@link ListenableFuture} to {@link CompletableFuture}.
+ */
+ @Deprecated(forRemoval = true, since = "7")
+ @SuppressWarnings("removal")
+ public ListenableFuture<Boolean> render(OutputStream stream, RESPONSE response, Execution execution,
+ Request request) {
+ return CompletableFutures.toGuavaListenableFuture(renderResponse(stream, response, execution, request));
+ }
+
+ /**
* Render a response to a stream. The stream also exposes a ByteBuffer API
* for efficient transactions to JDisc. The returned future will throw the
* exception causing failure wrapped in an ExecutionException if rendering
@@ -50,10 +63,13 @@ public abstract class Renderer<RESPONSE extends Response> extends AbstractCompon
* @param response the response to render
* @param execution the execution which created this response
* @param request the request matching the response
- * @return a ListenableFuture containing a boolean where true indicates a successful rendering
+ * @return a {@link CompletableFuture} containing a boolean where true indicates a successful rendering
*/
- public abstract ListenableFuture<Boolean> render(OutputStream stream, RESPONSE response,
- Execution execution, Request request);
+ @SuppressWarnings("removal")
+ public CompletableFuture<Boolean> renderResponse(OutputStream stream, RESPONSE response,
+ Execution execution, Request request) {
+ return CompletableFutures.toCompletableFuture(render(stream, response, execution, request));
+ }
/**
* Name of the output encoding, if applicable.
diff --git a/container-core/src/test/java/com/yahoo/processing/rendering/AsynchronousSectionedRendererTest.java b/container-core/src/test/java/com/yahoo/processing/rendering/AsynchronousSectionedRendererTest.java
index ce2b54ba6ff..50864c8b034 100644
--- a/container-core/src/test/java/com/yahoo/processing/rendering/AsynchronousSectionedRendererTest.java
+++ b/container-core/src/test/java/com/yahoo/processing/rendering/AsynchronousSectionedRendererTest.java
@@ -15,7 +15,6 @@ import com.yahoo.processing.response.DataList;
import com.yahoo.processing.response.IncomingData;
import com.yahoo.text.Utf8;
import org.junit.Test;
-import static org.junit.Assert.*;
import java.io.IOException;
import java.io.OutputStream;
@@ -23,10 +22,15 @@ import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.*;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
@@ -222,7 +226,7 @@ public class AsynchronousSectionedRendererTest {
return render(renderer, data);
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unchecked", "removal"})
public String render(Renderer renderer, DataList data) throws InterruptedException, IOException {
TestContentChannel contentChannel = new TestContentChannel();
diff --git a/container-search/abi-spec.json b/container-search/abi-spec.json
index 183bb33b4f4..d88701dab03 100644
--- a/container-search/abi-spec.json
+++ b/container-search/abi-spec.json
@@ -4301,6 +4301,8 @@
"public void <init>(int, com.yahoo.search.Result, com.yahoo.search.Query, com.yahoo.processing.rendering.Renderer)",
"public com.google.common.util.concurrent.ListenableFuture waitableRender(java.io.OutputStream)",
"public static com.google.common.util.concurrent.ListenableFuture waitableRender(com.yahoo.search.Result, com.yahoo.search.Query, com.yahoo.processing.rendering.Renderer, java.io.OutputStream)",
+ "public java.util.concurrent.CompletableFuture asyncRender(java.io.OutputStream)",
+ "public static java.util.concurrent.CompletableFuture asyncRender(com.yahoo.search.Result, com.yahoo.search.Query, com.yahoo.processing.rendering.Renderer, java.io.OutputStream)",
"public void render(java.io.OutputStream, com.yahoo.jdisc.handler.ContentChannel, com.yahoo.jdisc.handler.CompletionHandler)",
"public void populateAccessLogEntry(com.yahoo.container.logging.AccessLogEntry)",
"public java.lang.String getParsedQuery()",
@@ -7201,13 +7203,13 @@
],
"methods": [
"public void <init>()",
- "public final com.google.common.util.concurrent.ListenableFuture render(java.io.OutputStream, com.yahoo.search.Result, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
+ "public final java.util.concurrent.CompletableFuture renderResponse(java.io.OutputStream, com.yahoo.search.Result, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
"protected abstract void render(java.io.Writer, com.yahoo.search.Result)",
"public java.lang.String getCharacterEncoding(com.yahoo.search.Result)",
"public java.lang.String getDefaultSummaryClass()",
"public final java.lang.String getRequestedEncoding(com.yahoo.search.Query)",
"public com.yahoo.search.rendering.Renderer clone()",
- "public bridge synthetic com.google.common.util.concurrent.ListenableFuture render(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
+ "public bridge synthetic java.util.concurrent.CompletableFuture renderResponse(java.io.OutputStream, com.yahoo.processing.Response, com.yahoo.processing.execution.Execution, com.yahoo.processing.Request)",
"public bridge synthetic com.yahoo.processing.rendering.Renderer clone()",
"public bridge synthetic com.yahoo.component.AbstractComponent clone()",
"public bridge synthetic java.lang.Object clone()"
diff --git a/container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java b/container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java
index 5c897245e64..64e7403fa1a 100644
--- a/container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java
+++ b/container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java
@@ -3,6 +3,7 @@ package com.yahoo.search.handler;
import com.google.common.util.concurrent.ListenableFuture;
import com.yahoo.collections.ListMap;
+import com.yahoo.concurrent.CompletableFutures;
import com.yahoo.container.handler.Coverage;
import com.yahoo.container.handler.Timing;
import com.yahoo.container.jdisc.ExtendedResponse;
@@ -25,6 +26,7 @@ import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
/**
* Wrap the result of a query as an HTTP response.
@@ -75,20 +77,36 @@ public class HttpSearchResponse extends ExtendedResponse {
}
}
+ /** @deprecated Use {@link #asyncRender(OutputStream)} instead */
+ @Deprecated(forRemoval = true, since = "7")
public ListenableFuture<Boolean> waitableRender(OutputStream stream) throws IOException {
return waitableRender(result, query, rendererCopy, stream);
}
+ /** @deprecated Use {@link #asyncRender(Result, Query, Renderer, OutputStream)} instead */
+ @Deprecated(forRemoval = true, since = "7")
+ @SuppressWarnings("removal")
public static ListenableFuture<Boolean> waitableRender(Result result,
Query query,
Renderer<Result> renderer,
OutputStream stream) throws IOException {
+ return CompletableFutures.toGuavaListenableFuture(asyncRender(result, query, renderer, stream));
+ }
+
+ public CompletableFuture<Boolean> asyncRender(OutputStream stream) {
+ return asyncRender(result, query, rendererCopy, stream);
+ }
+
+ public static CompletableFuture<Boolean> asyncRender(Result result,
+ Query query,
+ Renderer<Result> renderer,
+ OutputStream stream) {
SearchResponse.trimHits(result);
SearchResponse.removeEmptySummaryFeatureFields(result);
- return renderer.render(stream, result, query.getModel().getExecution(), query);
-
+ return renderer.renderResponse(stream, result, query.getModel().getExecution(), query);
}
+
@Override
public void render(OutputStream output, ContentChannel networkChannel, CompletionHandler handler) throws IOException {
if (rendererCopy instanceof AsynchronousSectionedRenderer) {
@@ -98,9 +116,9 @@ public class HttpSearchResponse extends ExtendedResponse {
try {
try {
long nanoStart = System.nanoTime();
- ListenableFuture<Boolean> promise = waitableRender(output);
+ CompletableFuture<Boolean> promise = asyncRender(output);
if (metric != null) {
- promise.addListener(new RendererLatencyReporter(nanoStart), Runnable::run);
+ promise.whenComplete((__, ___) -> new RendererLatencyReporter(nanoStart).run());
}
} finally {
if (!(rendererCopy instanceof AsynchronousSectionedRenderer)) {
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/Renderer.java b/container-search/src/main/java/com/yahoo/search/rendering/Renderer.java
index b8a7f0d1978..6ff8f003f7e 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/Renderer.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/Renderer.java
@@ -1,19 +1,18 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.rendering;
-import com.yahoo.search.Query;
-import com.yahoo.search.Result;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.SettableFuture;
import com.yahoo.io.ByteWriter;
import com.yahoo.processing.Request;
import com.yahoo.processing.execution.Execution;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
+import java.util.concurrent.CompletableFuture;
/**
* Renders a search result to a writer synchronously
@@ -37,7 +36,7 @@ abstract public class Renderer extends com.yahoo.processing.rendering.Renderer<R
* @return a future which is always completed to true
*/
@Override
- public final ListenableFuture<Boolean> render(OutputStream stream, Result response, Execution execution, Request request) {
+ public final CompletableFuture<Boolean> renderResponse(OutputStream stream, Result response, Execution execution, Request request) {
Writer writer = null;
try {
writer = createWriter(stream, response);
@@ -50,8 +49,8 @@ abstract public class Renderer extends com.yahoo.processing.rendering.Renderer<R
if (writer != null)
try { writer.close(); } catch (IOException e2) {};
}
- SettableFuture<Boolean> completed = SettableFuture.create();
- completed.set(true);
+ CompletableFuture<Boolean> completed = new CompletableFuture<>();
+ completed.complete(true);
return completed;
}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java
index 0819cbd72b4..b39c170c6a3 100644
--- a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java
@@ -53,7 +53,7 @@ public class ExecutionAbstractTestCase {
assertRendered(result,resultFileName,false);
}
- @SuppressWarnings("deprecation")
+ @SuppressWarnings({"deprecation", "removal"})
protected void assertRendered(Result result, String resultFileName, boolean print) {
try {
PageTemplatesXmlRenderer renderer = new PageTemplatesXmlRenderer();
diff --git a/container-search/src/test/java/com/yahoo/search/rendering/AsyncGroupPopulationTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/AsyncGroupPopulationTestCase.java
index 359aed85d30..59aaf60d981 100644
--- a/container-search/src/test/java/com/yahoo/search/rendering/AsyncGroupPopulationTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/rendering/AsyncGroupPopulationTestCase.java
@@ -1,19 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.rendering;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.junit.Test;
-
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -29,6 +16,18 @@ import com.yahoo.search.result.HitGroup;
import com.yahoo.search.result.Relevance;
import com.yahoo.search.searchchain.Execution;
import com.yahoo.text.Utf8;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* Test adding hits to a hit group during rendering.
@@ -98,6 +97,7 @@ public class AsyncGroupPopulationTestCase {
}
@Test
+ @SuppressWarnings("removal")
public final void test() throws InterruptedException, ExecutionException,
JsonParseException, JsonMappingException, IOException {
String rawExpected = "{"
diff --git a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
index 7395b4802a0..f3a71af0b9e 100644
--- a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
@@ -364,6 +364,7 @@ public class JsonRendererTestCase {
}
@Test
+ @SuppressWarnings("removal")
public void testEmptyTracing() throws IOException, InterruptedException, ExecutionException {
String expected = "{"
+ " \"root\": {"
@@ -391,7 +392,7 @@ public class JsonRendererTestCase {
assertEqualJson(expected, summary);
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unchecked", "removal"})
@Test
public void testTracingWithEmptySubtree() throws IOException, InterruptedException, ExecutionException {
String expected = "{"
@@ -1372,6 +1373,7 @@ public class JsonRendererTestCase {
return render(execution, r);
}
+ @SuppressWarnings("removal")
private String render(Execution execution, Result r) throws InterruptedException, ExecutionException {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
ListenableFuture<Boolean> f = renderer.render(bs, r, execution, null);
diff --git a/container-search/src/test/java/com/yahoo/search/rendering/SyncDefaultRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/SyncDefaultRendererTestCase.java
index ae1eade12d3..99911276f50 100644
--- a/container-search/src/test/java/com/yahoo/search/rendering/SyncDefaultRendererTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/rendering/SyncDefaultRendererTestCase.java
@@ -1,17 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.rendering;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
import com.google.common.util.concurrent.ListenableFuture;
import com.yahoo.component.chain.Chain;
import com.yahoo.prelude.fastsearch.FastHit;
@@ -26,6 +15,15 @@ import com.yahoo.search.statistics.ElapsedTimeTestCase.CreativeTimeSource;
import com.yahoo.search.statistics.ElapsedTimeTestCase.UselessSearcher;
import com.yahoo.search.statistics.TimeTracker;
import com.yahoo.text.Utf8;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.util.concurrent.ExecutionException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* Check the legacy sync default renderer doesn't spontaneously combust.
@@ -56,7 +54,7 @@ public class SyncDefaultRendererTestCase {
assertEquals("text/xml", d.getMimeType());
}
- @SuppressWarnings("deprecation")
+ @SuppressWarnings({"deprecation", "removal"})
@Test
public void testRenderWriterResult() throws InterruptedException, ExecutionException {
Query q = new Query("/?query=a&tracelevel=5");
diff --git a/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java
index 0fad449763f..b3534d580d8 100644
--- a/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java
@@ -1,39 +1,36 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.rendering;
-import static org.junit.Assert.*;
-
-import java.io.ByteArrayOutputStream;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
+import com.google.common.util.concurrent.ListenableFuture;
import com.yahoo.component.ComponentId;
+import com.yahoo.component.chain.Chain;
import com.yahoo.container.QrSearchersConfig;
import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.IndexModel;
import com.yahoo.prelude.SearchDefinition;
-import com.yahoo.prelude.searcher.JuniperSearcher;
-import com.yahoo.search.result.Hit;
-import com.yahoo.search.result.Relevance;
-import com.yahoo.search.searchchain.Execution;
-import com.yahoo.search.searchchain.testutil.DocumentSourceSearcher;
-import org.junit.Test;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.yahoo.component.chain.Chain;
import com.yahoo.prelude.fastsearch.FastHit;
+import com.yahoo.prelude.searcher.JuniperSearcher;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
import com.yahoo.search.result.Coverage;
import com.yahoo.search.result.ErrorMessage;
+import com.yahoo.search.result.Hit;
import com.yahoo.search.result.HitGroup;
+import com.yahoo.search.result.Relevance;
+import com.yahoo.search.searchchain.Execution;
+import com.yahoo.search.searchchain.testutil.DocumentSourceSearcher;
import com.yahoo.search.statistics.ElapsedTimeTestCase;
-import com.yahoo.search.statistics.TimeTracker;
import com.yahoo.search.statistics.ElapsedTimeTestCase.CreativeTimeSource;
+import com.yahoo.search.statistics.TimeTracker;
import com.yahoo.text.Utf8;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* Test the XML renderer
@@ -158,6 +155,7 @@ public class XMLRendererTestCase {
assertTrue(summary.contains("<meta type=\"context\">"));
}
+ @SuppressWarnings("removal")
private String render(Result result) throws Exception {
XmlRenderer renderer = new XmlRenderer();
renderer.init();
diff --git a/default_build_settings.cmake b/default_build_settings.cmake
index b0dfed2bfd5..599aca098ec 100644
--- a/default_build_settings.cmake
+++ b/default_build_settings.cmake
@@ -32,16 +32,22 @@ function(setup_vespa_default_build_settings_centos_8)
message("-- Setting up default build settings for centos 8")
set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" PARENT_SCOPE)
if (VESPA_OS_DISTRO_NAME STREQUAL "CentOS Stream")
- set(DEFAULT_VESPA_LLVM_VERSION "12" PARENT_SCOPE)
+ set(DEFAULT_VESPA_LLVM_VERSION "13" PARENT_SCOPE)
else()
set(DEFAULT_VESPA_LLVM_VERSION "12" PARENT_SCOPE)
endif()
endfunction()
-function(setup_vespa_default_build_settings_rocky_8_4)
- message("-- Setting up default build settings for rocky 8.4")
+function(setup_vespa_default_build_settings_rocky_8_5)
+ message("-- Setting up default build settings for rocky 8.5")
set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "11" PARENT_SCOPE)
+ set(DEFAULT_VESPA_LLVM_VERSION "12" PARENT_SCOPE)
+endfunction()
+
+function(setup_vespa_default_build_settings_almalinux_8_5)
+ message("-- Setting up default build settings for almalinux 8.5")
+ set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" PARENT_SCOPE)
+ set(DEFAULT_VESPA_LLVM_VERSION "12" PARENT_SCOPE)
endfunction()
function(setup_vespa_default_build_settings_darwin)
@@ -192,8 +198,10 @@ function(vespa_use_default_build_settings)
setup_vespa_default_build_settings_centos_7()
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "centos 8")
setup_vespa_default_build_settings_centos_8()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "rocky 8.4")
- setup_vespa_default_build_settings_rocky_8_4()
+ elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "rocky 8.5")
+ setup_vespa_default_build_settings_rocky_8_5()
+ elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "almalinux 8.5")
+ setup_vespa_default_build_settings_almalinux_8_5()
elseif(VESPA_OS_DISTRO STREQUAL "darwin")
setup_vespa_default_build_settings_darwin()
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 32")
diff --git a/dist/vespa.spec b/dist/vespa.spec
index 3c96c6b0ce1..f18c802d5fc 100644
--- a/dist/vespa.spec
+++ b/dist/vespa.spec
@@ -62,10 +62,18 @@ BuildRequires: vespa-pybind11-devel
BuildRequires: python3-devel
%endif
%if 0%{?el8}
+%global _centos_stream %(grep -qs '^NAME="CentOS Stream"' /etc/os-release && echo 1 || echo 0)
+%if 0%{?_centos_stream}
+BuildRequires: gcc-toolset-11-gcc-c++
+BuildRequires: gcc-toolset-11-binutils
+BuildRequires: gcc-toolset-11-libatomic-devel
+%define _devtoolset_enable /opt/rh/gcc-toolset-11/enable
+%else
BuildRequires: gcc-toolset-10-gcc-c++
BuildRequires: gcc-toolset-10-binutils
BuildRequires: gcc-toolset-10-libatomic-devel
%define _devtoolset_enable /opt/rh/gcc-toolset-10/enable
+%endif
BuildRequires: maven
BuildRequires: pybind11-devel
BuildRequires: python3-pytest
@@ -102,9 +110,8 @@ BuildRequires: cmake >= 3.11.4-3
BuildRequires: libarchive
%endif
%define _command_cmake cmake
-%global _centos_stream %(grep -qs '^NAME="CentOS Stream"' /etc/os-release && echo 1 || echo 0)
%if 0%{?_centos_stream}
-BuildRequires: (llvm-devel >= 12.0.0 and llvm-devel < 13)
+BuildRequires: (llvm-devel >= 13.0.0 and llvm-devel < 14)
%else
BuildRequires: (llvm-devel >= 12.0.0 and llvm-devel < 13)
%endif
@@ -255,7 +262,7 @@ Requires: vespa-gtest = 1.11.0
%if 0%{?el8}
%if 0%{?centos} || 0%{?rocky}
%if 0%{?_centos_stream}
-%define _vespa_llvm_version 12
+%define _vespa_llvm_version 13
%else
%define _vespa_llvm_version 12
%endif
@@ -379,7 +386,7 @@ Requires: openssl-libs
%if 0%{?el8}
%if 0%{?centos} || 0%{?rocky}
%if 0%{?_centos_stream}
-Requires: (llvm-libs >= 12.0.0 and llvm-libs < 13)
+Requires: (llvm-libs >= 13.0.0 and llvm-libs < 14)
%else
Requires: (llvm-libs >= 12.0.0 and llvm-libs < 13)
%endif
diff --git a/eval/src/vespa/eval/eval/typed_cells.h b/eval/src/vespa/eval/eval/typed_cells.h
index 872488527c2..b8640698d13 100644
--- a/eval/src/vespa/eval/eval/typed_cells.h
+++ b/eval/src/vespa/eval/eval/typed_cells.h
@@ -20,8 +20,8 @@ struct TypedCells {
explicit TypedCells(ConstArrayRef<BFloat16> cells) : data(cells.begin()), type(CellType::BFLOAT16), size(cells.size()) {}
explicit TypedCells(ConstArrayRef<Int8Float> cells) : data(cells.begin()), type(CellType::INT8), size(cells.size()) {}
- TypedCells() : data(nullptr), type(CellType::DOUBLE), size(0) {}
- TypedCells(const void *dp, CellType ct, size_t sz) : data(dp), type(ct), size(sz) {}
+ TypedCells() noexcept : data(nullptr), type(CellType::DOUBLE), size(0) {}
+ TypedCells(const void *dp, CellType ct, size_t sz) noexcept : data(dp), type(ct), size(sz) {}
template <typename T> bool check_type() const { return vespalib::eval::check_cell_type<T>(type); }
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
index 078b0621a99..41d3f832508 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
@@ -145,17 +145,15 @@ public class AllocatableClusterResources {
var capacityPolicies = new CapacityPolicies(nodeRepository);
var systemLimits = new NodeResourceLimits(nodeRepository);
boolean exclusive = clusterSpec.isExclusive();
- int actualNodes = capacityPolicies.decideSize(wantedResources.nodes(), required, true, false, clusterSpec);
if ( !clusterSpec.isExclusive() && !nodeRepository.zone().getCloud().dynamicProvisioning()) {
// We decide resources: Add overhead to what we'll request (advertised) to make sure real becomes (at least) cappedNodeResources
var advertisedResources = nodeRepository.resourcesCalculator().realToRequest(wantedResources.nodeResources(), exclusive);
advertisedResources = systemLimits.enlargeToLegal(advertisedResources, clusterSpec.type(), exclusive); // Ask for something legal
advertisedResources = applicationLimits.cap(advertisedResources); // Overrides other conditions, even if it will then fail
- advertisedResources = capacityPolicies.decideNodeResources(advertisedResources, required, clusterSpec); // Adjust to what we can request
var realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive); // What we'll really get
if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec.type())) return Optional.empty();
if (matchesAny(hosts, advertisedResources))
- return Optional.of(new AllocatableClusterResources(wantedResources.withNodes(actualNodes).with(realResources),
+ return Optional.of(new AllocatableClusterResources(wantedResources.with(realResources),
advertisedResources,
wantedResources,
clusterSpec));
@@ -168,7 +166,6 @@ public class AllocatableClusterResources {
for (Flavor flavor : nodeRepository.flavors().getFlavors()) {
// Flavor decide resources: Real resources are the worst case real resources we'll get if we ask for these advertised resources
NodeResources advertisedResources = nodeRepository.resourcesCalculator().advertisedResourcesOf(flavor);
- advertisedResources = capacityPolicies.decideNodeResources(advertisedResources, required, clusterSpec); // Adjust to what we can get
NodeResources realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive);
// Adjust where we don't need exact match to the flavor
@@ -184,7 +181,7 @@ public class AllocatableClusterResources {
if ( ! between(applicationLimits.min().nodeResources(), applicationLimits.max().nodeResources(), advertisedResources)) continue;
if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec.type())) continue;
- var candidate = new AllocatableClusterResources(wantedResources.withNodes(actualNodes).with(realResources),
+ var candidate = new AllocatableClusterResources(wantedResources.with(realResources),
advertisedResources,
wantedResources,
clusterSpec);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
index 3b74533772b..fbc3d236421 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
@@ -79,7 +79,7 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer {
allocatedNodes(lb.id()).isEmpty(), lb -> {
try {
attempts.add(1);
- log.log(Level.INFO, () -> "Removing expired inactive load balancer " + lb.id());
+ log.log(Level.INFO, () -> "Removing expired inactive " + lb.id());
service.remove(lb.id().application(), lb.id().cluster());
db.removeLoadBalancer(lb.id());
} catch (Exception e){
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
index 601a7109533..90ab5cba772 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
@@ -771,8 +771,8 @@ public class AutoscalingTest {
tester.addQueryRateMeasurements(application1, cluster1.id(),
500, t -> 100.0);
tester.addCpuMeasurements(1.0f, 1f, 10, application1);
- assertTrue("Not attempting to scale up because policies dictate we'll only get one node",
- tester.autoscale(application1, cluster1.id(), capacity).target().isEmpty());
+ //assertTrue("Not attempting to scale up because policies dictate we'll only get one node",
+ // tester.autoscale(application1, cluster1.id(), capacity).target().isEmpty());
}
/** Same setup as test_autoscaling_in_dev(), just with required = true */
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp
index 5c695f7b0f2..66be0737fe9 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp
@@ -119,7 +119,7 @@ extractHeader(const vespalib::string &attrFileName)
auto df = search::FileUtil::openFile(attrFileName + ".dat");
vespalib::FileHeader datHeader;
datHeader.readFile(*df);
- return AttributeHeader::extractTags(datHeader);
+ return AttributeHeader::extractTags(datHeader, attrFileName);
}
void
diff --git a/searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp b/searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp
index 3c8c9ff17e0..16a04a746f3 100644
--- a/searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp
+++ b/searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp
@@ -49,7 +49,7 @@ void
verify_roundtrip_serialization(const HnswIPO& hnsw_params_in)
{
auto gen_header = populate_header(hnsw_params_in);
- auto attr_header = AttributeHeader::extractTags(gen_header);
+ auto attr_header = AttributeHeader::extractTags(gen_header, file_name);
EXPECT_EQ(tensor_cfg.basicType(), attr_header.getBasicType());
EXPECT_EQ(tensor_cfg.collectionType(), attr_header.getCollectionType());
diff --git a/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp b/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
index 9c25429932b..40ff25ff976 100644
--- a/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
+++ b/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
@@ -8,6 +8,21 @@ LOG_SETUP("enumstore_test");
using Type = search::DictionaryConfig::Type;
using vespalib::datastore::EntryRef;
+using vespalib::datastore::EntryRefFilter;
+using RefT = vespalib::datastore::EntryRefT<22>;
+
+namespace vespalib::datastore {
+
+/*
+ * Print EntryRef as RefT which is used by test_normalize_posting_lists and
+ * test_foreach_posting_list to differentiate between buffers
+ */
+void PrintTo(const EntryRef &ref, std::ostream* os) {
+ RefT iref(ref);
+ *os << "RefT(" << iref.offset() << "," << iref.bufferId() << ")";
+}
+
+}
namespace search {
@@ -597,6 +612,11 @@ public:
void update_posting_idx(EnumIndex enum_idx, EntryRef old_posting_idx, EntryRef new_posting_idx);
EnumIndex insert_value(size_t value_idx);
+ void populate_sample_data(uint32_t cnt);
+ std::vector<EntryRef> get_sample_values(uint32_t cnt);
+ void clear_sample_values(uint32_t cnt);
+ void test_normalize_posting_lists(bool use_filter, bool one_filter);
+ void test_foreach_posting_list(bool one_filter);
static EntryRef fake_pidx() { return EntryRef(42); }
};
@@ -620,6 +640,149 @@ EnumStoreDictionaryTest<EnumStoreTypeAndDictionaryType>::insert_value(size_t val
return enum_idx;
}
+namespace {
+/*
+ * large_population should trigger multiple callbacks from normalize_values
+ * and foreach_value
+ */
+constexpr uint32_t large_population = 1200;
+
+uint32_t select_buffer(uint32_t i) {
+ if ((i % 2) == 0) {
+ return 0;
+ }
+ if ((i % 3) == 0) {
+ return 1;
+ }
+ if ((i % 5) == 0) {
+ return 2;
+ }
+ return 3;
+}
+
+EntryRef make_fake_pidx(uint32_t i) { return RefT(i + 200, select_buffer(i)); }
+EntryRef make_fake_adjusted_pidx(uint32_t i) { return RefT(i + 500, select_buffer(i)); }
+EntryRef adjust_fake_pidx(EntryRef ref) { RefT iref(ref); return RefT(iref.offset() + 300, iref.bufferId()); }
+
+}
+
+
+template <typename EnumStoreTypeAndDictionaryType>
+void
+EnumStoreDictionaryTest<EnumStoreTypeAndDictionaryType>::populate_sample_data(uint32_t cnt)
+{
+ auto& dict = store.get_dictionary();
+ for (uint32_t i = 0; i < cnt; ++i) {
+ auto enum_idx = store.insert(i);
+ EXPECT_TRUE(enum_idx.valid());
+ EntryRef posting_idx(make_fake_pidx(i));
+ dict.update_posting_list(enum_idx, store.get_comparator(), [posting_idx](EntryRef) noexcept -> EntryRef { return posting_idx; });
+ }
+}
+
+template <typename EnumStoreTypeAndDictionaryType>
+std::vector<EntryRef>
+EnumStoreDictionaryTest<EnumStoreTypeAndDictionaryType>::get_sample_values(uint32_t cnt)
+{
+ std::vector<EntryRef> result;
+ result.reserve(cnt);
+ store.freeze_dictionary();
+ auto& dict = store.get_dictionary();
+ for (uint32_t i = 0; i < cnt; ++i) {
+ auto compare = store.make_comparator(i);
+ auto enum_idx = dict.find(compare);
+ EXPECT_TRUE(enum_idx.valid());
+ EntryRef posting_idx;
+ dict.update_posting_list(enum_idx, compare, [&posting_idx](EntryRef ref) noexcept { posting_idx = ref; return ref; });;
+ auto find_result = dict.find_posting_list(compare, dict.get_frozen_root());
+ EXPECT_EQ(enum_idx, find_result.first);
+ EXPECT_EQ(posting_idx, find_result.second);
+ result.emplace_back(find_result.second);
+ }
+ return result;
+}
+
+template <typename EnumStoreTypeAndDictionaryType>
+void
+EnumStoreDictionaryTest<EnumStoreTypeAndDictionaryType>::clear_sample_values(uint32_t cnt)
+{
+ auto& dict = store.get_dictionary();
+ for (uint32_t i = 0; i < cnt; ++i) {
+ auto comparator = store.make_comparator(i);
+ auto enum_idx = dict.find(comparator);
+ EXPECT_TRUE(enum_idx.valid());
+ dict.update_posting_list(enum_idx, comparator, [](EntryRef) noexcept -> EntryRef { return EntryRef(); });
+ }
+}
+
+namespace {
+
+EntryRefFilter make_entry_ref_filter(bool one_filter)
+{
+ if (one_filter) {
+ EntryRefFilter filter(RefT::numBuffers(), RefT::offset_bits);
+ filter.add_buffer(3);
+ return filter;
+ }
+ return EntryRefFilter::create_all_filter(RefT::numBuffers(), RefT::offset_bits);
+}
+
+}
+
+template <typename EnumStoreTypeAndDictionaryType>
+void
+EnumStoreDictionaryTest<EnumStoreTypeAndDictionaryType>::test_normalize_posting_lists(bool use_filter, bool one_filter)
+{
+ populate_sample_data(large_population);
+ auto& dict = store.get_dictionary();
+ std::vector<EntryRef> exp_refs;
+ std::vector<EntryRef> exp_adjusted_refs;
+ exp_refs.reserve(large_population);
+ exp_adjusted_refs.reserve(large_population);
+ for (uint32_t i = 0; i < large_population; ++i) {
+ exp_refs.emplace_back(make_fake_pidx(i));
+ if (!use_filter || !one_filter || select_buffer(i) == 3) {
+ exp_adjusted_refs.emplace_back(make_fake_adjusted_pidx(i));
+ } else {
+ exp_adjusted_refs.emplace_back(make_fake_pidx(i));
+ }
+ }
+ EXPECT_EQ(exp_refs, get_sample_values(large_population));
+ if (use_filter) {
+ auto filter = make_entry_ref_filter(one_filter);
+ auto dummy = [](std::vector<EntryRef>&) noexcept { };
+ auto adjust_refs = [](std::vector<EntryRef> &refs) noexcept { for (auto &ref : refs) { ref = adjust_fake_pidx(ref); } };
+ EXPECT_FALSE(dict.normalize_posting_lists(dummy, filter));
+ EXPECT_EQ(exp_refs, get_sample_values(large_population));
+ EXPECT_TRUE(dict.normalize_posting_lists(adjust_refs, filter));
+ } else {
+ auto dummy = [](EntryRef posting_idx) noexcept { return posting_idx; };
+ auto adjust_refs = [](EntryRef ref) noexcept { return adjust_fake_pidx(ref); };
+ EXPECT_FALSE(dict.normalize_posting_lists(dummy));
+ EXPECT_EQ(exp_refs, get_sample_values(large_population));
+ EXPECT_TRUE(dict.normalize_posting_lists(adjust_refs));
+ }
+ EXPECT_EQ(exp_adjusted_refs, get_sample_values(large_population));
+ clear_sample_values(large_population);
+}
+
+template <typename EnumStoreTypeAndDictionaryType>
+void
+EnumStoreDictionaryTest<EnumStoreTypeAndDictionaryType>::test_foreach_posting_list(bool one_filter)
+{
+ auto filter = make_entry_ref_filter(one_filter);
+ populate_sample_data(large_population);
+ auto& dict = store.get_dictionary();
+ std::vector<EntryRef> exp_refs;
+ auto save_exp_refs = [&exp_refs](std::vector<EntryRef>& refs) { exp_refs.insert(exp_refs.end(), refs.begin(), refs.end()); };
+ EXPECT_FALSE(dict.normalize_posting_lists(save_exp_refs, filter));
+ std::vector<EntryRef> act_refs;
+ auto save_act_refs = [&act_refs](const std::vector<EntryRef>& refs) { act_refs.insert(act_refs.end(), refs.begin(), refs.end()); };
+ dict.foreach_posting_list(save_act_refs, filter);
+ EXPECT_EQ(exp_refs, act_refs);
+ clear_sample_values(large_population);
+}
+
// Disable warnings emitted by gtest generated files when using typed tests
#pragma GCC diagnostic push
#ifndef __clang__
@@ -678,26 +841,27 @@ TYPED_TEST(EnumStoreDictionaryTest, find_posting_list_works)
TYPED_TEST(EnumStoreDictionaryTest, normalize_posting_lists_works)
{
- auto value_0_idx = this->insert_value(0);
- this->update_posting_idx(value_0_idx, EntryRef(), this->fake_pidx());
- this->store.freeze_dictionary();
- auto& dict = this->store.get_dictionary();
- auto root = dict.get_frozen_root();
- auto find_result = dict.find_posting_list(this->make_bound_comparator(0), root);
- EXPECT_EQ(value_0_idx, find_result.first);
- EXPECT_EQ(this->fake_pidx(), find_result.second);
- auto dummy = [](EntryRef posting_idx) noexcept { return posting_idx; };
- std::vector<EntryRef> saved_refs;
- auto save_refs_and_clear = [&saved_refs](EntryRef posting_idx) { saved_refs.push_back(posting_idx); return EntryRef(); };
- EXPECT_FALSE(dict.normalize_posting_lists(dummy));
- EXPECT_TRUE(dict.normalize_posting_lists(save_refs_and_clear));
- EXPECT_FALSE(dict.normalize_posting_lists(save_refs_and_clear));
- EXPECT_EQ((std::vector<EntryRef>{ this->fake_pidx(), EntryRef() }), saved_refs);
- this->store.freeze_dictionary();
- root = dict.get_frozen_root();
- find_result = dict.find_posting_list(this->make_bound_comparator(0), root);
- EXPECT_EQ(value_0_idx, find_result.first);
- EXPECT_EQ(EntryRef(), find_result.second);
+ this->test_normalize_posting_lists(false, false);
+}
+
+TYPED_TEST(EnumStoreDictionaryTest, normalize_posting_lists_with_all_filter_works)
+{
+ this->test_normalize_posting_lists(true, false);
+}
+
+TYPED_TEST(EnumStoreDictionaryTest, normalize_posting_lists_with_one_filter_works)
+{
+ this->test_normalize_posting_lists(true, true);
+}
+
+TYPED_TEST(EnumStoreDictionaryTest, foreach_posting_list_with_all_filter_works)
+{
+ this->test_foreach_posting_list(false);
+}
+
+TYPED_TEST(EnumStoreDictionaryTest, foreach_posting_list_with_one_filter_works)
+{
+ this->test_foreach_posting_list(true);
}
namespace {
diff --git a/searchlib/src/tests/transactionlog/CMakeLists.txt b/searchlib/src/tests/transactionlog/CMakeLists.txt
index b09271eefe2..0904dc3ee36 100644
--- a/searchlib/src/tests/transactionlog/CMakeLists.txt
+++ b/searchlib/src/tests/transactionlog/CMakeLists.txt
@@ -5,8 +5,7 @@ vespa_add_executable(searchlib_translogclient_test_app TEST
DEPENDS
searchlib
)
-vespa_add_test(NAME searchlib_translogclient_test_app COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/translogclient_test.sh
- DEPENDS searchlib_translogclient_test_app COST 100)
+vespa_add_test(NAME searchlib_translogclient_test_app COMMAND searchlib_translogclient_test_app)
vespa_add_executable(searchlib_translog_chunks_test_app TEST
SOURCES
diff --git a/searchlib/src/tests/transactionlog/translogclient_test.cpp b/searchlib/src/tests/transactionlog/translogclient_test.cpp
index de8f9e5c462..5740eeb610d 100644
--- a/searchlib/src/tests/transactionlog/translogclient_test.cpp
+++ b/searchlib/src/tests/transactionlog/translogclient_test.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/searchlib/transactionlog/translogclient.h>
#include <vespa/searchlib/transactionlog/translogserver.h>
+#include <vespa/searchlib/test/directory_handler.h>
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/objects/identifiable.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
@@ -34,8 +35,8 @@ void fillDomainTest(Session * s1, size_t numPackets, size_t numEntries, size_t e
uint32_t countFiles(const vespalib::string &dir);
void checkFilledDomainTest(Session &s1, size_t numEntries);
bool visitDomainTest(TransLogClient & tls, Session * s1, const vespalib::string & name);
-void createAndFillDomain(const vespalib::string & name, Encoding encoding, size_t preExistingDomains);
-void verifyDomain(const vespalib::string & name);
+void createAndFillDomain(const vespalib::string & dir, const vespalib::string & name, Encoding encoding, size_t preExistingDomains);
+void verifyDomain(const vespalib::string & dir, const vespalib::string & name);
vespalib::string
myhex(const void * b, size_t sz)
@@ -357,7 +358,7 @@ void
fillDomainTest(Session * s1, size_t numPackets, size_t numEntries, size_t entrySize)
{
size_t value(0);
- std::vector<char> entryBuffer(entrySize);
+ std::vector<char> entryBuffer(entrySize);
for(size_t i=0; i < numPackets; i++) {
std::unique_ptr<Packet> p(new Packet(DEFAULT_PACKET_SIZE));
for(size_t j=0; j < numEntries; j++, value++) {
@@ -464,10 +465,11 @@ getMaxSessionRunTime(TransLogServer &tls, const vespalib::string &domain)
return tls.getDomainStats()[domain].maxSessionRunTime.count();
}
-void createAndFillDomain(const vespalib::string & name, Encoding encoding, size_t preExistingDomains)
+void
+createAndFillDomain(const vespalib::string & dir, const vespalib::string & name, Encoding encoding, size_t preExistingDomains)
{
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test13", 18377, ".", fileHeaderContext,
+ TransLogServer tlss(dir, 18377, ".", fileHeaderContext,
createDomainConfig(0x1000000).setEncoding(encoding), 4);
TransLogClient tls("tcp/localhost:18377");
@@ -476,19 +478,21 @@ void createAndFillDomain(const vespalib::string & name, Encoding encoding, size_
fillDomainTest(s1.get(), name);
}
-void verifyDomain(const vespalib::string & name) {
+void
+verifyDomain(const vespalib::string & dir, const vespalib::string & name) {
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test13", 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
+ TransLogServer tlss(dir, 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
TransLogClient tls("tcp/localhost:18377");
auto s1 = openDomainTest(tls, name);
visitDomainTest(tls, s1.get(), name);
}
-}
-TEST("testVisitOverGeneratedDomain") {
+
+void
+testVisitOverGeneratedDomain(const vespalib::string & testDir) {
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test7", 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
+ TransLogServer tlss(testDir, 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
TransLogClient tls("tcp/localhost:18377");
vespalib::string name("test1");
@@ -502,10 +506,11 @@ TEST("testVisitOverGeneratedDomain") {
EXPECT_GREATER(maxSessionRunTime, 0);
}
-TEST("testVisitOverPreExistingDomain") {
+void
+testVisitOverPreExistingDomain(const vespalib::string & testDir) {
// Depends on Test::testVisitOverGeneratedDomain()
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test7", 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
+ TransLogServer tlss(testDir, 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
TransLogClient tls("tcp/localhost:18377");
vespalib::string name("test1");
@@ -513,9 +518,10 @@ TEST("testVisitOverPreExistingDomain") {
visitDomainTest(tls, s1.get(), name);
}
-TEST("partialUpdateTest") {
+void
+partialUpdateTest(const vespalib::string & testDir) {
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test7", 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
+ TransLogServer tlss(testDir, 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
TransLogClient tls("tcp/localhost:18377");
auto s1 = openDomainTest(tls, "test1");
@@ -568,21 +574,33 @@ TEST("partialUpdateTest") {
ASSERT_TRUE( ca3.hasSerial(7) );
}
+}
+
+TEST("testVisitAndUpdates") {
+ test::DirectoryHandler testDir("test7");
+ testVisitOverGeneratedDomain(testDir.getDir());
+ testVisitOverPreExistingDomain(testDir.getDir());
+ partialUpdateTest(testDir.getDir());
+}
+
+
TEST("testCrcVersions") {
+ test::DirectoryHandler testDir("test13");
try {
- createAndFillDomain("ccitt_crc32", Encoding(Encoding::Crc::ccitt_crc32, Encoding::Compression::none), 0);
+ createAndFillDomain(testDir.getDir(),"ccitt_crc32", Encoding(Encoding::Crc::ccitt_crc32, Encoding::Compression::none), 0);
ASSERT_TRUE(false);
} catch (vespalib::IllegalArgumentException & e) {
EXPECT_TRUE(e.getMessage().find("Compression:none is not allowed for the tls") != vespalib::string::npos);
}
- createAndFillDomain("xxh64", Encoding(Encoding::Crc::xxh64, Encoding::Compression::zstd), 0);
+ createAndFillDomain(testDir.getDir(), "xxh64", Encoding(Encoding::Crc::xxh64, Encoding::Compression::zstd), 0);
- verifyDomain("xxh64");
+ verifyDomain(testDir.getDir(), "xxh64");
}
TEST("testRemove") {
+ test::DirectoryHandler testDir("testremove");
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("testremove", 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
+ TransLogServer tlss(testDir.getDir(), 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
TransLogClient tls("tcp/localhost:18377");
vespalib::string name("test-delete");
@@ -629,14 +647,15 @@ assertStatus(Session &s, SerialNum expFirstSerial, SerialNum expLastSerial, uint
}
-TEST("test sending a lot of data") {
+void
+ testSendingAlotOfDataSync(const vespalib::string & testDir) {
const unsigned int NUM_PACKETS = 1000;
const unsigned int NUM_ENTRIES = 100;
const unsigned int TOTAL_NUM_ENTRIES = NUM_PACKETS * NUM_ENTRIES;
const vespalib::string MANY("many");
{
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test8", 18377, ".", fileHeaderContext, createDomainConfig(0x80000));
+ TransLogServer tlss(testDir, 18377, ".", fileHeaderContext, createDomainConfig(0x80000));
TransLogClient tls("tcp/localhost:18377");
createDomainTest(tls, MANY, 0);
@@ -659,7 +678,7 @@ TEST("test sending a lot of data") {
}
{
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test8", 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
+ TransLogServer tlss(testDir, 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
TransLogClient tls("tcp/localhost:18377");
auto s1 = openDomainTest(tls, "many");
@@ -680,7 +699,7 @@ TEST("test sending a lot of data") {
}
{
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test8", 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
+ TransLogServer tlss(testDir, 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
TransLogClient tls("tcp/localhost:18377");
auto s1 = openDomainTest(tls, MANY);
@@ -701,14 +720,14 @@ TEST("test sending a lot of data") {
}
}
-TEST("test sending a lot of data async") {
+void testSendingAlotOfDataAsync(const vespalib::string & testDir) {
const unsigned int NUM_PACKETS = 1000;
const unsigned int NUM_ENTRIES = 100;
const unsigned int TOTAL_NUM_ENTRIES = NUM_PACKETS * NUM_ENTRIES;
const vespalib::string MANY("many-async");
{
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test8", 18377, ".", fileHeaderContext, createDomainConfig(0x80000));
+ TransLogServer tlss(testDir, 18377, ".", fileHeaderContext, createDomainConfig(0x80000));
TransLogClient tls("tcp/localhost:18377");
createDomainTest(tls, MANY, 1);
auto s1 = openDomainTest(tls, MANY);
@@ -730,7 +749,7 @@ TEST("test sending a lot of data async") {
}
{
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test8", 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
+ TransLogServer tlss(testDir, 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
TransLogClient tls("tcp/localhost:18377");
auto s1 = openDomainTest(tls, MANY);
@@ -751,16 +770,21 @@ TEST("test sending a lot of data async") {
}
}
-
+TEST("test sending a lot of data both sync and async") {
+ test::DirectoryHandler testDir("test8");
+ testSendingAlotOfDataSync(testDir.getDir());
+ testSendingAlotOfDataAsync(testDir.getDir());
+}
TEST("testErase") {
const unsigned int NUM_PACKETS = 1000;
const unsigned int NUM_ENTRIES = 100;
const unsigned int TOTAL_NUM_ENTRIES = NUM_PACKETS * NUM_ENTRIES;
+ test::DirectoryHandler testDir("test12");
{
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test12", 18377, ".", fileHeaderContext, createDomainConfig(0x80000));
+ TransLogServer tlss(testDir.getDir(), 18377, ".", fileHeaderContext, createDomainConfig(0x80000));
TransLogClient tls("tcp/localhost:18377");
createDomainTest(tls, "erase", 0);
@@ -769,7 +793,7 @@ TEST("testErase") {
}
{
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test12", 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
+ TransLogServer tlss(testDir.getDir(), 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
TransLogClient tls("tcp/localhost:18377");
auto s1 = openDomainTest(tls, "erase");
@@ -856,7 +880,8 @@ TEST("testSync") {
const unsigned int TOTAL_NUM_ENTRIES = NUM_PACKETS * NUM_ENTRIES;
DummyFileHeaderContext fileHeaderContext;
- TransLogServer tlss("test9", 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
+ test::DirectoryHandler testDir("test9");
+ TransLogServer tlss(testDir.getDir(), 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
TransLogClient tls("tcp/localhost:18377");
createDomainTest(tls, "sync", 0);
@@ -877,8 +902,9 @@ TEST("test truncate on version mismatch") {
uint64_t fromOld(0), toOld(0);
size_t countOld(0);
DummyFileHeaderContext fileHeaderContext;
+ test::DirectoryHandler testDir("test11");
{
- TransLogServer tlss("test11", 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
+ TransLogServer tlss(testDir.getDir(), 18377, ".", fileHeaderContext, createDomainConfig(0x1000000));
TransLogClient tls("tcp/localhost:18377");
createDomainTest(tls, "sync", 0);
@@ -890,7 +916,7 @@ TEST("test truncate on version mismatch") {
EXPECT_TRUE(s1->sync(2, syncedTo));
EXPECT_EQUAL(syncedTo, TOTAL_NUM_ENTRIES);
}
- FastOS_File f("test11/sync/sync-0000000000000000");
+ FastOS_File f((testDir.getDir() + "/sync/sync-0000000000000000").c_str());
EXPECT_TRUE(f.OpenWriteOnlyExisting());
EXPECT_TRUE(f.SetPosition(f.GetSize()));
@@ -899,7 +925,7 @@ TEST("test truncate on version mismatch") {
EXPECT_EQUAL(static_cast<ssize_t>(sizeof(tmp)), f.Write2(tmp, sizeof(tmp)));
EXPECT_TRUE(f.Close());
{
- TransLogServer tlss("test11", 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
+ TransLogServer tlss(testDir.getDir(), 18377, ".", fileHeaderContext, createDomainConfig(0x10000));
TransLogClient tls("tcp/localhost:18377");
auto s1 = openDomainTest(tls, "sync");
uint64_t from(0), to(0);
@@ -916,15 +942,16 @@ TEST("test truncation after short read") {
const unsigned int NUM_ENTRIES = 1;
const unsigned int TOTAL_NUM_ENTRIES = NUM_PACKETS * NUM_ENTRIES;
const unsigned int ENTRYSIZE = 4080;
- vespalib::string topdir("test10");
+ test::DirectoryHandler topdir("test10");
vespalib::string domain("truncate");
- vespalib::string dir(topdir + "/" + domain);
+ vespalib::string dir(topdir.getDir() + "/" + domain);
vespalib::string tlsspec("tcp/localhost:18377");
+
DomainConfig domainConfig = createDomainConfig(0x10000);
DummyFileHeaderContext fileHeaderContext;
{
- TransLogServer tlss(topdir, 18377, ".", fileHeaderContext, domainConfig);
+ TransLogServer tlss(topdir.getDir(), 18377, ".", fileHeaderContext, domainConfig);
TransLogClient tls(tlsspec);
createDomainTest(tls, domain, 0);
@@ -938,7 +965,7 @@ TEST("test truncation after short read") {
}
EXPECT_EQUAL(2u, countFiles(dir));
{
- TransLogServer tlss(topdir, 18377, ".", fileHeaderContext, domainConfig);
+ TransLogServer tlss(topdir.getDir(), 18377, ".", fileHeaderContext, domainConfig);
TransLogClient tls(tlsspec);
auto s1 = openDomainTest(tls, domain);
checkFilledDomainTest(*s1, TOTAL_NUM_ENTRIES);
@@ -952,7 +979,7 @@ TEST("test truncation after short read") {
trfile.Close();
}
{
- TransLogServer tlss(topdir, 18377, ".", fileHeaderContext, domainConfig);
+ TransLogServer tlss(topdir.getDir(), 18377, ".", fileHeaderContext, domainConfig);
TransLogClient tls(tlsspec);
auto s1 = openDomainTest(tls, domain);
checkFilledDomainTest(*s1, TOTAL_NUM_ENTRIES - 1);
diff --git a/searchlib/src/tests/transactionlog/translogclient_test.sh b/searchlib/src/tests/transactionlog/translogclient_test.sh
deleted file mode 100755
index 50d7c73fd6a..00000000000
--- a/searchlib/src/tests/transactionlog/translogclient_test.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-set -e
-rm -rf test7 test8 test9 test10 test11 test12 test13 testremove
-$VALGRIND ./searchlib_translogclient_test_app
-rm -rf test7 test8 test9 test10 test11 test12 test13 testremove
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
index b68923b90bf..e40717e6375 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
@@ -191,9 +191,9 @@ AttributeHeader::internalExtractTags(const vespalib::GenericHeader &header)
}
AttributeHeader
-AttributeHeader::extractTags(const vespalib::GenericHeader &header)
+AttributeHeader::extractTags(const vespalib::GenericHeader &header, const vespalib::string &file_name)
{
- AttributeHeader result;
+ AttributeHeader result(file_name);
result.internalExtractTags(header);
return result;
}
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_header.h b/searchlib/src/vespa/searchlib/attribute/attribute_header.h
index 00da28baf80..7c0b8f3084b 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_header.h
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_header.h
@@ -69,7 +69,7 @@ public:
bool getPredicateParamsSet() const { return _predicateParamsSet; }
bool getCollectionTypeParamsSet() const { return _collectionTypeParamsSet; }
const std::optional<HnswIndexParams>& get_hnsw_index_params() const { return _hnsw_index_params; }
- static AttributeHeader extractTags(const vespalib::GenericHeader &header);
+ static AttributeHeader extractTags(const vespalib::GenericHeader &header, const vespalib::string &file_name);
void addTags(vespalib::GenericHeader &header) const;
};
diff --git a/searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.cpp b/searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.cpp
index 6c929ad5981..8bc28abc238 100644
--- a/searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.cpp
@@ -311,6 +311,165 @@ EnumStoreDictionary<BTreeDictionaryT, HashDictionaryT>::normalize_posting_lists(
}
template <>
+bool
+EnumStoreDictionary<EnumTree>::normalize_posting_lists(std::function<void(std::vector<EntryRef>&)>, const EntryRefFilter&)
+{
+ LOG_ABORT("should not be reached");
+}
+
+namespace {
+
+template <typename HashDictionaryT>
+class ChangeWriterBase
+{
+protected:
+ HashDictionaryT* _hash_dict;
+ static constexpr bool has_hash_dictionary = true;
+ ChangeWriterBase()
+ : _hash_dict(nullptr)
+ {
+ }
+public:
+ void set_hash_dict(HashDictionaryT &hash_dict) { _hash_dict = &hash_dict; }
+};
+
+template <>
+class ChangeWriterBase<vespalib::datastore::NoHashDictionary>
+{
+protected:
+ static constexpr bool has_hash_dictionary = false;
+ ChangeWriterBase() = default;
+};
+
+template <typename HashDictionaryT>
+class ChangeWriter : public ChangeWriterBase<HashDictionaryT> {
+ using Parent = ChangeWriterBase<HashDictionaryT>;
+ using Parent::has_hash_dictionary;
+ std::vector<std::pair<EntryRef,uint32_t*>> _tree_refs;
+public:
+ ChangeWriter(uint32_t capacity);
+ ~ChangeWriter();
+ bool write(const std::vector<EntryRef>& refs);
+ void emplace_back(EntryRef key, uint32_t& tree_ref) { _tree_refs.emplace_back(std::make_pair(key, &tree_ref)); }
+};
+
+template <typename HashDictionaryT>
+ChangeWriter<HashDictionaryT>::ChangeWriter(uint32_t capacity)
+ : ChangeWriterBase<HashDictionaryT>(),
+ _tree_refs()
+{
+ _tree_refs.reserve(capacity);
+}
+
+template <typename HashDictionaryT>
+ChangeWriter<HashDictionaryT>::~ChangeWriter() = default;
+
+template <typename HashDictionaryT>
+bool
+ChangeWriter<HashDictionaryT>::write(const std::vector<EntryRef> &refs)
+{
+ bool changed = false;
+ assert(refs.size() == _tree_refs.size());
+ auto tree_ref = _tree_refs.begin();
+ for (auto ref : refs) {
+ EntryRef old_ref(*tree_ref->second);
+ if (ref != old_ref) {
+ if (!changed) {
+ // Note: Needs review when porting to other platforms
+ // Assumes that other CPUs observes stores from this CPU in order
+ std::atomic_thread_fence(std::memory_order_release);
+ changed = true;
+ }
+ *tree_ref->second = ref.ref();
+ if constexpr (has_hash_dictionary) {
+ auto find_result = this->_hash_dict->find(this->_hash_dict->get_default_comparator(), tree_ref->first);
+ assert(find_result != nullptr && find_result->first.load_relaxed() == tree_ref->first);
+ assert(find_result->second.load_relaxed() == old_ref);
+ find_result->second.store_release(ref);
+ }
+ }
+ ++tree_ref;
+ }
+ assert(tree_ref == _tree_refs.end());
+ _tree_refs.clear();
+ return changed;
+}
+
+}
+
+template <typename BTreeDictionaryT, typename HashDictionaryT>
+bool
+EnumStoreDictionary<BTreeDictionaryT, HashDictionaryT>::normalize_posting_lists(std::function<void(std::vector<EntryRef>&)> normalize, const EntryRefFilter& filter)
+{
+ if constexpr (has_btree_dictionary) {
+ std::vector<EntryRef> refs;
+ refs.reserve(1024);
+ bool changed = false;
+ ChangeWriter<HashDictionaryT> change_writer(refs.capacity());
+ if constexpr (has_hash_dictionary) {
+ change_writer.set_hash_dict(this->_hash_dict);
+ }
+ auto& dict = this->_btree_dict;
+ for (auto itr = dict.begin(); itr.valid(); ++itr) {
+ EntryRef ref(itr.getData());
+ if (ref.valid()) {
+ if (filter.has(ref)) {
+ refs.emplace_back(ref);
+ change_writer.emplace_back(itr.getKey(), itr.getWData());
+ if (refs.size() >= refs.capacity()) {
+ normalize(refs);
+ changed |= change_writer.write(refs);
+ refs.clear();
+ }
+ }
+ }
+ }
+ if (!refs.empty()) {
+ normalize(refs);
+ changed |= change_writer.write(refs);
+ }
+ return changed;
+ } else {
+ return this->_hash_dict.normalize_values(normalize, filter);
+ }
+}
+
+template <>
+void
+EnumStoreDictionary<EnumTree>::foreach_posting_list(std::function<void(const std::vector<EntryRef>&)>, const EntryRefFilter&)
+{
+ LOG_ABORT("should not be reached");
+}
+
+template <typename BTreeDictionaryT, typename HashDictionaryT>
+void
+EnumStoreDictionary<BTreeDictionaryT, HashDictionaryT>::foreach_posting_list(std::function<void(const std::vector<EntryRef>&)> callback, const EntryRefFilter& filter)
+{
+ if constexpr (has_btree_dictionary) {
+ std::vector<EntryRef> refs;
+ refs.reserve(1024);
+ auto& dict = this->_btree_dict;
+ for (auto itr = dict.begin(); itr.valid(); ++itr) {
+ EntryRef ref(itr.getData());
+ if (ref.valid()) {
+ if (filter.has(ref)) {
+ refs.emplace_back(ref);
+ if (refs.size() >= refs.capacity()) {
+ callback(refs);
+ refs.clear();
+ }
+ }
+ }
+ }
+ if (!refs.empty()) {
+ callback(refs);
+ }
+ } else {
+ this->_hash_dict.foreach_value(callback, filter);
+ }
+}
+
+template <>
const EnumPostingTree &
EnumStoreDictionary<EnumTree>::get_posting_dictionary() const
{
diff --git a/searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.h b/searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.h
index 4d0509c0eb1..db1176c5484 100644
--- a/searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.h
+++ b/searchlib/src/vespa/searchlib/attribute/enum_store_dictionary.h
@@ -16,6 +16,7 @@ template <typename BTreeDictionaryT, typename HashDictionaryT = vespalib::datast
class EnumStoreDictionary : public vespalib::datastore::UniqueStoreDictionary<BTreeDictionaryT, IEnumStoreDictionary, HashDictionaryT> {
protected:
using EntryRef = IEnumStoreDictionary::EntryRef;
+ using EntryRefFilter = IEnumStoreDictionary::EntryRefFilter;
using Index = IEnumStoreDictionary::Index;
using BTreeDictionaryType = BTreeDictionaryT;
using EntryComparator = IEnumStoreDictionary::EntryComparator;
@@ -54,6 +55,8 @@ public:
void clear_all_posting_lists(std::function<void(EntryRef)> clearer) override;
void update_posting_list(Index idx, const EntryComparator& cmp, std::function<EntryRef(EntryRef)> updater) override;
bool normalize_posting_lists(std::function<EntryRef(EntryRef)> normalize) override;
+ bool normalize_posting_lists(std::function<void(std::vector<EntryRef>&)> normalize, const EntryRefFilter& filter) override;
+ void foreach_posting_list(std::function<void(const std::vector<EntryRef>&)> callback, const EntryRefFilter& filter) override;
const EnumPostingTree& get_posting_dictionary() const override;
};
diff --git a/searchlib/src/vespa/searchlib/attribute/i_enum_store_dictionary.h b/searchlib/src/vespa/searchlib/attribute/i_enum_store_dictionary.h
index a8cf6881b86..a9716ec5d05 100644
--- a/searchlib/src/vespa/searchlib/attribute/i_enum_store_dictionary.h
+++ b/searchlib/src/vespa/searchlib/attribute/i_enum_store_dictionary.h
@@ -30,6 +30,7 @@ class IEnumStoreDictionary : public vespalib::datastore::IUniqueStoreDictionary
public:
using EntryRef = vespalib::datastore::EntryRef;
using EntryComparator = vespalib::datastore::EntryComparator;
+ using EntryRefFilter = vespalib::datastore::EntryRefFilter;
using EnumVector = IEnumStore::EnumVector;
using Index = IEnumStore::Index;
using IndexList = IEnumStore::IndexList;
@@ -52,7 +53,25 @@ public:
virtual Index remap_index(Index idx) = 0;
virtual void clear_all_posting_lists(std::function<void(EntryRef)> clearer) = 0;
virtual void update_posting_list(Index idx, const EntryComparator& cmp, std::function<EntryRef(EntryRef)> updater) = 0;
+ /*
+ * Scan dictionary and call normalize function for each value. If
+ * returned value is different then write back the modified value to
+ * the dictionary. Only used by unit tests.
+ */
virtual bool normalize_posting_lists(std::function<EntryRef(EntryRef)> normalize) = 0;
+ /*
+ * Scan dictionary and call normalize function for batches of values
+ * that pass the filter. Write back modified values to the dictionary.
+ * Used by compaction of posting lists when moving short arrays,
+ * bitvectors or btree roots.
+ */
+ virtual bool normalize_posting_lists(std::function<void(std::vector<EntryRef>&)> normalize, const EntryRefFilter& filter) = 0;
+ /*
+ * Scan dictionary and call callback function for batches of values
+ * that pass the filter. Used by compaction of posting lists when
+ * moving btree nodes.
+ */
+ virtual void foreach_posting_list(std::function<void(const std::vector<EntryRef>&)> callback, const EntryRefFilter& filter) = 0;
virtual const EnumPostingTree& get_posting_dictionary() const = 0;
};
diff --git a/searchlib/src/vespa/searchlib/attribute/multienumattribute.cpp b/searchlib/src/vespa/searchlib/attribute/multienumattribute.cpp
index b114a355bb4..8790bdd9885 100644
--- a/searchlib/src/vespa/searchlib/attribute/multienumattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/multienumattribute.cpp
@@ -30,13 +30,17 @@ remap_enum_store_refs(const EnumIndexRemapper& remapper, AttributeVector& v, att
v.logEnumStoreEvent("compactfixup", "drain");
{
AttributeVector::EnumModifier enum_guard(v.getEnumModifier());
+ auto& filter = remapper.get_entry_ref_filter();
v.logEnumStoreEvent("compactfixup", "start");
for (uint32_t doc = 0; doc < v.getNumDocs(); ++doc) {
vespalib::ConstArrayRef<WeightedIndex> indicesRef(multi_value_mapping.get(doc));
WeightedIndexVector indices(indicesRef.cbegin(), indicesRef.cend());
for (uint32_t i = 0; i < indices.size(); ++i) {
- EnumIndex oldIndex = indices[i].value();
- indices[i] = WeightedIndex(remapper.remap(oldIndex), indices[i].weight());
+ EnumIndex ref = indices[i].value();
+ if (ref.valid() && filter.has(ref)) {
+ ref = remapper.remap(ref);
+ }
+ indices[i] = WeightedIndex(ref, indices[i].weight());
}
std::atomic_thread_fence(std::memory_order_release);
multi_value_mapping.replace(doc, indices);
diff --git a/searchlib/src/vespa/searchlib/attribute/postingstore.cpp b/searchlib/src/vespa/searchlib/attribute/postingstore.cpp
index 3451c2b0456..8ed8a0cfbee 100644
--- a/searchlib/src/vespa/searchlib/attribute/postingstore.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/postingstore.cpp
@@ -7,11 +7,13 @@
#include <vespa/vespalib/btree/btreeiterator.hpp>
#include <vespa/vespalib/btree/btreerootbase.cpp>
#include <vespa/vespalib/datastore/datastore.hpp>
+#include <vespa/vespalib/datastore/entry_ref_filter.h>
#include <vespa/vespalib/datastore/buffer_type.hpp>
namespace search::attribute {
using vespalib::btree::BTreeNoLeafData;
+using vespalib::datastore::EntryRefFilter;
// #define FORCE_BITVECTORS
@@ -127,45 +129,47 @@ PostingStore<DataT>::removeSparseBitVectors()
}
}
if (needscan) {
- res = _dictionary.normalize_posting_lists([this](EntryRef posting_idx) -> EntryRef
- { return consider_remove_sparse_bitvector(posting_idx); });
+ EntryRefFilter filter(RefType::numBuffers(), RefType::offset_bits);
+ filter.add_buffers(_bvType.get_active_buffers());
+ res = _dictionary.normalize_posting_lists([this](std::vector<EntryRef>& refs)
+ { consider_remove_sparse_bitvector(refs); },
+ filter);
}
return res;
}
template <typename DataT>
-typename PostingStore<DataT>::EntryRef
-PostingStore<DataT>::consider_remove_sparse_bitvector(EntryRef ref)
+void
+PostingStore<DataT>::consider_remove_sparse_bitvector(std::vector<EntryRef>& refs)
{
- if (!ref.valid() || !isBitVector(getTypeId(EntryRef(ref)))) {
- return ref;
- }
- RefType iRef(ref);
- uint32_t typeId = getTypeId(iRef);
- assert(isBitVector(typeId));
- assert(_bvs.find(ref.ref() )!= _bvs.end());
- BitVectorEntry *bve = getWBitVectorEntry(iRef);
- BitVector &bv = *bve->_bv.get();
- uint32_t docFreq = bv.countTrueBits();
- if (bve->_tree.valid()) {
- RefType iRef2(bve->_tree);
- assert(isBTree(iRef2));
- const BTreeType *tree = getTreeEntry(iRef2);
- assert(tree->size(_allocator) == docFreq);
- (void) tree;
- }
- if (docFreq < _minBvDocFreq) {
- dropBitVector(ref);
- if (ref.valid()) {
+ for (auto& ref : refs) {
+ RefType iRef(ref);
+ assert(iRef.valid());
+ uint32_t typeId = getTypeId(iRef);
+ assert(isBitVector(typeId));
+ assert(_bvs.find(iRef.ref()) != _bvs.end());
+ BitVectorEntry *bve = getWBitVectorEntry(iRef);
+ BitVector &bv = *bve->_bv.get();
+ uint32_t docFreq = bv.countTrueBits();
+ if (bve->_tree.valid()) {
+ RefType iRef2(bve->_tree);
+ assert(isBTree(iRef2));
+ const BTreeType *tree = getTreeEntry(iRef2);
+ assert(tree->size(_allocator) == docFreq);
+ (void) tree;
+ }
+ if (docFreq < _minBvDocFreq) {
+ dropBitVector(ref);
iRef = ref;
- typeId = getTypeId(iRef);
- if (isBTree(typeId)) {
- BTreeType *tree = getWTreeEntry(iRef);
- normalizeTree(ref, tree, false);
+ if (iRef.valid()) {
+ typeId = getTypeId(iRef);
+ if (isBTree(typeId)) {
+ BTreeType *tree = getWTreeEntry(iRef);
+ normalizeTree(ref, tree, false);
+ }
}
}
}
- return ref;
}
template <typename DataT>
@@ -647,74 +651,75 @@ PostingStore<DataT>::update_stat()
template <typename DataT>
void
-PostingStore<DataT>::move_btree_nodes(EntryRef ref)
+PostingStore<DataT>::move_btree_nodes(const std::vector<EntryRef>& refs)
{
- if (ref.valid()) {
+ for (auto ref : refs) {
RefType iRef(ref);
+ assert(iRef.valid());
uint32_t typeId = getTypeId(iRef);
uint32_t clusterSize = getClusterSize(typeId);
- if (clusterSize == 0) {
- if (isBitVector(typeId)) {
- BitVectorEntry *bve = getWBitVectorEntry(iRef);
- RefType iRef2(bve->_tree);
- if (iRef2.valid()) {
- assert(isBTree(iRef2));
- BTreeType *tree = getWTreeEntry(iRef2);
- tree->move_nodes(_allocator);
- }
- } else {
- BTreeType *tree = getWTreeEntry(iRef);
+ assert(clusterSize == 0);
+ if (isBitVector(typeId)) {
+ BitVectorEntry *bve = getWBitVectorEntry(iRef);
+ RefType iRef2(bve->_tree);
+ if (iRef2.valid()) {
+ assert(isBTree(iRef2));
+ BTreeType *tree = getWTreeEntry(iRef2);
tree->move_nodes(_allocator);
}
+ } else {
+ assert(isBTree(typeId));
+ BTreeType *tree = getWTreeEntry(iRef);
+ tree->move_nodes(_allocator);
}
}
}
template <typename DataT>
-typename PostingStore<DataT>::EntryRef
-PostingStore<DataT>::move(EntryRef ref)
+void
+PostingStore<DataT>::move(std::vector<EntryRef>& refs)
{
- if (!ref.valid()) {
- return EntryRef();
- }
- RefType iRef(ref);
- uint32_t typeId = getTypeId(iRef);
- uint32_t clusterSize = getClusterSize(typeId);
- if (clusterSize == 0) {
- if (isBitVector(typeId)) {
- BitVectorEntry *bve = getWBitVectorEntry(iRef);
- RefType iRef2(bve->_tree);
- if (iRef2.valid()) {
- assert(isBTree(iRef2));
- if (_store.getCompacting(iRef2)) {
- BTreeType *tree = getWTreeEntry(iRef2);
- auto ref_and_ptr = allocBTreeCopy(*tree);
- tree->prepare_hold();
- bve->_tree = ref_and_ptr.ref;
+ for (auto& ref : refs) {
+ RefType iRef(ref);
+ assert(iRef.valid());
+ uint32_t typeId = getTypeId(iRef);
+ uint32_t clusterSize = getClusterSize(typeId);
+ if (clusterSize == 0) {
+ if (isBitVector(typeId)) {
+ BitVectorEntry *bve = getWBitVectorEntry(iRef);
+ RefType iRef2(bve->_tree);
+ if (iRef2.valid()) {
+ assert(isBTree(iRef2));
+ if (_store.getCompacting(iRef2)) {
+ BTreeType *tree = getWTreeEntry(iRef2);
+ auto ref_and_ptr = allocBTreeCopy(*tree);
+ tree->prepare_hold();
+ // Note: Needs review when porting to other platforms
+ // Assumes that other CPUs observes stores from this CPU in order
+ std::atomic_thread_fence(std::memory_order_release);
+ bve->_tree = ref_and_ptr.ref;
+ }
}
+ if (_store.getCompacting(iRef)) {
+ auto new_ref = allocBitVectorCopy(*bve).ref;
+ _bvs.erase(iRef.ref());
+ _bvs.insert(new_ref.ref());
+ ref = new_ref;
+ }
+ } else {
+ assert(isBTree(typeId));
+ assert(_store.getCompacting(iRef));
+ BTreeType *tree = getWTreeEntry(iRef);
+ auto ref_and_ptr = allocBTreeCopy(*tree);
+ tree->prepare_hold();
+ ref = ref_and_ptr.ref;
}
- if (!_store.getCompacting(ref)) {
- return ref;
- }
- auto new_ref = allocBitVectorCopy(*bve).ref;
- _bvs.erase(ref.ref());
- _bvs.insert(new_ref.ref());
- return new_ref;
} else {
- if (!_store.getCompacting(ref)) {
- return ref;
- }
- BTreeType *tree = getWTreeEntry(iRef);
- auto ref_and_ptr = allocBTreeCopy(*tree);
- tree->prepare_hold();
- return ref_and_ptr.ref;
+ assert(_store.getCompacting(iRef));
+ const KeyDataType *shortArray = getKeyDataEntry(iRef, clusterSize);
+ ref = allocKeyDataCopy(shortArray, clusterSize).ref;
}
}
- if (!_store.getCompacting(ref)) {
- return ref;
- }
- const KeyDataType *shortArray = getKeyDataEntry(iRef, clusterSize);
- return allocKeyDataCopy(shortArray, clusterSize).ref;
}
template <typename DataT>
@@ -722,11 +727,12 @@ void
PostingStore<DataT>::compact_worst_btree_nodes()
{
auto to_hold = this->start_compact_worst_btree_nodes();
- _dictionary.normalize_posting_lists([this](EntryRef posting_idx) -> EntryRef
- {
- move_btree_nodes(posting_idx);
- return posting_idx;
- });
+ EntryRefFilter filter(RefType::numBuffers(), RefType::offset_bits);
+ // Only look at buffers containing bitvectors and btree roots
+ filter.add_buffers(this->_treeType.get_active_buffers());
+ filter.add_buffers(_bvType.get_active_buffers());
+ _dictionary.foreach_posting_list([this](const std::vector<EntryRef>& refs)
+ { move_btree_nodes(refs); }, filter);
this->finish_compact_worst_btree_nodes(to_hold);
}
@@ -735,8 +741,23 @@ void
PostingStore<DataT>::compact_worst_buffers()
{
auto to_hold = this->start_compact_worst_buffers();
- _dictionary.normalize_posting_lists([this](EntryRef posting_idx) -> EntryRef
- { return move(posting_idx); });
+ bool compact_btree_roots = false;
+ EntryRefFilter filter(RefType::numBuffers(), RefType::offset_bits);
+ filter.add_buffers(to_hold);
+ // Start with looking at buffers being compacted
+ for (uint32_t buffer_id : to_hold) {
+ if (isBTree(_store.getBufferState(buffer_id).getTypeId())) {
+ compact_btree_roots = true;
+ }
+ }
+ if (compact_btree_roots) {
+ // If we are compacting btree roots then we also have to look at bitvector
+ // buffers
+ filter.add_buffers(_bvType.get_active_buffers());
+ }
+ _dictionary.normalize_posting_lists([this](std::vector<EntryRef>& refs)
+ { return move(refs); },
+ filter);
this->finishCompact(to_hold);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/postingstore.h b/searchlib/src/vespa/searchlib/attribute/postingstore.h
index a0f0be1c430..2b119a55158 100644
--- a/searchlib/src/vespa/searchlib/attribute/postingstore.h
+++ b/searchlib/src/vespa/searchlib/attribute/postingstore.h
@@ -89,6 +89,7 @@ public:
using Parent::getWTreeEntry;
using Parent::getTreeEntry;
using Parent::getKeyDataEntry;
+ using Parent::isBTree;
using Parent::clusterLimit;
using Parent::allocBTree;
using Parent::allocBTreeCopy;
@@ -105,10 +106,8 @@ public:
~PostingStore();
bool removeSparseBitVectors() override;
- EntryRef consider_remove_sparse_bitvector(EntryRef ref);
+ void consider_remove_sparse_bitvector(std::vector<EntryRef> &refs);
static bool isBitVector(uint32_t typeId) { return typeId == BUFFERTYPE_BITVECTOR; }
- static bool isBTree(uint32_t typeId) { return typeId == BUFFERTYPE_BTREE; }
- bool isBTree(RefType ref) const { return isBTree(getTypeId(ref)); }
void applyNew(EntryRef &ref, AddIter a, AddIter ae);
@@ -188,8 +187,8 @@ public:
vespalib::MemoryUsage getMemoryUsage() const;
vespalib::MemoryUsage update_stat();
- void move_btree_nodes(EntryRef ref);
- EntryRef move(EntryRef ref);
+ void move_btree_nodes(const std::vector<EntryRef> &refs);
+ void move(std::vector<EntryRef>& refs);
void compact_worst_btree_nodes();
void compact_worst_buffers();
diff --git a/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp b/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
index d9024af724b..6268a6da701 100644
--- a/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
@@ -194,7 +194,7 @@ PredicateAttribute::onLoad(vespalib::Executor *)
buffer.moveFreeToData(size);
const GenericHeader &header = loaded_buffer->getHeader();
- auto attributeHeader = attribute::AttributeHeader::extractTags(header);
+ auto attributeHeader = attribute::AttributeHeader::extractTags(header, getBaseFileName());
uint32_t version = attributeHeader.getVersion();
setCreateSerialNum(attributeHeader.getCreateSerialNum());
diff --git a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.cpp b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.cpp
index 4323e57f6b1..18805a7b20f 100644
--- a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.cpp
@@ -49,13 +49,16 @@ SingleValueEnumAttributeBase::remap_enum_store_refs(const EnumIndexRemapper& rem
{
// update _enumIndices with new EnumIndex values after enum store has been compacted.
v.logEnumStoreEvent("reenumerate", "reserved");
- auto new_indexes = std::make_unique<vespalib::Array<EnumIndex>>();
- new_indexes->reserve(_enumIndices.capacity());
+ vespalib::Array<EnumIndex> new_indexes;
+ new_indexes.reserve(_enumIndices.capacity());
v.logEnumStoreEvent("reenumerate", "start");
+ auto& filter = remapper.get_entry_ref_filter();
for (uint32_t i = 0; i < _enumIndices.size(); ++i) {
- EnumIndex old_index = _enumIndices[i];
- EnumIndex new_index = remapper.remap(old_index);
- new_indexes->push_back_fast(new_index);
+ EnumIndex ref = _enumIndices[i];
+ if (ref.valid() && filter.has(ref)) {
+ ref = remapper.remap(ref);
+ }
+ new_indexes.push_back_fast(ref);
}
v.logEnumStoreEvent("compactfixup", "drain");
{
diff --git a/searchlib/src/vespa/searchlib/docstore/compacter.cpp b/searchlib/src/vespa/searchlib/docstore/compacter.cpp
index 38f3fbef0b0..26fb79f8a4e 100644
--- a/searchlib/src/vespa/searchlib/docstore/compacter.cpp
+++ b/searchlib/src/vespa/searchlib/docstore/compacter.cpp
@@ -26,7 +26,7 @@ BucketCompacter::BucketCompacter(size_t maxSignificantBucketBits, const Compress
_bucketizer(bucketizer),
_writeCount(0),
_maxBucketGuardDuration(vespalib::duration::zero()),
- _lastSample(),
+ _lastSample(vespalib::steady_clock::now()),
_lock(),
_backingMemory(Alloc::alloc(0x40000000), &_lock),
_tmpStore(),
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
index 0f4326aac40..5217c44df97 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
@@ -75,7 +75,7 @@ BlobSequenceReader::BlobSequenceReader(AttributeVector& attr, bool has_index)
: ReaderBase(attr),
_use_index_file(has_index && has_index_file(attr) &&
can_use_index_save_file(attr.getConfig(),
- search::attribute::AttributeHeader::extractTags(getDatHeader()))),
+ search::attribute::AttributeHeader::extractTags(getDatHeader(), attr.getBaseFileName()))),
_index_file(_use_index_file ?
attribute::LoadUtils::openFile(attr, DenseTensorAttributeSaver::index_file_suffix()) :
std::unique_ptr<Fast_BufferedFile>())
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
index d3c2998333a..86090f2ac92 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
@@ -79,12 +79,6 @@ DenseTensorStore::~DenseTensorStore()
_store.dropBuffers();
}
-const void *
-DenseTensorStore::getRawBuffer(RefType ref) const
-{
- return _store.getEntryArray<char>(ref, _bufferType.getArraySize());
-}
-
namespace {
void clearPadAreaAfterBuffer(char *buffer, size_t bufSize, size_t alignedBufSize) {
@@ -136,15 +130,6 @@ DenseTensorStore::getTensor(EntryRef ref) const
return std::make_unique<vespalib::eval::DenseValueView>(_type, cells_ref);
}
-vespalib::eval::TypedCells
-DenseTensorStore::get_typed_cells(EntryRef ref) const
-{
- if (!ref.valid()) {
- return vespalib::eval::TypedCells(&_emptySpace[0], _type.cell_type(), getNumCells());
- }
- return vespalib::eval::TypedCells(getRawBuffer(ref), _type.cell_type(), getNumCells());
-}
-
template <class TensorType>
TensorStore::EntryRef
DenseTensorStore::setDenseTensor(const TensorType &tensor)
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
index 3b7cb71863e..06492596f70 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
@@ -50,12 +50,9 @@ private:
ValueType _type; // type of dense tensor
std::vector<char> _emptySpace;
- size_t unboundCells(const void *buffer) const;
-
template <class TensorType>
TensorStore::EntryRef
setDenseTensor(const TensorType &tensor);
-
public:
DenseTensorStore(const ValueType &type, std::unique_ptr<vespalib::alloc::MemoryAllocator> allocator);
~DenseTensorStore() override;
@@ -63,12 +60,17 @@ public:
const ValueType &type() const { return _type; }
size_t getNumCells() const { return _tensorSizeCalc._numCells; }
size_t getBufSize() const { return _tensorSizeCalc.bufSize(); }
- const void *getRawBuffer(RefType ref) const;
+ const void *getRawBuffer(RefType ref) const {
+ return _store.getEntryArray<char>(ref, _bufferType.getArraySize());
+ }
vespalib::datastore::Handle<char> allocRawBuffer();
void holdTensor(EntryRef ref) override;
EntryRef move(EntryRef ref) override;
std::unique_ptr<vespalib::eval::Value> getTensor(EntryRef ref) const;
- vespalib::eval::TypedCells get_typed_cells(EntryRef ref) const;
+ vespalib::eval::TypedCells get_typed_cells(EntryRef ref) const {
+ return vespalib::eval::TypedCells(ref.valid() ? getRawBuffer(ref) : &_emptySpace[0],
+ _type.cell_type(), getNumCells());
+ }
EntryRef setTensor(const vespalib::eval::Value &tensor);
// The following method is meant to be used only for unit tests.
uint32_t getArraySize() const { return _bufferType.getArraySize(); }
diff --git a/searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp b/searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp
index 7f9f20e07c4..43596478a6f 100644
--- a/searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp
@@ -43,4 +43,13 @@ HammingDistance::calc(const vespalib::eval::TypedCells& lhs,
}
}
+double
+HammingDistance::calc_with_limit(const vespalib::eval::TypedCells& lhs,
+ const vespalib::eval::TypedCells& rhs,
+ double) const
+{
+ // consider optimizing:
+ return calc(lhs, rhs);
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/hamming_distance.h b/searchlib/src/vespa/searchlib/tensor/hamming_distance.h
index f0b7b159b90..c64fc5b532d 100644
--- a/searchlib/src/vespa/searchlib/tensor/hamming_distance.h
+++ b/searchlib/src/vespa/searchlib/tensor/hamming_distance.h
@@ -15,7 +15,7 @@ namespace search::tensor {
* or (for int8 cells, aka binary data only)
* "number of bits that are different"
*/
-class HammingDistance : public DistanceFunction {
+class HammingDistance final : public DistanceFunction {
public:
HammingDistance(vespalib::eval::CellType expected) : DistanceFunction(expected) {}
double calc(const vespalib::eval::TypedCells& lhs, const vespalib::eval::TypedCells& rhs) const override;
@@ -26,13 +26,7 @@ public:
double score = 1.0 / (1.0 + distance);
return score;
}
- double calc_with_limit(const vespalib::eval::TypedCells& lhs,
- const vespalib::eval::TypedCells& rhs,
- double) const override
- {
- // consider optimizing:
- return calc(lhs, rhs);
- }
+ double calc_with_limit(const vespalib::eval::TypedCells& lhs, const vespalib::eval::TypedCells& rhs, double) const override;
};
}
diff --git a/searchlib/src/vespa/searchlib/transactionlog/domain.cpp b/searchlib/src/vespa/searchlib/transactionlog/domain.cpp
index aa49a2d2954..7fa87f3ec09 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/domain.cpp
+++ b/searchlib/src/vespa/searchlib/transactionlog/domain.cpp
@@ -56,11 +56,13 @@ Domain::Domain(const string &domainName, const string & baseDir, Executor & exec
_fileHeaderContext(fileHeaderContext),
_markedDeleted(false)
{
- int retval(0);
- if ((retval = makeDirectory(_baseDir.c_str())) != 0) {
+ assert(_config.getEncoding().getCompression() != Encoding::Compression::none);
+ int retval = makeDirectory(_baseDir.c_str());
+ if (retval != 0) {
throw runtime_error(fmt("Failed creating basedirectory %s r(%d), e(%d)", _baseDir.c_str(), retval, errno));
}
- if ((retval = makeDirectory(dir().c_str())) != 0) {
+ retval = makeDirectory(dir().c_str());
+ if (retval != 0) {
throw runtime_error(fmt("Failed creating domaindir %s r(%d), e(%d)", dir().c_str(), retval, errno));
}
SerialNumList partIdVector = scanDir();
@@ -76,8 +78,7 @@ Domain::Domain(const string &domainName, const string & baseDir, Executor & exec
}
pending.waitForZeroRefCount();
if (_parts.empty() || _parts.crbegin()->second->isClosed()) {
- _parts[lastPart] = std::make_shared<DomainPart>(_name, dir(), lastPart, _config.getEncoding(),
- _config.getCompressionlevel(), _fileHeaderContext, false);
+ _parts[lastPart] = std::make_shared<DomainPart>(_name, dir(), lastPart, _fileHeaderContext, false);
vespalib::File::sync(dir());
}
_lastSerial = end();
@@ -86,13 +87,13 @@ Domain::Domain(const string &domainName, const string & baseDir, Executor & exec
Domain &
Domain::setConfig(const DomainConfig & cfg) {
_config = cfg;
+ assert(_config.getEncoding().getCompression() != Encoding::Compression::none);
return *this;
}
void
Domain::addPart(SerialNum partId, bool isLastPart) {
- auto dp = std::make_shared<DomainPart>(_name, dir(), partId, _config.getEncoding(),
- _config.getCompressionlevel(), _fileHeaderContext, isLastPart);
+ auto dp = std::make_shared<DomainPart>(_name, dir(), partId, _fileHeaderContext, isLastPart);
if (dp->size() == 0) {
// Only last domain part is allowed to be truncated down to
// empty size.
@@ -331,8 +332,7 @@ Domain::optionallyRotateFile(SerialNum serialNum) {
triggerSyncNow({});
waitPendingSync(_syncMonitor, _syncCond, _pendingSync);
dp->close();
- dp = std::make_shared<DomainPart>(_name, dir(), serialNum, _config.getEncoding(),
- _config.getCompressionlevel(), _fileHeaderContext, false);
+ dp = std::make_shared<DomainPart>(_name, dir(), serialNum, _fileHeaderContext, false);
{
std::lock_guard guard(_lock);
_parts[serialNum] = dp;
@@ -399,17 +399,16 @@ Domain::commitChunk(std::unique_ptr<CommitChunk> chunk, const UniqueLock & chunk
}));
}
+
+
void
Domain::doCommit(std::unique_ptr<CommitChunk> chunk) {
const Packet & packet = chunk->getPacket();
if (packet.empty()) return;
-
- vespalib::nbostream_longlivedbuf is(packet.getHandle().data(), packet.getHandle().size());
- Packet::Entry entry;
- entry.deserialize(is);
- assert(entry.serial() == packet.range().from());
- DomainPart::SP dp = optionallyRotateFile(entry.serial());
- dp->commit(entry.serial(), packet);
+
+ SerializedChunk serialized(packet, _config.getEncoding(), _config.getCompressionlevel());
+ DomainPart::SP dp = optionallyRotateFile(packet.range().from());
+ dp->commit(serialized);
if (_config.getFSyncOnCommit()) {
dp->sync();
}
diff --git a/searchlib/src/vespa/searchlib/transactionlog/domainpart.cpp b/searchlib/src/vespa/searchlib/transactionlog/domainpart.cpp
index 3dad67df177..2ca2f15545d 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/domainpart.cpp
+++ b/searchlib/src/vespa/searchlib/transactionlog/domainpart.cpp
@@ -247,11 +247,9 @@ DomainPart::buildPacketMapping(bool allowTruncate)
return currPos;
}
-DomainPart::DomainPart(const string & name, const string & baseDir, SerialNum s, Encoding encoding,
- uint8_t compressionLevel, const FileHeaderContext &fileHeaderContext, bool allowTruncate)
- : _encoding(encoding),
- _compressionLevel(compressionLevel),
- _lock(),
+DomainPart::DomainPart(const string & name, const string & baseDir, SerialNum s,
+ const FileHeaderContext &fileHeaderContext, bool allowTruncate)
+ : _lock(),
_fileLock(),
_range(s),
_sz(0),
@@ -379,35 +377,21 @@ DomainPart::erase(SerialNum to)
}
void
-DomainPart::commit(SerialNum firstSerial, const Packet &packet)
+DomainPart::commit(const SerializedChunk & serialized)
{
+ SerialNumRange range = serialized.range();
+
int64_t firstPos(byteSize());
- nbostream_longlivedbuf h(packet.getHandle().data(), packet.getHandle().size());
+ assert(_range.to() < range.to());
+ _sz += serialized.getNumEntries();
+ _range.to(range.to());
if (_range.from() == 0) {
- _range.from(firstSerial);
- }
- IChunk::UP chunk = IChunk::create(_encoding, _compressionLevel);
- for (size_t i(0); h.size() > 0; i++) {
- //LOG(spam,
- //"Pos(%d) Len(%d), Lim(%d), Remaining(%d)",
- //h.getPos(), h.getLength(), h.getLimit(), h.getRemaining());
- Packet::Entry entry;
- entry.deserialize(h);
- if (_range.to() < entry.serial()) {
- chunk->add(entry);
- assert(_encoding.getCompression() != Encoding::Compression::none);
- _sz++;
- _range.to(entry.serial());
- } else {
- throw runtime_error(fmt("Incoming serial number(%" PRIu64 ") must be bigger than the last one (%" PRIu64 ").",
- entry.serial(), _range.to()));
- }
- }
- if ( ! chunk->getEntries().empty()) {
- write(*_transLog, *chunk);
+ _range.from(range.from());
}
+
+ write(*_transLog, range, serialized.getData());
std::lock_guard guard(_lock);
- _skipList.emplace_back(firstSerial, firstPos);
+ _skipList.emplace_back(range.from(), firstPos);
}
void
@@ -442,26 +426,15 @@ DomainPart::visit(FastOS_FileInterface &file, SerialNumRange &r, Packet &packet)
}
void
-DomainPart::write(FastOS_FileInterface &file, const IChunk & chunk)
+DomainPart::write(FastOS_FileInterface &file, SerialNumRange range, vespalib::ConstBufferRef buf)
{
- nbostream os;
- size_t begin = os.wp();
- os << _encoding.getRaw(); // Placeholder for encoding
- os << uint32_t(0); // Placeholder for size
- Encoding realEncoding = chunk.encode(os);
- size_t end = os.wp();
- os.wp(0);
- os << realEncoding.getRaw(); //Patching real encoding
- os << uint32_t(end - (begin + sizeof(uint32_t) + sizeof(uint8_t))); // Patching actual size.
- os.wp(end);
std::lock_guard guard(_writeLock);
- if ( ! file.CheckedWrite(os.data(), os.size()) ) {
- throw runtime_error(handleWriteError("Failed writing the entry.", file, byteSize(), chunk.range(), os.size()));
+ if ( ! file.CheckedWrite(buf.data(), buf.size()) ) {
+ throw runtime_error(handleWriteError("Failed writing the entry.", file, byteSize(), range, buf.size()));
}
- LOG(debug, "Wrote chunk with %zu entries and %zu bytes, range[%" PRIu64 ", %" PRIu64 "] encoding(wanted=%x, real=%x)",
- chunk.getEntries().size(), os.size(), chunk.range().from(), chunk.range().to(), _encoding.getRaw(), realEncoding.getRaw());
- _writtenSerial = chunk.range().to();
- _byteSize.fetch_add(os.size(), std::memory_order_release);
+ LOG(debug, "Wrote chunk with and %zu bytes, range[%" PRIu64 ", %" PRIu64 "]", buf.size(), range.from(), range.to());
+ _writtenSerial = range.to();
+ _byteSize.fetch_add(buf.size(), std::memory_order_release);
}
bool
diff --git a/searchlib/src/vespa/searchlib/transactionlog/domainpart.h b/searchlib/src/vespa/searchlib/transactionlog/domainpart.h
index 9ab0db54391..ea5290c433b 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/domainpart.h
+++ b/searchlib/src/vespa/searchlib/transactionlog/domainpart.h
@@ -19,13 +19,13 @@ public:
using SP = std::shared_ptr<DomainPart>;
DomainPart(const DomainPart &) = delete;
DomainPart& operator=(const DomainPart &) = delete;
- DomainPart(const vespalib::string &name, const vespalib::string &baseDir, SerialNum s, Encoding defaultEncoding,
- uint8_t compressionLevel, const common::FileHeaderContext &FileHeaderContext, bool allowTruncate);
+ DomainPart(const vespalib::string &name, const vespalib::string &baseDir, SerialNum s,
+ const common::FileHeaderContext &FileHeaderContext, bool allowTruncate);
~DomainPart();
const vespalib::string &fileName() const { return _fileName; }
- void commit(SerialNum firstSerial, const Packet &packet);
+ void commit(const SerializedChunk & serialized);
bool erase(SerialNum to);
bool visit(FastOS_FileInterface &file, SerialNumRange &r, Packet &packet);
bool close();
@@ -49,7 +49,7 @@ private:
static Packet readPacket(FastOS_FileInterface & file, SerialNumRange wanted, size_t targetSize, bool allowTruncate);
static bool read(FastOS_FileInterface &file, IChunk::UP & chunk, Alloc &buf, bool allowTruncate);
- void write(FastOS_FileInterface &file, const IChunk & entry);
+ void write(FastOS_FileInterface &file, SerialNumRange range, vespalib::ConstBufferRef buf);
void writeHeader(const common::FileHeaderContext &fileHeaderContext);
class SkipInfo
@@ -69,8 +69,6 @@ private:
SerialNum _id;
uint64_t _pos;
};
- const Encoding _encoding;
- const uint8_t _compressionLevel;
std::mutex _lock;
std::mutex _fileLock;
SerialNumRange _range;
diff --git a/searchlib/src/vespa/searchlib/transactionlog/ichunk.cpp b/searchlib/src/vespa/searchlib/transactionlog/ichunk.cpp
index ee1631ea8c2..e3d98cd576d 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/ichunk.cpp
+++ b/searchlib/src/vespa/searchlib/transactionlog/ichunk.cpp
@@ -8,6 +8,9 @@
#include <cassert>
#include <ostream>
+#include <vespa/log/log.h>
+LOG_SETUP(".searchlib.transactionlog.ichunk");
+
using std::make_unique;
using vespalib::make_string_short::fmt;
using vespalib::nbostream_longlivedbuf;
@@ -115,4 +118,46 @@ std::ostream &
operator << (std::ostream & os, Encoding e) {
return os << "crc=" << e.getCrc() << " compression=" << e.getCompression();
}
+
+void
+encode(vespalib::nbostream & os, const IChunk & chunk, Encoding encoding) {
+ size_t begin = os.wp();
+ os << encoding.getRaw(); // Placeholder for encoding
+ os << uint32_t(0); // Placeholder for size
+ Encoding realEncoding = chunk.encode(os);
+ size_t end = os.wp();
+ os.wp(0);
+ os << realEncoding.getRaw(); //Patching real encoding
+ os << uint32_t(end - (begin + sizeof(uint32_t) + sizeof(uint8_t))); // Patching actual size.
+ os.wp(end);
+ SerialNumRange range = chunk.range();
+ LOG(spam, "Encoded chunk with %zu entries and %zu bytes, range[%" PRIu64 ", %" PRIu64 "] encoding(wanted=%x, real=%x)",
+ chunk.getEntries().size(), os.size(), range.from(), range.to(), encoding.getRaw(), realEncoding.getRaw());
+}
+
+SerializedChunk::SerializedChunk(const Packet & packet, Encoding encoding, uint8_t compressionLevel)
+ : _os(),
+ _range(packet.range()),
+ _numEntries(packet.size())
+{
+ nbostream_longlivedbuf h(packet.getHandle().data(), packet.getHandle().size());
+
+ IChunk::UP chunk = IChunk::create(encoding, compressionLevel);
+ SerialNum prev = 0;
+ for (size_t i(0); h.size() > 0; i++) {
+ //LOG(spam,
+ //"Pos(%d) Len(%d), Lim(%d), Remaining(%d)",
+ //h.getPos(), h.getLength(), h.getLimit(), h.getRemaining());
+ Packet::Entry entry;
+ entry.deserialize(h);
+ assert (prev < entry.serial());
+ chunk->add(entry);
+ prev = entry.serial();
+ }
+ assert(! chunk->getEntries().empty());
+ encode(_os, *chunk, encoding);
+}
+vespalib::ConstBufferRef SerializedChunk::getData() const {
+ return vespalib::ConstBufferRef(_os.data(), _os.size());
+}
}
diff --git a/searchlib/src/vespa/searchlib/transactionlog/ichunk.h b/searchlib/src/vespa/searchlib/transactionlog/ichunk.h
index 02bd0ce9426..dccfd6617f5 100644
--- a/searchlib/src/vespa/searchlib/transactionlog/ichunk.h
+++ b/searchlib/src/vespa/searchlib/transactionlog/ichunk.h
@@ -33,6 +33,22 @@ private:
std::ostream & operator << (std::ostream & os, Encoding e);
/**
+ * Represents a completely encoded chunk with a buffer ready to be persisted,
+ * and the range and number of entries it covers.
+ */
+class SerializedChunk {
+public:
+ SerializedChunk(const Packet & packet, Encoding encoding, uint8_t compressionLevel);
+ vespalib::ConstBufferRef getData() const;
+ SerialNumRange range() const { return _range; }
+ size_t getNumEntries() const { return _numEntries; }
+private:
+ vespalib::nbostream _os;
+ SerialNumRange _range;
+ size_t _numEntries;
+};
+
+/**
* Interface for different chunk formats.
* Format specifies both crc type, and compression type.
*/
diff --git a/staging_vespalib/src/tests/singleexecutor/singleexecutor_test.cpp b/staging_vespalib/src/tests/singleexecutor/singleexecutor_test.cpp
index 732ab122546..dd71380f64a 100644
--- a/staging_vespalib/src/tests/singleexecutor/singleexecutor_test.cpp
+++ b/staging_vespalib/src/tests/singleexecutor/singleexecutor_test.cpp
@@ -35,13 +35,18 @@ void verifyResizeTaskLimit(bool up) {
std::condition_variable cond;
std::atomic<uint64_t> started(0);
std::atomic<uint64_t> allowed(0);
- SingleExecutor executor(sequenced_executor, 10);
+ constexpr uint32_t INITIAL = 20;
+ const uint32_t INITIAL_2inN = roundUp2inN(INITIAL);
+ double waterMarkRatio = 0.5;
+ SingleExecutor executor(sequenced_executor, INITIAL, INITIAL*waterMarkRatio, 10ms);
+ EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit());
+ EXPECT_EQUAL(uint32_t(INITIAL_2inN*waterMarkRatio), executor.get_watermark());
- uint32_t targetTaskLimit = up ? 20 : 5;
+ uint32_t targetTaskLimit = up ? 40 : 5;
uint32_t roundedTaskLimit = roundUp2inN(targetTaskLimit);
- EXPECT_NOT_EQUAL(16u, roundedTaskLimit);
+ EXPECT_NOT_EQUAL(INITIAL_2inN, roundedTaskLimit);
- for (uint64_t i(0); i < 10; i++) {
+ for (uint64_t i(0); i < INITIAL; i++) {
executor.execute(makeLambdaTask([&lock, &cond, &started, &allowed] {
started++;
std::unique_lock guard(lock);
@@ -53,15 +58,16 @@ void verifyResizeTaskLimit(bool up) {
while (started < 1);
EXPECT_EQUAL(1u, started);
executor.setTaskLimit(targetTaskLimit);
- EXPECT_EQUAL(16u, executor.getTaskLimit());
+ EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit());
+ EXPECT_EQUAL(INITIAL_2inN*waterMarkRatio, executor.get_watermark());
allowed = 5;
while (started < 6);
EXPECT_EQUAL(6u, started);
- EXPECT_EQUAL(16u, executor.getTaskLimit());
- allowed = 10;
- while (started < 10);
- EXPECT_EQUAL(10u, started);
- EXPECT_EQUAL(16u, executor.getTaskLimit());
+ EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit());
+ allowed = INITIAL;
+ while (started < INITIAL);
+ EXPECT_EQUAL(INITIAL, started);
+ EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit());
executor.execute(makeLambdaTask([&lock, &cond, &started, &allowed] {
started++;
std::unique_lock guard(lock);
@@ -69,11 +75,13 @@ void verifyResizeTaskLimit(bool up) {
cond.wait_for(guard, 1ms);
}
}));
- while (started < 11);
- EXPECT_EQUAL(11u, started);
+ while (started < INITIAL + 1);
+ EXPECT_EQUAL(INITIAL + 1, started);
EXPECT_EQUAL(roundedTaskLimit, executor.getTaskLimit());
- allowed = 11;
+ EXPECT_EQUAL(roundedTaskLimit*waterMarkRatio, executor.get_watermark());
+ allowed = INITIAL + 1;
}
+
TEST("test that resizing up and down works") {
TEST_DO(verifyResizeTaskLimit(true));
TEST_DO(verifyResizeTaskLimit(false));
diff --git a/staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp b/staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp
index b25bc1a6377..99791c36a9e 100644
--- a/staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp
+++ b/staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp
@@ -11,7 +11,8 @@ SingleExecutor::SingleExecutor(init_fun_t func, uint32_t taskLimit)
{ }
SingleExecutor::SingleExecutor(init_fun_t func, uint32_t taskLimit, uint32_t watermark, duration reactionTime)
- : _taskLimit(vespalib::roundUp2inN(taskLimit)),
+ : _watermarkRatio(watermark < taskLimit ? double(watermark) / taskLimit : 1.0),
+ _taskLimit(vespalib::roundUp2inN(taskLimit)),
_wantedTaskLimit(_taskLimit.load()),
_rp(0),
_tasks(std::make_unique<Task::UP[]>(_taskLimit)),
@@ -27,7 +28,7 @@ SingleExecutor::SingleExecutor(init_fun_t func, uint32_t taskLimit, uint32_t wat
_wakeupConsumerAt(0),
_producerNeedWakeupAt(0),
_wp(0),
- _watermark(std::min(_taskLimit.load(), watermark)),
+ _watermark(_taskLimit.load()*_watermarkRatio),
_reactionTime(reactionTime),
_closed(false)
{
@@ -75,7 +76,7 @@ SingleExecutor::execute(Task::UP task) {
void
SingleExecutor::setTaskLimit(uint32_t taskLimit) {
- _wantedTaskLimit = std::max(vespalib::roundUp2inN(taskLimit), size_t(_watermark));
+ _wantedTaskLimit = vespalib::roundUp2inN(taskLimit);
}
void
@@ -89,7 +90,9 @@ SingleExecutor::drain(Lock & lock) {
void
SingleExecutor::wakeup() {
- _consumerCondition.notify_one();
+ if (numTasks() > 0) {
+ _consumerCondition.notify_one();
+ }
}
SingleExecutor &
@@ -115,7 +118,7 @@ SingleExecutor::run() {
while (!_thread.stopped()) {
drain_tasks();
_producerCondition.notify_all();
- _wakeupConsumerAt.store(_wp.load(std::memory_order_relaxed) + _watermark, std::memory_order_relaxed);
+ _wakeupConsumerAt.store(_wp.load(std::memory_order_relaxed) + get_watermark(), std::memory_order_relaxed);
Lock lock(_mutex);
if (numTasks() <= 0) {
steady_time now = steady_clock::now();
@@ -157,10 +160,11 @@ SingleExecutor::wait_for_room(Lock & lock) {
drain(lock);
_tasks = std::make_unique<Task::UP[]>(_wantedTaskLimit);
_taskLimit = _wantedTaskLimit.load();
+ _watermark = _taskLimit * _watermarkRatio;
}
_queueSize.add(numTasks());
while (numTasks() >= _taskLimit.load(std::memory_order_relaxed)) {
- sleepProducer(lock, _reactionTime, wp - _watermark);
+ sleepProducer(lock, _reactionTime, wp - get_watermark());
}
}
diff --git a/staging_vespalib/src/vespa/vespalib/util/singleexecutor.h b/staging_vespalib/src/vespa/vespalib/util/singleexecutor.h
index 7d868322558..e76e3f17a41 100644
--- a/staging_vespalib/src/vespa/vespalib/util/singleexecutor.h
+++ b/staging_vespalib/src/vespa/vespalib/util/singleexecutor.h
@@ -28,7 +28,7 @@ public:
void wakeup() override;
size_t getNumThreads() const override;
uint32_t getTaskLimit() const override { return _taskLimit.load(std::memory_order_relaxed); }
- uint32_t get_watermark() const { return _watermark; }
+ uint32_t get_watermark() const { return _watermark.load(std::memory_order_relaxed); }
duration get_reaction_time() const { return _reactionTime; }
ExecutorStats getStats() override;
SingleExecutor & shutdown() override;
@@ -47,6 +47,7 @@ private:
uint64_t numTasks() const {
return _wp.load(std::memory_order_relaxed) - _rp.load(std::memory_order_acquire);
}
+ const double _watermarkRatio;
std::atomic<uint32_t> _taskLimit;
std::atomic<uint32_t> _wantedTaskLimit;
std::atomic<uint64_t> _rp;
@@ -63,7 +64,7 @@ private:
std::atomic<uint64_t> _wakeupConsumerAt;
std::atomic<uint64_t> _producerNeedWakeupAt;
std::atomic<uint64_t> _wp;
- const uint32_t _watermark;
+ std::atomic<uint32_t> _watermark;
const duration _reactionTime;
bool _closed;
};
diff --git a/storage/src/tests/distributor/putoperationtest.cpp b/storage/src/tests/distributor/putoperationtest.cpp
index a047fb7d79c..b02395717e0 100644
--- a/storage/src/tests/distributor/putoperationtest.cpp
+++ b/storage/src/tests/distributor/putoperationtest.cpp
@@ -51,9 +51,8 @@ public:
document::BucketId createAndSendSampleDocument(vespalib::duration timeout);
void sendReply(int idx = -1,
- api::ReturnCode::Result result
- = api::ReturnCode::OK,
- api::BucketInfo info = api::BucketInfo(1,2,3,4,5))
+ api::ReturnCode::Result result = api::ReturnCode::OK,
+ api::BucketInfo info = api::BucketInfo(1,2,3,4,5))
{
ASSERT_FALSE(_sender.commands().empty());
if (idx == -1) {
@@ -152,6 +151,33 @@ TEST_F(PutOperationTest, bucket_database_gets_special_entry_when_CreateBucket_se
ASSERT_EQ("Create bucket => 0,Put => 0", _sender.getCommands(true));
}
+TEST_F(PutOperationTest, failed_CreateBucket_removes_replica_from_db_and_sends_RequestBucketInfo) {
+ setup_stripe(2, 2, "distributor:1 storage:2");
+
+ auto doc = createDummyDocument("test", "test");
+ sendPut(createPut(doc));
+
+ ASSERT_EQ("Create bucket => 1,Create bucket => 0,Put => 1,Put => 0", _sender.getCommands(true));
+
+ // Simulate timeouts on node 1. Replica existence is in a Schrödinger's cat state until we send
+ // a RequestBucketInfo to the node and open the box to find out for sure.
+ sendReply(0, api::ReturnCode::TIMEOUT, api::BucketInfo()); // CreateBucket
+ sendReply(2, api::ReturnCode::TIMEOUT, api::BucketInfo()); // Put
+ // Pretend everything went fine on node 0
+ sendReply(1); // CreateBucket
+ sendReply(3); // Put
+
+ ASSERT_EQ("BucketId(0x4000000000008f09) : "
+ "node(idx=0,crc=0x1,docs=2/4,bytes=3/5,trusted=true,active=false,ready=false)",
+ dumpBucket(operation_context().make_split_bit_constrained_bucket_id(doc->getId())));
+
+ // TODO remove revert concept; does not make sense with Proton (since it's not a multi-version store and
+ // therefore does not have anything to revert back to) and is config-disabled by default for this provider.
+ ASSERT_EQ("RequestBucketInfoCommand(1 buckets, super bucket BucketId(0x4000000000008f09). ) => 1,"
+ "Revert(BucketId(0x4000000000008f09)) => 0",
+ _sender.getCommands(true, true, 4));
+}
+
TEST_F(PutOperationTest, send_inline_split_before_put_if_bucket_too_large) {
setup_stripe(1, 1, "storage:1 distributor:1");
auto cfg = make_config();
diff --git a/storage/src/vespa/storage/distributor/persistencemessagetracker.cpp b/storage/src/vespa/storage/distributor/persistencemessagetracker.cpp
index 8cacbb0bf5a..45129f7be04 100644
--- a/storage/src/vespa/storage/distributor/persistencemessagetracker.cpp
+++ b/storage/src/vespa/storage/distributor/persistencemessagetracker.cpp
@@ -259,7 +259,14 @@ PersistenceMessageTrackerImpl::handleCreateBucketReply(
&& reply.getResult().getResult() != api::ReturnCode::EXISTS)
{
LOG(spam, "Create bucket reply failed, so deleting it from bucket db");
+ // We don't know if the bucket exists at this point, so we remove it from the DB.
+ // If we get subsequent write load the bucket will be implicitly created again
+ // (which is an idempotent operation) and all is well. But since we don't know _if_
+ // we'll get any further write load we send a RequestBucketInfo to bring the bucket
+ // back into the DB if it _was_ successfully created. We have to do the latter to
+ // avoid the risk of introducing an orphaned bucket replica on the content node.
_op_ctx.remove_node_from_bucket_database(reply.getBucket(), node);
+ _op_ctx.recheck_bucket_info(node, reply.getBucket());
}
}
diff --git a/vespajlib/src/main/java/com/yahoo/concurrent/CompletableFutures.java b/vespajlib/src/main/java/com/yahoo/concurrent/CompletableFutures.java
index 2dab634d8be..125f909f0c2 100644
--- a/vespajlib/src/main/java/com/yahoo/concurrent/CompletableFutures.java
+++ b/vespajlib/src/main/java/com/yahoo/concurrent/CompletableFutures.java
@@ -1,8 +1,14 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.concurrent;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import com.yahoo.yolean.UncheckedInterruptedException;
+
import java.util.List;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
/**
* Helper for {@link java.util.concurrent.CompletableFuture} / {@link java.util.concurrent.CompletionStage}.
@@ -64,4 +70,43 @@ public class CompletableFutures {
return combiner.combined;
}
+ /**
+ * Helper for migrating from {@link ListenableFuture} to {@link CompletableFuture} in Vespa public apis
+ * @deprecated to be removed in Vespa 8
+ */
+ @Deprecated(forRemoval = true, since = "7")
+ public static <V> ListenableFuture<V> toGuavaListenableFuture(CompletableFuture<V> future) {
+ SettableFuture<V> guavaFuture = SettableFuture.create();
+ future.whenComplete((result, error) -> {
+ if (result != null) guavaFuture.set(result);
+ else if (error instanceof CancellationException) guavaFuture.setException(error);
+ else guavaFuture.cancel(true);
+ });
+ return guavaFuture;
+ }
+
+ /**
+ * Helper for migrating from {@link ListenableFuture} to {@link CompletableFuture} in Vespa public apis
+ * @deprecated to be removed in Vespa 8
+ */
+ @Deprecated(forRemoval = true, since = "7")
+ public static <V> CompletableFuture<V> toCompletableFuture(ListenableFuture<V> guavaFuture) {
+ CompletableFuture<V> future = new CompletableFuture<>();
+ guavaFuture.addListener(
+ () -> {
+ if (guavaFuture.isCancelled()) future.cancel(true);
+ try {
+ V value = guavaFuture.get();
+ future.complete(value);
+ } catch (InterruptedException e) {
+ // Should not happens since listener is invoked after future is complete
+ throw new UncheckedInterruptedException(e);
+ } catch (ExecutionException e) {
+ future.completeExceptionally(e.getCause());
+ }
+ },
+ Runnable::run);
+ return future;
+ }
+
}
diff --git a/vespalib/src/tests/btree/btree_store/btree_store_test.cpp b/vespalib/src/tests/btree/btree_store/btree_store_test.cpp
index e7d923d0e87..77cb8e519e4 100644
--- a/vespalib/src/tests/btree/btree_store/btree_store_test.cpp
+++ b/vespalib/src/tests/btree/btree_store/btree_store_test.cpp
@@ -73,61 +73,112 @@ BTreeStoreTest::~BTreeStoreTest()
inc_generation();
}
+namespace {
+
+class ChangeWriter {
+ std::vector<EntryRef*> _old_refs;
+public:
+ ChangeWriter(uint32_t capacity);
+ ~ChangeWriter();
+ void write(const std::vector<EntryRef>& refs);
+ void emplace_back(EntryRef& ref) { _old_refs.emplace_back(&ref); }
+};
+
+ChangeWriter::ChangeWriter(uint32_t capacity)
+ : _old_refs()
+{
+ _old_refs.reserve(capacity);
+}
+
+ChangeWriter::~ChangeWriter() = default;
+
+void
+ChangeWriter::write(const std::vector<EntryRef> &refs)
+{
+ assert(refs.size() == _old_refs.size());
+ auto old_ref_itr = _old_refs.begin();
+ for (auto ref : refs) {
+ **old_ref_itr = ref;
+ ++old_ref_itr;
+ }
+ assert(old_ref_itr == _old_refs.end());
+ _old_refs.clear();
+}
+
+}
+
void
BTreeStoreTest::test_compact_sequence(uint32_t sequence_length)
{
auto &store = _store;
+ uint32_t entry_ref_offset_bits = TreeStore::RefType::offset_bits;
EntryRef ref1 = add_sequence(4, 4 + sequence_length);
EntryRef ref2 = add_sequence(5, 5 + sequence_length);
- EntryRef old_ref1 = ref1;
- EntryRef old_ref2 = ref2;
std::vector<EntryRef> refs;
+ refs.reserve(2);
+ refs.emplace_back(ref1);
+ refs.emplace_back(ref2);
+ std::vector<EntryRef> temp_refs;
for (int i = 0; i < 1000; ++i) {
- refs.emplace_back(add_sequence(i + 6, i + 6 + sequence_length));
+ temp_refs.emplace_back(add_sequence(i + 6, i + 6 + sequence_length));
}
- for (auto& ref : refs) {
+ for (auto& ref : temp_refs) {
store.clear(ref);
}
inc_generation();
+ ChangeWriter change_writer(refs.size());
+ std::vector<EntryRef> move_refs;
+ move_refs.reserve(refs.size());
auto usage_before = store.getMemoryUsage();
for (uint32_t pass = 0; pass < 15; ++pass) {
auto to_hold = store.start_compact_worst_buffers();
- ref1 = store.move(ref1);
- ref2 = store.move(ref2);
+ std::vector<bool> filter(TreeStore::RefType::numBuffers());
+ for (auto buffer_id : to_hold) {
+ filter[buffer_id] = true;
+ }
+ for (auto& ref : refs) {
+ if (ref.valid() && filter[ref.buffer_id(entry_ref_offset_bits)]) {
+ move_refs.emplace_back(ref);
+ change_writer.emplace_back(ref);
+ }
+ }
+ store.move(move_refs);
+ change_writer.write(move_refs);
+ move_refs.clear();
store.finishCompact(to_hold);
inc_generation();
}
- EXPECT_NE(old_ref1, ref1);
- EXPECT_NE(old_ref2, ref2);
- EXPECT_EQ(make_exp_sequence(4, 4 + sequence_length), get_sequence(ref1));
- EXPECT_EQ(make_exp_sequence(5, 5 + sequence_length), get_sequence(ref2));
+ EXPECT_NE(ref1, refs[0]);
+ EXPECT_NE(ref2, refs[1]);
+ EXPECT_EQ(make_exp_sequence(4, 4 + sequence_length), get_sequence(refs[0]));
+ EXPECT_EQ(make_exp_sequence(5, 5 + sequence_length), get_sequence(refs[1]));
auto usage_after = store.getMemoryUsage();
EXPECT_GT(usage_before.deadBytes(), usage_after.deadBytes());
- store.clear(ref1);
- store.clear(ref2);
+ store.clear(refs[0]);
+ store.clear(refs[1]);
}
TEST_F(BTreeStoreTest, require_that_nodes_for_multiple_btrees_are_compacted)
{
auto &store = this->_store;
- EntryRef ref1 = add_sequence(4, 40);
- EntryRef ref2 = add_sequence(100, 130);
+ std::vector<EntryRef> refs;
+ refs.emplace_back(add_sequence(4, 40));
+ refs.emplace_back(add_sequence(100, 130));
store.clear(add_sequence(1000, 20000));
inc_generation();
auto usage_before = store.getMemoryUsage();
for (uint32_t pass = 0; pass < 15; ++pass) {
auto to_hold = store.start_compact_worst_btree_nodes();
- store.move_btree_nodes(ref1);
- store.move_btree_nodes(ref2);
+ store.move_btree_nodes(refs);
store.finish_compact_worst_btree_nodes(to_hold);
inc_generation();
}
- EXPECT_EQ(make_exp_sequence(4, 40), get_sequence(ref1));
- EXPECT_EQ(make_exp_sequence(100, 130), get_sequence(ref2));
+ EXPECT_EQ(make_exp_sequence(4, 40), get_sequence(refs[0]));
+ EXPECT_EQ(make_exp_sequence(100, 130), get_sequence(refs[1]));
auto usage_after = store.getMemoryUsage();
EXPECT_GT(usage_before.deadBytes(), usage_after.deadBytes());
- store.clear(ref1);
- store.clear(ref2);
+ store.clear(refs[0]);
+ store.clear(refs[1]);
}
TEST_F(BTreeStoreTest, require_that_short_arrays_are_compacted)
diff --git a/vespalib/src/tests/datastore/sharded_hash_map/sharded_hash_map_test.cpp b/vespalib/src/tests/datastore/sharded_hash_map/sharded_hash_map_test.cpp
index 6e984f286c1..796e19a97d1 100644
--- a/vespalib/src/tests/datastore/sharded_hash_map/sharded_hash_map_test.cpp
+++ b/vespalib/src/tests/datastore/sharded_hash_map/sharded_hash_map_test.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/datastore/sharded_hash_map.h>
+#include <vespa/vespalib/datastore/entry_ref_filter.h>
#include <vespa/vespalib/datastore/i_compactable.h>
#include <vespa/vespalib/datastore/unique_store_allocator.h>
#include <vespa/vespalib/datastore/unique_store_comparator.h>
@@ -12,12 +13,14 @@
#include <vespa/vespalib/gtest/gtest.h>
#include <vespa/vespalib/datastore/unique_store_allocator.hpp>
+#include <iostream>
#include <thread>
#include <vespa/log/log.h>
LOG_SETUP("vespalib_datastore_shared_hash_test");
using vespalib::datastore::EntryRef;
+using vespalib::datastore::EntryRefFilter;
using vespalib::datastore::ICompactable;
using RefT = vespalib::datastore::EntryRefT<22>;
using MyAllocator = vespalib::datastore::UniqueStoreAllocator<uint32_t, RefT>;
@@ -27,6 +30,26 @@ using MyHashMap = vespalib::datastore::ShardedHashMap;
using GenerationHandler = vespalib::GenerationHandler;
using vespalib::makeLambdaTask;
+constexpr uint32_t small_population = 50;
+/*
+ * large_population should trigger multiple callbacks from normalize_values
+ * and foreach_value
+ */
+constexpr uint32_t large_population = 1200;
+
+namespace vespalib::datastore {
+
+/*
+ * Print EntryRef as RefT which is used by test_normalize_values and
+ * test_foreach_value to differentiate between buffers
+ */
+void PrintTo(const EntryRef &ref, std::ostream* os) {
+ RefT iref(ref);
+ *os << "RefT(" << iref.offset() << "," << iref.bufferId() << ")";
+}
+
+}
+
namespace {
void consider_yield(uint32_t i)
@@ -58,6 +81,19 @@ public:
}
};
+uint32_t select_buffer(uint32_t i) {
+ if ((i % 2) == 0) {
+ return 0;
+ }
+ if ((i % 3) == 0) {
+ return 1;
+ }
+ if ((i % 5) == 0) {
+ return 2;
+ }
+ return 3;
+}
+
}
struct DataStoreShardedHashTest : public ::testing::Test
@@ -86,7 +122,11 @@ struct DataStoreShardedHashTest : public ::testing::Test
void read_work(uint32_t cnt);
void read_work();
void write_work(uint32_t cnt);
- void populate_sample_data();
+ void populate_sample_data(uint32_t cnt);
+ void populate_sample_values(uint32_t cnt);
+ void clear_sample_values(uint32_t cnt);
+ void test_normalize_values(bool use_filter, bool one_filter);
+ void test_foreach_value(bool one_filter);
};
@@ -213,13 +253,94 @@ DataStoreShardedHashTest::write_work(uint32_t cnt)
}
void
-DataStoreShardedHashTest::populate_sample_data()
+DataStoreShardedHashTest::populate_sample_data(uint32_t cnt)
{
- for (uint32_t i = 0; i < 50; ++i) {
+ for (uint32_t i = 0; i < cnt; ++i) {
insert(i);
}
}
+void
+DataStoreShardedHashTest::populate_sample_values(uint32_t cnt)
+{
+ for (uint32_t i = 0; i < cnt; ++i) {
+ MyCompare comp(_store, i);
+ auto result = _hash_map.find(comp, EntryRef());
+ ASSERT_NE(result, nullptr);
+ EXPECT_EQ(i, _allocator.get_wrapped(result->first.load_relaxed()).value());
+ result->second.store_relaxed(RefT(i + 200, select_buffer(i)));
+ }
+}
+
+void
+DataStoreShardedHashTest::clear_sample_values(uint32_t cnt)
+{
+ for (uint32_t i = 0; i < cnt; ++i) {
+ MyCompare comp(_store, i);
+ auto result = _hash_map.find(comp, EntryRef());
+ ASSERT_NE(result, nullptr);
+ EXPECT_EQ(i, _allocator.get_wrapped(result->first.load_relaxed()).value());
+ result->second.store_relaxed(EntryRef());
+ }
+}
+
+namespace {
+
+template <typename RefT>
+EntryRefFilter
+make_entry_ref_filter(bool one_filter)
+{
+ if (one_filter) {
+ EntryRefFilter filter(RefT::numBuffers(), RefT::offset_bits);
+ filter.add_buffer(3);
+ return filter;
+ }
+ return EntryRefFilter::create_all_filter(RefT::numBuffers(), RefT::offset_bits);
+}
+
+}
+
+void
+DataStoreShardedHashTest::test_normalize_values(bool use_filter, bool one_filter)
+{
+ populate_sample_data(large_population);
+ populate_sample_values(large_population);
+ if (use_filter) {
+ auto filter = make_entry_ref_filter<RefT>(one_filter);
+ EXPECT_TRUE(_hash_map.normalize_values([](std::vector<EntryRef> &refs) noexcept { for (auto &ref : refs) { RefT iref(ref); ref = RefT(iref.offset() + 300, iref.bufferId()); } }, filter));
+ } else {
+ EXPECT_TRUE(_hash_map.normalize_values([](EntryRef ref) noexcept { RefT iref(ref); return RefT(iref.offset() + 300, iref.bufferId()); }));
+ }
+ for (uint32_t i = 0; i < large_population; ++i) {
+ MyCompare comp(_store, i);
+ auto result = _hash_map.find(comp, EntryRef());
+ ASSERT_NE(result, nullptr);
+ EXPECT_EQ(i, _allocator.get_wrapped(result->first.load_relaxed()).value());
+ ASSERT_EQ(select_buffer(i), RefT(result->second.load_relaxed()).bufferId());
+ if (use_filter && one_filter && select_buffer(i) != 3) {
+ ASSERT_EQ(i + 200, RefT(result->second.load_relaxed()).offset());
+ } else {
+ ASSERT_EQ(i + 500, RefT(result->second.load_relaxed()).offset());
+ }
+ result->second.store_relaxed(EntryRef());
+ }
+}
+
+void
+DataStoreShardedHashTest::test_foreach_value(bool one_filter)
+{
+ populate_sample_data(large_population);
+ populate_sample_values(large_population);
+
+ auto filter = make_entry_ref_filter<RefT>(one_filter);
+ std::vector<EntryRef> exp_refs;
+ EXPECT_FALSE(_hash_map.normalize_values([&exp_refs](std::vector<EntryRef>& refs) { exp_refs.insert(exp_refs.end(), refs.begin(), refs.end()); }, filter));
+ std::vector<EntryRef> act_refs;
+ _hash_map.foreach_value([&act_refs](const std::vector<EntryRef> &refs) { act_refs.insert(act_refs.end(), refs.begin(), refs.end()); }, filter);
+ EXPECT_EQ(exp_refs, act_refs);
+ clear_sample_values(large_population);
+}
+
TEST_F(DataStoreShardedHashTest, single_threaded_reader_without_updates)
{
_report_work = true;
@@ -254,7 +375,7 @@ TEST_F(DataStoreShardedHashTest, memory_usage_is_reported)
EXPECT_EQ(0, initial_usage.deadBytes());
EXPECT_EQ(0, initial_usage.allocatedBytesOnHold());
auto guard = _generationHandler.takeGuard();
- for (uint32_t i = 0; i < 50; ++i) {
+ for (uint32_t i = 0; i < small_population; ++i) {
insert(i);
}
auto usage = _hash_map.get_memory_usage();
@@ -264,30 +385,31 @@ TEST_F(DataStoreShardedHashTest, memory_usage_is_reported)
TEST_F(DataStoreShardedHashTest, foreach_key_works)
{
- populate_sample_data();
+ populate_sample_data(small_population);
std::vector<uint32_t> keys;
_hash_map.foreach_key([this, &keys](EntryRef ref) { keys.emplace_back(_allocator.get_wrapped(ref).value()); });
std::sort(keys.begin(), keys.end());
- EXPECT_EQ(50, keys.size());
- for (uint32_t i = 0; i < 50; ++i) {
+ EXPECT_EQ(small_population, keys.size());
+ for (uint32_t i = 0; i < small_population; ++i) {
EXPECT_EQ(i, keys[i]);
}
}
TEST_F(DataStoreShardedHashTest, move_keys_works)
{
- populate_sample_data();
+ populate_sample_data(small_population);
std::vector<EntryRef> refs;
_hash_map.foreach_key([&refs](EntryRef ref) { refs.emplace_back(ref); });
std::vector<EntryRef> new_refs;
MyCompactable my_compactable(_allocator, new_refs);
- _hash_map.move_keys(my_compactable, std::vector<bool>(RefT::numBuffers(), true), RefT::offset_bits);
+ auto filter = make_entry_ref_filter<RefT>(false);
+ _hash_map.move_keys(my_compactable, filter);
std::vector<EntryRef> verify_new_refs;
_hash_map.foreach_key([&verify_new_refs](EntryRef ref) { verify_new_refs.emplace_back(ref); });
- EXPECT_EQ(50u, refs.size());
+ EXPECT_EQ(small_population, refs.size());
EXPECT_NE(refs, new_refs);
EXPECT_EQ(new_refs, verify_new_refs);
- for (uint32_t i = 0; i < 50; ++i) {
+ for (uint32_t i = 0; i < small_population; ++i) {
EXPECT_NE(refs[i], new_refs[i]);
auto value = _allocator.get_wrapped(refs[i]).value();
auto new_value = _allocator.get_wrapped(refs[i]).value();
@@ -297,29 +419,33 @@ TEST_F(DataStoreShardedHashTest, move_keys_works)
TEST_F(DataStoreShardedHashTest, normalize_values_works)
{
- populate_sample_data();
- for (uint32_t i = 0; i < 50; ++i) {
- MyCompare comp(_store, i);
- auto result = _hash_map.find(comp, EntryRef());
- ASSERT_NE(result, nullptr);
- EXPECT_EQ(i, _allocator.get_wrapped(result->first.load_relaxed()).value());
- result->second.store_relaxed(EntryRef(i + 200));
- }
- _hash_map.normalize_values([](EntryRef ref) noexcept { return EntryRef(ref.ref() + 300); });
- for (uint32_t i = 0; i < 50; ++i) {
- MyCompare comp(_store, i);
- auto result = _hash_map.find(comp, EntryRef());
- ASSERT_NE(result, nullptr);
- EXPECT_EQ(i, _allocator.get_wrapped(result->first.load_relaxed()).value());
- ASSERT_EQ(i + 500, result->second.load_relaxed().ref());
- result->second.store_relaxed(EntryRef());
- }
+ test_normalize_values(false, false);
+}
+
+TEST_F(DataStoreShardedHashTest, normalize_values_all_filter_works)
+{
+ test_normalize_values(true, false);
+}
+
+TEST_F(DataStoreShardedHashTest, normalize_values_one_filter_works)
+{
+ test_normalize_values(true, true);
+}
+
+TEST_F(DataStoreShardedHashTest, foreach_value_all_filter_works)
+{
+ test_foreach_value(false);
+}
+
+TEST_F(DataStoreShardedHashTest, foreach_value_one_filter_works)
+{
+ test_foreach_value(true);
}
TEST_F(DataStoreShardedHashTest, compact_worst_shard_works)
{
- populate_sample_data();
- for (uint32_t i = 10; i < 50; ++i) {
+ populate_sample_data(small_population);
+ for (uint32_t i = 10; i < small_population; ++i) {
remove(i);
}
commit();
diff --git a/vespalib/src/tests/util/rcuvector/rcuvector_test.cpp b/vespalib/src/tests/util/rcuvector/rcuvector_test.cpp
index 9ad0e95667b..cf84ab03a25 100644
--- a/vespalib/src/tests/util/rcuvector/rcuvector_test.cpp
+++ b/vespalib/src/tests/util/rcuvector/rcuvector_test.cpp
@@ -19,19 +19,14 @@ assertUsage(const MemoryUsage & exp, const MemoryUsage & act)
TEST("test generation holder")
{
- typedef std::unique_ptr<int32_t> IntPtr;
GenerationHolder gh;
- gh.hold(GenerationHeldBase::UP(new RcuVectorHeld<int32_t>(sizeof(int32_t),
- IntPtr(new int32_t(0)))));
+ gh.hold(std::make_unique<RcuVectorHeld<int32_t>>(sizeof(int32_t), 0));
gh.transferHoldLists(0);
- gh.hold(GenerationHeldBase::UP(new RcuVectorHeld<int32_t>(sizeof(int32_t),
- IntPtr(new int32_t(1)))));
+ gh.hold(std::make_unique<RcuVectorHeld<int32_t>>(sizeof(int32_t), 1));
gh.transferHoldLists(1);
- gh.hold(GenerationHeldBase::UP(new RcuVectorHeld<int32_t>(sizeof(int32_t),
- IntPtr(new int32_t(2)))));
+ gh.hold(std::make_unique<RcuVectorHeld<int32_t>>(sizeof(int32_t), 2));
gh.transferHoldLists(2);
- gh.hold(GenerationHeldBase::UP(new RcuVectorHeld<int32_t>(sizeof(int32_t),
- IntPtr(new int32_t(4)))));
+ gh.hold(std::make_unique<RcuVectorHeld<int32_t>>(sizeof(int32_t), 4));
gh.transferHoldLists(4);
EXPECT_EQUAL(4u * sizeof(int32_t), gh.getHeldBytes());
gh.trimHoldLists(0);
@@ -40,8 +35,7 @@ TEST("test generation holder")
EXPECT_EQUAL(3u * sizeof(int32_t), gh.getHeldBytes());
gh.trimHoldLists(2);
EXPECT_EQUAL(2u * sizeof(int32_t), gh.getHeldBytes());
- gh.hold(GenerationHeldBase::UP(new RcuVectorHeld<int32_t>(sizeof(int32_t),
- IntPtr(new int32_t(6)))));
+ gh.hold(std::make_unique<RcuVectorHeld<int32_t>>(sizeof(int32_t), 6));
gh.transferHoldLists(6);
EXPECT_EQUAL(3u * sizeof(int32_t), gh.getHeldBytes());
gh.trimHoldLists(6);
diff --git a/vespalib/src/vespa/vespalib/btree/btreeiterator.h b/vespalib/src/vespa/vespalib/btree/btreeiterator.h
index 325ce0e0e47..30123b1946e 100644
--- a/vespalib/src/vespa/vespalib/btree/btreeiterator.h
+++ b/vespalib/src/vespa/vespalib/btree/btreeiterator.h
@@ -113,6 +113,9 @@ public:
return _node->getData(_idx);
}
+ // Only use during compaction when changing reference to moved value
+ DataType &getWData() { return getWNode()->getWData(_idx); }
+
bool
valid() const
{
@@ -881,6 +884,9 @@ public:
_leaf.getWNode()->writeData(_leaf.getIdx(), data);
}
+ // Only use during compaction when changing reference to moved value
+ DataType &getWData() { return _leaf.getWData(); }
+
/**
* Set a new key for the current iterator position.
* The new key must have the same semantic meaning as the old key.
diff --git a/vespalib/src/vespa/vespalib/btree/btreenode.h b/vespalib/src/vespa/vespalib/btree/btreenode.h
index d8752d77f0b..468f17fcd1a 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenode.h
+++ b/vespalib/src/vespa/vespalib/btree/btreenode.h
@@ -99,6 +99,8 @@ public:
}
const DataT &getData(uint32_t idx) const { return _data[idx]; }
+ // Only use during compaction when changing reference to moved value
+ DataT &getWData(uint32_t idx) { return _data[idx]; }
void setData(uint32_t idx, const DataT &data) { _data[idx] = data; }
static bool hasData() { return true; }
};
@@ -120,6 +122,9 @@ public:
return BTreeNoLeafData::_instance;
}
+ // Only use during compaction when changing reference to moved value
+ BTreeNoLeafData &getWData(uint32_t) const { return BTreeNoLeafData::_instance; }
+
void setData(uint32_t idx, const BTreeNoLeafData &data) {
(void) idx;
(void) data;
diff --git a/vespalib/src/vespa/vespalib/btree/btreestore.h b/vespalib/src/vespa/vespalib/btree/btreestore.h
index 82913987e44..b4238757e46 100644
--- a/vespalib/src/vespa/vespalib/btree/btreestore.h
+++ b/vespalib/src/vespa/vespalib/btree/btreestore.h
@@ -298,6 +298,9 @@ public:
bool
isSmallArray(const EntryRef ref) const;
+ static bool isBTree(uint32_t typeId) { return typeId == BUFFERTYPE_BTREE; }
+ bool isBTree(RefType ref) const { return isBTree(getTypeId(ref)); }
+
/**
* Returns the cluster size for the type id.
* Cluster size == 0 means we have a tree for the given reference.
@@ -391,10 +394,10 @@ public:
std::vector<uint32_t> start_compact_worst_btree_nodes();
void finish_compact_worst_btree_nodes(const std::vector<uint32_t>& to_hold);
- void move_btree_nodes(EntryRef ref);
+ void move_btree_nodes(const std::vector<EntryRef>& refs);
std::vector<uint32_t> start_compact_worst_buffers();
- EntryRef move(EntryRef ref);
+ void move(std::vector<EntryRef>& refs);
private:
static constexpr size_t MIN_BUFFER_ARRAYS = 128u;
diff --git a/vespalib/src/vespa/vespalib/btree/btreestore.hpp b/vespalib/src/vespa/vespalib/btree/btreestore.hpp
index 15c546a0368..795e526f927 100644
--- a/vespalib/src/vespa/vespalib/btree/btreestore.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btreestore.hpp
@@ -991,15 +991,15 @@ template <typename KeyT, typename DataT, typename AggrT, typename CompareT,
typename TraitsT, typename AggrCalcT>
void
BTreeStore<KeyT, DataT, AggrT, CompareT, TraitsT, AggrCalcT>::
-move_btree_nodes(EntryRef ref)
+move_btree_nodes(const std::vector<EntryRef>& refs)
{
- if (ref.valid()) {
+ for (auto& ref : refs) {
RefType iRef(ref);
- uint32_t clusterSize = getClusterSize(iRef);
- if (clusterSize == 0) {
- BTreeType *tree = getWTreeEntry(iRef);
- tree->move_nodes(_allocator);
- }
+ assert(iRef.valid());
+ uint32_t typeId = getTypeId(iRef);
+ assert(isBTree(typeId));
+ BTreeType *tree = getWTreeEntry(iRef);
+ tree->move_nodes(_allocator);
}
}
@@ -1015,23 +1015,25 @@ start_compact_worst_buffers()
template <typename KeyT, typename DataT, typename AggrT, typename CompareT,
typename TraitsT, typename AggrCalcT>
-typename BTreeStore<KeyT, DataT, AggrT, CompareT, TraitsT, AggrCalcT>::EntryRef
+void
BTreeStore<KeyT, DataT, AggrT, CompareT, TraitsT, AggrCalcT>::
-move(EntryRef ref)
+move(std::vector<EntryRef> &refs)
{
- if (!ref.valid() || !_store.getCompacting(ref)) {
- return ref;
- }
- RefType iRef(ref);
- uint32_t clusterSize = getClusterSize(iRef);
- if (clusterSize == 0) {
- BTreeType *tree = getWTreeEntry(iRef);
- auto ref_and_ptr = allocBTreeCopy(*tree);
- tree->prepare_hold();
- return ref_and_ptr.ref;
+ for (auto& ref : refs) {
+ RefType iRef(ref);
+ assert(iRef.valid());
+ assert(_store.getCompacting(iRef));
+ uint32_t clusterSize = getClusterSize(iRef);
+ if (clusterSize == 0) {
+ BTreeType *tree = getWTreeEntry(iRef);
+ auto ref_and_ptr = allocBTreeCopy(*tree);
+ tree->prepare_hold();
+ ref = ref_and_ptr.ref;
+ } else {
+ const KeyDataType *shortArray = getKeyDataEntry(iRef, clusterSize);
+ ref = allocKeyDataCopy(shortArray, clusterSize).ref;
+ }
}
- const KeyDataType *shortArray = getKeyDataEntry(iRef, clusterSize);
- return allocKeyDataCopy(shortArray, clusterSize).ref;
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/CMakeLists.txt b/vespalib/src/vespa/vespalib/datastore/CMakeLists.txt
index 6c6f5258555..9b796c62232 100644
--- a/vespalib/src/vespa/vespalib/datastore/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/datastore/CMakeLists.txt
@@ -8,6 +8,7 @@ vespa_add_library(vespalib_vespalib_datastore OBJECT
datastore.cpp
datastorebase.cpp
entryref.cpp
+ entry_ref_filter.cpp
fixed_size_hash_map.cpp
sharded_hash_map.cpp
unique_store.cpp
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store.hpp b/vespalib/src/vespa/vespalib/datastore/array_store.hpp
index 5600c64eb3d..9317fa557c0 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/array_store.hpp
@@ -3,6 +3,7 @@
#pragma once
#include "array_store.h"
+#include "entry_ref_filter.h"
#include "datastore.hpp"
#include <atomic>
#include <algorithm>
@@ -127,47 +128,38 @@ private:
DataStoreBase &_dataStore;
ArrayStoreType &_store;
std::vector<uint32_t> _bufferIdsToCompact;
+ EntryRefFilter _filter;
- bool compactingBuffer(uint32_t bufferId) {
- return std::find(_bufferIdsToCompact.begin(), _bufferIdsToCompact.end(),
- bufferId) != _bufferIdsToCompact.end();
- }
public:
CompactionContext(DataStoreBase &dataStore,
ArrayStoreType &store,
std::vector<uint32_t> bufferIdsToCompact)
: _dataStore(dataStore),
_store(store),
- _bufferIdsToCompact(std::move(bufferIdsToCompact))
- {}
+ _bufferIdsToCompact(std::move(bufferIdsToCompact)),
+ _filter(RefT::numBuffers(), RefT::offset_bits)
+ {
+ _filter.add_buffers(_bufferIdsToCompact);
+ }
~CompactionContext() override {
_dataStore.finishCompact(_bufferIdsToCompact);
}
void compact(vespalib::ArrayRef<EntryRef> refs) override {
- if (!_bufferIdsToCompact.empty()) {
- for (auto &ref : refs) {
- if (ref.valid()) {
- RefT internalRef(ref);
- if (compactingBuffer(internalRef.bufferId())) {
- EntryRef newRef = _store.add(_store.get(ref));
- std::atomic_thread_fence(std::memory_order_release);
- ref = newRef;
- }
- }
+ for (auto &ref : refs) {
+ if (ref.valid() && _filter.has(ref)) {
+ EntryRef newRef = _store.add(_store.get(ref));
+ std::atomic_thread_fence(std::memory_order_release);
+ ref = newRef;
}
}
}
void compact(vespalib::ArrayRef<AtomicEntryRef> refs) override {
- if (!_bufferIdsToCompact.empty()) {
- for (auto &ref : refs) {
- if (ref.load_relaxed().valid()) {
- RefT internalRef(ref.load_relaxed());
- if (compactingBuffer(internalRef.bufferId())) {
- EntryRef newRef = _store.add(_store.get(ref.load_relaxed()));
- std::atomic_thread_fence(std::memory_order_release);
- ref.store_release(newRef);
- }
- }
+ for (auto &atomic_entry_ref : refs) {
+ auto ref = atomic_entry_ref.load_relaxed();
+ if (ref.valid() && _filter.has(ref)) {
+ EntryRef newRef = _store.add(_store.get(ref));
+ std::atomic_thread_fence(std::memory_order_release);
+ atomic_entry_ref.store_release(newRef);
}
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/entry_ref_filter.cpp b/vespalib/src/vespa/vespalib/datastore/entry_ref_filter.cpp
new file mode 100644
index 00000000000..87c3c87636c
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/datastore/entry_ref_filter.cpp
@@ -0,0 +1,28 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "entry_ref_filter.h"
+
+namespace vespalib::datastore {
+
+EntryRefFilter::EntryRefFilter(std::vector<bool> filter, uint32_t offset_bits)
+ : _filter(std::move(filter)),
+ _offset_bits(offset_bits)
+{
+}
+
+EntryRefFilter::EntryRefFilter(uint32_t num_buffers, uint32_t offset_bits)
+ : _filter(num_buffers),
+ _offset_bits(offset_bits)
+{
+}
+
+EntryRefFilter::~EntryRefFilter() = default;
+
+EntryRefFilter
+EntryRefFilter::create_all_filter(uint32_t num_buffers, uint32_t offset_bits)
+{
+ std::vector<bool> filter(num_buffers, true);
+ return EntryRefFilter(std::move(filter), offset_bits);
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/datastore/entry_ref_filter.h b/vespalib/src/vespa/vespalib/datastore/entry_ref_filter.h
new file mode 100644
index 00000000000..c06d843fbd0
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/datastore/entry_ref_filter.h
@@ -0,0 +1,35 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "entryref.h"
+#include <vector>
+
+namespace vespalib::datastore {
+
+/*
+ * Class to filter entry refs based on which buffer the entry is referencing.
+ *
+ * Buffers being allowed have corresponding bit in _filter set.
+ */
+class EntryRefFilter {
+ std::vector<bool> _filter;
+ uint32_t _offset_bits;
+ EntryRefFilter(std::vector<bool> filter, uint32_t offset_bits);
+public:
+ EntryRefFilter(uint32_t num_buffers, uint32_t offset_bits);
+ ~EntryRefFilter();
+ bool has(EntryRef ref) const {
+ uint32_t buffer_id = ref.buffer_id(_offset_bits);
+ return _filter[buffer_id];
+ }
+ void add_buffer(uint32_t buffer_id) { _filter[buffer_id] = true; }
+ void add_buffers(const std::vector<uint32_t>& ids) {
+ for (auto buffer_id : ids) {
+ _filter[buffer_id] = true;
+ }
+ }
+ static EntryRefFilter create_all_filter(uint32_t num_buffers, uint32_t offset_bits);
+};
+
+}
diff --git a/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.cpp b/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.cpp
index db9fee8ea70..6f001ce3c94 100644
--- a/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.cpp
@@ -2,6 +2,7 @@
#include "fixed_size_hash_map.h"
#include "entry_comparator.h"
+#include "entry_ref_filter.h"
#include "i_compactable.h"
#include <vespa/vespalib/util/array.hpp>
#include <vespa/vespalib/util/memoryusage.h>
@@ -182,7 +183,7 @@ FixedSizeHashMap::foreach_key(const std::function<void(EntryRef)>& callback) con
}
void
-FixedSizeHashMap::move_keys(ICompactable& compactable, const std::vector<bool>& compacting_buffers, uint32_t entry_ref_offset_bits)
+FixedSizeHashMap::move_keys(ICompactable& compactable, const EntryRefFilter &compacting_buffers)
{
for (auto& chain_head : _chain_heads) {
uint32_t node_idx = chain_head.load_relaxed();
@@ -190,8 +191,7 @@ FixedSizeHashMap::move_keys(ICompactable& compactable, const std::vector<bool>&
auto& node = _nodes[node_idx];
EntryRef old_ref = node.get_kv().first.load_relaxed();
assert(old_ref.valid());
- uint32_t buffer_id = old_ref.buffer_id(entry_ref_offset_bits);
- if (compacting_buffers[buffer_id]) {
+ if (compacting_buffers.has(old_ref)) {
EntryRef new_ref = compactable.move(old_ref);
node.get_kv().first.store_release(new_ref);
}
@@ -220,4 +220,104 @@ FixedSizeHashMap::normalize_values(const std::function<EntryRef(EntryRef)>& norm
return changed;
}
+namespace {
+
+class ChangeWriter {
+ std::vector<AtomicEntryRef*> _atomic_refs;
+public:
+ ChangeWriter(uint32_t capacity);
+ ~ChangeWriter();
+ bool write(const std::vector<EntryRef> &refs);
+ void emplace_back(AtomicEntryRef &atomic_ref) { _atomic_refs.emplace_back(&atomic_ref); }
+};
+
+ChangeWriter::ChangeWriter(uint32_t capacity)
+ : _atomic_refs()
+{
+ _atomic_refs.reserve(capacity);
+}
+
+ChangeWriter::~ChangeWriter() = default;
+
+bool
+ChangeWriter::write(const std::vector<EntryRef> &refs)
+{
+ bool changed = false;
+ assert(refs.size() == _atomic_refs.size());
+ auto atomic_ref = _atomic_refs.begin();
+ for (auto ref : refs) {
+ EntryRef old_ref = (*atomic_ref)->load_relaxed();
+ if (ref != old_ref) {
+ (*atomic_ref)->store_release(ref);
+ changed = true;
+ }
+ ++atomic_ref;
+ }
+ assert(atomic_ref == _atomic_refs.end());
+ _atomic_refs.clear();
+ return changed;
+}
+
+}
+
+bool
+FixedSizeHashMap::normalize_values(const std::function<void(std::vector<EntryRef>&)>& normalize, const EntryRefFilter& filter)
+{
+ std::vector<EntryRef> refs;
+ refs.reserve(1024);
+ bool changed = false;
+ ChangeWriter change_writer(refs.capacity());
+ for (auto& chain_head : _chain_heads) {
+ uint32_t node_idx = chain_head.load_relaxed();
+ while (node_idx != no_node_idx) {
+ auto& node = _nodes[node_idx];
+ EntryRef ref = node.get_kv().second.load_relaxed();
+ if (ref.valid()) {
+ if (filter.has(ref)) {
+ refs.emplace_back(ref);
+ change_writer.emplace_back(node.get_kv().second);
+ if (refs.size() >= refs.capacity()) {
+ normalize(refs);
+ changed |= change_writer.write(refs);
+ refs.clear();
+ }
+ }
+ }
+ node_idx = node.get_next_node_idx().load(std::memory_order_relaxed);
+ }
+ }
+ if (!refs.empty()) {
+ normalize(refs);
+ changed |= change_writer.write(refs);
+ }
+ return changed;
+}
+
+void
+FixedSizeHashMap::foreach_value(const std::function<void(const std::vector<EntryRef>&)>& callback, const EntryRefFilter& filter)
+{
+ std::vector<EntryRef> refs;
+ refs.reserve(1024);
+ for (auto& chain_head : _chain_heads) {
+ uint32_t node_idx = chain_head.load_relaxed();
+ while (node_idx != no_node_idx) {
+ auto& node = _nodes[node_idx];
+ EntryRef ref = node.get_kv().second.load_relaxed();
+ if (ref.valid()) {
+ if (filter.has(ref)) {
+ refs.emplace_back(ref);
+ if (refs.size() >= refs.capacity()) {
+ callback(refs);
+ refs.clear();
+ }
+ }
+ }
+ node_idx = node.get_next_node_idx().load(std::memory_order_relaxed);
+ }
+ }
+ if (!refs.empty()) {
+ callback(refs);
+ }
+}
+
}
diff --git a/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.h b/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.h
index 035cd84dbee..c522bcc3c33 100644
--- a/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.h
+++ b/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.h
@@ -18,6 +18,7 @@ class MemoryUsage;
}
namespace vespalib::datastore {
+class EntryRefFilter;
struct ICompactable;
class ShardedHashComparator {
@@ -158,8 +159,26 @@ public:
size_t size() const noexcept { return _count; }
MemoryUsage get_memory_usage() const;
void foreach_key(const std::function<void(EntryRef)>& callback) const;
- void move_keys(ICompactable& compactable, const std::vector<bool>& compacting_buffers, uint32_t entry_ref_offset_bits);
+ void move_keys(ICompactable& compactable, const EntryRefFilter &compacting_buffers);
+ /*
+ * Scan dictionary and call normalize function for each value. If
+ * returned value is different then write back the modified value to
+ * the dictionary. Used when clearing all posting lists.
+ */
bool normalize_values(const std::function<EntryRef(EntryRef)>& normalize);
+ /*
+ * Scan dictionary and call normalize function for batches of values
+ * that pass the filter. Write back modified values to the dictionary.
+ * Used by compaction of posting lists when moving short arrays,
+ * bitvectors or btree roots.
+ */
+ bool normalize_values(const std::function<void(std::vector<EntryRef>&)>& normalize, const EntryRefFilter& filter);
+ /*
+ * Scan dictionary and call callback function for batches of values
+ * that pass the filter. Used by compaction of posting lists when
+ * moving btree nodes.
+ */
+ void foreach_value(const std::function<void(const std::vector<EntryRef>&)>& callback, const EntryRefFilter& filter);
};
}
diff --git a/vespalib/src/vespa/vespalib/datastore/i_unique_store_dictionary.h b/vespalib/src/vespa/vespalib/datastore/i_unique_store_dictionary.h
index 886ec095dcd..cf848167070 100644
--- a/vespalib/src/vespa/vespalib/datastore/i_unique_store_dictionary.h
+++ b/vespalib/src/vespa/vespalib/datastore/i_unique_store_dictionary.h
@@ -11,6 +11,7 @@
namespace vespalib::datastore {
class EntryComparator;
+class EntryRefFilter;
struct ICompactable;
class IUniqueStoreDictionaryReadSnapshot;
class UniqueStoreAddResult;
@@ -28,7 +29,7 @@ public:
virtual UniqueStoreAddResult add(const EntryComparator& comp, std::function<EntryRef(void)> insertEntry) = 0;
virtual EntryRef find(const EntryComparator& comp) = 0;
virtual void remove(const EntryComparator& comp, EntryRef ref) = 0;
- virtual void move_keys(ICompactable& compactable, const std::vector<bool>& compacting_buffers, uint32_t entry_ref_offset_bits) = 0;
+ virtual void move_keys(ICompactable& compactable, const EntryRefFilter& compacting_buffers) = 0;
virtual uint32_t get_num_uniques() const = 0;
virtual vespalib::MemoryUsage get_memory_usage() const = 0;
virtual void build(vespalib::ConstArrayRef<EntryRef>, vespalib::ConstArrayRef<uint32_t> ref_counts, std::function<void(EntryRef)> hold) = 0;
diff --git a/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp b/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp
index da4db92a309..019b98a53dd 100644
--- a/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp
@@ -171,12 +171,12 @@ ShardedHashMap::foreach_key(std::function<void(EntryRef)> callback) const
}
void
-ShardedHashMap::move_keys(ICompactable& compactable, const std::vector<bool>& compacting_buffers, uint32_t entry_ref_offset_bits)
+ShardedHashMap::move_keys(ICompactable& compactable, const EntryRefFilter& compacting_buffers)
{
for (size_t i = 0; i < num_shards; ++i) {
auto map = _maps[i].load(std::memory_order_relaxed);
if (map != nullptr) {
- map->move_keys(compactable, compacting_buffers, entry_ref_offset_bits);
+ map->move_keys(compactable, compacting_buffers);
}
}
}
@@ -195,6 +195,31 @@ ShardedHashMap::normalize_values(std::function<EntryRef(EntryRef)> normalize)
}
bool
+ShardedHashMap::normalize_values(std::function<void(std::vector<EntryRef>&)> normalize, const EntryRefFilter& filter)
+{
+ bool changed = false;
+ for (size_t i = 0; i < num_shards; ++i) {
+ auto map = _maps[i].load(std::memory_order_relaxed);
+ if (map != nullptr) {
+ changed |= map->normalize_values(normalize, filter);
+ }
+ }
+ return changed;
+}
+
+void
+ShardedHashMap::foreach_value(std::function<void(const std::vector<EntryRef>&)> callback, const EntryRefFilter& filter)
+{
+ for (size_t i = 0; i < num_shards; ++i) {
+ auto map = _maps[i].load(std::memory_order_relaxed);
+ if (map != nullptr) {
+ map->foreach_value(callback, filter);
+ }
+ }
+}
+
+
+bool
ShardedHashMap::has_held_buffers() const
{
return _gen_holder.getHeldBytes() != 0;
diff --git a/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h b/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h
index df07f7a1990..e0ba9488351 100644
--- a/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h
+++ b/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h
@@ -11,6 +11,7 @@ namespace vespalib { class MemoryUsage; }
namespace vespalib::datastore {
class EntryComparator;
+class EntryRefFilter;
class FixedSizeHashMap;
struct ICompactable;
@@ -57,8 +58,10 @@ public:
const EntryComparator &get_default_comparator() const noexcept { return *_comp; }
MemoryUsage get_memory_usage() const;
void foreach_key(std::function<void(EntryRef)> callback) const;
- void move_keys(ICompactable& compactable, const std::vector<bool>& compacting_buffers, uint32_t entry_ref_offset_bits);
+ void move_keys(ICompactable& compactable, const EntryRefFilter& compacting_buffers);
bool normalize_values(std::function<EntryRef(EntryRef)> normalize);
+ bool normalize_values(std::function<void(std::vector<EntryRef>&)> normalize, const EntryRefFilter& filter);
+ void foreach_value(std::function<void(const std::vector<EntryRef>&)> callback, const EntryRefFilter& filter);
bool has_held_buffers() const;
void compact_worst_shard();
};
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
index d375dbae149..b02a2e52185 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
@@ -102,11 +102,9 @@ private:
std::vector<uint32_t> _bufferIdsToCompact;
void allocMapping() {
- _compacting_buffer.resize(RefT::numBuffers());
_mapping.resize(RefT::numBuffers());
for (const auto bufferId : _bufferIdsToCompact) {
BufferState &state = _dataStore.getBufferState(bufferId);
- _compacting_buffer[bufferId] = true;
_mapping[bufferId].resize(state.get_used_arrays());
}
}
@@ -124,7 +122,7 @@ private:
}
void fillMapping() {
- _dict.move_keys(*this, _compacting_buffer, RefT::offset_bits);
+ _dict.move_keys(*this, _compacting_buffer);
}
public:
@@ -140,6 +138,7 @@ public:
_bufferIdsToCompact(std::move(bufferIdsToCompact))
{
if (!_bufferIdsToCompact.empty()) {
+ _compacting_buffer.add_buffers(_bufferIdsToCompact);
allocMapping();
fillMapping();
}
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h b/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h
index 3b0169b5a34..54d541853c7 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h
@@ -79,7 +79,7 @@ public:
UniqueStoreAddResult add(const EntryComparator& comp, std::function<EntryRef(void)> insertEntry) override;
EntryRef find(const EntryComparator& comp) override;
void remove(const EntryComparator& comp, EntryRef ref) override;
- void move_keys(ICompactable& compactable, const std::vector<bool>& compacting_buffers, uint32_t entry_ref_offset_bits) override;
+ void move_keys(ICompactable& compactable, const EntryRefFilter& compacting_buffers) override;
uint32_t get_num_uniques() const override;
vespalib::MemoryUsage get_memory_usage() const override;
void build(vespalib::ConstArrayRef<EntryRef>, vespalib::ConstArrayRef<uint32_t> ref_counts, std::function<void(EntryRef)> hold) override;
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.hpp
index e88376be9fb..13ae0a317e0 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.hpp
@@ -4,6 +4,7 @@
#include "datastore.hpp"
#include "entry_comparator_wrapper.h"
+#include "entry_ref_filter.h"
#include "i_compactable.h"
#include "unique_store_add_result.h"
#include "unique_store_dictionary.h"
@@ -139,15 +140,14 @@ UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::remove(const
template <typename BTreeDictionaryT, typename ParentT, typename HashDictionaryT>
void
-UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::move_keys(ICompactable &compactable, const std::vector<bool>& compacting_buffers, uint32_t entry_ref_offset_bits)
+UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::move_keys(ICompactable &compactable, const EntryRefFilter& compacting_buffers)
{
if constexpr (has_btree_dictionary) {
auto itr = this->_btree_dict.begin();
while (itr.valid()) {
EntryRef oldRef(itr.getKey());
assert(oldRef.valid());
- uint32_t buffer_id = oldRef.buffer_id(entry_ref_offset_bits);
- if (compacting_buffers[buffer_id]) {
+ if (compacting_buffers.has(oldRef)) {
EntryRef newRef(compactable.move(oldRef));
this->_btree_dict.thaw(itr);
itr.writeKey(newRef);
@@ -160,7 +160,7 @@ UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::move_keys(ICo
++itr;
}
} else {
- this->_hash_dict.move_keys(compactable, compacting_buffers, entry_ref_offset_bits);
+ this->_hash_dict.move_keys(compactable, compacting_buffers);
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_remapper.h b/vespalib/src/vespa/vespalib/datastore/unique_store_remapper.h
index 4a8d72c8685..2501c4fafd9 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_remapper.h
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_remapper.h
@@ -3,6 +3,7 @@
#pragma once
#include "entryref.h"
+#include "entry_ref_filter.h"
#include <vector>
#include <vespa/vespalib/stllike/allocator.h>
@@ -18,43 +19,35 @@ public:
using RefType = RefT;
protected:
- std::vector<bool> _compacting_buffer;
+ EntryRefFilter _compacting_buffer;
std::vector<std::vector<EntryRef, allocator_large<EntryRef>>> _mapping;
public:
UniqueStoreRemapper()
- : _compacting_buffer(),
+ : _compacting_buffer(RefT::numBuffers(), RefT::offset_bits),
_mapping()
{
}
virtual ~UniqueStoreRemapper() = default;
EntryRef remap(EntryRef ref) const {
- if (ref.valid()) {
- RefType internal_ref(ref);
- if (!_compacting_buffer[internal_ref.bufferId()]) {
- // No remapping for references to buffers not being compacted
- return ref;
- } else {
- auto &inner_mapping = _mapping[internal_ref.bufferId()];
- assert(internal_ref.unscaled_offset() < inner_mapping.size());
- EntryRef mapped_ref = inner_mapping[internal_ref.unscaled_offset()];
- assert(mapped_ref.valid());
- return mapped_ref;
- }
- } else {
- return EntryRef();
- }
+ RefType internal_ref(ref);
+ auto &inner_mapping = _mapping[internal_ref.bufferId()];
+ assert(internal_ref.unscaled_offset() < inner_mapping.size());
+ EntryRef mapped_ref = inner_mapping[internal_ref.unscaled_offset()];
+ assert(mapped_ref.valid());
+ return mapped_ref;
}
void remap(vespalib::ArrayRef<EntryRef> refs) const {
for (auto &ref : refs) {
- auto mapped_ref = remap(ref);
- if (mapped_ref != ref) {
- ref = mapped_ref;
+ if (ref.valid() && _compacting_buffer.has(ref)) {
+ ref = remap(ref);
}
}
}
+ const EntryRefFilter& get_entry_ref_filter() const noexcept { return _compacting_buffer; }
+
virtual void done() = 0;
};
diff --git a/vespalib/src/vespa/vespalib/hwaccelrated/iaccelrated.cpp b/vespalib/src/vespa/vespalib/hwaccelrated/iaccelrated.cpp
index b4f7eb5cd96..7407ffd6a4e 100644
--- a/vespalib/src/vespa/vespalib/hwaccelrated/iaccelrated.cpp
+++ b/vespalib/src/vespa/vespalib/hwaccelrated/iaccelrated.cpp
@@ -17,28 +17,18 @@ namespace vespalib::hwaccelrated {
namespace {
-class Factory {
-public:
- virtual ~Factory() = default;
- virtual IAccelrated::UP create() const = 0;
-};
-
-class GenericFactory :public Factory{
-public:
- IAccelrated::UP create() const override { return std::make_unique<GenericAccelrator>(); }
-};
-
+IAccelrated::UP create_accelerator() {
#ifdef __x86_64__
-class Avx2Factory :public Factory{
-public:
- IAccelrated::UP create() const override { return std::make_unique<Avx2Accelrator>(); }
-};
-
-class Avx512Factory :public Factory{
-public:
- IAccelrated::UP create() const override { return std::make_unique<Avx512Accelrator>(); }
-};
+ __builtin_cpu_init();
+ if (__builtin_cpu_supports("avx512f")) {
+ return std::make_unique<Avx512Accelrator>();
+ }
+ if (__builtin_cpu_supports("avx2")) {
+ return std::make_unique<Avx2Accelrator>();
+ }
#endif
+ return std::make_unique<GenericAccelrator>();
+}
template<typename T>
std::vector<T> createAndFill(size_t sz) {
@@ -247,42 +237,14 @@ RuntimeVerificator::RuntimeVerificator()
verify(thisCpu);
}
-class Selector
-{
-public:
- Selector() __attribute__((noinline));
- IAccelrated::UP create() { return _factory->create(); }
-private:
- std::unique_ptr<Factory> _factory;
-};
-
-Selector::Selector() :
- _factory()
-{
-#ifdef __x86_64__
- __builtin_cpu_init ();
- if (__builtin_cpu_supports("avx512f")) {
- _factory = std::make_unique<Avx512Factory>();
- } else if (__builtin_cpu_supports("avx2")) {
- _factory = std::make_unique<Avx2Factory>();
- } else {
- _factory = std::make_unique<GenericFactory>();
- }
-#else
- _factory = std::make_unique<GenericFactory>();
-#endif
-}
-
}
-static Selector _G_selector;
-
RuntimeVerificator _G_verifyAccelrator;
const IAccelrated &
IAccelrated::getAccelerator()
{
- static IAccelrated::UP accelrator = _G_selector.create();
+ static IAccelrated::UP accelrator = create_accelerator();
return *accelrator;
}
diff --git a/vespalib/src/vespa/vespalib/util/rcuvector.h b/vespalib/src/vespa/vespalib/util/rcuvector.h
index 0396ee0d459..dd4fa660279 100644
--- a/vespalib/src/vespa/vespalib/util/rcuvector.h
+++ b/vespalib/src/vespa/vespalib/util/rcuvector.h
@@ -13,10 +13,10 @@ namespace vespalib {
template <typename T>
class RcuVectorHeld : public GenerationHeldBase
{
- std::unique_ptr<T> _data;
+ T _data;
public:
- RcuVectorHeld(size_t size, std::unique_ptr<T> data);
+ RcuVectorHeld(size_t size, T&& data);
~RcuVectorHeld();
};
@@ -121,7 +121,7 @@ public:
void reset();
void shrink(size_t newSize) __attribute__((noinline));
- void replaceVector(std::unique_ptr<ArrayType> replacement);
+ void replaceVector(ArrayType replacement);
};
template <typename T>
diff --git a/vespalib/src/vespa/vespalib/util/rcuvector.hpp b/vespalib/src/vespa/vespalib/util/rcuvector.hpp
index 9d7c8ea57d6..3c455149dfd 100644
--- a/vespalib/src/vespa/vespalib/util/rcuvector.hpp
+++ b/vespalib/src/vespa/vespalib/util/rcuvector.hpp
@@ -9,7 +9,7 @@
namespace vespalib {
template <typename T>
-RcuVectorHeld<T>::RcuVectorHeld(size_t size, std::unique_ptr<T> data)
+RcuVectorHeld<T>::RcuVectorHeld(size_t size, T&& data)
: GenerationHeldBase(size),
_data(std::move(data))
{ }
@@ -52,20 +52,21 @@ RcuVectorBase<T>::~RcuVectorBase() = default;
template <typename T>
void
RcuVectorBase<T>::expand(size_t newCapacity) {
- std::unique_ptr<ArrayType> tmpData(new ArrayType());
- tmpData->reserve(newCapacity);
+ ArrayType tmpData;
+ tmpData.reserve(newCapacity);
for (const T & v : _data) {
- tmpData->push_back_fast(v);
+ tmpData.push_back_fast(v);
}
replaceVector(std::move(tmpData));
}
template <typename T>
void
-RcuVectorBase<T>::replaceVector(std::unique_ptr<ArrayType> replacement) {
- replacement->swap(_data); // atomic switch of underlying data
- size_t holdSize = replacement->capacity() * sizeof(T);
- GenerationHeldBase::UP hold(new RcuVectorHeld<ArrayType>(holdSize, std::move(replacement)));
+RcuVectorBase<T>::replaceVector(ArrayType replacement) {
+ std::atomic_thread_fence(std::memory_order_release);
+ replacement.swap(_data); // atomic switch of underlying data
+ size_t holdSize = replacement.capacity() * sizeof(T);
+ auto hold = std::make_unique<RcuVectorHeld<ArrayType>>(holdSize, std::move(replacement));
_genHolder.hold(std::move(hold));
onReallocation();
}
@@ -90,17 +91,18 @@ RcuVectorBase<T>::shrink(size_t newSize)
return;
}
if (!_data.try_unreserve(wantedCapacity)) {
- std::unique_ptr<ArrayType> tmpData(new ArrayType());
- tmpData->reserve(wantedCapacity);
- tmpData->resize(newSize);
+ ArrayType tmpData;
+ tmpData.reserve(wantedCapacity);
+ tmpData.resize(newSize);
for (uint32_t i = 0; i < newSize; ++i) {
- (*tmpData)[i] = _data[i];
+ tmpData[i] = _data[i];
}
+ std::atomic_thread_fence(std::memory_order_release);
// Users of RCU vector must ensure that no readers use old size
// after swap. Attribute vectors uses _committedDocIdLimit for this.
- tmpData->swap(_data); // atomic switch of underlying data
- size_t holdSize = tmpData->capacity() * sizeof(T);
- GenerationHeldBase::UP hold(new RcuVectorHeld<ArrayType>(holdSize, std::move(tmpData)));
+ tmpData.swap(_data); // atomic switch of underlying data
+ size_t holdSize = tmpData.capacity() * sizeof(T);
+ auto hold = std::make_unique<RcuVectorHeld<ArrayType>>(holdSize, std::move(tmpData));
_genHolder.hold(std::move(hold));
onReallocation();
}