aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2022-10-17 16:27:02 +0200
committerGitHub <noreply@github.com>2022-10-17 16:27:02 +0200
commitbbcccf78cfaa5438c18f188c5dd15a9a979617ee (patch)
treecb3b35e15c47c108bae252c1cc169945c88c365c
parent849401dd245eb9193d1ca31bc288c6b665795747 (diff)
parentb7123d3a07bc823961e452ad527d00e236012ebe (diff)
Merge branch 'master' into balder/gc-unused-phrase-flags
-rw-r--r--CMakeLists.txt18
-rw-r--r--application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java68
-rw-r--r--application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java11
-rw-r--r--application/src/main/java/com/yahoo/application/container/handler/Headers.java30
-rw-r--r--build_settings.cmake3
-rw-r--r--client/go/cmd/logfmt/cmd.go1
-rw-r--r--client/go/cmd/logfmt/formatflags.go41
-rw-r--r--client/go/cmd/logfmt/formatflags_test.go30
-rw-r--r--client/go/cmd/logfmt/handleline.go127
-rw-r--r--client/go/cmd/logfmt/options.go1
-rw-r--r--client/pom.xml2
-rw-r--r--cloud-tenant-base-dependencies-enforcer/pom.xml7
-rw-r--r--clustercontroller-apps/src/main/java/com/yahoo/vespa/clustercontroller/apps/clustercontroller/StateRestApiV2Handler.java1
-rw-r--r--clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java1
-rw-r--r--component/src/main/java/com/yahoo/component/ComponentId.java19
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java76
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java45
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java20
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java6
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTagsTest.java124
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java86
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java5
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java13
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java47
-rw-r--r--config-model-api/abi-spec.json5
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java34
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java11
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java38
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java43
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java39
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java62
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java2
-rw-r--r--config-model/src/main/java/com/yahoo/schema/processing/PagedAttributeValidator.java8
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryTransform.java54
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java8
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java10
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java7
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/FileStorProducer.java18
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorServerProducer.java21
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorageCluster.java2
-rw-r--r--config-model/src/main/resources/schema/deployment.rnc1
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java13
-rw-r--r--config-model/src/test/java/com/yahoo/schema/processing/PagedAttributeValidatorTestCase.java34
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java5
-rwxr-xr-xconfig-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java1
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java1
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java17
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java40
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java45
-rw-r--r--config-model/src/test/schema-test-files/deployment-with-instances.xml2
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/HostLivenessTracker.java24
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/NodeAllocationException.java9
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Tags.java64
-rw-r--r--config-provisioning/src/test/java/com/yahoo/config/provision/TagsTest.java57
-rw-r--r--config-proxy/pom.xml2
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/CachedFilesMaintainer.java4
-rw-r--r--configdefinitions/src/vespa/curator.def3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java16
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java27
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/host/ConfigRequestHostLivenessTracker.java45
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/InvalidApplicationException.java6
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java11
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java12
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java61
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java24
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java32
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java16
-rw-r--r--configserver/src/main/resources/configserver-app/services.xml1
-rw-r--r--configserver/src/test/apps/hosted-invalid-file-extension/deployment.xml7
-rw-r--r--configserver/src/test/apps/hosted-invalid-file-extension/schemas/file-with-invalid.extension0
-rw-r--r--configserver/src/test/apps/hosted-invalid-file-extension/services.xml16
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java22
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java11
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpcServer.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcTester.java7
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java15
-rw-r--r--container-core/abi-spec.json45
-rw-r--r--container-core/src/main/java/com/yahoo/container/handler/LogReader.java17
-rw-r--r--container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java47
-rw-r--r--container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java6
-rw-r--r--container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java11
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java17
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorSpecificContextHandler.java35
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FormPostRequestHandler.java56
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java17
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java7
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java106
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java58
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java41
-rw-r--r--container-core/src/main/java/com/yahoo/processing/handler/AbstractProcessingHandler.java2
-rw-r--r--container-core/src/main/java/com/yahoo/processing/request/CompoundName.java2
-rw-r--r--container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def8
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java37
-rw-r--r--container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java3
-rw-r--r--container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java20
-rw-r--r--container-core/src/test/java/com/yahoo/metrics/simple/BucketTest.java26
-rw-r--r--container-core/src/test/java/com/yahoo/metrics/simple/CounterTest.java8
-rw-r--r--container-core/src/test/java/com/yahoo/metrics/simple/MetricsTest.java5
-rw-r--r--container-core/src/test/java/com/yahoo/metrics/simple/jdisc/SnapshotConverterTest.java5
-rw-r--r--container-disc/pom.xml6
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java8
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/SystemInfoProvider.java2
-rw-r--r--container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java5
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java47
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/SortDataHitSorter.java16
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/Item.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java40
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/searcher/ValidateSortingSearcher.java19
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java16
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/MapConverter.java24
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java49
-rw-r--r--container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java16
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/QueryTree.java30
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Sorting.java46
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java12
-rw-r--r--container-search/src/main/java/com/yahoo/search/result/FieldComparator.java47
-rw-r--r--container-search/src/main/java/com/yahoo/search/result/Hit.java13
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java92
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/YqlParser.java18
-rw-r--r--container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java2
-rw-r--r--container-search/src/test/java/com/yahoo/search/yql/UserInputTestCase.java12
-rw-r--r--container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java19
-rw-r--r--container-test/pom.xml4
-rw-r--r--container/pom.xml2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTarget.java57
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyDirectTarget.java66
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java19
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java1
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedDirectTarget.java70
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTargetTest.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java52
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java30
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java30
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java34
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java60
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java28
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java107
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java32
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java61
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java31
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java5
-rw-r--r--default_build_settings.cmake191
-rw-r--r--dist/STLExtras.h.diff20
-rw-r--r--dist/vespa.spec114
-rw-r--r--docproc/src/main/java/com/yahoo/docproc/DocumentProcessor.java2
-rw-r--r--document/src/main/java/com/yahoo/document/Document.java3
-rw-r--r--document/src/main/java/com/yahoo/document/DocumentUpdate.java1
-rw-r--r--document/src/main/java/com/yahoo/document/datatypes/StructuredFieldValue.java16
-rw-r--r--document/src/main/java/com/yahoo/document/json/JsonFeedReader.java4
-rw-r--r--document/src/main/java/com/yahoo/document/json/JsonReader.java28
-rw-r--r--document/src/main/java/com/yahoo/document/json/ParsedDocumentOperation.java51
-rw-r--r--document/src/main/java/com/yahoo/document/json/TokenBuffer.java8
-rw-r--r--document/src/main/java/com/yahoo/document/json/readers/CompositeReader.java19
-rw-r--r--document/src/main/java/com/yahoo/document/json/readers/SingleValueReader.java36
-rw-r--r--document/src/main/java/com/yahoo/document/json/readers/StructReader.java38
-rw-r--r--document/src/main/java/com/yahoo/document/json/readers/VespaJsonDocumentReader.java58
-rw-r--r--document/src/main/java/com/yahoo/document/json/readers/WeightedSetReader.java3
-rw-r--r--document/src/main/java/com/yahoo/document/update/FieldUpdate.java8
-rw-r--r--document/src/main/java/com/yahoo/document/update/ValueUpdate.java10
-rw-r--r--document/src/main/java/com/yahoo/vespaxmlparser/FeedOperation.java3
-rw-r--r--document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java2
-rw-r--r--document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java82
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/ProgressToken.java2
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/Response.java6
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/VisitorSession.java13
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/MapVisitorMessage.java1
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java8
-rwxr-xr-xdocumentapi/src/test/java/com/yahoo/documentapi/messagebus/MessageBusVisitorSessionTestCase.java2
-rw-r--r--documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/ErrorCodesTest.java1
-rw-r--r--eval/CMakeLists.txt1
-rw-r--r--eval/src/tests/eval/gen_spec/gen_spec_test.cpp56
-rw-r--r--eval/src/tests/eval/tensor_lambda/tensor_lambda_test.cpp2
-rw-r--r--eval/src/tests/eval/value_type/value_type_test.cpp37
-rw-r--r--eval/src/tests/instruction/mapped_lookup/CMakeLists.txt9
-rw-r--r--eval/src/tests/instruction/mapped_lookup/mapped_lookup_test.cpp126
-rw-r--r--eval/src/vespa/eval/eval/optimize_tensor_function.cpp3
-rw-r--r--eval/src/vespa/eval/eval/test/eval_fixture.h34
-rw-r--r--eval/src/vespa/eval/eval/test/gen_spec.cpp2
-rw-r--r--eval/src/vespa/eval/eval/test/gen_spec.h1
-rw-r--r--eval/src/vespa/eval/eval/value_type.cpp12
-rw-r--r--eval/src/vespa/eval/eval/value_type.h1
-rw-r--r--eval/src/vespa/eval/instruction/CMakeLists.txt1
-rw-r--r--eval/src/vespa/eval/instruction/mapped_lookup.cpp152
-rw-r--r--eval/src/vespa/eval/instruction/mapped_lookup.h49
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java83
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java7
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/FetchVectorHelper.java2
-rw-r--r--functions.cmake16
-rw-r--r--hosted-zone-api/abi-spec.json2
-rw-r--r--hosted-zone-api/src/main/java/ai/vespa/cloud/Cluster.java25
-rw-r--r--hosted-zone-api/src/test/java/ai/vespa/cloud/SystemInfoTest.java6
-rw-r--r--integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java2
-rw-r--r--integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java2
-rw-r--r--jdisc_core/abi-spec.json3
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java41
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java2
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java1
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java8
-rw-r--r--jrt/pom.xml2
-rw-r--r--linguistics/src/main/java/com/yahoo/language/opennlp/LanguageDetectorFactory.java10
-rw-r--r--linguistics/src/main/java/com/yahoo/language/opennlp/VespaCharSequenceNormalizer.java51
-rw-r--r--linguistics/src/main/java/com/yahoo/language/opennlp/WordCharDetector.java48
-rw-r--r--logserver/src/main/java/com/yahoo/logserver/handlers/archive/LogWriterLRUCache.java1
-rw-r--r--logserver/src/test/java/com/yahoo/logserver/handlers/logmetrics/test/LogMetricsTestCase.java32
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java17
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java48
-rw-r--r--messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java3
-rw-r--r--metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsRetriever.java2
-rw-r--r--metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonUtil.java1
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/WireguardMaintainer.java14
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandler.java2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java12
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java9
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java63
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java30
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/History.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersions.java20
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/TestHostLivenessTracker.java32
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java48
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java42
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java186
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java18
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java4
-rw-r--r--parent/pom.xml15
-rw-r--r--predicate-search-core/src/main/java/com/yahoo/document/predicate/BooleanPredicate.java3
-rw-r--r--predicate-search-core/src/main/java/com/yahoo/document/predicate/FeatureRange.java7
-rw-r--r--searchcore/src/tests/proton/attribute/attribute_manager/attribute_manager_test.cpp4
-rw-r--r--searchcore/src/tests/proton/attribute/attribute_populator/attribute_populator_test.cpp1
-rw-r--r--searchcore/src/tests/proton/attribute/attribute_test.cpp76
-rw-r--r--searchcore/src/tests/proton/attribute/document_field_populator/CMakeLists.txt1
-rw-r--r--searchcore/src/tests/proton/attribute/document_field_populator/document_field_populator_test.cpp29
-rw-r--r--searchcore/src/tests/proton/attribute/imported_attributes_context/imported_attributes_context_test.cpp5
-rw-r--r--searchcore/src/tests/proton/docsummary/docsummary.cpp39
-rw-r--r--searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp41
-rw-r--r--searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp61
-rw-r--r--searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp13
-rw-r--r--searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.cpp6
-rw-r--r--searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.h16
-rw-r--r--searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_handler_test.cpp6
-rw-r--r--searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.cpp2
-rw-r--r--searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.h4
-rw-r--r--searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp9
-rw-r--r--searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp10
-rw-r--r--searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp36
-rw-r--r--searchcore/src/tests/proton/documentmetastore/lid_allocator/lid_allocator_test.cpp8
-rw-r--r--searchcore/src/tests/proton/documentmetastore/lid_state_vector/lid_state_vector_test.cpp6
-rw-r--r--searchcore/src/tests/proton/feed_and_search/CMakeLists.txt1
-rw-r--r--searchcore/src/tests/proton/feed_and_search/feed_and_search.cpp18
-rw-r--r--searchcore/src/tests/proton/index/fusionrunner_test.cpp20
-rw-r--r--searchcore/src/tests/proton/index/index_writer/index_writer_test.cpp20
-rw-r--r--searchcore/src/tests/proton/index/indexmanager_test.cpp19
-rw-r--r--searchcore/src/tests/proton/reference/gid_to_lid_mapper/gid_to_lid_mapper_test.cpp4
-rw-r--r--searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/CMakeLists.txt1
-rw-r--r--searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/document_reprocessing_handler_test.cpp8
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.cpp20
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/attribute_vector_explorer.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/attributemanager.cpp52
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/attributemanager.h24
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/document_meta_store_adapter.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp89
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h10
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.h3
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreflushtarget.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp11
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/CMakeLists.txt1
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.cpp5
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.h9
-rw-r--r--searchlib/CMakeLists.txt4
-rw-r--r--searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp27
-rw-r--r--searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp7
-rw-r--r--searchlib/src/tests/attribute/enum_comparator/enum_comparator_test.cpp4
-rw-r--r--searchlib/src/tests/attribute/enumstore/enumstore_test.cpp12
-rw-r--r--searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp16
-rw-r--r--searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp18
-rw-r--r--searchlib/src/tests/attribute/posting_store/posting_store_test.cpp8
-rw-r--r--searchlib/src/tests/attribute/postinglist/postinglist.cpp26
-rw-r--r--searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp102
-rw-r--r--searchlib/src/tests/common/bitvector/bitvector_test.cpp8
-rw-r--r--searchlib/src/tests/diskindex/fusion/CMakeLists.txt2
-rw-r--r--searchlib/src/tests/diskindex/fusion/fusion_test.cpp84
-rw-r--r--searchlib/src/tests/index/docbuilder/.gitignore5
-rw-r--r--searchlib/src/tests/index/docbuilder/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/index/docbuilder/docbuilder_test.cpp437
-rw-r--r--searchlib/src/tests/index/doctypebuilder/.gitignore5
-rw-r--r--searchlib/src/tests/index/doctypebuilder/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/index/doctypebuilder/doctypebuilder_test.cpp74
-rw-r--r--searchlib/src/tests/memoryindex/document_inverter/document_inverter_test.cpp78
-rw-r--r--searchlib/src/tests/memoryindex/field_index/field_index_test.cpp370
-rw-r--r--searchlib/src/tests/memoryindex/field_inverter/field_inverter_test.cpp115
-rw-r--r--searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt2
-rw-r--r--searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp38
-rw-r--r--searchlib/src/tests/memoryindex/url_field_inverter/url_field_inverter_test.cpp235
-rw-r--r--searchlib/src/tests/predicate/simple_index_test.cpp2
-rw-r--r--searchlib/src/tests/sortspec/multilevelsort.cpp176
-rw-r--r--searchlib/src/tests/tensor/dense_tensor_store/dense_tensor_store_test.cpp6
-rw-r--r--searchlib/src/tests/tensor/direct_tensor_store/direct_tensor_store_test.cpp17
-rw-r--r--searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp5
-rw-r--r--searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp5
-rw-r--r--searchlib/src/tests/tensor/tensor_buffer_operations/tensor_buffer_operations_test.cpp5
-rw-r--r--searchlib/src/tests/tensor/tensor_buffer_store/tensor_buffer_store_test.cpp10
-rw-r--r--searchlib/src/tests/tensor/tensor_buffer_type_mapper/CMakeLists.txt9
-rw-r--r--searchlib/src/tests/tensor/tensor_buffer_type_mapper/tensor_buffer_type_mapper_test.cpp121
-rw-r--r--searchlib/src/tests/test/string_field_builder/CMakeLists.txt9
-rw-r--r--searchlib/src/tests/test/string_field_builder/string_field_builder_test.cpp141
-rw-r--r--searchlib/src/vespa/searchcommon/common/growstrategy.h2
-rw-r--r--searchlib/src/vespa/searchcommon/common/iblobconverter.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/CMakeLists.txt2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributevector.cpp24
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributevector.h14
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attrvector.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enumstore.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enumstore.hpp8
-rw-r--r--searchlib/src/vespa/searchlib/attribute/flagattribute.cpp8
-rw-r--r--searchlib/src/vespa/searchlib/attribute/flagattribute.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/load_utils.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.cpp15
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multienumattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multienumattribute.hpp14
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp10
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multivalueattribute.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp16
-rw-r--r--searchlib/src/vespa/searchlib/attribute/predicate_attribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp20
-rw-r--r--searchlib/src/vespa/searchlib/attribute/reference_attribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/reference_mappings.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_enum_search_context.cpp18
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp18
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singleboolattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singleenumattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp22
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp18
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp18
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/stringbase.cpp16
-rw-r--r--searchlib/src/vespa/searchlib/common/growablebitvector.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/common/sortresults.cpp121
-rw-r--r--searchlib/src/vespa/searchlib/common/sortresults.h26
-rw-r--r--searchlib/src/vespa/searchlib/common/sortspec.cpp12
-rw-r--r--searchlib/src/vespa/searchlib/common/sortspec.h2
-rw-r--r--searchlib/src/vespa/searchlib/common/threaded_compactable_lid_space.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/docstore/logdatastore.cpp17
-rw-r--r--searchlib/src/vespa/searchlib/index/CMakeLists.txt3
-rw-r--r--searchlib/src/vespa/searchlib/index/docbuilder.cpp814
-rw-r--r--searchlib/src/vespa/searchlib/index/docbuilder.h282
-rw-r--r--searchlib/src/vespa/searchlib/index/doctypebuilder.cpp175
-rw-r--r--searchlib/src/vespa/searchlib/index/doctypebuilder.h28
-rw-r--r--searchlib/src/vespa/searchlib/index/empty_doc_builder.cpp59
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp17
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/feature_store.h11
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/field_index.cpp14
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/field_index.h24
-rw-r--r--searchlib/src/vespa/searchlib/predicate/predicate_index.cpp20
-rw-r--r--searchlib/src/vespa/searchlib/predicate/predicate_index.h4
-rw-r--r--searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp8
-rw-r--r--searchlib/src/vespa/searchlib/predicate/predicate_interval_store.h4
-rw-r--r--searchlib/src/vespa/searchlib/predicate/simple_index.h4
-rw-r--r--searchlib/src/vespa/searchlib/predicate/simple_index.hpp22
-rw-r--r--searchlib/src/vespa/searchlib/tensor/CMakeLists.txt4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp25
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp44
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h13
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.cpp62
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.h2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_saver.cpp54
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_saver.h35
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp58
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h8
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp14
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index.h4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.cpp56
-rw-r--r--searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.h2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp288
-rw-r--r--searchlib/src/vespa/searchlib/tensor/streamed_value_store.h84
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp65
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_attribute.h6
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.cpp32
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.h32
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.cpp10
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.h10
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_deserialize.cpp7
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_deserialize.h11
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_store.h18
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_store_saver.cpp (renamed from searchlib/src/vespa/searchlib/tensor/streamed_value_saver.cpp)18
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_store_saver.h (renamed from searchlib/src/vespa/searchlib/tensor/streamed_value_saver.h)16
-rw-r--r--searchlib/src/vespa/searchlib/test/CMakeLists.txt2
-rw-r--r--searchlib/src/vespa/searchlib/test/doc_builder.cpp117
-rw-r--r--searchlib/src/vespa/searchlib/test/doc_builder.h (renamed from searchlib/src/vespa/searchlib/index/empty_doc_builder.h)23
-rw-r--r--searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.cpp20
-rw-r--r--searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h4
-rw-r--r--searchlib/src/vespa/searchlib/test/string_field_builder.cpp140
-rw-r--r--searchlib/src/vespa/searchlib/test/string_field_builder.h45
-rw-r--r--searchsummary/src/tests/docsummary/annotation_converter/annotation_converter_test.cpp11
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/annotation_converter.cpp4
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.cpp20
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.h3
-rw-r--r--security-utils/pom.xml2
-rw-r--r--security-utils/src/main/java/com/yahoo/security/HKDF.java221
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java73
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SecretSharedKey.java24
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java129
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java54
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java4
-rw-r--r--security-utils/src/test/java/com/yahoo/security/HKDFTest.java298
-rw-r--r--security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java136
-rw-r--r--security-utils/src/test/java/com/yahoo/security/SideChannelSafeTest.java38
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/health/ApplicationHealthMonitor.java2
-rw-r--r--storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.hpp10
-rw-r--r--valgrind-suppressions.txt10
-rw-r--r--vespa-athenz/pom.xml4
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java12
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java4
-rw-r--r--vespa-feed-client/pom.xml2
-rw-r--r--vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java3
-rw-r--r--vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java8
-rw-r--r--vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java7
-rw-r--r--vespabase/CMakeLists.txt1
-rwxr-xr-xvespabase/src/rhel-prestart.sh1
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java134
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java32
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/DocumentOperationMessageV3.java8
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandler.java5
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandlerV3.java9
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java2
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Headers.java7
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/StreamReaderV3.java19
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/package-info.java2
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java79
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/vespa/http/server/FeedHandlerV3Test.java2
-rw-r--r--vespaclient-core/src/main/java/com/yahoo/feedapi/Feeder.java17
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java3
-rw-r--r--vespajlib/src/main/java/com/yahoo/io/GrowableByteBuffer.java4
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java4
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/serialization/TypedBinaryFormat.java21
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java2
-rw-r--r--vespalib/CMakeLists.txt12
-rw-r--r--vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp30
-rw-r--r--vespalib/src/tests/btree/btree_store/btree_store_test.cpp4
-rw-r--r--vespalib/src/tests/btree/btree_test.cpp30
-rw-r--r--vespalib/src/tests/btree/btreeaggregation_test.cpp20
-rw-r--r--vespalib/src/tests/btree/frozenbtree_test.cpp8
-rw-r--r--vespalib/src/tests/coro/detached/CMakeLists.txt9
-rw-r--r--vespalib/src/tests/coro/detached/detached_test.cpp19
-rw-r--r--vespalib/src/tests/coro/lazy/CMakeLists.txt9
-rw-r--r--vespalib/src/tests/coro/lazy/lazy_test.cpp121
-rw-r--r--vespalib/src/tests/datastore/array_store/array_store_test.cpp50
-rw-r--r--vespalib/src/tests/datastore/buffer_stats/CMakeLists.txt9
-rw-r--r--vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp34
-rw-r--r--vespalib/src/tests/datastore/datastore/datastore_test.cpp120
-rw-r--r--vespalib/src/tests/datastore/fixed_size_hash_map/fixed_size_hash_map_test.cpp12
-rw-r--r--vespalib/src/tests/datastore/sharded_hash_map/sharded_hash_map_test.cpp16
-rw-r--r--vespalib/src/tests/datastore/unique_store/unique_store_test.cpp38
-rw-r--r--vespalib/src/tests/datastore/unique_store_dictionary/unique_store_dictionary_test.cpp4
-rw-r--r--vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp74
-rw-r--r--vespalib/src/tests/util/generation_hold_list/CMakeLists.txt9
-rw-r--r--vespalib/src/tests/util/generation_hold_list/generation_hold_list_test.cpp95
-rw-r--r--vespalib/src/tests/util/generation_holder/CMakeLists.txt9
-rw-r--r--vespalib/src/tests/util/generation_holder/generation_holder_test.cpp38
-rw-r--r--vespalib/src/tests/util/generationhandler/generationhandler_test.cpp28
-rw-r--r--vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp8
-rw-r--r--vespalib/src/tests/util/rcuvector/rcuvector_test.cpp50
-rw-r--r--vespalib/src/vespa/vespalib/btree/btree.hpp2
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodeallocator.h11
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp18
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodestore.h22
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodestore.hpp23
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreestore.h18
-rw-r--r--vespalib/src/vespa/vespalib/coro/CMakeLists.txt5
-rw-r--r--vespalib/src/vespa/vespalib/coro/detached.h32
-rw-r--r--vespalib/src/vespa/vespalib/coro/lazy.h111
-rw-r--r--vespalib/src/vespa/vespalib/coro/sync_wait.h59
-rw-r--r--vespalib/src/vespa/vespalib/datastore/CMakeLists.txt2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/allocator.hpp6
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store.h10
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store.hpp49
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp57
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_stats.h76
-rw-r--r--vespalib/src/vespa/vespalib/datastore/bufferstate.cpp86
-rw-r--r--vespalib/src/vespa/vespalib/datastore/bufferstate.h66
-rw-r--r--vespalib/src/vespa/vespalib/datastore/compact_buffer_candidates.cpp2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/compaction_context.cpp2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/compaction_strategy.cpp7
-rw-r--r--vespalib/src/vespa/vespalib/datastore/compaction_strategy.h1
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastore.cpp2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastore.h28
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastore.hpp59
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastorebase.cpp192
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastorebase.h164
-rw-r--r--vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.cpp63
-rw-r--r--vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.h33
-rw-r--r--vespalib/src/vespa/vespalib/datastore/free_list.cpp1
-rw-r--r--vespalib/src/vespa/vespalib/datastore/i_compactable.h7
-rw-r--r--vespalib/src/vespa/vespalib/datastore/i_unique_store_dictionary.h6
-rw-r--r--vespalib/src/vespa/vespalib/datastore/memory_stats.cpp40
-rw-r--r--vespalib/src/vespa/vespalib/datastore/memory_stats.h32
-rw-r--r--vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp24
-rw-r--r--vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h10
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store.h4
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store.hpp18
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_allocator.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp4
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h6
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.hpp18
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp8
-rw-r--r--vespalib/src/vespa/vespalib/util/generation_hold_list.h106
-rw-r--r--vespalib/src/vespa/vespalib/util/generation_hold_list.hpp88
-rw-r--r--vespalib/src/vespa/vespalib/util/generationhandler.cpp14
-rw-r--r--vespalib/src/vespa/vespalib/util/generationhandler.h14
-rw-r--r--vespalib/src/vespa/vespalib/util/generationholder.cpp60
-rw-r--r--vespalib/src/vespa/vespalib/util/generationholder.h70
-rw-r--r--vespalib/src/vespa/vespalib/util/rcuvector.h2
-rw-r--r--vespalib/src/vespa/vespalib/util/rcuvector.hpp14
-rw-r--r--zkfacade/abi-spec.json63
-rw-r--r--zkfacade/pom.xml6
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java84
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorWrapper.java166
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/SingletonManager.java427
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/api/VespaCurator.java183
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCuratorFramework.java122
-rw-r--r--zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorTest.java3
-rw-r--r--zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorWrapperTest.java333
-rw-r--r--zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java353
-rw-r--r--zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/Learner.java920
-rw-r--r--zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java2
-rw-r--r--zookeeper-server/zookeeper-server/src/test/java/com/yahoo/vespa/zookeper/VespaZooKeeperTest.java10
608 files changed, 11336 insertions, 7806 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1b8d24d690d..3b9bc1c9f07 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@
# @author Vegard Sjonfjell
# @author Eirik Nygaard
# @author Arnstein Ressem
-cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
include(functions.cmake)
list(APPEND CMAKE_MODULE_PATH
@@ -19,8 +19,24 @@ vespa_use_default_java_home()
project(vespa CXX C)
vespa_use_default_vespa_unprivileged()
vespa_use_default_cmake_install_prefix()
+include(GNUInstallDirs)
vespa_use_default_vespa_user()
vespa_use_default_vespa_group()
+vespa_use_default_vespa_deps_prefix()
+vespa_use_default_cmake_prefix_path()
+
+SET(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
+SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
+
+find_package(LLVM REQUIRED CONFIG)
+message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
+message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
+message(STATUS "LLVM_INCLUDE_DIRS is ${LLVM_INCLUDE_DIRS}")
+message(STATUS "LLVM_LIBRARY_DIRS is ${LLVM_LIBRARY_DIRS}")
+message(STATUS "LLVM_INCLUDE_DIR is ${LLVM_INCLUDE_DIR}")
+message(STATUS "LLVM_MAIN_INCLUDE_DIR is ${LLVM_MAIN_INCLUDE_DIR}")
+message(STATUS "LLVM_LIBRARY_DIR is ${LLVM_LIBRARY_DIR}")
+
vespa_use_default_build_settings()
# allows import of project in CLion on OSX
diff --git a/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java b/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
index 081e9ad6036..9696e9c9848 100644
--- a/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
+++ b/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
@@ -3,14 +3,21 @@ package com.yahoo.application.preprocessor;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.model.application.provider.DeployData;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.yolean.Exceptions;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
+import java.util.Set;
/**
* Main entry for preprocessing an application package.
@@ -21,36 +28,75 @@ public class ApplicationPreprocessor {
private final File applicationDir;
private final Optional<File> outputDir;
+ private final Optional<InstanceName> instance;
private final Optional<Environment> environment;
private final Optional<RegionName> region;
+ private final Tags tags;
- public ApplicationPreprocessor(File applicationDir, Optional<File> outputDir, Optional<Environment> environment, Optional<RegionName> region) {
+ public ApplicationPreprocessor(File applicationDir,
+ Optional<File> outputDir,
+ Optional<InstanceName> instance,
+ Optional<Environment> environment,
+ Optional<RegionName> region,
+ Tags tags) {
this.applicationDir = applicationDir;
this.outputDir = outputDir;
+ this.instance = instance;
this.environment = environment;
this.region = region;
+ this.tags = tags;
}
public void run() throws IOException {
FilesApplicationPackage.Builder applicationPackageBuilder = new FilesApplicationPackage.Builder(applicationDir);
outputDir.ifPresent(applicationPackageBuilder::preprocessedDir);
- ApplicationPackage preprocessed = applicationPackageBuilder.build().preprocess(
- new Zone(environment.orElse(Environment.defaultEnvironment()), region.orElse(RegionName.defaultName())),
- new BaseDeployLogger());
+ applicationPackageBuilder.deployData(new DeployData(applicationDir.getAbsolutePath(),
+ ApplicationId.from(TenantName.defaultName(),
+ ApplicationName.defaultName(),
+ instance.orElse(InstanceName.defaultName())),
+ tags,
+ System.currentTimeMillis(),
+ false,
+ 0L,
+ -1));
+
+ ApplicationPackage preprocessed = applicationPackageBuilder.build().preprocess(new Zone(environment.orElse(Environment.defaultEnvironment()),
+ region.orElse(RegionName.defaultName())),
+ new BaseDeployLogger());
preprocessed.validateXML();
}
public static void main(String[] args) {
- int argCount = args.length;
- if (argCount < 1) {
- System.out.println("Usage: vespa-application-preprocessor <application package path> [environment] [region] [output path]");
+ if (args.length < 1) {
+ System.out.println("Usage: vespa-application-preprocessor <application package path> [instance] [environment] [region] [tag] [output path]");
System.exit(1);
}
File applicationDir = new File(args[0]);
- Optional<Environment> environment = (argCount > 1) ? Optional.of(Environment.valueOf(args[1])) : Optional.empty();
- Optional<RegionName> region = (argCount > 2) ? Optional.of(RegionName.from(args[2])) : Optional.empty();
- Optional<File> outputDir = (argCount > 3) ? Optional.of(new File(args[3])) : Optional.empty();
- ApplicationPreprocessor preprocessor = new ApplicationPreprocessor(applicationDir, outputDir, environment, region);
+ Optional<InstanceName> instance;
+ Optional<Environment> environment;
+ Optional<RegionName> region;
+ Tags tags;
+ Optional<File> outputDir;
+ if (args.length <= 4) { // Legacy: No instance and tags
+ instance = Optional.empty();
+ environment = args.length > 1 ? Optional.of(Environment.valueOf(args[1])) : Optional.empty();
+ region = args.length > 2 ? Optional.of(RegionName.from(args[2])) : Optional.empty();
+ tags = Tags.empty();
+ outputDir = args.length > 3 ? Optional.of(new File(args[3])) : Optional.empty();
+ }
+ else {
+ instance = Optional.of(InstanceName.from(args[1]));
+ environment = Optional.of(Environment.valueOf(args[2]));
+ region = Optional.of(RegionName.from(args[3]));
+ tags = Tags.fromString(args[4]);
+ outputDir = args.length > 5 ? Optional.of(new File(args[5])) : Optional.empty();
+ }
+ ApplicationPreprocessor preprocessor = new ApplicationPreprocessor(applicationDir,
+ outputDir,
+ instance,
+ environment,
+ region,
+ tags);
try {
preprocessor.run();
System.out.println("Application preprocessed successfully and written to " +
diff --git a/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java b/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java
index 0e5d8fb2784..4de4312c099 100644
--- a/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java
+++ b/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.application.preprocessor;
+import com.yahoo.config.provision.Tags;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.xml.sax.SAXException;
@@ -18,11 +19,13 @@ public class ApplicationPreprocessorTest {
// Basic test just to check that instantiation and run() works. Unit testing is in config-application-package
@Test
- void basic() throws ParserConfigurationException, TransformerException, SAXException, IOException {
+ void basic() throws IOException {
ApplicationPreprocessor preprocessor = new ApplicationPreprocessor(new File("src/test/resources/simple"),
- Optional.of(newFolder(outputDir, "basic")),
- Optional.empty(),
- Optional.empty());
+ Optional.of(newFolder(outputDir, "basic")),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Tags.empty());
preprocessor.run();
}
diff --git a/application/src/main/java/com/yahoo/application/container/handler/Headers.java b/application/src/main/java/com/yahoo/application/container/handler/Headers.java
index 03b31e38659..53ffe76c923 100644
--- a/application/src/main/java/com/yahoo/application/container/handler/Headers.java
+++ b/application/src/main/java/com/yahoo/application/container/handler/Headers.java
@@ -175,44 +175,44 @@ public class Headers implements Map<String, List<String>> {
}
/**
- * <p>Removes the given value from the entry of the specified key.</p>
+ * Removes the given value from the entry of the specified key.
*
- * @param key The key of the entry to remove from.
- * @param value The value to remove from the entry.
- * @return True if the value was removed.
+ * @param key the key of the entry to remove from
+ * @param value the value to remove from the entry
+ * @return true if the value was removed
*/
public boolean remove(String key, String value) {
return h.remove(key, value);
}
/**
- * <p>Convenience method for retrieving the first value of a named header field. If the header is not set, or if the
- * value list is empty, this method returns null.</p>
+ * Convenience method for retrieving the first value of a named header field. If the header is not set, or if the
+ * value list is empty, this method returns null.
*
- * @param key The key whose first value to return.
- * @return The first value of the named header, or null.
+ * @param key the key whose first value to return.
+ * @return the first value of the named header, or null.
*/
public String getFirst(String key) {
return h.getFirst(key);
}
/**
- * <p>Convenience method for checking whether or not a named header field is <em>true</em>. To satisfy this, the
+ * Convenience method for checking whether a named header field is <em>true</em>. To satisfy this, the
* header field needs to have at least 1 entry, and Boolean.valueOf() of all its values must parse as
- * <em>true</em>.</p>
+ * <em>true</em>.
*
- * @param key The key whose values to parse as a boolean.
- * @return The boolean value of the named header.
+ * @param key the key whose values to parse as a boolean
+ * @return the boolean value of the named header
*/
public boolean isTrue(String key) {
return h.isTrue(key);
}
/**
- * <p>Returns an unmodifiable list of all key-value pairs of this. This provides a flattened view on the content of
- * this map.</p>
+ * Returns an unmodifiable list of all key-value pairs of this. This provides a flattened view on the content of
+ * this map.
*
- * @return The collection of entries.
+ * @return the collection of entries
*/
public List<Entry<String, String>> entries() {
return h.entries();
diff --git a/build_settings.cmake b/build_settings.cmake
index 0976e73442e..0d5b51cc26f 100644
--- a/build_settings.cmake
+++ b/build_settings.cmake
@@ -83,6 +83,9 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden ")
endif()
+if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 11.0)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines")
+endif()
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGOOGLE_PROTOBUF_NO_RDTSC")
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0)
diff --git a/client/go/cmd/logfmt/cmd.go b/client/go/cmd/logfmt/cmd.go
index 54e82844a30..84322c7ff08 100644
--- a/client/go/cmd/logfmt/cmd.go
+++ b/client/go/cmd/logfmt/cmd.go
@@ -38,6 +38,7 @@ and converts it to something human-readable`,
cmd.Flags().StringVarP(&curOptions.OnlyHostname, "host", "H", "", "select only one host")
cmd.Flags().StringVarP(&curOptions.OnlyPid, "pid", "p", "", "select only one process ID")
cmd.Flags().StringVarP(&curOptions.OnlyService, "service", "S", "", "select only one service")
+ cmd.Flags().VarP(&curOptions.Format, "format", "F", "select logfmt output format, vespa (default), json or raw are supported. The json output format is not stable, and will change in the future.")
cmd.Flags().MarkHidden("tc")
cmd.Flags().MarkHidden("ts")
cmd.Flags().MarkHidden("dequotenewlines")
diff --git a/client/go/cmd/logfmt/formatflags.go b/client/go/cmd/logfmt/formatflags.go
new file mode 100644
index 00000000000..097746d696f
--- /dev/null
+++ b/client/go/cmd/logfmt/formatflags.go
@@ -0,0 +1,41 @@
+package logfmt
+
+import (
+ "fmt"
+ "strings"
+)
+
+type OutputFormat int
+
+const (
+ FormatVespa OutputFormat = iota //default is vespa
+ FormatRaw
+ FormatJSON
+)
+
+func (v *OutputFormat) Type() string {
+ return "output format"
+}
+
+func (v *OutputFormat) String() string {
+ flagNames := []string{
+ "vespa",
+ "raw",
+ "json",
+ }
+ return flagNames[*v]
+}
+
+func (v *OutputFormat) Set(val string) error {
+ switch strings.ToLower(val) {
+ case "vespa":
+ *v = FormatVespa
+ case "raw":
+ *v = FormatRaw
+ case "json":
+ *v = FormatJSON
+ default:
+ return fmt.Errorf("'%s' is not a valid format argument", val)
+ }
+ return nil
+}
diff --git a/client/go/cmd/logfmt/formatflags_test.go b/client/go/cmd/logfmt/formatflags_test.go
new file mode 100644
index 00000000000..53c47d24208
--- /dev/null
+++ b/client/go/cmd/logfmt/formatflags_test.go
@@ -0,0 +1,30 @@
+package logfmt
+
+import (
+ "fmt"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestOutputFormat(t *testing.T) {
+ type args struct {
+ val string
+ }
+ tests := []struct {
+ expected OutputFormat
+ arg string
+ wantErr assert.ErrorAssertionFunc
+ }{
+ {FormatVespa, "vespa", assert.NoError},
+ {FormatRaw, "raw", assert.NoError},
+ {FormatJSON, "json", assert.NoError},
+ {-1, "foo", assert.Error},
+ }
+ for _, tt := range tests {
+ t.Run(tt.arg, func(t *testing.T) {
+ var v OutputFormat = -1
+ tt.wantErr(t, v.Set(tt.arg), fmt.Sprintf("Set(%v)", tt.arg))
+ assert.Equal(t, v, tt.expected)
+ })
+ }
+}
diff --git a/client/go/cmd/logfmt/handleline.go b/client/go/cmd/logfmt/handleline.go
index 33a1a1b386b..813ca82acb4 100644
--- a/client/go/cmd/logfmt/handleline.go
+++ b/client/go/cmd/logfmt/handleline.go
@@ -5,58 +5,122 @@
package logfmt
import (
+ "bytes"
+ "encoding/json"
"fmt"
"strconv"
"strings"
"time"
)
-// handle a line in "vespa.log" format; do filtering and formatting as specified in opts
+type logFields struct {
+ timestamp string // seconds, optional fractional seconds
+ host string
+ pid string // pid, optional tid
+ service string
+ component string
+ level string
+ messages []string
+}
-func handleLine(opts *Options, line string) (output string, err error) {
- fields := strings.SplitN(line, "\t", 7)
- if len(fields) < 7 {
+// handle a line in "vespa.log" format; do filtering and formatting as specified in opts
+func handleLine(opts *Options, line string) (string, error) {
+ fieldStrings := strings.SplitN(line, "\t", 7)
+ if len(fieldStrings) < 7 {
return "", fmt.Errorf("not enough fields: '%s'", line)
}
- timestampfield := fields[0] // seconds, optional fractional seconds
- hostfield := fields[1]
- pidfield := fields[2] // pid, optional tid
- servicefield := fields[3]
- componentfield := fields[4]
- levelfield := fields[5]
- messagefields := fields[6:]
+ fields := logFields{
+ timestamp: fieldStrings[0],
+ host: fieldStrings[1],
+ pid: fieldStrings[2],
+ service: fieldStrings[3],
+ component: fieldStrings[4],
+ level: fieldStrings[5],
+ messages: fieldStrings[6:],
+ }
- if !opts.showLevel(levelfield) {
+ if !opts.showLevel(fields.level) {
return "", nil
}
- if opts.OnlyHostname != "" && opts.OnlyHostname != hostfield {
+ if opts.OnlyHostname != "" && opts.OnlyHostname != fields.host {
return "", nil
}
- if opts.OnlyPid != "" && opts.OnlyPid != pidfield {
+ if opts.OnlyPid != "" && opts.OnlyPid != fields.pid {
return "", nil
}
- if opts.OnlyService != "" && opts.OnlyService != servicefield {
+ if opts.OnlyService != "" && opts.OnlyService != fields.service {
return "", nil
}
- if opts.OnlyInternal && !isInternal(componentfield) {
+ if opts.OnlyInternal && !isInternal(fields.component) {
return "", nil
}
- if opts.ComponentFilter.unmatched(componentfield) {
+ if opts.ComponentFilter.unmatched(fields.component) {
return "", nil
}
- if opts.MessageFilter.unmatched(strings.Join(messagefields, "\t")) {
+ if opts.MessageFilter.unmatched(strings.Join(fields.messages, "\t")) {
return "", nil
}
+ switch opts.Format {
+ case FormatRaw:
+ return line + "\n", nil
+ case FormatJSON:
+ return handleLineJson(opts, &fields)
+ case FormatVespa:
+ fallthrough
+ default:
+ return handleLineVespa(opts, &fields)
+ }
+}
+
+func parseTimestamp(timestamp string) (time.Time, error) {
+ secs, err := strconv.ParseFloat(timestamp, 64)
+ if err != nil {
+ return time.Time{}, err
+ }
+ nsecs := int64(secs * 1e9)
+ return time.Unix(0, nsecs), nil
+}
+
+type logFieldsJson struct {
+ Timestamp string `json:"timestamp"`
+ Host string `json:"host"`
+ Pid string `json:"pid"`
+ Service string `json:"service"`
+ Component string `json:"component"`
+ Level string `json:"level"`
+ Messages []string `json:"messages"`
+}
+
+func handleLineJson(_ *Options, fields *logFields) (string, error) {
+ timestamp, err := parseTimestamp(fields.timestamp)
+ if err != nil {
+ return "", err
+ }
+ outputFields := logFieldsJson{
+ Timestamp: timestamp.Format(time.RFC3339Nano),
+ Host: fields.host,
+ Pid: fields.pid,
+ Service: fields.service,
+ Component: fields.component,
+ Level: fields.level,
+ Messages: fields.messages,
+ }
+ buf := bytes.Buffer{}
+ if err := json.NewEncoder(&buf).Encode(&outputFields); err != nil {
+ return "", err
+ }
+ return buf.String(), nil
+}
+
+func handleLineVespa(opts *Options, fields *logFields) (string, error) {
var buf strings.Builder
if opts.showField("fmttime") {
- secs, err := strconv.ParseFloat(timestampfield, 64)
+ timestamp, err := parseTimestamp(fields.timestamp)
if err != nil {
return "", err
}
- nsecs := int64(secs * 1e9)
- timestamp := time.Unix(0, nsecs)
if opts.showField("usecs") {
buf.WriteString(timestamp.Format("[2006-01-02 15:04:05.000000] "))
} else if opts.showField("msecs") {
@@ -65,36 +129,36 @@ func handleLine(opts *Options, line string) (output string, err error) {
buf.WriteString(timestamp.Format("[2006-01-02 15:04:05] "))
}
} else if opts.showField("time") {
- buf.WriteString(timestampfield)
+ buf.WriteString(fields.timestamp)
buf.WriteString(" ")
}
if opts.showField("host") {
- buf.WriteString(fmt.Sprintf("%-8s ", hostfield))
+ buf.WriteString(fmt.Sprintf("%-8s ", fields.host))
}
if opts.showField("level") {
- buf.WriteString(fmt.Sprintf("%-7s ", strings.ToUpper(levelfield)))
+ buf.WriteString(fmt.Sprintf("%-7s ", strings.ToUpper(fields.level)))
}
if opts.showField("pid") {
// OnlyPid, _, _ := strings.Cut(pidfield, "/")
- buf.WriteString(fmt.Sprintf("%6s ", pidfield))
+ buf.WriteString(fmt.Sprintf("%6s ", fields.pid))
}
if opts.showField("service") {
if opts.TruncateService {
- buf.WriteString(fmt.Sprintf("%-9.9s ", servicefield))
+ buf.WriteString(fmt.Sprintf("%-9.9s ", fields.service))
} else {
- buf.WriteString(fmt.Sprintf("%-16s ", servicefield))
+ buf.WriteString(fmt.Sprintf("%-16s ", fields.service))
}
}
if opts.showField("component") {
if opts.TruncateComponent {
- buf.WriteString(fmt.Sprintf("%-15.15s ", componentfield))
+ buf.WriteString(fmt.Sprintf("%-15.15s ", fields.component))
} else {
- buf.WriteString(fmt.Sprintf("%s\t", componentfield))
+ buf.WriteString(fmt.Sprintf("%s\t", fields.component))
}
}
if opts.showField("message") {
var msgBuf strings.Builder
- for idx, message := range messagefields {
+ for idx, message := range fields.messages {
if idx > 0 {
msgBuf.WriteString("\n\t")
}
@@ -111,6 +175,5 @@ func handleLine(opts *Options, line string) (output string, err error) {
buf.WriteString(message)
}
buf.WriteString("\n")
- output = buf.String()
- return
+ return buf.String(), nil
}
diff --git a/client/go/cmd/logfmt/options.go b/client/go/cmd/logfmt/options.go
index f564ffd4df0..864868d4ce5 100644
--- a/client/go/cmd/logfmt/options.go
+++ b/client/go/cmd/logfmt/options.go
@@ -24,6 +24,7 @@ type Options struct {
TruncateComponent bool
ComponentFilter regexFlag
MessageFilter regexFlag
+ Format OutputFormat
}
func NewOptions() (ret Options) {
diff --git a/client/pom.xml b/client/pom.xml
index 065cb2c4317..1da3cbc68c5 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -25,7 +25,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
- <version>1.6</version>
+ <version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
diff --git a/cloud-tenant-base-dependencies-enforcer/pom.xml b/cloud-tenant-base-dependencies-enforcer/pom.xml
index 17b7c7b8995..10f66d355b9 100644
--- a/cloud-tenant-base-dependencies-enforcer/pom.xml
+++ b/cloud-tenant-base-dependencies-enforcer/pom.xml
@@ -21,7 +21,7 @@
<!-- MUST BE KEPT IN SYNC WITH parent/pom.xml -->
<athenz.version>1.10.54</athenz.version>
- <bouncycastle.version>1.68</bouncycastle.version>
+ <bouncycastle.version>1.72</bouncycastle.version>
<felix.version>7.0.1</felix.version>
<httpclient5.version>5.1.3</httpclient5.version>
<httpclient.version>4.5.13</httpclient.version>
@@ -195,8 +195,9 @@
<include>org.apache.httpcomponents:httpmime:${httpclient.version}:jar:test</include>
<include>org.apache.opennlp:opennlp-tools:1.9.3:jar:test</include>
<include>org.apiguardian:apiguardian-api:1.1.0:jar:test</include>
- <include>org.bouncycastle:bcpkix-jdk15on:[${bouncycastle.version}]:jar:test</include>
- <include>org.bouncycastle:bcprov-jdk15on:[${bouncycastle.version}]:jar:test</include>
+ <include>org.bouncycastle:bcpkix-jdk18on:[${bouncycastle.version}]:jar:test</include>
+ <include>org.bouncycastle:bcprov-jdk18on:[${bouncycastle.version}]:jar:test</include>
+ <include>org.bouncycastle:bcutil-jdk18on:[${bouncycastle.version}]:jar:test</include>
<include>org.eclipse.jetty.alpn:alpn-api:[${jetty-alpn.version}]:jar:test</include>
<include>org.eclipse.jetty.http2:http2-common:[${jetty.version}]:jar:test</include>
<include>org.eclipse.jetty.http2:http2-hpack:[${jetty.version}]:jar:test</include>
diff --git a/clustercontroller-apps/src/main/java/com/yahoo/vespa/clustercontroller/apps/clustercontroller/StateRestApiV2Handler.java b/clustercontroller-apps/src/main/java/com/yahoo/vespa/clustercontroller/apps/clustercontroller/StateRestApiV2Handler.java
index 5ef4c544bc8..346e58b652f 100644
--- a/clustercontroller-apps/src/main/java/com/yahoo/vespa/clustercontroller/apps/clustercontroller/StateRestApiV2Handler.java
+++ b/clustercontroller-apps/src/main/java/com/yahoo/vespa/clustercontroller/apps/clustercontroller/StateRestApiV2Handler.java
@@ -15,6 +15,7 @@ import java.util.TreeMap;
import java.util.logging.Logger;
public class StateRestApiV2Handler extends JDiscHttpRequestHandler {
+
private static final Logger log = Logger.getLogger(StateRestApiV2Handler.class.getName());
@Inject
diff --git a/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java b/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java
index d5324a3f0b8..5c369d2508e 100644
--- a/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java
+++ b/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java
@@ -15,7 +15,6 @@ import com.yahoo.vespa.curator.Lock;
import com.yahoo.yolean.Exceptions;
import java.io.Closeable;
-import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
diff --git a/component/src/main/java/com/yahoo/component/ComponentId.java b/component/src/main/java/com/yahoo/component/ComponentId.java
index 7320d24b57c..b869202d3c7 100644
--- a/component/src/main/java/com/yahoo/component/ComponentId.java
+++ b/component/src/main/java/com/yahoo/component/ComponentId.java
@@ -16,19 +16,11 @@ public final class ComponentId implements Comparable<ComponentId> {
private final Spec<Version> spec;
private final boolean anonymous;
- private static AtomicInteger threadIdCounter = new AtomicInteger(0);
+ private static final AtomicInteger threadIdCounter = new AtomicInteger(0);
- private static ThreadLocal<Counter> threadLocalUniqueId = new ThreadLocal<Counter>() {
- @Override protected Counter initialValue() {
- return new Counter();
- }
- };
+ private static final ThreadLocal<Counter> threadLocalUniqueId = ThreadLocal.withInitial(Counter::new);
- private static ThreadLocal<String> threadId = new ThreadLocal<String>() {
- @Override protected String initialValue() {
- return new String("_" + threadIdCounter.getAndIncrement() + "_");
- }
- };
+ private static final ThreadLocal<String> threadId = ThreadLocal.withInitial(() -> "_" + threadIdCounter.getAndIncrement() + "_");
/** Precomputed string value */
private final String stringValue;
@@ -97,9 +89,8 @@ public final class ComponentId implements Comparable<ComponentId> {
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if ( ! (o instanceof ComponentId)) return false;
+ if ( ! (o instanceof ComponentId c)) return false;
- ComponentId c = (ComponentId) o;
if (isAnonymous() || c.isAnonymous()) // TODO: Stop doing this
return false;
@@ -221,7 +212,7 @@ public final class ComponentId implements Comparable<ComponentId> {
return new ComponentId(splitter.name, Version.fromString(splitter.version), splitter.namespace, true);
}
- private final class VersionHandler implements Spec.VersionHandler<Version> {
+ private static final class VersionHandler implements Spec.VersionHandler<Version> {
@Override
public Version emptyVersion() {
diff --git a/config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java b/config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java
index d7efca3b723..21bb193ef93 100644
--- a/config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java
+++ b/config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.text.XML;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -23,7 +24,7 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
- * Handles overrides in a XML document according to the rules defined for multi environment application packages.
+ * Handles overrides in a XML document according to the rules defined for multi-environment application packages.
*
* Rules:
*
@@ -41,16 +42,19 @@ class OverrideProcessor implements PreProcessor {
private final InstanceName instance;
private final Environment environment;
private final RegionName region;
+ private final Tags tags;
private static final String ID_ATTRIBUTE = "id";
private static final String INSTANCE_ATTRIBUTE = "instance";
private static final String ENVIRONMENT_ATTRIBUTE = "environment";
private static final String REGION_ATTRIBUTE = "region";
+ private static final String TAGS_ATTRIBUTE = "tags";
- public OverrideProcessor(InstanceName instance, Environment environment, RegionName region) {
+ public OverrideProcessor(InstanceName instance, Environment environment, RegionName region, Tags tags) {
this.instance = instance;
this.environment = environment;
this.region = region;
+ this.tags = tags;
}
public Document process(Document input) throws TransformerException {
@@ -80,6 +84,7 @@ class OverrideProcessor implements PreProcessor {
child.removeAttributeNS(XmlPreProcessor.deployNamespaceUri, INSTANCE_ATTRIBUTE);
child.removeAttributeNS(XmlPreProcessor.deployNamespaceUri, ENVIRONMENT_ATTRIBUTE);
child.removeAttributeNS(XmlPreProcessor.deployNamespaceUri, REGION_ATTRIBUTE);
+ child.removeAttributeNS(XmlPreProcessor.deployNamespaceUri, TAGS_ATTRIBUTE);
}
}
@@ -87,13 +92,16 @@ class OverrideProcessor implements PreProcessor {
Set<InstanceName> instances = context.instances;
Set<Environment> environments = context.environments;
Set<RegionName> regions = context.regions;
+ Tags tags = context.tags;
if (instances.isEmpty())
instances = getInstances(parent);
if (environments.isEmpty())
environments = getEnvironments(parent);
if (regions.isEmpty())
regions = getRegions(parent);
- return Context.create(instances, environments, regions);
+ if (tags.isEmpty())
+ tags = getTags(parent);
+ return Context.create(instances, environments, regions, tags);
}
/**
@@ -128,17 +136,21 @@ class OverrideProcessor implements PreProcessor {
throw new IllegalArgumentException("Regions in child (" + regions +
") are not a subset of those of the parent (" + context.regions + ") at " + child);
}
+
+ Tags tags = getTags(child);
+ if ( ! tags.isEmpty() && ! context.tags.isEmpty() && ! context.tags.containsAll(tags)) {
+ throw new IllegalArgumentException("Tags in child (" + environments +
+ ") are not a subset of those of the parent (" + context.tags + ") at " + child);
+ }
}
}
- /**
- * Prune elements that are not matching our environment and region
- */
+ /** Prune elements that are not matching our environment and region. */
private void pruneNonMatching(Element parent, List<Element> children) {
Iterator<Element> elemIt = children.iterator();
while (elemIt.hasNext()) {
Element child = elemIt.next();
- if ( ! matches(getInstances(child), getEnvironments(child), getRegions(child))) {
+ if ( ! matches(getInstances(child), getEnvironments(child), getRegions(child), getTags(child))) {
parent.removeChild(child);
elemIt.remove();
}
@@ -147,7 +159,8 @@ class OverrideProcessor implements PreProcessor {
private boolean matches(Set<InstanceName> elementInstances,
Set<Environment> elementEnvironments,
- Set<RegionName> elementRegions) {
+ Set<RegionName> elementRegions,
+ Tags elementTags) {
if ( ! elementInstances.isEmpty()) { // match instance
if ( ! elementInstances.contains(instance)) return false;
}
@@ -164,12 +177,14 @@ class OverrideProcessor implements PreProcessor {
if ( ! environment.isMultiRegion() && elementEnvironments.isEmpty() ) return false;
}
+ if ( ! elementTags.isEmpty()) { // match tags
+ if ( ! elementTags.intersects(tags)) return false;
+ }
+
return true;
}
- /**
- * Find the most specific element and remove all others.
- */
+ /** Find the most specific element and remove all others. */
private void retainMostSpecific(Element parent, List<Element> children, Context context) {
// Keep track of elements with highest number of matches (might be more than one element with same tag, need a list)
List<Element> bestMatches = new ArrayList<>();
@@ -205,12 +220,15 @@ class OverrideProcessor implements PreProcessor {
Set<InstanceName> elementInstances = hasInstance(child) ? getInstances(child) : context.instances;
Set<Environment> elementEnvironments = hasEnvironment(child) ? getEnvironments(child) : context.environments;
Set<RegionName> elementRegions = hasRegion(child) ? getRegions(child) : context.regions;
+ Tags elementTags = hasTag(child) ? getTags(child) : context.tags;
if ( ! elementInstances.isEmpty() && elementInstances.contains(instance))
currentMatch++;
if ( ! elementEnvironments.isEmpty() && elementEnvironments.contains(environment))
currentMatch++;
if ( ! elementRegions.isEmpty() && elementRegions.contains(region))
currentMatch++;
+ if ( elementTags.intersects(tags))
+ currentMatch++;
return currentMatch;
}
@@ -233,16 +251,14 @@ class OverrideProcessor implements PreProcessor {
return false;
}
- /**
- * Retains all elements where at least one element is overridden. Removes non-overridden elements from map.
- */
+ /** Retains all elements where at least one element is overridden. Removes non-overridden elements from map. */
private void retainOverriddenElements(Map<String, List<Element>> elementsByTagName) {
Iterator<Map.Entry<String, List<Element>>> it = elementsByTagName.entrySet().iterator();
while (it.hasNext()) {
List<Element> elements = it.next().getValue();
boolean hasOverrides = false;
for (Element element : elements) {
- if (hasEnvironment(element) || hasRegion(element)) {
+ if (hasInstance(element) || hasEnvironment(element) || hasRegion(element) || hasTag(element)) {
hasOverrides = true;
}
}
@@ -264,24 +280,34 @@ class OverrideProcessor implements PreProcessor {
return element.hasAttributeNS(XmlPreProcessor.deployNamespaceUri, ENVIRONMENT_ATTRIBUTE);
}
+ private boolean hasTag(Element element) {
+ return element.hasAttributeNS(XmlPreProcessor.deployNamespaceUri, TAGS_ATTRIBUTE);
+ }
+
private Set<InstanceName> getInstances(Element element) {
String instance = element.getAttributeNS(XmlPreProcessor.deployNamespaceUri, INSTANCE_ATTRIBUTE);
- if (instance == null || instance.isEmpty()) return Collections.emptySet();
+ if (instance == null || instance.isEmpty()) return Set.of();
return Arrays.stream(instance.split(" ")).map(InstanceName::from).collect(Collectors.toSet());
}
private Set<Environment> getEnvironments(Element element) {
String env = element.getAttributeNS(XmlPreProcessor.deployNamespaceUri, ENVIRONMENT_ATTRIBUTE);
- if (env == null || env.isEmpty()) return Collections.emptySet();
+ if (env == null || env.isEmpty()) return Set.of();
return Arrays.stream(env.split(" ")).map(Environment::from).collect(Collectors.toSet());
}
private Set<RegionName> getRegions(Element element) {
String reg = element.getAttributeNS(XmlPreProcessor.deployNamespaceUri, REGION_ATTRIBUTE);
- if (reg == null || reg.isEmpty()) return Collections.emptySet();
+ if (reg == null || reg.isEmpty()) return Set.of();
return Arrays.stream(reg.split(" ")).map(RegionName::from).collect(Collectors.toSet());
}
+ private Tags getTags(Element element) {
+ String env = element.getAttributeNS(XmlPreProcessor.deployNamespaceUri, TAGS_ATTRIBUTE);
+ if (env == null || env.isEmpty()) return Tags.empty();
+ return Tags.fromString(env);
+ }
+
private Map<String, List<Element>> elementsByTagNameAndId(List<Element> children) {
Map<String, List<Element>> elementsByTagName = new LinkedHashMap<>();
// Index by tag name
@@ -336,21 +362,27 @@ class OverrideProcessor implements PreProcessor {
final Set<InstanceName> instances;
final Set<Environment> environments;
final Set<RegionName> regions;
+ final Tags tags;
- private Context(Set<InstanceName> instances, Set<Environment> environments, Set<RegionName> regions) {
+ private Context(Set<InstanceName> instances,
+ Set<Environment> environments,
+ Set<RegionName> regions,
+ Tags tags) {
this.instances = Set.copyOf(instances);
this.environments = Set.copyOf(environments);
this.regions = Set.copyOf(regions);
+ this.tags = tags;
}
static Context empty() {
- return new Context(Set.of(), Set.of(), Set.of());
+ return new Context(Set.of(), Set.of(), Set.of(), Tags.empty());
}
public static Context create(Set<InstanceName> instances,
Set<Environment> environments,
- Set<RegionName> regions) {
- return new Context(instances, environments, regions);
+ Set<RegionName> regions,
+ Tags tags) {
+ return new Context(instances, environments, regions, tags);
}
}
diff --git a/config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java b/config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java
index ba68894c9f9..42333ea7662 100644
--- a/config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java
+++ b/config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java
@@ -5,6 +5,7 @@ import com.yahoo.config.application.FileSystemWrapper.FileWrapper;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.text.XML;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
@@ -19,6 +20,7 @@ import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
* A preprocessor for services.xml files that handles deploy:environment, deploy:region, preprocess:properties, preprocess:include
@@ -38,22 +40,53 @@ public class XmlPreProcessor {
private final InstanceName instance;
private final Environment environment;
private final RegionName region;
+ private final Tags tags;
private final List<PreProcessor> chain;
- public XmlPreProcessor(File applicationDir, File xmlInput, InstanceName instance, Environment environment, RegionName region) throws IOException {
- this(applicationDir, new FileReader(xmlInput), instance, environment, region);
+ // TODO: Remove after November 2022
+ public XmlPreProcessor(File applicationDir,
+ File xmlInput,
+ InstanceName instance,
+ Environment environment,
+ RegionName region) throws IOException {
+ this(applicationDir, new FileReader(xmlInput), instance, environment, region, Tags.empty());
}
- public XmlPreProcessor(File applicationDir, Reader xmlInput, InstanceName instance, Environment environment, RegionName region) {
- this(FileSystemWrapper.getDefault(applicationDir.toPath()).wrap(applicationDir.toPath()), xmlInput, instance, environment, region);
+ public XmlPreProcessor(File applicationDir,
+ File xmlInput,
+ InstanceName instance,
+ Environment environment,
+ RegionName region,
+ Tags tags) throws IOException {
+ this(applicationDir, new FileReader(xmlInput), instance, environment, region, tags);
}
- public XmlPreProcessor(FileWrapper applicationDir, Reader xmlInput, InstanceName instance, Environment environment, RegionName region) {
+ public XmlPreProcessor(File applicationDir,
+ Reader xmlInput,
+ InstanceName instance,
+ Environment environment,
+ RegionName region,
+ Tags tags) {
+ this(FileSystemWrapper.getDefault(applicationDir.toPath()).wrap(applicationDir.toPath()),
+ xmlInput,
+ instance,
+ environment,
+ region,
+ tags);
+ }
+
+ public XmlPreProcessor(FileWrapper applicationDir,
+ Reader xmlInput,
+ InstanceName instance,
+ Environment environment,
+ RegionName region,
+ Tags tags) {
this.applicationDir = applicationDir;
this.xmlInput = xmlInput;
this.instance = instance;
this.environment = environment;
this.region = region;
+ this.tags = tags;
this.chain = setupChain();
}
@@ -73,7 +106,7 @@ public class XmlPreProcessor {
private List<PreProcessor> setupChain() {
List<PreProcessor> chain = new ArrayList<>();
chain.add(new IncludeProcessor(applicationDir));
- chain.add(new OverrideProcessor(instance, environment, region));
+ chain.add(new OverrideProcessor(instance, environment, region, tags));
chain.add(new PropertiesProcessor());
chain.add(new ValidationProcessor()); // must be last in chain
return chain;
diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java
index 279af646a8c..c3e9b99f562 100644
--- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java
+++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java
@@ -2,6 +2,9 @@
package com.yahoo.config.model.application.provider;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
+
+import java.util.Set;
/**
* Data generated or computed during deployment
@@ -12,6 +15,8 @@ public class DeployData {
private final ApplicationId applicationId;
+ private final Tags tags;
+
/** The absolute path to the directory holding the application */
private final String deployedFromDir;
@@ -25,25 +30,16 @@ public class DeployData {
private final long generation;
private final long currentlyActiveGeneration;
- // TODO: Remove when oldest version in use is 8.13
- public DeployData(String ignored,
- String deployedFromDir,
- ApplicationId applicationId,
- Long deployTimestamp,
- boolean internalRedeploy,
- Long generation,
- long currentlyActiveGeneration) {
- this(deployedFromDir, applicationId, deployTimestamp, internalRedeploy, generation, currentlyActiveGeneration);
- }
-
public DeployData(String deployedFromDir,
ApplicationId applicationId,
+ Tags tags,
Long deployTimestamp,
boolean internalRedeploy,
Long generation,
long currentlyActiveGeneration) {
this.deployedFromDir = deployedFromDir;
this.applicationId = applicationId;
+ this.tags = tags;
this.deployTimestamp = deployTimestamp;
this.internalRedeploy = internalRedeploy;
this.generation = generation;
@@ -62,4 +58,6 @@ public class DeployData {
public ApplicationId getApplicationId() { return applicationId; }
+ public Tags getTags() { return tags; }
+
}
diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
index 7b483d0603c..e61ea01a99a 100644
--- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
+++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
@@ -17,6 +17,7 @@ import com.yahoo.config.model.application.AbstractApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.io.HexDump;
@@ -138,6 +139,7 @@ public class FilesApplicationPackage extends AbstractApplicationPackage {
deployData.getDeployTimestamp(),
deployData.isInternalRedeploy(),
deployData.getApplicationId(),
+ deployData.getTags(),
computeCheckSum(appDir),
deployData.getGeneration(),
deployData.getCurrentlyActiveGeneration());
@@ -484,6 +486,7 @@ public class FilesApplicationPackage extends AbstractApplicationPackage {
ApplicationId.from(TenantName.defaultName(),
ApplicationName.from(originalAppDir),
InstanceName.defaultName()),
+ Tags.empty(),
"",
0L,
0L);
@@ -583,7 +586,8 @@ public class FilesApplicationPackage extends AbstractApplicationPackage {
inputXml,
metaData.getApplicationId().instance(),
zone.environment(),
- zone.region())
+ zone.region(),
+ metaData.getTags())
.run();
try (FileOutputStream outputStream = new FileOutputStream(destination)) {
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTagsTest.java b/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTagsTest.java
new file mode 100644
index 00000000000..8d7431d33b6
--- /dev/null
+++ b/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTagsTest.java
@@ -0,0 +1,124 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.application;
+
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.Test;
+import org.w3c.dom.Document;
+
+import javax.xml.transform.TransformerException;
+import java.io.StringReader;
+
+/**
+ * @author bratseth
+ */
+public class HostedOverrideProcessorTagsTest {
+
+ static {
+ XMLUnit.setIgnoreWhitespace(true);
+ }
+
+ private static final String input =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='5' deploy:tags='a' deploy:environment='perf'/>" +
+ " <nodes count='10' deploy:tags='a b'/>" +
+ " <nodes count='20' deploy:tags='c'/>" +
+ " <search deploy:tags='b'/>" +
+ " <document-api deploy:tags='d'/>" +
+ " </container>" +
+ "</services>";
+
+ @Test
+ public void testParsingTagAPerf() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='5' required='true'/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.perf,
+ RegionName.defaultName(),
+ Tags.fromString("a"),
+ expected);
+ }
+
+ @Test
+ public void testParsingTagAProd() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='10' required='true'/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.fromString("a"),
+ expected);
+ }
+
+ @Test
+ public void testParsingTagB() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='10' required='true'/>" +
+ " <search/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.fromString("b"),
+ expected);
+ }
+
+ @Test
+ public void testParsingTagC() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='20' required='true'/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.fromString("c"),
+ expected);
+ }
+
+ @Test
+ public void testParsingTagCAndD() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='20' required='true'/>" +
+ " <document-api/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.fromString("c d"),
+ expected);
+ }
+
+ private void assertOverride(InstanceName instance, Environment environment, RegionName region, Tags tags, String expected) throws TransformerException {
+ Document inputDoc = Xml.getDocument(new StringReader(input));
+ Document newDoc = new OverrideProcessor(instance, environment, region, tags).process(inputDoc);
+ TestBase.assertDocument(expected, newDoc);
+ }
+
+}
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java
index 1a4dab01930..451c7a3c217 100644
--- a/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Test;
import org.w3c.dom.Document;
@@ -14,6 +15,7 @@ import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.io.StringReader;
+import java.util.List;
/**
* @author bratseth
@@ -48,7 +50,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='1'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.test, RegionName.defaultName(), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.test,
+ RegionName.defaultName(),
+ Tags.empty(),
+ expected);
}
@Test
@@ -60,7 +66,27 @@ public class HostedOverrideProcessorTest {
" <nodes count='4' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("prod"), RegionName.from("us-west"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("prod"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
+ }
+
+ @Test
+ public void testParsingSpecificTag() throws TransformerException {
+ String expected =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
+ "<services xmlns:deploy=\"vespa\" xmlns:preprocess=\"?\" version=\"1.0\">" +
+ " <container id=\"foo\" version=\"1.0\">" +
+ " <nodes count='4' required='true'/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("prod"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -72,7 +98,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='1' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(InstanceName.from("myinstance"), Environment.from("prod"), RegionName.from("us-west"), expected);
+ assertOverride(InstanceName.from("myinstance"),
+ Environment.from("prod"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -84,7 +114,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='5' flavor='v-8-8-100' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("prod"), RegionName.from("us-east-3"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("prod"),
+ RegionName.from("us-east-3"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -96,7 +130,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='3' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("perf"), RegionName.from("us-east-3"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("perf"),
+ RegionName.from("us-east-3"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -108,7 +146,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='3' flavor='v-4-8-100' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.valueOf("prod"), RegionName.from("unknown"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.valueOf("prod"),
+ RegionName.from("unknown"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -120,7 +162,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='3' flavor='v-4-8-100' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("prod"), RegionName.defaultName(), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("prod"),
+ RegionName.defaultName(),
+ Tags.empty(),
+ expected);
}
@Test
@@ -132,7 +178,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='1'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("dev"), RegionName.defaultName(), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("dev"),
+ RegionName.defaultName(),
+ Tags.empty(),
+ expected);
}
@Test
@@ -144,7 +194,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='1'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("test"), RegionName.from("us-west"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("test"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -156,16 +210,16 @@ public class HostedOverrideProcessorTest {
" <nodes count='2' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("staging"), RegionName.from("us-west"), expected);
- }
-
- private void assertOverride(Environment environment, RegionName region, String expected) throws TransformerException {
- assertOverride(InstanceName.from("default"), environment, region, expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("staging"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
}
- private void assertOverride(InstanceName instance, Environment environment, RegionName region, String expected) throws TransformerException {
+ private void assertOverride(InstanceName instance, Environment environment, RegionName region, Tags tags, String expected) throws TransformerException {
Document inputDoc = Xml.getDocument(new StringReader(input));
- Document newDoc = new OverrideProcessor(instance, environment, region).process(inputDoc);
+ Document newDoc = new OverrideProcessor(instance, environment, region, tags).process(inputDoc);
TestBase.assertDocument(expected, newDoc);
}
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java
index 44bcb12957a..debde6c1438 100644
--- a/config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Test;
import org.w3c.dom.Document;
@@ -124,13 +125,13 @@ public class MultiOverrideProcessorTest {
private void assertOverride(Environment environment, RegionName region, String expected) throws TransformerException {
Document inputDoc = Xml.getDocument(new StringReader(input));
- Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region).process(inputDoc);
+ Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region, Tags.empty()).process(inputDoc);
TestBase.assertDocument(expected, newDoc);
}
private void assertOverrideWithIds(Environment environment, RegionName region, String expected) throws TransformerException {
Document inputDoc = Xml.getDocument(new StringReader(inputWithIds));
- Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region).process(inputDoc);
+ Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region, Tags.empty()).process(inputDoc);
TestBase.assertDocument(expected, newDoc);
}
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java
index e9ee7c97876..150999390d8 100644
--- a/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Test;
import org.w3c.dom.Document;
@@ -332,7 +333,10 @@ public class OverrideProcessorTest {
" </admin>" +
"</services>";
Document inputDoc = Xml.getDocument(new StringReader(in));
- new OverrideProcessor(InstanceName.from("default"), Environment.from("prod"), RegionName.from("us-west")).process(inputDoc);
+ new OverrideProcessor(InstanceName.from("default"),
+ Environment.from("prod"),
+ RegionName.from("us-west"),
+ Tags.empty()).process(inputDoc);
}
@Test(expected = IllegalArgumentException.class)
@@ -344,7 +348,10 @@ public class OverrideProcessorTest {
" </admin>" +
"</services>";
Document inputDoc = Xml.getDocument(new StringReader(in));
- new OverrideProcessor(InstanceName.from("default"), Environment.defaultEnvironment(), RegionName.from("us-west")).process(inputDoc);
+ new OverrideProcessor(InstanceName.from("default"),
+ Environment.defaultEnvironment(),
+ RegionName.from("us-west"),
+ Tags.empty()).process(inputDoc);
}
@Test
@@ -399,7 +406,7 @@ public class OverrideProcessorTest {
private void assertOverride(String input, Environment environment, RegionName region, String expected) throws TransformerException {
Document inputDoc = Xml.getDocument(new StringReader(input));
- Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region).process(inputDoc);
+ Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region, Tags.empty()).process(inputDoc);
TestBase.assertDocument(expected, newDoc);
}
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java
index 92c2c2a820f..0da94b69e58 100644
--- a/config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java
@@ -4,11 +4,13 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import org.junit.Test;
import org.w3c.dom.Document;
import java.io.File;
import java.io.StringReader;
+import java.util.Set;
/**
* @author hmusum
@@ -44,7 +46,13 @@ public class XmlPreprocessorTest {
" </nodes>\n" +
" </container>\n" +
"</services>";
- TestBase.assertDocument(expectedDev, new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.dev, RegionName.defaultName()).run());
+ TestBase.assertDocument(expectedDev,
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.dev,
+ RegionName.defaultName(),
+ Tags.empty()).run());
String expectedStaging =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
@@ -70,7 +78,13 @@ public class XmlPreprocessorTest {
" </nodes>\n" +
" </container>\n" +
"</services>";
- TestBase.assertDocument(expectedStaging, new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.staging, RegionName.defaultName()).run());
+ TestBase.assertDocument(expectedStaging,
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.staging,
+ RegionName.defaultName(),
+ Tags.empty()).run());
String expectedUsWest =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
@@ -104,7 +118,13 @@ public class XmlPreprocessorTest {
" </nodes>\n" +
" </container>\n" +
"</services>";
- TestBase.assertDocument(expectedUsWest, new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.prod, RegionName.from("us-west")).run());
+ TestBase.assertDocument(expectedUsWest,
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.from("us-west"),
+ Tags.empty()).run());
String expectedUsEastAndCentral =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
@@ -138,9 +158,19 @@ public class XmlPreprocessorTest {
" </container>\n" +
"</services>";
TestBase.assertDocument(expectedUsEastAndCentral,
- new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.prod, RegionName.from("us-east")).run());
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.from("us-east"),
+ Tags.empty()).run());
TestBase.assertDocument(expectedUsEastAndCentral,
- new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.prod, RegionName.from("us-central")).run());
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.from("us-central"),
+ Tags.empty()).run());
}
@Test
@@ -184,7 +214,12 @@ public class XmlPreprocessorTest {
" <adminserver hostalias=\"node0\"/>" +
" </admin>" +
"</services>";
- Document docDev = (new XmlPreProcessor(appDir, new StringReader(input), InstanceName.defaultName(), Environment.prod, RegionName.defaultName()).run());
+ Document docDev = (new XmlPreProcessor(appDir,
+ new StringReader(input),
+ InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.empty()).run());
TestBase.assertDocument(expectedProd, docDev);
}
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json
index 7e17cd0f600..4f66ded727b 100644
--- a/config-model-api/abi-spec.json
+++ b/config-model-api/abi-spec.json
@@ -71,11 +71,13 @@
"public"
],
"methods": [
+ "public void <init>(java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, com.yahoo.config.provision.Tags, java.lang.String, java.lang.Long, long)",
"public void <init>(java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, java.lang.String, java.lang.Long, long)",
"public void <init>(java.lang.String, java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, java.lang.String, java.lang.Long, long)",
"public java.lang.String getDeployedByUser()",
"public java.lang.String getDeployPath()",
"public com.yahoo.config.provision.ApplicationId getApplicationId()",
+ "public com.yahoo.config.provision.Tags getTags()",
"public java.lang.Long getDeployTimestamp()",
"public java.lang.Long getGeneration()",
"public boolean isInternalRedeploy()",
@@ -194,8 +196,9 @@
"public"
],
"methods": [
- "public void <init>(com.yahoo.config.provision.InstanceName, java.util.List, com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy, com.yahoo.config.application.api.DeploymentSpec$RevisionTarget, com.yahoo.config.application.api.DeploymentSpec$RevisionChange, com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout, int, int, int, java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, com.yahoo.config.application.api.Notifications, java.util.List, java.time.Instant)",
+ "public void <init>(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Tags, java.util.List, com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy, com.yahoo.config.application.api.DeploymentSpec$RevisionTarget, com.yahoo.config.application.api.DeploymentSpec$RevisionChange, com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout, int, int, int, java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, com.yahoo.config.application.api.Notifications, java.util.List, java.time.Instant)",
"public com.yahoo.config.provision.InstanceName name()",
+ "public com.yahoo.config.provision.Tags tags()",
"public com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy upgradePolicy()",
"public com.yahoo.config.application.api.DeploymentSpec$RevisionTarget revisionTarget()",
"public com.yahoo.config.application.api.DeploymentSpec$RevisionChange revisionChange()",
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java
index a23afd994f2..c830c2baa1f 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java
@@ -2,6 +2,7 @@
package com.yahoo.config.application.api;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
@@ -9,6 +10,9 @@ import com.yahoo.slime.SlimeUtils;
import com.yahoo.text.Utf8;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* Metadata about an application package.
@@ -21,32 +25,40 @@ public class ApplicationMetaData {
private final long deployTimestamp;
private final boolean internalRedeploy;
private final ApplicationId applicationId;
+ private final Tags tags;
private final String checksum;
private final long generation;
private final long previousActiveGeneration;
public ApplicationMetaData(String deployedFromDir, Long deployTimestamp, boolean internalRedeploy,
- ApplicationId applicationId, String checksum, Long generation, long previousActiveGeneration) {
- this("unknown", deployedFromDir, deployTimestamp, internalRedeploy, applicationId, checksum, generation, previousActiveGeneration);
- }
-
- @Deprecated
- // TODO: Remove in Vespa 9
- public ApplicationMetaData(String ignored, String deployedFromDir, Long deployTimestamp, boolean internalRedeploy,
- ApplicationId applicationId, String checksum, Long generation, long previousActiveGeneration) {
+ ApplicationId applicationId, Tags tags,
+ String checksum, Long generation, long previousActiveGeneration) {
this.deployedFromDir = deployedFromDir;
this.deployTimestamp = deployTimestamp;
this.internalRedeploy = internalRedeploy;
this.applicationId = applicationId;
+ this.tags = tags;
this.checksum = checksum;
this.generation = generation;
this.previousActiveGeneration = previousActiveGeneration;
}
+ @Deprecated // TODO: Remove on Vespa 9
+ public ApplicationMetaData(String deployedFromDir, Long deployTimestamp, boolean internalRedeploy,
+ ApplicationId applicationId, String checksum, Long generation, long previousActiveGeneration) {
+ this(deployedFromDir, deployTimestamp, internalRedeploy, applicationId, Tags.empty(), checksum, generation, previousActiveGeneration);
+ }
+
+ @Deprecated // TODO: Remove on Vespa 9
+ public ApplicationMetaData(String ignored, String deployedFromDir, Long deployTimestamp, boolean internalRedeploy,
+ ApplicationId applicationId, String checksum, Long generation, long previousActiveGeneration) {
+ this(deployedFromDir, deployTimestamp, internalRedeploy, applicationId, Tags.empty(), checksum, generation, previousActiveGeneration);
+ }
+
/**
* Gets the user who deployed the application.
*
- * @return user name for the user who ran "deploy-application"
+ * @return username of the user who ran "deploy-application"
*/
@Deprecated // TODO: Remove in Vespa 9
public String getDeployedByUser() { return "unknown"; }
@@ -61,6 +73,8 @@ public class ApplicationMetaData {
public ApplicationId getApplicationId() { return applicationId; }
+ public Tags getTags() { return tags; }
+
/**
* Gets the time the application was deployed.
* Will return null if a problem occurred while getting metadata.
@@ -103,6 +117,7 @@ public class ApplicationMetaData {
deploy.field("timestamp").asLong(),
booleanField("internalRedeploy", false, deploy),
ApplicationId.fromSerializedForm(app.field("id").asString()),
+ Tags.fromString(deploy.field("tags").asString()),
app.field("checksum").asString(),
app.field("generation").asLong(),
app.field("previousActiveGeneration").asLong());
@@ -118,6 +133,7 @@ public class ApplicationMetaData {
deploy.setString("from", deployedFromDir);
deploy.setLong("timestamp", deployTimestamp);
deploy.setBool("internalRedeploy", internalRedeploy);
+ deploy.setString("tags", tags.asString());
Cursor app = meta.setObject("application");
app.setString("id", applicationId.serializedForm());
app.setString("checksum", checksum);
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
index cd20b5b8910..fdde4c38fb8 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
@@ -6,6 +6,7 @@ import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import java.time.Duration;
import java.time.Instant;
@@ -40,6 +41,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
/** The name of the instance this step deploys */
private final InstanceName name;
+ private final Tags tags;
private final DeploymentSpec.UpgradePolicy upgradePolicy;
private final DeploymentSpec.RevisionTarget revisionTarget;
private final DeploymentSpec.RevisionChange revisionChange;
@@ -55,6 +57,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
private final List<Endpoint> endpoints;
public DeploymentInstanceSpec(InstanceName name,
+ Tags tags,
List<DeploymentSpec.Step> steps,
DeploymentSpec.UpgradePolicy upgradePolicy,
DeploymentSpec.RevisionTarget revisionTarget,
@@ -70,6 +73,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
Instant now) {
super(steps);
this.name = Objects.requireNonNull(name);
+ this.tags = Objects.requireNonNull(tags);
this.upgradePolicy = Objects.requireNonNull(upgradePolicy);
Objects.requireNonNull(revisionTarget);
Objects.requireNonNull(revisionChange);
@@ -94,6 +98,8 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
public InstanceName name() { return name; }
+ public Tags tags() { return tags; }
+
/**
* Throws an IllegalArgumentException if any production deployment or test is declared multiple times,
* or if any production test is declared not after its corresponding deployment.
@@ -267,12 +273,13 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
}
int deployableHashCode() {
- List<DeploymentSpec.DeclaredZone> zones = zones().stream().filter(zone -> zone.concerns(prod)).collect(toList());
- Object[] toHash = new Object[zones.size() + 3];
+ List<DeploymentSpec.DeclaredZone> zones = zones().stream().filter(zone -> zone.concerns(prod)).toList();
+ Object[] toHash = new Object[zones.size() + 4];
int i = 0;
toHash[i++] = name;
toHash[i++] = endpoints;
toHash[i++] = globalServiceId;
+ toHash[i++] = tags;
for (DeploymentSpec.DeclaredZone zone : zones)
toHash[i++] = Objects.hash(zone, zone.athenzService());
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
index b85150356e3..2df55ffce95 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
@@ -194,7 +194,7 @@ public class DeploymentSpec {
/** Returns the instance names declared in this */
public List<InstanceName> instanceNames() {
- return instances().stream().map(DeploymentInstanceSpec::name).collect(Collectors.toUnmodifiableList());
+ return instances().stream().map(DeploymentInstanceSpec::name).toList();
}
/** Returns the step descendants of this which are instances */
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
index 83fba75325e..77894a8cf1f 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
@@ -25,6 +25,7 @@ import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.io.IOUtils;
import com.yahoo.text.XML;
import org.w3c.dom.Element;
@@ -55,6 +56,7 @@ public class DeploymentSpecXmlReader {
private static final String deploymentTag = "deployment";
private static final String instanceTag = "instance";
+ private static final String tagsTag = "tags";
private static final String testTag = "test";
private static final String stagingTag = "staging";
private static final String devTag = "dev";
@@ -155,48 +157,50 @@ public class DeploymentSpecXmlReader {
* Reads the content of an (implicit or explicit) instance tag producing an instances step
*
* @param instanceNameString a comma-separated list of the names of the instances this is for
- * @param instanceTag the element having the content of this instance
+ * @param instanceElement the element having the content of this instance
* @param parentTag the parent of instanceTag (or the same, if this instance is implicitly defined, which means instanceTag is the root)
* @return the instances specified, one for each instance name element
*/
private List<DeploymentInstanceSpec> readInstanceContent(String instanceNameString,
- Element instanceTag,
+ Element instanceElement,
Map<String, String> prodAttributes,
Element parentTag) {
if (instanceNameString.isBlank())
illegal("<instance> attribute 'id' must be specified, and not be blank");
// If this is an absolutely empty instance, or the implicit "default" instance but without content, ignore it
- if (XML.getChildren(instanceTag).isEmpty() && (instanceTag.getAttributes().getLength() == 0 || instanceTag == parentTag))
+ if (XML.getChildren(instanceElement).isEmpty() && (instanceElement.getAttributes().getLength() == 0 || instanceElement == parentTag))
return List.of();
if (validate)
- validateTagOrder(instanceTag);
+ validateTagOrder(instanceElement);
// Values where the parent may provide a default
- DeploymentSpec.UpgradePolicy upgradePolicy = getWithFallback(instanceTag, parentTag, upgradeTag, "policy", this::readUpgradePolicy, UpgradePolicy.defaultPolicy);
- DeploymentSpec.RevisionTarget revisionTarget = getWithFallback(instanceTag, parentTag, upgradeTag, "revision-target", this::readRevisionTarget, RevisionTarget.latest);
- DeploymentSpec.RevisionChange revisionChange = getWithFallback(instanceTag, parentTag, upgradeTag, "revision-change", this::readRevisionChange, RevisionChange.whenFailing);
- DeploymentSpec.UpgradeRollout upgradeRollout = getWithFallback(instanceTag, parentTag, upgradeTag, "rollout", this::readUpgradeRollout, UpgradeRollout.separate);
- int minRisk = getWithFallback(instanceTag, parentTag, upgradeTag, "min-risk", Integer::parseInt, 0);
- int maxRisk = getWithFallback(instanceTag, parentTag, upgradeTag, "max-risk", Integer::parseInt, 0);
- int maxIdleHours = getWithFallback(instanceTag, parentTag, upgradeTag, "max-idle-hours", Integer::parseInt, 8);
- List<DeploymentSpec.ChangeBlocker> changeBlockers = readChangeBlockers(instanceTag, parentTag);
- Optional<AthenzService> athenzService = mostSpecificAttribute(instanceTag, athenzServiceAttribute).map(AthenzService::from);
- Optional<CloudAccount> cloudAccount = mostSpecificAttribute(instanceTag, cloudAccountAttribute).map(CloudAccount::new);
- Notifications notifications = readNotifications(instanceTag, parentTag);
+ DeploymentSpec.UpgradePolicy upgradePolicy = getWithFallback(instanceElement, parentTag, upgradeTag, "policy", this::readUpgradePolicy, UpgradePolicy.defaultPolicy);
+ DeploymentSpec.RevisionTarget revisionTarget = getWithFallback(instanceElement, parentTag, upgradeTag, "revision-target", this::readRevisionTarget, RevisionTarget.latest);
+ DeploymentSpec.RevisionChange revisionChange = getWithFallback(instanceElement, parentTag, upgradeTag, "revision-change", this::readRevisionChange, RevisionChange.whenFailing);
+ DeploymentSpec.UpgradeRollout upgradeRollout = getWithFallback(instanceElement, parentTag, upgradeTag, "rollout", this::readUpgradeRollout, UpgradeRollout.separate);
+ int minRisk = getWithFallback(instanceElement, parentTag, upgradeTag, "min-risk", Integer::parseInt, 0);
+ int maxRisk = getWithFallback(instanceElement, parentTag, upgradeTag, "max-risk", Integer::parseInt, 0);
+ int maxIdleHours = getWithFallback(instanceElement, parentTag, upgradeTag, "max-idle-hours", Integer::parseInt, 8);
+ List<DeploymentSpec.ChangeBlocker> changeBlockers = readChangeBlockers(instanceElement, parentTag);
+ Optional<AthenzService> athenzService = mostSpecificAttribute(instanceElement, athenzServiceAttribute).map(AthenzService::from);
+ Optional<CloudAccount> cloudAccount = mostSpecificAttribute(instanceElement, cloudAccountAttribute).map(CloudAccount::new);
+ Notifications notifications = readNotifications(instanceElement, parentTag);
// Values where there is no default
+ Tags tags = XML.attribute(tagsTag, instanceElement).map(value -> Tags.fromString(value)).orElse(Tags.empty());
List<Step> steps = new ArrayList<>();
- for (Element instanceChild : XML.getChildren(instanceTag))
+ for (Element instanceChild : XML.getChildren(instanceElement))
steps.addAll(readNonInstanceSteps(instanceChild, prodAttributes, instanceChild));
- List<Endpoint> endpoints = readEndpoints(instanceTag, Optional.of(instanceNameString), steps);
+ List<Endpoint> endpoints = readEndpoints(instanceElement, Optional.of(instanceNameString), steps);
// Build and return instances with these values
Instant now = clock.instant();
return Arrays.stream(instanceNameString.split(","))
.map(name -> name.trim())
.map(name -> new DeploymentInstanceSpec(InstanceName.from(name),
+ tags,
steps,
upgradePolicy,
revisionTarget,
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index ecb66910784..9ede27cb47f 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -73,14 +73,10 @@ public interface ModelContext {
*/
interface FeatureFlags {
@ModelFeatureFlag(owners = {"baldersheim"}, comment = "Revisit in May or June 2021") default double defaultTermwiseLimit() { throw new UnsupportedOperationException("TODO specify default value"); }
- @ModelFeatureFlag(owners = {"vekterli"}, removeAfter="7.last") default boolean useThreePhaseUpdates() { return true; }
@ModelFeatureFlag(owners = {"baldersheim"}, comment = "Select sequencer type use while feeding") default String feedSequencerType() { return "THROUGHPUT"; }
@ModelFeatureFlag(owners = {"baldersheim"}) default String responseSequencerType() { throw new UnsupportedOperationException("TODO specify default value"); }
@ModelFeatureFlag(owners = {"baldersheim"}) default String queryDispatchPolicy() { return "adaptive"; }
@ModelFeatureFlag(owners = {"baldersheim"}) default int defaultNumResponseThreads() { return 2; }
- @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean skipCommunicationManagerThread() { return true; }
- @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean skipMbusRequestThread() { return true; }
- @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean skipMbusReplyThread() { return true; }
@ModelFeatureFlag(owners = {"baldersheim"}) default int mbusNetworkThreads() { return 1; }
@ModelFeatureFlag(owners = {"baldersheim"}) default int mbusJavaRpcNumTargets() { return 1; }
@ModelFeatureFlag(owners = {"baldersheim"}) default int mbusJavaEventsBeforeWakeup() { return 1; }
@@ -91,11 +87,7 @@ public interface ModelContext {
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean useAsyncMessageHandlingOnSchedule() { throw new UnsupportedOperationException("TODO specify default value"); }
@ModelFeatureFlag(owners = {"baldersheim"}) default double feedConcurrency() { throw new UnsupportedOperationException("TODO specify default value"); }
@ModelFeatureFlag(owners = {"baldersheim"}) default double feedNiceness() { return 0.0; }
- @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default int defaultPoolNumThreads() { return 1; }
- @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default int availableProcessors() { return 1; }
@ModelFeatureFlag(owners = {"baldersheim"}) default int maxUnCommittedMemory() { return 130000; }
- @ModelFeatureFlag(owners = {"baldersheim"}) default int maxConcurrentMergesPerNode() { return 16; }
- @ModelFeatureFlag(owners = {"baldersheim"}) default int maxMergeQueueSize() { return 100; }
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean sharedStringRepoNoReclaim() { return false; }
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean loadCodeAsHugePages() { return false; }
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean containerDumpHeapOnShutdownTimeout() { throw new UnsupportedOperationException("TODO specify default value"); }
@@ -108,26 +100,37 @@ public interface ModelContext {
@ModelFeatureFlag(owners = {"geirst", "vekterli"}) default double minNodeRatioPerGroup() { return 0.0; }
@ModelFeatureFlag(owners = {"arnej"}) default boolean forwardIssuesAsErrors() { return true; }
@ModelFeatureFlag(owners = {"arnej"}) default boolean ignoreThreadStackSizes() { return false; }
- @ModelFeatureFlag(owners = {"vekterli", "geirst"}) default boolean unorderedMergeChaining() { return true; }
@ModelFeatureFlag(owners = {"arnej"}) default boolean useV8GeoPositions() { return false; }
@ModelFeatureFlag(owners = {"baldersheim", "geirst", "toregge"}) default int maxCompactBuffers() { return 1; }
@ModelFeatureFlag(owners = {"arnej", "andreer"}) default List<String> ignoredHttpUserAgents() { return List.of(); }
- @ModelFeatureFlag(owners = {"bjorncs"}, removeAfter="7.last") default boolean enableServerOcspStapling() { return true; }
- @ModelFeatureFlag(owners = {"vekterli"}) default String mergeThrottlingPolicy() { throw new UnsupportedOperationException("TODO specify default value"); }
- @ModelFeatureFlag(owners = {"vekterli"}) default double persistenceThrottlingWsDecrementFactor() { throw new UnsupportedOperationException("TODO specify default value"); }
- @ModelFeatureFlag(owners = {"vekterli"}) default double persistenceThrottlingWsBackoff() { throw new UnsupportedOperationException("TODO specify default value"); }
- @ModelFeatureFlag(owners = {"vekterli"}) default int persistenceThrottlingWindowSize() { throw new UnsupportedOperationException("TODO specify default value"); }
- @ModelFeatureFlag(owners = {"vekterli"}) default double persistenceThrottlingWsResizeRate() { throw new UnsupportedOperationException("TODO specify default value"); }
- @ModelFeatureFlag(owners = {"vekterli"}) default boolean persistenceThrottlingOfMergeFeedOps() { throw new UnsupportedOperationException("TODO specify default value"); }
- @ModelFeatureFlag(owners = {"arnej"}, removeAfter="7.last") default boolean useQrserverServiceName() { return true; }
- @ModelFeatureFlag(owners = {"arnej"}, removeAfter="7.last") default boolean avoidRenamingSummaryFeatures() { return false; }
- @ModelFeatureFlag(owners = {"arnej"}, removeAfter="7.last") default boolean experimentalSdParsing() { return true; } // TODO: Remove after June 2022
- @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean enableBitVectors() { return true; }
@ModelFeatureFlag(owners = {"hmusum"}) default Architecture adminClusterArchitecture() { return Architecture.getDefault(); }
@ModelFeatureFlag(owners = {"tokle"}) default boolean enableProxyProtocolMixedMode() { return true; }
@ModelFeatureFlag(owners = {"arnej"}) default String logFileCompressionAlgorithm(String defVal) { return defVal; }
@ModelFeatureFlag(owners = {"vekterli"}) default boolean useTwoPhaseDocumentGc() { return false; }
@ModelFeatureFlag(owners = {"tokle"}) default boolean useRestrictedDataPlaneBindings() { return false; }
+ @ModelFeatureFlag(owners = {"baldersheim", "vekterli"}, removeAfter="8.61") default boolean computeCoverageFromTargetActiveDocs() { return true; }
+
+ //Below are all flags that must be kept until 7 is out of the door
+ @ModelFeatureFlag(owners = {"vekterli"}, removeAfter="7.last") default boolean useThreePhaseUpdates() { return true; }
+ @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean skipCommunicationManagerThread() { return true; }
+ @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean skipMbusRequestThread() { return true; }
+ @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean skipMbusReplyThread() { return true; }
+ @ModelFeatureFlag(owners = {"arnej"}, removeAfter="7.last") default boolean useQrserverServiceName() { return true; }
+ @ModelFeatureFlag(owners = {"arnej"}, removeAfter="7.last") default boolean avoidRenamingSummaryFeatures() { return false; }
+ @ModelFeatureFlag(owners = {"arnej"}, removeAfter="7.last") default boolean experimentalSdParsing() { return true; } // TODO: Remove after June 2022
+ @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default boolean enableBitVectors() { return true; }
+ @ModelFeatureFlag(owners = {"bjorncs"}, removeAfter="7.last") default boolean enableServerOcspStapling() { return true; }
+ @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default int defaultPoolNumThreads() { return 1; }
+ @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default int availableProcessors() { return 1; }
+ @ModelFeatureFlag(owners = {"vekterli", "geirst"}, removeAfter="7.last") default boolean unorderedMergeChaining() { return true; }
+ @ModelFeatureFlag(owners = {"vekterli"}, removeAfter="7.last") default String mergeThrottlingPolicy() { return "STATIC"; }
+ @ModelFeatureFlag(owners = {"vekterli"}, removeAfter="7.last") default double persistenceThrottlingWsDecrementFactor() { return 1.2; }
+ @ModelFeatureFlag(owners = {"vekterli"}, removeAfter="7.last") default double persistenceThrottlingWsBackoff() { return 0.95; }
+ @ModelFeatureFlag(owners = {"vekterli"}, removeAfter="7.last") default int persistenceThrottlingWindowSize() { return -1; }
+ @ModelFeatureFlag(owners = {"vekterli"}, removeAfter="7.last") default double persistenceThrottlingWsResizeRate() { return 3; }
+ @ModelFeatureFlag(owners = {"vekterli"}, removeAfter="7.last") default boolean persistenceThrottlingOfMergeFeedOps() { return true; }
+ @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default int maxConcurrentMergesPerNode() { return 16; }
+ @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter="7.last") default int maxMergeQueueSize() { return 100; }
}
/** Warning: As elsewhere in this package, do not make backwards incompatible changes that will break old config models! */
diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
index 87b0f709125..3870768ceb4 100644
--- a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
+++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.test.ManualClock;
import org.junit.Test;
@@ -133,6 +134,29 @@ public class DeploymentSpecTest {
}
@Test
+ public void specWithTags() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='a' tags='tag1 tag2'>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ " </instance>" +
+ " <instance id='b' tags='tag3'>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(Tags.fromString("tag1 tag2"), spec.requireInstance("a").tags());
+ assertEquals(Tags.fromString("tag3"), spec.requireInstance("b").tags());
+ }
+
+ @Test
public void maximalProductionSpec() {
StringReader r = new StringReader(
"<deployment version='1.0'>" +
@@ -1402,7 +1426,7 @@ public class DeploymentSpecTest {
</deployment>""").deployableHashCode(),
DeploymentSpec.fromXml("""
<deployment>
- <instance id='default'>
+ <instance id='default' tags=' '>
<test />
<staging tester-flavor='2-8-50' />
<block-change days='mon' />
@@ -1423,7 +1447,8 @@ public class DeploymentSpecTest {
<region>name</region>
</prod>
</instance>
- <instance id='two' /> </parallel>
+ <instance id='two' />
+ </parallel>
</deployment>""").deployableHashCode(),
DeploymentSpec.fromXml("""
<deployment>
@@ -1459,6 +1484,16 @@ public class DeploymentSpecTest {
assertNotEquals(DeploymentSpec.fromXml(referenceSpec).deployableHashCode(),
DeploymentSpec.fromXml("""
<deployment>
+ <instance id='default' tags='tag1'>
+ <prod>
+ <region>name</region>
+ </prod>
+ </instance>
+ </deployment>""").deployableHashCode());
+
+ assertNotEquals(DeploymentSpec.fromXml(referenceSpec).deployableHashCode(),
+ DeploymentSpec.fromXml("""
+ <deployment>
<instance id='custom'>
<prod>
<region>name</region>
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
index b1b6870a004..eb628db6975 100644
--- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
+++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
@@ -54,8 +54,6 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
private int maxActivationInhibitedOutOfSyncGroups = 0;
private List<TenantSecretStore> tenantSecretStores = Collections.emptyList();
private String jvmOmitStackTraceInFastThrowOption;
- private int maxConcurrentMergesPerNode = 16;
- private int maxMergeQueueSize = 100;
private boolean allowDisableMtls = true;
private List<X509Certificate> operatorCertificates = Collections.emptyList();
private double resourceLimitDisk = 0.75;
@@ -64,15 +62,8 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
private boolean containerDumpHeapOnShutdownTimeout = false;
private double containerShutdownTimeout = 50.0;
private int maxUnCommittedMemory = 123456;
- private boolean unorderedMergeChaining = true;
private List<String> zoneDnsSuffixes = List.of();
private int maxCompactBuffers = 1;
- private String mergeThrottlingPolicy = "STATIC";
- private double persistenceThrottlingWsDecrementFactor = 1.2;
- private double persistenceThrottlingWsBackoff = 0.95;
- private int persistenceThrottlingWindowSize = -1;
- private double persistenceThrottlingWsResizeRate = 3.0;
- private boolean persistenceThrottlingOfMergeFeedOps = true;
private boolean useV8GeoPositions = true;
private List<String> environmentVariables = List.of();
private boolean loadCodeAsHugePages = false;
@@ -117,23 +108,14 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
@Override public String jvmOmitStackTraceInFastThrowOption(ClusterSpec.Type type) { return jvmOmitStackTraceInFastThrowOption; }
@Override public boolean allowDisableMtls() { return allowDisableMtls; }
@Override public List<X509Certificate> operatorCertificates() { return operatorCertificates; }
- @Override public int maxConcurrentMergesPerNode() { return maxConcurrentMergesPerNode; }
- @Override public int maxMergeQueueSize() { return maxMergeQueueSize; }
@Override public double resourceLimitDisk() { return resourceLimitDisk; }
@Override public double resourceLimitMemory() { return resourceLimitMemory; }
@Override public double minNodeRatioPerGroup() { return minNodeRatioPerGroup; }
@Override public double containerShutdownTimeout() { return containerShutdownTimeout; }
@Override public boolean containerDumpHeapOnShutdownTimeout() { return containerDumpHeapOnShutdownTimeout; }
@Override public int maxUnCommittedMemory() { return maxUnCommittedMemory; }
- @Override public boolean unorderedMergeChaining() { return unorderedMergeChaining; }
@Override public List<String> zoneDnsSuffixes() { return zoneDnsSuffixes; }
@Override public int maxCompactBuffers() { return maxCompactBuffers; }
- @Override public String mergeThrottlingPolicy() { return mergeThrottlingPolicy; }
- @Override public double persistenceThrottlingWsDecrementFactor() { return persistenceThrottlingWsDecrementFactor; }
- @Override public double persistenceThrottlingWsBackoff() { return persistenceThrottlingWsBackoff; }
- @Override public int persistenceThrottlingWindowSize() { return persistenceThrottlingWindowSize; }
- @Override public double persistenceThrottlingWsResizeRate() { return persistenceThrottlingWsResizeRate; }
- @Override public boolean persistenceThrottlingOfMergeFeedOps() { return persistenceThrottlingOfMergeFeedOps; }
@Override public boolean useV8GeoPositions() { return useV8GeoPositions; }
@Override public List<String> environmentVariables() { return environmentVariables; }
@Override public Architecture adminClusterArchitecture() { return adminClusterNodeResourcesArchitecture; }
@@ -214,15 +196,6 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
return this;
}
- public TestProperties setMaxConcurrentMergesPerNode(int maxConcurrentMergesPerNode) {
- this.maxConcurrentMergesPerNode = maxConcurrentMergesPerNode;
- return this;
- }
- public TestProperties setMaxMergeQueueSize(int maxMergeQueueSize) {
- this.maxMergeQueueSize = maxMergeQueueSize;
- return this;
- }
-
public TestProperties setDefaultTermwiseLimit(double limit) {
defaultTermwiseLimit = limit;
return this;
@@ -313,11 +286,6 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
return this;
}
- public TestProperties setUnorderedMergeChaining(boolean unordered) {
- unorderedMergeChaining = unordered;
- return this;
- }
-
public TestProperties setZoneDnsSuffixes(List<String> zoneDnsSuffixes) {
this.zoneDnsSuffixes = List.copyOf(zoneDnsSuffixes);
return this;
@@ -328,36 +296,6 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
return this;
}
- public TestProperties setMergeThrottlingPolicy(String policy) {
- this.mergeThrottlingPolicy = policy;
- return this;
- }
-
- public TestProperties setPersistenceThrottlingWsDecrementFactor(double factor) {
- this.persistenceThrottlingWsDecrementFactor = factor;
- return this;
- }
-
- public TestProperties setPersistenceThrottlingWsBackoff(double backoff) {
- this.persistenceThrottlingWsBackoff = backoff;
- return this;
- }
-
- public TestProperties setPersistenceThrottlingWindowSize(int windowSize) {
- this.persistenceThrottlingWindowSize = windowSize;
- return this;
- }
-
- public TestProperties setPersistenceThrottlingWsResizeRate(double resizeRate) {
- this.persistenceThrottlingWsResizeRate = resizeRate;
- return this;
- }
-
- public TestProperties setPersistenceThrottlingOfMergeFeedOps(boolean throttleOps) {
- this.persistenceThrottlingOfMergeFeedOps = throttleOps;
- return this;
- }
-
public TestProperties setUseV8GeoPositions(boolean value) {
this.useV8GeoPositions = value;
return this;
diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
index 9ee279c68d3..2d98c8b35c0 100644
--- a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
+++ b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
@@ -10,6 +10,7 @@ import com.yahoo.config.application.api.UnparsedConfigDefinition;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.io.IOUtils;
import com.yahoo.io.reader.NamedReader;
@@ -85,6 +86,7 @@ public class MockApplicationPackage implements ApplicationPackage {
ApplicationId.from(TenantName.defaultName(),
ApplicationName.from(APPLICATION_NAME),
InstanceName.defaultName()),
+ Tags.empty(),
"checksum",
APPLICATION_GENERATION,
0L);
diff --git a/config-model/src/main/java/com/yahoo/schema/processing/PagedAttributeValidator.java b/config-model/src/main/java/com/yahoo/schema/processing/PagedAttributeValidator.java
index 793505acd01..6f470cfdc56 100644
--- a/config-model/src/main/java/com/yahoo/schema/processing/PagedAttributeValidator.java
+++ b/config-model/src/main/java/com/yahoo/schema/processing/PagedAttributeValidator.java
@@ -41,19 +41,19 @@ public class PagedAttributeValidator extends Processor {
private void validatePagedSetting(Field field, Attribute attribute) {
if (!isSupportedType(attribute)) {
- fail(schema, field, "The 'paged' attribute setting is not supported for non-dense tensor, predicate and reference types");
+ fail(schema, field, "The 'paged' attribute setting is not supported for fast-rank tensor and predicate types");
}
}
private boolean isSupportedType(Attribute attribute) {
var type = attribute.getType();
return (type != Attribute.Type.PREDICATE) &&
- (isSupportedTensorType(attribute.tensorType()));
+ (isSupportedTensorType(attribute.tensorType(), attribute.isFastRank()));
}
- private boolean isSupportedTensorType(Optional<TensorType> tensorType) {
+ private boolean isSupportedTensorType(Optional<TensorType> tensorType, boolean fastRank) {
if (tensorType.isPresent()) {
- return isDenseTensorType(tensorType.get());
+ return !fastRank;
}
return true;
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryTransform.java b/config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryTransform.java
index e94518c988d..d1d22886642 100644
--- a/config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryTransform.java
+++ b/config-model/src/main/java/com/yahoo/vespa/documentmodel/SummaryTransform.java
@@ -37,33 +37,20 @@ public enum SummaryTransform {
/** Returns the bolded version of this transform if possible, throws if not */
public SummaryTransform bold() {
- switch (this) {
- case NONE:
- case BOLDED:
- return BOLDED;
-
- case DYNAMICBOLDED:
- case DYNAMICTEASER:
- return DYNAMICBOLDED;
-
- default:
- throw new IllegalArgumentException("Can not bold a '" + this + "' field.");
- }
+ return switch (this) {
+ case NONE, BOLDED -> BOLDED;
+ case DYNAMICBOLDED, DYNAMICTEASER -> DYNAMICBOLDED;
+ default -> throw new IllegalArgumentException("Can not bold a '" + this + "' field.");
+ };
}
/** Returns the unbolded version of this transform */
public SummaryTransform unbold() {
- switch (this) {
- case NONE:
- case BOLDED:
- return NONE;
-
- case DYNAMICBOLDED:
- return DYNAMICTEASER;
-
- default:
- return this;
- }
+ return switch (this) {
+ case NONE, BOLDED -> NONE;
+ case DYNAMICBOLDED -> DYNAMICTEASER;
+ default -> this;
+ };
}
/** Returns whether this value is bolded */
@@ -83,23 +70,16 @@ public enum SummaryTransform {
/** Returns whether this transform always gets its value by accessing memory only */
public boolean isInMemory() {
- switch (this) {
- case ATTRIBUTE:
- case DISTANCE:
- case POSITIONS:
- case GEOPOS:
- case RANKFEATURES:
- case SUMMARYFEATURES:
- case ATTRIBUTECOMBINER:
- case MATCHED_ATTRIBUTE_ELEMENTS_FILTER:
- return true;
-
- default:
- return false;
- }
+ return switch (this) {
+ case ATTRIBUTE, DISTANCE, POSITIONS, GEOPOS, RANKFEATURES, SUMMARYFEATURES, ATTRIBUTECOMBINER, MATCHED_ATTRIBUTE_ELEMENTS_FILTER ->
+ true;
+ default -> false;
+ };
}
+ @Override
public String toString() {
return name;
}
+
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
index f01e7799e13..4d03944c4e9 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
@@ -202,6 +202,14 @@ public class VespaMetricSet {
metrics.add(new Metric("jdisc.deactivated_containers.total.last"));
metrics.add(new Metric("jdisc.deactivated_containers.with_retained_refs.last"));
+ metrics.add(new Metric("jdisc.singleton.is_active.last"));
+ metrics.add(new Metric("jdisc.singleton.activation.count.last"));
+ metrics.add(new Metric("jdisc.singleton.activation.failure.count.last"));
+ metrics.add(new Metric("jdisc.singleton.activation.millis.last"));
+ metrics.add(new Metric("jdisc.singleton.deactivation.count.last"));
+ metrics.add(new Metric("jdisc.singleton.deactivation.failure.count.last"));
+ metrics.add(new Metric("jdisc.singleton.deactivation.millis.last"));
+
metrics.add(new Metric("athenz-tenant-cert.expiry.seconds.last"));
metrics.add(new Metric("container-iam-role.expiry.seconds"));
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
index 4c74282c061..06453bffaaf 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
@@ -1,7 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.builder.xml.dom;
-import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.ConfigModelContext.ApplicationType;
import com.yahoo.config.model.api.ConfigServerSpec;
import com.yahoo.config.model.deploy.DeployState;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
index e316f826ad6..8907c21d39a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.model.container;
import ai.vespa.metricsproxy.http.application.ApplicationMetricsHandler;
+import com.yahoo.cloud.config.CuratorConfig;
import com.yahoo.cloud.config.ZookeeperServerConfig;
import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
@@ -305,6 +306,15 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
}
}
+ @Override
+ public void getConfig(CuratorConfig.Builder builder) {
+ if (getParent() instanceof ConfigserverCluster) return; // Produces its own config
+ super.getConfig(builder);
+
+ // 12s is 2x the current ZookeeperServerConfig.tickTime() of 6s, and the default minimum the server will accept.
+ builder.zookeeperSessionTimeoutSeconds(12); // TODO jonmv: make configurable
+ }
+
public Optional<String> getTlsClientAuthority() {
return tlsClientAuthority;
}
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 5e09dd4732d..307e0e17955 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
@@ -246,6 +246,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
", have " + nonRetiredNodes + " non-retired");
}
cluster.addSimpleComponent("com.yahoo.vespa.curator.Curator", null, "zkfacade");
+ cluster.addSimpleComponent("com.yahoo.vespa.curator.CuratorWrapper", null, "zkfacade");
// These need to be setup so that they will use the container's config id, since each container
// have different config (id of zookeeper server)
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java
index b1258247e2e..dae823bcc9f 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java
@@ -32,7 +32,6 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl
private final GcOptions gc;
private final boolean hasIndexedDocumentType;
private final int maxActivationInhibitedOutOfSyncGroups;
- private final boolean unorderedMergeChaining;
private final boolean useTwoPhaseDocumentGc;
public static class Builder extends VespaDomBuilder.DomConfigProducerBuilder<DistributorCluster> {
@@ -95,20 +94,18 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl
final GcOptions gc = parseGcOptions(documentsNode);
final boolean hasIndexedDocumentType = clusterContainsIndexedDocumentType(documentsNode);
int maxInhibitedGroups = deployState.getProperties().featureFlags().maxActivationInhibitedOutOfSyncGroups();
- boolean unorderedMergeChaining = deployState.getProperties().featureFlags().unorderedMergeChaining();
boolean useTwoPhaseDocumentGc = deployState.getProperties().featureFlags().useTwoPhaseDocumentGc();
return new DistributorCluster(parent,
new BucketSplitting.Builder().build(new ModelElement(producerSpec)), gc,
hasIndexedDocumentType,
- maxInhibitedGroups, unorderedMergeChaining, useTwoPhaseDocumentGc);
+ maxInhibitedGroups, useTwoPhaseDocumentGc);
}
}
private DistributorCluster(ContentCluster parent, BucketSplitting bucketSplitting,
GcOptions gc, boolean hasIndexedDocumentType,
int maxActivationInhibitedOutOfSyncGroups,
- boolean unorderedMergeChaining,
boolean useTwoPhaseDocumentGc)
{
super(parent, "distributor");
@@ -117,7 +114,6 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl
this.gc = gc;
this.hasIndexedDocumentType = hasIndexedDocumentType;
this.maxActivationInhibitedOutOfSyncGroups = maxActivationInhibitedOutOfSyncGroups;
- this.unorderedMergeChaining = unorderedMergeChaining;
this.useTwoPhaseDocumentGc = useTwoPhaseDocumentGc;
}
@@ -131,7 +127,6 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl
builder.enable_revert(parent.getPersistence().supportRevert());
builder.disable_bucket_activation(!hasIndexedDocumentType);
builder.max_activation_inhibited_out_of_sync_groups(maxActivationInhibitedOutOfSyncGroups);
- builder.use_unordered_merge_chaining(unorderedMergeChaining);
builder.enable_two_phase_garbage_collection(useTwoPhaseDocumentGc);
bucketSplitting.getConfig(builder);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/FileStorProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/FileStorProducer.java
index ff905187969..092b94d72dc 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/FileStorProducer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/FileStorProducer.java
@@ -46,11 +46,6 @@ public class FileStorProducer implements StorFilestorConfig.Producer {
private final ContentCluster cluster;
private final int responseNumThreads;
private final StorFilestorConfig.Response_sequencer_type.Enum responseSequencerType;
- private final double persistenceThrottlingWsDecrementFactor;
- private final double persistenceThrottlingWsBackoff;
- private final int persistenceThrottlingWindowSize;
- private final double persistenceThrottlingWsResizeRate;
- private final boolean persistenceThrottlingOfMergeFeedOps;
private final boolean useAsyncMessageHandlingOnSchedule;
private static StorFilestorConfig.Response_sequencer_type.Enum convertResponseSequencerType(String sequencerType) {
@@ -66,11 +61,6 @@ public class FileStorProducer implements StorFilestorConfig.Producer {
this.cluster = parent;
this.responseNumThreads = featureFlags.defaultNumResponseThreads();
this.responseSequencerType = convertResponseSequencerType(featureFlags.responseSequencerType());
- this.persistenceThrottlingWsDecrementFactor = featureFlags.persistenceThrottlingWsDecrementFactor();
- this.persistenceThrottlingWsBackoff = featureFlags.persistenceThrottlingWsBackoff();
- this.persistenceThrottlingWindowSize = featureFlags.persistenceThrottlingWindowSize();
- this.persistenceThrottlingWsResizeRate = featureFlags.persistenceThrottlingWsResizeRate();
- this.persistenceThrottlingOfMergeFeedOps = featureFlags.persistenceThrottlingOfMergeFeedOps();
this.useAsyncMessageHandlingOnSchedule = featureFlags.useAsyncMessageHandlingOnSchedule();
}
@@ -84,14 +74,6 @@ public class FileStorProducer implements StorFilestorConfig.Producer {
builder.response_sequencer_type(responseSequencerType);
builder.use_async_message_handling_on_schedule(useAsyncMessageHandlingOnSchedule);
var throttleBuilder = new StorFilestorConfig.Async_operation_throttler.Builder();
- throttleBuilder.window_size_decrement_factor(persistenceThrottlingWsDecrementFactor);
- throttleBuilder.window_size_backoff(persistenceThrottlingWsBackoff);
- if (persistenceThrottlingWindowSize > 0) {
- throttleBuilder.min_window_size(persistenceThrottlingWindowSize);
- throttleBuilder.max_window_size(persistenceThrottlingWindowSize);
- }
- throttleBuilder.resize_rate(persistenceThrottlingWsResizeRate);
- throttleBuilder.throttle_individual_merge_feed_ops(persistenceThrottlingOfMergeFeedOps);
builder.async_operation_throttler(throttleBuilder);
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorServerProducer.java b/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorServerProducer.java
index e66f2c48f26..4298488b1fd 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorServerProducer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorServerProducer.java
@@ -1,7 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.content.storagecluster;
-import com.yahoo.config.model.api.ModelContext;
import com.yahoo.vespa.config.content.core.StorServerConfig;
import com.yahoo.vespa.model.builder.xml.dom.ModelElement;
import com.yahoo.vespa.model.content.cluster.ContentCluster;
@@ -11,10 +10,10 @@ import com.yahoo.vespa.model.content.cluster.ContentCluster;
*/
public class StorServerProducer implements StorServerConfig.Producer {
public static class Builder {
- StorServerProducer build(ModelContext.Properties properties, ModelElement element) {
+ StorServerProducer build(ModelElement element) {
ModelElement tuning = element.child("tuning");
- StorServerProducer producer = new StorServerProducer(ContentCluster.getClusterId(element), properties.featureFlags());
+ StorServerProducer producer = new StorServerProducer(ContentCluster.getClusterId(element));
if (tuning == null) return producer;
ModelElement merges = tuning.child("merges");
@@ -29,7 +28,6 @@ public class StorServerProducer implements StorServerConfig.Producer {
private final String clusterName;
private Integer maxMergesPerNode;
private Integer queueSize;
- private final StorServerConfig.Merge_throttling_policy.Type.Enum mergeThrottlingPolicyType;
private StorServerProducer setMaxMergesPerNode(Integer value) {
if (value != null) {
@@ -44,19 +42,8 @@ public class StorServerProducer implements StorServerConfig.Producer {
return this;
}
- private static StorServerConfig.Merge_throttling_policy.Type.Enum toThrottlePolicyType(String policyType) {
- try {
- return StorServerConfig.Merge_throttling_policy.Type.Enum.valueOf(policyType);
- } catch (Throwable t) {
- return StorServerConfig.Merge_throttling_policy.Type.STATIC;
- }
- }
-
- StorServerProducer(String clusterName, ModelContext.FeatureFlags featureFlags) {
+ StorServerProducer(String clusterName) {
this.clusterName = clusterName;
- maxMergesPerNode = featureFlags.maxConcurrentMergesPerNode();
- queueSize = featureFlags.maxMergeQueueSize();
- mergeThrottlingPolicyType = toThrottlePolicyType(featureFlags.mergeThrottlingPolicy());
}
@Override
@@ -73,7 +60,5 @@ public class StorServerProducer implements StorServerConfig.Producer {
if (queueSize != null) {
builder.max_merge_queue_size(queueSize);
}
- // TODO set throttle policy params based on existing or separate flags
- builder.merge_throttling_policy(new StorServerConfig.Merge_throttling_policy.Builder().type(mergeThrottlingPolicyType));
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorageCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorageCluster.java
index da82a69842a..9b59f6db742 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorageCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/storagecluster/StorageCluster.java
@@ -38,7 +38,7 @@ public class StorageCluster extends AbstractConfigProducer<StorageNode>
ContentCluster.getClusterId(clusterElem),
new FileStorProducer.Builder().build(deployState.getProperties(), cluster, clusterElem),
new IntegrityCheckerProducer.Builder().build(cluster, clusterElem),
- new StorServerProducer.Builder().build(deployState.getProperties(), clusterElem),
+ new StorServerProducer.Builder().build(clusterElem),
new StorVisitorProducer.Builder().build(clusterElem),
new PersistenceProducer.Builder().build(clusterElem));
}
diff --git a/config-model/src/main/resources/schema/deployment.rnc b/config-model/src/main/resources/schema/deployment.rnc
index 9723e531bd2..bf94ee3b750 100644
--- a/config-model/src/main/resources/schema/deployment.rnc
+++ b/config-model/src/main/resources/schema/deployment.rnc
@@ -35,6 +35,7 @@ PrimitiveStep =
Instance = element instance {
attribute id { xsd:string } &
+ attribute tags { xsd:string }? &
attribute athenz-service { xsd:string }? &
attribute cloud-account { xsd:string }? &
StepExceptInstance
diff --git a/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java b/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
index c1dd62316db..6507341670f 100644
--- a/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
+++ b/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
@@ -10,6 +10,7 @@ import com.yahoo.config.model.application.provider.DeployData;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.document.DataType;
import com.yahoo.io.IOUtils;
import com.yahoo.path.Path;
@@ -225,17 +226,19 @@ public class ApplicationDeployTest {
IOUtils.copyDirectory(new File(appPkg), tmp);
ApplicationId applicationId = ApplicationId.from("tenant1", "application1", "instance1");
DeployData deployData = new DeployData("bar",
- applicationId,
- 13L,
- false,
- 1337L,
- 3L);
+ applicationId,
+ Tags.fromString("tag1 tag2"),
+ 13L,
+ false,
+ 1337L,
+ 3L);
FilesApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(tmp, deployData);
app.writeMetaData();
FilesApplicationPackage newApp = FilesApplicationPackage.fromFileWithDeployData(tmp, deployData);
ApplicationMetaData meta = newApp.getMetaData();
assertEquals("bar", meta.getDeployPath());
assertEquals(applicationId, meta.getApplicationId());
+ assertEquals(Tags.fromString("tag1 tag2"), meta.getTags());
assertEquals(13L, (long) meta.getDeployTimestamp());
assertEquals(1337L, (long) meta.getGeneration());
assertEquals(3L, meta.getPreviousActiveGeneration());
diff --git a/config-model/src/test/java/com/yahoo/schema/processing/PagedAttributeValidatorTestCase.java b/config-model/src/test/java/com/yahoo/schema/processing/PagedAttributeValidatorTestCase.java
index 719db2ffdcc..9de44d28c09 100644
--- a/config-model/src/test/java/com/yahoo/schema/processing/PagedAttributeValidatorTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/processing/PagedAttributeValidatorTestCase.java
@@ -71,8 +71,13 @@ public class PagedAttributeValidatorTestCase {
}
@Test
- void non_dense_tensor_attribute_does_not_support_paged_setting() throws ParseException {
- assertPagedSettingNotSupported("tensor(x{},y[2])");
+ void non_dense_none_fast_rank_tensor_attribute_supports_paged_setting() throws ParseException {
+ assertPagedSupported("tensor(x{},y[2])");
+ }
+
+ @Test
+ void non_dense_fast_rank_tensor_attribute_does_not_support_paged_setting() throws ParseException {
+ assertPagedSettingNotSupported("tensor(x{},y[2])", true);
}
@Test
@@ -82,36 +87,45 @@ public class PagedAttributeValidatorTestCase {
@Test
void reference_attribute_support_paged_setting() throws ParseException {
- assertPagedSupported("reference<parent>", Optional.of(getSd("parent", "int")));
+ assertPagedSupported("reference<parent>", Optional.of(getSd("parent", "int", false)));
}
private void assertPagedSettingNotSupported(String fieldType) throws ParseException {
- assertPagedSettingNotSupported(fieldType, Optional.empty());
+ assertPagedSettingNotSupported(fieldType, false);
+ }
+
+ private void assertPagedSettingNotSupported(String fieldType, boolean fastRank) throws ParseException {
+ assertPagedSettingNotSupported(fieldType, fastRank, Optional.empty());
}
- private void assertPagedSettingNotSupported(String fieldType, Optional<String> parentSd) throws ParseException {
+ private void assertPagedSettingNotSupported(String fieldType, boolean fastRank, Optional<String> parentSd) throws ParseException {
try {
if (parentSd.isPresent()) {
- createFromStrings(new BaseDeployLogger(), parentSd.get(), getSd(fieldType));
+ createFromStrings(new BaseDeployLogger(), parentSd.get(), getSd(fieldType, fastRank));
} else {
- createFromString(getSd(fieldType));
+ createFromString(getSd(fieldType, fastRank));
}
fail("Expected exception");
} catch (IllegalArgumentException e) {
- assertEquals("For schema 'test', field 'foo': The 'paged' attribute setting is not supported for non-dense tensor, predicate and reference types",
+ assertEquals("For schema 'test', field 'foo': The 'paged' attribute setting is not supported for fast-rank tensor and predicate types",
e.getMessage());
}
}
private String getSd(String fieldType) {
- return getSd("test", fieldType);
+ return getSd(fieldType, false);
+ }
+
+ private String getSd(String fieldType, boolean fastRank) {
+ return getSd("test", fieldType, fastRank);
}
- private String getSd(String docType, String fieldType) {
+ private String getSd(String docType, String fieldType, boolean fastRank) {
return joinLines(
"schema " + docType + " {",
" document " + docType + " {",
" field foo type " + fieldType + "{",
+ (fastRank ? "attribute: fast-rank" : ""),
" indexing: attribute",
" attribute: paged",
" }",
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java
index 376cf49c396..eb77187014f 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/ClusterControllerTestCase.java
@@ -413,6 +413,11 @@ public class ClusterControllerTestCase extends DomBuilderTest {
assertEquals(0, qrStartConfig.jvm().directMemorySizeCache());
assertEquals(16, qrStartConfig.jvm().baseMaxDirectMemorySize());
+ CuratorConfig.Builder curatorBuilder = new CuratorConfig.Builder();
+ model.getConfig(curatorBuilder, "foo");
+ CuratorConfig curatorConfig = curatorBuilder.build();
+ assertEquals(120, curatorConfig.zookeeperSessionTimeoutSeconds());
+
assertReindexingConfigPresent(model);
assertReindexingConfiguredOnAdminCluster(model);
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
index cc6b84de698..da70daa2b4d 100755
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
@@ -343,6 +343,7 @@ public class ContainerClusterTest {
assertEquals(List.of("host-c1", "host-c2"),
config.server().stream().map(CuratorConfig.Server::hostname).collect(Collectors.toList()));
assertTrue(config.zookeeperLocalhostAffinity());
+ assertEquals(12, config.zookeeperSessionTimeoutSeconds());
}
@Test
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
index 272dfa19f64..cf6b6365792 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
@@ -570,6 +570,7 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
ApplicationContainerCluster cluster = model.getContainerClusters().get("default");
assertNotNull(cluster);
assertComponentConfigured(cluster, "com.yahoo.vespa.curator.Curator");
+ assertComponentConfigured(cluster, "com.yahoo.vespa.curator.CuratorWrapper");
cluster.getContainers().forEach(container -> {
assertComponentConfigured(container, "com.yahoo.vespa.zookeeper.ReconfigurableVespaZooKeeperServer");
assertComponentConfigured(container, "com.yahoo.vespa.zookeeper.Reconfigurer");
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
index 0ba47d9c58b..c41840eaefa 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
@@ -1132,23 +1132,6 @@ public class ContentClusterTest extends ContentBaseTest {
assertEquals(4, resolveTunedNumDistributorStripesConfig(65));
}
- @Test
- void unordered_merge_chaining_config_controlled_by_properties() throws Exception {
- assertFalse(resolveUnorderedMergeChainingConfig(Optional.of(false)));
- assertTrue(resolveUnorderedMergeChainingConfig(Optional.empty()));
- }
-
- private boolean resolveUnorderedMergeChainingConfig(Optional<Boolean> unorderedMergeChaining) throws Exception {
- var props = new TestProperties();
- if (unorderedMergeChaining.isPresent()) {
- props.setUnorderedMergeChaining(unorderedMergeChaining.get());
- }
- var cluster = createOneNodeCluster(props);
- var builder = new StorDistributormanagerConfig.Builder();
- cluster.getDistributorNodes().getConfig(builder);
- return (new StorDistributormanagerConfig(builder)).use_unordered_merge_chaining();
- }
-
private boolean resolveTwoPhaseGcConfigWithFeatureFlag(Boolean flagEnableTwoPhase) {
var props = new TestProperties();
if (flagEnableTwoPhase != null) {
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java
index f7afcc281f9..f3a59733ece 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/StorageClusterTest.java
@@ -120,9 +120,7 @@ public class StorageClusterTest {
parse(cluster("foofighters", joinLines(
"<tuning>",
" <merges max-per-node=\"1K\" max-queue-size=\"10K\"/>",
- "</tuning>")),
- new TestProperties().setMaxMergeQueueSize(1919).setMaxConcurrentMergesPerNode(37)
- ).getConfig(builder);
+ "</tuning>"))).getConfig(builder);
StorServerConfig config = new StorServerConfig(builder);
assertEquals(1024, config.max_merges_per_node());
@@ -174,9 +172,9 @@ public class StorageClusterTest {
@Test
void testMergeFeatureFlags() {
- var config = configFromProperties(new TestProperties().setMaxMergeQueueSize(1919).setMaxConcurrentMergesPerNode(37));
- assertEquals(37, config.max_merges_per_node());
- assertEquals(1919, config.max_merge_queue_size());
+ var config = configFromProperties(new TestProperties());
+ assertEquals(16, config.max_merges_per_node());
+ assertEquals(100, config.max_merge_queue_size());
}
@Test
@@ -186,19 +184,6 @@ public class StorageClusterTest {
}
@Test
- void merge_throttling_policy_config_is_derived_from_flag() {
- var config = configFromProperties(new TestProperties().setMergeThrottlingPolicy("STATIC"));
- assertEquals(StorServerConfig.Merge_throttling_policy.Type.STATIC, config.merge_throttling_policy().type());
-
- config = configFromProperties(new TestProperties().setMergeThrottlingPolicy("DYNAMIC"));
- assertEquals(StorServerConfig.Merge_throttling_policy.Type.DYNAMIC, config.merge_throttling_policy().type());
-
- // Invalid enum values fall back to the default
- config = configFromProperties(new TestProperties().setMergeThrottlingPolicy("UKULELE"));
- assertEquals(StorServerConfig.Merge_throttling_policy.Type.STATIC, config.merge_throttling_policy().type());
- }
-
- @Test
void testVisitors() {
StorVisitorConfig.Builder builder = new StorVisitorConfig.Builder();
parse(cluster("bees",
@@ -340,23 +325,6 @@ public class StorageClusterTest {
}
@Test
- void persistence_dynamic_throttling_parameters_can_be_set_through_feature_flags() {
- var config = filestorConfigFromProducer(simpleCluster(new TestProperties()
- .setPersistenceThrottlingWsDecrementFactor(1.5)
- .setPersistenceThrottlingWsBackoff(0.8)
- .setPersistenceThrottlingWindowSize(42)
- .setPersistenceThrottlingWsResizeRate(2.5)
- .setPersistenceThrottlingOfMergeFeedOps(false)));
- assertEquals(1.5, config.async_operation_throttler().window_size_decrement_factor(), 0.0001);
- assertEquals(0.8, config.async_operation_throttler().window_size_backoff(), 0.0001);
- // If window size is set, min and max are locked to the same value
- assertEquals(42, config.async_operation_throttler().min_window_size());
- assertEquals(42, config.async_operation_throttler().max_window_size());
- assertEquals(2.5, config.async_operation_throttler().resize_rate(), 0.0001);
- assertFalse(config.async_operation_throttler().throttle_individual_merge_feed_ops());
- }
-
- @Test
void integrity_checker_explicitly_disabled_when_not_running_with_vds_provider() {
StorIntegritycheckerConfig.Builder builder = new StorIntegritycheckerConfig.Builder();
parse(cluster("bees", "")).getConfig(builder);
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java
index 8830e5484b3..ee885cdf43e 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/search/test/DocumentDatabaseTestCase.java
@@ -19,7 +19,6 @@ import com.yahoo.vespa.model.content.utils.DocType;
import com.yahoo.vespa.model.search.IndexedSearchCluster;
import org.junit.jupiter.api.Test;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -55,7 +54,7 @@ public class DocumentDatabaseTestCase {
@Test
void requireThatMixedModeConcurrencyIsReflectedCorrectlyForDefault() {
- verifyConcurrency(Arrays.asList(DocType.create("a", "index"), DocType.create("b", "streaming")), "", 1.0);
+ verifyConcurrency(List.of(DocType.create("a", "index"), DocType.create("b", "streaming")), "", 1.0);
}
@Test
@@ -63,7 +62,7 @@ public class DocumentDatabaseTestCase {
String feedTuning = "<feeding>" +
" <concurrency>0.7</concurrency>" +
"</feeding>\n";
- verifyConcurrency(Arrays.asList(DocType.create("a", "index"), DocType.create("b", "streaming")), feedTuning, 0.7);
+ verifyConcurrency(List.of(DocType.create("a", "index"), DocType.create("b", "streaming")), feedTuning, 0.7);
}
@Test
@@ -77,11 +76,11 @@ public class DocumentDatabaseTestCase {
}
private void verifyConcurrency(String mode, String xmlTuning, double expectedConcurrency, double featureFlagConcurrency) {
- verifyConcurrency(Arrays.asList(DocType.create("a", mode)), xmlTuning, expectedConcurrency, featureFlagConcurrency);
+ verifyConcurrency(List.of(DocType.create("a", mode)), xmlTuning, expectedConcurrency, featureFlagConcurrency);
}
private void verifyConcurrency(String mode, String xmlTuning, double expectedConcurrency) {
- verifyConcurrency(Arrays.asList(DocType.create("a", mode)), xmlTuning, expectedConcurrency, null);
+ verifyConcurrency(List.of(DocType.create("a", mode)), xmlTuning, expectedConcurrency, null);
}
private void verifyConcurrency(List<DocType> nameAndModes, String xmlTuning, double expectedConcurrency) {
@@ -114,14 +113,14 @@ public class DocumentDatabaseTestCase {
@Test
void requireFeedNicenessIsReflected() {
- verifyFeedNiceness(Arrays.asList(DocType.create("a", "index")), 0.0, null);
- verifyFeedNiceness(Arrays.asList(DocType.create("a", "index")), 0.32, 0.32);
+ verifyFeedNiceness(List.of(DocType.create("a", "index")), 0.0, null);
+ verifyFeedNiceness(List.of(DocType.create("a", "index")), 0.32, 0.32);
}
@Test
void requireThatModeIsSet() {
var tester = new SchemaTester();
- VespaModel model = tester.createModel(Arrays.asList(DocType.create("a", "index"),
+ VespaModel model = tester.createModel(List.of(DocType.create("a", "index"),
DocType.create("b", "streaming"),
DocType.create("c", "store-only")), "");
ContentSearchCluster contentSearchCluster = model.getContentClusters().get("test").getSearch();
@@ -150,8 +149,8 @@ public class DocumentDatabaseTestCase {
@Test
void requireThatMixedModeInitialDocumentCountIsReflectedCorrectlyForDefault() {
final long DEFAULT = 1024L;
- verifyInitialDocumentCount(Arrays.asList(DocType.create("a", "index"), DocType.create("b", "streaming")),
- "", Arrays.asList(DEFAULT, DEFAULT));
+ verifyInitialDocumentCount(List.of(DocType.create("a", "index"), DocType.create("b", "streaming")),
+ "", List.of(DEFAULT, DEFAULT));
}
@Test
@@ -160,8 +159,8 @@ public class DocumentDatabaseTestCase {
String feedTuning = "<resizing>" +
" <initialdocumentcount>1000000000</initialdocumentcount>" +
"</resizing>\n";
- verifyInitialDocumentCount(Arrays.asList(DocType.create("a", "index"), DocType.create("b", "streaming")),
- feedTuning, Arrays.asList(INITIAL, INITIAL));
+ verifyInitialDocumentCount(List.of(DocType.create("a", "index"), DocType.create("b", "streaming")),
+ feedTuning, List.of(INITIAL, INITIAL));
}
private void assertDocTypeConfig(VespaModel model, String configId, String indexField, String attributeField) {
@@ -345,30 +344,30 @@ public class DocumentDatabaseTestCase {
@Test
void testThatAttributesMaxUnCommittedMemoryIsControlledByFeatureFlag() {
- assertAttributesConfigIndependentOfMode("index", Arrays.asList("type1"),
- Arrays.asList("test/search/cluster.test/type1"),
- ImmutableMap.of("type1", Arrays.asList("f2", "f2_nfa")),
+ assertAttributesConfigIndependentOfMode("index", List.of("type1"),
+ List.of("test/search/cluster.test/type1"),
+ ImmutableMap.of("type1", List.of("f2", "f2_nfa")),
new DeployState.Builder().properties(new TestProperties().maxUnCommittedMemory(193452)), 193452);
}
@Test
void testThatAttributesConfigIsProducedForIndexed() {
- assertAttributesConfigIndependentOfMode("index", Arrays.asList("type1"),
- Arrays.asList("test/search/cluster.test/type1"),
- ImmutableMap.of("type1", Arrays.asList("f2", "f2_nfa")));
+ assertAttributesConfigIndependentOfMode("index", List.of("type1"),
+ List.of("test/search/cluster.test/type1"),
+ ImmutableMap.of("type1", List.of("f2", "f2_nfa")));
}
@Test
void testThatAttributesConfigIsProducedForStreamingForFastAccessFields() {
- assertAttributesConfigIndependentOfMode("streaming", Arrays.asList("type1"),
- Arrays.asList("test/search/type1"),
- ImmutableMap.of("type1", Arrays.asList("f2")));
+ assertAttributesConfigIndependentOfMode("streaming", List.of("type1"),
+ List.of("test/search/type1"),
+ ImmutableMap.of("type1", List.of("f2")));
}
@Test
void testThatAttributesConfigIsNotProducedForStoreOnlyEvenForFastAccessFields() {
- assertAttributesConfigIndependentOfMode("store-only", Arrays.asList("type1"),
- Arrays.asList("test/search"), Collections.emptyMap());
+ assertAttributesConfigIndependentOfMode("store-only", List.of("type1"),
+ List.of("test/search"), Collections.emptyMap());
}
}
diff --git a/config-model/src/test/schema-test-files/deployment-with-instances.xml b/config-model/src/test/schema-test-files/deployment-with-instances.xml
index 39771ca9d41..f37ff9f6cc6 100644
--- a/config-model/src/test/schema-test-files/deployment-with-instances.xml
+++ b/config-model/src/test/schema-test-files/deployment-with-instances.xml
@@ -35,7 +35,7 @@
<delay hours='2'/>
<parallel>
- <instance id="three">
+ <instance id="three" tags="a b">
<test/>
<staging/>
</instance>
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/HostLivenessTracker.java b/config-provisioning/src/main/java/com/yahoo/config/provision/HostLivenessTracker.java
deleted file mode 100644
index f3326ec2bc8..00000000000
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/HostLivenessTracker.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.config.provision;
-
-import java.time.Instant;
-import java.util.Optional;
-
-/**
- * Instances of this are used to keep track of (notify and query)
- * which hosts are currently connected to the config system.
- *
- * @author bratseth
- */
-public interface HostLivenessTracker {
-
- /** Called each time a config request is received from a client */
- void receivedRequestFrom(String hostname);
-
- /**
- * Returns the epoch timestamp of the last request received from the given hostname,
- * or empty if there is no memory of this host making a request
- */
- Optional<Instant> lastRequestFrom(String hostname);
-
-}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeAllocationException.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeAllocationException.java
index d568a61fc69..507d95c1d7b 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeAllocationException.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeAllocationException.java
@@ -9,8 +9,15 @@ package com.yahoo.config.provision;
*/
public class NodeAllocationException extends RuntimeException {
- public NodeAllocationException(String message) {
+ private final boolean retryable;
+
+ public NodeAllocationException(String message, boolean retryable) {
super(message);
+ this.retryable = retryable;
+ }
+
+ public boolean retryable() {
+ return retryable;
}
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Tags.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Tags.java
new file mode 100644
index 00000000000..3a54a0b9ebb
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Tags.java
@@ -0,0 +1,64 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A deployment may have a list of tags associated with it. Config files may have variants for these tags similar
+ * to how they may have variants for instance and zone.
+ *
+ * @author bratseth
+ */
+public class Tags {
+
+ private final Set<String> tags;
+
+ public Tags(Set<String> tags) {
+ this.tags = Set.copyOf(tags);
+ }
+
+ public boolean contains(String tag) {
+ return tags.contains(tag);
+ }
+
+ public boolean intersects(Tags other) {
+ return this.tags.stream().anyMatch(other::contains);
+ }
+
+ public boolean isEmpty() { return tags.isEmpty(); }
+
+ public boolean containsAll(Tags other) { return tags.containsAll(other.tags); }
+
+ /** Returns this as a space-separated string which can be used to recreate this by calling fromString(). */
+ public String asString() {
+ return tags.stream().sorted().collect(Collectors.joining(" "));
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other == null || other.getClass() != getClass()) return false;
+ return tags.equals(((Tags)other).tags);
+ }
+
+ @Override
+ public int hashCode() {
+ return tags.hashCode();
+ }
+
+ public static Tags empty() { return new Tags(Set.of()); }
+
+ /**
+ * Creates this from a space-separated string or null. */
+ public static Tags fromString(String tagsString) {
+ if (tagsString == null || tagsString.isBlank()) return empty();
+ return new Tags(Set.of(tagsString.trim().split(" +")));
+ }
+
+}
diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/TagsTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/TagsTest.java
new file mode 100644
index 00000000000..a20e674c66e
--- /dev/null
+++ b/config-provisioning/src/test/java/com/yahoo/config/provision/TagsTest.java
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @author bratseth
+ */
+public class TagsTest {
+
+ @Test
+ public void testEmpty() {
+ assertEquals(Tags.empty(), Tags.fromString(null));
+ assertEquals(Tags.empty(), Tags.fromString(""));
+ assertEquals(Tags.empty(), Tags.fromString(" "));
+ }
+
+ @Test
+ public void testDeserialization() {
+ assertEquals(new Tags(Set.of("tag1", "tag2")), Tags.fromString(" tag1 tag2 "));
+ }
+
+ @Test
+ public void testSerialization() {
+ Tags tags = new Tags(Set.of("a", "tag2", "3"));
+ assertEquals(tags, Tags.fromString(tags.toString())); // Required by automatic serialization
+ assertEquals(tags, Tags.fromString(tags.asString())); // Required by automatic serialization
+ }
+
+ @Test
+ public void testContains() {
+ Tags tags = new Tags(Set.of("a", "tag2", "3"));
+ assertTrue(tags.contains("a"));
+ assertTrue(tags.contains("tag2"));
+ assertTrue(tags.contains("3"));
+ assertFalse(tags.contains("other"));
+
+ Tags subTags = new Tags(Set.of("a", "3"));
+ assertTrue(tags.containsAll(subTags));
+ assertFalse(subTags.containsAll(tags));
+ }
+
+ @Test
+ public void testIntersects() {
+ Tags tags1 = new Tags(Set.of("a", "tag2", "3"));
+ Tags tags2 = new Tags(Set.of("a", "tag3"));
+ assertTrue(tags1.intersects(tags2));
+ assertTrue(tags2.intersects(tags1));
+ }
+
+}
diff --git a/config-proxy/pom.xml b/config-proxy/pom.xml
index 476f5f99b86..e241f50e851 100644
--- a/config-proxy/pom.xml
+++ b/config-proxy/pom.xml
@@ -50,7 +50,7 @@
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>bcpkix-jdk18on</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/CachedFilesMaintainer.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/CachedFilesMaintainer.java
index 3eafdf8f2b4..4ca5eb4ee90 100644
--- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/CachedFilesMaintainer.java
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/CachedFilesMaintainer.java
@@ -2,9 +2,7 @@
package com.yahoo.vespa.config.proxy.filedistribution;
import com.yahoo.io.IOUtils;
-import java.util.logging.Level;
import com.yahoo.vespa.filedistribution.FileDownloader;
-
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -31,7 +29,7 @@ class CachedFilesMaintainer implements Runnable {
private static final File defaultUrlDownloadDir = UrlDownloadRpcServer.downloadDir;
private static final File defaultFileReferencesDownloadDir = FileDownloader.defaultDownloadDirectory;
- private static final Duration defaultDurationToKeepFiles = Duration.ofDays(30);
+ private static final Duration defaultDurationToKeepFiles = Duration.ofDays(14);
private final File urlDownloadDir;
private final File fileReferencesDownloadDir;
diff --git a/configdefinitions/src/vespa/curator.def b/configdefinitions/src/vespa/curator.def
index c762766a567..db7cea00882 100644
--- a/configdefinitions/src/vespa/curator.def
+++ b/configdefinitions/src/vespa/curator.def
@@ -7,3 +7,6 @@ server[].port int default=2181
# if true, only connect to server on localhost (must be in one of the servers above)
zookeeperLocalhostAffinity bool default=false
+
+# session timeout, the high default is used by config servers
+zookeeperSessionTimeoutSeconds int default=120 \ No newline at end of file
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
index 2a15f724b29..cf79005b1ab 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
@@ -23,6 +23,7 @@ import com.yahoo.config.provision.InfraDeployer;
import com.yahoo.config.provision.Provisioner;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.config.provision.exception.ActivationConflictException;
@@ -361,8 +362,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
}
private PrepareResult deploy(File applicationDir, PrepareParams prepareParams, DeployHandlerLogger logger) {
- ApplicationId applicationId = prepareParams.getApplicationId();
- long sessionId = createSession(applicationId, prepareParams.getTimeoutBudget(), applicationDir, logger);
+ long sessionId = createSession(prepareParams.getApplicationId(),
+ prepareParams.tags(),
+ prepareParams.getTimeoutBudget(),
+ applicationDir,
+ logger);
Deployment deployment = prepare(sessionId, prepareParams, logger);
if ( ! prepareParams.isDryRun())
@@ -870,21 +874,21 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
return sessionRepository.createSessionFromExisting(fromSession, internalRedeploy, timeoutBudget, deployLogger).getSessionId();
}
- public long createSession(ApplicationId applicationId, TimeoutBudget timeoutBudget, InputStream in,
+ public long createSession(ApplicationId applicationId, Tags tags, TimeoutBudget timeoutBudget, InputStream in,
String contentType, DeployLogger logger) {
File tempDir = uncheck(() -> Files.createTempDirectory("deploy")).toFile();
long sessionId;
try {
- sessionId = createSession(applicationId, timeoutBudget, decompressApplication(in, contentType, tempDir), logger);
+ sessionId = createSession(applicationId, tags, timeoutBudget, decompressApplication(in, contentType, tempDir), logger);
} finally {
cleanupTempDirectory(tempDir, logger);
}
return sessionId;
}
- public long createSession(ApplicationId applicationId, TimeoutBudget timeoutBudget, File applicationDirectory, DeployLogger deployLogger) {
+ public long createSession(ApplicationId applicationId, Tags tags, TimeoutBudget timeoutBudget, File applicationDirectory, DeployLogger deployLogger) {
SessionRepository sessionRepository = getTenant(applicationId).getSessionRepository();
- Session session = sessionRepository.createSessionFromApplicationPackage(applicationDirectory, applicationId, timeoutBudget, deployLogger);
+ Session session = sessionRepository.createSessionFromApplicationPackage(applicationDirectory, applicationId, tags, timeoutBudget, deployLogger);
return session.getSessionId();
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
index 353c8c49053..e6c3eaabdfd 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
@@ -180,8 +180,6 @@ public class ModelContextImpl implements ModelContext {
private final List<String> allowedAthenzProxyIdentities;
private final int maxActivationInhibitedOutOfSyncGroups;
private final ToIntFunction<ClusterSpec.Type> jvmOmitStackTraceInFastThrow;
- private final int maxConcurrentMergesPerContentNode;
- private final int maxMergeQueueSize;
private final double resourceLimitDisk;
private final double resourceLimitMemory;
private final double minNodeRatioPerGroup;
@@ -191,16 +189,9 @@ public class ModelContextImpl implements ModelContext {
private final int maxUnCommittedMemory;
private final boolean forwardIssuesAsErrors;
private final boolean ignoreThreadStackSizes;
- private final boolean unorderedMergeChaining;
private final boolean useV8GeoPositions;
private final int maxCompactBuffers;
private final List<String> ignoredHttpUserAgents;
- private final String mergeThrottlingPolicy;
- private final double persistenceThrottlingWsDecrementFactor;
- private final double persistenceThrottlingWsBackoff;
- private final int persistenceThrottlingWindowSize;
- private final double persistenceThrottlingWsResizeRate;
- private final boolean persistenceThrottlingOfMergeFeedOps;
private final boolean useQrserverServiceName;
private final boolean avoidRenamingSummaryFeatures;
private final Architecture adminClusterArchitecture;
@@ -232,8 +223,6 @@ public class ModelContextImpl implements ModelContext {
this.allowedAthenzProxyIdentities = flagValue(source, appId, version, Flags.ALLOWED_ATHENZ_PROXY_IDENTITIES);
this.maxActivationInhibitedOutOfSyncGroups = flagValue(source, appId, version, Flags.MAX_ACTIVATION_INHIBITED_OUT_OF_SYNC_GROUPS);
this.jvmOmitStackTraceInFastThrow = type -> flagValueAsInt(source, appId, version, type, PermanentFlags.JVM_OMIT_STACK_TRACE_IN_FAST_THROW);
- this.maxConcurrentMergesPerContentNode = flagValue(source, appId, version, Flags.MAX_CONCURRENT_MERGES_PER_NODE);
- this.maxMergeQueueSize = flagValue(source, appId, version, Flags.MAX_MERGE_QUEUE_SIZE);
this.resourceLimitDisk = flagValue(source, appId, version, PermanentFlags.RESOURCE_LIMIT_DISK);
this.resourceLimitMemory = flagValue(source, appId, version, PermanentFlags.RESOURCE_LIMIT_MEMORY);
this.minNodeRatioPerGroup = flagValue(source, appId, version, Flags.MIN_NODE_RATIO_PER_GROUP);
@@ -243,16 +232,9 @@ public class ModelContextImpl implements ModelContext {
this.maxUnCommittedMemory = flagValue(source, appId, version, Flags.MAX_UNCOMMITTED_MEMORY);
this.forwardIssuesAsErrors = flagValue(source, appId, version, PermanentFlags.FORWARD_ISSUES_AS_ERRORS);
this.ignoreThreadStackSizes = flagValue(source, appId, version, Flags.IGNORE_THREAD_STACK_SIZES);
- this.unorderedMergeChaining = flagValue(source, appId, version, Flags.UNORDERED_MERGE_CHAINING);
this.useV8GeoPositions = flagValue(source, appId, version, Flags.USE_V8_GEO_POSITIONS);
this.maxCompactBuffers = flagValue(source, appId, version, Flags.MAX_COMPACT_BUFFERS);
this.ignoredHttpUserAgents = flagValue(source, appId, version, PermanentFlags.IGNORED_HTTP_USER_AGENTS);
- this.mergeThrottlingPolicy = flagValue(source, appId, version, Flags.MERGE_THROTTLING_POLICY);
- this.persistenceThrottlingWsDecrementFactor = flagValue(source, appId, version, Flags.PERSISTENCE_THROTTLING_WS_DECREMENT_FACTOR);
- this.persistenceThrottlingWsBackoff = flagValue(source, appId, version, Flags.PERSISTENCE_THROTTLING_WS_BACKOFF);
- this.persistenceThrottlingWindowSize = flagValue(source, appId, version, Flags.PERSISTENCE_THROTTLING_WINDOW_SIZE);
- this.persistenceThrottlingWsResizeRate = flagValue(source, appId, version, Flags.PERSISTENCE_THROTTLING_WS_RESIZE_RATE);
- this.persistenceThrottlingOfMergeFeedOps = flagValue(source, appId, version, Flags.PERSISTENCE_THROTTLING_OF_MERGE_FEED_OPS);
this.useQrserverServiceName = flagValue(source, appId, version, Flags.USE_QRSERVER_SERVICE_NAME);
this.avoidRenamingSummaryFeatures = flagValue(source, appId, version, Flags.AVOID_RENAMING_SUMMARY_FEATURES);
this.adminClusterArchitecture = Architecture.valueOf(flagValue(source, appId, version, PermanentFlags.ADMIN_CLUSTER_NODE_ARCHITECTURE));
@@ -287,8 +269,6 @@ public class ModelContextImpl implements ModelContext {
@Override public String jvmOmitStackTraceInFastThrowOption(ClusterSpec.Type type) {
return translateJvmOmitStackTraceInFastThrowIntToString(jvmOmitStackTraceInFastThrow, type);
}
- @Override public int maxConcurrentMergesPerNode() { return maxConcurrentMergesPerContentNode; }
- @Override public int maxMergeQueueSize() { return maxMergeQueueSize; }
@Override public double resourceLimitDisk() { return resourceLimitDisk; }
@Override public double resourceLimitMemory() { return resourceLimitMemory; }
@Override public double minNodeRatioPerGroup() { return minNodeRatioPerGroup; }
@@ -298,16 +278,9 @@ public class ModelContextImpl implements ModelContext {
@Override public int maxUnCommittedMemory() { return maxUnCommittedMemory; }
@Override public boolean forwardIssuesAsErrors() { return forwardIssuesAsErrors; }
@Override public boolean ignoreThreadStackSizes() { return ignoreThreadStackSizes; }
- @Override public boolean unorderedMergeChaining() { return unorderedMergeChaining; }
@Override public boolean useV8GeoPositions() { return useV8GeoPositions; }
@Override public int maxCompactBuffers() { return maxCompactBuffers; }
@Override public List<String> ignoredHttpUserAgents() { return ignoredHttpUserAgents; }
- @Override public String mergeThrottlingPolicy() { return mergeThrottlingPolicy; }
- @Override public double persistenceThrottlingWsDecrementFactor() { return persistenceThrottlingWsDecrementFactor; }
- @Override public double persistenceThrottlingWsBackoff() { return persistenceThrottlingWsBackoff; }
- @Override public int persistenceThrottlingWindowSize() { return persistenceThrottlingWindowSize; }
- @Override public double persistenceThrottlingWsResizeRate() { return persistenceThrottlingWsResizeRate; }
- @Override public boolean persistenceThrottlingOfMergeFeedOps() { return persistenceThrottlingOfMergeFeedOps; }
@Override public boolean useQrserverServiceName() { return useQrserverServiceName; }
@Override public boolean avoidRenamingSummaryFeatures() { return avoidRenamingSummaryFeatures; }
@Override public Architecture adminClusterArchitecture() { return adminClusterArchitecture; }
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/host/ConfigRequestHostLivenessTracker.java b/configserver/src/main/java/com/yahoo/vespa/config/server/host/ConfigRequestHostLivenessTracker.java
deleted file mode 100644
index 9195f78b23b..00000000000
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/host/ConfigRequestHostLivenessTracker.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.config.server.host;
-
-import com.yahoo.component.annotation.Inject;
-import com.yahoo.config.provision.HostLivenessTracker;
-
-import java.time.Clock;
-import java.time.Instant;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Keeps track of the last config request made by each hostname.
- * This always remembers all requests forever since the moment is is constructed.
- * This is the implementation which will be injected to components who request a HostLivenessTracker.
- *
- * @author bratseth
- */
-public class ConfigRequestHostLivenessTracker implements HostLivenessTracker {
-
- private final Clock clock;
- private final Map<String, Instant> lastRequestFromHost = new ConcurrentHashMap<>();
-
- @Inject
- @SuppressWarnings("unused")
- public ConfigRequestHostLivenessTracker() {
- this(Clock.systemUTC());
- }
-
- public ConfigRequestHostLivenessTracker(Clock clock) {
- this.clock = clock;
- }
-
- @Override
- public void receivedRequestFrom(String hostname) {
- lastRequestFromHost.put(hostname, clock.instant());
- }
-
- @Override
- public Optional<Instant> lastRequestFrom(String hostname) {
- return Optional.ofNullable(lastRequestFromHost.get(hostname));
- }
-
-}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java
index 25ae21f3383..dc3a05e65f9 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpHandler.java
@@ -51,7 +51,8 @@ public class HttpHandler extends ThreadedHttpRequestHandler {
} catch (IllegalArgumentException | UnsupportedOperationException e) {
return HttpErrorResponse.badRequest(getMessage(e, request));
} catch (NodeAllocationException e) {
- return HttpErrorResponse.nodeAllocationFailure(getMessage(e, request));
+ return e.retryable() ? HttpErrorResponse.nodeAllocationFailure(getMessage(e, request))
+ : HttpErrorResponse.invalidApplicationPackage(getMessage(e, request));
} catch (InternalServerException e) {
return HttpErrorResponse.internalServerError(getMessage(e, request));
} catch (UnknownVespaVersionException e) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/InvalidApplicationException.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/InvalidApplicationException.java
index 70cc89c08af..49776ebdb1b 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/InvalidApplicationException.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/InvalidApplicationException.java
@@ -6,9 +6,9 @@ package com.yahoo.vespa.config.server.http;
*/
public class InvalidApplicationException extends IllegalArgumentException {
- public InvalidApplicationException(String message) {
- super(message);
- }
+ public InvalidApplicationException(String message) { super(message); }
+
+ public InvalidApplicationException(Throwable t) { super(t); }
public InvalidApplicationException(String message, Throwable e) {
super(message, e);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java
index f29b05b66af..71702e2926c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java
@@ -6,6 +6,7 @@ import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
@@ -46,7 +47,7 @@ public class SessionCreateHandler extends SessionHandler {
@Override
protected HttpResponse handlePOST(HttpRequest request) {
- final TenantName tenantName = Utils.getTenantNameFromSessionRequest(request);
+ TenantName tenantName = Utils.getTenantNameFromSessionRequest(request);
Utils.checkThatTenantExists(applicationRepository.tenantRepository(), tenantName);
TimeoutBudget timeoutBudget = SessionHandler.getTimeoutBudget(request, zookeeperBarrierTimeout);
boolean verbose = request.getBooleanProperty("verbose");
@@ -62,8 +63,12 @@ public class SessionCreateHandler extends SessionHandler {
logger = DeployHandlerLogger.forTenant(tenantName, verbose);
// TODO: Avoid using application id here at all
ApplicationId applicationId = ApplicationId.from(tenantName, ApplicationName.defaultName(), InstanceName.defaultName());
- sessionId = applicationRepository.createSession(applicationId, timeoutBudget, request.getData(),
- request.getHeader(ApplicationApiHandler.contentTypeHeader), logger);
+ sessionId = applicationRepository.createSession(applicationId,
+ Tags.empty(),
+ timeoutBudget,
+ request.getData(),
+ request.getHeader(ApplicationApiHandler.contentTypeHeader),
+ logger);
}
return new SessionCreateResponse(logger.slime(), tenantName, request.getHost(), request.getPort(), sessionId);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java
index b7327ef3aa7..1c419ce047a 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/GetConfigProcessor.java
@@ -20,6 +20,7 @@ import com.yahoo.vespa.config.protocol.VespaVersion;
import com.yahoo.vespa.config.server.GetConfigContext;
import com.yahoo.vespa.config.server.UnknownConfigDefinitionException;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
+
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
@@ -150,7 +151,6 @@ class GetConfigProcessor implements Runnable {
@Override
public void run() {
- rpcServer.hostLivenessTracker().receivedRequestFrom(request.getClientHostName());
Pair<GetConfigContext, Long> delayed = getConfig(request);
if (delayed != null) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
index b36967d76a4..a2461706f11 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/rpc/RpcServer.java
@@ -7,7 +7,6 @@ import com.yahoo.component.annotation.Inject;
import com.yahoo.concurrent.ThreadFactoryFactory;
import com.yahoo.config.FileReference;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.HostLivenessTracker;
import com.yahoo.config.provision.TenantName;
import com.yahoo.jrt.Acceptor;
import com.yahoo.jrt.DataValue;
@@ -27,8 +26,8 @@ import com.yahoo.vespa.config.protocol.ConfigResponse;
import com.yahoo.vespa.config.protocol.JRTServerConfigRequest;
import com.yahoo.vespa.config.protocol.JRTServerConfigRequestV3;
import com.yahoo.vespa.config.protocol.Trace;
-import com.yahoo.vespa.config.server.GetConfigContext;
import com.yahoo.vespa.config.server.ConfigActivationListener;
+import com.yahoo.vespa.config.server.GetConfigContext;
import com.yahoo.vespa.config.server.RequestHandler;
import com.yahoo.vespa.config.server.SuperModelRequestHandler;
import com.yahoo.vespa.config.server.application.ApplicationSet;
@@ -44,6 +43,7 @@ import com.yahoo.vespa.filedistribution.FileDownloader;
import com.yahoo.vespa.filedistribution.FileReceiver;
import com.yahoo.vespa.filedistribution.FileReferenceData;
import com.yahoo.vespa.filedistribution.FileReferenceDownload;
+
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Arrays;
@@ -102,7 +102,6 @@ public class RpcServer implements Runnable, ConfigActivationListener, TenantList
private final SuperModelRequestHandler superModelRequestHandler;
private final MetricUpdater metrics;
private final MetricUpdaterFactory metricUpdaterFactory;
- private final HostLivenessTracker hostLivenessTracker;
private final FileServer fileServer;
private final RpcAuthorizer rpcAuthorizer;
@@ -128,13 +127,12 @@ public class RpcServer implements Runnable, ConfigActivationListener, TenantList
@Inject
public RpcServer(ConfigserverConfig config, SuperModelRequestHandler superModelRequestHandler,
MetricUpdaterFactory metrics, HostRegistry hostRegistry,
- HostLivenessTracker hostLivenessTracker, FileServer fileServer, RpcAuthorizer rpcAuthorizer,
+ FileServer fileServer, RpcAuthorizer rpcAuthorizer,
RpcRequestHandlerProvider handlerProvider) {
this.superModelRequestHandler = superModelRequestHandler;
metricUpdaterFactory = metrics;
supervisor.setMaxOutputBufferSize(config.maxoutputbuffersize());
this.metrics = metrics.getOrCreateMetricUpdater(Collections.emptyMap());
- this.hostLivenessTracker = hostLivenessTracker;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(config.maxgetconfigclients());
int rpcWorkerThreads = (config.numRpcThreads() == 0) ? threadsToUse() : config.numRpcThreads();
executorService = new ThreadPoolExecutor(rpcWorkerThreads, rpcWorkerThreads,
@@ -613,8 +611,4 @@ public class RpcServer implements Runnable, ConfigActivationListener, TenantList
req.returnValues().add(new Int32Value(0));
});
}
-
- HostLivenessTracker hostLivenessTracker() {
- return hostLivenessTracker;
- }
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
index 175b6f6457f..ec673377af9 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
@@ -10,6 +10,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.security.X509CertificateUtils;
@@ -41,6 +42,7 @@ public final class PrepareParams {
static final String APPLICATION_NAME_PARAM_NAME = "applicationName";
static final String INSTANCE_PARAM_NAME = "instance";
+ static final String TAGS_PARAM_NAME = "tags";
static final String IGNORE_VALIDATION_PARAM_NAME = "ignoreValidationErrors";
static final String DRY_RUN_PARAM_NAME = "dryRun";
static final String VERBOSE_PARAM_NAME = "verbose";
@@ -57,6 +59,7 @@ public final class PrepareParams {
static final String CLOUD_ACCOUNT = "cloudAccount";
private final ApplicationId applicationId;
+ private final Tags tags;
private final TimeoutBudget timeoutBudget;
private final boolean ignoreValidationErrors;
private final boolean dryRun;
@@ -74,16 +77,27 @@ public final class PrepareParams {
private final List<X509Certificate> operatorCertificates;
private final Optional<CloudAccount> cloudAccount;
- private PrepareParams(ApplicationId applicationId, TimeoutBudget timeoutBudget, boolean ignoreValidationErrors,
- boolean dryRun, boolean verbose, boolean isBootstrap, Optional<Version> vespaVersion,
+ private PrepareParams(ApplicationId applicationId,
+ Tags tags,
+ TimeoutBudget timeoutBudget,
+ boolean ignoreValidationErrors,
+ boolean dryRun,
+ boolean verbose,
+ boolean isBootstrap,
+ Optional<Version> vespaVersion,
List<ContainerEndpoint> containerEndpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
- Optional<DockerImage> dockerImageRepository, Optional<AthenzDomain> athenzDomain,
- Optional<Quota> quota, List<TenantSecretStore> tenantSecretStores,
- boolean force, boolean waitForResourcesInPrepare, List<X509Certificate> operatorCertificates,
+ Optional<DockerImage> dockerImageRepository,
+ Optional<AthenzDomain> athenzDomain,
+ Optional<Quota> quota,
+ List<TenantSecretStore> tenantSecretStores,
+ boolean force,
+ boolean waitForResourcesInPrepare,
+ List<X509Certificate> operatorCertificates,
Optional<CloudAccount> cloudAccount) {
this.timeoutBudget = timeoutBudget;
this.applicationId = Objects.requireNonNull(applicationId);
+ this.tags = tags;
this.ignoreValidationErrors = ignoreValidationErrors;
this.dryRun = dryRun;
this.verbose = verbose;
@@ -110,6 +124,7 @@ public final class PrepareParams {
private boolean force = false;
private boolean waitForResourcesInPrepare = false;
private ApplicationId applicationId = null;
+ private Tags tags = Tags.empty();
private TimeoutBudget timeoutBudget = new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(60));
private Optional<Version> vespaVersion = Optional.empty();
private List<ContainerEndpoint> containerEndpoints = null;
@@ -128,6 +143,11 @@ public final class PrepareParams {
return this;
}
+ public Builder tags(Tags tags) {
+ this.tags = tags;
+ return this;
+ }
+
public Builder ignoreValidationErrors(boolean ignoreValidationErrors) {
this.ignoreValidationErrors = ignoreValidationErrors;
return this;
@@ -258,11 +278,24 @@ public final class PrepareParams {
}
public PrepareParams build() {
- return new PrepareParams(applicationId, timeoutBudget, ignoreValidationErrors, dryRun,
- verbose, isBootstrap, vespaVersion, containerEndpoints,
- endpointCertificateMetadata, dockerImageRepository, athenzDomain,
- quota, tenantSecretStores, force, waitForResourcesInPrepare,
- operatorCertificates, cloudAccount);
+ return new PrepareParams(applicationId,
+ tags,
+ timeoutBudget,
+ ignoreValidationErrors,
+ dryRun,
+ verbose,
+ isBootstrap,
+ vespaVersion,
+ containerEndpoints,
+ endpointCertificateMetadata,
+ dockerImageRepository,
+ athenzDomain,
+ quota,
+ tenantSecretStores,
+ force,
+ waitForResourcesInPrepare,
+ operatorCertificates,
+ cloudAccount);
}
}
@@ -273,6 +306,7 @@ public final class PrepareParams {
.verbose(request.getBooleanProperty(VERBOSE_PARAM_NAME))
.timeoutBudget(SessionHandler.getTimeoutBudget(request, barrierTimeout))
.applicationId(createApplicationId(request, tenant))
+ .tags(Tags.fromString(request.getProperty(TAGS_PARAM_NAME)))
.vespaVersion(request.getProperty(VESPA_VERSION_PARAM_NAME))
.containerEndpoints(request.getProperty(CONTAINER_ENDPOINTS_PARAM_NAME))
.endpointCertificateMetadata(request.getProperty(ENDPOINT_CERTIFICATE_METADATA_PARAM_NAME))
@@ -295,6 +329,7 @@ public final class PrepareParams {
.verbose(booleanValue(params, VERBOSE_PARAM_NAME))
.timeoutBudget(SessionHandler.getTimeoutBudget(getTimeout(params, barrierTimeout)))
.applicationId(createApplicationId(params, tenant))
+ .tags(Tags.fromString(params.field(TAGS_PARAM_NAME).asString()))
.vespaVersion(SlimeUtils.optionalString(params.field(VESPA_VERSION_PARAM_NAME)).orElse(null))
.containerEndpointList(deserialize(params.field(CONTAINER_ENDPOINTS_PARAM_NAME), ContainerEndpointSerializer::endpointListFromSlime, List.of()))
.endpointCertificateMetadata(deserialize(params.field(ENDPOINT_CERTIFICATE_METADATA_PARAM_NAME), EndpointCertificateMetadataSerializer::fromSlime))
@@ -367,9 +402,9 @@ public final class PrepareParams {
return applicationId.application().value();
}
- public ApplicationId getApplicationId() {
- return applicationId;
- }
+ public ApplicationId getApplicationId() { return applicationId; }
+
+ public Tags tags() { return tags; }
/** Returns the Vespa version the nodes running the prepared system should have, or empty to use the system version */
public Optional<Version> vespaVersion() { return vespaVersion; }
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
index 82faeae01e8..e4bbe120c11 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
@@ -12,6 +12,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.path.Path;
import com.yahoo.transaction.Transaction;
@@ -119,6 +120,10 @@ public abstract class Session implements Comparable<Session> {
sessionZooKeeperClient.writeApplicationId(applicationId);
}
+ public void setTags(Tags tags) {
+ sessionZooKeeperClient.writeTags(tags);
+ }
+
void setApplicationPackageReference(FileReference applicationPackageReference) {
sessionZooKeeperClient.writeApplicationPackageReference(Optional.ofNullable(applicationPackageReference));
}
@@ -153,6 +158,10 @@ public abstract class Session implements Comparable<Session> {
.orElseThrow(() -> new RuntimeException("Unable to read application id for session " + sessionId));
}
+ public Tags getTags() {
+ return sessionZooKeeperClient.readTags();
+ }
+
/** Returns application id read from ZooKeeper. Will return Optional.empty() if not found */
public Optional<ApplicationId> getOptionalApplicationId() {
try {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
index 69796e4d0f8..8d023cac88a 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
@@ -17,7 +17,6 @@ import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.EndpointCertificateMetadata;
import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.api.FileDistribution;
-import com.yahoo.config.model.api.ModelContext;
import com.yahoo.config.model.api.Quota;
import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.provision.AllocatedHosts;
@@ -25,6 +24,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.net.HostName;
@@ -35,13 +35,11 @@ import com.yahoo.vespa.config.server.TimeoutBudget;
import com.yahoo.vespa.config.server.application.ApplicationSet;
import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
-import com.yahoo.vespa.config.server.deploy.ModelContextImpl;
import com.yahoo.vespa.config.server.deploy.ZooKeeperDeployer;
import com.yahoo.vespa.config.server.filedistribution.FileDistributionFactory;
import com.yahoo.vespa.config.server.host.HostValidator;
import com.yahoo.vespa.config.server.http.InvalidApplicationException;
import com.yahoo.vespa.config.server.modelfactory.AllocatedHostsFromAllModels;
-import com.yahoo.vespa.config.server.modelfactory.LegacyFlags;
import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
import com.yahoo.vespa.config.server.modelfactory.PreparedModelsBuilder;
import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
@@ -73,8 +71,6 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.zip.ZipException;
-import static com.yahoo.vespa.config.server.ConfigServerSpec.fromConfig;
-
/**
* A SessionPreparer is responsible for preparing a session given an application package.
*
@@ -162,6 +158,7 @@ public class SessionPreparer {
final PrepareParams params;
final ApplicationId applicationId;
+ final Tags tags;
/** The repository part of docker image to be used for this deployment */
final Optional<DockerImage> dockerImageRepository;
@@ -193,6 +190,7 @@ public class SessionPreparer {
this.applicationPackage = applicationPackage;
this.sessionZooKeeperClient = sessionZooKeeperClient;
this.applicationId = params.getApplicationId();
+ this.tags = params.tags();
this.dockerImageRepository = params.dockerImageRepository();
this.vespaVersion = params.vespaVersion().orElse(Vtag.currentVersion);
this.containerEndpointsCache = new ContainerEndpointsCache(tenantPath, curator);
@@ -229,7 +227,7 @@ public class SessionPreparer {
if (! timeoutBudget.hasTimeLeft(step)) {
String used = timeoutBudget.timesUsed();
throw new UncheckedTimeoutException("prepare timed out " + used + " after " + step +
- " step (timeout " + timeoutBudget.timeout() + "): " + applicationId);
+ " step (timeout " + timeoutBudget.timeout() + "): " + applicationId);
}
}
@@ -253,7 +251,7 @@ public class SessionPreparer {
this.preprocessedApplicationPackage = applicationPackage.preprocess(zone, logger);
} catch (IOException | RuntimeException e) {
throw new IllegalArgumentException("Error preprocessing application package for " + applicationId +
- ", session " + sessionZooKeeperClient.sessionId(), e);
+ ", session " + sessionZooKeeperClient.sessionId(), e);
}
checkTimeout("preprocess");
}
@@ -323,10 +321,11 @@ public class SessionPreparer {
void vespaPreprocess(File appDir, File inputXml, ApplicationMetaData metaData) {
try {
new XmlPreProcessor(appDir,
- inputXml,
- metaData.getApplicationId().instance(),
- zone.environment(),
- zone.region())
+ inputXml,
+ metaData.getApplicationId().instance(),
+ zone.environment(),
+ zone.region(),
+ metaData.getTags())
.run();
} catch (ParserConfigurationException | IOException | SAXException | TransformerException e) {
throw new RuntimeException(e);
@@ -351,6 +350,7 @@ public class SessionPreparer {
writeStateToZooKeeper(sessionZooKeeperClient,
preprocessedApplicationPackage,
applicationId,
+ tags,
filereference,
dockerImageRepository,
vespaVersion,
@@ -392,6 +392,7 @@ public class SessionPreparer {
private void writeStateToZooKeeper(SessionZooKeeperClient zooKeeperClient,
ApplicationPackage applicationPackage,
ApplicationId applicationId,
+ Tags tags,
FileReference fileReference,
Optional<DockerImage> dockerImageRepository,
Version vespaVersion,
@@ -408,6 +409,7 @@ public class SessionPreparer {
zkDeployer.deploy(applicationPackage, fileRegistryMap, allocatedHosts);
// Note: When changing the below you need to also change similar calls in SessionRepository.createSessionFromExisting()
zooKeeperClient.writeApplicationId(applicationId);
+ zooKeeperClient.writeTags(tags);
zooKeeperClient.writeApplicationPackageReference(Optional.of(fileReference));
zooKeeperClient.writeVespaVersion(vespaVersion);
zooKeeperClient.writeDockerImageRepository(dockerImageRepository);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
index 9e7af5a44a3..15be909c069 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
@@ -13,6 +13,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.model.application.provider.DeployData;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.secretstore.SecretStore;
@@ -30,6 +31,7 @@ import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs;
import com.yahoo.vespa.config.server.filedistribution.FileDirectory;
import com.yahoo.vespa.config.server.filedistribution.FileDistributionFactory;
+import com.yahoo.vespa.config.server.http.InvalidApplicationException;
import com.yahoo.vespa.config.server.http.UnknownVespaVersionException;
import com.yahoo.vespa.config.server.modelfactory.ActivatedModelsBuilder;
import com.yahoo.vespa.config.server.modelfactory.AllocatedHostsFromAllModels;
@@ -282,10 +284,17 @@ public class SessionRepository {
TimeoutBudget timeoutBudget,
DeployLogger deployLogger) {
ApplicationId existingApplicationId = existingSession.getApplicationId();
+ Tags existingTags = existingSession.getTags();
File existingApp = getSessionAppDir(existingSession.getSessionId());
- LocalSession session = createSessionFromApplication(existingApp, existingApplicationId, internalRedeploy, timeoutBudget, deployLogger);
+ LocalSession session = createSessionFromApplication(existingApp,
+ existingApplicationId,
+ existingTags,
+ internalRedeploy,
+ timeoutBudget,
+ deployLogger);
// Note: Setters below need to be kept in sync with calls in SessionPreparer.writeStateToZooKeeper()
session.setApplicationId(existingApplicationId);
+ session.setTags(existingTags);
session.setApplicationPackageReference(existingSession.getApplicationPackageReference());
session.setVespaVersion(existingSession.getVespaVersion());
session.setDockerImageRepository(existingSession.getDockerImageRepository());
@@ -306,19 +315,20 @@ public class SessionRepository {
*/
public LocalSession createSessionFromApplicationPackage(File applicationDirectory,
ApplicationId applicationId,
+ Tags tags,
TimeoutBudget timeoutBudget,
DeployLogger deployLogger) {
applicationRepo.createApplication(applicationId);
- return createSessionFromApplication(applicationDirectory, applicationId, false, timeoutBudget, deployLogger);
+ return createSessionFromApplication(applicationDirectory, applicationId, tags, false, timeoutBudget, deployLogger);
}
/**
* Creates a local session based on a remote session and the distributed application package.
* Does not wait for session being created on other servers.
*/
- private void createLocalSession(File applicationFile, ApplicationId applicationId, long sessionId) {
+ private void createLocalSession(File applicationFile, ApplicationId applicationId, Tags tags, long sessionId) {
try {
- ApplicationPackage applicationPackage = createApplicationPackage(applicationFile, applicationId, sessionId, false, Optional.empty());
+ ApplicationPackage applicationPackage = createApplicationPackage(applicationFile, applicationId, tags, sessionId, false, Optional.empty());
createLocalSession(sessionId, applicationPackage);
} catch (Exception e) {
throw new RuntimeException("Error creating session " + sessionId, e);
@@ -706,12 +716,13 @@ public class SessionRepository {
private ApplicationPackage createApplication(File userDir,
File configApplicationDir,
ApplicationId applicationId,
+ Tags tags,
long sessionId,
Optional<Long> currentlyActiveSessionId,
boolean internalRedeploy,
Optional<DeployLogger> deployLogger) {
long deployTimestamp = System.currentTimeMillis();
- DeployData deployData = new DeployData(userDir.getAbsolutePath(), applicationId, deployTimestamp, internalRedeploy,
+ DeployData deployData = new DeployData(userDir.getAbsolutePath(), applicationId, tags, deployTimestamp, internalRedeploy,
sessionId, currentlyActiveSessionId.orElse(nonExistingActiveSessionId));
FilesApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(configApplicationDir, deployData);
validateFileExtensions(applicationId, deployLogger, app);
@@ -727,7 +738,7 @@ public class SessionRepository {
UnboundStringFlag flag = PermanentFlags.APPLICATION_FILES_WITH_UNKNOWN_EXTENSION;
String value = flag.bindTo(flagSource).with(APPLICATION_ID, applicationId.serializedForm()).value();
switch (value) {
- case "FAIL" -> throw e;
+ case "FAIL" -> throw new InvalidApplicationException(e);
case "LOG" -> deployLogger.ifPresent(logger -> logger.logApplicationPackage(Level.WARNING, e.getMessage()));
default -> log.log(Level.WARNING, "Unknown value for flag " + flag.id() + ": " + value);
}
@@ -739,13 +750,14 @@ public class SessionRepository {
private LocalSession createSessionFromApplication(File applicationDirectory,
ApplicationId applicationId,
+ Tags tags,
boolean internalRedeploy,
TimeoutBudget timeoutBudget,
DeployLogger deployLogger) {
long sessionId = getNextSessionId();
try {
ensureSessionPathDoesNotExist(sessionId);
- ApplicationPackage app = createApplicationPackage(applicationDirectory, applicationId, sessionId, internalRedeploy, Optional.of(deployLogger));
+ ApplicationPackage app = createApplicationPackage(applicationDirectory, applicationId, tags, sessionId, internalRedeploy, Optional.of(deployLogger));
log.log(Level.FINE, () -> TenantRepository.logPre(tenantName) + "Creating session " + sessionId + " in ZooKeeper");
SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId);
sessionZKClient.createNewSession(clock.instant());
@@ -754,13 +766,14 @@ public class SessionRepository {
waiter.awaitCompletion(Duration.ofSeconds(Math.min(120, timeoutBudget.timeLeft().getSeconds())));
addLocalSession(session);
return session;
- } catch (Exception e) {
+ } catch (IOException e) {
throw new RuntimeException("Error creating session " + sessionId, e);
}
}
private ApplicationPackage createApplicationPackage(File applicationDirectory,
ApplicationId applicationId,
+ Tags tags,
long sessionId,
boolean internalRedeploy,
Optional<DeployLogger> deployLogger) throws IOException {
@@ -773,6 +786,7 @@ public class SessionRepository {
ApplicationPackage applicationPackage = createApplication(applicationDirectory,
userApplicationDir,
applicationId,
+ tags,
sessionId,
activeSessionId,
internalRedeploy,
@@ -884,7 +898,7 @@ public class SessionRepository {
ApplicationId applicationId = sessionZKClient.readApplicationId()
.orElseThrow(() -> new RuntimeException("Could not find application id for session " + sessionId));
log.log(Level.FINE, () -> "Creating local session for tenant '" + tenantName + "' with session id " + sessionId);
- createLocalSession(sessionDir, applicationId, sessionId);
+ createLocalSession(sessionDir, applicationId, sessionZKClient.readTags(), sessionId);
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
index 988d13b1978..9218b03af1e 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java
@@ -14,6 +14,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.path.Path;
import com.yahoo.slime.SlimeUtils;
@@ -57,6 +58,7 @@ public class SessionZooKeeperClient {
// NOTE: Any state added here MUST also be propagated in com.yahoo.vespa.config.server.deploy.Deployment.prepare()
static final String APPLICATION_ID_PATH = "applicationId";
+ static final String TAGS_PATH = "tags";
static final String APPLICATION_PACKAGE_REFERENCE_PATH = "applicationPackageReference";
private static final String VERSION_PATH = "version";
private static final String CREATE_TIME_PATH = "createTime";
@@ -171,6 +173,20 @@ public class SessionZooKeeperClient {
return curator.getData(applicationIdPath()).map(d -> ApplicationId.fromSerializedForm(Utf8.toString(d)));
}
+ private Path tagsPath() {
+ return sessionPath.append(TAGS_PATH);
+ }
+
+ public void writeTags(Tags tags) {
+ curator.set(tagsPath(), Utf8.toBytes(tags.asString()));
+ }
+
+ public Tags readTags() {
+ Optional<byte[]> data = curator.getData(tagsPath());
+ if (data.isEmpty()) return Tags.empty();
+ return Tags.fromString(Utf8.toString(data.get()));
+ }
+
void writeApplicationPackageReference(Optional<FileReference> applicationPackageReference) {
applicationPackageReference.ifPresent(
reference -> curator.set(applicationPackageReferencePath(), Utf8.toBytes(reference.value())));
diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml
index 650176829e6..b8397722b3d 100644
--- a/configserver/src/main/resources/configserver-app/services.xml
+++ b/configserver/src/main/resources/configserver-app/services.xml
@@ -30,7 +30,6 @@
<component id="com.yahoo.vespa.config.server.host.HostRegistry" bundle="configserver" />
<component id="com.yahoo.vespa.config.server.ApplicationRepository" bundle="configserver" />
<component id="com.yahoo.vespa.config.server.version.VersionState" bundle="configserver" />
- <component id="com.yahoo.vespa.config.server.host.ConfigRequestHostLivenessTracker" bundle="configserver" />
<component id="com.yahoo.config.provision.Zone" bundle="config-provisioning" />
<component id="com.yahoo.vespa.config.server.application.ConfigConvergenceChecker" bundle="configserver" />
<component id="com.yahoo.vespa.config.server.application.HttpProxy" bundle="configserver" />
diff --git a/configserver/src/test/apps/hosted-invalid-file-extension/deployment.xml b/configserver/src/test/apps/hosted-invalid-file-extension/deployment.xml
new file mode 100644
index 00000000000..43bc1496b2c
--- /dev/null
+++ b/configserver/src/test/apps/hosted-invalid-file-extension/deployment.xml
@@ -0,0 +1,7 @@
+<!-- Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<deployment version='1.0'>
+ <prod>
+ <region active="true">us-north-1</region>
+ <region active="true">us-north-2</region>
+ </prod>
+</deployment>
diff --git a/configserver/src/test/apps/hosted-invalid-file-extension/schemas/file-with-invalid.extension b/configserver/src/test/apps/hosted-invalid-file-extension/schemas/file-with-invalid.extension
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/configserver/src/test/apps/hosted-invalid-file-extension/schemas/file-with-invalid.extension
diff --git a/configserver/src/test/apps/hosted-invalid-file-extension/services.xml b/configserver/src/test/apps/hosted-invalid-file-extension/services.xml
new file mode 100644
index 00000000000..08b1d5d01c2
--- /dev/null
+++ b/configserver/src/test/apps/hosted-invalid-file-extension/services.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<services version="1.0">
+
+ <container version="1.0">
+ <http>
+ <filtering>
+ <access-control domain="myDomain" write="true" />
+ </filtering>
+ <server id="foo"/>
+ </http>
+ <search/>
+ <nodes count='1'/>
+ </container>
+
+</services>
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
index 99487230c5d..b185bb5915d 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
@@ -17,6 +17,7 @@ import com.yahoo.config.provision.HostFilter;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NetworkPorts;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.exception.ActivationConflictException;
import com.yahoo.container.jdisc.HttpResponse;
@@ -847,7 +848,7 @@ public class ApplicationRepositoryTest {
}
private long createSession(ApplicationId applicationId, TimeoutBudget timeoutBudget, File app) {
- return applicationRepository.createSession(applicationId, timeoutBudget, app, new BaseDeployLogger());
+ return applicationRepository.createSession(applicationId, Tags.empty(), timeoutBudget, app, new BaseDeployLogger());
}
private long createSessionFromExisting(ApplicationId applicationId, TimeoutBudget timeoutBudget) {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java
index 6d4217d3df4..b1b42465b00 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.deploy;
+import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.model.api.ConfigChangeAction;
@@ -36,6 +37,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import java.nio.file.Files;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
@@ -49,6 +51,7 @@ import java.util.stream.IntStream;
import static com.yahoo.vespa.config.server.deploy.DeployTester.CountingModelFactory;
import static com.yahoo.vespa.config.server.deploy.DeployTester.createFailingModelFactory;
import static com.yahoo.vespa.config.server.deploy.DeployTester.createHostedModelFactory;
+import static com.yahoo.yolean.Exceptions.uncheck;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -489,6 +492,25 @@ public class HostedDeployTest {
}
@Test
+ public void testThatAppWithFilesWithInvalidFileExtensionFails() {
+ DeployTester tester = new DeployTester.Builder(temporaryFolder)
+ .configserverConfig(new ConfigserverConfig(new ConfigserverConfig.Builder()
+ .hostedVespa(true)
+ .configServerDBDir(uncheck(() -> Files.createTempDirectory("serverdb")).toString())
+ .configDefinitionsDir(uncheck(() -> Files.createTempDirectory("configdefinitions")).toString())
+ .fileReferencesDir(uncheck(() -> Files.createTempDirectory("configdefinitions")).toString())))
+ .modelFactory(createHostedModelFactory(Version.fromString("8.7.6"), Clock.systemUTC()))
+ .build();
+ try {
+ tester.deployApp("src/test/apps/hosted-invalid-file-extension/", "8.7.6");
+ fail();
+ } catch (InvalidApplicationException e) {
+ assertEquals("java.lang.IllegalArgumentException: File in application package with unknown extension: schemas/file-with-invalid.extension, please delete or move file to another directory.",
+ e.getMessage());
+ }
+ }
+
+ @Test
public void testRedeployWithCloudAccount() {
CloudAccount cloudAccount = new CloudAccount("012345678912");
DeployTester tester = new DeployTester.Builder(temporaryFolder)
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
index fd6440a9632..653753c97e7 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
@@ -13,6 +13,7 @@ import com.yahoo.config.model.application.provider.MockFileRegistry;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostSpec;
+import com.yahoo.config.provision.Tags;
import com.yahoo.path.Path;
import com.yahoo.text.Utf8;
import com.yahoo.vespa.config.server.zookeeper.ZKApplicationPackage;
@@ -59,6 +60,7 @@ public class ZooKeeperClientTest {
ApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(new File("src/test/apps/zkfeed"),
new DeployData("/bar/baz",
ApplicationId.from("default", "appName", "default"),
+ Tags.fromString("tag1 tag2"),
1345L,
true,
3L,
@@ -121,6 +123,7 @@ public class ZooKeeperClientTest {
assertTrue(metaData.getChecksum().length() > 0);
assertTrue(metaData.isInternalRedeploy());
assertEquals("/bar/baz", metaData.getDeployPath());
+ assertEquals(Tags.fromString("tag1 tag2"), metaData.getTags());
assertEquals(1345, metaData.getDeployTimestamp().longValue());
assertEquals(3, metaData.getGeneration().longValue());
assertEquals(2, metaData.getPreviousActiveGeneration());
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
index 1c71ef0b7fb..816f7e3dcec 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
@@ -6,6 +6,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.application.api.ApplicationMetaData;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.HttpResponse;
@@ -131,6 +132,7 @@ public class SessionActiveHandlerTest {
void invoke() {
long sessionId = applicationRepository.createSession(applicationId(),
+ Tags.empty(),
new TimeoutBudget(clock, Duration.ofSeconds(10)),
testApp,
new BaseDeployLogger());
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
index 2b07cffffce..525a969ed1e 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
@@ -10,6 +10,7 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeAllocationException;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.jdisc.http.HttpRequest;
@@ -187,7 +188,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
}
@Test
- public void require_that_preparing_with_multiple_tenants_work() throws Exception {
+ public void prepare_with_multiple_tenants() throws Exception {
SessionHandler handler = createHandler();
TenantName defaultTenant = TenantName.from("test2");
@@ -206,7 +207,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
assertEquals(sessionId, sessionId2); // Want to test when they are equal (but for different tenants)
pathPrefix = "/application/v2/tenant/" + tenant + "/session/" + sessionId2 +
- "/prepared?applicationName=" + applicationName;
+ "/prepared?applicationName=" + applicationName;
response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix));
assertNotNull(response);
assertEquals(SessionHandlerTest.getRenderedString(response), OK, response.getStatus());
@@ -214,7 +215,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
ApplicationId applicationId3 = ApplicationId.from(tenant.value(), applicationName, "quux");
long sessionId3 = createSession(applicationId3);
pathPrefix = "/application/v2/tenant/" + tenant + "/session/" + sessionId3 +
- "/prepared?applicationName=" + applicationName + "&instance=quux";
+ "/prepared?applicationName=" + applicationName + "&instance=quux";
response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix));
assertNotNull(response);
assertEquals(SessionHandlerTest.getRenderedString(response), OK, response.getStatus());
@@ -243,7 +244,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
FailingSessionPrepareHandler handler = new FailingSessionPrepareHandler(SessionPrepareHandler.testContext(),
applicationRepository,
configserverConfig,
- new NodeAllocationException(exceptionMessage));
+ new NodeAllocationException(exceptionMessage, true));
HttpResponse response = handler.handle(createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, sessionId));
assertEquals(400, response.getStatus());
Slime data = getData(response);
@@ -324,7 +325,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
}
private long createSession(ApplicationId applicationId) {
- return applicationRepository.createSession(applicationId, timeoutBudget, app, new BaseDeployLogger());
+ return applicationRepository.createSession(applicationId, Tags.empty(), timeoutBudget, app, new BaseDeployLogger());
}
private static class FailingSessionPrepareHandler extends SessionPrepareHandler {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpcServer.java b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpcServer.java
index 3272689473e..0f9ce9eff13 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpcServer.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/MockRpcServer.java
@@ -7,7 +7,6 @@ import com.yahoo.vespa.config.protocol.ConfigResponse;
import com.yahoo.vespa.config.protocol.JRTServerConfigRequest;
import com.yahoo.vespa.config.server.GetConfigContext;
import com.yahoo.vespa.config.server.filedistribution.FileServer;
-import com.yahoo.vespa.config.server.host.ConfigRequestHostLivenessTracker;
import com.yahoo.vespa.config.server.host.HostRegistry;
import com.yahoo.vespa.config.server.monitoring.Metrics;
import com.yahoo.vespa.config.server.rpc.security.NoopRpcAuthorizer;
@@ -38,7 +37,6 @@ public class MockRpcServer extends RpcServer {
null,
Metrics.createTestMetrics(),
new HostRegistry(),
- new ConfigRequestHostLivenessTracker(),
new FileServer(tempDir),
new NoopRpcAuthorizer(),
new RpcRequestHandlerProvider());
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcTester.java
index 441f6c3a6ce..e5ed4e4673d 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcTester.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/RpcTester.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.config.server.rpc;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.HostLivenessTracker;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.jrt.Request;
@@ -21,7 +20,6 @@ import com.yahoo.vespa.config.server.SuperModelRequestHandler;
import com.yahoo.vespa.config.server.TestConfigDefinitionRepo;
import com.yahoo.vespa.config.server.application.OrchestratorMock;
import com.yahoo.vespa.config.server.filedistribution.FileServer;
-import com.yahoo.vespa.config.server.host.ConfigRequestHostLivenessTracker;
import com.yahoo.vespa.config.server.host.HostRegistry;
import com.yahoo.vespa.config.server.monitoring.Metrics;
import com.yahoo.vespa.config.server.rpc.security.NoopRpcAuthorizer;
@@ -51,7 +49,6 @@ public class RpcTester implements AutoCloseable {
private final ManualClock clock = new ManualClock(Instant.ofEpochMilli(100));
private final String myHostname = HostName.getLocalhost();
- private final HostLivenessTracker hostLivenessTracker = new ConfigRequestHostLivenessTracker(clock);
private final Spec spec;
private final RpcServer rpcServer;
@@ -95,7 +92,6 @@ public class RpcTester implements AutoCloseable {
.withProvisioner(new MockProvisioner())
.withOrchestrator(new OrchestratorMock())
.build();
- assertFalse(hostLivenessTracker.lastRequestFrom(myHostname).isPresent());
}
public void close() {
@@ -122,7 +118,6 @@ public class RpcTester implements AutoCloseable {
new InMemoryFlagSource())),
Metrics.createTestMetrics(),
hostRegistry,
- hostLivenessTracker,
new FileServer(temporaryFolder.newFolder()),
new NoopRpcAuthorizer(),
new RpcRequestHandlerProvider());
@@ -167,8 +162,6 @@ public class RpcTester implements AutoCloseable {
void performRequest(Request req) {
clock.advance(Duration.ofMillis(10));
sup.connect(spec).invokeSync(req, Duration.ofSeconds(10));
- if (req.methodName().equals(RpcServer.getConfigMethodName))
- assertEquals(clock.instant(), hostLivenessTracker.lastRequestFrom(myHostname).get());
}
RpcServer rpcServer() {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java
index e5b550fdc1a..34921db1bb7 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.model.api.EndpointCertificateMetadata;
import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
@@ -62,6 +63,7 @@ public class PrepareParamsTest {
PrepareParams prepareParams = createParams("http://foo:19071/application/v2/", TenantName.defaultName());
assertEquals(ApplicationId.defaultId(), prepareParams.getApplicationId());
+ assertTrue(prepareParams.tags().isEmpty());
assertFalse(prepareParams.isDryRun());
assertFalse(prepareParams.isVerbose());
assertFalse(prepareParams.ignoreValidationErrors());
@@ -72,6 +74,18 @@ public class PrepareParamsTest {
}
@Test
+ public void testTagsParsing() throws IOException {
+ var prepareParams = createParams(request + "&" + PrepareParams.TAGS_PARAM_NAME + "=tag1%20tag2", TenantName.from("foo"));
+ assertEquals(Tags.fromString("tag1 tag2"), prepareParams.tags());
+
+ // Verify using json object
+ var slime = SlimeUtils.jsonToSlime(json);
+ slime.get().setString(PrepareParams.TAGS_PARAM_NAME, "tag1 tag2");
+ PrepareParams prepareParamsJson = PrepareParams.fromJson(SlimeUtils.toJsonBytes(slime), TenantName.from("foo"), Duration.ofSeconds(60));
+ assertPrepareParamsEqual(prepareParams, prepareParamsJson);
+ }
+
+ @Test
public void testCorrectParsingWithContainerEndpoints() throws IOException {
var endpoints = List.of(new ContainerEndpoint("qrs1", ApplicationClusterEndpoint.Scope.global,
List.of("c1.example.com",
@@ -207,6 +221,7 @@ public class PrepareParamsTest {
assertEquals(urlParams.force(), jsonParams.force());
assertEquals(urlParams.waitForResourcesInPrepare(), jsonParams.waitForResourcesInPrepare());
assertEquals(urlParams.getApplicationId(), jsonParams.getApplicationId());
+ assertEquals(urlParams.tags(), jsonParams.tags());
assertEquals(urlParams.getTimeoutBudget().timeout(), jsonParams.getTimeoutBudget().timeout());
assertEquals(urlParams.vespaVersion(), jsonParams.vespaVersion());
assertEquals(urlParams.containerEndpoints(), jsonParams.containerEndpoints());
diff --git a/container-core/abi-spec.json b/container-core/abi-spec.json
index 5985e79b786..d324656abf2 100644
--- a/container-core/abi-spec.json
+++ b/container-core/abi-spec.json
@@ -1051,8 +1051,6 @@
"public com.yahoo.jdisc.http.ConnectorConfig$Builder healthCheckProxy(java.util.function.Consumer)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder proxyProtocol(com.yahoo.jdisc.http.ConnectorConfig$ProxyProtocol$Builder)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder proxyProtocol(java.util.function.Consumer)",
- "public com.yahoo.jdisc.http.ConnectorConfig$Builder secureRedirect(com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder)",
- "public com.yahoo.jdisc.http.ConnectorConfig$Builder secureRedirect(java.util.function.Consumer)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder maxRequestsPerConnection(int)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder maxConnectionLife(double)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder http2Enabled(boolean)",
@@ -1074,7 +1072,6 @@
"public com.yahoo.jdisc.http.ConnectorConfig$TlsClientAuthEnforcer$Builder tlsClientAuthEnforcer",
"public com.yahoo.jdisc.http.ConnectorConfig$HealthCheckProxy$Builder healthCheckProxy",
"public com.yahoo.jdisc.http.ConnectorConfig$ProxyProtocol$Builder proxyProtocol",
- "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder secureRedirect",
"public com.yahoo.jdisc.http.ConnectorConfig$Http2$Builder http2",
"public com.yahoo.jdisc.http.ConnectorConfig$ServerName$Builder serverName"
]
@@ -1193,37 +1190,6 @@
],
"fields": []
},
- "com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder": {
- "superClass": "java.lang.Object",
- "interfaces": [
- "com.yahoo.config.ConfigBuilder"
- ],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>()",
- "public void <init>(com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect)",
- "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder enabled(boolean)",
- "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder port(int)",
- "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect build()"
- ],
- "fields": []
- },
- "com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect": {
- "superClass": "com.yahoo.config.InnerNode",
- "interfaces": [],
- "attributes": [
- "public",
- "final"
- ],
- "methods": [
- "public void <init>(com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder)",
- "public boolean enabled()",
- "public int port()"
- ],
- "fields": []
- },
"com.yahoo.jdisc.http.ConnectorConfig$ServerName$Builder": {
"superClass": "java.lang.Object",
"interfaces": [
@@ -1236,9 +1202,13 @@
"public void <init>()",
"public void <init>(com.yahoo.jdisc.http.ConnectorConfig$ServerName)",
"public com.yahoo.jdisc.http.ConnectorConfig$ServerName$Builder fallback(java.lang.String)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$ServerName$Builder allowed(java.lang.String)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$ServerName$Builder allowed(java.util.Collection)",
"public com.yahoo.jdisc.http.ConnectorConfig$ServerName build()"
],
- "fields": []
+ "fields": [
+ "public java.util.List allowed"
+ ]
},
"com.yahoo.jdisc.http.ConnectorConfig$ServerName": {
"superClass": "com.yahoo.config.InnerNode",
@@ -1249,7 +1219,9 @@
],
"methods": [
"public void <init>(com.yahoo.jdisc.http.ConnectorConfig$ServerName$Builder)",
- "public java.lang.String fallback()"
+ "public java.lang.String fallback()",
+ "public java.util.List allowed()",
+ "public java.lang.String allowed(int)"
],
"fields": []
},
@@ -1443,7 +1415,6 @@
"public com.yahoo.jdisc.http.ConnectorConfig$TlsClientAuthEnforcer tlsClientAuthEnforcer()",
"public com.yahoo.jdisc.http.ConnectorConfig$HealthCheckProxy healthCheckProxy()",
"public com.yahoo.jdisc.http.ConnectorConfig$ProxyProtocol proxyProtocol()",
- "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect secureRedirect()",
"public int maxRequestsPerConnection()",
"public double maxConnectionLife()",
"public boolean http2Enabled()",
diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java
index 2890cbfb5ab..9f270acce5f 100644
--- a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java
+++ b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java
@@ -29,6 +29,7 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
+import java.util.PriorityQueue;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -43,13 +44,13 @@ import static java.nio.charset.StandardCharsets.UTF_8;
* @author jonmv
*/
class LogReader {
+
static final Pattern logArchivePathPattern = Pattern.compile("(\\d{4})/(\\d{2})/(\\d{2})/(\\d{2})-\\d+(\\.gz|\\.zst)?");
static final Pattern vespaLogPathPattern = Pattern.compile("vespa\\.log(?:-(\\d{4})-(\\d{2})-(\\d{2})\\.(\\d{2})-(\\d{2})-(\\d{2})(?:\\.gz|\\.zst)?)?");
private final Path logDirectory;
private final Pattern logFilePattern;
-
LogReader(String logDirectory, String logFilePattern) {
this(Paths.get(Defaults.getDefaults().underVespaHome(logDirectory)), Pattern.compile(logFilePattern));
}
@@ -73,10 +74,18 @@ class LogReader {
Iterator<LineWithTimestamp> lines = Iterators.mergeSorted(logLineIterators,
Comparator.comparingDouble(LineWithTimestamp::timestamp));
+ PriorityQueue<LineWithTimestamp> heap = new PriorityQueue<>(Comparator.comparingDouble(LineWithTimestamp::timestamp));
while (lines.hasNext()) {
+ heap.offer(lines.next());
+ if (heap.size() > 1000) {
+ if (linesWritten++ >= maxLines) return;
+ writer.write(heap.poll().line);
+ writer.newLine();
+ }
+ }
+ while ( ! heap.isEmpty()) {
if (linesWritten++ >= maxLines) return;
- String line = lines.next().line();
- writer.write(line);
+ writer.write(heap.poll().line);
writer.newLine();
}
}
@@ -170,7 +179,7 @@ class LogReader {
if (parts.length != 7)
continue;
- if (hostname.map(host -> !host.equals(parts[1])).orElse(false))
+ if (hostname.map(host -> ! host.equals(parts[1])).orElse(false))
continue;
double timestamp = Double.parseDouble(parts[0]);
diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
index cfd2244bd70..25fe775082d 100644
--- a/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
+++ b/container-core/src/main/java/com/yahoo/container/jdisc/state/StateHandler.java
@@ -102,22 +102,14 @@ public class StateHandler extends AbstractRequestHandler {
private ByteBuffer buildContent(URI requestUri) {
String suffix = resolvePath(requestUri);
- switch (suffix) {
- case "":
- return ByteBuffer.wrap(apiLinks(requestUri));
- case CONFIG_GENERATION_PATH:
- return ByteBuffer.wrap(config);
- case HISTOGRAMS_PATH:
- return ByteBuffer.wrap(buildHistogramsOutput());
- case HEALTH_PATH:
- case METRICS_PATH:
- return ByteBuffer.wrap(buildMetricOutput(suffix));
- case VERSION_PATH:
- return ByteBuffer.wrap(buildVersionOutput());
- default:
- // XXX should possibly do something else here
- return ByteBuffer.wrap(buildMetricOutput(suffix));
- }
+ return switch (suffix) {
+ case "" -> ByteBuffer.wrap(apiLinks(requestUri));
+ case CONFIG_GENERATION_PATH -> ByteBuffer.wrap(config);
+ case HISTOGRAMS_PATH -> ByteBuffer.wrap(buildHistogramsOutput());
+ case HEALTH_PATH, METRICS_PATH -> ByteBuffer.wrap(buildMetricOutput(suffix));
+ case VERSION_PATH -> ByteBuffer.wrap(buildVersionOutput());
+ default -> ByteBuffer.wrap(buildMetricOutput(suffix)); // XXX should possibly do something else here
+ };
}
private byte[] apiLinks(URI requestUri) {
@@ -228,13 +220,11 @@ public class StateHandler extends AbstractRequestHandler {
for (Tuple tuple : collapseMetrics(metricSnapshot, consumer)) {
ObjectNode jsonTuple = jsonMapper.createObjectNode();
jsonTuple.put("name", tuple.key);
- if (tuple.val instanceof CountMetric) {
- CountMetric count = (CountMetric)tuple.val;
+ if (tuple.val instanceof CountMetric count) {
jsonTuple.set("values", jsonMapper.createObjectNode()
.put("count", count.getCount())
.put("rate", sanitizeDouble(count.getCount() * 1000.0) / periodInMillis));
- } else if (tuple.val instanceof GaugeMetric) {
- GaugeMetric gauge = (GaugeMetric) tuple.val;
+ } else if (tuple.val instanceof GaugeMetric gauge) {
ObjectNode valueFields = jsonMapper.createObjectNode();
valueFields.put("average", sanitizeDouble(gauge.getAverage()))
.put("sum", sanitizeDouble(gauge.getSum()))
@@ -274,15 +264,11 @@ public class StateHandler extends AbstractRequestHandler {
}
private static List<Tuple> collapseMetrics(MetricSnapshot snapshot, String consumer) {
- switch (consumer) {
- case HEALTH_PATH:
- return collapseHealthMetrics(snapshot);
- case "all": // deprecated name
- case METRICS_PATH:
- return flattenAllMetrics(snapshot);
- default:
- throw new IllegalArgumentException("Unknown consumer '" + consumer + "'.");
- }
+ return switch (consumer) {
+ case HEALTH_PATH -> collapseHealthMetrics(snapshot);
+ case "all", METRICS_PATH -> flattenAllMetrics(snapshot); // TODO: Remove "all" on Vespa 9
+ default -> throw new IllegalArgumentException("Unknown consumer '" + consumer + "'.");
+ };
}
private static List<Tuple> collapseHealthMetrics(MetricSnapshot snapshot) {
@@ -291,8 +277,7 @@ public class StateHandler extends AbstractRequestHandler {
for (Map.Entry<MetricDimensions, MetricSet> entry : snapshot) {
MetricSet metricSet = entry.getValue();
MetricValue val = metricSet.get("serverTotalSuccessfulResponseLatency");
- if (val instanceof GaugeMetric) {
- GaugeMetric gauge = (GaugeMetric)val;
+ if (val instanceof GaugeMetric gauge) {
latencySeconds.add(GaugeMetric.newInstance(gauge.getLast() / 1000,
gauge.getMax() / 1000,
gauge.getMin() / 1000,
diff --git a/container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java b/container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
index c469c90f6ab..2639239d23f 100644
--- a/container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
+++ b/container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
@@ -65,16 +65,14 @@ public class AccessLogEntry {
return null;
}
- final Map<String, List<String>> newMapWithImmutableValues = mapValues(
+ Map<String, List<String>> newMapWithImmutableValues = mapValues(
keyValues.entrySet(),
valueList -> Collections.unmodifiableList(new ArrayList<>(valueList)));
return Collections.unmodifiableMap(newMapWithImmutableValues);
}
}
- private static <K, V1, V2> Map<K, V2> mapValues(
- final Set<Map.Entry<K, V1>> entrySet,
- final Function<V1, V2> valueConverter) {
+ private static <K, V1, V2> Map<K, V2> mapValues(Set<Map.Entry<K, V1>> entrySet, Function<V1, V2> valueConverter) {
return entrySet.stream()
.collect(toMap(
entry -> entry.getKey(),
diff --git a/container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java b/container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java
index 23804613f4e..b4692a43890 100644
--- a/container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java
+++ b/container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java
@@ -14,6 +14,9 @@ import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
import static java.util.Objects.requireNonNull;
@@ -47,7 +50,7 @@ public class RequestLogEntry {
private final Principal sslPrincipal;
private final HitCounts hitCounts;
private final TraceNode traceNode;
- private final Map<String, Collection<String>> extraAttributes;
+ private final SortedMap<String, Collection<String>> extraAttributes;
private RequestLogEntry(Builder builder) {
this.connectionId = builder.connectionId;
@@ -99,7 +102,7 @@ public class RequestLogEntry {
public Optional<Principal> sslPrincipal() { return Optional.ofNullable(sslPrincipal); }
public Optional<HitCounts> hitCounts() { return Optional.ofNullable(hitCounts); }
public Optional<TraceNode> traceNode() { return Optional.ofNullable(traceNode); }
- public Collection<String> extraAttributeKeys() { return Collections.unmodifiableCollection(extraAttributes.keySet()); }
+ public SortedSet<String> extraAttributeKeys() { return Collections.unmodifiableSortedSet((SortedSet<String>)extraAttributes.keySet()); }
public Collection<String> extraAttributeValues(String key) { return Collections.unmodifiableCollection(extraAttributes.get(key)); }
private static OptionalInt optionalInt(int value) {
@@ -112,8 +115,8 @@ public class RequestLogEntry {
return OptionalLong.of(value);
}
- private static Map<String, Collection<String>> copyExtraAttributes(Map<String, Collection<String>> extraAttributes) {
- Map<String, Collection<String>> copy = new HashMap<>();
+ private static SortedMap<String, Collection<String>> copyExtraAttributes(Map<String, Collection<String>> extraAttributes) {
+ SortedMap<String, Collection<String>> copy = new TreeMap<>();
extraAttributes.forEach((key, value) -> copy.put(key, new ArrayList<>(value)));
return copy;
}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
index bf278981b69..6282e334409 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
@@ -9,7 +9,6 @@ import com.yahoo.jdisc.http.ssl.impl.DefaultConnectorSsl;
import com.yahoo.security.tls.MixedMode;
import com.yahoo.security.tls.TransportSecurityUtils;
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
-import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
@@ -57,7 +56,6 @@ public class ConnectorFactory {
// e.g. due to TLS configuration through environment variables.
private static void runtimeConnectorConfigValidation(ConnectorConfig config) {
validateProxyProtocolConfiguration(config);
- validateSecureRedirectConfig(config);
}
private static void validateProxyProtocolConfiguration(ConnectorConfig config) {
@@ -70,28 +68,15 @@ public class ConnectorFactory {
}
}
- private static void validateSecureRedirectConfig(ConnectorConfig config) {
- if (config.secureRedirect().enabled() && isSslEffectivelyEnabled(config)) {
- throw new IllegalArgumentException("Secure redirect can only be enabled on connectors without HTTPS");
- }
- }
-
public ConnectorConfig getConnectorConfig() {
return connectorConfig;
}
public ServerConnector createConnector(final Metric metric, final Server server, JettyConnectionLogger connectionLogger,
ConnectionMetricAggregator connectionMetricAggregator) {
- ServerConnector connector = new JDiscServerConnector(
+ return new JDiscServerConnector(
connectorConfig, metric, server, connectionLogger, connectionMetricAggregator,
createConnectionFactories(metric).toArray(ConnectionFactory[]::new));
- connector.setPort(connectorConfig.listenPort());
- connector.setName(connectorConfig.name());
- connector.setAcceptQueueSize(connectorConfig.acceptQueueSize());
- connector.setReuseAddress(connectorConfig.reuseAddress());
- connector.setIdleTimeout(toMillis(connectorConfig.idleTimeout()));
- connector.addBean(HttpCompliance.RFC7230);
- return connector;
}
private List<ConnectionFactory> createConnectionFactories(Metric metric) {
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorSpecificContextHandler.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorSpecificContextHandler.java
new file mode 100644
index 00000000000..3554d371cf8
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorSpecificContextHandler.java
@@ -0,0 +1,35 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.server.jetty;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+class ConnectorSpecificContextHandler extends ContextHandler {
+
+ private final JDiscServerConnector connector;
+
+ ConnectorSpecificContextHandler(JDiscServerConnector c) {
+ this.connector = c;
+ List<String> allowedServerNames = c.connectorConfig().serverName().allowed();
+ if (allowedServerNames.isEmpty()) {
+ setVirtualHosts(new String[]{"@%s".formatted(c.getName())});
+ } else {
+ String[] virtualHosts = allowedServerNames.stream()
+ .map(name -> "%s@%s".formatted(name, c.getName()))
+ .toArray(String[]::new);
+ setVirtualHosts(virtualHosts);
+ }
+ }
+
+ @Override
+ public boolean checkVirtualHost(Request req) {
+ // Accept health checks independently of virtual host configuration when connector matches
+ if (req.getRequestURI().equals("/status.html") && req.getHttpChannel().getConnector() == connector) return true;
+ return super.checkVirtualHost(req);
+ }
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FormPostRequestHandler.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FormPostRequestHandler.java
index 72057563e36..d2dbfaa3514 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FormPostRequestHandler.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FormPostRequestHandler.java
@@ -54,17 +54,14 @@ class FormPostRequestHandler extends AbstractRequestHandler implements ContentCh
* @param delegateHandler the "real" request handler that this handler wraps
* @param contentCharsetName name of the charset to use when interpreting the content data
*/
- public FormPostRequestHandler(
- final RequestHandler delegateHandler,
- final String contentCharsetName,
- final boolean removeBody) {
+ public FormPostRequestHandler(RequestHandler delegateHandler, String contentCharsetName, boolean removeBody) {
this.delegateHandler = Objects.requireNonNull(delegateHandler);
this.contentCharsetName = Objects.requireNonNull(contentCharsetName);
this.removeBody = removeBody;
}
@Override
- public ContentChannel handleRequest(final Request request, final ResponseHandler responseHandler) {
+ public ContentChannel handleRequest(Request request, ResponseHandler responseHandler) {
Preconditions.checkArgument(request instanceof HttpRequest, "Expected HttpRequest, got " + request);
Objects.requireNonNull(responseHandler, "responseHandler");
@@ -77,24 +74,24 @@ class FormPostRequestHandler extends AbstractRequestHandler implements ContentCh
}
@Override
- public void write(final ByteBuffer buf, final CompletionHandler completionHandler) {
+ public void write(ByteBuffer buf, CompletionHandler completionHandler) {
assert buf.hasArray();
accumulatedRequestContent.write(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
completionHandler.completed();
}
@Override
- public void close(final CompletionHandler completionHandler) {
- try (final ResourceReference ref = requestReference) {
- final byte[] requestContentBytes = accumulatedRequestContent.toByteArray();
- final String content = new String(requestContentBytes, contentCharset);
+ public void close(CompletionHandler completionHandler) {
+ try (ResourceReference ref = requestReference) {
+ byte[] requestContentBytes = accumulatedRequestContent.toByteArray();
+ String content = new String(requestContentBytes, contentCharset);
completionHandler.completed();
- final Map<String, List<String>> parameterMap = parseFormParameters(content);
+ Map<String, List<String>> parameterMap = parseFormParameters(content);
mergeParameters(parameterMap, request.parameters());
- final ContentChannel contentChannel = delegateHandler.handleRequest(request, responseHandler);
+ ContentChannel contentChannel = delegateHandler.handleRequest(request, responseHandler);
if (contentChannel != null) {
if (!removeBody) {
- final ByteBuffer byteBuffer = ByteBuffer.wrap(requestContentBytes);
+ ByteBuffer byteBuffer = ByteBuffer.wrap(requestContentBytes);
contentChannel.write(byteBuffer, NOOP_COMPLETION_HANDLER);
}
contentChannel.close(NOOP_COMPLETION_HANDLER);
@@ -109,14 +106,10 @@ class FormPostRequestHandler extends AbstractRequestHandler implements ContentCh
* @return a valid Charset for the charset name (never returns null)
* @throws RequestException if the charset name is invalid or unsupported
*/
- private static Charset getCharsetByName(final String charsetName) throws RequestException {
+ private static Charset getCharsetByName(String charsetName) throws RequestException {
try {
- final Charset charset = Charset.forName(charsetName);
- if (charset == null) {
- throw new RequestException(UNSUPPORTED_MEDIA_TYPE, "Unsupported charset " + charsetName);
- }
- return charset;
- } catch (final IllegalCharsetNameException |UnsupportedCharsetException e) {
+ return Charset.forName(charsetName);
+ } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
throw new RequestException(UNSUPPORTED_MEDIA_TYPE, "Unsupported charset " + charsetName, e);
}
}
@@ -127,17 +120,17 @@ class FormPostRequestHandler extends AbstractRequestHandler implements ContentCh
* @param formContent raw form content data (body)
* @return map of decoded parameters
*/
- private static Map<String, List<String>> parseFormParameters(final String formContent) {
+ private static Map<String, List<String>> parseFormParameters(String formContent) {
if (formContent.isEmpty()) {
return Collections.emptyMap();
}
- final Map<String, List<String>> parameterMap = new HashMap<>();
- final String[] params = formContent.split("&");
- for (final String param : params) {
- final String[] parts = param.split("=");
- final String paramName = urlDecode(parts[0]);
- final String paramValue = parts.length > 1 ? urlDecode(parts[1]) : "";
+ Map<String, List<String>> parameterMap = new HashMap<>();
+ String[] params = formContent.split("&");
+ for (String param : params) {
+ String[] parts = param.split("=");
+ String paramName = urlDecode(parts[0]);
+ String paramValue = parts.length > 1 ? urlDecode(parts[1]) : "";
List<String> currentValues = parameterMap.get(paramName);
if (currentValues == null) {
currentValues = new LinkedList<>();
@@ -159,7 +152,7 @@ class FormPostRequestHandler extends AbstractRequestHandler implements ContentCh
// Regardless of the charset used to transfer the request body,
// all percent-escaping of non-ascii characters should use UTF-8 code points.
return URLDecoder.decode(encoded, StandardCharsets.UTF_8.name());
- } catch (final UnsupportedEncodingException e) {
+ } catch (UnsupportedEncodingException e) {
// Unfortunately, there is no URLDecoder.decode() method that takes a Charset, so we have to deal
// with this exception.
throw new IllegalStateException("Whoa, JVM doesn't support UTF-8 today.", e);
@@ -172,11 +165,9 @@ class FormPostRequestHandler extends AbstractRequestHandler implements ContentCh
* @param source containing the parameters to copy into the destination
* @param destination receiver of parameters, possibly already containing data
*/
- private static void mergeParameters(
- final Map<String,List<String>> source,
- final Map<String,List<String>> destination) {
+ private static void mergeParameters(Map<String,List<String>> source, Map<String,List<String>> destination) {
for (Map.Entry<String, List<String>> entry : source.entrySet()) {
- final List<String> destinationValues = destination.get(entry.getKey());
+ List<String> destinationValues = destination.get(entry.getKey());
if (destinationValues != null) {
destinationValues.addAll(entry.getValue());
} else {
@@ -189,4 +180,5 @@ class FormPostRequestHandler extends AbstractRequestHandler implements ContentCh
public RequestHandler getDelegate() {
return delegateHandler;
}
+
}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java
index 22c5b2ebfdb..3fb81cb5352 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollector.java
@@ -3,6 +3,7 @@ package com.yahoo.jdisc.http.server.jetty;
import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.jdisc.http.ServerConfig;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.AsyncContextEvent;
@@ -22,7 +23,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -52,13 +52,20 @@ class HttpResponseStatisticsCollector extends HandlerWrapper implements Graceful
private final AtomicReference<FutureCallback> shutdown = new AtomicReference<>();
private final List<String> monitoringHandlerPaths;
private final List<String> searchHandlerPaths;
+ private final Set<String> ignoredUserAgents;
private final AtomicLong inFlight = new AtomicLong();
private final ConcurrentMap<StatusCodeMetric, LongAdder> statistics = new ConcurrentHashMap<>();
- HttpResponseStatisticsCollector(List<String> monitoringHandlerPaths, List<String> searchHandlerPaths) {
+ HttpResponseStatisticsCollector(ServerConfig.Metric cfg) {
+ this(cfg.monitoringHandlerPaths(), cfg.searchHandlerPaths(), cfg.ignoredUserAgents());
+ }
+
+ HttpResponseStatisticsCollector(List<String> monitoringHandlerPaths, List<String> searchHandlerPaths,
+ Collection<String> ignoredUserAgents) {
this.monitoringHandlerPaths = monitoringHandlerPaths;
this.searchHandlerPaths = searchHandlerPaths;
+ this.ignoredUserAgents = Set.copyOf(ignoredUserAgents);
}
private final AsyncListener completionWatcher = new AsyncListener() {
@@ -108,12 +115,6 @@ class HttpResponseStatisticsCollector extends HandlerWrapper implements Graceful
}
}
- void ignoreUserAgent(String agentName) {
- ignoredUserAgents.add(agentName);
- }
-
- private Set<String> ignoredUserAgents = new HashSet<>();
-
private boolean shouldLogMetricsFor(Request request) {
String agent = request.getHeader(HttpHeader.USER_AGENT.toString());
if (agent == null) return true;
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
index 79cdb8f67cf..49db22c3e38 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java
@@ -3,6 +3,7 @@ package com.yahoo.jdisc.http.server.jetty;
import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.http.ConnectorConfig;
+import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Server;
@@ -50,6 +51,12 @@ class JDiscServerConnector extends ServerConnector {
}
addBean(connectionLogger);
addBean(connectionMetricAggregator);
+ setPort(config.listenPort());
+ setName(config.name());
+ setAcceptQueueSize(config.acceptQueueSize());
+ setReuseAddress(config.reuseAddress());
+ setIdleTimeout((long) (config.idleTimeout() * 1000));
+ addBean(HttpCompliance.RFC7230);
}
@Override
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java
index 96c5bac335b..775c903f5f8 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java
@@ -18,7 +18,10 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHttpOutputInterceptor;
@@ -83,20 +86,13 @@ public class JettyHttpServer extends AbstractServerProvider {
listenedPorts.add(connectorConfig.listenPort());
}
- JDiscContext jDiscContext = new JDiscContext(filterBindings,
- container,
- janitor,
- metric,
- serverConfig);
+ JDiscContext jDiscContext = new JDiscContext(filterBindings, container, janitor, metric, serverConfig);
ServletHolder jdiscServlet = new ServletHolder(new JDiscHttpServlet(jDiscContext));
List<JDiscServerConnector> connectors = Arrays.stream(server.getConnectors())
.map(JDiscServerConnector.class::cast)
.collect(toList());
-
- server.setHandler(getHandlerCollection(serverConfig,
- connectors,
- jdiscServlet));
+ server.setHandler(createRootHandler(serverConfig, connectors, jdiscServlet));
this.metricsReporter = new ServerMetricReporter(metric, server);
}
@@ -136,46 +132,36 @@ public class JettyHttpServer extends AbstractServerProvider {
}
}
- private HandlerCollection getHandlerCollection(ServerConfig serverConfig,
- List<JDiscServerConnector> connectors,
- ServletHolder jdiscServlet) {
- ServletContextHandler servletContextHandler = createServletContextHandler();
- servletContextHandler.addServlet(jdiscServlet, "/*");
-
- List<ConnectorConfig> connectorConfigs = connectors.stream().map(JDiscServerConnector::connectorConfig).collect(toList());
- var secureRedirectHandler = new SecuredRedirectHandler(connectorConfigs);
- secureRedirectHandler.setHandler(servletContextHandler);
-
- var proxyHandler = new HealthCheckProxyHandler(connectors);
- proxyHandler.setHandler(secureRedirectHandler);
-
- var authEnforcer = new TlsClientAuthenticationEnforcer(connectorConfigs);
- authEnforcer.setHandler(proxyHandler);
-
- GzipHandler gzipHandler = newGzipHandler(serverConfig);
- gzipHandler.setHandler(authEnforcer);
-
- HttpResponseStatisticsCollector statisticsCollector =
- new HttpResponseStatisticsCollector(serverConfig.metric().monitoringHandlerPaths(),
- serverConfig.metric().searchHandlerPaths());
- statisticsCollector.setHandler(gzipHandler);
- for (String agent : serverConfig.metric().ignoredUserAgents()) {
- statisticsCollector.ignoreUserAgent(agent);
+ private Handler createRootHandler(
+ ServerConfig serverCfg, List<JDiscServerConnector> connectors, ServletHolder jdiscServlet) {
+ HandlerCollection perConnectorHandlers = new ContextHandlerCollection();
+ for (JDiscServerConnector connector : connectors) {
+ ConnectorConfig connectorCfg = connector.connectorConfig();
+ List<Handler> connectorChain = new ArrayList<>();
+ if (connectorCfg.tlsClientAuthEnforcer().enable()) {
+ connectorChain.add(newTlsClientAuthEnforcerHandler(connectorCfg));
+ }
+ if (connectorCfg.healthCheckProxy().enable()) {
+ connectorChain.add(newHealthCheckProxyHandler(connectors));
+ } else {
+ connectorChain.add(newServletHandler(jdiscServlet));
+ }
+ ContextHandler connectorRoot = newConnectorContextHandler(connector);
+ addChainToRoot(connectorRoot, connectorChain);
+ perConnectorHandlers.addHandler(connectorRoot);
}
-
- StatisticsHandler statisticsHandler = newStatisticsHandler();
- statisticsHandler.setHandler(statisticsCollector);
-
- HandlerCollection handlerCollection = new HandlerCollection();
- handlerCollection.setHandlers(new Handler[] { statisticsHandler });
- return handlerCollection;
+ StatisticsHandler root = newGenericStatisticsHandler();
+ addChainToRoot(root, List.of(
+ newResponseStatisticsHandler(serverCfg), newGzipHandler(serverCfg), perConnectorHandlers));
+ return root;
}
- private ServletContextHandler createServletContextHandler() {
- ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
- servletContextHandler.setContextPath("/");
- servletContextHandler.setDisplayName(getDisplayName(listenedPorts));
- return servletContextHandler;
+ private static void addChainToRoot(Handler root, List<Handler> chain) {
+ Handler parent = root;
+ for (Handler h : chain) {
+ ((HandlerWrapper)parent).setHandler(h);
+ parent = h;
+ }
}
private static String getDisplayName(List<Integer> ports) {
@@ -237,13 +223,37 @@ public class JettyHttpServer extends AbstractServerProvider {
Server server() { return server; }
- private StatisticsHandler newStatisticsHandler() {
+ private ServletContextHandler newServletHandler(ServletHolder servlet) {
+ var h = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
+ h.setContextPath("/");
+ h.setDisplayName(getDisplayName(listenedPorts));
+ h.addServlet(servlet, "/*");
+ return h;
+ }
+
+ private static ContextHandler newConnectorContextHandler(JDiscServerConnector c) {
+ return new ConnectorSpecificContextHandler(c);
+ }
+
+ private static HealthCheckProxyHandler newHealthCheckProxyHandler(List<JDiscServerConnector> connectors) {
+ return new HealthCheckProxyHandler(connectors);
+ }
+
+ private static TlsClientAuthenticationEnforcer newTlsClientAuthEnforcerHandler(ConnectorConfig cfg) {
+ return new TlsClientAuthenticationEnforcer(cfg.tlsClientAuthEnforcer());
+ }
+
+ private static HttpResponseStatisticsCollector newResponseStatisticsHandler(ServerConfig cfg) {
+ return new HttpResponseStatisticsCollector(cfg.metric());
+ }
+
+ private static StatisticsHandler newGenericStatisticsHandler() {
StatisticsHandler statisticsHandler = new StatisticsHandler();
statisticsHandler.statsReset();
return statisticsHandler;
}
- private GzipHandler newGzipHandler(ServerConfig serverConfig) {
+ private static GzipHandler newGzipHandler(ServerConfig serverConfig) {
GzipHandler gzipHandler = new GzipHandlerWithVaryHeaderFixed();
gzipHandler.setCompressionLevel(serverConfig.responseCompressionLevel());
gzipHandler.setInflateBufferSize(8 * 1024);
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java
deleted file mode 100644
index e5dddf285ef..00000000000
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.jdisc.http.server.jetty;
-
-import com.yahoo.jdisc.http.ConnectorConfig;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.util.URIUtil;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static com.yahoo.jdisc.http.server.jetty.RequestUtils.getConnectorLocalPort;
-
-/**
- * A secure redirect handler inspired by {@link org.eclipse.jetty.server.handler.SecuredRedirectHandler}.
- *
- * @author bjorncs
- */
-class SecuredRedirectHandler extends HandlerWrapper {
-
- private static final String HEALTH_CHECK_PATH = "/status.html";
-
- private final Map<Integer, Integer> redirectMap;
-
- SecuredRedirectHandler(List<ConnectorConfig> connectorConfigs) {
- this.redirectMap = createRedirectMap(connectorConfigs);
- }
-
- @Override
- public void handle(String target, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
- int localPort = getConnectorLocalPort(request);
- if (!redirectMap.containsKey(localPort)) {
- _handler.handle(target, request, servletRequest, servletResponse);
- return;
- }
- servletResponse.setContentLength(0);
- if (!servletRequest.getRequestURI().equals(HEALTH_CHECK_PATH)) {
- servletResponse.sendRedirect(
- URIUtil.newURI("https", request.getServerName(), redirectMap.get(localPort), request.getRequestURI(), request.getQueryString()));
- }
- request.setHandled(true);
- }
-
- private static Map<Integer, Integer> createRedirectMap(List<ConnectorConfig> connectorConfigs) {
- var redirectMap = new HashMap<Integer, Integer>();
- for (ConnectorConfig connectorConfig : connectorConfigs) {
- if (connectorConfig.secureRedirect().enabled()) {
- redirectMap.put(connectorConfig.listenPort(), connectorConfig.secureRedirect().port());
- }
- }
- return redirectMap;
- }
-}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java
index ce949074bfa..b420aabc598 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java
@@ -11,11 +11,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static com.yahoo.jdisc.http.server.jetty.RequestUtils.getConnectorLocalPort;
/**
* A Jetty handler that enforces TLS client authentication with configurable white list.
@@ -24,10 +19,11 @@ import static com.yahoo.jdisc.http.server.jetty.RequestUtils.getConnectorLocalPo
*/
class TlsClientAuthenticationEnforcer extends HandlerWrapper {
- private final Map<Integer, List<String>> portToWhitelistedPathsMapping;
+ private final ConnectorConfig.TlsClientAuthEnforcer cfg;
- TlsClientAuthenticationEnforcer(List<ConnectorConfig> connectorConfigs) {
- portToWhitelistedPathsMapping = createWhitelistMapping(connectorConfigs);
+ TlsClientAuthenticationEnforcer(ConnectorConfig.TlsClientAuthEnforcer cfg) {
+ if (!cfg.enable()) throw new IllegalArgumentException();
+ this.cfg = cfg;
}
@Override
@@ -44,36 +40,11 @@ class TlsClientAuthenticationEnforcer extends HandlerWrapper {
}
}
- private static Map<Integer, List<String>> createWhitelistMapping(List<ConnectorConfig> connectorConfigs) {
- var mapping = new HashMap<Integer, List<String>>();
- for (ConnectorConfig connectorConfig : connectorConfigs) {
- var enforcerConfig = connectorConfig.tlsClientAuthEnforcer();
- if (enforcerConfig.enable()) {
- mapping.put(connectorConfig.listenPort(), enforcerConfig.pathWhitelist());
- }
- }
- return mapping;
- }
-
- private boolean isRequest(Request request) {
- return request.getDispatcherType() == DispatcherType.REQUEST;
- }
+ private boolean isRequest(Request request) { return request.getDispatcherType() == DispatcherType.REQUEST; }
private boolean isRequestToWhitelistedBinding(Request jettyRequest) {
- int localPort = getConnectorLocalPort(jettyRequest);
- List<String> whiteListedPaths = getWhitelistedPathsForPort(localPort);
- if (whiteListedPaths == null) {
- return true; // enforcer not enabled
- }
// Note: Same path definition as HttpRequestFactory.getUri()
- return whiteListedPaths.contains(jettyRequest.getRequestURI());
- }
-
- private List<String> getWhitelistedPathsForPort(int localPort) {
- if (portToWhitelistedPathsMapping.containsKey(0) && portToWhitelistedPathsMapping.size() == 1) {
- return portToWhitelistedPathsMapping.get(0); // for unit tests which uses 0 for listen port
- }
- return portToWhitelistedPathsMapping.get(localPort);
+ return cfg.pathWhitelist().contains(jettyRequest.getRequestURI());
}
private boolean isClientAuthenticated(HttpServletRequest servletRequest) {
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 2c15c994bb7..f4db1c5085a 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
@@ -187,7 +187,7 @@ public abstract class AbstractProcessingHandler<COMPONENT extends Processor> ext
private void populate(String prefixName,Map<String,?> parameters,Properties properties) {
CompoundName prefix = new CompoundName(prefixName);
for (Map.Entry<String,?> entry : parameters.entrySet())
- properties.set(prefix.append(entry.getKey()),entry.getValue());
+ properties.set(prefix.append(entry.getKey()), entry.getValue());
}
private static class FreezeListener implements Runnable, ResponseReceiver {
diff --git a/container-core/src/main/java/com/yahoo/processing/request/CompoundName.java b/container-core/src/main/java/com/yahoo/processing/request/CompoundName.java
index 5e52f8d8b37..6af4811fa1b 100644
--- a/container-core/src/main/java/com/yahoo/processing/request/CompoundName.java
+++ b/container-core/src/main/java/com/yahoo/processing/request/CompoundName.java
@@ -175,6 +175,8 @@ public final class CompoundName {
if (compounds.size() < n)
throw new IllegalArgumentException("Asked for the first " + n + " components but '" +
this + "' only have " + compounds.size() + " components.");
+ if (compounds.size() == n) return this;
+ if (compounds.size() == 0) return empty;
return new CompoundName(compounds.subList(0, n));
}
diff --git a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
index 1f4763d32a7..ecbc451ead1 100644
--- a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
+++ b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
@@ -116,12 +116,6 @@ proxyProtocol.enabled bool default=false
# Allow https in parallel with proxy protocol
proxyProtocol.mixedMode bool default=false
-# Redirect all requests to https port
-secureRedirect.enabled bool default=false
-
-# Target port for redirect
-secureRedirect.port int default=443
-
# Maximum number of request per connection before server marks connections as non-persistent. Set to '0' to disable.
maxRequestsPerConnection int default=0
@@ -138,3 +132,5 @@ http2.maxConcurrentStreams int default=4096
# Override the default server name when authority is missing from request.
serverName.fallback string default=""
+# The list of accepted server names. Empty list to accept any. Elements follows format of 'serverName.default'.
+serverName.allowed[] string
diff --git a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java
index e98f8cac276..749033cd1bf 100644
--- a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java
+++ b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java
@@ -3,8 +3,8 @@ package com.yahoo.container.handler;
import com.yahoo.compress.ZstdCompressor;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.io.TempDir;
import java.io.ByteArrayOutputStream;
@@ -30,7 +30,8 @@ public class LogReaderTest {
private static final String logv11 = "3600.2\tnode1.com\t5480\tcontainer\tstdout\tinfo\tfourth\n";
private static final String logv = "90000.1\tnode1.com\t5480\tcontainer\tstdout\tinfo\tlast\n";
- private static final String log100 = "0.2\tnode2.com\t5480\tcontainer\tstdout\tinfo\tsecond\n";
+ private static final String log100a = "0.2\tnode2.com\t5480\tcontainer\tstdout\tinfo\tsecond\n";
+ private static final String log100b = "0.15\tnode2.com\t5480\tcontainer\tstdout\tinfo\tfirst\n";
private static final String log101 = "0.1\tnode2.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n";
private static final String log110 = "3600.1\tnode1.com\t5480\tcontainer\tstderr\twarning\tthird\n";
private static final String log200 = "86400.1\tnode2.com\t5480\tcontainer\tstderr\twarning\tjava.lang.NullPointerException\\n\\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\\n\\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)\n";
@@ -41,27 +42,37 @@ public class LogReaderTest {
// Log archive paths and file names indicate what hour they contain logs for, with the start of that hour.
// Multiple entries may exist for each hour.
Files.createDirectories(logDirectory.resolve("1970/01/01"));
- Files.write(logDirectory.resolve("1970/01/01/00-0.gz"), compress1(log100));
- Files.write(logDirectory.resolve("1970/01/01/00-1"), log101.getBytes(UTF_8));
+ // Files may contain out-of-order entries.
+ Files.write(logDirectory.resolve("1970/01/01/00-0.gz"), compress1(log100a + log100b));
+ Files.writeString(logDirectory.resolve("1970/01/01/00-1"), log101);
Files.write(logDirectory.resolve("1970/01/01/01-0.zst"), compress2(log110));
Files.createDirectories(logDirectory.resolve("1970/01/02"));
- Files.write(logDirectory.resolve("1970/01/02/00-0"), log200.getBytes(UTF_8));
+ Files.writeString(logDirectory.resolve("1970/01/02/00-0"), log200);
// Vespa log file names are the second-truncated timestamp of the last entry.
// The current log file has no timestamp suffix.
- Files.write(logDirectory.resolve("vespa.log-1970-01-01.01-00-00"), logv11.getBytes(UTF_8));
- Files.write(logDirectory.resolve("vespa.log"), logv.getBytes(UTF_8));
+ Files.writeString(logDirectory.resolve("vespa.log-1970-01-01.01-00-00"), logv11);
+ Files.writeString(logDirectory.resolve("vespa.log"), logv);
}
- @Disabled
+ private static boolean hasZstdcat() {
+ try {
+ return new ProcessBuilder("zstdcat", "--version").start().waitFor() == 0;
+ }
+ catch (Exception e) {
+ return false;
+ }
+ }
+
+ @EnabledIf("hasZstdcat")
@Test
void testThatLogsOutsideRangeAreExcluded() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*"));
logReader.writeLogs(baos, Instant.ofEpochMilli(150), Instant.ofEpochMilli(3601050), 100, Optional.empty());
- assertEquals(log100 + logv11 + log110, baos.toString(UTF_8));
+ assertEquals(log100b + log100a + logv11 + log110, baos.toString(UTF_8));
}
@Test
@@ -73,14 +84,14 @@ public class LogReaderTest {
assertEquals(log101 + logv11, baos.toString(UTF_8));
}
- @Disabled // TODO: zts log line missing on Mac
+ @EnabledIf("hasZstdcat")
@Test
void testZippedStreaming() {
ByteArrayOutputStream zippedBaos = new ByteArrayOutputStream();
LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*"));
logReader.writeLogs(zippedBaos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), 100, Optional.empty());
- assertEquals(log101 + log100 + logv11 + log110 + log200 + logv, zippedBaos.toString(UTF_8));
+ assertEquals(log101 + log100b + log100a + logv11 + log110 + log200 + logv, zippedBaos.toString(UTF_8));
}
@Test
@@ -89,7 +100,7 @@ public class LogReaderTest {
LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*"));
logReader.writeLogs(baos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), 100, Optional.of("node2.com"));
- assertEquals(log101 + log100 + log200, baos.toString(UTF_8));
+ assertEquals(log101 + log100b + log100a + log200, baos.toString(UTF_8));
}
@Test
@@ -98,7 +109,7 @@ public class LogReaderTest {
LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*"));
logReader.writeLogs(baos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), 2, Optional.of("node2.com"));
- assertEquals(log101 + log100, baos.toString(UTF_8));
+ assertEquals(log101 + log100b, baos.toString(UTF_8));
}
private byte[] compress1(String input) throws IOException {
diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java
index 1f65bc4f582..165659389ec 100644
--- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java
+++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpResponseStatisticsCollectorTest.java
@@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
+import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@@ -38,7 +39,7 @@ public class HttpResponseStatisticsCollectorTest {
private Connector connector;
private List<String> monitoringPaths = List.of("/status.html");
private List<String> searchPaths = List.of("/search");
- private HttpResponseStatisticsCollector collector = new HttpResponseStatisticsCollector(monitoringPaths, searchPaths);
+ private HttpResponseStatisticsCollector collector = new HttpResponseStatisticsCollector(monitoringPaths, searchPaths, Set.of());
private int httpResponseCode = 500;
@Test
diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
index 2c5d36bd776..318067ac634 100644
--- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
+++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
@@ -743,7 +743,7 @@ public class HttpServerTest {
}
@Test
- void requestThatFallbackServerNameCanBeOverridden() throws Exception {
+ void fallbackServerNameCanBeOverridden() throws Exception {
String fallbackHostname = "myhostname";
JettyTestDriver driver = JettyTestDriver.newConfiguredInstance(
new UriRequestHandler(),
@@ -752,13 +752,29 @@ public class HttpServerTest {
.serverName(new ConnectorConfig.ServerName.Builder().fallback(fallbackHostname)));
int listenPort = driver.server().getListenPort();
HttpGet req = new HttpGet("http://localhost:" + listenPort + "/");
- req.addHeader("Host", null);
+ req.setHeader("Host", null);
driver.client().execute(req)
.expectStatusCode(is(OK))
.expectContent(containsString("http://" + fallbackHostname + ":" + listenPort + "/"));
assertTrue(driver.close());
}
+ @Test
+ void acceptedServerNamesCanBeRestricted() throws Exception {
+ String requiredServerName = "myhostname";
+ JettyTestDriver driver = JettyTestDriver.newConfiguredInstance(
+ new EchoRequestHandler(),
+ new ServerConfig.Builder(),
+ new ConnectorConfig.Builder()
+ .serverName(new ConnectorConfig.ServerName.Builder().allowed(requiredServerName)));
+ int listenPort = driver.server().getListenPort();
+ HttpGet req = new HttpGet("http://localhost:" + listenPort + "/");
+ req.setHeader("Host", requiredServerName);
+ driver.client().execute(req).expectStatusCode(is(OK));
+ driver.client().get("/").expectStatusCode(is(NOT_FOUND));
+ assertTrue(driver.close());
+ }
+
private static JettyTestDriver createSslWithTlsClientAuthenticationEnforcer(Path certificateFile, Path privateKeyFile) {
ConnectorConfig.Builder connectorConfig = new ConnectorConfig.Builder()
.tlsClientAuthEnforcer(
diff --git a/container-core/src/test/java/com/yahoo/metrics/simple/BucketTest.java b/container-core/src/test/java/com/yahoo/metrics/simple/BucketTest.java
index 11cc8b86a09..75d1c37c5c1 100644
--- a/container-core/src/test/java/com/yahoo/metrics/simple/BucketTest.java
+++ b/container-core/src/test/java/com/yahoo/metrics/simple/BucketTest.java
@@ -23,9 +23,10 @@ import com.yahoo.metrics.simple.UntypedMetric.AssumedType;
* Functional tests for the value buckets, as implemented in the class Bucket,
* and by extension the value store itself, UntypedValue.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
*/
public class BucketTest {
+
private Bucket bucket;
@BeforeEach
@@ -55,23 +56,12 @@ public class BucketTest {
for (Entry<Identifier, UntypedMetric> x : bucket.entrySet()) {
String metricName = x.getKey().getName();
switch (metricName) {
- case "nalle":
- ++nalle;
- break;
- case "nalle_0":
- ++nalle0;
- break;
- case "nalle_1":
- ++nalle1;
- break;
- case "nalle_2":
- ++nalle2;
- break;
- case "nalle_3":
- ++nalle3;
- break;
- default:
- throw new IllegalStateException();
+ case "nalle" -> ++nalle;
+ case "nalle_0" -> ++nalle0;
+ case "nalle_1" -> ++nalle1;
+ case "nalle_2" -> ++nalle2;
+ case "nalle_3" -> ++nalle3;
+ default -> throw new IllegalStateException();
}
}
assertEquals(4, nalle);
diff --git a/container-core/src/test/java/com/yahoo/metrics/simple/CounterTest.java b/container-core/src/test/java/com/yahoo/metrics/simple/CounterTest.java
index 45a76078619..074c0c7b2e5 100644
--- a/container-core/src/test/java/com/yahoo/metrics/simple/CounterTest.java
+++ b/container-core/src/test/java/com/yahoo/metrics/simple/CounterTest.java
@@ -33,7 +33,7 @@ public class CounterTest {
}
@Test
- final void testAdd() throws InterruptedException {
+ final void testAdd() {
final String metricName = "unitTestCounter";
Counter c = receiver.declareCounter(metricName);
c.add();
@@ -47,7 +47,7 @@ public class CounterTest {
}
@Test
- final void testAddLong() throws InterruptedException {
+ final void testAddLong() {
final String metricName = "unitTestCounter";
Counter c = receiver.declareCounter(metricName);
final long twoToThePowerOfFourtyeight = 65536L * 65536L * 65536L;
@@ -62,7 +62,7 @@ public class CounterTest {
}
@Test
- final void testAddPoint() throws InterruptedException {
+ final void testAddPoint() {
final String metricName = "unitTestCounter";
Point p = receiver.pointBuilder().set("x", 2L).set("y", 3.0d).set("z", "5").build();
Counter c = receiver.declareCounter(metricName, p);
@@ -77,7 +77,7 @@ public class CounterTest {
}
@Test
- final void testAddLongPoint() throws InterruptedException {
+ final void testAddLongPoint() {
final String metricName = "unitTestCounter";
Point p = receiver.pointBuilder().set("x", 2L).set("y", 3.0d).set("z", "5").build();
Counter c = receiver.declareCounter(metricName, p);
diff --git a/container-core/src/test/java/com/yahoo/metrics/simple/MetricsTest.java b/container-core/src/test/java/com/yahoo/metrics/simple/MetricsTest.java
index f64998f0be4..dd949627f30 100644
--- a/container-core/src/test/java/com/yahoo/metrics/simple/MetricsTest.java
+++ b/container-core/src/test/java/com/yahoo/metrics/simple/MetricsTest.java
@@ -17,9 +17,10 @@ import com.yahoo.metrics.simple.jdisc.SimpleMetricConsumer;
/**
* Functional test for simple metric implementation.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
*/
public class MetricsTest extends UnitTestSetup {
+
SimpleMetricConsumer metricApi;
@BeforeEach
@@ -36,7 +37,7 @@ public class MetricsTest extends UnitTestSetup {
@Test
final void smokeTest() throws InterruptedException {
final String metricName = "testMetric";
- metricApi.set(metricName, Double.valueOf(1.0d), null);
+ metricApi.set(metricName, 1.0d, null);
updater.gotData.await(10, TimeUnit.SECONDS);
Bucket s = getUpdatedSnapshot();
Collection<Entry<Point, UntypedMetric>> values = s.getValuesForMetric(metricName);
diff --git a/container-core/src/test/java/com/yahoo/metrics/simple/jdisc/SnapshotConverterTest.java b/container-core/src/test/java/com/yahoo/metrics/simple/jdisc/SnapshotConverterTest.java
index 7981e5904f3..1d5cf264964 100644
--- a/container-core/src/test/java/com/yahoo/metrics/simple/jdisc/SnapshotConverterTest.java
+++ b/container-core/src/test/java/com/yahoo/metrics/simple/jdisc/SnapshotConverterTest.java
@@ -20,6 +20,7 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* @author bratseth
@@ -50,7 +51,7 @@ public class SnapshotConverterTest {
for (Map.Entry<MetricDimensions, MetricSet> entry : snapshot) {
for (Map.Entry<String, String> dv : entry.getKey()) {
- assertTrue(false);
+ fail();
}
int cnt = 0;
@@ -67,7 +68,7 @@ public class SnapshotConverterTest {
assertEquals(42.25, ((GaugeMetric) mv.getValue()).getLast(), 0.001);
assertEquals(1, ((GaugeMetric) mv.getValue()).getCount());
} else {
- assertTrue(false);
+ fail();
}
}
assertEquals(3, cnt);
diff --git a/container-disc/pom.xml b/container-disc/pom.xml
index 273270b208b..173979fbe81 100644
--- a/container-disc/pom.xml
+++ b/container-disc/pom.xml
@@ -195,11 +195,11 @@
hosted-zone-api-jar-with-dependencies.jar,
container-apache-http-client-bundle-jar-with-dependencies.jar,
security-utils.jar,
- bcprov-jdk15on-${bouncycastle.version}.jar, <!-- Used by security-utils -->
+ bcprov-jdk18on-${bouncycastle.version}.jar, <!-- Used by security-utils -->
+ bcpkix-jdk18on-${bouncycastle.version}.jar, <!-- Used by security-utils -->
+ bcutil-jdk18on-${bouncycastle.version}.jar, <!-- Used by security-utils -->
<!-- END Bundles needed to retrieve config, or used by container-disc -->
- bcpkix-jdk15on-${bouncycastle.version}.jar, <!-- Used by security-utils -->
-
jackson-annotations-${jackson2.version}.jar,
jackson-core-${jackson2.version}.jar,
jackson-databind-${jackson-databind.version}.jar,
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
index 7ea91726673..625bd87c2db 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java
@@ -366,6 +366,12 @@ public final class ConfiguredApplication implements Application {
synchronized (monitor) {
Set<ServerProvider> serversToClose = createIdentityHashSet(startedServers);
serversToClose.removeAll(currentServers);
+ for (ServerProvider server : currentServers) {
+ if ( ! startedServers.contains(server) && server.isMultiplexed()) {
+ server.start();
+ startedServers.add(server);
+ }
+ }
if (serversToClose.size() > 0) {
log.info(String.format("Closing %d server instances", serversToClose.size()));
for (ServerProvider server : serversToClose) {
@@ -374,7 +380,7 @@ public final class ConfiguredApplication implements Application {
}
}
for (ServerProvider server : currentServers) {
- if (!startedServers.contains(server)) {
+ if ( ! startedServers.contains(server)) {
server.start();
startedServers.add(server);
}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/SystemInfoProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/SystemInfoProvider.java
index d98ea0ffc25..d8298491944 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/SystemInfoProvider.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/SystemInfoProvider.java
@@ -36,7 +36,7 @@ public class SystemInfoProvider extends AbstractComponent implements Provider<Sy
applicationIdConfig.instance()),
new Zone(Environment.valueOf(csConfig.environment()), csConfig.region()),
new Cloud(csConfig.cloud()),
- new Cluster(ciConfig.nodeCount(), ciConfig.nodeIndices()),
+ new Cluster(ciConfig.clusterId(), ciConfig.nodeCount(), ciConfig.nodeIndices()),
new Node(qrConfig.nodeIndex()));
}
diff --git a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java
index 8f21cb227d8..3cf3b4bf8b0 100644
--- a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java
+++ b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java
@@ -60,6 +60,11 @@ public final class MbusServer extends AbstractResource implements ServerProvider
}
@Override
+ public boolean isMultiplexed() {
+ return true;
+ }
+
+ @Override
protected void destroy() {
log.log(Level.FINE, "Destroying message bus server.");
runState.set(State.STOPPED);
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java
index a094be943a2..23cdff15ad9 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java
@@ -4,6 +4,7 @@ package com.yahoo.prelude.fastsearch;
import com.yahoo.data.access.ObjectTraverser;
import com.yahoo.document.GlobalId;
import com.yahoo.net.URI;
+import com.yahoo.search.dispatch.LeanHit;
import com.yahoo.search.query.Sorting;
import com.yahoo.search.result.FeatureData;
import com.yahoo.search.result.Hit;
@@ -11,7 +12,6 @@ import com.yahoo.search.result.Relevance;
import com.yahoo.data.access.Inspector;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -31,18 +31,18 @@ import java.util.function.BiConsumer;
*/
public class FastHit extends Hit {
- private static final byte [] emptyGID = new byte[GlobalId.LENGTH];
+ private static final byte[] emptyGID = new byte[GlobalId.LENGTH];
/** The index of the content node this hit originated at */
- private int distributionKey = 0;
+ private int distributionKey;
/** The local identifier of the content store for this hit on the node it originated at */
- private int partId;
+ private final int partId;
/** The global id of this document in the backend node which produced it */
- private byte [] globalId;
+ private byte[] globalId;
private transient byte[] sortData = null;
- // TODO I suspect this one can be dropped.
+ // TODO: I suspect this one can be dropped.
private transient Sorting sortDataSorting = null;
/**
@@ -73,10 +73,10 @@ public class FastHit extends Hit {
distributionKey = 0;
}
- public FastHit(byte [] gid, double relevance, int partId, int distributionKey) {
+ public FastHit(byte[] gid, double relevance, int partId, int distributionKey) {
this(gid, new Relevance(relevance), partId, distributionKey);
}
- public FastHit(byte [] gid, Relevance relevance, int partId, int distributionKey) {
+ public FastHit(byte[] gid, Relevance relevance, int partId, int distributionKey) {
super(relevance);
this.globalId = gid;
this.partId = partId;
@@ -109,8 +109,8 @@ public class FastHit extends Hit {
*/
@Override
public URI getId() {
- URI uri = super.getId();
- if (uri != null) return uri;
+ URI id = super.getId();
+ if (id != null) return id;
// Fallback to index:[source]/[partid]/[id]
StringBuilder sb = new StringBuilder(64);
@@ -129,16 +129,6 @@ public class FastHit extends Hit {
public int getPartId() { return partId; }
- /**
- * Sets the part id number, which specifies the node where this hit is
- * found. The row count is used to decode the part id into a column and a
- * row number: the number of n least significant bits required to hold the
- * highest row number are the row bits, the rest are column bits.
- *
- * Note: Remove partId when all dispatching happens from the container dispatcher, not fdispatch
- */
- public void setPartId(int partId) { this.partId = partId; }
-
/** Returns the index of the node this hit originated at */
public int getDistributionKey() { return distributionKey; }
@@ -167,17 +157,7 @@ public class FastHit extends Hit {
if (!left.hasSortData(sorting) || !right.hasSortData(sorting)) {
return 0; // cannot sort
}
- int i = Arrays.mismatch(left.sortData, right.sortData);
- if (i < 0) {
- return 0;
- }
- int max = Integer.min(left.sortData.length, right.sortData.length);
- if (i >= max) {
- return left.sortData.length - right.sortData.length;
- }
- int vl = (int) left.sortData[i] & 0xFF;
- int vr = (int) right.sortData[i] & 0xFF;
- return vl - vr;
+ return LeanHit.compareData(left.sortData, right.sortData);
}
/** For internal use */
@@ -188,11 +168,6 @@ public class FastHit extends Hit {
summaries.add(0, new SummaryData(this, docsumDef, value, 1 + summaries.size()));
}
- /** Returns the raw summary data available in this as an unmodifiable list */
- public List<SummaryData> summaryData() {
- return Collections.unmodifiableList(summaries);
- }
-
/**
* Returns values for the features listed in
* <a href="https://docs.vespa.ai/en/reference/schema-reference.html#summary-features">summary-features</a>
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/SortDataHitSorter.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/SortDataHitSorter.java
index 6067f85df9b..722e7155dc8 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/SortDataHitSorter.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/SortDataHitSorter.java
@@ -5,7 +5,6 @@ import com.yahoo.search.query.Sorting;
import com.yahoo.search.result.Hit;
import com.yahoo.search.result.HitGroup;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -18,15 +17,14 @@ public class SortDataHitSorter {
return;
}
var fallbackComparator = fallbackOrderer.getComparator();
- Collections.sort(hits, getComparator(sorting, fallbackComparator));
+ hits.sort(getComparator(sorting, fallbackComparator));
}
public static boolean isSortable(Hit hit, Sorting sorting) {
if (sorting == null) {
return false;
}
- if (hit instanceof FastHit) {
- var fhit = (FastHit) hit;
+ if (hit instanceof FastHit fhit) {
return fhit.hasSortData(sorting);
} else {
return false;
@@ -42,20 +40,14 @@ public class SortDataHitSorter {
}
private static int compareTwo(Hit left, Hit right, Sorting sorting) {
- if (left == null || right == null || !(left instanceof FastHit) || !(right instanceof FastHit)) {
- return 0;
- }
- FastHit fl = (FastHit) left;
- FastHit fr = (FastHit) right;
+ if (!(left instanceof FastHit fl) || !(right instanceof FastHit fr)) return 0;
return FastHit.compareSortData(fl, fr, sorting);
}
private static int compareWithFallback(Hit left, Hit right, Sorting sorting, Comparator<Hit> fallback) {
- if (left == null || right == null || !(left instanceof FastHit) || !(right instanceof FastHit)) {
+ if (!(left instanceof FastHit fl) || !(right instanceof FastHit fr)) {
return fallback.compare(left, right);
}
- FastHit fl = (FastHit) left;
- FastHit fr = (FastHit) right;
if (fl.hasSortData(sorting) && fr.hasSortData(sorting)) {
return FastHit.compareSortData(fl, fr, sorting);
} else {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/Item.java b/container-search/src/main/java/com/yahoo/prelude/query/Item.java
index 7f421832d5f..8c154072a42 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/Item.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/Item.java
@@ -1,7 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.query;
-
import com.yahoo.collections.CopyOnWriteHashMap;
import com.yahoo.compress.IntegerCompressor;
import com.yahoo.language.Language;
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java b/container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java
index 29cf7803d61..79fbeb99119 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java
@@ -2,6 +2,7 @@
package com.yahoo.prelude.query;
import java.nio.ByteBuffer;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -70,43 +71,26 @@ public class RegExpItem extends TermItem {
putString(getIndexedString(), buffer);
}
+ public Pattern getRegexp() { return regexp; }
+
@Override
public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("RegExpItem [expression=").append(expression).append("]");
- return builder.toString();
+ return "RegExpItem [expression=" + expression + "]";
}
@Override
public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((expression == null) ? 0 : expression.hashCode());
- return result;
+ return Objects.hash(super.hashCode(), expression);
}
@Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- RegExpItem other = (RegExpItem) obj;
- if (expression == null) {
- if (other.expression != null) {
- return false;
- }
- } else if (!expression.equals(other.expression)) {
- return false;
- }
- return true;
- }
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if ( ! super.equals(o)) return false;
+ if (getClass() != o.getClass()) return false;
- public Pattern getRegexp() { return regexp; }
+ RegExpItem other = (RegExpItem)o;
+ return Objects.equals(this.expression, other.expression);
+ }
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/ValidateSortingSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/ValidateSortingSearcher.java
index 6ecf9cd906f..47125d198e1 100644
--- a/container-search/src/main/java/com/yahoo/prelude/searcher/ValidateSortingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/searcher/ValidateSortingSearcher.java
@@ -20,7 +20,6 @@ import java.util.Map;
import static com.yahoo.prelude.querytransform.NormalizingSearcher.ACCENT_REMOVAL;
-
/**
* Check sorting specification makes sense to the search cluster before
* passing it on to the backend.
@@ -35,6 +34,13 @@ public class ValidateSortingSearcher extends Searcher {
private String clusterName = "";
private final QrSearchersConfig.Searchcluster.Indexingmode.Enum indexingMode;
+ public ValidateSortingSearcher(QrSearchersConfig qrsConfig, ClusterConfig clusterConfig,
+ AttributesConfig attributesConfig) {
+ initAttributeNames(attributesConfig);
+ setClusterName(qrsConfig.searchcluster(clusterConfig.clusterId()).name());
+ indexingMode = qrsConfig.searchcluster(clusterConfig.clusterId()).indexingmode();
+ }
+
public String getClusterName() {
return clusterName;
}
@@ -63,14 +69,6 @@ public class ValidateSortingSearcher extends Searcher {
setAttributeNames(attributes);
}
- public ValidateSortingSearcher(QrSearchersConfig qrsConfig, ClusterConfig clusterConfig,
- AttributesConfig attributesConfig)
- {
- initAttributeNames(attributesConfig);
- setClusterName(qrsConfig.searchcluster(clusterConfig.clusterId()).name());
- indexingMode = qrsConfig.searchcluster(clusterConfig.clusterId()).indexingmode();
- }
-
@Override
public Result search(Query query, Execution execution) {
if (indexingMode != QrSearchersConfig.Searchcluster.Indexingmode.STREAMING) {
@@ -157,8 +155,7 @@ public class ValidateSortingSearcher extends Searcher {
}
}
}
- if (f.getSorter() instanceof Sorting.UcaSorter) {
- Sorting.UcaSorter sorter = (Sorting.UcaSorter) f.getSorter();
+ if (f.getSorter() instanceof Sorting.UcaSorter sorter) {
String locale = sorter.getLocale();
if (locale == null || locale.isEmpty()) {
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java
index b4f04da5986..39be91cf3e8 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java
@@ -210,17 +210,17 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
indexPartial++;
}
}
- while ((indexCurrent < current.size()) && (merged.size() < needed)) {
- LeanHit currentHit = current.get(indexCurrent++);
- merged.add(currentHit);
- }
- while ((indexPartial < partial.size()) && (merged.size() < needed)) {
- LeanHit incomingHit = partial.get(indexPartial++);
- merged.add(incomingHit);
- }
+ appendRemainingIfNeeded(merged, needed, current, indexCurrent);
+ appendRemainingIfNeeded(merged, needed, partial, indexPartial);
return merged;
}
+ private void appendRemainingIfNeeded(List<LeanHit> merged, int needed, List<LeanHit> hits, int index) {
+ while ((index < hits.size()) && (merged.size() < needed)) {
+ merged.add(hits.get(index++));
+ }
+ }
+
private void ejectInvoker(SearchInvoker invoker) {
invokers.remove(invoker);
invoker.release();
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java b/container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java
index bd0415fa449..f80cebe90c4 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java
@@ -51,7 +51,7 @@ public class LeanHit implements Comparable<LeanHit> {
return (res != 0) ? res : compareData(gid, o.gid);
}
- private static int compareData(byte[] left, byte[] right) {
+ public static int compareData(byte[] left, byte[] right) {
int i = Arrays.mismatch(left, right);
if (i < 0) {
return 0;
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/MapConverter.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/MapConverter.java
index a71ec5d8345..7e54afcc070 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/MapConverter.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/MapConverter.java
@@ -4,10 +4,11 @@ package com.yahoo.search.dispatch.rpc;
import ai.vespa.searchlib.searchprotocol.protobuf.SearchProtocol.StringProperty;
import ai.vespa.searchlib.searchprotocol.protobuf.SearchProtocol.TensorProperty;
import com.google.protobuf.ByteString;
+import com.yahoo.io.GrowableByteBuffer;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.serialization.TypedBinaryFormat;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -17,12 +18,13 @@ import java.util.function.Consumer;
*/
public class MapConverter {
- public static void convertMapTensors(Map<String, Object> map, Consumer<TensorProperty.Builder> inserter) {
+ public static void convertMapTensors(GrowableByteBuffer buffer, Map<String, Object> map, Consumer<TensorProperty.Builder> inserter) {
for (var entry : map.entrySet()) {
var value = entry.getValue();
- if (value instanceof Tensor) {
- byte[] tensor = TypedBinaryFormat.encode((Tensor) value);
- inserter.accept(TensorProperty.newBuilder().setName(entry.getKey()).setValue(ByteString.copyFrom(tensor)));
+ if (value instanceof Tensor tensor) {
+ buffer.clear();
+ TypedBinaryFormat.encode(tensor, buffer);
+ inserter.accept(TensorProperty.newBuilder().setName(entry.getKey()).setValue(ByteString.copyFrom(buffer.getByteBuffer().flip())));
}
}
}
@@ -45,18 +47,20 @@ public class MapConverter {
}
}
- public static void convertMultiMap(Map<String, List<Object>> map,
+ public static void convertMultiMap(GrowableByteBuffer buffer,
+ Map<String, List<Object>> map,
Consumer<StringProperty.Builder> stringInserter,
Consumer<TensorProperty.Builder> tensorInserter) {
for (var entry : map.entrySet()) {
if (entry.getValue() != null) {
var key = entry.getKey();
- var stringValues = new LinkedList<String>();
+ var stringValues = new ArrayList<String>(entry.getValue().size());
for (var value : entry.getValue()) {
if (value != null) {
- if (value instanceof Tensor) {
- byte[] tensor = TypedBinaryFormat.encode((Tensor) value);
- tensorInserter.accept(TensorProperty.newBuilder().setName(key).setValue(ByteString.copyFrom(tensor)));
+ if (value instanceof Tensor tensor) {
+ buffer.clear();
+ TypedBinaryFormat.encode(tensor, buffer);
+ tensorInserter.accept(TensorProperty.newBuilder().setName(key).setValue(ByteString.copyFrom(buffer.getByteBuffer().flip())));
} else {
stringValues.add(value.toString());
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java
index de4f4f45eed..90e16e9dd44 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java
@@ -38,7 +38,14 @@ import java.util.function.Consumer;
public class ProtobufSerialization {
- private static final int INITIAL_SERIALIZATION_BUFFER_SIZE = 10 * 1024;
+ /*
+ * This is a thread local buffer that is used as scratchpad during serialization.
+ * - avoids the unnecessary cost of allocating and initializing a buffer that is too large.
+ * - avoids resizing for large queries.
+ * - Reduces garbage creation.
+ * There is a limited number of threads that will use this so the upper bound should be fine.
+ */
+ private static final ThreadLocal<GrowableByteBuffer> threadLocalBuffer = ThreadLocal.withInitial(() -> new GrowableByteBuffer(4096));
static byte[] serializeSearchRequest(Query query, int hits, String serverId, double requestTimeout) {
return convertFromQuery(query, hits, serverId, requestTimeout).toByteArray();
@@ -58,7 +65,8 @@ public class ProtobufSerialization {
if (documentDb != null) {
builder.setDocumentType(documentDb);
}
- builder.setQueryTreeBlob(serializeQueryTree(query.getModel().getQueryTree()));
+ GrowableByteBuffer scratchPad = threadLocalBuffer.get();
+ builder.setQueryTreeBlob(serializeQueryTree(query.getModel().getQueryTree(), scratchPad));
if (query.getGroupingSessionCache() || query.getRanking().getQueryCache()) {
// TODO verify that the session key is included whenever rank properties would have been
@@ -69,7 +77,8 @@ public class ProtobufSerialization {
}
if (GroupingExecutor.hasGroupingList(query)) {
List<Grouping> groupingList = GroupingExecutor.getGroupingList(query);
- BufferSerializer gbuf = new BufferSerializer(new GrowableByteBuffer());
+ scratchPad.clear();
+ BufferSerializer gbuf = new BufferSerializer(scratchPad);
gbuf.putInt(null, groupingList.size());
for (Grouping g : groupingList) {
g.serialize(gbuf);
@@ -84,7 +93,7 @@ public class ProtobufSerialization {
builder.setTraceLevel(getTraceLevelForBackend(query));
builder.setProfileDepth(query.getTrace().getProfileDepth());
- mergeToSearchRequestFromRanking(query.getRanking(), builder);
+ mergeToSearchRequestFromRanking(query.getRanking(), scratchPad, builder);
return builder.build();
}
@@ -100,7 +109,7 @@ public class ProtobufSerialization {
return traceLevel;
}
- private static void mergeToSearchRequestFromRanking(Ranking ranking, SearchProtocol.SearchRequest.Builder builder) {
+ private static void mergeToSearchRequestFromRanking(Ranking ranking, GrowableByteBuffer scratchPad, SearchProtocol.SearchRequest.Builder builder) {
builder.setRankProfile(ranking.getProfile());
if (ranking.getQueryCache()) {
@@ -115,8 +124,8 @@ public class ProtobufSerialization {
var featureMap = ranking.getFeatures().asMap();
MapConverter.convertMapPrimitives(featureMap, builder::addFeatureOverrides);
- MapConverter.convertMapTensors(featureMap, builder::addTensorFeatureOverrides);
- mergeRankProperties(ranking, builder::addRankProperties, builder::addTensorRankProperties);
+ MapConverter.convertMapTensors(scratchPad, featureMap, builder::addTensorFeatureOverrides);
+ mergeRankProperties(ranking, scratchPad, builder::addRankProperties, builder::addTensorRankProperties);
}
private static void mergeToSearchRequestFromSorting(Sorting sorting, SearchProtocol.SearchRequest.Builder builder) {
@@ -160,8 +169,9 @@ public class ProtobufSerialization {
if (ranking.getLocation() != null) {
builder.setGeoLocation(ranking.getLocation().backendString());
}
+ GrowableByteBuffer scratchPad = threadLocalBuffer.get();
if (includeQueryData) {
- mergeQueryDataToDocsumRequest(query, builder);
+ mergeQueryDataToDocsumRequest(query, scratchPad, builder);
}
if (query.getTrace().getLevel() >= 3) {
query.trace((includeQueryData ? "ProtoBuf: Resending " : "Not resending ") + "query during document summary fetching", 3);
@@ -178,18 +188,18 @@ public class ProtobufSerialization {
return builder.build().toByteArray();
}
- private static void mergeQueryDataToDocsumRequest(Query query, SearchProtocol.DocsumRequest.Builder builder) {
+ private static void mergeQueryDataToDocsumRequest(Query query, GrowableByteBuffer scratchPad, SearchProtocol.DocsumRequest.Builder builder) {
var ranking = query.getRanking();
var featureMap = ranking.getFeatures().asMap();
- builder.setQueryTreeBlob(serializeQueryTree(query.getModel().getQueryTree()));
+ builder.setQueryTreeBlob(serializeQueryTree(query.getModel().getQueryTree(), scratchPad));
MapConverter.convertMapPrimitives(featureMap, builder::addFeatureOverrides);
- MapConverter.convertMapTensors(featureMap, builder::addTensorFeatureOverrides);
+ MapConverter.convertMapTensors(scratchPad, featureMap, builder::addTensorFeatureOverrides);
if (query.getPresentation().getHighlight() != null) {
MapConverter.convertStringMultiMap(query.getPresentation().getHighlight().getHighlightTerms(), builder::addHighlightTerms);
}
- mergeRankProperties(ranking, builder::addRankProperties, builder::addTensorRankProperties);
+ mergeRankProperties(ranking, scratchPad, builder::addRankProperties, builder::addTensorRankProperties);
}
static byte[] serializeResult(Result searchResult) {
return convertFromResult(searchResult).toByteArray();
@@ -297,24 +307,25 @@ public class ProtobufSerialization {
return builder.build();
}
- private static ByteString serializeQueryTree(QueryTree queryTree) {
- int bufferSize = INITIAL_SERIALIZATION_BUFFER_SIZE;
+ private static ByteString serializeQueryTree(QueryTree queryTree, GrowableByteBuffer scratchPad) {
while (true) {
try {
- ByteBuffer treeBuffer = ByteBuffer.allocate(bufferSize);
+ scratchPad.clear();
+ ByteBuffer treeBuffer = scratchPad.getByteBuffer();
queryTree.encode(treeBuffer);
- treeBuffer.flip();
- return ByteString.copyFrom(treeBuffer);
+ return ByteString.copyFrom(treeBuffer.flip());
} catch (java.nio.BufferOverflowException e) {
- bufferSize *= 2;
+ scratchPad.clear();
+ scratchPad.grow(scratchPad.capacity()*2);
}
}
}
private static void mergeRankProperties(Ranking ranking,
+ GrowableByteBuffer scratchPad,
Consumer<StringProperty.Builder> stringProperties,
Consumer<TensorProperty.Builder> tensorProperties) {
- MapConverter.convertMultiMap(ranking.getProperties().asMap(), propB -> {
+ MapConverter.convertMultiMap(scratchPad, ranking.getProperties().asMap(), propB -> {
if (!GetDocSumsPacket.sessionIdKey.equals(propB.getName())) {
stringProperties.accept(propB);
}
diff --git a/container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java b/container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java
index bf0272f4f66..7dd1772a53e 100644
--- a/container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java
+++ b/container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java
@@ -22,9 +22,11 @@ import java.util.Map;
* @author baldersheim
*/
class Json2SingleLevelMap {
+
private static final ObjectMapper jsonMapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
private final byte [] buf;
private final JsonParser parser;
+
Json2SingleLevelMap(InputStream data) {
try {
buf = data.readAllBytes();
@@ -33,6 +35,7 @@ class Json2SingleLevelMap {
throw new RuntimeException("Problem reading POSTed data", e);
}
}
+
Map<String, String> parse() {
try {
Map<String, String> map = new HashMap<>();
@@ -47,16 +50,17 @@ class Json2SingleLevelMap {
throw new RuntimeException("Problem reading POSTed data", e);
}
}
+
void parse(Map<String, String> map, String parent) throws IOException {
for (parser.nextToken(); parser.getCurrentToken() != JsonToken.END_OBJECT; parser.nextToken()) {
String fieldName = parent + parser.getCurrentName();
JsonToken token = parser.nextToken();
if ((token == JsonToken.VALUE_STRING) ||
- (token == JsonToken.VALUE_NUMBER_FLOAT) ||
- (token == JsonToken.VALUE_NUMBER_INT) ||
- (token == JsonToken.VALUE_TRUE) ||
- (token == JsonToken.VALUE_FALSE) ||
- (token == JsonToken.VALUE_NULL)) {
+ (token == JsonToken.VALUE_NUMBER_FLOAT) ||
+ (token == JsonToken.VALUE_NUMBER_INT) ||
+ (token == JsonToken.VALUE_TRUE) ||
+ (token == JsonToken.VALUE_FALSE) ||
+ (token == JsonToken.VALUE_NULL)) {
map.put(fieldName, parser.getText());
} else if (token == JsonToken.START_ARRAY) {
map.put(fieldName, skipChildren(parser, buf));
@@ -71,6 +75,7 @@ class Json2SingleLevelMap {
}
}
}
+
private String skipChildren(JsonParser parser, byte [] input) throws IOException {
JsonLocation start = parser.getCurrentLocation();
parser.skipChildren();
@@ -78,4 +83,5 @@ class Json2SingleLevelMap {
int offset = (int)start.getByteOffset() - 1;
return new String(input, offset, (int)(end.getByteOffset() - offset), StandardCharsets.UTF_8);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/QueryTree.java b/container-search/src/main/java/com/yahoo/search/query/QueryTree.java
index 6326097d9bd..1cc2b98c65b 100644
--- a/container-search/src/main/java/com/yahoo/search/query/QueryTree.java
+++ b/container-search/src/main/java/com/yahoo/search/query/QueryTree.java
@@ -140,22 +140,18 @@ public class QueryTree extends CompositeItem {
else if (b == null || b instanceof NullItem) {
return a;
}
- else if (a instanceof NotItem && b instanceof NotItem) {
- NotItem notItemA = (NotItem)a;
- NotItem notItemB = (NotItem)b;
+ else if (a instanceof NotItem notItemA && b instanceof NotItem notItemB) {
NotItem combined = new NotItem();
combined.addPositiveItem(and(notItemA.getPositiveItem(), notItemB.getPositiveItem()));
notItemA.negativeItems().forEach(item -> combined.addNegativeItem(item));
notItemB.negativeItems().forEach(item -> combined.addNegativeItem(item));
return combined;
}
- else if (a instanceof NotItem){
- NotItem notItem = (NotItem)a;
+ else if (a instanceof NotItem notItem){
notItem.addPositiveItem(b);
return a;
}
- else if (b instanceof NotItem){
- NotItem notItem = (NotItem)b;
+ else if (b instanceof NotItem notItem){
notItem.addPositiveItem(a);
return notItem;
}
@@ -179,17 +175,16 @@ public class QueryTree extends CompositeItem {
}
private static void getPositiveTerms(Item item, List<IndexedItem> terms) {
- if (item instanceof NotItem) {
- getPositiveTerms(((NotItem) item).getPositiveItem(), terms);
- } else if (item instanceof PhraseItem) {
- PhraseItem pItem = (PhraseItem)item;
- terms.add(pItem);
- } else if (item instanceof CompositeItem) {
- for (Iterator<Item> i = ((CompositeItem) item).getItemIterator(); i.hasNext();) {
+ if (item instanceof NotItem notItem) {
+ getPositiveTerms(notItem.getPositiveItem(), terms);
+ } else if (item instanceof PhraseItem phraseItem) {
+ terms.add(phraseItem);
+ } else if (item instanceof CompositeItem compositeItem) {
+ for (Iterator<Item> i = compositeItem.getItemIterator(); i.hasNext();) {
getPositiveTerms(i.next(), terms);
}
- } else if (item instanceof TermItem) {
- terms.add((TermItem)item);
+ } else if (item instanceof TermItem termItem) {
+ terms.add(termItem);
}
}
@@ -203,8 +198,7 @@ public class QueryTree extends CompositeItem {
private int countItemsRecursively(Item item) {
int children = 0;
- if (item instanceof CompositeItem) {
- CompositeItem composite = (CompositeItem)item;
+ if (item instanceof CompositeItem composite) {
for (ListIterator<Item> i = composite.getItemIterator(); i.hasNext(); ) {
children += countItemsRecursively(i.next());
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/Sorting.java b/container-search/src/main/java/com/yahoo/search/query/Sorting.java
index 6b07b5ef1d5..63bc48ff804 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Sorting.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Sorting.java
@@ -6,7 +6,6 @@ import com.ibm.icu.util.ULocale;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.processing.IllegalInputException;
import com.yahoo.search.Query;
-import com.yahoo.search.searchchain.Execution;
import com.yahoo.text.Utf8;
import java.nio.ByteBuffer;
@@ -161,6 +160,7 @@ public class Sorting implements Cloneable {
*/
public List<FieldOrder> fieldOrders() { return fieldOrders; }
+ @Override
public Sorting clone() {
return new Sorting(this.fieldOrders);
}
@@ -173,16 +173,13 @@ public class Sorting implements Cloneable {
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if( ! (o instanceof Sorting)) return false;
-
- Sorting ss = (Sorting) o;
+ if( ! (o instanceof Sorting ss)) return false;
return fieldOrders.equals(ss.fieldOrders);
}
public int encode(ByteBuffer buffer) {
int usedBytes = 0;
byte[] nameBuffer;
- buffer.position();
byte space = '.';
for (FieldOrder fieldOrder : fieldOrders) {
if (space == ' ') {
@@ -231,10 +228,10 @@ public class Sorting implements Cloneable {
@Override
public boolean equals(Object other) {
- if (!(other instanceof AttributeSorter)) {
+ if (!(other instanceof AttributeSorter sorter)) {
return false;
}
- return ((AttributeSorter) other).fieldName.equals(fieldName);
+ return sorter.fieldName.equals(fieldName);
}
@Override
@@ -305,15 +302,14 @@ public class Sorting implements Cloneable {
public UcaSorter(String fieldName) { super(fieldName); }
static private int strength2Collator(Strength strength) {
- switch (strength) {
- case PRIMARY: return Collator.PRIMARY;
- case SECONDARY: return Collator.SECONDARY;
- case TERTIARY: return Collator.TERTIARY;
- case QUATERNARY: return Collator.QUATERNARY;
- case IDENTICAL: return Collator.IDENTICAL;
- case UNDEFINED: return Collator.PRIMARY;
- }
- return Collator.PRIMARY;
+ return switch (strength) {
+ case PRIMARY -> Collator.PRIMARY;
+ case SECONDARY -> Collator.SECONDARY;
+ case TERTIARY -> Collator.TERTIARY;
+ case QUATERNARY -> Collator.QUATERNARY;
+ case IDENTICAL -> Collator.IDENTICAL;
+ case UNDEFINED -> Collator.PRIMARY;
+ };
}
public void setLocale(String locale, Strength strength) {
@@ -323,15 +319,15 @@ public class Sorting implements Cloneable {
try {
uloc = new ULocale(locale);
} catch (Throwable e) {
- throw new RuntimeException("ULocale("+locale+") failed with exception " + e.toString());
+ throw new IllegalArgumentException("ULocale '" + locale + "' failed", e);
}
try {
collator = Collator.getInstance(uloc);
if (collator == null) {
- throw new RuntimeException("No collator available for: " + locale);
+ throw new IllegalArgumentException("No collator available for locale '" + locale + "'");
}
} catch (Throwable e) {
- throw new RuntimeException("Collator.getInstance(ULocale("+locale+")) failed with exception " + e.toString());
+ throw new RuntimeException("Collator.getInstance(ULocale(" + locale + ")) failed", e);
}
collator.setStrength(strength2Collator(strength));
// collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
@@ -343,19 +339,22 @@ public class Sorting implements Cloneable {
public String getDecomposition() { return (collator.getDecomposition() == Collator.CANONICAL_DECOMPOSITION) ? "CANONICAL_DECOMPOSITION" : "NO_DECOMPOSITION"; }
@Override
- public String toSerialForm() { return "uca(" + getName() + ',' + locale + ',' + ((strength != Strength.UNDEFINED) ? strength.toString() : "PRIMARY") + ')'; }
+ public String toSerialForm() {
+ return "uca(" + getName() + ',' + locale + ',' +
+ ((strength != Strength.UNDEFINED) ? strength.toString() : "PRIMARY") + ')';
+ }
@Override
public int hashCode() { return 1 + 3*locale.hashCode() + 5*strength.hashCode() + 7*super.hashCode(); }
@Override
public boolean equals(Object other) {
- if (!(other instanceof UcaSorter)) {
- return false;
- }
+ if (this == other) return true;
+ if (!(other instanceof UcaSorter)) return false;
return super.equals(other) && locale.equals(((UcaSorter)other).locale) && (strength == ((UcaSorter)other).strength);
}
+ @Override
public UcaSorter clone() {
UcaSorter clone = (UcaSorter)super.clone();
if (locale != null) {
@@ -365,6 +364,7 @@ public class Sorting implements Cloneable {
}
@SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
public int compare(Comparable a, Comparable b) {
if ((a instanceof String) && (b instanceof String)) {
return collator.compare((String)a, (String) b);
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java
index 224f75e7034..f5d797b4b9f 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java
@@ -357,7 +357,7 @@ public class QueryProfileProperties extends Properties {
if (profile.getTypes().isEmpty()) return name;
CompoundName unaliasedName = name;
- for (int i = 0; i<name.size(); i++) {
+ for (int i = 0; i < name.size(); i++) {
QueryProfileType type = profile.getType(name.first(i), context);
if (type == null) continue;
if (type.aliases() == null) continue; // TODO: Make never null
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java b/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
index afd25132510..bcdc84c1808 100644
--- a/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
@@ -69,8 +69,7 @@ public class NGramSearcher extends Searcher {
private boolean rewriteToNGramMatching(Item item, int indexInParent, IndexFacts.Session indexFacts, Query query) {
boolean rewritten = false;
- if (item instanceof SegmentItem) { // handle CJK segmented terms which should be grams instead
- SegmentItem segments = (SegmentItem)item;
+ if (item instanceof SegmentItem segments) { // handle CJK segmented terms which should be grams instead
Index index = indexFacts.getIndex(segments.getIndexName());
if (index.isNGram()) {
Item grams = splitToGrams(segments, toLowerCase(segments.getRawWord()), index.getGramSize(), query);
@@ -78,13 +77,11 @@ public class NGramSearcher extends Searcher {
rewritten = true;
}
}
- else if (item instanceof CompositeItem) {
- CompositeItem composite = (CompositeItem)item;
+ else if (item instanceof CompositeItem composite) {
for (int i=0; i<composite.getItemCount(); i++)
rewritten = rewriteToNGramMatching(composite.getItem(i), i, indexFacts, query) || rewritten;
}
- else if (item instanceof TermItem) {
- TermItem term = (TermItem)item;
+ else if (item instanceof TermItem term) {
Index index = indexFacts.getIndex(term.getIndexName());
if (index.isNGram()) {
Item grams = splitToGrams(term,term.stringValue(), index.getGramSize(), query);
@@ -149,11 +146,10 @@ public class NGramSearcher extends Searcher {
}
private void replaceItemByGrams(Item item, Item grams, int indexInParent) {
- if (!(grams instanceof CompositeItem) || !(item.getParent() instanceof PhraseItem)) { // usually, simply replace
+ if (!(grams instanceof CompositeItem) || !(item.getParent() instanceof PhraseItem phraseParent)) { // usually, simply replace
item.getParent().setItem(indexInParent, grams);
}
else { // but if the parent is a phrase, we cannot add the AND to it, so add each gram to the phrase
- PhraseItem phraseParent = (PhraseItem)item.getParent();
phraseParent.removeItem(indexInParent);
int addedTerms = 0;
for (Iterator<Item> i = ((CompositeItem)grams).getItemIterator(); i.hasNext(); ) {
diff --git a/container-search/src/main/java/com/yahoo/search/result/FieldComparator.java b/container-search/src/main/java/com/yahoo/search/result/FieldComparator.java
index 0259dd66dbe..7f392d43753 100644
--- a/container-search/src/main/java/com/yahoo/search/result/FieldComparator.java
+++ b/container-search/src/main/java/com/yahoo/search/result/FieldComparator.java
@@ -17,7 +17,7 @@ import java.util.Comparator;
public class FieldComparator extends ChainableComparator {
/** The definition of sorting order */
- private Sorting sorting;
+ private final Sorting sorting;
/** Creates a field comparator using a sort order and having no chained comparator */
public FieldComparator(Sorting sorting) {
@@ -32,7 +32,7 @@ public class FieldComparator extends ChainableComparator {
/** Creates a comparator given a sorting, or returns null if the given sorting is null */
public static FieldComparator create(Sorting sorting) {
- if (sorting==null) return null;
+ if (sorting == null) return null;
return new FieldComparator(sorting);
}
@@ -41,7 +41,7 @@ public class FieldComparator extends ChainableComparator {
* stored in hit fields.0
* <p>
* When one of the hits has the requested property and the other
- * has not, the the hit containing the property precedes the one
+ * has not, the hit containing the property precedes the one
* that does not.
* <p>
* There is no locale based sorting here, as the backend does
@@ -78,19 +78,14 @@ public class FieldComparator extends ChainableComparator {
}
Inspector sub = top.field(key);
if (sub.valid()) {
- switch (sub.type()) {
- case EMPTY:
- return null;
- case BOOL:
- return (sub.asBool() ? Boolean.TRUE : Boolean.FALSE);
- case LONG:
- return sub.asLong();
- case DOUBLE:
- return sub.asDouble();
- case STRING:
- return sub.asString();
- }
- return sub.toString();
+ return switch (sub.type()) {
+ case EMPTY -> null;
+ case BOOL -> (sub.asBool() ? Boolean.TRUE : Boolean.FALSE);
+ case LONG -> sub.asLong();
+ case DOUBLE -> sub.asDouble();
+ case STRING -> sub.asString();
+ default -> sub.toString();
+ };
}
}
// fallback value
@@ -115,15 +110,14 @@ public class FieldComparator extends ChainableComparator {
@SuppressWarnings("rawtypes")
private int compareValues(Object first, Object second, Sorting.AttributeSorter s) {
- if (first == null) {
- if (second == null) return 0;
- return -1;
- } else if (second == null) {
+ if (first == null)
+ return second == null ? 0 : -1;
+ else if (second == null)
return 1;
- }
+
if (first.getClass().isInstance(second) && first instanceof Comparable) {
// We now know:
- // second is of a type which is a subclass of first's type
+ // Second is of a type which is a subclass of first's type
// They both implement Comparable
return s.compare((Comparable)first, (Comparable)second);
} else {
@@ -133,14 +127,7 @@ public class FieldComparator extends ChainableComparator {
@Override
public String toString() {
- StringBuilder b = new StringBuilder();
- b.append("FieldComparator:");
- if (sorting == null) {
- b.append(" null");
- } else {
- b.append(sorting.toString());
- }
- return b.toString();
+ return "FieldComparator:" + (sorting == null ? " null" : sorting.toString());
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/result/Hit.java b/container-search/src/main/java/com/yahoo/search/result/Hit.java
index d7acccc75a7..0011d69fc2c 100644
--- a/container-search/src/main/java/com/yahoo/search/result/Hit.java
+++ b/container-search/src/main/java/com/yahoo/search/result/Hit.java
@@ -16,6 +16,7 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.BiConsumer;
@@ -50,7 +51,7 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
private Map<String, Object> fields = null;
private Map<String, Object> unmodifiableFieldMap = null;
- /** Meta data describing how a given searcher should treat this hit. */
+ /** Metadata describing how a given searcher should treat this hit. */
// TODO: The case for this is to allow multiple levels of federation searcher routing.
// Replace this by a cleaner specific solution to that problem.
private Map<Searcher, Object> searcherSpecificMetaData;
@@ -289,7 +290,8 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
* Returns the id to display, or null to not display (render) the id.
* This is useful to avoid displaying ids when they are not assigned explicitly
* but are just generated values for internal use.
- * This default implementation returns {@link #getId()}.toString()
+ * This default implementation returns the field DOCUMENT_ID if set,
+ * and {@link #getId()}.toString() otherwise.
*/
public String getDisplayId() {
String id = null;
@@ -304,8 +306,7 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
/** Sets the relevance of this hit */
public void setRelevance(Relevance relevance) {
- if (relevance == null) throw new NullPointerException("Cannot assign null as relevance");
- this.relevance = relevance;
+ this.relevance = Objects.requireNonNull(relevance);
}
/** Does setRelevance(new Relevance(relevance) */
@@ -330,7 +331,7 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
* have been used for filling so far. Invoking this method
* multiple times is allowed and will have no addition
* effect. Note that a fillable hit may not be made unfillable.
- **/
+ */
public void setFillable() {
if (filled == null) {
filled = Collections.emptySet();
@@ -344,7 +345,7 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
* tag this hit as fillable if it is currently not.
*
* @param summaryClass summary class used for filling
- **/
+ */
public void setFilled(String summaryClass) {
if (filled == null || filled.isEmpty()) {
filled = Collections.singleton(summaryClass);
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java b/container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java
index adab4d59ec0..205c65a6256 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java
@@ -67,7 +67,7 @@ public class AsyncExecution {
* Creates an async execution.
*
* @param chain the chain to execute
- * @param context the the context of this
+ * @param context the context of this
*/
public AsyncExecution(Chain<? extends Searcher> chain, Execution.Context context) {
this(context, chain);
diff --git a/container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java b/container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java
index 5258087eb44..32880f9b1a8 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java
@@ -277,29 +277,28 @@ final class ProgramParser {
switch (getParseTreeIndex(sourceNode)) {
// ALL_SOURCE and MULTI_SOURCE are how FROM SOURCES
// *|source_name,... are parsed
- case yqlplusParser.RULE_select_source_all:
- Location location = toLocation(scope, sourceNode.getChild(2));
+ case yqlplusParser.RULE_select_source_all -> {
+ Location location = toLocation(scope, sourceNode.getChild(2));
source = OperatorNode.create(location, SequenceOperator.ALL);
source.putAnnotation("alias", "row");
scope.defineDataSource(location, "row");
- break;
- case yqlplusParser.RULE_select_source_multi:
- Source_listContext multiSourceContext = ((Select_source_multiContext) sourceNode).source_list();
+ }
+ case yqlplusParser.RULE_select_source_multi -> {
+ Source_listContext multiSourceContext = ((Select_source_multiContext) sourceNode).source_list();
source = readMultiSource(scope, multiSourceContext);
source.putAnnotation("alias", "row");
scope.defineDataSource(toLocation(scope, multiSourceContext), "row");
- break;
- case yqlplusParser.RULE_select_source_from:
- source = convertSource((ParserRuleContext) sourceNode.getChild(1), scope);
- break;
}
- } else {
- source = OperatorNode.create(SequenceOperator.EMPTY);
+ case yqlplusParser.RULE_select_source_from ->
+ source = convertSource((ParserRuleContext) sourceNode.getChild(1), scope);
}
+ } else {
+ source = OperatorNode.create(SequenceOperator.EMPTY);
+ }
- for (int i = 1; i < node.getChildCount(); ++i) {
- ParseTree child = node.getChild(i);
- switch (getParseTreeIndex(child)) {
+ for (int i = 1; i < node.getChildCount(); ++i) {
+ ParseTree child = node.getChild(i);
+ switch (getParseTreeIndex(child)) {
case yqlplusParser.RULE_select_field_spec:
if (getParseTreeIndex(child.getChild(0)) == yqlplusParser.RULE_project_spec) {
proj = readProjection(((Project_specContext) child.getChild(0)).field_def(), scope);
@@ -311,7 +310,7 @@ final class ProgramParser {
case yqlplusParser.RULE_orderby:
// OrderbyContext orderby()
List<Orderby_fieldContext> orderFieds = ((OrderbyContext) child)
- .orderby_fields().orderby_field();
+ .orderby_fields().orderby_field();
orderby = Lists.newArrayListWithExpectedSize(orderFieds.size());
for (var field: orderFieds) {
orderby.add(convertSortKey(field, scope));
@@ -326,8 +325,8 @@ final class ProgramParser {
case yqlplusParser.RULE_timeout:
timeout = convertExpr(((TimeoutContext) child).fixed_or_parameter(), scope);
break;
- }
}
+ }
// now assemble the logical plan
OperatorNode<SequenceOperator> result = source;
// filter
@@ -415,8 +414,7 @@ final class ProgramParser {
private OperatorNode<SequenceOperator> convertQuery(ParseTree node, Scope scope) {
if (node instanceof Select_statementContext) {
return convertSelect(node, scope.getRoot());
- } else if (node instanceof Source_statementContext) { // for pipe
- Source_statementContext sourceStatementContext = (Source_statementContext)node;
+ } else if (node instanceof Source_statementContext sourceStatementContext) { // for pipe
return convertPipe(sourceStatementContext.query_statement(), sourceStatementContext.pipeline_step(), scope);
} else {
throw new IllegalArgumentException("Unexpected argument type to convertQueryStatement: " + node.toStringTree());
@@ -469,47 +467,44 @@ final class ProgramParser {
dataSourceNode = (ParserRuleContext)dataSourceNode.getChild(1);
}
}
- switch (getParseTreeIndex(dataSourceNode)) {
- case yqlplusParser.RULE_call_source: {
- List<String> names = readName(dataSourceNode.getChild(Namespaced_nameContext.class, 0));
- alias = assignAlias(names.get(names.size() - 1), aliasContext, scope);
- List<OperatorNode<ExpressionOperator>> arguments = ImmutableList.of();
- ArgumentsContext argumentsContext = dataSourceNode.getRuleContext(ArgumentsContext.class,0);
- if ( argumentsContext != null) {
- List<ArgumentContext> argumentContexts = argumentsContext.argument();
- arguments = Lists.newArrayListWithExpectedSize(argumentContexts.size());
- for (ArgumentContext argumentContext:argumentContexts) {
- arguments.add(convertExpr(argumentContext, scope));
- }
- }
- if (names.size() == 1 && scope.isVariable(names.get(0))) {
- String ident = names.get(0);
- if (arguments.size() > 0) {
- throw new ProgramCompileException(toLocation(scope, argumentsContext), "Invalid call-with-arguments on local source '%s'", ident);
- }
- result = OperatorNode.create(toLocation(scope, dataSourceNode), SequenceOperator.EVALUATE, OperatorNode.create(toLocation(scope, dataSourceNode), ExpressionOperator.VARREF, ident));
+ switch (getParseTreeIndex(dataSourceNode)) {
+ case yqlplusParser.RULE_call_source -> {
+ List<String> names = readName(dataSourceNode.getChild(Namespaced_nameContext.class, 0));
+ alias = assignAlias(names.get(names.size() - 1), aliasContext, scope);
+ List<OperatorNode<ExpressionOperator>> arguments = ImmutableList.of();
+ ArgumentsContext argumentsContext = dataSourceNode.getRuleContext(ArgumentsContext.class, 0);
+ if (argumentsContext != null) {
+ List<ArgumentContext> argumentContexts = argumentsContext.argument();
+ arguments = Lists.newArrayListWithExpectedSize(argumentContexts.size());
+ for (ArgumentContext argumentContext : argumentContexts) {
+ arguments.add(convertExpr(argumentContext, scope));
+ }
+ }
+ if (names.size() == 1 && scope.isVariable(names.get(0))) {
+ String ident = names.get(0);
+ if (arguments.size() > 0) {
+ throw new ProgramCompileException(toLocation(scope, argumentsContext), "Invalid call-with-arguments on local source '%s'", ident);
+ }
+ result = OperatorNode.create(toLocation(scope, dataSourceNode), SequenceOperator.EVALUATE, OperatorNode.create(toLocation(scope, dataSourceNode), ExpressionOperator.VARREF, ident));
} else {
- result = OperatorNode.create(toLocation(scope, dataSourceNode), SequenceOperator.SCAN, scope.resolvePath(names), arguments);
+ result = OperatorNode.create(toLocation(scope, dataSourceNode), SequenceOperator.SCAN, scope.resolvePath(names), arguments);
}
- break;
}
- case yqlplusParser.RULE_sequence_source: {
- IdentContext identContext = dataSourceNode.getRuleContext(IdentContext.class,0);
+ case yqlplusParser.RULE_sequence_source -> {
+ IdentContext identContext = dataSourceNode.getRuleContext(IdentContext.class, 0);
String ident = identContext.getText();
if (!scope.isVariable(ident)) {
throw new ProgramCompileException(toLocation(scope, identContext), "Unknown variable reference '%s'", ident);
}
alias = assignAlias(ident, aliasContext, scope);
result = OperatorNode.create(toLocation(scope, dataSourceNode), SequenceOperator.EVALUATE, OperatorNode.create(toLocation(scope, dataSourceNode), ExpressionOperator.VARREF, ident));
- break;
}
- case yqlplusParser.RULE_source_statement: {
+ case yqlplusParser.RULE_source_statement -> {
alias = assignAlias(null, dataSourceNode, scope);
result = convertQuery(dataSourceNode, scope);
- break;
}
- default:
- throw new IllegalArgumentException("Unexpected argument type to convertSource: " + dataSourceNode.getText());
+ default ->
+ throw new IllegalArgumentException("Unexpected argument type to convertSource: " + dataSourceNode.getText());
}
result.putAnnotation("alias", alias);
return result;
@@ -522,10 +517,7 @@ final class ProgramParser {
List<OperatorNode<StatementOperator>> stmts = Lists.newArrayList();
int output = 0;
for (ParseTree node : program.children) {
- if (!(node instanceof ParserRuleContext)) {
- continue;
- }
- ParserRuleContext ruleContext = (ParserRuleContext) node;
+ if (!(node instanceof ParserRuleContext ruleContext)) continue;
if (ruleContext.getRuleIndex() != yqlplusParser.RULE_statement)
throw new ProgramCompileException("Unknown program element: " + node.getText());
diff --git a/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java b/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java
index cab989d4466..4b035e0ccc5 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java
@@ -121,7 +121,6 @@ import com.yahoo.search.query.QueryTree;
public class VespaSerializer {
// TODO: Refactor, too much copy/paste
-
private static abstract class Serializer<ITEM extends Item> {
abstract void onExit(StringBuilder destination, ITEM item);
diff --git a/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java b/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java
index 72cb102760a..3a1927983d2 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java
@@ -90,11 +90,9 @@ import com.yahoo.search.query.parser.ParserFactory;
/**
* The YQL query language.
*
- * <p>
* This class <em>must</em> be kept in lockstep with {@link VespaSerializer}.
* Adding anything here will usually require a corresponding addition in
* VespaSerializer.
- * </p>
*
* @author Steinar Knutsen
* @author Stian Kristoffersen
@@ -132,8 +130,9 @@ public class YqlParser implements Parser {
private static final String USER_INPUT_DEFAULT_INDEX = "defaultIndex";
private static final String USER_INPUT_GRAMMAR = "grammar";
public static final String USER_INPUT_LANGUAGE = "language";
- private static final String USER_INPUT_RAW = "raw";
- private static final String USER_INPUT_SEGMENT = "segment";
+ private static final String USER_INPUT_GRAMMAR_RAW = "raw";
+ private static final String USER_INPUT_GRAMMAR_SEGMENT = "segment";
+ private static final String USER_INPUT_GRAMMAR_WEAKAND = "weakAnd";
private static final String USER_INPUT = "userInput";
private static final String USER_QUERY = "userQuery";
private static final String NON_EMPTY = "nonEmpty";
@@ -722,14 +721,19 @@ public class YqlParser implements Parser {
String.class, "default", "default index for user input terms");
Language language = decideParsingLanguage(ast, wordData);
Item item;
- if (USER_INPUT_RAW.equals(grammar)) {
+ if (USER_INPUT_GRAMMAR_RAW.equals(grammar)) {
item = instantiateWordItem(defaultIndex, wordData, ast, null, SegmentWhen.NEVER, true, language);
- } else if (USER_INPUT_SEGMENT.equals(grammar)) {
+ } else if (USER_INPUT_GRAMMAR_SEGMENT.equals(grammar)) {
item = instantiateWordItem(defaultIndex, wordData, ast, null, SegmentWhen.ALWAYS, false, language);
} else {
item = parseUserInput(grammar, defaultIndex, wordData, language, allowEmpty);
propagateUserInputAnnotations(ast, item);
}
+
+ // Set grammar-specific annotations
+ if (USER_INPUT_GRAMMAR_WEAKAND.equals(grammar) && item instanceof WeakAndItem weakAndItem) {
+ weakAndItem.setN(getAnnotation(ast, TARGET_HITS, Integer.class, WeakAndItem.defaultN, "'targetHits' (N) for weak and"));
+ }
return item;
}
@@ -1428,7 +1432,7 @@ public class YqlParser implements Parser {
wordItem = new WordItem(wordData, fromQuery);
break;
case POSSIBLY:
- if (shouldSegment(field, fromQuery) && ! grammar.equals(USER_INPUT_RAW)) {
+ if (shouldSegment(field, fromQuery) && ! grammar.equals(USER_INPUT_GRAMMAR_RAW)) {
wordItem = segment(field, ast, wordData, fromQuery, parent, language);
} else {
wordItem = new WordItem(wordData, fromQuery);
diff --git a/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java b/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
index ae2a8800bbc..5b972e40774 100644
--- a/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
@@ -65,7 +65,7 @@ import static org.junit.jupiter.api.Assertions.*;
public class QueryTestCase {
@Test
- void testIt() throws Exception {
+ void testIt() {
JSONObject newroot = new JSONObject("{\"key\": 3}");
var hit = new FastHit();
hit.setField("data", (JsonProducer) s -> s.append(newroot));
diff --git a/container-search/src/test/java/com/yahoo/search/yql/UserInputTestCase.java b/container-search/src/test/java/com/yahoo/search/yql/UserInputTestCase.java
index 1e3b52c23af..858d5a16352 100644
--- a/container-search/src/test/java/com/yahoo/search/yql/UserInputTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/yql/UserInputTestCase.java
@@ -7,6 +7,7 @@ import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.IndexModel;
import com.yahoo.prelude.SearchDefinition;
+import com.yahoo.prelude.query.WeakAndItem;
import org.apache.http.client.utils.URIBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -89,6 +90,17 @@ public class UserInputTestCase {
}
@Test
+ void testUserInputSettingTargetHits() {
+ URIBuilder builder = searchUri();
+ builder.setParameter("yql",
+ "select * from sources * where {grammar: \"weakAnd\", targetHits: 17, defaultIndex: \"f\"}userInput(\"a test\")");
+ Query query = searchAndAssertNoErrors(builder);
+ assertEquals("select * from sources * where ({targetNumHits: 17}weakAnd(f contains \"a\", f contains \"test\"))", query.yqlRepresentation());
+ WeakAndItem weakAnd = (WeakAndItem)query.getModel().getQueryTree().getRoot();
+ assertEquals(17, weakAnd.getN());
+ }
+
+ @Test
void testSegmentedNoiseUserInput() {
URIBuilder builder = searchUri();
builder.setParameter("yql",
diff --git a/container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java b/container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java
index 5beea5352aa..807681e1d7b 100644
--- a/container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/yql/YqlParserTestCase.java
@@ -1002,10 +1002,21 @@ public class YqlParserTestCase {
@Test
void testRegexp() {
- QueryTree x = parse("select * from sources * where foo matches \"a b\"");
- Item root = x.getRoot();
- assertSame(RegExpItem.class, root.getClass());
- assertEquals("a b", ((RegExpItem) root).stringValue());
+ {
+ QueryTree x = parse("select * from sources * where foo matches \"a b\"");
+ Item root = x.getRoot();
+ assertSame(RegExpItem.class, root.getClass());
+ assertEquals("a b", ((RegExpItem) root).stringValue());
+ }
+
+ {
+ String expression = "a\\\\.b\\\\.c";
+ QueryTree query = parse("select * from sources * where foo matches \"" + expression + "\"");
+ var regExpItem = (RegExpItem) query.getRoot();
+ assertEquals("a\\.b\\.c", regExpItem.stringValue());
+ assertTrue(regExpItem.getRegexp().matcher("a.b.c").matches(), "a.b.c is matched");
+ assertFalse(regExpItem.getRegexp().matcher("a,b,c").matches(), "a,b,c is matched?");
+ }
}
@Test
diff --git a/container-test/pom.xml b/container-test/pom.xml
index 65418f75ae2..100fe749394 100644
--- a/container-test/pom.xml
+++ b/container-test/pom.xml
@@ -32,7 +32,7 @@
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
@@ -40,7 +40,7 @@
in provided scope when container-test is declared before, and together with, the container artifact -->
<dependency>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>bcpkix-jdk18on</artifactId>
</dependency>
<!-- All dependencies that should be visible in test classpath, but not compile classpath,
diff --git a/container/pom.xml b/container/pom.xml
index 966d8d49f5a..10322758d1a 100644
--- a/container/pom.xml
+++ b/container/pom.xml
@@ -24,7 +24,7 @@
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.ow2.asm</groupId>
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
index a35d01f6891..d3331c3cfd4 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
@@ -22,12 +23,14 @@ import static java.util.Objects.requireNonNull;
/**
* Data pertaining to a deployment to be done on a config server.
+ * Accessor names must match the names in com.yahoo.vespa.config.server.session.PrepareParams.
*
* @author jonmv
*/
public class DeploymentData {
private final ApplicationId instance;
+ private final Tags tags;
private final ZoneId zone;
private final byte[] applicationPackage;
private final Version platform;
@@ -41,7 +44,7 @@ public class DeploymentData {
private final Optional<CloudAccount> cloudAccount;
private final boolean dryRun;
- public DeploymentData(ApplicationId instance, ZoneId zone, byte[] applicationPackage, Version platform,
+ public DeploymentData(ApplicationId instance, Tags tags, ZoneId zone, byte[] applicationPackage, Version platform,
Set<ContainerEndpoint> containerEndpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
Optional<DockerImage> dockerImageRepo,
@@ -51,6 +54,7 @@ public class DeploymentData {
List<X509Certificate> operatorCertificates,
Optional<CloudAccount> cloudAccount, boolean dryRun) {
this.instance = requireNonNull(instance);
+ this.tags = requireNonNull(tags);
this.zone = requireNonNull(zone);
this.applicationPackage = requireNonNull(applicationPackage);
this.platform = requireNonNull(platform);
@@ -69,6 +73,8 @@ public class DeploymentData {
return instance;
}
+ public Tags tags() { return tags; }
+
public ZoneId zone() {
return zone;
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java
index 4e5f2ab64cb..28bb9182c6a 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java
@@ -133,6 +133,11 @@ public class ZmsClientMock implements ZmsClient {
return new ArrayList<>(athenz.domains.keySet());
}
+ public List<AthenzDomain> getDomainListByAccount(String id) {
+ log("getDomainListById()");
+ return new ArrayList<>();
+ }
+
@Override
public boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity) {
log("hasAccess(resource=%s, action=%s, identity=%s)", resource, action, identity);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java
index 1659a87acb3..7ccbcf2a954 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/AliasTarget.java
@@ -59,8 +59,8 @@ public sealed abstract class AliasTarget permits LatencyAliasTarget, WeightedAli
public static AliasTarget unpack(RecordData data) {
String[] parts = data.asString().split("/");
switch (parts[0]) {
- case "latency": return LatencyAliasTarget.unpack(data);
- case "weighted": return WeightedAliasTarget.unpack(data);
+ case LatencyAliasTarget.TARGET_TYPE: return LatencyAliasTarget.unpack(data);
+ case WeightedAliasTarget.TARGET_TYPE: return WeightedAliasTarget.unpack(data);
}
throw new IllegalArgumentException("Unknown alias type '" + parts[0] + "'");
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTarget.java
new file mode 100644
index 00000000000..c3cedf93841
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTarget.java
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+import java.util.Objects;
+
+/**
+ * Same as {@link AliasTarget}, except for targets outside AWS (cannot be targeted with ALIAS record).
+ *
+ * @author freva
+ */
+public sealed abstract class DirectTarget permits LatencyDirectTarget, WeightedDirectTarget {
+
+ private final RecordData recordData;
+ private final String id;
+
+ protected DirectTarget(RecordData recordData, String id) {
+ this.recordData = Objects.requireNonNull(recordData, "recordData must be non-null");
+ this.id = Objects.requireNonNull(id, "id must be non-null");
+ }
+
+ /** A unique identifier of this record within the record group */
+ public String id() {
+ return id;
+ }
+
+ /** Data in this, e.g. IP address for records of type A */
+ public RecordData recordData() {
+ return recordData;
+ }
+
+ /** Returns the fields in this encoded as record data */
+ public abstract RecordData pack();
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DirectTarget that = (DirectTarget) o;
+ return recordData.equals(that.recordData) && id.equals(that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(recordData, id);
+ }
+
+ /** Unpack target from given record data */
+ public static DirectTarget unpack(RecordData data) {
+ String[] parts = data.asString().split("/");
+ return switch (parts[0]) {
+ case LatencyDirectTarget.TARGET_TYPE -> LatencyDirectTarget.unpack(data);
+ case WeightedDirectTarget.TARGET_TYPE -> WeightedDirectTarget.unpack(data);
+ default -> throw new IllegalArgumentException("Unknown alias type '" + parts[0] + "'");
+ };
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java
index 70c89b05f09..00e5218dead 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyAliasTarget.java
@@ -13,6 +13,8 @@ import java.util.Objects;
*/
public final class LatencyAliasTarget extends AliasTarget {
+ static final String TARGET_TYPE = "latency";
+
private final ZoneId zone;
public LatencyAliasTarget(DomainName name, String dnsZone, ZoneId zone) {
@@ -27,7 +29,7 @@ public final class LatencyAliasTarget extends AliasTarget {
@Override
public RecordData pack() {
- return RecordData.from("latency/" + name().value() + "/" + dnsZone() + "/" + id());
+ return RecordData.from(String.join("/", TARGET_TYPE, name().value(), dnsZone(), id()));
}
@Override
@@ -56,7 +58,7 @@ public final class LatencyAliasTarget extends AliasTarget {
throw new IllegalArgumentException("Expected data to be on format type/name/DNS-zone/zone-id, but got " +
data.asString());
}
- if (!"latency".equals(parts[0])) {
+ if (!TARGET_TYPE.equals(parts[0])) {
throw new IllegalArgumentException("Unexpected type '" + parts[0] + "'");
}
return new LatencyAliasTarget(DomainName.of(parts[1]), parts[2], ZoneId.from(parts[3]));
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyDirectTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyDirectTarget.java
new file mode 100644
index 00000000000..09795ae08a7
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/LatencyDirectTarget.java
@@ -0,0 +1,66 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+import com.yahoo.config.provision.zone.ZoneId;
+
+import java.util.Objects;
+
+/**
+ * An implementation of {@link DirectTarget} that uses latency-based routing.
+ *
+ * @author freva
+ */
+public final class LatencyDirectTarget extends DirectTarget {
+
+ static final String TARGET_TYPE = "latency";
+
+ private final ZoneId zone;
+
+ public LatencyDirectTarget(RecordData recordData, ZoneId zone) {
+ super(recordData, zone.value());
+ this.zone = Objects.requireNonNull(zone);
+ }
+
+ /** The zone this record points to */
+ public ZoneId zone() {
+ return zone;
+ }
+
+ @Override
+ public RecordData pack() {
+ return RecordData.from(String.join("/", TARGET_TYPE, recordData().asString(), id()));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ LatencyDirectTarget that = (LatencyDirectTarget) o;
+ return zone.equals(that.zone);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), zone);
+ }
+
+ @Override
+ public String toString() {
+ return "latency target for " + recordData() + " [id=" + id() + "]";
+ }
+
+ /** Unpack latency alias from given record data */
+ public static LatencyDirectTarget unpack(RecordData data) {
+ var parts = data.asString().split("/");
+ if (parts.length != 3) {
+ throw new IllegalArgumentException("Expected data to be on format target-type/record-data/zone-id, but got " +
+ data.asString());
+ }
+ if (!TARGET_TYPE.equals(parts[0])) {
+ throw new IllegalArgumentException("Unexpected type '" + parts[0] + "'");
+ }
+ return new LatencyDirectTarget(RecordData.from(parts[1]), ZoneId.from(parts[2]));
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java
index 18d7bc53035..9a9270bdf7f 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/MemoryNameService.java
@@ -54,6 +54,20 @@ public class MemoryNameService implements NameService {
}
@Override
+ public List<Record> createDirect(RecordName name, Set<DirectTarget> targets) {
+ var records = targets.stream()
+ .sorted((a, b) -> Comparator.comparing((DirectTarget target) -> target.recordData().asString()).compare(a, b))
+ .map(d -> new Record(Record.Type.DIRECT, name, d.pack()))
+ .collect(Collectors.toList());
+ // Satisfy idempotency contract of interface
+ for (var r1 : records) {
+ this.records.removeIf(r2 -> conflicts(r1, r2));
+ }
+ this.records.addAll(records);
+ return records;
+ }
+
+ @Override
public List<Record> createTxtRecords(RecordName name, List<RecordData> txtData) {
var records = txtData.stream()
.map(data -> new Record(Record.Type.TXT, name, data))
@@ -122,6 +136,11 @@ public class MemoryNameService implements NameService {
AliasTarget t2 = AliasTarget.unpack(r2.data());
return t1.name().equals(t2.name()); // ALIAS records require distinct targets
}
+ if (r1.type() == Record.Type.DIRECT && r1.type() == r2.type()) {
+ DirectTarget t1 = DirectTarget.unpack(r1.data());
+ DirectTarget t2 = DirectTarget.unpack(r2.data());
+ return t1.id().equals(t2.id()); // DIRECT records require distinct IDs
+ }
return true; // Anything else is considered a conflict
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java
index 505ff3850ab..72e983680d9 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/NameService.java
@@ -31,6 +31,15 @@ public interface NameService {
List<Record> createAlias(RecordName name, Set<AliasTarget> targets);
/**
+ * Create a non-standard record pointing to given targets. Implementations of this are expected to be
+ * idempotent
+ *
+ * @param targets Targets that should be resolved by this name.
+ * @return The created records. One per target.
+ */
+ List<Record> createDirect(RecordName name, Set<DirectTarget> targets);
+
+ /**
* Create a new TXT record containing the provided data.
* @param name Name of the created record
* @param txtRecords TXT data values for the record, each consisting of one or more space-separated double-quoted
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java
index 2f9312b2f89..e76445faa60 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/Record.java
@@ -55,6 +55,7 @@ public record Record(Type type,
AAAA,
ALIAS,
CNAME,
+ DIRECT,
MX,
NS,
PTR,
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java
index 6a61b62f3a4..ca01c713e93 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedAliasTarget.java
@@ -16,6 +16,8 @@ import java.util.Objects;
*/
public final class WeightedAliasTarget extends AliasTarget {
+ static final String TARGET_TYPE = "weighted";
+
private final long weight;
public WeightedAliasTarget(DomainName name, String dnsZone, ZoneId zone, long weight) {
@@ -31,7 +33,7 @@ public final class WeightedAliasTarget extends AliasTarget {
@Override
public RecordData pack() {
- return RecordData.from("weighted/" + name().value() + "/" + dnsZone() + "/" + id() + "/" + weight);
+ return RecordData.from(String.join("/", TARGET_TYPE, name().value(), dnsZone(), id(), Long.toString(weight)));
}
@Override
@@ -60,7 +62,7 @@ public final class WeightedAliasTarget extends AliasTarget {
throw new IllegalArgumentException("Expected data to be on format type/name/DNS-zone/zone-id/weight, " +
"but got " + data.asString());
}
- if (!"weighted".equals(parts[0])) {
+ if (!TARGET_TYPE.equals(parts[0])) {
throw new IllegalArgumentException("Unexpected type '" + parts[0] + "'");
}
return new WeightedAliasTarget(DomainName.of(parts[1]), parts[2], ZoneId.from(parts[3]),
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedDirectTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedDirectTarget.java
new file mode 100644
index 00000000000..b899cb57b60
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/dns/WeightedDirectTarget.java
@@ -0,0 +1,70 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+import com.yahoo.config.provision.zone.ZoneId;
+
+import java.util.Objects;
+
+/**
+ * An implementation of {@link DirectTarget} where is requests are answered based on the weight assigned to the
+ * record, as a proportion of the total weight for all records having the same DNS name.
+ *
+ * The portion of received traffic is calculated as follows: (record weight / sum of the weights of all records).
+ *
+ * @author freva
+ */
+public final class WeightedDirectTarget extends DirectTarget {
+
+ static final String TARGET_TYPE = "weighted";
+
+ private final long weight;
+
+ public WeightedDirectTarget(RecordData recordData, ZoneId zone, long weight) {
+ super(recordData, zone.value());
+ this.weight = weight;
+ if (weight < 0) throw new IllegalArgumentException("Weight cannot be negative");
+ }
+
+ /** The weight of this target */
+ public long weight() {
+ return weight;
+ }
+
+ @Override
+ public RecordData pack() {
+ return RecordData.from(String.join("/", TARGET_TYPE, recordData().asString(), id(), Long.toString(weight)));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ WeightedDirectTarget that = (WeightedDirectTarget) o;
+ return weight == that.weight;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), weight);
+ }
+
+ @Override
+ public String toString() {
+ return "weighted target for " + recordData() + "[id=" + id() + ",weight=" + weight + "]";
+ }
+
+ /** Unpack weighted alias from given record data */
+ public static WeightedDirectTarget unpack(RecordData data) {
+ var parts = data.asString().split("/");
+ if (parts.length != 4) {
+ throw new IllegalArgumentException("Expected data to be on format target-type/record-data/zone-id/weight, " +
+ "but got " + data.asString());
+ }
+ if (!TARGET_TYPE.equals(parts[0])) {
+ throw new IllegalArgumentException("Unexpected type '" + parts[0] + "'");
+ }
+ return new WeightedDirectTarget(RecordData.from(parts[1]), ZoneId.from(parts[2]), Long.parseLong(parts[3]));
+ }
+
+}
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTargetTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTargetTest.java
new file mode 100644
index 00000000000..f262821a638
--- /dev/null
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/dns/DirectTargetTest.java
@@ -0,0 +1,36 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.dns;
+
+import com.yahoo.config.provision.zone.ZoneId;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * @author freva
+ */
+class DirectTargetTest {
+
+ @Test
+ void packing() {
+ List<DirectTarget> tests = List.of(
+ new LatencyDirectTarget(RecordData.from("foo.example.com"), ZoneId.from("prod.us-north-1")),
+ new WeightedDirectTarget(RecordData.from("bar.example.com"), ZoneId.from("prod.us-north-2"), 50));
+ for (var target : tests) {
+ DirectTarget unpacked = DirectTarget.unpack(target.pack());
+ assertEquals(target, unpacked);
+ }
+
+ List<RecordData> invalidData = List.of(RecordData.from(""), RecordData.from("foobar"));
+ for (var data : invalidData) {
+ try {
+ DirectTarget.unpack(data);
+ fail("Expected exception");
+ } catch (IllegalArgumentException ignored) { }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index e3a0ad7cb84..66e62ff7b95 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -65,7 +65,7 @@ public class Application {
Set.of(), OptionalLong.empty(), RevisionHistory.empty(), List.of());
}
- // DO NOT USE! For serialization purposes, only.
+ // Do not use directly - edit through LockedApplication.
public Application(TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Set<PublicKey> deployKeys, OptionalLong projectId,
@@ -230,11 +230,8 @@ public class Application {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (! (o instanceof Application)) return false;
-
- Application that = (Application) o;
-
- return id.equals(that.id);
+ if (! (o instanceof Application other)) return false;
+ return id.equals(other.id);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index 19775ef420d..d8234e4f269 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -12,6 +12,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.log.LogLevel;
@@ -166,10 +167,11 @@ public class ApplicationController {
int count = 0;
for (TenantAndApplicationId id : curator.readApplicationIds()) {
lockApplicationIfPresent(id, application -> {
- for (InstanceName instance : application.get().deploymentSpec().instanceNames())
- if ( ! application.get().instances().containsKey(instance))
- application = withNewInstance(application, id.instance(instance));
-
+ for (var declaredInstance : application.get().deploymentSpec().instances())
+ if ( ! application.get().instances().containsKey(declaredInstance.name()))
+ application = withNewInstance(application,
+ id.instance(declaredInstance.name()),
+ declaredInstance.tags());
store(application);
});
count++;
@@ -451,14 +453,14 @@ public class ApplicationController {
*
* @throws IllegalArgumentException if the instance already exists, or has an invalid instance name.
*/
- public void createInstance(ApplicationId id) {
+ public void createInstance(ApplicationId id, Tags tags) {
lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
- store(withNewInstance(application, id));
+ store(withNewInstance(application, id, tags));
});
}
/** Returns given application with a new instance */
- public LockedApplication withNewInstance(LockedApplication application, ApplicationId instance) {
+ public LockedApplication withNewInstance(LockedApplication application, ApplicationId instance, Tags tags) {
if (instance.instance().isTester())
throw new IllegalArgumentException("'" + instance + "' is a tester application!");
InstanceId.validate(instance.instance().value());
@@ -469,7 +471,7 @@ public class ApplicationController {
throw new IllegalArgumentException("Could not create '" + instance + "': Instance " + dashToUnderscore(instance) + " already exists");
log.info("Created " + instance);
- return application.withNewInstance(instance.instance());
+ return application.withNewInstance(instance.instance(), tags);
}
/** Deploys an application package for an existing application instance. */
@@ -496,10 +498,11 @@ public class ApplicationController {
ApplicationPackage applicationPackage = new ApplicationPackage(applicationStore.get(deployment, revision));
AtomicReference<RevisionId> lastRevision = new AtomicReference<>();
+ Instance instance;
try (Mutex lock = lock(applicationId)) {
LockedApplication application = new LockedApplication(requireApplication(applicationId), lock);
application.get().revisions().last().map(ApplicationVersion::id).ifPresent(lastRevision::set);
- Instance instance = application.get().require(job.application().instance());
+ instance = application.get().require(job.application().instance());
if ( ! applicationPackage.trustedCertificates().isEmpty()
&& run.testerCertificate().isPresent())
@@ -512,7 +515,7 @@ public class ApplicationController {
} // Release application lock while doing the deployment, which is a lengthy task.
// Carry out deployment without holding the application lock.
- ActivateResult result = deploy(job.application(), applicationPackage, zone, platform, containerEndpoints,
+ ActivateResult result = deploy(job.application(), instance.tags(), applicationPackage, zone, platform, containerEndpoints,
endpointCertificateMetadata, run.isDryRun());
endpointCertificateMetadata.ifPresent(e -> deployLogger.accept("Using CA signed certificate version %s".formatted(e.version())));
@@ -543,9 +546,9 @@ public class ApplicationController {
lockApplicationOrThrow(applicationId, application ->
store(application.with(job.application().instance(),
- instance -> instance.withNewDeployment(zone, revision, platform,
- clock.instant(), warningsFrom(result),
- quotaUsage))));
+ i -> i.withNewDeployment(zone, revision, platform,
+ clock.instant(), warningsFrom(result),
+ quotaUsage))));
return result;
}
}
@@ -557,16 +560,19 @@ public class ApplicationController {
application = application.with(applicationPackage.deploymentSpec());
application = application.with(applicationPackage.validationOverrides());
- var existingInstances = application.get().instances().keySet();
- var declaredInstances = applicationPackage.deploymentSpec().instanceNames();
- for (var name : declaredInstances)
- if ( ! existingInstances.contains(name))
- application = withNewInstance(application, application.get().id().instance(name));
+ var existingInstances = application.get().instances();
+ var declaredInstances = applicationPackage.deploymentSpec().instances();
+ for (var declaredInstance : declaredInstances) {
+ if ( ! existingInstances.containsKey(declaredInstance.name()))
+ application = withNewInstance(application, application.get().id().instance(declaredInstance.name()), declaredInstance.tags());
+ else if ( ! existingInstances.get(declaredInstance.name()).tags().equals(declaredInstance.tags()))
+ application = application.with(declaredInstance.name(), instance -> instance.with(declaredInstance.tags()));
+ }
// Delete zones not listed in DeploymentSpec, if allowed
// We do this at deployment time for externally built applications, and at submission time
// for internally built ones, to be able to return a validation failure message when necessary
- for (InstanceName name : existingInstances) {
+ for (InstanceName name : existingInstances.keySet()) {
application = withoutDeletedDeployments(application, name);
}
@@ -599,7 +605,7 @@ public class ApplicationController {
ApplicationPackage applicationPackage = new ApplicationPackage(
artifactRepository.getSystemApplicationPackage(application.id(), zone, version)
);
- return deploy(application.id(), applicationPackage, zone, version, Set.of(), /* No application cert */ Optional.empty(), false);
+ return deploy(application.id(), Tags.empty(), applicationPackage, zone, version, Set.of(), /* No application cert */ Optional.empty(), false);
} else {
throw new RuntimeException("This system application does not have an application package: " + application.id().toShortString());
}
@@ -607,10 +613,10 @@ public class ApplicationController {
/** Deploys the given tester application to the given zone. */
public ActivateResult deployTester(TesterId tester, ApplicationPackage applicationPackage, ZoneId zone, Version platform) {
- return deploy(tester.id(), applicationPackage, zone, platform, Set.of(), /* No application cert for tester*/ Optional.empty(), false);
+ return deploy(tester.id(), Tags.empty(), applicationPackage, zone, platform, Set.of(), /* No application cert for tester*/ Optional.empty(), false);
}
- private ActivateResult deploy(ApplicationId application, ApplicationPackage applicationPackage,
+ private ActivateResult deploy(ApplicationId application, Tags tags, ApplicationPackage applicationPackage,
ZoneId zone, Version platform, Set<ContainerEndpoint> endpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
boolean dryRun) {
@@ -646,7 +652,7 @@ public class ApplicationController {
.collect(toList());
Optional<CloudAccount> cloudAccount = decideCloudAccountOf(deployment, applicationPackage.deploymentSpec());
ConfigServer.PreparedApplication preparedApplication =
- configServer.deploy(new DeploymentData(application, zone, applicationPackage.zippedContent(), platform,
+ configServer.deploy(new DeploymentData(application, tags, zone, applicationPackage.zippedContent(), platform,
endpoints, endpointCertificateMetadata, dockerImageRepo, domain,
deploymentQuota, tenantSecretStores, operatorCertificates,
cloudAccount, dryRun));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
index d66d1491f73..430bafe5c44 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
@@ -40,6 +41,7 @@ import java.util.stream.Collectors;
public class Instance {
private final ApplicationId id;
+ private final Tags tags;
private final Map<ZoneId, Deployment> deployments;
private final List<AssignedRotation> rotations;
private final RotationStatus rotationStatus;
@@ -47,14 +49,15 @@ public class Instance {
private final Change change;
/** Creates an empty instance */
- public Instance(ApplicationId id) {
- this(id, Set.of(), Map.of(), List.of(), RotationStatus.EMPTY, Change.empty());
+ public Instance(ApplicationId id, Tags tags) {
+ this(id, tags, Set.of(), Map.of(), List.of(), RotationStatus.EMPTY, Change.empty());
}
/** Creates an empty instance*/
- public Instance(ApplicationId id, Collection<Deployment> deployments, Map<JobType, Instant> jobPauses,
+ public Instance(ApplicationId id, Tags tags, Collection<Deployment> deployments, Map<JobType, Instant> jobPauses,
List<AssignedRotation> rotations, RotationStatus rotationStatus, Change change) {
this.id = Objects.requireNonNull(id, "id cannot be null");
+ this.tags = Objects.requireNonNull(tags, "tags cannot be null");
this.deployments = Objects.requireNonNull(deployments, "deployments cannot be null").stream()
.collect(Collectors.toUnmodifiableMap(Deployment::zone, Function.identity()));
this.jobPauses = Map.copyOf(Objects.requireNonNull(jobPauses, "deploymentJobs cannot be null"));
@@ -63,6 +66,10 @@ public class Instance {
this.change = Objects.requireNonNull(change, "change cannot be null");
}
+ public Instance with(Tags tags) {
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ }
+
public Instance withNewDeployment(ZoneId zone, RevisionId revision, Version version,
Instant instant, Map<DeploymentMetrics.Warning, Integer> warnings, QuotaUsage quotaUsage) {
// Use info from previous deployment if available, otherwise create a new one.
@@ -87,7 +94,7 @@ public class Instance {
else
jobPauses.remove(jobType);
- return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
}
public Instance recordActivityAt(Instant instant, ZoneId zone) {
@@ -118,15 +125,15 @@ public class Instance {
}
public Instance with(List<AssignedRotation> assignedRotations) {
- return new Instance(id, deployments.values(), jobPauses, assignedRotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, assignedRotations, rotationStatus, change);
}
public Instance with(RotationStatus rotationStatus) {
- return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
}
public Instance withChange(Change change) {
- return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
}
private Instance with(Deployment deployment) {
@@ -136,13 +143,15 @@ public class Instance {
}
private Instance with(Map<ZoneId, Deployment> deployments) {
- return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
}
public ApplicationId id() { return id; }
public InstanceName name() { return id.instance(); }
+ public Tags tags() { return tags; }
+
/** Returns an immutable map of the current deployments of this */
public Map<ZoneId, Deployment> deployments() { return deployments; }
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index 3e822415e96..fa702a166d2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
@@ -88,9 +89,9 @@ public class LockedApplication {
projectId, revisions, instances.values());
}
- LockedApplication withNewInstance(InstanceName instance) {
+ LockedApplication withNewInstance(InstanceName instance, Tags tags) {
var instances = new HashMap<>(this.instances);
- instances.put(instance, new Instance(id.instance(instance)));
+ instances.put(instance, new Instance(id.instance(instance), tags));
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, instances, revisions);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
index 4bc9aeb00e4..c2c95a0c4bf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
@@ -16,6 +16,7 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
@@ -202,7 +203,8 @@ public class ApplicationPackage {
new InputStreamReader(new ByteArrayInputStream(servicesXml.content()), UTF_8),
InstanceName.defaultName(),
Environment.prod,
- RegionName.defaultName())
+ RegionName.defaultName(),
+ Tags.empty())
.run(); // Populates the zip archive cache with files that would be included.
}
catch (IllegalArgumentException e) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
index 8e8a4e24970..f72b6f2e9f0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
@@ -10,7 +10,6 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
-import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
@@ -26,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -97,15 +97,10 @@ public class ApplicationPackageValidator {
var clouds = new HashSet<CloudName>();
for (var region : endpoint.regions()) {
for (ZoneApi zone : controller.zoneRegistry().zones().all().in(Environment.prod).in(region).zones()) {
- if (zone.getCloudName().equals(CloudName.GCP)) {
- throw new IllegalArgumentException("Endpoint '" + endpoint.endpointId() + "' in " + instance +
- " contains a Google Cloud region (" + region +
- "), which is not yet supported");
- }
clouds.add(zone.getCloudName());
}
}
- if (clouds.size() != 1) {
+ if (clouds.size() != 1 && !clouds.equals(Set.of(CloudName.GCP, CloudName.AWS))) {
throw new IllegalArgumentException("Endpoint '" + endpoint.endpointId() + "' in " + instance +
" cannot contain regions in different clouds: " +
endpoint.regions().stream().sorted().toList());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
index 9bc2c5a5595..0fe9a84f5fa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
@@ -54,6 +54,7 @@ import static com.yahoo.config.application.api.DeploymentSpec.RevisionTarget.nex
import static com.yahoo.config.provision.Environment.prod;
import static com.yahoo.config.provision.Environment.staging;
import static com.yahoo.config.provision.Environment.test;
+import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.invalidApplication;
import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
@@ -1027,10 +1028,11 @@ public class DeploymentStatus {
Versions lastVersions = job.lastCompleted().get().versions();
Versions toRun = Versions.from(change, status.application, dependent.flatMap(status::deploymentFor), status.fallbackPlatform(change, job.id()));
if ( ! toRun.targetsMatch(lastVersions)) return Optional.empty();
- if ( job.id().type().environment().isTest()
+ if ( job.id().type().environment().isTest()
&& ! dependent.map(JobId::type).map(status::findCloud).map(List.of(CloudName.AWS, CloudName.GCP)::contains).orElse(true)
- && job.isNodeAllocationFailure()) return Optional.empty();
+ && job.isNodeAllocationFailure()) return Optional.empty();
+ if (job.lastStatus().get() == invalidApplication) return Optional.of(status.now.plus(Duration.ofDays(36524))); // 100 years
Instant firstFailing = job.firstFailing().get().end().get();
Instant lastCompleted = job.lastCompleted().get().end().get();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index cf09afa7181..d5a31a07408 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -91,10 +91,31 @@ public class DeploymentTrigger {
status,
false));
}
+
+ // If app has been broken since it was first submitted, and not fixed for a long time, we stop managing it until a new submission comes in.
+ if (applicationWasAlwaysBroken(status))
+ application = application.withProjectId(OptionalLong.empty());
+
applications().store(application);
});
}
+ private boolean applicationWasAlwaysBroken(DeploymentStatus status) {
+ // If application has a production deployment, we cannot forget it.
+ if (status.application().instances().values().stream().anyMatch(instance -> ! instance.productionDeployments().isEmpty()))
+ return false;
+
+ // Then, we need a job that always failed, and failed on the last revision for at least 30 days.
+ RevisionId last = status.application().revisions().last().get().id();
+ Instant threshold = clock.instant().minus(Duration.ofDays(30));
+ for (JobStatus job : status.jobs().asList())
+ for (Run run : job.runs().descendingMap().values())
+ if (run.hasEnded() && ! run.hasFailed() || ! run.versions().targetRevision().equals(last)) break;
+ else if (run.start().isBefore(threshold)) return true;
+
+ return false;
+ }
+
/**
* Records information when a job completes (successfully or not). This information is used when deciding what to
* trigger next.
@@ -339,8 +360,8 @@ public class DeploymentTrigger {
/** Returns the set of all jobs which have changes to propagate from the upstream steps. */
private List<Job> computeReadyJobs() {
return jobs.deploymentStatuses(ApplicationList.from(applications().readable())
- .withProjectId() // Need to keep this, as we have applications with deployment spec that shouldn't be orchestrated. // Maybe not any longer?
- .withDeploymentSpec())
+ .withProjectId() // Need to keep this, as we have applications with deployment spec that shouldn't be orchestrated.
+ .withJobs())
.withChanges()
.asList().stream()
.filter(status -> ! hasExceededQuota(status.application().id().tenant()))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index 4d7b84be65e..dcdfea6e594 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -73,10 +73,12 @@ import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Nod
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.deploymentFailed;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.error;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.installationFailed;
+import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.invalidApplication;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.noTests;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.nodeAllocationFailure;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.reset;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.running;
+import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.success;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.testFailure;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.succeeded;
import static com.yahoo.vespa.hosted.controller.deployment.Step.copyVespaLogs;
@@ -242,6 +244,10 @@ public class InternalStepRunner implements StepRunner {
logger.log("Deployment failed with possibly transient error " + e.code() +
", will retry: " + e.getMessage());
return result;
+ case INTERNAL_SERVER_ERROR:
+ // Log only error code, to avoid exposing internal data in error message
+ logger.log("Deployment failed with possibly transient error " + e.code() + ", will retry");
+ return result;
case LOAD_BALANCER_NOT_READY:
case PARENT_HOST_NOT_READY:
logger.log(e.message()); // Consider splitting these messages in summary and details, on config server.
@@ -252,6 +258,8 @@ public class InternalStepRunner implements StepRunner {
? result
: Optional.of(nodeAllocationFailure);
case INVALID_APPLICATION_PACKAGE:
+ logger.log(WARNING, e.getMessage());
+ return Optional.of(invalidApplication);
case BAD_REQUEST:
logger.log(WARNING, e.getMessage());
return Optional.of(deploymentFailed);
@@ -759,14 +767,18 @@ public class InternalStepRunner implements StepRunner {
private Optional<RunStatus> report(RunId id, DualLogger logger) {
try {
+ boolean isRemoved = ! id.type().environment().isManuallyDeployed()
+ && ! controller.jobController().deploymentStatus(controller.applications().requireApplication(TenantAndApplicationId.from(id.application())))
+ .jobSteps().containsKey(id.job());
+
controller.jobController().active(id).ifPresent(run -> {
if (run.status() == reset)
return;
- if (run.hasFailed())
+ if (run.hasFailed() && ! isRemoved)
sendEmailNotification(run, logger);
- updateConsoleNotification(run);
+ updateConsoleNotification(run, isRemoved);
});
}
catch (IllegalStateException e) {
@@ -816,10 +828,10 @@ public class InternalStepRunner implements StepRunner {
.orElse(true);
}
- private void updateConsoleNotification(Run run) {
+ private void updateConsoleNotification(Run run, boolean isRemoved) {
NotificationSource source = NotificationSource.from(run.id());
Consumer<String> updater = msg -> controller.notificationsDb().setNotification(source, Notification.Type.deployment, Notification.Level.error, msg);
- switch (run.status()) {
+ switch (isRemoved ? success : run.status()) {
case aborted: return; // wait and see how the next run goes.
case noTests:
case running:
@@ -829,9 +841,12 @@ public class InternalStepRunner implements StepRunner {
case nodeAllocationFailure:
if ( ! run.id().type().environment().isTest()) updater.accept("could not allocate the requested capacity to your tenant. Please contact Vespa Cloud support.");
return;
- case deploymentFailed:
+ case invalidApplication:
updater.accept("invalid application configuration. Please review warnings and errors in the deployment job log.");
return;
+ case deploymentFailed:
+ updater.accept("failure processing application configuration. Please review warnings and errors in the deployment job log.");
+ return;
case installationFailed:
updater.accept("nodes were not able to deploy to the new configuration. Please check the Vespa log for errors, and contact Vespa Cloud support if unable to resolve these.");
return;
@@ -858,6 +873,7 @@ public class InternalStepRunner implements StepRunner {
case nodeAllocationFailure:
return run.id().type().isProduction() ? Optional.of(mails.nodeAllocationFailure(run.id(), recipients)) : Optional.empty();
case deploymentFailed:
+ case invalidApplication:
return Optional.of(mails.deploymentFailure(run.id(), recipients));
case installationFailed:
return Optional.of(mails.installationFailure(run.id(), recipients));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 0d4b2c658cf..edddaa5dee7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -5,9 +5,9 @@ import com.google.common.collect.ImmutableSortedMap;
import com.yahoo.component.Version;
import com.yahoo.component.VersionCompatibility;
import com.yahoo.concurrent.UncheckedTimeoutException;
-import com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.hosted.controller.Application;
@@ -52,6 +52,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
@@ -126,7 +127,7 @@ public class JobController {
this.curator = controller.curator();
this.logs = new BufferedLogStore(curator, controller.serviceRegistry().runDataStore());
this.cloud = controller.serviceRegistry().testerCloud();
- this.metric = new JobMetrics(controller.metric(), controller::system);
+ this.metric = new JobMetrics(controller.metric());
}
public TesterCloud cloud() { return cloud; }
@@ -320,15 +321,13 @@ public class JobController {
public List<ApplicationId> instances() {
return controller.applications().readable().stream()
.flatMap(application -> application.instances().values().stream())
- .map(Instance::id)
- .collect(toUnmodifiableList());
+ .map(Instance::id).toList();
}
/** Returns all job types which have been run for the given application. */
private List<JobType> jobs(ApplicationId id) {
return JobType.allIn(controller.zoneRegistry()).stream()
- .filter(type -> last(id, type).isPresent())
- .collect(toUnmodifiableList());
+ .filter(type -> last(id, type).isPresent()).toList();
}
/** Returns an immutable map of all known runs for the given application and job type. */
@@ -339,9 +338,8 @@ public class JobController {
/** Lists the start time of non-redeployment runs of the given job, in order of increasing age. */
public List<Instant> jobStarts(JobId id) {
return runs(id).descendingMap().values().stream()
- .filter(run -> ! run.isRedeployment())
- .map(Run::start)
- .collect(toUnmodifiableList());
+ .filter(run -> !run.isRedeployment())
+ .map(Run::start).toList();
}
/** Returns when given deployment last started deploying, falling back to time of deployment if it cannot be determined from job runs */
@@ -697,7 +695,7 @@ public class JobController {
controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
if ( ! application.get().instances().containsKey(id.instance()))
- application = controller.applications().withNewInstance(application, id);
+ application = controller.applications().withNewInstance(application, id, Tags.empty());
// TODO(mpolden): Enable for public CD once all tests have been updated
if (controller.system() != SystemName.PublicCd) {
controller.applications().validatePackage(applicationPackage, application.get());
@@ -767,8 +765,16 @@ public class JobController {
.filter(versions::contains) // Don't deploy versions that are no longer known.
.ifPresent(versions::add);
- if (versions.isEmpty())
- throw new IllegalStateException("no deployable platform version found in the system");
+ // Remove all versions that are older than the compile version.
+ versions.removeIf(version -> applicationPackage.compileVersion().map(version::isBefore).orElse(false));
+ if (versions.isEmpty()) {
+ // Fall back to the newest deployable version, if all the ones with normal confidence were too old.
+ Iterator<VespaVersion> descending = reversed(versionStatus.deployableVersions()).iterator();
+ if ( ! descending.hasNext())
+ throw new IllegalStateException("no deployable platform version found in the system");
+ else
+ versions.add(descending.next().versionNumber());
+ }
VersionCompatibility compatibility = controller.applications().versionCompatibility(id.applicationId());
List<Version> compatibleVersions = new ArrayList<>();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java
index 14fce806152..d1fa00d1c41 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java
@@ -19,6 +19,7 @@ public class JobMetrics {
public static final String nodeAllocationFailure = "deployment.nodeAllocationFailure";
public static final String endpointCertificateTimeout = "deployment.endpointCertificateTimeout";
public static final String deploymentFailure = "deployment.deploymentFailure";
+ public static final String invalidApplication = "deployment.invalidApplication";
public static final String convergenceFailure = "deployment.convergenceFailure";
public static final String testFailure = "deployment.testFailure";
public static final String noTests = "deployment.noTests";
@@ -27,11 +28,9 @@ public class JobMetrics {
public static final String success = "deployment.success";
private final Metric metric;
- private final Supplier<SystemName> system;
- public JobMetrics(Metric metric, Supplier<SystemName> system) {
+ public JobMetrics(Metric metric) {
this.metric = metric;
- this.system = system;
}
public void jobStarted(JobId id) {
@@ -51,18 +50,19 @@ public class JobMetrics {
}
static String valueOf(RunStatus status) {
- switch (status) {
- case nodeAllocationFailure: return nodeAllocationFailure;
- case endpointCertificateTimeout: return endpointCertificateTimeout;
- case deploymentFailed: return deploymentFailure;
- case installationFailed: return convergenceFailure;
- case testFailure: return testFailure;
- case noTests: return noTests;
- case error: return error;
- case aborted: return abort;
- case success: return success;
- default: throw new IllegalArgumentException("Unexpected run status '" + status + "'");
- }
+ return switch (status) {
+ case nodeAllocationFailure -> nodeAllocationFailure;
+ case endpointCertificateTimeout -> endpointCertificateTimeout;
+ case invalidApplication -> invalidApplication;
+ case deploymentFailed -> deploymentFailure;
+ case installationFailed -> convergenceFailure;
+ case testFailure -> testFailure;
+ case noTests -> noTests;
+ case error -> error;
+ case aborted -> abort;
+ case success -> success;
+ default -> throw new IllegalArgumentException("Unexpected run status '" + status + "'");
+ };
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
index 9ca634b19fd..aa727b602e1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
@@ -14,7 +14,10 @@ public enum RunStatus {
/** Deployment was rejected due node allocation failure. */
nodeAllocationFailure,
- /** Deployment of the real application was rejected. */
+ /** Deployment of the real application was rejected because the package is faulty. */
+ invalidApplication,
+
+ /** Deployment of the real application was rejected, for other reasons. */
deploymentFailed,
/** Deployment timed out waiting for endpoint certificate */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
index d0c901ccb36..b97fdde560e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.AliasTarget;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.DirectTarget;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
@@ -28,8 +29,8 @@ public class CreateRecords implements NameServiceRequest {
this.name = requireOneOf(Record::name, records);
this.type = requireOneOf(Record::type, records);
this.records = List.copyOf(Objects.requireNonNull(records, "records must be non-null"));
- if (type != Record.Type.ALIAS && type != Record.Type.TXT) {
- throw new IllegalArgumentException("Records of type " + type + "are not supported: " + records);
+ if (type != Record.Type.ALIAS && type != Record.Type.TXT && type != Record.Type.DIRECT) {
+ throw new IllegalArgumentException("Records of type " + type + " are not supported: " + records);
}
}
@@ -40,14 +41,18 @@ public class CreateRecords implements NameServiceRequest {
@Override
public void dispatchTo(NameService nameService) {
switch (type) {
- case ALIAS:
+ case ALIAS -> {
var targets = records.stream().map(Record::data).map(AliasTarget::unpack).collect(Collectors.toSet());
nameService.createAlias(name, targets);
- break;
- case TXT:
+ }
+ case DIRECT -> {
+ var targets = records.stream().map(Record::data).map(DirectTarget::unpack).collect(Collectors.toSet());
+ nameService.createDirect(name, targets);
+ }
+ case TXT -> {
var dataFields = records.stream().map(Record::data).collect(Collectors.toList());
nameService.createTxtRecords(name, dataFields);
- break;
+ }
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
index 9d2c7918252..57c83280b8b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.hosted.controller.api.integration.dns.AliasTarget;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.DirectTarget;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
@@ -55,6 +56,14 @@ public class NameServiceForwarder {
forward(new CreateRecords(records), priority);
}
+ /** Create or update a DIRECT record with given name and targets */
+ public void createDirect(RecordName name, Set<DirectTarget> targets, NameServiceQueue.Priority priority) {
+ var records = targets.stream()
+ .map(target -> new Record(Record.Type.DIRECT, name, target.pack()))
+ .collect(Collectors.toList());
+ forward(new CreateRecords(records), priority);
+ }
+
/** Create or update a TXT record with given name and data */
public void createTxt(RecordName name, List<RecordData> txtData, NameServiceQueue.Priority priority) {
var records = txtData.stream()
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
index f940d53fab3..6fa7473ad1e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.AliasTarget;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.DirectTarget;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
@@ -69,11 +70,11 @@ public class RemoveRecords implements NameServiceRequest {
.stream()
.filter(record -> {
// Records to remove must match both name and data fields
- String dataValue = record.data().asString();
- // If we're comparing an ALIAS record we have to unpack it to access the target name
- if (record.type() == Record.Type.ALIAS) {
- dataValue = AliasTarget.unpack(record.data()).name().value();
- }
+ String dataValue = switch (record.type()) {
+ case ALIAS -> AliasTarget.unpack(record.data()).name().value();
+ case DIRECT -> DirectTarget.unpack(record.data()).recordData().asString();
+ default -> record.data().asString();
+ };
return fqdn(dataValue).equals(fqdn(data.get().asString()));
})
.forEach(records::add);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
index 42821ea8fe2..0c8a50fa821 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
@@ -8,6 +8,7 @@ import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.archive.CuratorArchiveBucketDb;
+import com.yahoo.yolean.Exceptions;
import java.net.URI;
import java.time.Duration;
@@ -15,6 +16,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.logging.Level;
/**
* Updates archive URIs for tenants in all zones.
@@ -51,20 +53,28 @@ public class ArchiveUriUpdater extends ControllerMaintainer {
}
}
- tenantsByZone.forEach((zone, tenants) -> {
- Map<TenantName, URI> zoneArchiveUris = nodeRepository.getArchiveUris(zone);
- for (TenantName tenant : tenants) {
- archiveBucketDb.archiveUriFor(zone, tenant, true)
- .filter(uri -> !uri.equals(zoneArchiveUris.get(tenant)))
- .ifPresent(uri -> nodeRepository.setArchiveUri(zone, tenant, uri));
- }
+ int failures = 0;
+ for (ZoneId zone : tenantsByZone.keySet()) {
+ try {
+ Map<TenantName, URI> zoneArchiveUris = nodeRepository.getArchiveUris(zone);
+
+ for (TenantName tenant : tenantsByZone.get(zone)) {
+ archiveBucketDb.archiveUriFor(zone, tenant, true)
+ .filter(uri -> !uri.equals(zoneArchiveUris.get(tenant)))
+ .ifPresent(uri -> nodeRepository.setArchiveUri(zone, tenant, uri));
+ }
- zoneArchiveUris.keySet().stream()
- .filter(tenant -> !tenants.contains(tenant))
- .forEach(tenant -> nodeRepository.removeArchiveUri(zone, tenant));
- });
+ zoneArchiveUris.keySet().stream()
+ .filter(tenant -> !tenantsByZone.get(zone).contains(tenant))
+ .forEach(tenant -> nodeRepository.removeArchiveUri(zone, tenant));
+ } catch (Exception e) {
+ log.log(Level.WARNING, "Failed to update archive URI in " + zone + ". Retrying in " + interval() + ". Error: " +
+ Exceptions.toMessageString(e));
+ failures++;
+ }
+ }
- return 1.0;
+ return asSuccessFactor(tenantsByZone.size(), failures);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
index 16f62c37ffa..502b3b0188f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
@@ -55,6 +55,7 @@ public class OsUpgradeScheduler extends ControllerMaintainer {
private boolean upgradingToNewMajor(CloudName cloud) {
return controller().osVersionStatus().versionsIn(cloud).stream()
+ .filter(version -> !version.isEmpty()) // Ignore empty/unknown versions
.map(Version::getMajor)
.distinct()
.count() > 1;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
index b33a43a2031..37b06fea066 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
@@ -26,9 +26,8 @@ public class OutstandingChangeDeployer extends ControllerMaintainer {
protected double maintain() {
double ok = 0, total = 0;
for (Application application : ApplicationList.from(controller().applications().readable())
- .withProductionDeployment()
.withProjectId()
- .withDeploymentSpec()
+ .withJobs()
.asList())
try {
++total;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index 037dacfcac9..d49cb244e47 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -70,7 +70,8 @@ public class Upgrader extends ControllerMaintainer {
private DeploymentStatusList deploymentStatuses(VersionStatus versionStatus) {
return controller().jobController().deploymentStatuses(ApplicationList.from(controller().applications().readable())
- .withProjectId(),
+ .withProjectId()
+ .withJobs(),
versionStatus);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index 5aa847f648a..37c45f38e36 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -9,6 +9,7 @@ import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.ArrayTraverser;
@@ -97,6 +98,7 @@ public class ApplicationSerializer {
// Instance fields
private static final String instanceNameField = "instanceName";
+ private static final String tagsField = "tags";
private static final String deploymentsField = "deployments";
private static final String deploymentJobsField = "deploymentJobs"; // TODO jonmv: clean up serialisation format
private static final String assignedRotationsField = "assignedRotations";
@@ -184,6 +186,7 @@ public class ApplicationSerializer {
for (Instance instance : application.instances().values()) {
Cursor instanceObject = array.addObject();
instanceObject.setString(instanceNameField, instance.name().value());
+ instanceObject.setString(tagsField, instance.tags().asString());
deploymentsToSlime(instance.deployments().values(), instanceObject.setArray(deploymentsField));
toSlime(instance.jobPauses(), instanceObject.setObject(deploymentJobsField));
assignedRotationsToSlime(instance.rotations(), instanceObject);
@@ -380,12 +383,14 @@ public class ApplicationSerializer {
List<Instance> instances = new ArrayList<>();
field.traverse((ArrayTraverser) (name, object) -> {
InstanceName instanceName = InstanceName.from(object.field(instanceNameField).asString());
- List<Deployment> deployments = deploymentsFromSlime(object.field(deploymentsField), id.instance(instanceName));
+ Tags tags = Tags.fromString(object.field(tagsField).asString());
+ List < Deployment > deployments = deploymentsFromSlime(object.field(deploymentsField), id.instance(instanceName));
Map<JobType, Instant> jobPauses = jobPausesFromSlime(object.field(deploymentJobsField));
List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(object);
RotationStatus rotationStatus = rotationStatusFromSlime(object);
Change change = changeFromSlime(object.field(deployingField));
instances.add(new Instance(id.instance(instanceName),
+ tags,
deployments,
jobPauses,
assignedRotations,
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
index fcc6d99aec2..49d108d08df 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
@@ -34,6 +34,7 @@ import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.deploymentF
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.endpointCertificateTimeout;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.error;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.installationFailed;
+import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.invalidApplication;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.noTests;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.nodeAllocationFailure;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.reset;
@@ -329,39 +330,38 @@ class RunSerializer {
}
static String valueOf(RunStatus status) {
- switch (status) {
- case running : return "running";
- case nodeAllocationFailure : return "nodeAllocationFailure";
- case endpointCertificateTimeout : return "endpointCertificateTimeout";
- case deploymentFailed : return "deploymentFailed";
- case installationFailed : return "installationFailed";
- case testFailure : return "testFailure";
- case noTests : return "noTests";
- case error : return "error";
- case success : return "success";
- case aborted : return "aborted";
- case reset : return "reset";
-
- default: throw new AssertionError("No value defined for '" + status + "'!");
- }
+ return switch (status) {
+ case running -> "running";
+ case nodeAllocationFailure -> "nodeAllocationFailure";
+ case endpointCertificateTimeout -> "endpointCertificateTimeout";
+ case deploymentFailed -> "deploymentFailed";
+ case invalidApplication -> "invalidApplication";
+ case installationFailed -> "installationFailed";
+ case testFailure -> "testFailure";
+ case noTests -> "noTests";
+ case error -> "error";
+ case success -> "success";
+ case aborted -> "aborted";
+ case reset -> "reset";
+ };
}
static RunStatus runStatusOf(String status) {
- switch (status) {
- case "running" : return running;
- case "nodeAllocationFailure" : return nodeAllocationFailure;
- case "endpointCertificateTimeout" : return endpointCertificateTimeout;
- case "deploymentFailed" : return deploymentFailed;
- case "installationFailed" : return installationFailed;
- case "noTests" : return noTests;
- case "testFailure" : return testFailure;
- case "error" : return error;
- case "success" : return success;
- case "aborted" : return aborted;
- case "reset" : return reset;
-
- default: throw new IllegalArgumentException("No run status defined by '" + status + "'!");
- }
+ return switch (status) {
+ case "running" -> running;
+ case "nodeAllocationFailure" -> nodeAllocationFailure;
+ case "endpointCertificateTimeout" -> endpointCertificateTimeout;
+ case "deploymentFailed" -> deploymentFailed;
+ case "invalidApplication" -> invalidApplication;
+ case "installationFailed" -> installationFailed;
+ case "noTests" -> noTests;
+ case "testFailure" -> testFailure;
+ case "error" -> error;
+ case "success" -> success;
+ case "aborted" -> aborted;
+ case "reset" -> reset;
+ default -> throw new IllegalArgumentException("No run status defined by '" + status + "'!");
+ };
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index e8fe7b7d5f0..81fb72e19fd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -22,6 +22,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeResources;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
@@ -2035,7 +2036,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (controller.applications().getApplication(applicationId).isEmpty())
createApplication(tenantName, applicationName, request);
- controller.applications().createInstance(applicationId.instance(instanceName));
+ controller.applications().createInstance(applicationId.instance(instanceName), Tags.empty());
Slime slime = new Slime();
toSlime(applicationId.instance(instanceName), slime.setObject(), request);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index 8c601f8c678..592fbd0e856 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -227,20 +227,18 @@ class JobControllerApiHandlerHelper {
}
private static String nameOf(RunStatus status) {
- switch (status) {
- case reset: // This means the run will reset and keep running.
- case running: return "running";
- case aborted: return "aborted";
- case error: return "error";
- case testFailure: return "testFailure";
- case noTests: return "noTests";
- case endpointCertificateTimeout: return "endpointCertificateTimeout";
- case nodeAllocationFailure: return "nodeAllocationFailure";
- case installationFailed: return "installationFailed";
- case deploymentFailed: return "deploymentFailed";
- case success: return "success";
- default: throw new IllegalArgumentException("Unexpected status '" + status + "'");
- }
+ return switch (status) {
+ case reset, running -> "running";
+ case aborted -> "aborted";
+ case error -> "error";
+ case testFailure -> "testFailure";
+ case noTests -> "noTests";
+ case endpointCertificateTimeout -> "endpointCertificateTimeout";
+ case nodeAllocationFailure -> "nodeAllocationFailure";
+ case installationFailed -> "installationFailed";
+ case invalidApplication, deploymentFailed -> "deploymentFailed";
+ case success -> "success";
+ };
}
/**
@@ -440,7 +438,7 @@ class JobControllerApiHandlerHelper {
runObject.setString("url", baseUriForJob.resolve(baseUriForJob.getPath() + "/run/" + run.id().number()).toString());
runObject.setLong("start", run.start().toEpochMilli());
run.end().ifPresent(end -> runObject.setLong("end", end.toEpochMilli()));
- runObject.setString("status", run.status().name());
+ runObject.setString("status", nameOf(run.status()));
run.reason().ifPresent(reason -> runObject.setString("reason", reason));
toSlime(runObject.setObject("versions"), run.versions(), application);
Cursor runStepsArray = runObject.setArray("steps");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
index 62b48307f37..999cf63abf6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
@@ -22,6 +22,7 @@ import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus;
import com.yahoo.vespa.hosted.controller.deployment.Run;
+import com.yahoo.vespa.hosted.controller.deployment.RunStatus;
import com.yahoo.vespa.hosted.controller.deployment.Versions;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponses;
import com.yahoo.vespa.hosted.controller.restapi.application.EmptyResponse;
@@ -121,7 +122,7 @@ public class DeploymentApiHandler extends ThreadedHttpRequestHandler {
Cursor applicationObject = failingArray.addObject();
toSlime(applicationObject, run.id().application(), request);
applicationObject.setString("failing", run.id().type().jobName());
- applicationObject.setString("status", run.status().name());
+ applicationObject.setString("status", nameOf(run.status()));
}
var statusByInstance = deploymentStatuses.asList().stream()
@@ -224,7 +225,7 @@ public class DeploymentApiHandler extends ThreadedHttpRequestHandler {
runObject.setLong("number", run.id().number());
runObject.setLong("start", run.start().toEpochMilli());
run.end().ifPresent(end -> runObject.setLong("end", end.toEpochMilli()));
- runObject.setString("status", run.status().name());
+ runObject.setString("status", nameOf(run.status()));
}
private void toSlime(Cursor object, ApplicationId id, HttpRequest request) {
@@ -247,6 +248,21 @@ public class DeploymentApiHandler extends ThreadedHttpRequestHandler {
return upgradePolicy.name();
}
+ public static String nameOf(RunStatus status) {
+ return switch (status) {
+ case reset, running -> "running";
+ case aborted -> "aborted";
+ case error -> "error";
+ case testFailure -> "testFailure";
+ case noTests -> "noTests";
+ case endpointCertificateTimeout -> "endpointCertificateTimeout";
+ case nodeAllocationFailure -> "nodeAllocationFailure";
+ case installationFailed -> "installationFailed";
+ case invalidApplication, deploymentFailed -> "deploymentFailed";
+ case success -> "success";
+ };
+ }
+
private static class RunInfo {
final Run run;
final boolean upgrade;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
index f76d04c9e1d..fc0badae9ea 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
@@ -12,11 +12,13 @@ import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
import com.yahoo.vespa.hosted.controller.api.integration.dns.AliasTarget;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.DirectTarget;
import com.yahoo.vespa.hosted.controller.api.integration.dns.LatencyAliasTarget;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
import com.yahoo.vespa.hosted.controller.api.integration.dns.WeightedAliasTarget;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.WeightedDirectTarget;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
@@ -34,6 +36,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -169,9 +172,16 @@ public class RoutingPolicies {
// Create a weighted ALIAS per region, pointing to all zones within the same region
Collection<RegionEndpoint> regionEndpoints = computeRegionEndpoints(policies, inactiveZones);
regionEndpoints.forEach(regionEndpoint -> {
- controller.nameServiceForwarder().createAlias(RecordName.from(regionEndpoint.target().name().value()),
- Collections.unmodifiableSet(regionEndpoint.zoneTargets()),
- Priority.normal);
+ if ( ! regionEndpoint.zoneAliasTargets().isEmpty()) {
+ controller.nameServiceForwarder().createAlias(RecordName.from(regionEndpoint.target().name().value()),
+ regionEndpoint.zoneAliasTargets(),
+ Priority.normal);
+ }
+ if ( ! regionEndpoint.zoneDirectTargets().isEmpty()) {
+ controller.nameServiceForwarder().createDirect(RecordName.from(regionEndpoint.target().name().value()),
+ regionEndpoint.zoneDirectTargets(),
+ Priority.normal);
+ }
});
// Create global latency-based ALIAS pointing to each per-region weighted ALIAS
@@ -203,7 +213,7 @@ public class RoutingPolicies {
private Collection<RegionEndpoint> computeRegionEndpoints(List<RoutingPolicy> policies, Set<ZoneId> inactiveZones) {
Map<Endpoint, RegionEndpoint> endpoints = new LinkedHashMap<>();
for (var policy : policies) {
- if (policy.dnsZone().isEmpty()) continue;
+ if (policy.dnsZone().isEmpty() && policy.canonicalName().isPresent()) continue;
if (controller.zoneRegistry().routingMethod(policy.id().zone()) != RoutingMethod.exclusive) continue;
Endpoint endpoint = policy.regionEndpointIn(controller.system(), RoutingMethod.exclusive);
var zonePolicy = db.readZoneRoutingPolicy(policy.id().zone());
@@ -219,9 +229,11 @@ public class RoutingPolicies {
if (policy.canonicalName().isPresent()) {
var weightedTarget = new WeightedAliasTarget(
policy.canonicalName().get(), policy.dnsZone().get(), policy.id().zone(), weight);
- regionEndpoint.zoneTargets().add(weightedTarget);
+ regionEndpoint.add(weightedTarget);
} else {
- // TODO (freva): Add direct weighted record
+ var weightedTarget = new WeightedDirectTarget(
+ RecordData.from(policy.ipAddress().get()), policy.id().zone(), weight);
+ regionEndpoint.add(weightedTarget);
}
}
return endpoints.values();
@@ -237,8 +249,8 @@ public class RoutingPolicies {
if (routingTable.isEmpty()) return;
Application application = controller.applications().requireApplication(routingTable.keySet().iterator().next().application());
- Map<Endpoint, Set<AliasTarget>> targetsByEndpoint = new LinkedHashMap<>();
- Map<Endpoint, Set<AliasTarget>> inactiveTargetsByEndpoint = new LinkedHashMap<>();
+ Map<Endpoint, Set<Target>> targetsByEndpoint = new LinkedHashMap<>();
+ Map<Endpoint, Set<Target>> inactiveTargetsByEndpoint = new LinkedHashMap<>();
for (Map.Entry<RoutingId, List<RoutingPolicy>> routeEntry : routingTable.entrySet()) {
RoutingId routingId = routeEntry.getKey();
EndpointList endpoints = controller.routing().declaredEndpointsOf(application)
@@ -253,17 +265,15 @@ public class RoutingPolicies {
for (var policy : routeEntry.getValue()) {
for (var target : endpoint.targets()) {
if (!policy.appliesTo(target.deployment())) continue;
- if (policy.dnsZone().isEmpty()) continue; // Does not support ALIAS records
- if (policy.canonicalName().isEmpty()) continue; // TODO (freva): Handle DIRECT records
+ if (policy.dnsZone().isEmpty() && policy.canonicalName().isPresent()) continue; // Does not support ALIAS records
ZoneRoutingPolicy zonePolicy = db.readZoneRoutingPolicy(policy.id().zone());
- WeightedAliasTarget weightedAliasTarget = new WeightedAliasTarget(policy.canonicalName().get(), policy.dnsZone().get(),
- target.deployment().zoneId(), target.weight());
- Set<AliasTarget> activeTargets = targetsByEndpoint.computeIfAbsent(endpoint, (k) -> new LinkedHashSet<>());
- Set<AliasTarget> inactiveTargets = inactiveTargetsByEndpoint.computeIfAbsent(endpoint, (k) -> new LinkedHashSet<>());
+
+ Set<Target> activeTargets = targetsByEndpoint.computeIfAbsent(endpoint, (k) -> new LinkedHashSet<>());
+ Set<Target> inactiveTargets = inactiveTargetsByEndpoint.computeIfAbsent(endpoint, (k) -> new LinkedHashSet<>());
if (isConfiguredOut(zonePolicy, policy, inactiveZones)) {
- inactiveTargets.add(weightedAliasTarget);
+ inactiveTargets.add(Target.weighted(policy, target));
} else {
- activeTargets.add(weightedAliasTarget);
+ activeTargets.add(Target.weighted(policy, target));
}
}
}
@@ -273,11 +283,11 @@ public class RoutingPolicies {
// the ALIAS records would cause the application endpoint to stop resolving entirely (NXDOMAIN).
for (var kv : targetsByEndpoint.entrySet()) {
Endpoint endpoint = kv.getKey();
- Set<AliasTarget> activeTargets = kv.getValue();
+ Set<Target> activeTargets = kv.getValue();
if (!activeTargets.isEmpty()) {
continue;
}
- Set<AliasTarget> inactiveTargets = inactiveTargetsByEndpoint.get(endpoint);
+ Set<Target> inactiveTargets = inactiveTargetsByEndpoint.get(endpoint);
activeTargets.addAll(inactiveTargets);
inactiveTargets.clear();
}
@@ -287,9 +297,21 @@ public class RoutingPolicies {
.map(DeploymentId::zoneId)
.findFirst()
.get();
- nameServiceForwarderIn(targetZone).createAlias(RecordName.from(applicationEndpoint.dnsName()),
- targets,
- Priority.normal);
+ Set<AliasTarget> aliasTargets = new LinkedHashSet<>();
+ Set<DirectTarget> directTargets = new LinkedHashSet<>();
+ for (Target target : targets) {
+ if (target.aliasOrDirectTarget() instanceof AliasTarget at) aliasTargets.add(at);
+ else directTargets.add((DirectTarget) target.aliasOrDirectTarget());
+ }
+
+ if ( ! aliasTargets.isEmpty()) {
+ nameServiceForwarderIn(targetZone).createAlias(
+ RecordName.from(applicationEndpoint.dnsName()), aliasTargets, Priority.normal);
+ }
+ if ( ! directTargets.isEmpty()) {
+ nameServiceForwarderIn(targetZone).createDirect(
+ RecordName.from(applicationEndpoint.dnsName()), directTargets, Priority.normal);
+ }
});
inactiveTargetsByEndpoint.forEach((applicationEndpoint, targets) -> {
ZoneId targetZone = applicationEndpoint.targets().stream()
@@ -298,9 +320,9 @@ public class RoutingPolicies {
.findFirst()
.get();
targets.forEach(target -> {
- nameServiceForwarderIn(targetZone).removeRecords(Record.Type.ALIAS,
+ nameServiceForwarderIn(targetZone).removeRecords(target.type(),
RecordName.from(applicationEndpoint.dnsName()),
- RecordData.fqdn(target.name().value()),
+ target.data(),
Priority.normal);
});
});
@@ -317,7 +339,8 @@ public class RoutingPolicies {
if (loadBalancer.hostname().isEmpty() && loadBalancer.ipAddress().isEmpty()) continue;
var policyId = new RoutingPolicyId(loadBalancer.application(), loadBalancer.cluster(), allocation.deployment.zoneId());
var existingPolicy = policies.get(policyId);
- var newPolicy = new RoutingPolicy(policyId, loadBalancer.hostname(), loadBalancer.ipAddress(), loadBalancer.dnsZone(),
+ var dnsZone = loadBalancer.ipAddress().isPresent() ? Optional.of("ignored") : loadBalancer.dnsZone();
+ var newPolicy = new RoutingPolicy(policyId, loadBalancer.hostname(), loadBalancer.ipAddress(), dnsZone,
allocation.instanceEndpointsOf(loadBalancer),
allocation.applicationEndpointsOf(loadBalancer),
new RoutingPolicy.Status(isActive(loadBalancer), RoutingStatus.DEFAULT));
@@ -407,7 +430,10 @@ public class RoutingPolicies {
RecordData.fqdn(policy.canonicalName().get().value()),
Priority.normal);
} else {
- // TODO (freva): Remove DIRECT records
+ forwarder.removeRecords(Record.Type.DIRECT,
+ RecordName.from(endpoint.dnsName()),
+ RecordData.from(policy.ipAddress().get()),
+ Priority.normal);
}
}
}
@@ -460,22 +486,23 @@ public class RoutingPolicies {
private static class RegionEndpoint {
private final LatencyAliasTarget target;
- private final Set<WeightedAliasTarget> zoneTargets = new LinkedHashSet<>();
+ private final Set<WeightedAliasTarget> zoneAliasTargets = new LinkedHashSet<>();
+ private final Set<WeightedDirectTarget> zoneDirectTargets = new LinkedHashSet<>();
public RegionEndpoint(LatencyAliasTarget target) {
this.target = Objects.requireNonNull(target);
}
- public LatencyAliasTarget target() {
- return target;
- }
+ public LatencyAliasTarget target() { return target; }
+ public Set<AliasTarget> zoneAliasTargets() { return Collections.unmodifiableSet(zoneAliasTargets); }
+ public Set<DirectTarget> zoneDirectTargets() { return Collections.unmodifiableSet(zoneDirectTargets); }
- public Set<WeightedAliasTarget> zoneTargets() {
- return zoneTargets;
- }
+ public void add(WeightedAliasTarget target) { zoneAliasTargets.add(target); }
+ public void add(WeightedDirectTarget target) { zoneDirectTargets.add(target); }
public boolean active() {
- return zoneTargets.stream().anyMatch(target -> target.weight() > 0);
+ return zoneAliasTargets.stream().anyMatch(target -> target.weight() > 0) ||
+ zoneDirectTargets.stream().anyMatch(target -> target.weight() > 0);
}
@Override
@@ -573,6 +600,20 @@ public class RoutingPolicies {
};
}
+ /** Denotes record data (record rhs) of either an ALIAS or a DIRECT target */
+ private record Target(Record.Type type, RecordData data, Object aliasOrDirectTarget) {
+ static Target weighted(RoutingPolicy policy, Endpoint.Target endpointTarget) {
+ if (policy.ipAddress().isPresent()) {
+ var wt = new WeightedDirectTarget(RecordData.from(policy.ipAddress().get()),
+ endpointTarget.deployment().zoneId(), endpointTarget.weight());
+ return new Target(Record.Type.DIRECT, wt.recordData(), wt);
+ }
+ var wt = new WeightedAliasTarget(policy.canonicalName().get(), policy.dnsZone().get(),
+ endpointTarget.deployment().zoneId(), endpointTarget.weight());
+ return new Target(Record.Type.ALIAS, RecordData.fqdn(wt.name().value()), wt);
+ }
+ }
+
/** A {@link NameServiceForwarder} that does nothing. Used in zones where no explicit DNS updates are needed */
private static class NameServiceDiscarder extends NameServiceForwarder {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
index 46bcd3b85c0..127e2828732 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
@@ -124,12 +124,12 @@ public record VespaVersion(Version version,
/** We don't have sufficient evidence that this version is working */
low,
- /** This version works, but we want users to stop using it */
- legacy,
-
/** We have sufficient evidence that this version is working */
normal,
-
+
+ /** This version works, but we want users to stop using it */
+ legacy,
+
/** We have overwhelming evidence that this version is working */
high;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index c4e138c4d18..cd3d6ca7531 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -912,38 +912,6 @@ public class ControllerTest {
}
@Test
- void testDeployWithGlobalEndpointsInGcp() {
- tester.controllerTester().zoneRegistry().setZones(
- ZoneApiMock.fromId("test.us-west-1"),
- ZoneApiMock.fromId("staging.us-west-1"),
- ZoneApiMock.newBuilder().with(CloudName.GCP).withId("prod.gcp-us-east1-b").build()
- );
- var context = tester.newDeploymentContext();
- var applicationPackage = new ApplicationPackageBuilder()
- .region("gcp-us-east1-b")
- .endpoint("default", "default") // Contains all regions by default
- .build();
-
- try {
- context.submit(applicationPackage);
- fail("Expected exception");
- } catch (IllegalArgumentException e) {
- assertEquals("Endpoint 'default' in instance 'default' contains a Google Cloud region (gcp-us-east1-b), which is not yet supported", e.getMessage());
- }
-
- var applicationPackage2 = new ApplicationPackageBuilder()
- .region("gcp-us-east1-b")
- .endpoint("gcp", "default", "gcp-us-east1-b")
- .build();
- try {
- context.submit(applicationPackage2);
- fail("Expected exception");
- } catch (IllegalArgumentException e) {
- assertEquals("Endpoint 'gcp' in instance 'default' contains a Google Cloud region (gcp-us-east1-b), which is not yet supported", e.getMessage());
- }
- }
-
- @Test
void testDeployWithoutSourceRevision() {
var context = tester.newDeploymentContext();
var applicationPackage = new ApplicationPackageBuilder()
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 5380cf4ee27..0c908b1f035 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneApi;
@@ -370,7 +371,7 @@ public final class ControllerTester {
public Application createApplication(String tenant, String applicationName, String instanceName) {
Application application = createApplication(tenant, applicationName);
- controller().applications().createInstance(application.id().instance(instanceName));
+ controller().applications().createInstance(application.id().instance(instanceName), Tags.empty());
return application;
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
index 3163c8b6439..db45933f498 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
@@ -8,6 +8,7 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
@@ -63,8 +64,8 @@ public class DeploymentQuotaCalculatorTest {
var existing_dev_deployment = new Application(TenantAndApplicationId.from(ApplicationId.defaultId()), Instant.EPOCH, DeploymentSpec.empty, ValidationOverrides.empty, Optional.empty(),
Optional.empty(), Optional.empty(), OptionalInt.empty(), new ApplicationMetrics(1, 1), Set.of(), OptionalLong.empty(), RevisionHistory.empty(),
- List.of(new Instance(ApplicationId.defaultId()).withNewDeployment(ZoneId.from(Environment.dev, RegionName.defaultName()),
- RevisionId.forProduction(1), Version.emptyVersion, Instant.EPOCH, Map.of(), QuotaUsage.create(0.53d))));
+ List.of(new Instance(ApplicationId.defaultId(), Tags.empty()).withNewDeployment(ZoneId.from(Environment.dev, RegionName.defaultName()),
+ RevisionId.forProduction(1), Version.emptyVersion, Instant.EPOCH, Map.of(), QuotaUsage.create(0.53d))));
Quota calculated = DeploymentQuotaCalculator.calculate(Quota.unlimited().withBudget(2), List.of(existing_dev_deployment), ApplicationId.defaultId(), ZoneId.defaultId(),
DeploymentSpec.fromXml(
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
index 9a2d9eddba7..274cf3c5867 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyAlgorithm;
@@ -100,7 +101,7 @@ public class EndpointCertificatesTest {
return x509CertificateBuilder.build();
}
- private final Instance testInstance = new Instance(ApplicationId.defaultId());
+ private final Instance testInstance = new Instance(ApplicationId.defaultId(), Tags.empty());
private final String testKeyName = "testKeyName";
private final String testCertName = "testCertName";
private ZoneId testZone;
@@ -234,7 +235,7 @@ public class EndpointCertificatesTest {
@Test
void includes_application_endpoint_when_declared() {
- Instance instance = new Instance(ApplicationId.from("t1", "a1", "default"));
+ Instance instance = new Instance(ApplicationId.from("t1", "a1", "default"), Tags.empty());
ZoneId zone1 = ZoneId.from(Environment.prod, RegionName.from("aws-us-east-1c"));
ZoneId zone2 = ZoneId.from(Environment.prod, RegionName.from("aws-us-west-2a"));
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
index d8cef45f124..73630488969 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
@@ -8,12 +8,15 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
@@ -118,9 +121,17 @@ public class DeploymentTriggerTest {
tester.triggerJobs();
app.assertRunning(productionUsWest1);
+ tester.configServer().throwOnNextPrepare(new ConfigServerException(ErrorCode.INVALID_APPLICATION_PACKAGE, "nope", "bah"));
+ tester.runner().run();
+ assertEquals(RunStatus.invalidApplication, tester.jobs().last(app.instanceId(), productionUsWest1).get().status());
+ tester.triggerJobs();
+ app.assertNotRunning(productionUsWest1);
+
// production-us-west-1 fails, but the app loses its projectId, and the job isn't retried.
+ app.submit(applicationPackage).runJob(systemTest).runJob(stagingTest).triggerJobs();
tester.applications().lockApplicationOrThrow(app.application().id(), locked ->
tester.applications().store(locked.withProjectId(OptionalLong.empty())));
+
app.timeOutConvergence(productionUsWest1);
tester.triggerJobs();
assertEquals(0, tester.jobs().active().size(), "Job is not triggered when no projectId is present");
@@ -986,7 +997,7 @@ public class DeploymentTriggerTest {
@Test
void testUserInstancesNotInDeploymentSpec() {
var app = tester.newDeploymentContext();
- tester.controller().applications().createInstance(app.application().id().instance("user"));
+ tester.controller().applications().createInstance(app.application().id().instance("user"), Tags.empty());
app.submit().deploy();
}
@@ -2700,6 +2711,54 @@ public class DeploymentTriggerTest {
}
@Test
+ void testBrokenApplication() {
+ DeploymentContext app = tester.newDeploymentContext();
+ app.submit().runJob(systemTest).failDeployment(stagingTest).failDeployment(stagingTest);
+ tester.clock().advance(Duration.ofDays(31));
+ tester.outstandingChangeDeployer().run();
+ assertEquals(OptionalLong.empty(), app.application().projectId());
+
+ app.assertNotRunning(stagingTest);
+ tester.triggerJobs();
+ app.assertNotRunning(stagingTest);
+ assertEquals(4, app.deploymentStatus().jobsToRun().size());
+
+ app.submit().runJob(systemTest).failDeployment(stagingTest);
+ tester.clock().advance(Duration.ofDays(20));
+ app.submit().runJob(systemTest).failDeployment(stagingTest);
+ tester.clock().advance(Duration.ofDays(20));
+ tester.outstandingChangeDeployer().run();
+ assertEquals(OptionalLong.of(1000), app.application().projectId());
+ tester.clock().advance(Duration.ofDays(20));
+ tester.outstandingChangeDeployer().run();
+ assertEquals(OptionalLong.empty(), app.application().projectId());
+
+ app.assertNotRunning(stagingTest);
+ tester.triggerJobs();
+ app.assertNotRunning(stagingTest);
+ assertEquals(4, app.deploymentStatus().jobsToRun().size());
+
+ app.submit().runJob(systemTest).runJob(stagingTest).failDeployment(productionUsCentral1);
+ tester.clock().advance(Duration.ofDays(31));
+ tester.outstandingChangeDeployer().run();
+ assertEquals(OptionalLong.empty(), app.application().projectId());
+
+ app.assertNotRunning(productionUsCentral1);
+ tester.triggerJobs();
+ app.assertNotRunning(productionUsCentral1);
+ assertEquals(3, app.deploymentStatus().jobsToRun().size());
+
+ app.submit().runJob(systemTest).runJob(stagingTest).timeOutConvergence(productionUsCentral1);
+ tester.clock().advance(Duration.ofDays(31));
+ tester.outstandingChangeDeployer().run();
+ assertEquals(OptionalLong.of(1000), app.application().projectId());
+
+ app.assertNotRunning(productionUsCentral1);
+ tester.triggerJobs();
+ app.assertRunning(productionUsCentral1);
+ }
+
+ @Test
void testJobNames() {
ZoneRegistryMock zones = new ZoneRegistryMock(SystemName.main);
List<ZoneApi> existing = new ArrayList<>(zones.zones().all().zones());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 849261d5ae4..40217890351 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -6,6 +6,7 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.SlimeUtils;
@@ -127,18 +128,21 @@ public class ApplicationSerializerTest {
RevisionHistory revisions = RevisionHistory.ofRevisions(List.of(applicationVersion1),
Map.of(new JobId(id1, DeploymentContext.productionUsEast3), List.of(applicationVersion2)));
- List<Instance> instances = List.of(new Instance(id1,
- deployments,
- Map.of(DeploymentContext.systemTest, Instant.ofEpochMilli(333)),
- List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of("us-west-1"))),
- rotationStatus,
- Change.of(new Version("6.1"))),
- new Instance(id3,
- List.of(),
- Map.of(),
- List.of(),
- RotationStatus.EMPTY,
- Change.of(Version.fromString("6.7")).withPin()));
+ List<Instance> instances =
+ List.of(new Instance(id1,
+ Tags.fromString("tag1 tag2"),
+ deployments,
+ Map.of(DeploymentContext.systemTest, Instant.ofEpochMilli(333)),
+ List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of("us-west-1"))),
+ rotationStatus,
+ Change.of(new Version("6.1"))),
+ new Instance(id3,
+ Tags.empty(),
+ List.of(),
+ Map.of(),
+ List.of(),
+ RotationStatus.EMPTY,
+ Change.of(Version.fromString("6.7")).withPin()));
Application original = new Application(TenantAndApplicationId.from(id1),
Instant.now().truncatedTo(ChronoUnit.MILLIS),
@@ -177,6 +181,9 @@ public class ApplicationSerializerTest {
assertEquals(original.revisions().production(), serialized.revisions().production());
assertEquals(original.revisions().development(), serialized.revisions().development());
+ assertEquals(original.require(id1.instance()).tags(), serialized.require(id1.instance()).tags());
+ assertEquals(original.require(id3.instance()).tags(), serialized.require(id3.instance()).tags());
+
assertEquals(original.deploymentSpec().xmlForm(), serialized.deploymentSpec().xmlForm());
assertEquals(original.validationOverrides().xmlForm(), serialized.validationOverrides().xmlForm());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java
index 6f0a36690ed..c42d5621a46 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java
@@ -7,6 +7,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.LatencyAliasTarget;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.WeightedDirectTarget;
import com.yahoo.vespa.hosted.controller.dns.CreateRecord;
import com.yahoo.vespa.hosted.controller.dns.CreateRecords;
import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue;
@@ -38,8 +39,16 @@ public class NameServiceQueueSerializerTest {
new Record(Record.Type.ALIAS, RecordName.from("alias.example.com"),
new LatencyAliasTarget(HostName.of("alias2"),
"dns-zone-02",
- ZoneId.from("prod", "us-north-2")).pack()))
+ ZoneId.from("prod", "us-north-2")).pack()),
+ new Record(Record.Type.ALIAS, RecordName.from("alias.example.com"),
+ new LatencyAliasTarget(HostName.of("alias2"),
+ "ignored",
+ ZoneId.from("prod", "us-south-1")).pack()))
),
+ new CreateRecords(List.of(new Record(Record.Type.DIRECT, RecordName.from("direct.example.com"),
+ new WeightedDirectTarget(RecordData.from("10.1.2.3"),
+ ZoneId.from("prod", "us-north-1"),
+ 100).pack()))),
new RemoveRecords(record1.type(), record1.name()),
new RemoveRecords(record2.type(), record2.data())
);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
index 71e3607983c..6555277b06b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
@@ -39,6 +39,7 @@ import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.tes
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.applicationPackage;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.deploymentFailed;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.installationFailed;
+import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.invalidApplication;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.running;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -87,7 +88,7 @@ public class JobControllerApiHandlerHelperTest {
// us-east-3 eats the deployment failure and fails before deployment, while us-west-1 fails after.
tester.configServer().throwOnNextPrepare(new ConfigServerException(INVALID_APPLICATION_PACKAGE, "ERROR!", "Failed to deploy application"));
tester.runner().run();
- assertEquals(deploymentFailed, tester.jobs().last(app.instanceId(), productionUsEast3).get().status());
+ assertEquals(invalidApplication, tester.jobs().last(app.instanceId(), productionUsEast3).get().status());
tester.runner().run();
tester.clock().advance(Duration.ofHours(4).plusSeconds(1));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
index 3a539987443..2f4b4154c08 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.athenz;
import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
@@ -25,8 +26,8 @@ public class AthenzApiTest extends ControllerContainerTest {
controllerTester.createTenant("sandbox", AthenzApiHandler.sandboxDomainIn(tester.controller().system()), 123L);
controllerTester.createApplication("sandbox", "app", "default");
- tester.controller().applications().createInstance(ApplicationId.from("sandbox", "app", hostedOperator.getName()));
- tester.controller().applications().createInstance(ApplicationId.from("sandbox", "app", defaultUser.getName()));
+ tester.controller().applications().createInstance(ApplicationId.from("sandbox", "app", hostedOperator.getName()), Tags.empty());
+ tester.controller().applications().createInstance(ApplicationId.from("sandbox", "app", defaultUser.getName()), Tags.empty());
controllerTester.createApplication("sandbox", "opp", "default");
controllerTester.createTenant("tenant1", "domain1", 123L);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json
index 7a8eef983f1..68ac684b0fb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json
@@ -163,6 +163,8 @@
"targetVersion": true,
"upgradeBudget": "PT24H",
"scheduledAt": 1234,
+ "nextVersion": "8.2.1.20211228",
+ "nextScheduledAt": 1640743200000,
"cloud": "cloud2",
"nodes": [ ]
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
index 2761c736e11..29834863976 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
@@ -370,11 +370,16 @@ public class RoutingPoliciesTest {
List<String> expectedRecords = List.of("c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
"c0.app1.tenant1.gcp-us-south1-b.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1.w.vespa-app.cloud",
+ "c0.app1.tenant1.gcp-us-south1.w.vespa-app.cloud",
"r0.app1.tenant1.g.vespa-app.cloud");
assertEquals(Set.copyOf(expectedRecords), tester.recordNames());
assertEquals(List.of("lb-0--tenant1.app1.default--prod.aws-us-east-1c."), tester.recordDataOf(Record.Type.CNAME, expectedRecords.get(0)));
assertEquals(List.of("10.0.0.0"), tester.recordDataOf(Record.Type.A, expectedRecords.get(1)));
+ assertEquals(List.of("weighted/10.0.0.0/prod.gcp-us-south1-b/1"), tester.recordDataOf(Record.Type.DIRECT, expectedRecords.get(3)));
+ assertEquals(List.of("latency/c0.app1.tenant1.aws-us-east-1.w.vespa-app.cloud/dns-zone-1/prod.aws-us-east-1c",
+ "latency/c0.app1.tenant1.gcp-us-south1.w.vespa-app.cloud/ignored/prod.gcp-us-south1-b"),
+ tester.recordDataOf(Record.Type.ALIAS, expectedRecords.get(4)));
}
@Test
diff --git a/default_build_settings.cmake b/default_build_settings.cmake
index 08f5e7d14da..5a21d12d0c6 100644
--- a/default_build_settings.cmake
+++ b/default_build_settings.cmake
@@ -2,102 +2,16 @@
include(VespaExtendedDefaultBuildSettings OPTIONAL)
-function(setup_vespa_default_build_settings_rhel_8)
- message("-- Setting up default build settings for rhel 8")
- 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_centos_stream_8)
- message("-- Setting up default build settings for centos stream 8")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "14" PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_centos_stream_9)
- message("-- Setting up default build settings for centos stream 9")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "14" PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_rocky_8_6)
- message("-- Setting up default build settings for rocky 8.6")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "13" PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_almalinux_8_6)
- message("-- Setting up default build settings for almalinux 8.6")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "13" PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_almalinux_9_0)
- message("-- Setting up default build settings for almalinux 9.0")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "13" PARENT_SCOPE)
-endfunction()
-
function(setup_vespa_default_build_settings_darwin)
message("-- Setting up default build settings for darwin")
- set(DEFAULT_VESPA_LLVM_VERSION "15" PARENT_SCOPE)
- set(DEFAULT_CMAKE_PREFIX_PATH "${VESPA_DEPS}" "/usr/local/opt/bison" "/usr/local/opt/flex" "/usr/local/opt/openssl@1.1" "/usr/local/opt/openblas" "/usr/local/opt/icu4c" PARENT_SCOPE)
- set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS}/lib" "/usr/local/opt/bison/lib" "/usr/local/opt/flex/lib" "/usr/local/opt/icu4c/lib" "/usr/local/opt/openssl@1.1/lib" "/usr/local/opt/openblas/lib")
+ set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS_PREFIX}/lib" "/usr/local/opt/bison/lib" "/usr/local/opt/flex/lib" "/usr/local/opt/icu4c/lib" "/usr/local/opt/openssl@1.1/lib" "/usr/local/opt/openblas/lib")
list(APPEND DEFAULT_EXTRA_LINK_DIRECTORY "/usr/local/lib")
set(DEFAULT_EXTRA_LINK_DIRECTORY "${DEFAULT_EXTRA_LINK_DIRECTORY}" PARENT_SCOPE)
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/local/opt/flex/include" "/usr/local/opt/icu4c/include" "/usr/local/opt/openssl@1.1/include" "/usr/local/opt/openblas/include")
+ set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS_PREFIX}/include" "/usr/local/opt/flex/include" "/usr/local/opt/icu4c/include" "/usr/local/opt/openssl@1.1/include" "/usr/local/opt/openblas/include")
list(APPEND DEFAULT_EXTRA_INCLUDE_DIRECTORY "/usr/local/include")
set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${DEFAULT_EXTRA_INCLUDE_DIRECTORY}" PARENT_SCOPE)
endfunction()
-function(setup_vespa_default_build_settings_fedora_36)
- message("-- Setting up default build settings for fedora 36")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "14" PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_fedora_37)
- message("-- Setting up default build settings for fedora 37")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "15" PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_fedora_38)
- message("-- Setting up default build settings for fedora 38")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "15" PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_amzn_2022)
- message("-- Setting up default build settings for amzn 2022")
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" "/usr/include/openblas" PARENT_SCOPE)
- set(DEFAULT_VESPA_LLVM_VERSION "14" PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_ubuntu)
- message("-- Setting up default build settings for ubuntu")
- SET(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
- SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
- find_package(LLVM REQUIRED CONFIG)
- message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
- message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
- set(DEFAULT_VESPA_LLVM_VERSION ${LLVM_VERSION_MAJOR} PARENT_SCOPE)
- set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS}/lib" ${LLVM_LIBRARY_DIRS} PARENT_SCOPE)
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" ${LLVM_INCLUDE_DIRS} PARENT_SCOPE)
-endfunction()
-
-function(setup_vespa_default_build_settings_debian)
- message("-- Setting up default build settings for debian")
- set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS}/lib" PARENT_SCOPE)
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include" PARENT_SCOPE)
- SET(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
- SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
- find_package(LLVM REQUIRED CONFIG)
- message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
- message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
- set(DEFAULT_VESPA_LLVM_VERSION ${LLVM_VERSION_MAJOR} PARENT_SCOPE)
-endfunction()
-
function(vespa_use_default_vespa_unprivileged)
if(NOT DEFINED VESPA_UNPRIVILEGED)
message("-- Setting VESPA_UNPRIVILEGED to yes")
@@ -151,72 +65,62 @@ function(vespa_use_default_vespa_group)
endif()
endfunction()
-function(vespa_use_default_build_settings)
- set(VESPA_DEPS "/opt/vespa-deps")
- unset(DEFAULT_VESPA_LLVM_VERSION)
- unset(DEFAULT_CMAKE_PREFIX_PATH)
- unset(DEFAULT_EXTRA_LINK_DIRECTORY)
- unset(DEFAULT_EXTRA_INCLUDE_DIRECTORY)
- unset(DEFAULT_VESPA_CPU_ARCH_FLAGS)
- unset(DEFAULT_CMAKE_SHARED_LINKER_FLAGS)
+function(vespa_use_default_vespa_deps_prefix)
+ set(VESPA_DEPS_PREFIX "/opt/vespa-deps")
if(APPLE)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
- set(VESPA_DEPS "/opt/vespa-deps-clang")
+ set(VESPA_DEPS_PREFIX "/opt/vespa-deps-clang")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
- set(VESPA_DEPS "/opt/vespa-deps-appleclang")
+ set(VESPA_DEPS_PREFIX "/opt/vespa-deps-appleclang")
endif()
endif()
if(COMMAND vespa_use_specific_vespa_deps)
vespa_use_specific_vespa_deps()
endif()
+ message("-- Setting VESPA_DEPS_PREFIX to ${VESPA_DEPS_PREFIX}")
+ set(VESPA_DEPS_PREFIX ${VESPA_DEPS_PREFIX} PARENT_SCOPE)
+endfunction()
+
+function(vespa_use_default_cmake_prefix_path)
+ set(DEFAULT_CMAKE_PREFIX_PATH ${VESPA_DEPS_PREFIX})
+ if (APPLE)
+ list(APPEND DEFAULT_CMAKE_PREFIX_PATH "/usr/local/opt/bison" "/usr/local/opt/flex" "/usr/local/opt/openssl@1.1" "/usr/local/opt/openblas" "/usr/local/opt/icu4c")
+ endif()
+ message("-- DEFAULT_CMAKE_PREFIX_PATH is ${DEFAULT_CMAKE_PREFIX_PATH}")
+ if(NOT DEFINED CMAKE_PREFIX_PATH)
+ message("-- Setting CMAKE_PREFIX_PATH to ${DEFAULT_CMAKE_PREFIX_PATH}")
+ set(CMAKE_PREFIX_PATH ${DEFAULT_CMAKE_PREFIX_PATH} PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(vespa_use_default_build_settings)
+ unset(DEFAULT_EXTRA_LINK_DIRECTORY)
+ unset(DEFAULT_EXTRA_INCLUDE_DIRECTORY)
+ unset(DEFAULT_VESPA_CPU_ARCH_FLAGS)
+ unset(DEFAULT_CMAKE_SHARED_LINKER_FLAGS)
if(COMMAND vespa_use_specific_compiler_rpath)
vespa_use_specific_compiler_rpath()
endif()
- if(COMMAND vespa_use_specific_llvm_version)
- vespa_use_specific_llvm_version()
- endif()
- if(VESPA_OS_DISTRO STREQUAL "rhel" AND
- VESPA_OS_DISTRO_VERSION VERSION_GREATER_EQUAL "8" AND
- VESPA_OS_DISTRO_VERSION VERSION_LESS "9")
- setup_vespa_default_build_settings_rhel_8()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "centos 8")
- setup_vespa_default_build_settings_centos_stream_8()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "centos 9")
- setup_vespa_default_build_settings_centos_stream_9()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "rocky 8.6")
- setup_vespa_default_build_settings_rocky_8_6()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "almalinux 8.6")
- setup_vespa_default_build_settings_almalinux_8_6()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "almalinux 9.0")
- setup_vespa_default_build_settings_almalinux_9_0()
- elseif(VESPA_OS_DISTRO STREQUAL "darwin")
+ if(APPLE)
setup_vespa_default_build_settings_darwin()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 36")
- setup_vespa_default_build_settings_fedora_36()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 37")
- setup_vespa_default_build_settings_fedora_37()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "fedora 38")
- setup_vespa_default_build_settings_fedora_38()
- elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "amzn 2022")
- setup_vespa_default_build_settings_amzn_2022()
- elseif(VESPA_OS_DISTRO STREQUAL "ubuntu")
- setup_vespa_default_build_settings_ubuntu()
- elseif(VESPA_OS_DISTRO STREQUAL "debian")
- setup_vespa_default_build_settings_debian()
else()
- message(FATAL_ERROR "-- Unknown vespa build platform ${VESPA_OS_DISTRO_COMBINED}")
- endif()
- if(NOT DEFINED VESPA_LLVM_VERSION AND NOT DEFINED DEFAULT_VESPA_LLVM_VERSION)
- message(FATAL_ERROR "-- Unknown default llvm version")
- endif()
- if(NOT DEFINED DEFAULT_CMAKE_PREFIX_PATH)
- set(DEFAULT_CMAKE_PREFIX_PATH "${VESPA_DEPS}")
+ message("-- Setting up default build settings for for ${VESPA_OS_DISTRO_COMBINED}")
endif()
+ set(DEFAULT_VESPA_LLVM_VERSION ${LLVM_VERSION_MAJOR})
if(NOT DEFINED DEFAULT_EXTRA_LINK_DIRECTORY)
- set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS}/lib64")
+ set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
+ list(APPEND DEFAULT_EXTRA_LINK_DIRECTORY ${LLVM_LIBRARY_DIRS})
+ list(REMOVE_ITEM DEFAULT_EXTRA_LINK_DIRECTORY "/usr/${CMAKE_INSTALL_LIBDIR}")
+ list(REMOVE_DUPLICATES DEFAULT_EXTRA_LINK_DIRECTORY)
endif()
if(NOT DEFINED DEFAULT_EXTRA_INCLUDE_DIRECTORY)
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS}/include")
+ set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS_PREFIX}/include")
+ list(APPEND DEFAULT_EXTRA_INCLUDE_DIRECTORY ${LLVM_INCLUDE_DIRS})
+ if(EXISTS "/usr/include/openblas")
+ list(APPEND DEFAULT_EXTRA_INCLUDE_DIRECTORY "/usr/include/openblas")
+ endif()
+ list(REMOVE_ITEM DEFAULT_EXTRA_INCLUDE_DIRECTORY "/usr/include")
+ list(REMOVE_DUPLICATES DEFAULT_EXTRA_INCLUDE_DIRECTORY)
endif()
if(DEFINED DEFAULT_CMAKE_SHARED_LINKER_FLAGS)
message("-- DEFAULT_CMAKE_SHARED_LINKER_FLAGS is ${DEFAULT_CMAKE_SHARED_LINKER_FLAGS}")
@@ -235,23 +139,14 @@ function(vespa_use_default_build_settings)
set(DEFAULT_VESPA_CPU_ARCH_FLAGS "-march=armv8.2-a+fp16+rcpc+dotprod+crypto -mtune=neoverse-n1")
endif()
endif()
- if(DEFINED DEFAULT_CMAKE_PREFIX_PATH)
- message("-- DEFAULT_CMAKE_PREFIX_PATH is ${DEFAULT_CMAKE_PREFIX_PATH}")
- endif()
if(DEFINED DEFAULT_EXTRA_LINK_DIRECTORY)
message("-- DEFAULT_EXTRA_LINK_DIRECTORY is ${DEFAULT_EXTRA_LINK_DIRECTORY}")
endif()
if(DEFINED DEFAULT_EXTRA_INCLUDE_DIRECTORY)
message("-- DEFAULT_EXTRA_INCLUDE_DIRECTORY is ${DEFAULT_EXTRA_INCLUDE_DIRECTORY}")
endif()
- if(DEFINED DEFAULT_VESPA_LLVM_VERSION)
- message("-- DEFAULT_VESPA_LLVM_VERSION is ${DEFAULT_VESPA_LLVM_VERSION}")
- endif()
+ message("-- DEFAULT_VESPA_LLVM_VERSION is ${DEFAULT_VESPA_LLVM_VERSION}")
message("-- DEFAULT_VESPA_CPU_ARCH_FLAGS is ${DEFAULT_VESPA_CPU_ARCH_FLAGS}")
- if(NOT DEFINED CMAKE_PREFIX_PATH AND DEFINED DEFAULT_CMAKE_PREFIX_PATH)
- message("-- Setting CMAKE_PREFIX_PATH to ${DEFAULT_CMAKE_PREFIX_PATH}")
- set(CMAKE_PREFIX_PATH "${DEFAULT_CMAKE_PREFIX_PATH}" PARENT_SCOPE)
- endif()
if(NOT DEFINED EXTRA_INCLUDE_DIRECTORY AND DEFINED DEFAULT_EXTRA_INCLUDE_DIRECTORY)
message("-- Setting EXTRA_INCLUDE_DIRECTORY to ${DEFAULT_EXTRA_INCLUDE_DIRECTORY}")
set(EXTRA_INCLUDE_DIRECTORY "${DEFAULT_EXTRA_INCLUDE_DIRECTORY}" PARENT_SCOPE)
@@ -284,7 +179,7 @@ function(vespa_use_default_build_settings)
set(CMAKE_BUILD_RPATH "${CMAKE_BUILD_RPATH}" PARENT_SCOPE)
endif()
endif()
- if(NOT DEFINED VESPA_LLVM_VERSION AND DEFINED DEFAULT_VESPA_LLVM_VERSION)
+ if(NOT DEFINED VESPA_LLVM_VERSION)
message("-- Setting VESPA_LLVM_VERSION to ${DEFAULT_VESPA_LLVM_VERSION}")
set(VESPA_LLVM_VERSION "${DEFAULT_VESPA_LLVM_VERSION}" PARENT_SCOPE)
endif()
diff --git a/dist/STLExtras.h.diff b/dist/STLExtras.h.diff
deleted file mode 100644
index 40f6a2a12ba..00000000000
--- a/dist/STLExtras.h.diff
+++ /dev/null
@@ -1,20 +0,0 @@
---- STLExtras.h.orig 2021-01-28 01:34:01.000000000 +0100
-+++ STLExtras.h 2021-03-03 22:18:46.028992086 +0100
-@@ -1820,7 +1820,7 @@
- result_pair(std::size_t Index, IterOfRange<R> Iter)
- : Index(Index), Iter(Iter) {}
-
-- result_pair<R>(const result_pair<R> &Other)
-+ result_pair(const result_pair<R> &Other)
- : Index(Other.Index), Iter(Other.Iter) {}
- result_pair<R> &operator=(const result_pair<R> &Other) {
- Index = Other.Index;
-@@ -1870,7 +1870,7 @@
- return Result.Iter == RHS.Result.Iter;
- }
-
-- enumerator_iter<R>(const enumerator_iter<R> &Other) : Result(Other.Result) {}
-+ enumerator_iter(const enumerator_iter<R> &Other) : Result(Other.Result) {}
- enumerator_iter<R> &operator=(const enumerator_iter<R> &Other) {
- Result = Other.Result;
- return *this;
diff --git a/dist/vespa.spec b/dist/vespa.spec
index 8953efb82f8..b8498bed210 100644
--- a/dist/vespa.spec
+++ b/dist/vespa.spec
@@ -6,6 +6,17 @@
# Only strip debug info
%global _find_debuginfo_opts -g
+# Don't enable LTO
+%global _lto_cflags %{nil}
+
+# Disable hardened package build.
+%global _preprocessor_defines %{nil}
+%undefine _hardened_build
+
+# Libraries and binaries use shared libraries in /opt/vespa/lib64 and
+# /opt/vespa-deps/lib64
+%global __brp_check_rpaths %{nil}
+
# Go binaries' build-ids are not recognized by RPMs yet, see
# https://github.com/rpm-software-management/rpm/issues/367 and
# https://github.com/tpokorra/lbs-mono-fedora/issues/3#issuecomment-219857688.
@@ -13,7 +24,6 @@
# Force special prefix for Vespa
%define _prefix /opt/vespa
-%define _vespa_deps_prefix /opt/vespa-deps
%define _vespa_user vespa
%define _vespa_group vespa
%undefine _vespa_user_uid
@@ -32,7 +42,7 @@ License: Commercial
URL: http://vespa.ai
Source0: vespa-%{version}.tar.gz
-%if 0%{?centos} || 0%{?rocky}
+%if 0%{?centos} || 0%{?rocky} || 0%{?oraclelinux}
BuildRequires: epel-release
%endif
%if 0%{?el8}
@@ -78,7 +88,7 @@ BuildRequires: glibc-langpack-en
%endif
%if 0%{?el8}
BuildRequires: cmake >= 3.11.4-3
-%if 0%{?centos} || 0%{?rocky}
+%if 0%{?centos} || 0%{?rocky} || 0%{?oraclelinux}
%if 0%{?centos}
# Current cmake on CentOS 8 is broken and manually requires libarchive install
BuildRequires: libarchive
@@ -90,26 +100,26 @@ BuildRequires: (llvm-devel >= 14.0.0 and llvm-devel < 15)
BuildRequires: (llvm-devel >= 13.0.1 and llvm-devel < 14)
%endif
%else
-BuildRequires: (llvm-devel >= 12.0.1 and llvm-devel < 13)
+BuildRequires: (llvm-devel >= 13.0.1 and llvm-devel < 14)
%endif
BuildRequires: vespa-boost-devel >= 1.76.0-1
BuildRequires: vespa-openssl-devel >= 1.1.1o-1
%define _use_vespa_openssl 1
BuildRequires: vespa-gtest = 1.11.0
%define _use_vespa_gtest 1
-BuildRequires: vespa-lz4-devel >= 1.9.2-2
+BuildRequires: vespa-lz4-devel >= 1.9.4-1
BuildRequires: vespa-onnxruntime-devel = 1.12.1
-BuildRequires: vespa-protobuf-devel = 3.19.1
-BuildRequires: vespa-libzstd-devel >= 1.4.5-2
+BuildRequires: vespa-protobuf-devel = 3.21.7
+BuildRequires: vespa-libzstd-devel >= 1.5.2-1
%endif
%if 0%{?el9}
BuildRequires: cmake >= 3.20.2
BuildRequires: maven
BuildRequires: maven-openjdk17
BuildRequires: openssl-devel
-BuildRequires: vespa-lz4-devel >= 1.9.2-2
+BuildRequires: vespa-lz4-devel >= 1.9.4-1
BuildRequires: vespa-onnxruntime-devel = 1.12.1
-BuildRequires: vespa-libzstd-devel >= 1.4.5-2
+BuildRequires: vespa-libzstd-devel >= 1.5.2-1
BuildRequires: protobuf-devel
%if 0%{?_centos_stream}
BuildRequires: (llvm-devel >= 14.0.0 and llvm-devel < 15)
@@ -132,9 +142,9 @@ BuildRequires: maven-openjdk17
%endif
%endif
BuildRequires: openssl-devel
-BuildRequires: vespa-lz4-devel >= 1.9.2-2
+BuildRequires: vespa-lz4-devel >= 1.9.4-1
BuildRequires: vespa-onnxruntime-devel = 1.12.1
-BuildRequires: vespa-libzstd-devel >= 1.4.5-2
+BuildRequires: vespa-libzstd-devel >= 1.5.2-1
%if 0%{?amzn2022}
BuildRequires: protobuf-devel
BuildRequires: llvm-devel >= 14.0.5
@@ -164,9 +174,14 @@ BuildRequires: gtest-devel
BuildRequires: gmock-devel
%endif
%endif
-BuildRequires: xxhash-devel >= 0.8.0
+%if 0%{?amzn2022}
+BuildRequires: vespa-xxhash-devel >= 0.8.1
+%define _use_vespa_xxhash 1
+%else
+BuildRequires: xxhash-devel >= 0.8.1
+%endif
%if 0%{?el8}
-BuildRequires: vespa-openblas-devel = 0.3.18
+BuildRequires: vespa-openblas-devel = 0.3.21
%define _use_vespa_openblas 1
%else
BuildRequires: openblas-devel
@@ -220,8 +235,12 @@ BuildRequires: perl-Pod-Usage
BuildRequires: perl-URI
BuildRequires: valgrind
BuildRequires: perf
+%if 0%{?amzn2022}
+Requires: vespa-xxhash >= 0.8.1
+%else
Requires: xxhash
-Requires: xxhash-libs >= 0.8.0
+Requires: xxhash-libs >= 0.8.1
+%endif
Requires: gdb
Requires: hostname
Requires: nc
@@ -231,45 +250,13 @@ Requires: unzip
Requires: zlib
Requires: zstd
%if 0%{?el8}
-%if 0%{?centos} || 0%{?rocky}
-%if 0%{?_centos_stream}
-%define _vespa_llvm_version 14
-%else
-%define _vespa_llvm_version 13
-%endif
-%else
-%define _vespa_llvm_version 12
-%endif
Requires: vespa-gtest = 1.11.0
-%define _extra_link_directory %{_vespa_deps_prefix}/lib64
-%define _extra_include_directory %{_vespa_deps_prefix}/include
%endif
%if 0%{?el9}
-%if 0%{?_centos_stream}
-%define _vespa_llvm_version 14
-%else
-%define _vespa_llvm_version 13
-%endif
Requires: gtest
-%define _extra_link_directory %{_vespa_deps_prefix}/lib64
-%define _extra_include_directory %{_vespa_deps_prefix}/include;/usr/include/openblas
%endif
%if 0%{?fedora}
Requires: gtest
-%if 0%{?amzn2022}
-%define _vespa_llvm_version 14
-%endif
-%if 0%{?fc36}
-%define _vespa_llvm_version 14
-%endif
-%if 0%{?fc37}
-%define _vespa_llvm_version 15
-%endif
-%if 0%{?fc38}
-%define _vespa_llvm_version 15
-%endif
-%define _extra_link_directory %{_vespa_deps_prefix}/lib64
-%define _extra_include_directory %{_vespa_deps_prefix}/include;/usr/include/openblas
%endif
Requires: %{name}-base = %{version}-%{release}
Requires: %{name}-base-libs = %{version}-%{release}
@@ -312,19 +299,23 @@ Vespa - The open big data serving engine - base
Summary: Vespa - The open big data serving engine - base C++ libraries
-%if 0%{?centos} || 0%{?rocky}
+%if 0%{?centos} || 0%{?rocky} || 0%{?oraclelinux}
Requires: epel-release
%endif
-Requires: xxhash-libs >= 0.8.0
+%if 0%{?amzn2022}
+Requires: vespa-xxhash >= 0.8.1
+%else
+Requires: xxhash-libs >= 0.8.1
+%endif
%if 0%{?el8}
Requires: vespa-openssl >= 1.1.1o-1
%else
Requires: openssl-libs
%endif
-Requires: vespa-lz4 >= 1.9.2-2
-Requires: vespa-libzstd >= 1.4.5-2
+Requires: vespa-lz4 >= 1.9.4-1
+Requires: vespa-libzstd >= 1.5.2-1
%if 0%{?el8}
-Requires: vespa-openblas = 0.3.18
+Requires: vespa-openblas = 0.3.21
%else
Requires: openblas-serial
%endif
@@ -353,16 +344,16 @@ Requires: vespa-openssl >= 1.1.1o-1
Requires: openssl-libs
%endif
%if 0%{?el8}
-%if 0%{?centos} || 0%{?rocky}
+%if 0%{?centos} || 0%{?rocky} || 0%{?oraclelinux}
%if 0%{?_centos_stream}
Requires: (llvm-libs >= 14.0.0 and llvm-libs < 15)
%else
Requires: (llvm-libs >= 13.0.1 and llvm-libs < 14)
%endif
%else
-Requires: (llvm-libs >= 12.0.1 and llvm-libs < 13)
+Requires: (llvm-libs >= 13.0.1 and llvm-libs < 14)
%endif
-Requires: vespa-protobuf = 3.19.1
+Requires: vespa-protobuf = 3.21.7
%endif
%if 0%{?el9}
%if 0%{?_centos_stream}
@@ -492,12 +483,6 @@ nearest neighbor search used for low-level benchmarking.
%endif
%else
%setup -q
-%if 0%{?el8} && %{?_vespa_llvm_version}%{!?_vespa_llvm_version:13} < 13
-if grep -qs 'result_pair<R>(' /usr/include/llvm/ADT/STLExtras.h
-then
- patch /usr/include/llvm/ADT/STLExtras.h < dist/STLExtras.h.diff
-fi
-%endif
echo '%{version}' > VERSION
case '%{version}' in
*.0)
@@ -536,11 +521,6 @@ mvn --batch-mode -e -N io.takari:maven:wrapper -Dmaven=3.6.3
%{?_use_mvn_wrapper:./mvnw}%{!?_use_mvn_wrapper:mvn} --batch-mode -nsu -T 1C install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true
%{_command_cmake} -DCMAKE_INSTALL_PREFIX=%{_prefix} \
-DJAVA_HOME=$JAVA_HOME \
- -DCMAKE_PREFIX_PATH=%{_vespa_deps_prefix} \
- -DEXTRA_LINK_DIRECTORY="%{_extra_link_directory}" \
- -DEXTRA_INCLUDE_DIRECTORY="%{_extra_include_directory}" \
- -DCMAKE_INSTALL_RPATH="%{_prefix}/lib64%{?_extra_link_directory:;%{_extra_link_directory}}" \
- %{?_vespa_llvm_version:-DVESPA_LLVM_VERSION="%{_vespa_llvm_version}"} \
-DVESPA_USER=%{_vespa_user} \
-DVESPA_UNPRIVILEGED=no \
.
@@ -719,6 +699,7 @@ fi
%dir %attr(-,%{_vespa_user},%{_vespa_group}) %{_prefix}/var/db/vespa/config_server
%dir %attr(-,%{_vespa_user},%{_vespa_group}) %{_prefix}/var/db/vespa/config_server/serverdb
%dir %attr(-,%{_vespa_user},%{_vespa_group}) %{_prefix}/var/db/vespa/config_server/serverdb/tenants
+%dir %attr(-,%{_vespa_user},%{_vespa_group}) %{_prefix}/var/db/vespa/download
%dir %attr(-,%{_vespa_user},%{_vespa_group}) %{_prefix}/var/db/vespa/filedistribution
%dir %attr(-,%{_vespa_user},%{_vespa_group}) %{_prefix}/var/db/vespa/index
%dir %attr(-,%{_vespa_user},%{_vespa_group}) %{_prefix}/var/db/vespa/logcontrol
@@ -839,8 +820,7 @@ fi
%dir %{_prefix}/lib
%dir %{_prefix}/lib/jars
%{_prefix}/lib/jars/application-model-jar-with-dependencies.jar
-%{_prefix}/lib/jars/bcpkix-jdk15on-*.jar
-%{_prefix}/lib/jars/bcprov-jdk15on-*.jar
+%{_prefix}/lib/jars/bc*-jdk18on-*.jar
%{_prefix}/lib/jars/config-bundle-jar-with-dependencies.jar
%{_prefix}/lib/jars/configdefinitions-jar-with-dependencies.jar
%{_prefix}/lib/jars/config-model-api-jar-with-dependencies.jar
diff --git a/docproc/src/main/java/com/yahoo/docproc/DocumentProcessor.java b/docproc/src/main/java/com/yahoo/docproc/DocumentProcessor.java
index 7b8f07373a5..1f4ab3c8f00 100644
--- a/docproc/src/main/java/com/yahoo/docproc/DocumentProcessor.java
+++ b/docproc/src/main/java/com/yahoo/docproc/DocumentProcessor.java
@@ -90,7 +90,7 @@ public abstract class DocumentProcessor extends ChainedComponent {
Map<String, String> ret = new HashMap<>();
for (Entry<Pair<String, String>, String> e : fieldMap.entrySet()) {
// Remember to include tuple if doctype is unset in mapping
- if (docType.equals(e.getKey().getFirst()) || e.getKey().getFirst()==null || "".equals(e.getKey().getFirst())) {
+ if (docType.equals(e.getKey().getFirst()) || e.getKey().getFirst() == null || "".equals(e.getKey().getFirst())) {
ret.put(e.getKey().getSecond(), e.getValue());
}
}
diff --git a/document/src/main/java/com/yahoo/document/Document.java b/document/src/main/java/com/yahoo/document/Document.java
index 5937ba00292..760b9de0199 100644
--- a/document/src/main/java/com/yahoo/document/Document.java
+++ b/document/src/main/java/com/yahoo/document/Document.java
@@ -242,8 +242,7 @@ public class Document extends StructuredFieldValue {
@Override
public boolean equals(Object o) {
if (o == this) return true;
- if (!(o instanceof Document)) return false;
- Document other = (Document) o;
+ if (!(o instanceof Document other)) return false;
return (super.equals(o) && docId.equals(other.docId) &&
header.equals(other.header));
}
diff --git a/document/src/main/java/com/yahoo/document/DocumentUpdate.java b/document/src/main/java/com/yahoo/document/DocumentUpdate.java
index befabfb6c07..0731344cea9 100644
--- a/document/src/main/java/com/yahoo/document/DocumentUpdate.java
+++ b/document/src/main/java/com/yahoo/document/DocumentUpdate.java
@@ -443,4 +443,5 @@ public class DocumentUpdate extends DocumentOperation implements Iterable<FieldP
public Optional<Boolean> getOptionalCreateIfNonExistent() {
return Optional.ofNullable(createIfNonExistent);
}
+
}
diff --git a/document/src/main/java/com/yahoo/document/datatypes/StructuredFieldValue.java b/document/src/main/java/com/yahoo/document/datatypes/StructuredFieldValue.java
index 396a42b1237..6adbcda4772 100644
--- a/document/src/main/java/com/yahoo/document/datatypes/StructuredFieldValue.java
+++ b/document/src/main/java/com/yahoo/document/datatypes/StructuredFieldValue.java
@@ -46,14 +46,12 @@ public abstract class StructuredFieldValue extends CompositeFieldValue {
* and using the returned value to call {@link #getFieldValue(Field)}. If the named field does not exist, this
* method returns null.
*
- * @param fieldName The name of the field whose value to return.
- * @return The value of the field, or null.
+ * @param fieldName the name of the field whose value to return.
+ * @return the value of the field, or null if it is not declared in this, or has no value set
*/
public FieldValue getFieldValue(String fieldName) {
Field field = getField(fieldName);
- if (field == null) {
- return null;
- }
+ if (field == null) return null;
return getFieldValue(field);
}
@@ -61,10 +59,10 @@ public abstract class StructuredFieldValue extends CompositeFieldValue {
* Sets the value of the given field. The type of the value must match the type of this field, i.e.
* <pre>field.getDataType().getValueClass().isAssignableFrom(value.getClass())</pre> must be true.
*
- * @param field The field whose value to set.
- * @param value The value to set.
- * @return The previous value of the field, or null.
- * @throws IllegalArgumentException If the value is not compatible with the field.
+ * @param field the field whose value to set
+ * @param value the value to set
+ * @return the previous value of the field, or null
+ * @throws IllegalArgumentException if the value is not compatible with the field
*/
public FieldValue setFieldValue(Field field, FieldValue value) {
if (value == null) {
diff --git a/document/src/main/java/com/yahoo/document/json/JsonFeedReader.java b/document/src/main/java/com/yahoo/document/json/JsonFeedReader.java
index d0bd41c692c..4618a516f66 100644
--- a/document/src/main/java/com/yahoo/document/json/JsonFeedReader.java
+++ b/document/src/main/java/com/yahoo/document/json/JsonFeedReader.java
@@ -16,11 +16,9 @@ import com.yahoo.vespaxmlparser.RemoveFeedOperation;
import java.io.InputStream;
-
/**
* Facade between JsonReader and the FeedReader API.
*
- * <p>
* The feed reader will take ownership of the input stream and close it when the
* last parseable document has been read.
*
@@ -29,7 +27,7 @@ import java.io.InputStream;
public class JsonFeedReader implements FeedReader {
private final JsonReader reader;
- private InputStream stream;
+ private final InputStream stream;
private static final JsonFactory jsonFactory = new JsonFactoryBuilder().disable(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES).build();
public JsonFeedReader(InputStream stream, DocumentTypeManager docMan) {
diff --git a/document/src/main/java/com/yahoo/document/json/JsonReader.java b/document/src/main/java/com/yahoo/document/json/JsonReader.java
index 94ce986fc81..86023d52b63 100644
--- a/document/src/main/java/com/yahoo/document/json/JsonReader.java
+++ b/document/src/main/java/com/yahoo/document/json/JsonReader.java
@@ -29,11 +29,6 @@ import static com.yahoo.document.json.readers.JsonParserHelpers.expectArrayStart
*/
public class JsonReader {
- public Optional<DocumentParseInfo> parseDocument() throws IOException {
- DocumentParser documentParser = new DocumentParser(parser);
- return documentParser.parse(Optional.empty());
- }
-
private final JsonParser parser;
private final DocumentTypeManager typeManager;
private ReaderState state = ReaderState.AT_START;
@@ -53,14 +48,19 @@ public class JsonReader {
}
}
+ public Optional<DocumentParseInfo> parseDocument() throws IOException {
+ DocumentParser documentParser = new DocumentParser(parser);
+ return documentParser.parse(Optional.empty());
+ }
+
/**
* Reads a single operation. The operation is not expected to be part of an array.
*
* @param operationType the type of operation (update or put)
- * @param docIdString document ID.
- * @return the document
+ * @param docIdString document ID
+ * @return the parsed document operation
*/
- public DocumentOperation readSingleDocument(DocumentOperationType operationType, String docIdString) {
+ public ParsedDocumentOperation readOperation(DocumentOperationType operationType, String docIdString) {
DocumentId docId = new DocumentId(docIdString);
DocumentParseInfo documentParseInfo;
try {
@@ -72,12 +72,17 @@ public class JsonReader {
}
documentParseInfo.operationType = operationType;
VespaJsonDocumentReader vespaJsonDocumentReader = new VespaJsonDocumentReader(typeManager.getIgnoreUndefinedFields());
- DocumentOperation operation = vespaJsonDocumentReader.createDocumentOperation(
+ ParsedDocumentOperation operation = vespaJsonDocumentReader.createDocumentOperation(
getDocumentTypeFromString(documentParseInfo.documentId.getDocType(), typeManager), documentParseInfo);
- operation.setCondition(TestAndSetCondition.fromConditionString(documentParseInfo.condition));
+ operation.operation().setCondition(TestAndSetCondition.fromConditionString(documentParseInfo.condition));
return operation;
}
+ @Deprecated // Use readOperation instead
+ public DocumentOperation readSingleDocument(DocumentOperationType operationType, String docIdString) {
+ return readOperation(operationType, docIdString).operation();
+ }
+
/** Returns the next document operation, or null if we have reached the end */
public DocumentOperation next() {
switch (state) {
@@ -106,7 +111,7 @@ public class JsonReader {
VespaJsonDocumentReader vespaJsonDocumentReader = new VespaJsonDocumentReader(typeManager.getIgnoreUndefinedFields());
DocumentOperation operation = vespaJsonDocumentReader.createDocumentOperation(
getDocumentTypeFromString(documentParseInfo.get().documentId.getDocType(), typeManager),
- documentParseInfo.get());
+ documentParseInfo.get()).operation();
operation.setCondition(TestAndSetCondition.fromConditionString(documentParseInfo.get().condition));
return operation;
}
@@ -132,4 +137,5 @@ public class JsonReader {
throw new IllegalArgumentException(e);
}
}
+
}
diff --git a/document/src/main/java/com/yahoo/document/json/ParsedDocumentOperation.java b/document/src/main/java/com/yahoo/document/json/ParsedDocumentOperation.java
new file mode 100644
index 00000000000..a395973a55a
--- /dev/null
+++ b/document/src/main/java/com/yahoo/document/json/ParsedDocumentOperation.java
@@ -0,0 +1,51 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.document.json;
+
+import com.yahoo.document.DocumentOperation;
+
+import java.util.Objects;
+
+/**
+ * The result of JSON parsing a single document operation
+ */
+public final class ParsedDocumentOperation {
+
+ private final DocumentOperation operation;
+ private final boolean fullyApplied;
+
+ /**
+ * @param operation the parsed operation
+ * @param fullyApplied true if all the JSON content could be applied,
+ * false if some (or all) of the fields were not poresent in this document and was ignored
+ */
+ public ParsedDocumentOperation(DocumentOperation operation, boolean fullyApplied) {
+ this.operation = operation;
+ this.fullyApplied = fullyApplied;
+ }
+
+ public DocumentOperation operation() { return operation; }
+
+ public boolean fullyApplied() { return fullyApplied; }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) return true;
+ if (obj == null || obj.getClass() != this.getClass()) return false;
+ var that = (ParsedDocumentOperation) obj;
+ return Objects.equals(this.operation, that.operation) &&
+ this.fullyApplied == that.fullyApplied;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(operation, fullyApplied);
+ }
+
+ @Override
+ public String toString() {
+ return "ParsedDocumentOperation[" +
+ "operation=" + operation + ", " +
+ "fullyApplied=" + fullyApplied + ']';
+ }
+
+}
diff --git a/document/src/main/java/com/yahoo/document/json/TokenBuffer.java b/document/src/main/java/com/yahoo/document/json/TokenBuffer.java
index 4fff9c45ea5..e6d2021f90b 100644
--- a/document/src/main/java/com/yahoo/document/json/TokenBuffer.java
+++ b/document/src/main/java/com/yahoo/document/json/TokenBuffer.java
@@ -213,4 +213,12 @@ public class TokenBuffer {
}
return toReturn;
}
+
+ public void skipToRelativeNesting(int relativeNesting) {
+ int initialNesting = nesting();
+ do {
+ next();
+ } while ( nesting() > initialNesting + relativeNesting);
+ }
+
}
diff --git a/document/src/main/java/com/yahoo/document/json/readers/CompositeReader.java b/document/src/main/java/com/yahoo/document/json/readers/CompositeReader.java
index a2dd91b90a0..6cbc1c1e0b1 100644
--- a/document/src/main/java/com/yahoo/document/json/readers/CompositeReader.java
+++ b/document/src/main/java/com/yahoo/document/json/readers/CompositeReader.java
@@ -19,14 +19,22 @@ import static com.yahoo.document.json.readers.WeightedSetReader.fillWeightedSet;
public class CompositeReader {
- // TODO: reateComposite is extremely similar to add/remove, refactor
+ public static boolean populateComposite(TokenBuffer buffer, FieldValue fieldValue, boolean ignoreUndefinedFields) {
+ boolean fullyApplied = populateComposite(buffer.currentToken(), buffer, fieldValue, ignoreUndefinedFields);
+ expectCompositeEnd(buffer.currentToken());
+ return fullyApplied;
+ }
+
+ // TODO: createComposite is extremely similar to add/remove, refactor
// yes, this suppresswarnings ugliness is by intention, the code relies on the contracts in the builders
@SuppressWarnings({ "cast", "rawtypes" })
- public static void populateComposite(TokenBuffer buffer, FieldValue fieldValue, boolean ignoreUndefinedFields) {
- JsonToken token = buffer.currentToken();
+ private static boolean populateComposite(JsonToken token, TokenBuffer buffer, FieldValue fieldValue,
+ boolean ignoreUndefinedFields) {
if ((token != JsonToken.START_OBJECT) && (token != JsonToken.START_ARRAY)) {
throw new IllegalArgumentException("Expected '[' or '{'. Got '" + token + "'.");
}
+
+ boolean fullyApplied = true;
if (fieldValue instanceof CollectionFieldValue) {
DataType valueType = ((CollectionFieldValue) fieldValue).getDataType().getNestedType();
if (fieldValue instanceof WeightedSet) {
@@ -39,13 +47,14 @@ public class CompositeReader {
} else if (PositionDataType.INSTANCE.equals(fieldValue.getDataType())) {
GeoPositionReader.fillGeoPosition(buffer, fieldValue);
} else if (fieldValue instanceof StructuredFieldValue) {
- StructReader.fillStruct(buffer, (StructuredFieldValue) fieldValue, ignoreUndefinedFields);
+ fullyApplied = StructReader.fillStruct(buffer, (StructuredFieldValue) fieldValue, ignoreUndefinedFields);
} else if (fieldValue instanceof TensorFieldValue) {
TensorReader.fillTensor(buffer, (TensorFieldValue) fieldValue);
} else {
throw new IllegalArgumentException("Expected a " + fieldValue.getClass().getName() + " but got an " +
(token == JsonToken.START_OBJECT ? "object" : "array" ));
}
- expectCompositeEnd(buffer.currentToken());
+ return fullyApplied;
}
+
}
diff --git a/document/src/main/java/com/yahoo/document/json/readers/SingleValueReader.java b/document/src/main/java/com/yahoo/document/json/readers/SingleValueReader.java
index 1747e739bbf..3ae82676fa8 100644
--- a/document/src/main/java/com/yahoo/document/json/readers/SingleValueReader.java
+++ b/document/src/main/java/com/yahoo/document/json/readers/SingleValueReader.java
@@ -53,31 +53,17 @@ public class SingleValueReader {
@SuppressWarnings("rawtypes")
public static ValueUpdate readSingleUpdate(TokenBuffer buffer, DataType expectedType, String action, boolean ignoreUndefinedFields) {
- ValueUpdate update;
-
- switch (action) {
- case UPDATE_ASSIGN:
- update = (buffer.currentToken() == JsonToken.VALUE_NULL)
- ? ValueUpdate.createClear()
- : ValueUpdate.createAssign(readSingleValue(buffer, expectedType, ignoreUndefinedFields));
- break;
+ return switch (action) {
+ case UPDATE_ASSIGN -> (buffer.currentToken() == JsonToken.VALUE_NULL)
+ ? ValueUpdate.createClear()
+ : ValueUpdate.createAssign(readSingleValue(buffer, expectedType, ignoreUndefinedFields));
// double is silly, but it's what is used internally anyway
- case UPDATE_INCREMENT:
- update = ValueUpdate.createIncrement(Double.valueOf(buffer.currentText()));
- break;
- case UPDATE_DECREMENT:
- update = ValueUpdate.createDecrement(Double.valueOf(buffer.currentText()));
- break;
- case UPDATE_MULTIPLY:
- update = ValueUpdate.createMultiply(Double.valueOf(buffer.currentText()));
- break;
- case UPDATE_DIVIDE:
- update = ValueUpdate.createDivide(Double.valueOf(buffer.currentText()));
- break;
- default:
- throw new IllegalArgumentException("Operation '" + buffer.currentName() + "' not implemented.");
- }
- return update;
+ case UPDATE_INCREMENT -> ValueUpdate.createIncrement(Double.valueOf(buffer.currentText()));
+ case UPDATE_DECREMENT -> ValueUpdate.createDecrement(Double.valueOf(buffer.currentText()));
+ case UPDATE_MULTIPLY -> ValueUpdate.createMultiply(Double.valueOf(buffer.currentText()));
+ case UPDATE_DIVIDE -> ValueUpdate.createDivide(Double.valueOf(buffer.currentText()));
+ default -> throw new IllegalArgumentException("Operation '" + buffer.currentName() + "' not implemented.");
+ };
}
public static Matcher matchArithmeticOperation(String expression) {
@@ -94,7 +80,7 @@ public class SingleValueReader {
}
}
- private static FieldValue readReferenceFieldValue(final String refText, DataType expectedType) {
+ private static FieldValue readReferenceFieldValue(String refText, DataType expectedType) {
final FieldValue value = expectedType.createFieldValue();
if (!refText.isEmpty()) {
value.assign(new DocumentId(refText));
diff --git a/document/src/main/java/com/yahoo/document/json/readers/StructReader.java b/document/src/main/java/com/yahoo/document/json/readers/StructReader.java
index b9eaf0d8ec6..b944d273a72 100644
--- a/document/src/main/java/com/yahoo/document/json/readers/StructReader.java
+++ b/document/src/main/java/com/yahoo/document/json/readers/StructReader.java
@@ -12,15 +12,32 @@ import static com.yahoo.document.json.readers.SingleValueReader.readSingleValue;
public class StructReader {
- public static void fillStruct(TokenBuffer buffer, StructuredFieldValue parent, boolean ignoreUndefinedFields) {
+ /**
+ * Fills this struct.
+ *
+ * @return true if all this was applied and false if it was ignored because the field does not exist
+ */
+ public static boolean fillStruct(TokenBuffer buffer, StructuredFieldValue parent, boolean ignoreUndefinedFields) {
// do note the order of initializing initNesting and token is relevant for empty docs
- int initNesting = buffer.nesting();
+ int initialNesting = buffer.nesting();
buffer.next();
- while (buffer.nesting() >= initNesting) {
- Field field = getField(buffer, parent, ignoreUndefinedFields);
+ boolean fullyApplied = true;
+ while (buffer.nesting() >= initialNesting) {
+ Field field = parent.getField(buffer.currentName());
+ if (field == null) {
+ if (! ignoreUndefinedFields)
+ throw new IllegalArgumentException("No field '" + buffer.currentName() + "' in the structure of type '" +
+ parent.getDataType().getDataTypeName() +
+ "', which has the fields: " + parent.getDataType().getFields());
+
+ buffer.skipToRelativeNesting(0);
+ fullyApplied = false;
+ continue;
+ }
+
try {
- if (field != null && buffer.currentToken() != JsonToken.VALUE_NULL) {
+ if (buffer.currentToken() != JsonToken.VALUE_NULL) {
FieldValue v = readSingleValue(buffer, field.getDataType(), ignoreUndefinedFields);
parent.setFieldValue(field, v);
}
@@ -29,16 +46,7 @@ public class StructReader {
throw new JsonReaderException(field, e);
}
}
- }
-
- private static Field getField(TokenBuffer buffer, StructuredFieldValue parent, boolean ignoreUndefinedFields) {
- Field field = parent.getField(buffer.currentName());
- if (field == null && ! ignoreUndefinedFields) {
- throw new IllegalArgumentException("No field '" + buffer.currentName() + "' in the structure of type '" +
- parent.getDataType().getDataTypeName() +
- "', which has the fields: " + parent.getDataType().getFields());
- }
- return field;
+ return fullyApplied;
}
}
diff --git a/document/src/main/java/com/yahoo/document/json/readers/VespaJsonDocumentReader.java b/document/src/main/java/com/yahoo/document/json/readers/VespaJsonDocumentReader.java
index 7bc462ec73a..22a9e7a1119 100644
--- a/document/src/main/java/com/yahoo/document/json/readers/VespaJsonDocumentReader.java
+++ b/document/src/main/java/com/yahoo/document/json/readers/VespaJsonDocumentReader.java
@@ -17,6 +17,7 @@ import com.yahoo.document.fieldpathupdate.AssignFieldPathUpdate;
import com.yahoo.document.fieldpathupdate.FieldPathUpdate;
import com.yahoo.document.fieldpathupdate.RemoveFieldPathUpdate;
import com.yahoo.document.json.JsonReaderException;
+import com.yahoo.document.json.ParsedDocumentOperation;
import com.yahoo.document.json.TokenBuffer;
import com.yahoo.document.update.FieldUpdate;
@@ -50,67 +51,65 @@ public class VespaJsonDocumentReader {
this.ignoreUndefinedFields = ignoreUndefinedFields;
}
- public DocumentOperation createDocumentOperation(DocumentType documentType, DocumentParseInfo documentParseInfo) {
+ public ParsedDocumentOperation createDocumentOperation(DocumentType documentType, DocumentParseInfo documentParseInfo) {
final DocumentOperation documentOperation;
+ boolean fullyApplied = true;
try {
switch (documentParseInfo.operationType) {
- case PUT:
+ case PUT -> {
documentOperation = new DocumentPut(new Document(documentType, documentParseInfo.documentId));
- readPut(documentParseInfo.fieldsBuffer, (DocumentPut) documentOperation);
+ fullyApplied = readPut(documentParseInfo.fieldsBuffer, (DocumentPut) documentOperation);
verifyEndState(documentParseInfo.fieldsBuffer, JsonToken.END_OBJECT);
- break;
- case REMOVE:
- documentOperation = new DocumentRemove(documentParseInfo.documentId);
- break;
- case UPDATE:
+ }
+ case REMOVE -> documentOperation = new DocumentRemove(documentParseInfo.documentId);
+ case UPDATE -> {
documentOperation = new DocumentUpdate(documentType, documentParseInfo.documentId);
- readUpdate(documentParseInfo.fieldsBuffer, (DocumentUpdate) documentOperation);
+ fullyApplied = readUpdate(documentParseInfo.fieldsBuffer, (DocumentUpdate) documentOperation);
verifyEndState(documentParseInfo.fieldsBuffer, JsonToken.END_OBJECT);
- break;
- default:
- throw new IllegalStateException("Implementation out of sync with itself. This is a bug.");
+ }
+ default -> throw new IllegalStateException("Implementation out of sync with itself. This is a bug.");
}
} catch (JsonReaderException e) {
throw JsonReaderException.addDocId(e, documentParseInfo.documentId);
}
if (documentParseInfo.create.isPresent()) {
- if (! ( documentOperation instanceof DocumentUpdate)) {
+ if (! (documentOperation instanceof DocumentUpdate update)) {
throw new IllegalArgumentException("Could not set create flag on non update operation.");
}
- DocumentUpdate update = (DocumentUpdate) documentOperation;
update.setCreateIfNonExistent(documentParseInfo.create.get());
}
- return documentOperation;
+ return new ParsedDocumentOperation(documentOperation, fullyApplied);
}
// Exposed for unit testing...
- public void readPut(TokenBuffer buffer, DocumentPut put) {
+ public boolean readPut(TokenBuffer buffer, DocumentPut put) {
try {
if (buffer.isEmpty()) // no "fields" map
throw new IllegalArgumentException(put + " is missing a 'fields' map");
- populateComposite(buffer, put.getDocument(), ignoreUndefinedFields);
+ return populateComposite(buffer, put.getDocument(), ignoreUndefinedFields);
} catch (JsonReaderException e) {
throw JsonReaderException.addDocId(e, put.getId());
}
}
// Exposed for unit testing...
- public void readUpdate(TokenBuffer buffer, DocumentUpdate update) {
+ public boolean readUpdate(TokenBuffer buffer, DocumentUpdate update) {
if (buffer.isEmpty())
- throw new IllegalArgumentException("update of document " + update.getId() + " is missing a 'fields' map");
+ throw new IllegalArgumentException("Update of document " + update.getId() + " is missing a 'fields' map");
expectObjectStart(buffer.currentToken());
int localNesting = buffer.nesting();
buffer.next();
+ boolean fullyApplied = true;
while (localNesting <= buffer.nesting()) {
expectObjectStart(buffer.currentToken());
String fieldName = buffer.currentName();
try {
if (isFieldPath(fieldName)) {
- addFieldPathUpdates(update, buffer, fieldName);
+ fullyApplied &= addFieldPathUpdates(update, buffer, fieldName);
} else {
- addFieldUpdates(update, buffer, fieldName);
+ fullyApplied &= addFieldUpdates(update, buffer, fieldName);
}
expectObjectEnd(buffer.currentToken());
}
@@ -119,12 +118,18 @@ public class VespaJsonDocumentReader {
}
buffer.next();
}
+ return fullyApplied;
}
- private void addFieldUpdates(DocumentUpdate update, TokenBuffer buffer, String fieldName) {
+ private boolean addFieldUpdates(DocumentUpdate update, TokenBuffer buffer, String fieldName) {
Field field = update.getType().getField(fieldName);
- if (field == null)
- throw new IllegalArgumentException("No field named '" + fieldName + "' in " + update.getType());
+ if (field == null) {
+ if (! ignoreUndefinedFields)
+ throw new IllegalArgumentException("No field named '" + fieldName + "' in " + update.getType());
+ buffer.skipToRelativeNesting(-1);
+ return false;
+ }
+
int localNesting = buffer.nesting();
FieldUpdate fieldUpdate = FieldUpdate.create(field);
@@ -158,9 +163,10 @@ public class VespaJsonDocumentReader {
buffer.next();
}
update.addFieldUpdate(fieldUpdate);
+ return true;
}
- private void addFieldPathUpdates(DocumentUpdate update, TokenBuffer buffer, String fieldPath) {
+ private boolean addFieldPathUpdates(DocumentUpdate update, TokenBuffer buffer, String fieldPath) {
int localNesting = buffer.nesting();
buffer.next();
@@ -185,6 +191,7 @@ public class VespaJsonDocumentReader {
update.addFieldPathUpdate(fieldPathUpdate);
buffer.next();
}
+ return true; // TODO: Track fullyApplied for fieldPath updates
}
private AssignFieldPathUpdate readAssignFieldPathUpdate(DocumentType documentType, String fieldPath, TokenBuffer buffer) {
@@ -230,4 +237,5 @@ public class VespaJsonDocumentReader {
Preconditions.checkState(buffer.next() == null, "Dangling data at end of operation");
Preconditions.checkState(buffer.size() == 0, "Dangling data at end of operation");
}
+
}
diff --git a/document/src/main/java/com/yahoo/document/json/readers/WeightedSetReader.java b/document/src/main/java/com/yahoo/document/json/readers/WeightedSetReader.java
index c9af929735e..7a9921498ef 100644
--- a/document/src/main/java/com/yahoo/document/json/readers/WeightedSetReader.java
+++ b/document/src/main/java/com/yahoo/document/json/readers/WeightedSetReader.java
@@ -10,12 +10,14 @@ import static com.yahoo.document.json.readers.JsonParserHelpers.expectObjectStar
public class WeightedSetReader {
+
public static void fillWeightedSet(TokenBuffer buffer, DataType valueType, @SuppressWarnings("rawtypes") WeightedSet weightedSet) {
int initNesting = buffer.nesting();
expectObjectStart(buffer.currentToken());
buffer.next();
iterateThroughWeightedSet(buffer, initNesting, valueType, weightedSet);
}
+
public static void fillWeightedSetUpdate(TokenBuffer buffer, int initNesting, DataType valueType, @SuppressWarnings("rawtypes") WeightedSet weightedSet) {
iterateThroughWeightedSet(buffer, initNesting, valueType, weightedSet);
}
@@ -29,4 +31,5 @@ public class WeightedSetReader {
buffer.next();
}
}
+
}
diff --git a/document/src/main/java/com/yahoo/document/update/FieldUpdate.java b/document/src/main/java/com/yahoo/document/update/FieldUpdate.java
index b2992bf4988..fc4a293f0fb 100644
--- a/document/src/main/java/com/yahoo/document/update/FieldUpdate.java
+++ b/document/src/main/java/com/yahoo/document/update/FieldUpdate.java
@@ -46,10 +46,10 @@ import java.util.List;
* type - any name/value pair which existing in an updatable structure can be addressed by creating the Fields as
* needed. For example:
* <pre>
- * FieldUpdate field=FieldUpdate.createIncrement(new Field("myattribute",DataType.INT),130);
+ * FieldUpdate field = FieldUpdate.createIncrement(new Field("myattribute",DataType.INT),130);
* </pre>
*
- * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ * @author Einar M R Rosenvinge
* @see com.yahoo.document.update.ValueUpdate
* @see com.yahoo.document.DocumentUpdate
*/
@@ -135,7 +135,7 @@ public class FieldUpdate {
* @throws IllegalArgumentException if the data type of the value update is not equal to the data type of this field
*/
public FieldUpdate addValueUpdate(ValueUpdate valueUpdate) {
- valueUpdate.checkCompatibility(field.getDataType()); //will throw exception
+ valueUpdate.checkCompatibility(field.getDataType()); // will throw exception
valueUpdates.add(valueUpdate);
return this;
}
@@ -149,7 +149,7 @@ public class FieldUpdate {
* @throws IllegalArgumentException if the data type of the value update is not equal to the data type of this field
*/
public FieldUpdate addValueUpdate(int index, ValueUpdate valueUpdate) {
- valueUpdate.checkCompatibility(field.getDataType()); //will throw exception
+ valueUpdate.checkCompatibility(field.getDataType()); // will throw exception
valueUpdates.add(index, valueUpdate);
return this;
}
diff --git a/document/src/main/java/com/yahoo/document/update/ValueUpdate.java b/document/src/main/java/com/yahoo/document/update/ValueUpdate.java
index 3aa728ca5b2..74f5ba9d30a 100644
--- a/document/src/main/java/com/yahoo/document/update/ValueUpdate.java
+++ b/document/src/main/java/com/yahoo/document/update/ValueUpdate.java
@@ -13,7 +13,7 @@ import java.util.List;
/**
* A value update represents some action to perform to a value.
*
- * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ * @author Einar M R Rosenvinge
* @see com.yahoo.document.update.FieldUpdate
* @see com.yahoo.document.DocumentUpdate
* @see AddValueUpdate
@@ -31,11 +31,7 @@ public abstract class ValueUpdate<T extends FieldValue> {
this.valueUpdateClassID = valueUpdateClassID;
}
- /**
- * Returns the valueUpdateClassID of this value update.
- *
- * @return the valueUpdateClassID of this ValueUpdate
- */
+ /** Returns the valueUpdateClassID of this value update. */
public ValueUpdateClassID getValueUpdateClassID() {
return valueUpdateClassID;
}
@@ -46,7 +42,7 @@ public abstract class ValueUpdate<T extends FieldValue> {
@Override
public boolean equals(Object o) {
- return o instanceof ValueUpdate && valueUpdateClassID == ((ValueUpdate) o).valueUpdateClassID;
+ return o instanceof ValueUpdate && valueUpdateClassID == ((ValueUpdate<?>) o).valueUpdateClassID;
}
@Override
diff --git a/document/src/main/java/com/yahoo/vespaxmlparser/FeedOperation.java b/document/src/main/java/com/yahoo/vespaxmlparser/FeedOperation.java
index 6b084a55309..69d851b09bc 100644
--- a/document/src/main/java/com/yahoo/vespaxmlparser/FeedOperation.java
+++ b/document/src/main/java/com/yahoo/vespaxmlparser/FeedOperation.java
@@ -7,7 +7,9 @@ import com.yahoo.document.DocumentUpdate;
import com.yahoo.document.TestAndSetCondition;
public class FeedOperation {
+
public enum Type {DOCUMENT, REMOVE, UPDATE, INVALID}
+
public static final FeedOperation INVALID = new FeedOperation(Type.INVALID);
private Type type;
@@ -36,4 +38,5 @@ public class FeedOperation {
" testandset=" + getCondition() +
'}';
}
+
} \ No newline at end of file
diff --git a/document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java b/document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java
index e396fe8912b..070ede480ac 100644
--- a/document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java
+++ b/document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java
@@ -96,7 +96,7 @@ public class DocumentUpdateJsonSerializerTest {
private static DocumentUpdate jsonToDocumentUpdate(String jsonDoc, String docId) {
final InputStream rawDoc = new ByteArrayInputStream(Utf8.toBytes(jsonDoc));
JsonReader reader = new JsonReader(types, rawDoc, parserFactory);
- return (DocumentUpdate) reader.readSingleDocument(DocumentOperationType.UPDATE, docId);
+ return (DocumentUpdate) reader.readOperation(DocumentOperationType.UPDATE, docId).operation();
}
private static String documentUpdateToJson(DocumentUpdate update) {
diff --git a/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java b/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java
index 441f1fd28ea..bee2adb10a0 100644
--- a/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java
+++ b/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java
@@ -78,6 +78,7 @@ import static com.yahoo.document.json.readers.SingleValueReader.UPDATE_INCREMENT
import static com.yahoo.document.json.readers.SingleValueReader.UPDATE_MULTIPLY;
import static com.yahoo.test.json.JsonTestHelper.inputJson;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -103,6 +104,8 @@ public class JsonReaderTestCase {
DocumentType x = new DocumentType("smoke");
x.addField(new Field("something", DataType.STRING));
x.addField(new Field("nalle", DataType.STRING));
+ x.addField(new Field("field1", DataType.STRING));
+ x.addField(new Field("field2", DataType.STRING));
x.addField(new Field("int1", DataType.INT));
x.addField(new Field("flag", DataType.BOOL));
types.registerDocumentType(x);
@@ -215,8 +218,8 @@ public class JsonReaderTestCase {
" 'nalle': 'bamse'",
" }",
"}"));
- DocumentPut put = (DocumentPut) r.readSingleDocument(DocumentOperationType.PUT,
- "id:unittest:smoke::doc1");
+ DocumentPut put = (DocumentPut) r.readOperation(DocumentOperationType.PUT,
+ "id:unittest:smoke::doc1").operation();
smokeTestDoc(put.getDocument());
}
@@ -226,7 +229,7 @@ public class JsonReaderTestCase {
" 'fields': {",
" 'something': {",
" 'assign': 'orOther' }}}"));
- DocumentUpdate doc = (DocumentUpdate) r.readSingleDocument(DocumentOperationType.UPDATE, "id:unittest:smoke::whee");
+ DocumentUpdate doc = (DocumentUpdate) r.readOperation(DocumentOperationType.UPDATE, "id:unittest:smoke::whee").operation();
FieldUpdate f = doc.getFieldUpdate("something");
assertEquals(1, f.size());
assertTrue(f.getValueUpdate(0) instanceof AssignValueUpdate);
@@ -238,7 +241,7 @@ public class JsonReaderTestCase {
" 'fields': {",
" 'int1': {",
" 'assign': null }}}"));
- DocumentUpdate doc = (DocumentUpdate) r.readSingleDocument(DocumentOperationType.UPDATE, "id:unittest:smoke::whee");
+ DocumentUpdate doc = (DocumentUpdate) r.readOperation(DocumentOperationType.UPDATE, "id:unittest:smoke::whee").operation();
FieldUpdate f = doc.getFieldUpdate("int1");
assertEquals(1, f.size());
assertTrue(f.getValueUpdate(0) instanceof ClearValueUpdate);
@@ -1006,17 +1009,65 @@ public class JsonReaderTestCase {
}
@Test
- public void nonExistingFieldCanBeIgnored() throws IOException{
+ public void nonExistingFieldsCanBeIgnoredInPut() throws IOException{
JsonReader r = createReader(inputJson(
- "{ 'put': 'id:unittest:smoke::whee',",
+ "{ ",
+ " 'put': 'id:unittest:smoke::doc1',",
" 'fields': {",
- " 'smething': 'smoketest',",
- " 'nalle': 'bamse' }}"));
+ " 'nonexisting1': 'ignored value',",
+ " 'field1': 'value1',",
+ " 'nonexisting2': {",
+ " 'blocks':{",
+ " 'a':[2.0,3.0],",
+ " 'b':[4.0,5.0]",
+ " }",
+ " },",
+ " 'field2': 'value2',",
+ " 'nonexisting3': 'ignored value'",
+ " }",
+ "}"));
DocumentParseInfo parseInfo = r.parseDocument().get();
DocumentType docType = r.readDocumentType(parseInfo.documentId);
DocumentPut put = new DocumentPut(new Document(docType, parseInfo.documentId));
+ boolean fullyApplied = new VespaJsonDocumentReader(true).readPut(parseInfo.fieldsBuffer, put);
+ assertFalse(fullyApplied);
+ assertNull(put.getDocument().getField("nonexisting1"));
+ assertEquals("value1", put.getDocument().getFieldValue("field1").toString());
+ assertNull(put.getDocument().getField("nonexisting2"));
+ assertEquals("value2", put.getDocument().getFieldValue("field2").toString());
+ assertNull(put.getDocument().getField("nonexisting3"));
+ }
- new VespaJsonDocumentReader(true).readPut(parseInfo.fieldsBuffer, put);
+ @Test
+ public void nonExistingFieldsCanBeIgnoredInUpdate() throws IOException{
+ JsonReader r = createReader(inputJson(
+ "{ ",
+ " 'update': 'id:unittest:smoke::doc1',",
+ " 'fields': {",
+ " 'nonexisting1': { 'assign': 'ignored value' },",
+ " 'field1': { 'assign': 'value1' },",
+ " 'nonexisting2': { " +
+ " 'assign': {",
+ " 'blocks': {",
+ " 'a':[2.0,3.0],",
+ " 'b':[4.0,5.0]",
+ " }",
+ " }",
+ " },",
+ " 'field2': { 'assign': 'value2' },",
+ " 'nonexisting3': { 'assign': 'ignored value' }",
+ " }",
+ "}"));
+ DocumentParseInfo parseInfo = r.parseDocument().get();
+ DocumentType docType = r.readDocumentType(parseInfo.documentId);
+ DocumentUpdate update = new DocumentUpdate(docType, parseInfo.documentId);
+ boolean fullyApplied = new VespaJsonDocumentReader(true).readUpdate(parseInfo.fieldsBuffer, update);
+ assertFalse(fullyApplied);
+ assertNull(update.getFieldUpdate("nonexisting1"));
+ assertEquals("value1", update.getFieldUpdate("field1").getValueUpdates().get(0).getValue().getWrappedValue().toString());
+ assertNull(update.getFieldUpdate("nonexisting2"));
+ assertEquals("value2", update.getFieldUpdate("field2").getValueUpdates().get(0).getValue().getWrappedValue().toString());
+ assertNull(update.getFieldUpdate("nonexisting3"));
}
@Test
@@ -1044,7 +1095,8 @@ public class JsonReaderTestCase {
DocumentParseInfo parseInfo = r.parseDocument().get();
DocumentType docType = r.readDocumentType(parseInfo.documentId);
DocumentPut put = new DocumentPut(new Document(docType, parseInfo.documentId));
- new VespaJsonDocumentReader(false).readPut(parseInfo.fieldsBuffer, put);
+ boolean fullyApplied = new VespaJsonDocumentReader(false).readPut(parseInfo.fieldsBuffer, put);
+ assertTrue(fullyApplied);
smokeTestDoc(put.getDocument());
}
@@ -1271,7 +1323,7 @@ public class JsonReaderTestCase {
fail("Expected exception");
}
catch (IllegalArgumentException e) {
- assertEquals("update of document id:unittest:smoke::whee is missing a 'fields' map", e.getMessage());
+ assertEquals("Update of document id:unittest:smoke::whee is missing a 'fields' map", e.getMessage());
}
}
@@ -1287,8 +1339,8 @@ public class JsonReaderTestCase {
" 'tensorfield': null",
" }",
"}"));
- DocumentPut put = (DocumentPut) r.readSingleDocument(DocumentOperationType.PUT,
- "id:unittest:testnull::doc1");
+ DocumentPut put = (DocumentPut) r.readOperation(DocumentOperationType.PUT,
+ "id:unittest:testnull::doc1").operation();
Document doc = put.getDocument();
assertFieldValueNull(doc, "intfield");
assertFieldValueNull(doc, "stringfield");
@@ -1305,7 +1357,7 @@ public class JsonReaderTestCase {
" 'arrayfield': [ null ]",
" }",
"}"));
- r.readSingleDocument(DocumentOperationType.PUT, "id:unittest:testnull::doc1");
+ r.readOperation(DocumentOperationType.PUT, "id:unittest:testnull::doc1");
fail();
}
@@ -1587,7 +1639,7 @@ public class JsonReaderTestCase {
" 'fields': {",
" 'something': {",
" 'modify': {} }}}"));
- reader.readSingleDocument(DocumentOperationType.UPDATE, "id:unittest:smoke::doc1");
+ reader.readOperation(DocumentOperationType.UPDATE, "id:unittest:smoke::doc1");
fail("Expected exception");
}
catch (IllegalArgumentException e) {
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/ProgressToken.java b/documentapi/src/main/java/com/yahoo/documentapi/ProgressToken.java
index 4a77d30ec92..682bcc54a56 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/ProgressToken.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/ProgressToken.java
@@ -22,6 +22,7 @@ import com.yahoo.vespa.objects.BufferSerializer;
public class ProgressToken {
private static final Logger log = Logger.getLogger(ProgressToken.class.getName());
+
/**
* Any bucket kept track of by a <code>ProgressToken</code> instance may
* be in one of two states: pending or active. <em>Pending</em> means that
@@ -848,4 +849,5 @@ public class ProgressToken {
pendingBucketCount = 0;
activeBucketCount = 0;
}
+
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/Response.java b/documentapi/src/main/java/com/yahoo/documentapi/Response.java
index 3778189e272..133c3586276 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/Response.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/Response.java
@@ -9,10 +9,8 @@ import static com.yahoo.documentapi.Response.Outcome.ERROR;
import static com.yahoo.documentapi.Response.Outcome.SUCCESS;
/**
- * <p>An asynchronous response from the document api.
- * Subclasses of this provide additional response information for particular operations.</p>
- *
- * <p>This is a <i>value object</i>.</p>
+ * An asynchronous response from the document api.
+ * Subclasses of this provide additional response information for particular operations.
*
* @author bratseth
*/
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/VisitorSession.java b/documentapi/src/main/java/com/yahoo/documentapi/VisitorSession.java
index 4a41769ff4c..e2efaf1de10 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/VisitorSession.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/VisitorSession.java
@@ -4,10 +4,9 @@ package com.yahoo.documentapi;
import com.yahoo.messagebus.Trace;
/**
- * A session for tracking progress for and potentially receiving data from a
- * visitor.
+ * A session for tracking progress for and potentially receiving data from a visitor.
*
- * @author <a href="mailto:thomasg@yahoo-inc.com">Thomas Gundersen</a>
+ * @author Thomas Gundersen
*/
public interface VisitorSession extends VisitorControlSession {
/**
@@ -15,21 +14,21 @@ public interface VisitorSession extends VisitorControlSession {
*
* @return True if visiting is done (either by error or success).
*/
- public boolean isDone();
+ boolean isDone();
/**
* Retrieves the last progress token gotten for this visitor.
*
* @return The progress token.
*/
- public ProgressToken getProgress();
+ ProgressToken getProgress();
/**
* Returns the tracing information so far about the visitor.
*
* @return Returns the trace.
*/
- public Trace getTrace();
+ Trace getTrace();
/**
* Waits until visiting is done, or the given timeout (in ms) expires.
@@ -39,5 +38,5 @@ public interface VisitorSession extends VisitorControlSession {
* @return True if visiting is done (either by error or success).
* @throws InterruptedException If an interrupt signal was received while waiting.
*/
- public boolean waitUntilDone(long timeoutMs) throws InterruptedException;
+ boolean waitUntilDone(long timeoutMs) throws InterruptedException;
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/MapVisitorMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/MapVisitorMessage.java
index 15e971329e6..eb9640a38b8 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/MapVisitorMessage.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/MapVisitorMessage.java
@@ -44,4 +44,5 @@ public class MapVisitorMessage extends VisitorMessage {
public String toString() {
return "MapVisitorMessage(" + data.toString() + ")";
}
+
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
index c4d4fb216be..099839672a2 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
@@ -202,10 +202,10 @@ public abstract class RoutableFactories60 {
buf.putInt(null, msg.getBuckets().size());
for (BucketId id : msg.getBuckets()) {
long rawid = id.getRawId();
- long reversed = ((rawid >>> 56) & 0x00000000000000FFl) | ((rawid >>> 40) & 0x000000000000FF00l) |
- ((rawid >>> 24) & 0x0000000000FF0000l) | ((rawid >>> 8) & 0x00000000FF000000l) |
- ((rawid << 8) & 0x000000FF00000000l) | ((rawid << 24) & 0x0000FF0000000000l) |
- ((rawid << 40) & 0x00FF000000000000l) | ((rawid << 56) & 0xFF00000000000000l);
+ long reversed = ((rawid >>> 56) & 0x00000000000000FFL) | ((rawid >>> 40) & 0x000000000000FF00L) |
+ ((rawid >>> 24) & 0x0000000000FF0000L) | ((rawid >>> 8) & 0x00000000FF000000L) |
+ ((rawid << 8) & 0x000000FF00000000L) | ((rawid << 24) & 0x0000FF0000000000L) |
+ ((rawid << 40) & 0x00FF000000000000L) | ((rawid << 56) & 0xFF00000000000000L);
buf.putLong(null, reversed);
}
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/MessageBusVisitorSessionTestCase.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/MessageBusVisitorSessionTestCase.java
index 6b191b50a57..04f4800231f 100755
--- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/MessageBusVisitorSessionTestCase.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/MessageBusVisitorSessionTestCase.java
@@ -56,6 +56,7 @@ import static org.junit.Assert.fail;
// TODO replace explicit pre-mockito mock classes with proper mockito mocks wherever possible
public class MessageBusVisitorSessionTestCase {
+
private class MockSender implements MessageBusVisitorSession.Sender {
private int maxPending = 1000;
private int pendingCount = 0;
@@ -2520,4 +2521,5 @@ public class MessageBusVisitorSessionTestCase {
// TODO: consider refactoring locking granularity
// TODO: figure out if we risk a re-run of the "too many tasks" issue
+
}
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/ErrorCodesTest.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/ErrorCodesTest.java
index 3b4fac60fcd..ba4ae381057 100644
--- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/ErrorCodesTest.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/ErrorCodesTest.java
@@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals;
* @author vekterli
*/
public class ErrorCodesTest {
+
private class NamedErrorCodes {
private final TreeMap<String, Integer> nameAndCode = new TreeMap<>();
diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt
index 844c81acf52..822a48bffc3 100644
--- a/eval/CMakeLists.txt
+++ b/eval/CMakeLists.txt
@@ -72,6 +72,7 @@ vespa_define_module(
src/tests/instruction/inplace_map_function
src/tests/instruction/join_with_number
src/tests/instruction/l2_distance
+ src/tests/instruction/mapped_lookup
src/tests/instruction/mixed_112_dot_product
src/tests/instruction/mixed_inner_product_function
src/tests/instruction/mixed_simple_join_function
diff --git a/eval/src/tests/eval/gen_spec/gen_spec_test.cpp b/eval/src/tests/eval/gen_spec/gen_spec_test.cpp
index 855b298f295..a52ece90e6a 100644
--- a/eval/src/tests/eval/gen_spec/gen_spec_test.cpp
+++ b/eval/src/tests/eval/gen_spec/gen_spec_test.cpp
@@ -31,13 +31,13 @@ TEST(DimSpecTest, mapped_dimension) {
TEST(DimSpecTest, simple_dictionary_creation) {
auto dict = DimSpec::make_dict(5, 1, "");
- std::vector<vespalib::string> expect = {"0", "1", "2", "3", "4"};
+ std::vector<vespalib::string> expect = {"1", "2", "3", "4", "5"};
EXPECT_EQ(dict, expect);
}
TEST(DimSpecTest, advanced_dictionary_creation) {
auto dict = DimSpec::make_dict(5, 3, "str_");
- std::vector<vespalib::string> expect = {"str_0", "str_3", "str_6", "str_9", "str_12"};
+ std::vector<vespalib::string> expect = {"str_3", "str_6", "str_9", "str_12", "str_15"};
EXPECT_EQ(dict, expect);
}
@@ -176,50 +176,50 @@ TEST(GenSpecTest, generating_custom_vector) {
//-----------------------------------------------------------------------------
TensorSpec basic_map = TensorSpec("tensor(a{})")
- .add({{"a", "0"}}, 1.0)
- .add({{"a", "1"}}, 2.0)
- .add({{"a", "2"}}, 3.0);
+ .add({{"a", "1"}}, 1.0)
+ .add({{"a", "2"}}, 2.0)
+ .add({{"a", "3"}}, 3.0);
TensorSpec custom_map = TensorSpec("tensor(a{})")
- .add({{"a", "s0"}}, 1.0)
- .add({{"a", "s5"}}, 2.0)
- .add({{"a", "s10"}}, 3.0);
+ .add({{"a", "s5"}}, 1.0)
+ .add({{"a", "s10"}}, 2.0)
+ .add({{"a", "s15"}}, 3.0);
TEST(GenSpecTest, generating_basic_map) {
EXPECT_EQ(GenSpec().map("a", 3).gen(), basic_map);
EXPECT_EQ(GenSpec().map("a", 3, 1).gen(), basic_map);
EXPECT_EQ(GenSpec().map("a", 3, 1, "").gen(), basic_map);
- EXPECT_EQ(GenSpec().map("a", {"0", "1", "2"}).gen(), basic_map);
+ EXPECT_EQ(GenSpec().map("a", {"1", "2", "3"}).gen(), basic_map);
}
TEST(GenSpecTest, generating_custom_map) {
EXPECT_EQ(GenSpec().map("a", 3, 5, "s").gen(), custom_map);
- EXPECT_EQ(GenSpec().map("a", {"s0", "s5", "s10"}).gen(), custom_map);
+ EXPECT_EQ(GenSpec().map("a", {"s5", "s10", "s15"}).gen(), custom_map);
}
//-----------------------------------------------------------------------------
TensorSpec basic_mixed = TensorSpec("tensor(a{},b[1],c{},d[3])")
- .add({{"a", "0"},{"b", 0},{"c", "0"},{"d", 0}}, 1.0)
- .add({{"a", "0"},{"b", 0},{"c", "0"},{"d", 1}}, 2.0)
- .add({{"a", "0"},{"b", 0},{"c", "0"},{"d", 2}}, 3.0)
- .add({{"a", "1"},{"b", 0},{"c", "0"},{"d", 0}}, 4.0)
- .add({{"a", "1"},{"b", 0},{"c", "0"},{"d", 1}}, 5.0)
- .add({{"a", "1"},{"b", 0},{"c", "0"},{"d", 2}}, 6.0)
- .add({{"a", "2"},{"b", 0},{"c", "0"},{"d", 0}}, 7.0)
- .add({{"a", "2"},{"b", 0},{"c", "0"},{"d", 1}}, 8.0)
- .add({{"a", "2"},{"b", 0},{"c", "0"},{"d", 2}}, 9.0);
+ .add({{"a", "1"},{"b", 0},{"c", "1"},{"d", 0}}, 1.0)
+ .add({{"a", "1"},{"b", 0},{"c", "1"},{"d", 1}}, 2.0)
+ .add({{"a", "1"},{"b", 0},{"c", "1"},{"d", 2}}, 3.0)
+ .add({{"a", "2"},{"b", 0},{"c", "1"},{"d", 0}}, 4.0)
+ .add({{"a", "2"},{"b", 0},{"c", "1"},{"d", 1}}, 5.0)
+ .add({{"a", "2"},{"b", 0},{"c", "1"},{"d", 2}}, 6.0)
+ .add({{"a", "3"},{"b", 0},{"c", "1"},{"d", 0}}, 7.0)
+ .add({{"a", "3"},{"b", 0},{"c", "1"},{"d", 1}}, 8.0)
+ .add({{"a", "3"},{"b", 0},{"c", "1"},{"d", 2}}, 9.0);
TensorSpec inverted_mixed = TensorSpec("tensor(a{},b[1],c{},d[3])")
- .add({{"a", "0"},{"b", 0},{"c", "0"},{"d", 0}}, 1.0)
- .add({{"a", "1"},{"b", 0},{"c", "0"},{"d", 0}}, 2.0)
- .add({{"a", "2"},{"b", 0},{"c", "0"},{"d", 0}}, 3.0)
- .add({{"a", "0"},{"b", 0},{"c", "0"},{"d", 1}}, 4.0)
- .add({{"a", "1"},{"b", 0},{"c", "0"},{"d", 1}}, 5.0)
- .add({{"a", "2"},{"b", 0},{"c", "0"},{"d", 1}}, 6.0)
- .add({{"a", "0"},{"b", 0},{"c", "0"},{"d", 2}}, 7.0)
- .add({{"a", "1"},{"b", 0},{"c", "0"},{"d", 2}}, 8.0)
- .add({{"a", "2"},{"b", 0},{"c", "0"},{"d", 2}}, 9.0);
+ .add({{"a", "1"},{"b", 0},{"c", "1"},{"d", 0}}, 1.0)
+ .add({{"a", "2"},{"b", 0},{"c", "1"},{"d", 0}}, 2.0)
+ .add({{"a", "3"},{"b", 0},{"c", "1"},{"d", 0}}, 3.0)
+ .add({{"a", "1"},{"b", 0},{"c", "1"},{"d", 1}}, 4.0)
+ .add({{"a", "2"},{"b", 0},{"c", "1"},{"d", 1}}, 5.0)
+ .add({{"a", "3"},{"b", 0},{"c", "1"},{"d", 1}}, 6.0)
+ .add({{"a", "1"},{"b", 0},{"c", "1"},{"d", 2}}, 7.0)
+ .add({{"a", "2"},{"b", 0},{"c", "1"},{"d", 2}}, 8.0)
+ .add({{"a", "3"},{"b", 0},{"c", "1"},{"d", 2}}, 9.0);
TEST(GenSpecTest, generating_basic_mixed) {
EXPECT_EQ(GenSpec().map("a", 3).idx("b", 1).map("c", 1).idx("d", 3).gen(), basic_mixed);
diff --git a/eval/src/tests/eval/tensor_lambda/tensor_lambda_test.cpp b/eval/src/tests/eval/tensor_lambda/tensor_lambda_test.cpp
index 657a6ca8d0e..a455e5522b3 100644
--- a/eval/src/tests/eval/tensor_lambda/tensor_lambda_test.cpp
+++ b/eval/src/tests/eval/tensor_lambda/tensor_lambda_test.cpp
@@ -31,7 +31,7 @@ EvalFixture::ParamRepo make_params() {
.add("x3_float", GenSpec().idx("x", 3).cells(CellType::FLOAT))
.add("x3_bfloat16", GenSpec().idx("x", 3).cells(CellType::BFLOAT16))
.add("x3_int8", GenSpec().idx("x", 3).cells(CellType::INT8))
- .add("x3m", GenSpec().map("x", 3))
+ .add("x3m", GenSpec().map("x", {"0", "1", "2"}))
.add("x3y5", GenSpec().idx("x", 3).idx("y", 5))
.add("x3y5_float", GenSpec().idx("x", 3).idx("y", 5).cells(CellType::FLOAT))
.add("x3y5_bfloat16", GenSpec().idx("x", 3).idx("y", 5).cells(CellType::BFLOAT16))
diff --git a/eval/src/tests/eval/value_type/value_type_test.cpp b/eval/src/tests/eval/value_type/value_type_test.cpp
index 3d4872c2ab3..245c9c30242 100644
--- a/eval/src/tests/eval/value_type/value_type_test.cpp
+++ b/eval/src/tests/eval/value_type/value_type_test.cpp
@@ -353,33 +353,34 @@ TEST("require that dimension index can be obtained") {
void verify_predicates(const ValueType &type,
bool expect_error, bool expect_double, bool expect_tensor,
- bool expect_sparse, bool expect_dense)
+ bool expect_sparse, bool expect_dense, bool expect_mixed)
{
EXPECT_EQUAL(type.is_error(), expect_error);
EXPECT_EQUAL(type.is_double(), expect_double);
EXPECT_EQUAL(type.has_dimensions(), expect_tensor);
EXPECT_EQUAL(type.is_sparse(), expect_sparse);
EXPECT_EQUAL(type.is_dense(), expect_dense);
+ EXPECT_EQUAL(type.is_mixed(), expect_mixed);
}
TEST("require that type-related predicate functions work as expected") {
- TEST_DO(verify_predicates(type("error"), true, false, false, false, false));
- TEST_DO(verify_predicates(type("double"), false, true, false, false, false));
- TEST_DO(verify_predicates(type("tensor()"), false, true, false, false, false));
- TEST_DO(verify_predicates(type("tensor(x{})"), false, false, true, true, false));
- TEST_DO(verify_predicates(type("tensor(x{},y{})"), false, false, true, true, false));
- TEST_DO(verify_predicates(type("tensor(x[5])"), false, false, true, false, true));
- TEST_DO(verify_predicates(type("tensor(x[5],y[10])"), false, false, true, false, true));
- TEST_DO(verify_predicates(type("tensor(x[5],y{})"), false, false, true, false, false));
- TEST_DO(verify_predicates(type("tensor<float>(x{})"), false, false, true, true, false));
- TEST_DO(verify_predicates(type("tensor<float>(x[5])"), false, false, true, false, true));
- TEST_DO(verify_predicates(type("tensor<float>(x[5],y{})"), false, false, true, false, false));
- TEST_DO(verify_predicates(type("tensor<bfloat16>(x{})"), false, false, true, true, false));
- TEST_DO(verify_predicates(type("tensor<bfloat16>(x[5])"), false, false, true, false, true));
- TEST_DO(verify_predicates(type("tensor<bfloat16>(x[5],y{})"), false, false, true, false, false));
- TEST_DO(verify_predicates(type("tensor<int8>(x{})"), false, false, true, true, false));
- TEST_DO(verify_predicates(type("tensor<int8>(x[5])"), false, false, true, false, true));
- TEST_DO(verify_predicates(type("tensor<int8>(x[5],y{})"), false, false, true, false, false));
+ TEST_DO(verify_predicates(type("error"), true, false, false, false, false, false));
+ TEST_DO(verify_predicates(type("double"), false, true, false, false, false, false));
+ TEST_DO(verify_predicates(type("tensor()"), false, true, false, false, false, false));
+ TEST_DO(verify_predicates(type("tensor(x{})"), false, false, true, true, false, false));
+ TEST_DO(verify_predicates(type("tensor(x{},y{})"), false, false, true, true, false, false));
+ TEST_DO(verify_predicates(type("tensor(x[5])"), false, false, true, false, true, false));
+ TEST_DO(verify_predicates(type("tensor(x[5],y[10])"), false, false, true, false, true, false));
+ TEST_DO(verify_predicates(type("tensor(x[5],y{})"), false, false, true, false, false, true));
+ TEST_DO(verify_predicates(type("tensor<float>(x{})"), false, false, true, true, false, false));
+ TEST_DO(verify_predicates(type("tensor<float>(x[5])"), false, false, true, false, true, false));
+ TEST_DO(verify_predicates(type("tensor<float>(x[5],y{})"), false, false, true, false, false, true));
+ TEST_DO(verify_predicates(type("tensor<bfloat16>(x{})"), false, false, true, true, false, false));
+ TEST_DO(verify_predicates(type("tensor<bfloat16>(x[5])"), false, false, true, false, true, false));
+ TEST_DO(verify_predicates(type("tensor<bfloat16>(x[5],y{})"), false, false, true, false, false, true));
+ TEST_DO(verify_predicates(type("tensor<int8>(x{})"), false, false, true, true, false, false));
+ TEST_DO(verify_predicates(type("tensor<int8>(x[5])"), false, false, true, false, true, false));
+ TEST_DO(verify_predicates(type("tensor<int8>(x[5],y{})"), false, false, true, false, false, true));
}
TEST("require that mapped and indexed dimensions can be counted") {
diff --git a/eval/src/tests/instruction/mapped_lookup/CMakeLists.txt b/eval/src/tests/instruction/mapped_lookup/CMakeLists.txt
new file mode 100644
index 00000000000..6bc598235ea
--- /dev/null
+++ b/eval/src/tests/instruction/mapped_lookup/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(eval_mapped_lookup_test_app TEST
+ SOURCES
+ mapped_lookup_test.cpp
+ DEPENDS
+ vespaeval
+ GTest::GTest
+)
+vespa_add_test(NAME eval_mapped_lookup_test_app COMMAND eval_mapped_lookup_test_app)
diff --git a/eval/src/tests/instruction/mapped_lookup/mapped_lookup_test.cpp b/eval/src/tests/instruction/mapped_lookup/mapped_lookup_test.cpp
new file mode 100644
index 00000000000..1ffc6b04f4b
--- /dev/null
+++ b/eval/src/tests/instruction/mapped_lookup/mapped_lookup_test.cpp
@@ -0,0 +1,126 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/eval/eval/fast_value.h>
+#include <vespa/eval/eval/simple_value.h>
+#include <vespa/eval/instruction/mapped_lookup.h>
+#include <vespa/eval/eval/test/eval_fixture.h>
+#include <vespa/eval/eval/test/gen_spec.h>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using namespace vespalib::eval;
+using namespace vespalib::eval::test;
+
+//-----------------------------------------------------------------------------
+
+struct FunInfo {
+ using LookFor = MappedLookup;
+ bool expect_mutable;
+ FunInfo(bool expect_mutable_in)
+ : expect_mutable(expect_mutable_in) {}
+ void verify(const LookFor &fun) const {
+ EXPECT_EQ(fun.result_is_mutable(), expect_mutable);
+ }
+};
+
+void verify_optimized_cell_types(const vespalib::string &expr) {
+ auto same_stable_types = CellTypeSpace(CellTypeUtils::list_stable_types(), 2).same();
+ auto same_unstable_types = CellTypeSpace(CellTypeUtils::list_unstable_types(), 2).same();
+ auto different_types = CellTypeSpace(CellTypeUtils::list_types(), 2).different();
+ EvalFixture::verify<FunInfo>(expr, {FunInfo(false)}, same_stable_types);
+ EvalFixture::verify<FunInfo>(expr, {}, same_unstable_types);
+ EvalFixture::verify<FunInfo>(expr, {}, different_types);
+}
+
+void verify_optimized(const vespalib::string &expr, bool expect_mutable = false) {
+ CellTypeSpace just_float({CellType::FLOAT}, 2);
+ EvalFixture::verify<FunInfo>(expr, {FunInfo(expect_mutable)}, just_float);
+}
+
+void verify_not_optimized(const vespalib::string &expr) {
+ CellTypeSpace just_float({CellType::FLOAT}, 2);
+ EvalFixture::verify<FunInfo>(expr, {}, just_float);
+}
+
+//-----------------------------------------------------------------------------
+
+TEST(MappedLookup, expression_can_be_optimized) {
+ verify_optimized_cell_types("reduce(x1_1*x5_1y5,sum,x)");
+}
+
+TEST(MappedLookup, key_and_map_can_be_swapped) {
+ verify_optimized("reduce(x5_1y5*x1_1,sum,x)");
+}
+
+TEST(MappedLookup, trivial_indexed_dimensions_are_ignored) {
+ verify_optimized("reduce(c1d1x1_1*a1b1x5_1y5,sum,x,c,d,a,b)");
+ verify_optimized("reduce(c1d1x1_1*a1b1x5_1y5,sum,x,c,a)");
+ verify_optimized("reduce(c1d1x1_1*a1b1x5_1y5,sum,x)");
+}
+
+TEST(MappedLookup, mutable_map_gives_mutable_result) {
+ verify_optimized("reduce(@x1_1*x5_1y5,sum,x)", false);
+ verify_optimized("reduce(x1_1*@x5_1y5,sum,x)", true);
+ verify_optimized("reduce(@x5_1y5*x1_1,sum,x)", true);
+ verify_optimized("reduce(x5_1y5*@x1_1,sum,x)", false);
+ verify_optimized("reduce(@x5_1y5*@x1_1,sum,x)", true);
+}
+
+TEST(MappedLookup, similar_expressions_are_not_optimized) {
+ verify_not_optimized("reduce(x1_1*x5_1,sum,x)");
+ verify_not_optimized("reduce(x1_1*x5_1y5,sum,y)");
+ verify_not_optimized("reduce(x1_1*x5_1y5,sum)");
+ verify_not_optimized("reduce(x1_1*x5_1y5z8,sum,x,y)");
+ verify_not_optimized("reduce(x1_1*x5_1y5,prod,x)");
+ verify_not_optimized("reduce(x1_1y3_3*x5_1y3_2z5,sum,x)");
+ verify_not_optimized("reduce(x1_1y3_3*x5_1y3_2z5,sum,x,y)");
+ verify_not_optimized("reduce(x1_1y5*x5_1z5,sum,x)");
+}
+
+enum class KeyType { EMPTY, UNIT, SCALING, MULTI };
+GenSpec make_key(KeyType type) {
+ switch (type) {
+ case KeyType::EMPTY: return GenSpec().cells_float().map("x", {});
+ case KeyType::UNIT: return GenSpec().cells_float().map("x", {"1"}).seq({1.0});
+ case KeyType::SCALING: return GenSpec().cells_float().map("x", {"1"}).seq({5.0});
+ case KeyType::MULTI: return GenSpec().cells_float().map("x", {"1", "2", "3"}).seq({1.0});
+ }
+ abort();
+}
+
+enum class MapType { EMPTY, SMALL, MEDIUM, LARGE1, LARGE2, LARGE3 };
+GenSpec make_map(MapType type) {
+ switch (type) {
+ case MapType::EMPTY: return GenSpec().cells_float().idx("y", 5).map("x", {});
+ case MapType::SMALL: return GenSpec().cells_float().idx("y", 5).map("x", {"1"}).seq(N(10));
+ case MapType::MEDIUM: return GenSpec().cells_float().idx("y", 5).map("x", {"1", "2"}).seq(N(10));
+ case MapType::LARGE1: return GenSpec().cells_float().idx("y", 5).map("x", 5, 100).seq(N(10));
+ case MapType::LARGE2: return GenSpec().cells_float().idx("y", 5).map("x", 5, 2).seq(N(10));
+ case MapType::LARGE3: return GenSpec().cells_float().idx("y", 5).map("x", 5, 1).seq(N(10));
+ }
+ abort();
+}
+
+std::vector<MapType> map_types_for(KeyType key_type) {
+ if (key_type == KeyType::MULTI) {
+ return {MapType::EMPTY, MapType::SMALL, MapType::MEDIUM, MapType::LARGE1, MapType::LARGE2, MapType::LARGE3};
+ } else {
+ return {MapType::EMPTY, MapType::SMALL, MapType::MEDIUM};
+ }
+}
+
+TEST(MappedLookup, test_case_interactions) {
+ for (bool mutable_map: {false, true}) {
+ vespalib::string expr = mutable_map ? "reduce(a*@b,sum,x)" : "reduce(a*b,sum,x)";
+ for (KeyType key_type: {KeyType::EMPTY, KeyType::UNIT, KeyType::SCALING, KeyType::MULTI}) {
+ auto key = make_key(key_type);
+ for (MapType map_type: map_types_for(key_type)) {
+ auto map = make_map(map_type);
+ EvalFixture::verify<FunInfo>(expr, {FunInfo(mutable_map)}, {key,map});
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/eval/src/vespa/eval/eval/optimize_tensor_function.cpp b/eval/src/vespa/eval/eval/optimize_tensor_function.cpp
index 63f0f00a13c..41fa550ce07 100644
--- a/eval/src/vespa/eval/eval/optimize_tensor_function.cpp
+++ b/eval/src/vespa/eval/eval/optimize_tensor_function.cpp
@@ -35,7 +35,7 @@
#include <vespa/eval/instruction/dense_hamming_distance.h>
#include <vespa/eval/instruction/l2_distance.h>
#include <vespa/eval/instruction/simple_join_count.h>
-
+#include <vespa/eval/instruction/mapped_lookup.h>
#include <vespa/log/log.h>
LOG_SETUP(".eval.eval.optimize_tensor_function");
@@ -85,6 +85,7 @@ const TensorFunction &optimize_for_factory(const ValueBuilderFactory &, const Te
child.set(MixedInnerProductFunction::optimize(child.get(), stash));
child.set(DenseHammingDistance::optimize(child.get(), stash));
child.set(SimpleJoinCount::optimize(child.get(), stash));
+ child.set(MappedLookup::optimize(child.get(), stash));
});
run_optimize_pass(root, [&stash](const Child &child)
{
diff --git a/eval/src/vespa/eval/eval/test/eval_fixture.h b/eval/src/vespa/eval/eval/test/eval_fixture.h
index d2244cd4f5f..6cbbfca999b 100644
--- a/eval/src/vespa/eval/eval/test/eval_fixture.h
+++ b/eval/src/vespa/eval/eval/test/eval_fixture.h
@@ -131,6 +131,37 @@ public:
static const ValueBuilderFactory &test_factory() { return SimpleValueBuilderFactory::get(); }
// Verify the evaluation result and specific tensor function
+ // details for the given expression with the given parameters. A
+ // parameter can be tagged as mutable by giving it a name starting
+ // with '@'. Parameters must be given in automatic discovery order.
+
+ template <typename FunInfo>
+ static void verify(const vespalib::string &expr, const std::vector<FunInfo> &fun_info, std::vector<GenSpec> param_specs) {
+ UNWIND_MSG("in verify(%s) with %zu FunInfo", expr.c_str(), fun_info.size());
+ auto fun = Function::parse(expr);
+ REQUIRE_EQ(fun->num_params(), param_specs.size());
+ EvalFixture::ParamRepo param_repo;
+ for (size_t i = 0; i < fun->num_params(); ++i) {
+ if (fun->param_name(i)[0] == '@') {
+ param_repo.add_mutable(fun->param_name(i), param_specs[i]);
+ } else {
+ param_repo.add(fun->param_name(i), param_specs[i]);
+ }
+ }
+ EvalFixture fixture(prod_factory(), expr, param_repo, true, true);
+ EvalFixture slow_fixture(prod_factory(), expr, param_repo, false, false);
+ EvalFixture test_fixture(test_factory(), expr, param_repo, true, true);
+ REQUIRE_EQ(fixture.result(), test_fixture.result());
+ REQUIRE_EQ(fixture.result(), slow_fixture.result());
+ REQUIRE_EQ(fixture.result(), EvalFixture::ref(expr, param_repo));
+ auto info = fixture.find_all<typename FunInfo::LookFor>();
+ REQUIRE_EQ(info.size(), fun_info.size());
+ for (size_t i = 0; i < fun_info.size(); ++i) {
+ fixture.verify_callback<FunInfo>(fun_info[i], *info[i]);
+ }
+ }
+
+ // Verify the evaluation result and specific tensor function
// details for the given expression with different combinations of
// cell types. Parameter names must be valid GenSpec descriptions
// ('a5b8'), with an optional mutable prefix ('@a5b8') to denote
@@ -138,10 +169,9 @@ public:
// trailer starting with '$' ('a5b3$2') to allow multiple
// parameters with the same description as well as scalars
// ('$this_is_a_scalar').
-
+
template <typename FunInfo>
static void verify(const vespalib::string &expr, const std::vector<FunInfo> &fun_info, CellTypeSpace cell_type_space) {
-
UNWIND_MSG("in verify(%s) with %zu FunInfo", expr.c_str(), fun_info.size());
auto fun = Function::parse(expr);
REQUIRE_EQ(fun->num_params(), cell_type_space.n());
diff --git a/eval/src/vespa/eval/eval/test/gen_spec.cpp b/eval/src/vespa/eval/eval/test/gen_spec.cpp
index a175111a429..988ff035bbb 100644
--- a/eval/src/vespa/eval/eval/test/gen_spec.cpp
+++ b/eval/src/vespa/eval/eval/test/gen_spec.cpp
@@ -54,7 +54,7 @@ DimSpec::make_dict(size_t size, size_t stride, const vespalib::string &prefix)
{
std::vector<vespalib::string> dict;
for (size_t i = 0; i < size; ++i) {
- dict.push_back(fmt("%s%zu", prefix.c_str(), i * stride));
+ dict.push_back(fmt("%s%zu", prefix.c_str(), (i + 1) * stride));
}
return dict;
}
diff --git a/eval/src/vespa/eval/eval/test/gen_spec.h b/eval/src/vespa/eval/eval/test/gen_spec.h
index eab2924ac37..5f507eeb272 100644
--- a/eval/src/vespa/eval/eval/test/gen_spec.h
+++ b/eval/src/vespa/eval/eval/test/gen_spec.h
@@ -150,6 +150,7 @@ public:
_seq = seq_in;
return *this;
}
+ GenSpec &seq(const std::vector<double> &numbers) { return seq(Seq({numbers})); }
bool bad_scalar() const;
ValueType type() const;
TensorSpec gen() const;
diff --git a/eval/src/vespa/eval/eval/value_type.cpp b/eval/src/vespa/eval/eval/value_type.cpp
index 80e62b5f512..dc5ce645a8c 100644
--- a/eval/src/vespa/eval/eval/value_type.cpp
+++ b/eval/src/vespa/eval/eval/value_type.cpp
@@ -191,6 +191,18 @@ ValueType::is_dense() const
return true;
}
+bool
+ValueType::is_mixed() const
+{
+ bool seen_mapped = false;
+ bool seen_indexed = false;
+ for (const auto &dim : dimensions()) {
+ seen_mapped |= dim.is_mapped();
+ seen_indexed |= dim.is_indexed();
+ }
+ return (seen_mapped && seen_indexed);
+}
+
size_t
ValueType::count_indexed_dimensions() const
{
diff --git a/eval/src/vespa/eval/eval/value_type.h b/eval/src/vespa/eval/eval/value_type.h
index 0822fa1e4b0..b7a7c92e137 100644
--- a/eval/src/vespa/eval/eval/value_type.h
+++ b/eval/src/vespa/eval/eval/value_type.h
@@ -60,6 +60,7 @@ public:
bool has_dimensions() const { return !_dimensions.empty(); }
bool is_sparse() const;
bool is_dense() const;
+ bool is_mixed() const;
size_t count_indexed_dimensions() const;
size_t count_mapped_dimensions() const;
size_t dense_subspace_size() const;
diff --git a/eval/src/vespa/eval/instruction/CMakeLists.txt b/eval/src/vespa/eval/instruction/CMakeLists.txt
index 2146e3ee8ab..7df3f745e79 100644
--- a/eval/src/vespa/eval/instruction/CMakeLists.txt
+++ b/eval/src/vespa/eval/instruction/CMakeLists.txt
@@ -31,6 +31,7 @@ vespa_add_library(eval_instruction OBJECT
inplace_map_function.cpp
join_with_number_function.cpp
l2_distance.cpp
+ mapped_lookup.cpp
mixed_112_dot_product.cpp
mixed_inner_product_function.cpp
mixed_simple_join_function.cpp
diff --git a/eval/src/vespa/eval/instruction/mapped_lookup.cpp b/eval/src/vespa/eval/instruction/mapped_lookup.cpp
new file mode 100644
index 00000000000..31d3c9766bf
--- /dev/null
+++ b/eval/src/vespa/eval/instruction/mapped_lookup.cpp
@@ -0,0 +1,152 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "mapped_lookup.h"
+#include <vespa/eval/eval/wrap_param.h>
+#include <vespa/eval/eval/fast_value.hpp>
+#include <vespa/vespalib/util/require.h>
+
+namespace vespalib::eval {
+
+using namespace tensor_function;
+using namespace operation;
+using namespace instruction;
+
+namespace {
+
+template <typename CT>
+ConstArrayRef<CT> my_mapped_lookup_fallback(const Value::Index &key_idx, const Value::Index &map_idx,
+ const CT *key_cells, const CT *map_cells, size_t res_size, Stash &stash) __attribute__((noinline));
+template <typename CT>
+ConstArrayRef<CT> my_mapped_lookup_fallback(const Value::Index &key_idx, const Value::Index &map_idx,
+ const CT *key_cells, const CT * map_cells, size_t res_size, Stash &stash)
+{
+ SparseJoinPlan plan(1);
+ auto result = stash.create_array<CT>(res_size);
+ SparseJoinState sparse(plan, key_idx, map_idx);
+ auto outer = sparse.first_index.create_view({});
+ auto inner = sparse.second_index.create_view(sparse.second_view_dims);
+ outer->lookup({});
+ while (outer->next_result(sparse.first_address, sparse.first_subspace)) {
+ inner->lookup(sparse.address_overlap);
+ if (inner->next_result(sparse.second_only_address, sparse.second_subspace)) {
+ auto factor = key_cells[sparse.lhs_subspace];
+ const CT *match = map_cells + (res_size * sparse.rhs_subspace);
+ for (size_t i = 0; i < result.size(); ++i) {
+ result[i] += factor * match[i];
+ }
+ }
+ }
+ return result;
+}
+
+template <typename CT>
+struct MappedLookupResult {
+ ArrayRef<CT> value;
+ MappedLookupResult(size_t res_size, Stash &stash)
+ : value(stash.create_array<CT>(res_size)) {}
+ void process_match(CT factor, const CT *match) {
+ for (size_t i = 0; i < value.size(); ++i) {
+ value[i] += factor * match[i];
+ }
+ }
+};
+
+template <typename CT>
+ConstArrayRef<CT> my_fast_mapped_lookup(const FastAddrMap &key_map, const FastAddrMap &map_map,
+ const CT *key_cells, const CT *map_cells, size_t res_size, Stash &stash)
+{
+ if ((key_map.size() == 1) && (key_cells[0] == 1.0)) {
+ auto subspace = map_map.lookup_singledim(key_map.labels()[0]);
+ if (subspace != FastAddrMap::npos()) {
+ return {map_cells + (res_size * subspace), res_size};
+ } else {
+ return stash.create_array<CT>(res_size);
+ }
+ }
+ MappedLookupResult<CT> result(res_size, stash);
+ if (key_map.size() <= map_map.size()) {
+ const auto &labels = key_map.labels();
+ for (size_t i = 0; i < labels.size(); ++i) {
+ auto subspace = map_map.lookup_singledim(labels[i]);
+ if (subspace != FastAddrMap::npos()) {
+ result.process_match(key_cells[i], map_cells + (res_size * subspace));
+ }
+ }
+ } else {
+ const auto &labels = map_map.labels();
+ for (size_t i = 0; i < labels.size(); ++i) {
+ auto subspace = key_map.lookup_singledim(labels[i]);
+ if (subspace != FastAddrMap::npos()) {
+ result.process_match(key_cells[subspace], map_cells + (res_size * i));
+ }
+ }
+ }
+ return result.value;
+}
+
+template <typename CT>
+void my_mapped_lookup_op(InterpretedFunction::State &state, uint64_t param) {
+ const auto &res_type = unwrap_param<ValueType>(param);
+ const auto &key_idx = state.peek(1).index();
+ const auto &map_idx = state.peek(0).index();
+ const CT *key_cells = state.peek(1).cells().typify<CT>().cbegin();
+ const CT *map_cells = state.peek(0).cells().typify<CT>().cbegin();
+ auto result = __builtin_expect(are_fast(key_idx, map_idx), true)
+ ? my_fast_mapped_lookup<CT>(as_fast(key_idx).map, as_fast(map_idx).map, key_cells, map_cells, res_type.dense_subspace_size(), state.stash)
+ : my_mapped_lookup_fallback<CT>(key_idx, map_idx, key_cells, map_cells, res_type.dense_subspace_size(), state.stash);
+ state.pop_pop_push(state.stash.create<DenseValueView>(res_type, TypedCells(result)));
+}
+
+bool check_types(const ValueType &res, const ValueType &key, const ValueType &map) {
+ return ((res.is_dense()) && (key.dense_subspace_size() == 1) && (map.is_mixed()) &&
+ (res.cell_type() == key.cell_type()) &&
+ (res.cell_type() == map.cell_type()) &&
+ ((res.cell_type() == CellType::FLOAT) || (res.cell_type() == CellType::DOUBLE)) &&
+ (key.mapped_dimensions().size() == 1) &&
+ (key.mapped_dimensions() == map.mapped_dimensions()) &&
+ (map.nontrivial_indexed_dimensions() == res.nontrivial_indexed_dimensions()));
+}
+
+} // namespace <unnamed>
+
+MappedLookup::MappedLookup(const ValueType &res_type,
+ const TensorFunction &key_in,
+ const TensorFunction &map_in)
+ : tensor_function::Op2(res_type, key_in, map_in)
+{
+}
+
+InterpretedFunction::Instruction
+MappedLookup::compile_self(const ValueBuilderFactory &, Stash &) const
+{
+ uint64_t param = wrap_param<ValueType>(result_type());
+ if (result_type().cell_type() == CellType::FLOAT) {
+ return {my_mapped_lookup_op<float>, param};
+ }
+ if (result_type().cell_type() == CellType::DOUBLE) {
+ return {my_mapped_lookup_op<double>, param};
+ }
+ REQUIRE_FAILED("cell types must be float or double");
+}
+
+const TensorFunction &
+MappedLookup::optimize(const TensorFunction &expr, Stash &stash)
+{
+ auto reduce = as<Reduce>(expr);
+ if (reduce && (reduce->aggr() == Aggr::SUM)) {
+ auto join = as<Join>(reduce->child());
+ if (join && (join->function() == Mul::f)) {
+ const TensorFunction &lhs = join->lhs();
+ const TensorFunction &rhs = join->rhs();
+ if (check_types(expr.result_type(), lhs.result_type(), rhs.result_type())) {
+ return stash.create<MappedLookup>(expr.result_type(), lhs, rhs);
+ }
+ if (check_types(expr.result_type(), rhs.result_type(), lhs.result_type())) {
+ return stash.create<MappedLookup>(expr.result_type(), rhs, lhs);
+ }
+ }
+ }
+ return expr;
+}
+
+} // namespace
diff --git a/eval/src/vespa/eval/instruction/mapped_lookup.h b/eval/src/vespa/eval/instruction/mapped_lookup.h
new file mode 100644
index 00000000000..2b66c6cf4aa
--- /dev/null
+++ b/eval/src/vespa/eval/instruction/mapped_lookup.h
@@ -0,0 +1,49 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/eval/eval/tensor_function.h>
+
+namespace vespalib::eval {
+
+/**
+ * Tensor function implementing generalized lookup of 'key' in 'map'
+ * with some type restrictions.
+ *
+ * 'key' may only contain the lookup dimension (called 'x' here)
+ * 'map' must have full mapped overlap with 'key'
+ *
+ * Both tensors must have the same cell type, which can be either
+ * float or double.
+ *
+ * The optimized expression looks like this: reduce(key*map,sum,x)
+ *
+ * If 'map' is also sparse, the lookup operation is a sparse dot
+ * product and will be optimized using SparseDotProductFunction
+ * instead.
+ *
+ * The best performance (simple hash lookup with a result referencing
+ * existing cells without having to copy them) is achieved when a
+ * single dense subspace in 'map' matches a cell with value 1.0 from
+ * 'key'. This fast-path can be ensured if this optimization is
+ * combined with the simple_join_count optimization:
+ *
+ * key = tensor(x{}):{my_key:1}
+ * map = tensor(x{},y[128])
+ * fallback = tensor(y[128])
+ *
+ * simple lookup with fallback:
+ * if(reduce(key*map,count)==128,reduce(key*map,sum,x),fallback)
+ **/
+class MappedLookup : public tensor_function::Op2
+{
+public:
+ MappedLookup(const ValueType &res_type, const TensorFunction &key_in, const TensorFunction &map_in);
+ const TensorFunction &key() const { return lhs(); }
+ const TensorFunction &map() const { return rhs(); }
+ InterpretedFunction::Instruction compile_self(const ValueBuilderFactory &factory, Stash &stash) const override;
+ bool result_is_mutable() const override { return map().result_is_mutable(); }
+ static const TensorFunction &optimize(const TensorFunction &expr, Stash &stash);
+};
+
+} // namespace
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index d8e0ceb01bd..ad9a9e094cb 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -79,7 +79,7 @@ public class Flags {
public static final UnboundBooleanFlag KEEP_STORAGE_NODE_UP = defineFeatureFlag(
"keep-storage-node-up", true,
- List.of("hakonhall"), "2022-07-07", "2022-10-07",
+ List.of("hakonhall"), "2022-07-07", "2022-11-07",
"Whether to leave the storage node (with wanted state) UP while the node is permanently down.",
"Takes effect immediately for nodes transitioning to permanently down.",
ZONE_ID, APPLICATION_ID);
@@ -233,20 +233,6 @@ public class Flags {
"Takes effect at redeployment",
ZONE_ID, APPLICATION_ID);
- public static final UnboundIntFlag MAX_CONCURRENT_MERGES_PER_NODE = defineIntFlag(
- "max-concurrent-merges-per-node", 16,
- List.of("balder", "vekterli"), "2021-06-06", "2022-12-01",
- "Specifies max concurrent merges per content node.",
- "Takes effect at redeploy",
- ZONE_ID, APPLICATION_ID);
-
- public static final UnboundIntFlag MAX_MERGE_QUEUE_SIZE = defineIntFlag(
- "max-merge-queue-size", 100,
- List.of("balder", "vekterli"), "2021-06-06", "2022-12-01",
- "Specifies max size of merge queue.",
- "Takes effect at redeploy",
- ZONE_ID, APPLICATION_ID);
-
public static final UnboundDoubleFlag MIN_NODE_RATIO_PER_GROUP = defineDoubleFlag(
"min-node-ratio-per-group", 0.0,
List.of("geirst", "vekterli"), "2021-07-16", "2022-11-01",
@@ -262,13 +248,6 @@ public class Flags {
TENANT_ID, CONSOLE_USER_EMAIL
);
- public static final UnboundBooleanFlag UNORDERED_MERGE_CHAINING = defineFeatureFlag(
- "unordered-merge-chaining", true,
- List.of("vekterli", "geirst"), "2021-11-15", "2022-11-01",
- "Enables the use of unordered merge chains for data merge operations",
- "Takes effect at redeploy",
- ZONE_ID, APPLICATION_ID);
-
public static final UnboundBooleanFlag IGNORE_THREAD_STACK_SIZES = defineFeatureFlag(
"ignore-thread-stack-sizes", false,
List.of("arnej"), "2021-11-12", "2022-12-01",
@@ -283,7 +262,6 @@ public class Flags {
"Takes effect at redeployment",
ZONE_ID, APPLICATION_ID);
-
public static final UnboundIntFlag MAX_COMPACT_BUFFERS = defineIntFlag(
"max-compact-buffers", 1,
List.of("baldersheim", "geirst", "toregge"), "2021-12-15", "2023-01-01",
@@ -291,56 +269,6 @@ public class Flags {
"Takes effect at redeployment",
ZONE_ID, APPLICATION_ID);
- public static final UnboundStringFlag MERGE_THROTTLING_POLICY = defineStringFlag(
- "merge-throttling-policy", "STATIC",
- List.of("vekterli"), "2022-01-25", "2022-12-01",
- "Sets the policy used for merge throttling on the content nodes. " +
- "Valid values: STATIC, DYNAMIC",
- "Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
-
- public static final UnboundDoubleFlag PERSISTENCE_THROTTLING_WS_DECREMENT_FACTOR = defineDoubleFlag(
- "persistence-throttling-ws-decrement-factor", 1.2,
- List.of("vekterli"), "2022-01-27", "2022-12-01",
- "Sets the dynamic throttle policy window size decrement factor for persistence " +
- "async throttling. Only applies if DYNAMIC policy is used.",
- "Takes effect on redeployment",
- ZONE_ID, APPLICATION_ID);
-
- public static final UnboundDoubleFlag PERSISTENCE_THROTTLING_WS_BACKOFF = defineDoubleFlag(
- "persistence-throttling-ws-backoff", 0.95,
- List.of("vekterli"), "2022-01-27", "2022-12-01",
- "Sets the dynamic throttle policy window size backoff for persistence " +
- "async throttling. Only applies if DYNAMIC policy is used. Valid range [0, 1]",
- "Takes effect on redeployment",
- ZONE_ID, APPLICATION_ID);
-
- public static final UnboundIntFlag PERSISTENCE_THROTTLING_WINDOW_SIZE = defineIntFlag(
- "persistence-throttling-window-size", -1,
- List.of("vekterli"), "2022-02-23", "2022-11-01",
- "If greater than zero, sets both min and max window size to the given number, effectively " +
- "turning dynamic throttling into a static throttling policy. " +
- "Only applies if DYNAMIC policy is used.",
- "Takes effect on redeployment",
- ZONE_ID, APPLICATION_ID);
-
- public static final UnboundDoubleFlag PERSISTENCE_THROTTLING_WS_RESIZE_RATE = defineDoubleFlag(
- "persistence-throttling-ws-resize-rate", 3.0,
- List.of("vekterli"), "2022-02-23", "2022-11-01",
- "Sets the dynamic throttle policy resize rate. Only applies if DYNAMIC policy is used.",
- "Takes effect on redeployment",
- ZONE_ID, APPLICATION_ID);
-
- public static final UnboundBooleanFlag PERSISTENCE_THROTTLING_OF_MERGE_FEED_OPS = defineFeatureFlag(
- "persistence-throttling-of-merge-feed-ops", true,
- List.of("vekterli"), "2022-02-24", "2022-11-01",
- "If true, each put/remove contained within a merge is individually throttled as if it " +
- "were a put/remove from a client. If false, merges are throttled at a persistence thread " +
- "level, i.e. per ApplyBucketDiff message, regardless of how many document operations " +
- "are contained within. Only applies if DYNAMIC policy is used.",
- "Takes effect on redeployment",
- ZONE_ID, APPLICATION_ID);
-
public static final UnboundBooleanFlag USE_QRSERVER_SERVICE_NAME = defineFeatureFlag(
"use-qrserver-service-name", false,
List.of("arnej"), "2022-01-18", "2022-12-31",
@@ -471,7 +399,14 @@ public class Flags {
List.of("andreer", "gjoranv"), "2022-09-28", "2023-04-01",
"Set up a WireGuard endpoint on config servers",
"Takes effect on configserver restart",
- ZONE_ID, NODE_TYPE);
+ HOSTNAME);
+
+ public static final UnboundBooleanFlag USE_WIREGUARD_ON_TENANT_HOSTS = defineFeatureFlag(
+ "use-wireguard-on-tenant-hosts", false,
+ List.of("andreer", "gjoranv"), "2022-09-28", "2023-04-01",
+ "Set up a WireGuard endpoint on tenant hosts",
+ "Takes effect on host admin restart",
+ HOSTNAME);
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners,
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java b/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
index c2a7ce56054..ad1242aa7e9 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
@@ -11,7 +11,9 @@ import java.util.stream.Collectors;
* @author hakonhall
*/
public class DimensionHelper {
- private static Map<FetchVector.Dimension, String> serializedDimensions = new HashMap<>();
+
+ private static final Map<FetchVector.Dimension, String> serializedDimensions = new HashMap<>();
+
static {
serializedDimensions.put(FetchVector.Dimension.ZONE_ID, "zone");
serializedDimensions.put(FetchVector.Dimension.HOSTNAME, "hostname");
@@ -29,7 +31,7 @@ public class DimensionHelper {
}
}
- private static Map<String, FetchVector.Dimension> deserializedDimensions = serializedDimensions.
+ private static final Map<String, FetchVector.Dimension> deserializedDimensions = serializedDimensions.
entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
public static String toWire(FetchVector.Dimension dimension) {
@@ -51,4 +53,5 @@ public class DimensionHelper {
}
private DimensionHelper() { }
+
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/FetchVectorHelper.java b/flags/src/main/java/com/yahoo/vespa/flags/json/FetchVectorHelper.java
index 0b19be21b76..c0bb3128924 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/FetchVectorHelper.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/FetchVectorHelper.java
@@ -10,6 +10,7 @@ import java.util.stream.Collectors;
* @author hakonhall
*/
public class FetchVectorHelper {
+
public static Map<String, String> toWire(FetchVector vector) {
Map<FetchVector.Dimension, String> map = vector.toMap();
if (map.isEmpty()) return null;
@@ -24,4 +25,5 @@ public class FetchVectorHelper {
entry -> DimensionHelper.fromWire(entry.getKey()),
Map.Entry::getValue)));
}
+
}
diff --git a/functions.cmake b/functions.cmake
index fd492a55c5b..7fa0b0db954 100644
--- a/functions.cmake
+++ b/functions.cmake
@@ -221,7 +221,7 @@ function(vespa_add_library TARGET)
endif()
if(NOT ARG_OBJECT AND NOT ARG_STATIC AND NOT ARG_INTERFACE AND NOT ARG_ALLOW_UNRESOLVED_SYMBOLS AND DEFINED VESPA_DISALLOW_UNRESOLVED_SYMBOLS_IN_SHARED_LIBRARIES)
- __add_private_target_link_option(${TARGET} ${VESPA_DISALLOW_UNRESOLVED_SYMBOLS_IN_SHARED_LIBRARIES})
+ target_link_options(${TARGET} PRIVATE ${VESPA_DISALLOW_UNRESOLVED_SYMBOLS_IN_SHARED_LIBRARIES})
endif()
__add_target_to_module(${TARGET})
@@ -779,17 +779,3 @@ function(vespa_suppress_warnings_for_protobuf_sources)
set_source_files_properties(${ARG_SOURCES} PROPERTIES COMPILE_FLAGS "-Wno-array-bounds -Wno-suggest-override -Wno-inline ${VESPA_DISABLE_UNUSED_WARNING}")
endif()
endfunction()
-
-function(__add_private_target_link_option TARGET TARGET_LINK_OPTION)
- if(COMMAND target_link_options)
- target_link_options(${TARGET} PRIVATE ${TARGET_LINK_OPTION})
- else()
- get_target_property(TARGET_LINK_FLAGS ${TARGET} LINK_FLAGS)
- if (NOT DEFINED TARGET_LINK_FLAGS OR ${TARGET_LINK_FLAGS} STREQUAL "" OR ${TARGET_LINK_FLAGS} STREQUAL "TARGET_LINK_FLAGS-NOTFOUND")
- set(TARGET_LINK_FLAGS ${TARGET_LINK_OPTION})
- else()
- set(TARGET_LINK_FLAGS "${TARGET_LINK_FLAGS} ${TARGET_LINK_OPTION}")
- endif()
- set_target_properties(${TARGET} PROPERTIES LINK_FLAGS "${TARGET_LINK_FLAGS}")
- endif()
-endfunction()
diff --git a/hosted-zone-api/abi-spec.json b/hosted-zone-api/abi-spec.json
index 0d9a6409759..213f2883da0 100644
--- a/hosted-zone-api/abi-spec.json
+++ b/hosted-zone-api/abi-spec.json
@@ -41,6 +41,8 @@
],
"methods": [
"public void <init>(int, java.util.List)",
+ "public void <init>(java.lang.String, int, java.util.List)",
+ "public java.lang.String id()",
"public int size()",
"public java.util.List indices()",
"public boolean equals(java.lang.Object)",
diff --git a/hosted-zone-api/src/main/java/ai/vespa/cloud/Cluster.java b/hosted-zone-api/src/main/java/ai/vespa/cloud/Cluster.java
index 218545383e6..ce278848f29 100644
--- a/hosted-zone-api/src/main/java/ai/vespa/cloud/Cluster.java
+++ b/hosted-zone-api/src/main/java/ai/vespa/cloud/Cluster.java
@@ -12,15 +12,25 @@ import java.util.Objects;
*/
public class Cluster {
+ private final String id;
private final int size;
private final List<Integer> indices;
+ // TODO: Remove on Vespa 9
+ @Deprecated(forRemoval = true)
public Cluster(int size, List<Integer> indices) {
- Objects.requireNonNull(indices, "Indices cannot be null!");
+ this("default", size, indices);
+ }
+
+ public Cluster(String id, int size, List<Integer> indices) {
+ this.id = Objects.requireNonNull(id);
this.size = size;
- this.indices = Collections.unmodifiableList(indices);
+ this.indices = List.copyOf(Objects.requireNonNull(indices));
}
+ /** Returns the id of this cluster set in services.xml */
+ public String id() { return id; }
+
/** Returns the number of nodes in this cluster. */
public int size() { return size; }
@@ -32,15 +42,16 @@ public class Cluster {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Cluster cluster = (Cluster) o;
- return size == cluster.size &&
- indices.equals(cluster.indices);
+ if ( ! (o instanceof Cluster other)) return false;
+ if ( ! this.id.equals(other.id)) return false;
+ if ( this.size != other.size) return false;
+ if ( ! this.indices.equals(other.indices)) return false;
+ return true;
}
@Override
public int hashCode() {
- return Objects.hash(size, indices);
+ return Objects.hash(id, size, indices);
}
}
diff --git a/hosted-zone-api/src/test/java/ai/vespa/cloud/SystemInfoTest.java b/hosted-zone-api/src/test/java/ai/vespa/cloud/SystemInfoTest.java
index 2bae9f0c9e2..d97bba51c71 100644
--- a/hosted-zone-api/src/test/java/ai/vespa/cloud/SystemInfoTest.java
+++ b/hosted-zone-api/src/test/java/ai/vespa/cloud/SystemInfoTest.java
@@ -18,7 +18,7 @@ public class SystemInfoTest {
ApplicationId application = new ApplicationId("tenant1", "application1", "instance1");
Zone zone = new Zone(Environment.dev, "us-west-1");
Cloud cloud = new Cloud("aws");
- Cluster cluster = new Cluster(1, List.of());
+ Cluster cluster = new Cluster("clusterId", 1, List.of());
Node node = new Node(0);
SystemInfo info = new SystemInfo(application, zone, cloud, cluster, node);
@@ -59,9 +59,11 @@ public class SystemInfoTest {
@Test
void testCluster() {
+ String id = "clusterId";
int size = 1;
var indices = List.of(1);
- Cluster cluster = new Cluster(size, indices);
+ Cluster cluster = new Cluster("clusterId", size, indices);
+ assertEquals(id, cluster.id());
assertEquals(size, cluster.size());
assertEquals(indices, cluster.indices());
}
diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java
index 7d8aafc8f8b..89e0165d55e 100644
--- a/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java
+++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/findUsages/FunctionDefinitionFinder.java
@@ -51,7 +51,7 @@ public class FunctionDefinitionFinder extends UsageFinder {
private boolean findDefinitionIn(RankProfile profile) {
for (var entry : profile.definedFunctions().entrySet()) {
// TODO: Resolve the right function in the list by parameter count
- if (entry.getKey().equals(functionNameToFind) && entry.getValue().size() > 0)
+ if (entry.key().equals(functionNameToFind) && entry.getValue().size() > 0)
processor().process(new UsageInfo(entry.getValue().get(0).definition()));
}
return true;
diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java
index 88cbdd3e07a..5479949d25a 100644
--- a/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java
+++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java
@@ -92,7 +92,7 @@ public class Schema {
Map<String, List<Function>> functions = new HashMap<>();
for (var profile : rankProfiles().values()) {
for (var entry : profile.definedFunctions().entrySet())
- functions.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).addAll(entry.getValue());
+ functions.computeIfAbsent(entry.key(), k -> new ArrayList<>()).addAll(entry.getValue());
}
return functions;
}
diff --git a/jdisc_core/abi-spec.json b/jdisc_core/abi-spec.json
index 5beb723465b..8171b416b0c 100644
--- a/jdisc_core/abi-spec.json
+++ b/jdisc_core/abi-spec.json
@@ -905,7 +905,8 @@
],
"methods": [
"public abstract void start()",
- "public abstract void close()"
+ "public abstract void close()",
+ "public boolean isMultiplexed()"
],
"fields": []
}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java b/jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java
index af57cf39e73..fc8699c907f 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java
@@ -86,8 +86,8 @@ public class HeaderFields implements Map<String, List<String>> {
}
/**
- * <p>Adds the given value to the entry of the specified key. If no entry exists for the given key, a new one is
- * created containing only the given value.</p>
+ * Adds the given value to the entry of the specified key. If no entry exists for the given key, a new one is
+ * created containing only the given value.
*
* @param key The key with which the specified value is to be associated.
* @param value The value to be added to the list associated with the specified key.
@@ -102,11 +102,11 @@ public class HeaderFields implements Map<String, List<String>> {
}
/**
- * <p>Adds the given values to the entry of the specified key. If no entry exists for the given key, a new one is
- * created containing only the given values.</p>
+ * Adds the given values to the entry of the specified key. If no entry exists for the given key, a new one is
+ * created containing only the given values.
*
- * @param key The key with which the specified value is to be associated.
- * @param values The values to be added to the list associated with the specified key.
+ * @param key the key with which the specified value is to be associated.
+ * @param values the values to be added to the list associated with the specified key.
*/
public void add(String key, List<String> values) {
List<String> lst = content.get(key);
@@ -118,10 +118,10 @@ public class HeaderFields implements Map<String, List<String>> {
}
/**
- * <p>Adds all the entries of the given map to this. This is the same as calling {@link #add(String, List)} for each
- * entry in <code>values</code>.</p>
+ * Adds all the entries of the given map to this. This is the same as calling {@link #add(String, List)} for each
+ * entry in <code>values</code>.
*
- * @param values The values to be added to this.
+ * @param values the values to be added to this.
*/
public void addAll(Map<? extends String, ? extends List<String>> values) {
for (Entry<? extends String, ? extends List<String>> entry : values.entrySet()) {
@@ -193,11 +193,11 @@ public class HeaderFields implements Map<String, List<String>> {
}
/**
- * <p>Convenience method for retrieving the first value of a named header field. If the header is not set, or if the
- * value list is empty, this method returns null.</p>
+ * Convenience method for retrieving the first value of a named header field. If the header is not set, or if the
+ * value list is empty, this method returns null.
*
- * @param key The key whose first value to return.
- * @return The first value of the named header, or null.
+ * @param key the key whose first value to return
+ * @return the first value of the named header, or null
*/
public String getFirst(String key) {
List<String> lst = get(key);
@@ -208,12 +208,12 @@ public class HeaderFields implements Map<String, List<String>> {
}
/**
- * <p>Convenience method for checking whether or not a named header field is <em>true</em>. To satisfy this, the
+ * Convenience method for checking whether a named header field is <em>true</em>. To satisfy this, the
* header field needs to have at least 1 entry, and Boolean.valueOf() of all its values must parse as
- * <em>true</em>.</p>
+ * <em>true</em>.
*
- * @param key The key whose values to parse as a boolean.
- * @return The boolean value of the named header.
+ * @param key the key whose values to parse as a boolean.
+ * @return the boolean value of the named header.
*/
public boolean isTrue(String key) {
List<String> lst = content.get(key);
@@ -249,10 +249,10 @@ public class HeaderFields implements Map<String, List<String>> {
}
/**
- * <p>Returns an unmodifiable list of all key-value pairs of this. This provides a flattened view on the content of
- * this map.</p>
+ * Returns an unmodifiable list of all key-value pairs of this. This provides a flattened view on the content of
+ * this map.
*
- * @return The collection of entries.
+ * @return the collection of entries
*/
public List<Entry<String, String>> entries() {
List<Entry<String, String>> list = new ArrayList<>(content.size());
@@ -304,6 +304,7 @@ public class HeaderFields implements Map<String, List<String>> {
public String toString() {
return key + '=' + value;
}
+
}
}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java
index d450d18d952..318ce5fa61b 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java
@@ -11,7 +11,7 @@ import com.yahoo.jdisc.service.ClientProvider;
* {@link ClientProvider#handleRequest(Request, ResponseHandler)} and {@link RequestHandler#handleRequest(Request,
* ResponseHandler)}).</p>
*
- * <p>The jDISC API has intentionally been designed as not to provide a implicit reference from Response to
+ * <p>The jDISC API is designed to not provide an implicit reference from Response to
* corresponding Request, but rather leave that to the implementation of context-aware ResponseHandlers. By creating
* light-weight ResponseHandlers on a per-Request basis, any necessary reference can be embedded within.</p>
*
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java
index db6c266534f..1910c088263 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java
@@ -17,6 +17,7 @@ import java.util.logging.Logger;
* @author baldersheim
*/
public class DebugReferencesWithStack implements References {
+
private static final Logger log = Logger.getLogger(DebugReferencesWithStack.class.getName());
private final Map<Throwable, Object> activeReferences = new HashMap<>();
private final DestructableResource resource;
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java
index ef72e643dae..ecdc30400f1 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java
@@ -49,4 +49,12 @@ public interface ServerProvider extends SharedResource {
* Application} shutdown code.</p>
*/
void close();
+
+ /**
+ * Whether multiple instances of this can coexist, by means of a multiplexer on top of any exclusive resource.
+ * If this is true, new instances to replace old ones, during a graph generation switch, will be started before
+ * the obsolete ones are stopped; otherwise, the old will be stopped, and then the new ones started.
+ */
+ default boolean isMultiplexed() { return false; }
+
}
diff --git a/jrt/pom.xml b/jrt/pom.xml
index c4fb87d24c4..b43c2b3899c 100644
--- a/jrt/pom.xml
+++ b/jrt/pom.xml
@@ -38,7 +38,7 @@
</dependency>
<dependency> <!-- required due to bug in maven dependency resolving - bouncycastle is compile scope in security-utils, yet it is not part of test scope here -->
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>bcpkix-jdk18on</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/linguistics/src/main/java/com/yahoo/language/opennlp/LanguageDetectorFactory.java b/linguistics/src/main/java/com/yahoo/language/opennlp/LanguageDetectorFactory.java
index 305aead056b..0cf4634c6c3 100644
--- a/linguistics/src/main/java/com/yahoo/language/opennlp/LanguageDetectorFactory.java
+++ b/linguistics/src/main/java/com/yahoo/language/opennlp/LanguageDetectorFactory.java
@@ -2,10 +2,6 @@
package com.yahoo.language.opennlp;
import opennlp.tools.langdetect.LanguageDetectorContextGenerator;
-import opennlp.tools.util.normalizer.EmojiCharSequenceNormalizer;
-import opennlp.tools.util.normalizer.NumberCharSequenceNormalizer;
-import opennlp.tools.util.normalizer.ShrinkCharSequenceNormalizer;
-import opennlp.tools.util.normalizer.TwitterCharSequenceNormalizer;
/**
* Overrides the UrlCharSequenceNormalizer, which has a bad regex, until fixed: https://issues.apache.org/jira/browse/OPENNLP-1350
@@ -18,11 +14,7 @@ public class LanguageDetectorFactory extends opennlp.tools.langdetect.LanguageDe
@Override
public LanguageDetectorContextGenerator getContextGenerator() {
return new DefaultLanguageDetectorContextGenerator(1, 3,
- EmojiCharSequenceNormalizer.getInstance(),
- UrlCharSequenceNormalizer.getInstance(),
- TwitterCharSequenceNormalizer.getInstance(),
- NumberCharSequenceNormalizer.getInstance(),
- ShrinkCharSequenceNormalizer.getInstance());
+ VespaCharSequenceNormalizer.getInstance());
}
}
diff --git a/linguistics/src/main/java/com/yahoo/language/opennlp/VespaCharSequenceNormalizer.java b/linguistics/src/main/java/com/yahoo/language/opennlp/VespaCharSequenceNormalizer.java
new file mode 100644
index 00000000000..df8f3fad520
--- /dev/null
+++ b/linguistics/src/main/java/com/yahoo/language/opennlp/VespaCharSequenceNormalizer.java
@@ -0,0 +1,51 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.language.opennlp;
+
+import opennlp.tools.util.normalizer.CharSequenceNormalizer;
+
+import java.util.function.IntConsumer;
+import java.util.stream.IntStream;
+
+/**
+ * Simple normalizer
+ *
+ * @author arnej
+ */
+public class VespaCharSequenceNormalizer implements CharSequenceNormalizer {
+
+ private static final VespaCharSequenceNormalizer INSTANCE = new VespaCharSequenceNormalizer();
+
+ public static VespaCharSequenceNormalizer getInstance() {
+ return INSTANCE;
+ }
+
+ // filter replacing sequences of non-letters with a single space
+ static class OnlyLetters implements IntStream.IntMapMultiConsumer {
+ boolean addSpace = false;
+ public void accept(int codepoint, IntConsumer target) {
+ if (WordCharDetector.isWordChar(codepoint)) {
+ if (addSpace) {
+ target.accept(' ');
+ addSpace = false;
+ }
+ target.accept(Character.toLowerCase(codepoint));
+ } else {
+ addSpace = true;
+ }
+ }
+ }
+
+ public CharSequence normalize(CharSequence text) {
+ if (text.isEmpty()) {
+ return text;
+ }
+ var r = text
+ .codePoints()
+ .mapMulti(new OnlyLetters())
+ .collect(StringBuilder::new,
+ StringBuilder::appendCodePoint,
+ StringBuilder::append);
+ return r;
+ }
+
+}
diff --git a/linguistics/src/main/java/com/yahoo/language/opennlp/WordCharDetector.java b/linguistics/src/main/java/com/yahoo/language/opennlp/WordCharDetector.java
new file mode 100644
index 00000000000..d7e3f88ae8d
--- /dev/null
+++ b/linguistics/src/main/java/com/yahoo/language/opennlp/WordCharDetector.java
@@ -0,0 +1,48 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.language.opennlp;
+
+class WordCharDetector {
+ public static boolean isWordChar(int codepoint) {
+ int unicodeGeneralCategory = Character.getType(codepoint);
+ switch (unicodeGeneralCategory) {
+ case Character.LOWERCASE_LETTER:
+ case Character.OTHER_LETTER:
+ case Character.TITLECASE_LETTER:
+ case Character.UPPERCASE_LETTER:
+ case Character.MODIFIER_LETTER:
+ return true;
+/*
+ * these are the other categories, currently considered non-word-chars:
+ *
+ case Character.CONNECTOR_PUNCTUATION:
+ case Character.CONTROL:
+ case Character.CURRENCY_SYMBOL:
+ case Character.DASH_PUNCTUATION:
+ case Character.ENCLOSING_MARK:
+ case Character.END_PUNCTUATION:
+ case Character.FINAL_QUOTE_PUNCTUATION:
+ case Character.FORMAT:
+ case Character.INITIAL_QUOTE_PUNCTUATION:
+ case Character.MATH_SYMBOL:
+ case Character.MODIFIER_SYMBOL:
+ case Character.NON_SPACING_MARK:
+ case Character.OTHER_PUNCTUATION:
+ case Character.OTHER_SYMBOL:
+ case Character.PRIVATE_USE:
+ case Character.START_PUNCTUATION:
+ case Character.SURROGATE:
+ case Character.UNASSIGNED:
+ case Character.DECIMAL_DIGIT_NUMBER:
+ case Character.LETTER_NUMBER:
+ case Character.OTHER_NUMBER:
+ case Character.COMBINING_SPACING_MARK:
+ case Character.LINE_SEPARATOR:
+ case Character.SPACE_SEPARATOR:
+ case Character.PARAGRAPH_SEPARATOR:
+ *
+ */
+ default:
+ return false;
+ }
+ }
+}
diff --git a/logserver/src/main/java/com/yahoo/logserver/handlers/archive/LogWriterLRUCache.java b/logserver/src/main/java/com/yahoo/logserver/handlers/archive/LogWriterLRUCache.java
index c499c1c3054..c2be7f98f15 100644
--- a/logserver/src/main/java/com/yahoo/logserver/handlers/archive/LogWriterLRUCache.java
+++ b/logserver/src/main/java/com/yahoo/logserver/handlers/archive/LogWriterLRUCache.java
@@ -10,7 +10,6 @@ import java.util.Map;
/**
* @author Bjorn Borud
*/
-@SuppressWarnings("serial")
public class LogWriterLRUCache extends LinkedHashMap<Integer, LogWriter> {
private static final Logger log = Logger.getLogger(LogWriterLRUCache.class.getName());
diff --git a/logserver/src/test/java/com/yahoo/logserver/handlers/logmetrics/test/LogMetricsTestCase.java b/logserver/src/test/java/com/yahoo/logserver/handlers/logmetrics/test/LogMetricsTestCase.java
index 5602d5be0d6..ffd9ce15778 100644
--- a/logserver/src/test/java/com/yahoo/logserver/handlers/logmetrics/test/LogMetricsTestCase.java
+++ b/logserver/src/test/java/com/yahoo/logserver/handlers/logmetrics/test/LogMetricsTestCase.java
@@ -17,6 +17,7 @@ import static org.junit.Assert.*;
* @author hmusum
*/
public class LogMetricsTestCase {
+
// Some of the tests depend upon the number of messages for a
// host, log level etc. to succeed, so you may have update the
// tests if you change something in mStrings. config, debug and
@@ -72,7 +73,7 @@ public class LogMetricsTestCase {
* each level).
*/
@Test
- public void testLevelCountAggregated() throws java.io.IOException, InvalidLogFormatException {
+ public void testLevelCountAggregated() {
LogMetricsHandler a = new LogMetricsHandler();
for (LogMessage aMsg : msg) {
@@ -84,28 +85,13 @@ public class LogMetricsTestCase {
for (Map.Entry<String, Long> entry : levelCount.entrySet()) {
String key = entry.getKey();
switch (key) {
- case "config":
- assertEquals(entry.getValue(), Long.valueOf(1));
- break;
- case "info":
- assertEquals(entry.getValue(), Long.valueOf(4));
- break;
- case "warning":
- assertEquals(entry.getValue(), Long.valueOf(1));
- break;
- case "severe":
- assertEquals(entry.getValue(), Long.valueOf(0));
- break;
- case "error":
- assertEquals(entry.getValue(), Long.valueOf(1));
- break;
- case "fatal":
- assertEquals(entry.getValue(), Long.valueOf(1));
- break;
- case "debug":
- assertEquals(entry.getValue(), Long.valueOf(0)); // always 0
-
- break;
+ case "config" -> assertEquals(entry.getValue(), Long.valueOf(1));
+ case "info" -> assertEquals(entry.getValue(), Long.valueOf(4));
+ case "warning" -> assertEquals(entry.getValue(), Long.valueOf(1));
+ case "severe" -> assertEquals(entry.getValue(), Long.valueOf(0));
+ case "error" -> assertEquals(entry.getValue(), Long.valueOf(1));
+ case "fatal" -> assertEquals(entry.getValue(), Long.valueOf(1));
+ case "debug" -> assertEquals(entry.getValue(), Long.valueOf(0)); // always 0
}
}
a.close();
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java b/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java
index d36579d5be1..959fff488b1 100644
--- a/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java
@@ -59,7 +59,7 @@ public class NetworkMultiplexer implements NetworkOwner {
net.registerSession(session);
}
else if (owners.contains(owner))
- throw new IllegalArgumentException("Session '" + session + "' with owner '" + owner + "' already registered with this");
+ throw new IllegalArgumentException("Session '" + session + "' with owner '" + owner + "' already registered with " + this);
owners.push(owner);
return owners;
@@ -68,12 +68,12 @@ public class NetworkMultiplexer implements NetworkOwner {
public void unregisterSession(String session, NetworkOwner owner, boolean broadcast) {
sessions.computeIfPresent(session, (name, owners) -> {
- owners.remove(owner);
- if (owners.isEmpty()) {
+ if (owners.size() == 1 && owners.contains(owner)) {
if (broadcast)
net.unregisterSession(session);
return null;
}
+ owners.remove(owner);
return owners;
});
}
@@ -103,7 +103,7 @@ public class NetworkMultiplexer implements NetworkOwner {
/** Attach the network owner to this, allowing this to forward messages to it. */
public void attach(NetworkOwner owner) {
if (owners.contains(owner))
- throw new IllegalArgumentException(owner + " is already attached to this");
+ throw new IllegalArgumentException(owner + " is already attached to " + this);
owners.add(owner);
}
@@ -111,7 +111,7 @@ public class NetworkMultiplexer implements NetworkOwner {
/** Detach the network owner from this, no longer allowing messages to it, and shutting down this is ownerless. */
public void detach(NetworkOwner owner) {
if ( ! owners.remove(owner))
- throw new IllegalArgumentException(owner + " not attached to this");
+ throw new IllegalArgumentException(owner + " not attached to " + this);
destroyIfOwnerless();
}
@@ -137,12 +137,7 @@ public class NetworkMultiplexer implements NetworkOwner {
@Override
public String toString() {
- return "NetworkMultiplexer{" +
- "net=" + net +
- ", owners=" + owners +
- ", sessions=" + sessions +
- ", destructible=" + disowned +
- '}';
+ return "network multiplexer with owners: " + owners + ", sessions: " + sessions + " and destructible: " + disowned.get();
}
} \ No newline at end of file
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java
index 30c1a2fd5b3..74ae46353c5 100644
--- a/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java
+++ b/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTable.java
@@ -165,43 +165,29 @@ public class RoutingTable {
* Constructs a new iterator based on a given map. This is private so that only a {@link RoutingTable} can
* create one.
*
- * @param hops The map to iterate through.
+ * @param hops the map to iterate through
*/
private HopIterator(Map<String, HopBlueprint> hops) {
it = hops.entrySet().iterator();
next();
}
- /**
- * Steps to the next hop in the map.
- */
+ /** Steps to the next hop in the map. */
public void next() {
entry = it.hasNext() ? it.next() : null;
}
- /**
- * Returns whether or not this iterator is valid.
- *
- * @return True if valid.
- */
+ /** Returns whether this iterator is valid. */
public boolean isValid() {
return entry != null;
}
- /**
- * Returns the name of the current hop.
- *
- * @return The name.
- */
+ /** Returns the name of the current hop. */
public String getName() {
return entry.getKey();
}
- /**
- * Returns the current hop.
- *
- * @return The hop.
- */
+ /** Returns the current hop. */
public HopBlueprint getHop() {
return entry.getValue();
}
@@ -220,43 +206,29 @@ public class RoutingTable {
* Constructs a new iterator based on a given map. This is private so that only a {@link RoutingTable} can
* create one.
*
- * @param routes The map to iterate through.
+ * @param routes the map to iterate through
*/
private RouteIterator(Map<String, Route> routes) {
it = routes.entrySet().iterator();
next();
}
- /**
- * Steps to the next route in the map.
- */
+ /** Steps to the next route in the map. */
public void next() {
entry = it.hasNext() ? it.next() : null;
}
- /**
- * Returns whether or not this iterator is valid.
- *
- * @return True if valid.
- */
+ /** Returns whether this iterator is valid. */
public boolean isValid() {
return entry != null;
}
- /**
- * Returns the name of the current route.
- *
- * @return The name.
- */
+ /** Returns the name of the current route. */
public String getName() {
return entry.getKey();
}
- /**
- * Returns the current route.
- *
- * @return The route.
- */
+ /** Returns the current route. */
public Route getRoute() {
return entry.getValue();
}
diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java b/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java
index 60da1b49d90..57ded9f750d 100644
--- a/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java
+++ b/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java
@@ -43,7 +43,8 @@ public class NetworkMultiplexerTest {
fail("Illegal to register same session multiple times with the same owner");
}
catch (IllegalArgumentException expected) {
- assertEquals("Session 's1' with owner 'mock owner' already registered with this", expected.getMessage());
+ assertEquals("Session 's1' with owner 'mock owner' already registered with network multiplexer with owners: [mock owner], sessions: {s1=[mock owner]} and destructible: false",
+ expected.getMessage());
}
assertEquals(Set.of("s1"), net.registered);
diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsRetriever.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsRetriever.java
index 1cb6e1d665b..900f7df6ea2 100644
--- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsRetriever.java
+++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/ApplicationMetricsRetriever.java
@@ -167,7 +167,7 @@ public class ApplicationMetricsRetriever extends AbstractComponent implements Ru
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Throwable cause = e.getCause();
if ( e instanceof ExecutionException && ((cause instanceof SocketException) || cause instanceof ConnectTimeoutException)) {
- log.log(Level.FINE, "Failed retrieving metrics for '" + entry.getKey() + "' : " + cause.getMessage());
+ log.log(Level.FINE, "Failed retrieving metrics for '" + entry.getKey() + "' : " + cause.getMessage());
} else {
log.log(Level.WARNING, "Failed retrieving metrics for '" + entry.getKey() + "' : ", e);
}
diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonUtil.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonUtil.java
index b5b8e30a218..13b2a8d859c 100644
--- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonUtil.java
+++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonUtil.java
@@ -27,6 +27,7 @@ import static java.util.logging.Level.WARNING;
* @author gjoranv
*/
public class YamasJsonUtil {
+
private static final Logger log = Logger.getLogger(YamasJsonUtil.class.getName());
private static final JsonFactory factory = JsonFactory.builder()
.enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN)
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/WireguardMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/WireguardMaintainer.java
new file mode 100644
index 00000000000..f7f1a421cd8
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/WireguardMaintainer.java
@@ -0,0 +1,14 @@
+package com.yahoo.vespa.hosted.node.admin.maintenance;
+
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
+
+/**
+ * Ensures that wireguard-go is running on the host.
+ *
+ * @author gjoranv
+ */
+public interface WireguardMaintainer {
+
+ void converge(NodeAgentContext context);
+
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandler.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandler.java
index ece494a34d7..54aa136d877 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandler.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandler.java
@@ -167,7 +167,7 @@ public class CoredumpHandler {
*/
String getMetadata(NodeAgentContext context, ContainerPath coredumpDirectory, Supplier<Map<String, Object>> nodeAttributesSupplier) throws IOException {
UnixPath metadataPath = new UnixPath(coredumpDirectory.resolve(METADATA_FILE_NAME));
- if (!Files.exists(metadataPath.toPath())) {
+ if (!metadataPath.exists()) {
ContainerPath coredumpFile = findCoredumpFileInProcessingDirectory(coredumpDirectory);
Map<String, Object> metadata = new HashMap<>(coreCollector.collect(context, coredumpFile));
metadata.putAll(nodeAttributesSupplier.get());
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
index ea352791b36..20ea29381f3 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
@@ -24,6 +24,7 @@ import com.yahoo.vespa.hosted.node.admin.container.ContainerResources;
import com.yahoo.vespa.hosted.node.admin.container.RegistryCredentials;
import com.yahoo.vespa.hosted.node.admin.container.RegistryCredentialsProvider;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
+import com.yahoo.vespa.hosted.node.admin.maintenance.WireguardMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.identity.CredentialsMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.servicedump.VespaServiceDumper;
@@ -71,6 +72,7 @@ public class NodeAgentImpl implements NodeAgent {
private final Duration warmUpDuration;
private final DoubleFlag containerCpuCap;
private final VespaServiceDumper serviceDumper;
+ private final Optional<WireguardMaintainer> wireguardMaintainer;
private Thread loopThread;
private ContainerState containerState = UNKNOWN;
@@ -101,16 +103,15 @@ public class NodeAgentImpl implements NodeAgent {
}
- // Created in NodeAdminImpl
public NodeAgentImpl(NodeAgentContextSupplier contextSupplier, NodeRepository nodeRepository,
Orchestrator orchestrator, ContainerOperations containerOperations,
RegistryCredentialsProvider registryCredentialsProvider, StorageMaintainer storageMaintainer,
FlagSource flagSource, List<CredentialsMaintainer> credentialsMaintainers,
Optional<AclMaintainer> aclMaintainer, Optional<HealthChecker> healthChecker, Clock clock,
- VespaServiceDumper serviceDumper) {
+ VespaServiceDumper serviceDumper, Optional<WireguardMaintainer> wireguardMaintainer) {
this(contextSupplier, nodeRepository, orchestrator, containerOperations, registryCredentialsProvider,
storageMaintainer, flagSource, credentialsMaintainers, aclMaintainer, healthChecker, clock,
- DEFAULT_WARM_UP_DURATION, serviceDumper);
+ DEFAULT_WARM_UP_DURATION, serviceDumper, wireguardMaintainer);
}
public NodeAgentImpl(NodeAgentContextSupplier contextSupplier, NodeRepository nodeRepository,
@@ -118,7 +119,8 @@ public class NodeAgentImpl implements NodeAgent {
RegistryCredentialsProvider registryCredentialsProvider, StorageMaintainer storageMaintainer,
FlagSource flagSource, List<CredentialsMaintainer> credentialsMaintainers,
Optional<AclMaintainer> aclMaintainer, Optional<HealthChecker> healthChecker, Clock clock,
- Duration warmUpDuration, VespaServiceDumper serviceDumper) {
+ Duration warmUpDuration, VespaServiceDumper serviceDumper,
+ Optional<WireguardMaintainer> wireguardMaintainer) {
this.contextSupplier = contextSupplier;
this.nodeRepository = nodeRepository;
this.orchestrator = orchestrator;
@@ -132,6 +134,7 @@ public class NodeAgentImpl implements NodeAgent {
this.warmUpDuration = warmUpDuration;
this.containerCpuCap = PermanentFlags.CONTAINER_CPU_CAP.bindTo(flagSource);
this.serviceDumper = serviceDumper;
+ this.wireguardMaintainer = wireguardMaintainer;
}
@Override
@@ -495,6 +498,7 @@ public class NodeAgentImpl implements NodeAgent {
}
aclMaintainer.ifPresent(maintainer -> maintainer.converge(context));
+ wireguardMaintainer.ifPresent(maintainer -> maintainer.converge(context));
startServicesIfNeeded(context);
resumeNodeIfNeeded(context);
if (healthChecker.isPresent()) {
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java
index 1773eb4be25..2f9b282c44e 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java
@@ -95,7 +95,7 @@ public class ContainerTester implements AutoCloseable {
new NodeAgentImpl(contextSupplier, nodeRepository, orchestrator, containerOperations, () -> RegistryCredentials.none,
storageMaintainer, flagSource,
Collections.emptyList(), Optional.empty(), Optional.empty(), clock, Duration.ofSeconds(-1),
- VespaServiceDumper.DUMMY_INSTANCE) {
+ VespaServiceDumper.DUMMY_INSTANCE, Optional.empty()) {
@Override public void converge(NodeAgentContext context) {
super.converge(context);
phaser.arriveAndAwaitAdvance();
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
index a7697e5cb5f..fb132c9b717 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
@@ -789,7 +789,7 @@ public class NodeAgentImplTest {
return new NodeAgentImpl(contextSupplier, nodeRepository, orchestrator, containerOperations,
() -> RegistryCredentials.none, storageMaintainer, flagSource,
List.of(credentialsMaintainer), Optional.of(aclMaintainer), Optional.of(healthChecker),
- clock, warmUpDuration, VespaServiceDumper.DUMMY_INSTANCE);
+ clock, warmUpDuration, VespaServiceDumper.DUMMY_INSTANCE, Optional.empty());
}
private void mockGetContainer(DockerImage dockerImage, boolean isRunning) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
index 9cba823500b..d9e0e4292e8 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java
@@ -468,10 +468,13 @@ public final class Node implements Nodelike {
/** Returns a copy of this node with the current reboot generation set to the given number at the given instant */
public Node withCurrentRebootGeneration(long generation, Instant instant) {
- if (generation < status.reboot().current())
+ if (generation == status.reboot().current()) {
+ return this; // No change
+ }
+ if (generation < status.reboot().current()) {
throw new IllegalArgumentException("Cannot set reboot generation to " + generation +
- ": lower than current generation: " + status.reboot().current());
-
+ ": lower than current generation: " + status.reboot().current());
+ }
Status newStatus = status().withReboot(status().reboot().withCurrent(generation));
History newHistory = history.with(new History.Event(History.Event.Type.rebooted, Agent.system, instant));
return this.with(newStatus).with(newHistory);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
index 822ed338b56..87b5719cc19 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
@@ -168,7 +168,7 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer {
nodeRepository().nodes().write(updatedNode, mutex);
}
} catch (RuntimeException e) {
- log.log(Level.WARNING, "Failed to rebuild " + host.hostname() + ": " + Exceptions.toMessageString(e) + ", will retry in " + interval());
+ log.log(Level.WARNING, "Failed to rebuild " + host.hostname() + ", will retry in " + interval(), e);
}
}
}
@@ -293,7 +293,8 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer {
nodeRepository().nodes().addNodes(hosts, Agent.DynamicProvisioningMaintainer);
return hosts;
} catch (NodeAllocationException | IllegalArgumentException | IllegalStateException e) {
- throw new NodeAllocationException("Failed to provision " + count + " " + nodeResources + ": " + e.getMessage());
+ throw new NodeAllocationException("Failed to provision " + count + " " + nodeResources + ": " + e.getMessage(),
+ ! (e instanceof NodeAllocationException nae) || nae.retryable());
} catch (RuntimeException e) {
throw new RuntimeException("Failed to provision " + count + " " + nodeResources + ", will retry in " + interval(), e);
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
index 3e7abe8f053..32eac49a288 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
@@ -77,23 +77,6 @@ public class NodeFailer extends NodeRepositoryMaintainer {
int throttledHostFailures = 0;
int throttledNodeFailures = 0;
- // Ready nodes
- try (Mutex lock = nodeRepository().nodes().lockUnallocated()) {
- for (FailingNode failing : findReadyFailingNodes()) {
- attempts++;
- if (throttle(failing.node())) {
- failures++;
- if (failing.node().type().isHost())
- throttledHostFailures++;
- else
- throttledNodeFailures++;
- continue;
- }
- nodeRepository().nodes().fail(failing.node().hostname(), Agent.NodeFailer, failing.reason());
- }
- }
-
- // Active nodes
for (FailingNode failing : findActiveFailingNodes()) {
attempts++;
if (!failAllowedFor(failing.node().type())) continue;
@@ -116,22 +99,6 @@ public class NodeFailer extends NodeRepositoryMaintainer {
return asSuccessFactor(attempts, failures);
}
- private Collection<FailingNode> findReadyFailingNodes() {
- Set<FailingNode> failingNodes = new HashSet<>();
- for (Node node : nodeRepository().nodes().list(Node.State.ready)) {
- Node hostNode = node.parentHostname().flatMap(parent -> nodeRepository().nodes().node(parent)).orElse(node);
- List<String> failureReports = reasonsToFailHost(hostNode);
- if (failureReports.size() > 0) {
- if (hostNode.equals(node)) {
- failingNodes.add(new FailingNode(node, "Host has failure reports: " + failureReports));
- } else {
- failingNodes.add(new FailingNode(node, "Parent (" + hostNode + ") has failure reports: " + failureReports));
- }
- }
- }
- return failingNodes;
- }
-
private Collection<FailingNode> findActiveFailingNodes() {
Set<FailingNode> failingNodes = new HashSet<>();
NodeList activeNodes = nodeRepository().nodes().list(Node.State.active);
@@ -150,7 +117,7 @@ public class NodeFailer extends NodeRepositoryMaintainer {
for (Node node : activeNodes) {
if (allSuspended(node, activeNodes)) {
- Node host = node.parentHostname().flatMap(parent -> activeNodes.node(parent)).orElse(node);
+ Node host = node.parentHostname().flatMap(activeNodes::node).orElse(node);
if (host.type().isHost()) {
List<String> failureReports = reasonsToFailHost(host);
if ( ! failureReports.isEmpty()) {
@@ -175,7 +142,7 @@ public class NodeFailer extends NodeRepositoryMaintainer {
/** Returns whether node has any kind of hardware issue */
static boolean hasHardwareIssue(Node node, NodeList allNodes) {
- Node host = node.parentHostname().flatMap(parent -> allNodes.node(parent)).orElse(node);
+ Node host = node.parentHostname().flatMap(allNodes::node).orElse(node);
return reasonsToFailHost(host).size() > 0;
}
@@ -344,30 +311,6 @@ public class NodeFailer extends NodeRepositoryMaintainer {
}
- private static class FailingNode {
-
- private final Node node;
- private final String reason;
-
- public FailingNode(Node node, String reason) {
- this.node = node;
- this.reason = reason;
- }
-
- public Node node() { return node; }
- public String reason() { return reason; }
-
- @Override
- public boolean equals(Object other) {
- if ( ! (other instanceof FailingNode)) return false;
- return ((FailingNode)other).node().equals(this.node());
- }
-
- @Override
- public int hashCode() {
- return node.hashCode();
- }
-
- }
+ private record FailingNode(Node node, String reason) { }
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java
index b43e2ae051f..624492a14f3 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationLockException;
-import com.yahoo.config.provision.HostLivenessTracker;
import com.yahoo.jdisc.Metric;
import com.yahoo.lang.MutableInteger;
import com.yahoo.transaction.Mutex;
@@ -13,12 +12,10 @@ import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
-import com.yahoo.vespa.hosted.provision.node.History;
import com.yahoo.vespa.service.monitor.ServiceMonitor;
import com.yahoo.yolean.Exceptions;
import java.time.Duration;
-import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -34,41 +31,18 @@ import static java.util.stream.Collectors.counting;
*/
public class NodeHealthTracker extends NodeRepositoryMaintainer {
- /** Provides information about the status of ready hosts */
- private final HostLivenessTracker hostLivenessTracker;
-
/** Provides (more accurate) information about the status of active hosts */
private final ServiceMonitor serviceMonitor;
- public NodeHealthTracker(HostLivenessTracker hostLivenessTracker,
- ServiceMonitor serviceMonitor, NodeRepository nodeRepository,
+ public NodeHealthTracker(ServiceMonitor serviceMonitor, NodeRepository nodeRepository,
Duration interval, Metric metric) {
super(nodeRepository, interval, metric);
- this.hostLivenessTracker = hostLivenessTracker;
this.serviceMonitor = serviceMonitor;
}
@Override
protected double maintain() {
- return ( updateReadyNodeLivenessEvents() + updateActiveNodeDownState() ) / 2;
- }
-
- private double updateReadyNodeLivenessEvents() {
- // Update node last request events through ZooKeeper to collect request to all config servers.
- // We do this here ("lazily") to avoid writing to zk for each config request.
- try (Mutex lock = nodeRepository().nodes().lockUnallocated()) {
- for (Node node : nodeRepository().nodes().list(Node.State.ready)) {
- Optional<Instant> lastLocalRequest = hostLivenessTracker.lastRequestFrom(node.hostname());
- if (lastLocalRequest.isEmpty()) continue;
-
- if (!node.history().hasEventAfter(History.Event.Type.requested, lastLocalRequest.get())) {
- History updatedHistory = node.history()
- .with(new History.Event(History.Event.Type.requested, Agent.NodeHealthTracker, lastLocalRequest.get()));
- nodeRepository().nodes().write(node.with(updatedHistory), lock);
- }
- }
- }
- return 1.0;
+ return updateActiveNodeDownState();
}
/**
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
index dac6ee61ef3..708d8b59eb0 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
@@ -5,7 +5,6 @@ import com.yahoo.component.AbstractComponent;
import com.yahoo.component.annotation.Inject;
import com.yahoo.concurrent.maintenance.Maintainer;
import com.yahoo.config.provision.Deployer;
-import com.yahoo.config.provision.HostLivenessTracker;
import com.yahoo.config.provision.InfraDeployer;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Zone;
@@ -33,7 +32,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
@SuppressWarnings("unused")
@Inject
public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, InfraDeployer infraDeployer,
- HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor,
+ ServiceMonitor serviceMonitor,
Zone zone, Metric metric,
ProvisionServiceProvider provisionServiceProvider, FlagSource flagSource,
MetricsFetcher metricsFetcher) {
@@ -46,7 +45,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
maintainers.add(infrastructureProvisioner);
maintainers.add(new NodeFailer(deployer, nodeRepository, defaults.failGrace, defaults.nodeFailerInterval, defaults.throttlePolicy, metric));
- maintainers.add(new NodeHealthTracker(hostLivenessTracker, serviceMonitor, nodeRepository, defaults.nodeFailureStatusUpdateInterval, metric));
+ maintainers.add(new NodeHealthTracker(serviceMonitor, nodeRepository, defaults.nodeFailureStatusUpdateInterval, metric));
maintainers.add(new ExpeditedChangeApplicationMaintainer(deployer, metric, nodeRepository, defaults.expeditedChangeRedeployInterval));
maintainers.add(new ReservationExpirer(nodeRepository, defaults.reservationExpiry, metric));
maintainers.add(new RetiredExpirer(nodeRepository, deployer, metric, defaults.retiredInterval, defaults.retiredExpiry));
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/History.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/History.java
index 14fa2e1c8ff..4aca1cbd056 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/History.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/History.java
@@ -46,8 +46,10 @@ public class History {
private static ImmutableMap<Event.Type, Event> toImmutableMap(Collection<Event> events) {
ImmutableMap.Builder<Event.Type, Event> builder = new ImmutableMap.Builder<>();
- for (Event event : events)
+ for (Event event : events) {
+ if (event.type() == Event.Type.requested) continue; // TODO (freva): Remove requested event after 8.70
builder.put(event.type(), event);
+ }
return builder.build();
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersions.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersions.java
index 89fdf9d4b2a..91219ed0ce2 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersions.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersions.java
@@ -2,6 +2,8 @@
package com.yahoo.vespa.hosted.provision.os;
import com.yahoo.component.Version;
+import com.yahoo.config.provision.Cloud;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.NodeType;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.flags.BooleanFlag;
@@ -38,20 +40,20 @@ public class OsVersions {
private final NodeRepository nodeRepository;
private final CuratorDatabaseClient db;
- private final boolean dynamicProvisioning;
private final int maxDelegatedUpgrades;
private final BooleanFlag softRebuildFlag;
+ private final Cloud cloud;
public OsVersions(NodeRepository nodeRepository) {
- this(nodeRepository, nodeRepository.zone().getCloud().dynamicProvisioning(), MAX_DELEGATED_UPGRADES);
+ this(nodeRepository, nodeRepository.zone().getCloud(), MAX_DELEGATED_UPGRADES);
}
- OsVersions(NodeRepository nodeRepository, boolean dynamicProvisioning, int maxDelegatedUpgrades) {
+ OsVersions(NodeRepository nodeRepository, Cloud cloud, int maxDelegatedUpgrades) {
this.nodeRepository = Objects.requireNonNull(nodeRepository);
this.db = nodeRepository.database();
- this.dynamicProvisioning = dynamicProvisioning;
this.maxDelegatedUpgrades = maxDelegatedUpgrades;
this.softRebuildFlag = Flags.SOFT_REBUILD.bindTo(nodeRepository.flagSource());
+ this.cloud = Objects.requireNonNull(cloud);
// Read and write all versions to make sure they are stored in the latest version of the serialized format
try (var lock = db.lockOsVersionChange()) {
@@ -141,13 +143,13 @@ public class OsVersions {
/** Returns the upgrader to use when upgrading given node type to target */
private OsUpgrader chooseUpgrader(NodeType nodeType, Optional<Version> target) {
- if (dynamicProvisioning) {
- boolean softRebuild = softRebuildFlag.value();
- RetiringOsUpgrader retiringOsUpgrader = new RetiringOsUpgrader(nodeRepository, softRebuild);
- if (softRebuild) {
+ if (cloud.dynamicProvisioning()) {
+ boolean canSoftRebuild = cloud.name().equals(CloudName.AWS) && softRebuildFlag.value();
+ RetiringOsUpgrader retiringOsUpgrader = new RetiringOsUpgrader(nodeRepository, canSoftRebuild);
+ if (canSoftRebuild) {
// If soft rebuild is enabled, we can use RebuildingOsUpgrader for hosts with remote storage.
// RetiringOsUpgrader is then only used for hosts with local storage.
- return new CompositeOsUpgrader(List.of(new RebuildingOsUpgrader(nodeRepository, softRebuild),
+ return new CompositeOsUpgrader(List.of(new RebuildingOsUpgrader(nodeRepository, canSoftRebuild),
retiringOsUpgrader));
}
return retiringOsUpgrader;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
index b4e304155a6..a5ebc6b3efc 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.persistence;
+import ai.vespa.http.DomainName;
import com.yahoo.component.Version;
import com.yahoo.concurrent.UncheckedTimeoutException;
import com.yahoo.config.provision.ApplicationId;
@@ -484,15 +485,12 @@ public class CuratorDatabaseClient {
transaction.onCommitted(() -> {
for (var lb : loadBalancers) {
if (lb.state() == fromState) continue;
+ Optional<String> target = lb.instance().flatMap(instance -> instance.hostname().map(DomainName::value).or(instance::ipAddress));
if (fromState == null) {
- log.log(Level.INFO, () -> "Creating " + lb.id() + lb.instance()
- .map(instance -> " (" + instance.hostname() + ")")
- .orElse("") +
+ log.log(Level.INFO, () -> "Creating " + lb.id() + target.map(t -> " (" + t + ")").orElse("") +
" in " + lb.state());
} else {
- log.log(Level.INFO, () -> "Moving " + lb.id() + lb.instance()
- .map(instance -> " (" + instance.hostname() + ")")
- .orElse("") +
+ log.log(Level.INFO, () -> "Moving " + lb.id() + target.map(t -> " (" + t + ")").orElse("") +
" from " + fromState +
" to " + lb.state());
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
index 5d9d13c48dc..35f04683157 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
@@ -132,9 +132,9 @@ public class GroupPreparer {
}
if (! allocation.fulfilled() && requestedNodes.canFail())
- throw new NodeAllocationException((cluster.group().isPresent() ? "Node allocation failure on " +
- cluster.group().get() : "") +
- allocation.allocationFailureDetails());
+ throw new NodeAllocationException((cluster.group().isPresent() ? "Node allocation failure on " + cluster.group().get()
+ : "") + allocation.allocationFailureDetails(),
+ true);
// Carry out and return allocation
nodeRepository.nodes().reserve(allocation.reservableNodes());
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java
index ef6c0da9169..820a654c620 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java
@@ -43,7 +43,8 @@ class Preparer {
catch (NodeAllocationException e) {
throw new NodeAllocationException("Could not satisfy " + requestedNodes +
( wantedGroups > 1 ? " (in " + wantedGroups + " groups)" : "") +
- " in " + application + " " + cluster + ": " + e.getMessage());
+ " in " + application + " " + cluster + ": " + e.getMessage(),
+ e.retryable());
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java
index 83a80847ec8..024f071abb1 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java
@@ -21,7 +21,6 @@ public class ContainerConfig {
" <component id='com.yahoo.vespa.hosted.provision.testutils.MockDeployer'/>\n" +
" <component id='com.yahoo.vespa.hosted.provision.testutils.MockInfraDeployer'/>\n" +
" <component id='com.yahoo.vespa.hosted.provision.testutils.MockProvisioner'/>\n" +
- " <component id='com.yahoo.vespa.hosted.provision.testutils.TestHostLivenessTracker'/>\n" +
" <component id='com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub'/>\n" +
" <component id='com.yahoo.vespa.hosted.provision.testutils.MockDuperModel'/>\n" +
" <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors'/>\n" +
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
index 13753c12664..3ebaf764115 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
@@ -66,7 +66,7 @@ public class MockHostProvisioner implements HostProvisioner {
Optional<CloudAccount> cloudAccount) {
Flavor hostFlavor = this.hostFlavor.orElseGet(() -> flavors.stream().filter(f -> compatible(f, resources))
.findFirst()
- .orElseThrow(() -> new NodeAllocationException("No host flavor matches " + resources)));
+ .orElseThrow(() -> new NodeAllocationException("No host flavor matches " + resources, true)));
List<ProvisionedHost> hosts = new ArrayList<>();
for (int index : provisionIndices) {
String hostHostname = hostType == NodeType.host ? "hostname" + index : hostType.name() + index;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/TestHostLivenessTracker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/TestHostLivenessTracker.java
deleted file mode 100644
index 28d0d5f89d7..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/TestHostLivenessTracker.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.testutils;
-
-import com.yahoo.config.provision.HostLivenessTracker;
-
-import java.time.Clock;
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-/** This is a fully functional implementation */
-public class TestHostLivenessTracker implements HostLivenessTracker {
-
- private final Clock clock;
- private final Map<String, Instant> lastRequestFromHost = new HashMap<>();
-
- public TestHostLivenessTracker(Clock clock) {
- this.clock = clock;
- }
-
- @Override
- public void receivedRequestFrom(String hostname) {
- lastRequestFromHost.put(hostname, clock.instant());
- }
-
- @Override
- public Optional<Instant> lastRequestFrom(String hostname) {
- return Optional.ofNullable(lastRequestFromHost.get(hostname));
- }
-
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java
index 9e3213fc977..90e67a9b0cc 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java
@@ -13,12 +13,13 @@ import java.io.File;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -35,9 +36,7 @@ public class QuestMetricsDbTest {
@Test
public void testNodeMetricsReadWrite() {
- String dataDir = "data/QuestMetricsDbReadWrite";
- IOUtils.recursiveDeleteDir(new File(dataDir));
- IOUtils.createDirectory(dataDir + "/metrics");
+ String dataDir = createEmptyDataDir("QuestMetricsDbReadWrite", "metrics");
ManualClock clock = new ManualClock("2020-10-01T00:00:00");
QuestMetricsDb db = new QuestMetricsDb(dataDir, clock);
Instant startTime = clock.instant();
@@ -81,9 +80,7 @@ public class QuestMetricsDbTest {
@Test
public void testClusterMetricsReadWrite() {
- String dataDir = "data/QuestMetricsDbReadWrite";
- IOUtils.recursiveDeleteDir(new File(dataDir));
- IOUtils.createDirectory(dataDir + "/clusterMetrics");
+ String dataDir = createEmptyDataDir("QuestMetricsDbReadWrite", "clusterMetrics");
ManualClock clock = new ManualClock("2020-10-01T00:00:00");
QuestMetricsDb db = new QuestMetricsDb(dataDir, clock);
Instant startTime = clock.instant();
@@ -134,9 +131,7 @@ public class QuestMetricsDbTest {
@Test
public void testWriteOldData() {
- String dataDir = "data/QuestMetricsDbWriteOldData";
- IOUtils.recursiveDeleteDir(new File(dataDir));
- IOUtils.createDirectory(dataDir + "/metrics");
+ String dataDir = createEmptyDataDir("QuestMetricsDbWriteOldData", "metrics");
ManualClock clock = new ManualClock("2020-10-01T00:00:00");
QuestMetricsDb db = new QuestMetricsDb(dataDir, clock);
Instant startTime = clock.instant();
@@ -161,9 +156,7 @@ public class QuestMetricsDbTest {
@Test
public void testGc() {
- String dataDir = "data/QuestMetricsDbGc";
- IOUtils.recursiveDeleteDir(new File(dataDir));
- IOUtils.createDirectory(dataDir + "/metrics");
+ String dataDir = createEmptyDataDir("QuestMetricsDbGc", "metrics");
ManualClock clock = new ManualClock();
int days = 10; // The first metrics are this many days in the past
clock.retreat(Duration.ofDays(10));
@@ -189,7 +182,7 @@ public class QuestMetricsDbTest {
@Ignore
@Test
public void testReadingAndAppendingToExistingData() {
- String dataDir = "data/QuestMetricsDbExistingData";
+ String dataDir = dataDir("QuestMetricsDbExistingData");
if ( ! new File(dataDir).exists()) {
System.out.println("No existing data to check");
return;
@@ -219,9 +212,7 @@ public class QuestMetricsDbTest {
@Ignore
@Test
public void updateExistingData() {
- String dataDir = "data/QuestMetricsDbExistingData";
- IOUtils.recursiveDeleteDir(new File(dataDir));
- IOUtils.createDirectory(dataDir + "/metrics");
+ String dataDir = createEmptyDataDir("QuestMetricsDbExistingData", "metrics");
ManualClock clock = new ManualClock("2020-10-01T00:00:00");
QuestMetricsDb db = new QuestMetricsDb(dataDir, clock);
Instant startTime = clock.instant();
@@ -249,16 +240,6 @@ public class QuestMetricsDbTest {
return timeseries;
}
- private List<ClusterMetricSnapshot> clusterTimeseries(int count, Duration sampleRate, ManualClock clock,
- ClusterSpec.Id cluster) {
- List<ClusterMetricSnapshot> timeseries = new ArrayList<>();
- for (int i = 1; i <= count; i++) {
- timeseries.add(new ClusterMetricSnapshot(clock.instant(), 30.0, 0.0));
- clock.advance(sampleRate);
- }
- return timeseries;
- }
-
private Collection<Pair<String, NodeMetricSnapshot>> timeseriesAt(int countPerHost, Instant at, String ... hosts) {
Collection<Pair<String, NodeMetricSnapshot>> timeseries = new ArrayList<>();
for (int i = 1; i <= countPerHost; i++) {
@@ -272,4 +253,17 @@ public class QuestMetricsDbTest {
return timeseries;
}
+ private static String dataDir(String name) {
+ return "target/questdb/" + name;
+ }
+
+ private static String createEmptyDataDir(String name, String... subPath) {
+ String dataDir = dataDir(name);
+ IOUtils.recursiveDeleteDir(new File(dataDir));
+ String path = Stream.concat(Stream.of(dataDir), Arrays.stream(subPath))
+ .collect(Collectors.joining("/"));
+ IOUtils.createDirectory(path);
+ return dataDir;
+ }
+
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
index f67e9cd8345..977d72c11ea 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
@@ -27,7 +27,6 @@ import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner;
import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester;
import com.yahoo.vespa.hosted.provision.testutils.MockDeployer;
import com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub;
-import com.yahoo.vespa.hosted.provision.testutils.TestHostLivenessTracker;
import java.time.Clock;
import java.time.Duration;
@@ -63,7 +62,6 @@ public class NodeFailTester {
public ServiceMonitorStub serviceMonitor;
public MockDeployer deployer;
public TestMetric metric;
- private final TestHostLivenessTracker hostLivenessTracker;
private final NodeRepositoryProvisioner provisioner;
private final Curator curator;
@@ -74,7 +72,6 @@ public class NodeFailTester {
curator = tester.getCurator();
nodeRepository = tester.nodeRepository();
provisioner = tester.provisioner();
- hostLivenessTracker = new TestHostLivenessTracker(clock);
}
private void initializeMaintainers(Map<ApplicationId, MockDeployer.ApplicationContext> apps) {
@@ -112,7 +109,7 @@ public class NodeFailTester {
/** Create hostCount hosts, one app with containerCount containers, and one app with contentCount content nodes. */
public static NodeFailTester withTwoApplications(int hostCount, int containerCount, int contentCount) {
NodeFailTester tester = new NodeFailTester();
- tester.createHostNodes(hostCount);
+ tester.tester.makeReadyHosts(hostCount, new NodeResources(2, 8, 20, 10));
// Create tenant host application
ClusterSpec clusterNodeAdminApp = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin")).vespaVersion("6.42").build();
@@ -139,13 +136,7 @@ public class NodeFailTester {
public static NodeFailTester withTwoApplications(int numberOfHosts) {
NodeFailTester tester = new NodeFailTester();
-
- int nodesPerHost = 3;
- List<Node> hosts = tester.createHostNodes(numberOfHosts);
- for (int i = 0; i < hosts.size(); i++) {
- tester.createReadyNodes(nodesPerHost, i * nodesPerHost, Optional.of("parent" + (i + 1)),
- new NodeResources(1, 4, 100, 0.3), NodeType.tenant);
- }
+ tester.tester.makeReadyNodes(numberOfHosts, new NodeResources(4, 16, 400, 10), NodeType.host, 8);
// Create applications
ClusterSpec clusterNodeAdminApp = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin")).vespaVersion("6.42").build();
@@ -230,26 +221,11 @@ public class NodeFailTester {
}
public NodeHealthTracker createUpdater() {
- return new NodeHealthTracker(hostLivenessTracker, serviceMonitor, nodeRepository, Duration.ofMinutes(5), metric);
- }
-
- public void allNodesMakeAConfigRequestExcept(Node ... deadNodeArray) {
- allNodesMakeAConfigRequestExcept(List.of(deadNodeArray));
- }
-
- public void allNodesMakeAConfigRequestExcept(List<Node> deadNodes) {
- for (Node node : nodeRepository.nodes().list()) {
- if ( ! deadNodes.contains(node))
- hostLivenessTracker.receivedRequestFrom(node.hostname());
- }
+ return new NodeHealthTracker(serviceMonitor, nodeRepository, Duration.ofMinutes(5), metric);
}
public Clock clock() { return clock; }
- public List<Node> createReadyNodes(int count) {
- return createReadyNodes(count, 0);
- }
-
public List<Node> createReadyNodes(int count, NodeResources resources) {
return createReadyNodes(count, 0, resources);
}
@@ -258,22 +234,10 @@ public class NodeFailTester {
return createReadyNodes(count, 0, Optional.empty(), hostFlavors.getFlavorOrThrow("default"), nodeType);
}
- public List<Node> createReadyNodes(int count, int startIndex) {
- return createReadyNodes(count, startIndex, "default");
- }
-
- public List<Node> createReadyNodes(int count, int startIndex, String flavor) {
- return createReadyNodes(count, startIndex, Optional.empty(), hostFlavors.getFlavorOrThrow(flavor), NodeType.tenant);
- }
-
public List<Node> createReadyNodes(int count, int startIndex, NodeResources resources) {
return createReadyNodes(count, startIndex, Optional.empty(), new Flavor(resources), NodeType.tenant);
}
- private List<Node> createReadyNodes(int count, int startIndex, Optional<String> parentHostname, NodeResources resources, NodeType nodeType) {
- return createReadyNodes(count, startIndex, parentHostname, new Flavor(resources), nodeType);
- }
-
private List<Node> createReadyNodes(int count, int startIndex, Optional<String> parentHostname, Flavor flavor, NodeType nodeType) {
List<Node> nodes = new ArrayList<>(count);
int lastOctetOfPoolAddress = 0;
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
index 3ba536ee4d7..f3a4b5a9284 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
@@ -13,7 +13,6 @@ import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.Report;
-import com.yahoo.vespa.hosted.provision.node.Reports;
import org.junit.Test;
import java.time.Duration;
@@ -21,6 +20,7 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -62,23 +62,21 @@ public class NodeFailerTest {
}
private void testNodeFailingWith(NodeFailTester tester, String hostWithHwFailure) {
- // The host should have 2 nodes in active and 1 ready
+ // The host should have 2 nodes in active
Map<Node.State, List<String>> hostnamesByState = tester.nodeRepository.nodes().list().childrenOf(hostWithHwFailure).asList().stream()
.collect(Collectors.groupingBy(Node::state, Collectors.mapping(Node::hostname, Collectors.toList())));
assertEquals(2, hostnamesByState.get(Node.State.active).size());
- assertEquals(1, hostnamesByState.get(Node.State.ready).size());
// Suspend the first of the active nodes
tester.suspend(hostnamesByState.get(Node.State.active).get(0));
tester.runMaintainers();
tester.clock.advance(Duration.ofHours(25));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
// The first (and the only) ready node and the 1st active node that was allowed to fail should be failed
Map<Node.State, List<String>> expectedHostnamesByState1Iter = Map.of(
- Node.State.failed, List.of(hostnamesByState.get(Node.State.ready).get(0), hostnamesByState.get(Node.State.active).get(0)),
+ Node.State.failed, List.of(hostnamesByState.get(Node.State.active).get(0)),
Node.State.active, hostnamesByState.get(Node.State.active).subList(1, 2));
Map<Node.State, List<String>> hostnamesByState1Iter = tester.nodeRepository.nodes().list().childrenOf(hostWithHwFailure).asList().stream()
.collect(Collectors.groupingBy(Node::state, Collectors.mapping(Node::hostname, Collectors.toList())));
@@ -88,7 +86,6 @@ public class NodeFailerTest {
tester.suspend(hostnamesByState.get(Node.State.active).get(1));
tester.clock.advance(Duration.ofHours(25));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
// All of the children should be failed now
@@ -101,7 +98,7 @@ public class NodeFailerTest {
tester.suspend(hostWithHwFailure);
tester.runMaintainers();
assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(hostWithHwFailure).get().state());
- assertEquals(4, tester.nodeRepository.nodes().list(Node.State.failed).size());
+ assertEquals(3, tester.nodeRepository.nodes().list(Node.State.failed).size());
}
@Test
@@ -110,14 +107,12 @@ public class NodeFailerTest {
String hostWithFailureReports = selectFirstParentHostWithNActiveNodesExcept(tester.nodeRepository, 2);
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(hostWithFailureReports).get().state());
- // The host has 2 nodes in active and 1 ready
+ // The host has 2 nodes in active
Map<Node.State, List<String>> hostnamesByState = tester.nodeRepository.nodes().list().childrenOf(hostWithFailureReports).asList().stream()
.collect(Collectors.groupingBy(Node::state, Collectors.mapping(Node::hostname, Collectors.toList())));
assertEquals(2, hostnamesByState.get(Node.State.active).size());
String activeChild1 = hostnamesByState.get(Node.State.active).get(0);
String activeChild2 = hostnamesByState.get(Node.State.active).get(1);
- assertEquals(1, hostnamesByState.get(Node.State.ready).size());
- String readyChild = hostnamesByState.get(Node.State.ready).get(0);
// Set failure report to the parent and all its children.
Report badTotalMemorySizeReport = Report.basicReport("badTotalMemorySize", HARD_FAIL, Instant.now(), "too low");
@@ -128,20 +123,16 @@ public class NodeFailerTest {
tester.nodeRepository.nodes().write(updatedNode, () -> {});
});
- // The ready node will be failed, but neither the host nor the 2 active nodes since they have not been suspended
- tester.allNodesMakeAConfigRequestExcept();
+ // Neither the host nor the 2 active nodes are failed out because they have not been suspended
tester.runMaintainers();
- assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(readyChild).get().state());
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(hostWithFailureReports).get().state());
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(activeChild1).get().state());
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(activeChild2).get().state());
- // Suspending the host will not fail any more since none of the children are suspened
+ // Suspending the host will not fail any more since none of the children are suspended
tester.suspend(hostWithFailureReports);
tester.clock.advance(Duration.ofHours(25));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
- assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(readyChild).get().state());
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(hostWithFailureReports).get().state());
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(activeChild1).get().state());
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(activeChild2).get().state());
@@ -149,9 +140,7 @@ public class NodeFailerTest {
// Suspending one child node will fail that out.
tester.suspend(activeChild1);
tester.clock.advance(Duration.ofHours(25));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
- assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(readyChild).get().state());
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(hostWithFailureReports).get().state());
assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(activeChild1).get().state());
assertEquals(Node.State.active, tester.nodeRepository.nodes().node(activeChild2).get().state());
@@ -159,9 +148,8 @@ public class NodeFailerTest {
// Suspending the second child node will fail that out and the host.
tester.suspend(activeChild2);
tester.clock.advance(Duration.ofHours(25));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
- assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(readyChild).get().state());
+ tester.runMaintainers(); // hosts are typically failed in the 2. maintain()
assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(hostWithFailureReports).get().state());
assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(activeChild1).get().state());
assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(activeChild2).get().state());
@@ -210,31 +198,18 @@ public class NodeFailerTest {
@Test
public void node_failing() {
- NodeFailTester tester = NodeFailTester.withTwoApplications();
+ NodeFailTester tester = NodeFailTester.withTwoApplications(6);
// For a day all nodes work so nothing happens
for (int minutes = 0; minutes < 24 * 60; minutes +=5 ) {
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
- assertEquals( 0, tester.deployer.redeployments);
- assertEquals(12, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals( 0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
- assertEquals( 4, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
+ assertEquals(0, tester.deployer.redeployments);
+ assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
+ assertEquals(0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
}
- // Hardware failures are detected on two ready nodes, which are then failed
- Node readyFail1 = tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).asList().get(2);
- Node readyFail2 = tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).asList().get(3);
- tester.nodeRepository.nodes().write(readyFail1.with(new Reports().withReport(badTotalMemorySizeReport)), () -> {});
- tester.nodeRepository.nodes().write(readyFail2.with(new Reports().withReport(badTotalMemorySizeReport)), () -> {});
- assertEquals(4, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
- tester.runMaintainers();
- assertEquals(2, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
- assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(readyFail1.hostname()).get().state());
- assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(readyFail2.hostname()).get().state());
-
String downHost1 = tester.nodeRepository.nodes().list(Node.State.active).owner(NodeFailTester.app1).asList().get(1).hostname();
String downHost2 = tester.nodeRepository.nodes().list(Node.State.active).owner(NodeFailTester.app2).asList().get(3).hostname();
tester.serviceMonitor.setHostDown(downHost1);
@@ -243,41 +218,34 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 45; minutes +=5 ) {
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
- assertEquals( 0, tester.deployer.redeployments);
- assertEquals(12, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals( 2, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
- assertEquals( 2, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
+ assertEquals(0, tester.deployer.redeployments);
+ assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
+ assertEquals(0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
}
tester.serviceMonitor.setHostUp(downHost1);
// downHost2 should now be failed and replaced, but not downHost1
tester.clock.advance(Duration.ofDays(1));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
- assertEquals( 1, tester.deployer.redeployments);
- assertEquals(12, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals( 3, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
- assertEquals( 1, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
+ assertEquals(1, tester.deployer.redeployments);
+ assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
+ assertEquals(1, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertEquals(downHost2, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).asList().get(0).hostname());
// downHost1 fails again
tester.serviceMonitor.setHostDown(downHost1);
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
// the system goes down
tester.clock.advance(Duration.ofMinutes(120));
tester.failer = tester.createFailer();
tester.runMaintainers();
// the host is still down and fails
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
- assertEquals( 2, tester.deployer.redeployments);
- assertEquals(12, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals( 4, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
- assertEquals( 0, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
+ assertEquals(2, tester.deployer.redeployments);
+ assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
+ assertEquals(2, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
// the last host goes down
Node lastNode = tester.highestIndex(tester.nodeRepository.nodes().list(Node.State.active).owner(NodeFailTester.app1));
@@ -286,23 +254,19 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 75; minutes +=5 ) {
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
- assertEquals( 2, tester.deployer.redeployments);
- assertEquals(12, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals( 4, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
- assertEquals( 0, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
+ assertEquals(2, tester.deployer.redeployments);
+ assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
+ assertEquals(2, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
}
// A new node is available
tester.createReadyNodes(1, 16, NodeFailTester.nodeResources);
tester.clock.advance(Duration.ofDays(1));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
// The node is now failed
- assertEquals( 3, tester.deployer.redeployments);
- assertEquals(12, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals( 5, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
- assertEquals( 0, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
+ assertEquals(3, tester.deployer.redeployments);
+ assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
+ assertEquals(3, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertTrue("The index of the last failed node is not reused",
tester.highestIndex(tester.nodeRepository.nodes().list(Node.State.active).owner(NodeFailTester.app1)).allocation().get().membership().index()
>
@@ -319,12 +283,10 @@ public class NodeFailerTest {
String downNode = tester.nodeRepository.nodes().list(Node.State.active).owner(NodeFailTester.app1).asList().get(1).hostname();
tester.serviceMonitor.setHostDown(downNode);
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
tester.clock.advance(Duration.ofMinutes(75));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(1, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(downNode).get().state());
@@ -332,12 +294,10 @@ public class NodeFailerTest {
// Re-activate the node. It is still down, but should not be failed out until the grace period has passed again
tester.nodeRepository.nodes().reactivate(downNode, Agent.system, getClass().getSimpleName());
tester.clock.advance(Duration.ofMinutes(30));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
tester.clock.advance(Duration.ofMinutes(45));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(1, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertEquals(Node.State.failed, tester.nodeRepository.nodes().node(downNode).get().state());
@@ -360,7 +320,6 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 45; minutes +=5 ) {
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
assertEquals(0, tester.deployer.redeployments);
assertEquals(3, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
assertEquals(0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
@@ -368,7 +327,6 @@ public class NodeFailerTest {
// downHost should now be failed and replaced
tester.clock.advance(Duration.ofDays(1));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(1, tester.deployer.redeployments);
assertEquals(1, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
@@ -403,37 +361,15 @@ public class NodeFailerTest {
}
@Test
- public void host_not_failed_without_config_requests() {
- NodeFailTester tester = NodeFailTester.withTwoApplications();
-
- // For a day all nodes work so nothing happens
- for (int minutes = 0, interval = 30; minutes < 24 * 60; minutes += interval) {
- tester.clock.advance(Duration.ofMinutes(interval));
- tester.allNodesMakeAConfigRequestExcept();
- tester.runMaintainers();
- assertEquals( 3, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.host).size());
- assertEquals( 0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.host).size());
- }
-
- tester.clock.advance(Duration.ofMinutes(180));
- Node host = tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.host).first().get();
- tester.allNodesMakeAConfigRequestExcept(host);
- tester.runMaintainers();
- assertEquals( 3, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.host).size());
- assertEquals( 0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.host).size());
- }
-
- @Test
public void failing_hosts() {
NodeFailTester tester = NodeFailTester.withTwoApplications(7);
// For a day all nodes work so nothing happens
for (int minutes = 0, interval = 30; minutes < 24 * 60; minutes += interval) {
tester.clock.advance(Duration.ofMinutes(interval));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals(13, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
+ assertEquals(0, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
assertEquals(7, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.host).size());
}
@@ -446,21 +382,17 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 45; minutes += 5 ) {
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
assertEquals(0, tester.deployer.redeployments);
assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals(13, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
assertEquals(7, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.host).size());
}
tester.clock.advance(Duration.ofMinutes(30));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(2, tester.deployer.redeployments);
- assertEquals(3, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
+ assertEquals(2, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals(10, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
assertEquals(7, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.host).size());
assertEquals(0, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.host).size());
@@ -468,9 +400,8 @@ public class NodeFailerTest {
tester.runMaintainers();
assertEquals(2 + 1, tester.deployer.redeployments);
- assertEquals(3, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
+ assertEquals(2, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals(10, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
assertEquals(6, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.host).size());
assertEquals(1, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.host).size());
@@ -483,18 +414,15 @@ public class NodeFailerTest {
for (int minutes = 0, interval = 30; minutes < 24 * 60; minutes += interval) {
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(interval));
- tester.allNodesMakeAConfigRequestExcept();
- assertEquals(3 + 1, tester.nodeRepository.nodes().list(Node.State.failed).size());
+ assertEquals(2 + 1, tester.nodeRepository.nodes().list(Node.State.failed).size());
}
tester.clock.advance(Duration.ofMinutes(30));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(3 + 1, tester.deployer.redeployments);
- assertEquals(4, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
+ assertEquals(3, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals(9, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
assertEquals(6, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.host).size());
@@ -503,14 +431,12 @@ public class NodeFailerTest {
tester.serviceMonitor.setHostDown(downHost2);
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(90));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
tester.runMaintainers(); // The host is failed in the 2. maintain()
assertEquals(5 + 2, tester.deployer.redeployments);
- assertEquals(7, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
+ assertEquals(5, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals(6, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
assertEquals(5, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.host).size());
@@ -520,13 +446,11 @@ public class NodeFailerTest {
tester.serviceMonitor.setHostDown(downHost3);
tester.runMaintainers();
tester.clock.advance(Duration.ofDays(1));
- tester.allNodesMakeAConfigRequestExcept();
tester.runMaintainers();
assertEquals(6 + 2, tester.deployer.redeployments);
- assertEquals(9, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
+ assertEquals(6, tester.nodeRepository.nodes().list(Node.State.failed).nodeType(NodeType.tenant).size());
assertEquals(8, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.tenant).size());
- assertEquals(4, tester.nodeRepository.nodes().list(Node.State.ready).nodeType(NodeType.tenant).size());
assertEquals(5, tester.nodeRepository.nodes().list(Node.State.active).nodeType(NodeType.host).size());
}
@@ -547,7 +471,6 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 24 * 60; minutes +=5 ) {
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
assertEquals(count, tester.nodeRepository.nodes().list(Node.State.active).nodeType(nodeType).size());
}
@@ -560,7 +483,6 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 45; minutes +=5 ) {
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(5));
- tester.allNodesMakeAConfigRequestExcept();
assertEquals( 0, tester.deployer.redeployments);
assertEquals(count, tester.nodeRepository.nodes().list(Node.State.active).nodeType(nodeType).size());
}
@@ -582,38 +504,30 @@ public class NodeFailerTest {
}
@Test
- public void failing_divergent_ready_nodes() {
- NodeFailTester tester = NodeFailTester.withNoApplications();
-
- Node readyNode = tester.createReadyNodes(1).get(0);
-
- tester.runMaintainers();
- assertEquals(Node.State.ready, readyNode.state());
-
- tester.nodeRepository.nodes().write(readyNode.with(new Reports().withReport(badTotalMemorySizeReport)), () -> {});
-
- tester.runMaintainers();
- assertEquals(1, tester.nodeRepository.nodes().list(Node.State.failed).size());
- }
-
- @Test
public void node_failing_throttle() {
// Throttles based on an absolute number in small zone
{
- // 10 hosts with 3 tenant nodes each, total 40 nodes
- NodeFailTester tester = NodeFailTester.withTwoApplications(10);
- NodeList hosts = tester.nodeRepository.nodes().list().nodeType(NodeType.host);
+ // 10 hosts with 7 container and 7 content nodes, total 24 nodes
+ NodeFailTester tester = NodeFailTester.withTwoApplications(10, 7, 7);
+
+ List<String> failedHostHostnames = tester.nodeRepository.nodes().list().stream()
+ .flatMap(node -> node.parentHostname().stream())
+ .collect(Collectors.groupingBy(h -> h, Collectors.counting()))
+ .entrySet().stream()
+ .sorted(Comparator.comparingLong((Map.Entry<String, Long> e) -> e.getValue()).reversed())
+ .limit(3)
+ .map(Map.Entry::getKey)
+ .toList();
// 3 hosts fail. 2 of them and all of their children are allowed to fail
- List<Node> failedHosts = hosts.asList().subList(0, 3);
- failedHosts.forEach(host -> tester.serviceMonitor.setHostDown(host.hostname()));
+ failedHostHostnames.forEach(hostname -> tester.serviceMonitor.setHostDown(hostname));
tester.runMaintainers();
tester.clock.advance(Duration.ofMinutes(61));
tester.runMaintainers();
tester.runMaintainers(); // hosts are typically failed in the 2. maintain()
assertEquals(2 + /* hosts */
- (2 * 3) /* containers per host */,
+ (2 * 2) /* containers per host */,
tester.nodeRepository.nodes().list(Node.State.failed).size());
assertEquals("Throttling is indicated by the metric", 1, tester.metric.values.get(NodeFailer.throttlingActiveMetric));
assertEquals("Throttled host failures", 1, tester.metric.values.get(NodeFailer.throttledHostFailuresMetric));
@@ -623,7 +537,7 @@ public class NodeFailerTest {
tester.clock.advance(Duration.ofMinutes(interval));
}
tester.runMaintainers();
- assertEquals(8, tester.nodeRepository.nodes().list(Node.State.failed).size());
+ assertEquals(6, tester.nodeRepository.nodes().list(Node.State.failed).size());
assertEquals("Throttling is indicated by the metric", 1, tester.metric.values.get(NodeFailer.throttlingActiveMetric));
assertEquals("Throttled host failures", 1, tester.metric.values.get(NodeFailer.throttledHostFailuresMetric));
@@ -631,14 +545,14 @@ public class NodeFailerTest {
tester.clock.advance(Duration.ofMinutes(30));
tester.runMaintainers();
tester.runMaintainers(); // hosts are failed in the 2. maintain()
- assertEquals(12, tester.nodeRepository.nodes().list(Node.State.failed).size());
+ assertEquals(9, tester.nodeRepository.nodes().list(Node.State.failed).size());
assertEquals("Throttling is not indicated by the metric, as no throttled attempt is made", 0, tester.metric.values.get(NodeFailer.throttlingActiveMetric));
assertEquals("No throttled node failures", 0, tester.metric.values.get(NodeFailer.throttledNodeFailuresMetric));
// Nothing else to fail
tester.clock.advance(Duration.ofHours(25));
tester.runMaintainers();
- assertEquals(12, tester.nodeRepository.nodes().list(Node.State.failed).size());
+ assertEquals(9, tester.nodeRepository.nodes().list(Node.State.failed).size());
assertEquals("Throttling is not indicated by the metric", 0, tester.metric.values.get(NodeFailer.throttlingActiveMetric));
assertEquals("No throttled node failures", 0, tester.metric.values.get(NodeFailer.throttledNodeFailuresMetric));
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java
index 4d75b8a5acc..adcd40866d0 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java
@@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.provision.os;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Cloud;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.NodeResources;
@@ -96,7 +98,7 @@ public class OsVersionsTest {
public void max_active_upgrades() {
int totalNodes = 20;
int maxActiveUpgrades = 5;
- var versions = new OsVersions(tester.nodeRepository(), false, maxActiveUpgrades);
+ var versions = new OsVersions(tester.nodeRepository(), Cloud.defaultCloud(), maxActiveUpgrades);
provisionInfraApplication(totalNodes);
Supplier<NodeList> hostNodes = () -> tester.nodeRepository().nodes().list().state(Node.State.active).hosts();
@@ -161,7 +163,7 @@ public class OsVersionsTest {
@Test
public void upgrade_by_retiring() {
- var versions = new OsVersions(tester.nodeRepository(), true, Integer.MAX_VALUE);
+ var versions = new OsVersions(tester.nodeRepository(), Cloud.builder().dynamicProvisioning(true).build(), Integer.MAX_VALUE);
var clock = (ManualClock) tester.nodeRepository().clock();
int hostCount = 10;
// Provision hosts and children
@@ -229,7 +231,7 @@ public class OsVersionsTest {
@Test
public void upgrade_by_retiring_everything_at_once() {
- var versions = new OsVersions(tester.nodeRepository(), true, Integer.MAX_VALUE);
+ var versions = new OsVersions(tester.nodeRepository(), Cloud.builder().dynamicProvisioning(true).build(), Integer.MAX_VALUE);
int hostCount = 3;
provisionInfraApplication(hostCount, infraApplication, NodeType.confighost);
Supplier<NodeList> hostNodes = () -> tester.nodeRepository().nodes().list()
@@ -254,7 +256,7 @@ public class OsVersionsTest {
@Test
public void upgrade_by_rebuilding() {
tester.flagSource().withIntFlag(PermanentFlags.MAX_REBUILDS.id(), 1);
- var versions = new OsVersions(tester.nodeRepository(), false, Integer.MAX_VALUE);
+ var versions = new OsVersions(tester.nodeRepository(), Cloud.defaultCloud(), Integer.MAX_VALUE);
int hostCount = 10;
provisionInfraApplication(hostCount + 1);
Supplier<NodeList> hostNodes = () -> tester.nodeRepository().nodes().list().nodeType(NodeType.host);
@@ -333,7 +335,7 @@ public class OsVersionsTest {
tester.flagSource().withIntFlag(PermanentFlags.MAX_REBUILDS.id(), maxRebuilds);
tester.flagSource().withBooleanFlag(Flags.SOFT_REBUILD.id(), softRebuild);
- var versions = new OsVersions(tester.nodeRepository(), true, Integer.MAX_VALUE);
+ var versions = new OsVersions(tester.nodeRepository(), Cloud.builder().dynamicProvisioning(true).name(CloudName.AWS).build(), Integer.MAX_VALUE);
provisionInfraApplication(hostCount, infraApplication, NodeType.host, NodeResources.StorageType.remote);
Supplier<NodeList> hostNodes = () -> tester.nodeRepository().nodes().list().nodeType(NodeType.host);
@@ -378,7 +380,7 @@ public class OsVersionsTest {
@Test
public void upgrade_by_rebuilding_multiple_host_types() {
tester.flagSource().withIntFlag(PermanentFlags.MAX_REBUILDS.id(), 1);
- var versions = new OsVersions(tester.nodeRepository(), false, Integer.MAX_VALUE);
+ var versions = new OsVersions(tester.nodeRepository(), Cloud.defaultCloud(), Integer.MAX_VALUE);
int hostCount = 3;
provisionInfraApplication(hostCount, infraApplication, NodeType.host);
provisionInfraApplication(hostCount, ApplicationId.from("hosted-vespa", "confighost", "default"), NodeType.confighost);
@@ -411,7 +413,7 @@ public class OsVersionsTest {
@Test
public void upgrade_by_rebuilding_is_limited_by_stateful_clusters() {
tester.flagSource().withIntFlag(PermanentFlags.MAX_REBUILDS.id(), 3);
- var versions = new OsVersions(tester.nodeRepository(), false, Integer.MAX_VALUE);
+ var versions = new OsVersions(tester.nodeRepository(), Cloud.defaultCloud(), Integer.MAX_VALUE);
int hostCount = 5;
ApplicationId app1 = ApplicationId.from("t1", "a1", "i1");
ApplicationId app2 = ApplicationId.from("t2", "a2", "i2");
@@ -489,7 +491,7 @@ public class OsVersionsTest {
public void upgrade_by_rebuilding_limits_infrastructure_host() {
int hostCount = 3;
tester.flagSource().withIntFlag(PermanentFlags.MAX_REBUILDS.id(), hostCount);
- var versions = new OsVersions(tester.nodeRepository(), false, Integer.MAX_VALUE);
+ var versions = new OsVersions(tester.nodeRepository(), Cloud.defaultCloud(), Integer.MAX_VALUE);
provisionInfraApplication(hostCount, infraApplication, NodeType.proxyhost);
Supplier<NodeList> hosts = () -> tester.nodeRepository().nodes().list().nodeType(NodeType.proxyhost);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
index 03ab18c15d9..faf3f60e8af 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
@@ -74,6 +74,8 @@ import static org.junit.Assert.assertTrue;
*/
public class ProvisioningTester {
+ public static final ApplicationId tenantHostApp = ApplicationId.from("hosted-vespa", "tenant-host", "default");
+
private final Curator curator;
private final NodeFlavors nodeFlavors;
private final ManualClock clock;
@@ -543,7 +545,7 @@ public class ProvisioningTester {
}
public void activateTenantHosts() {
- prepareAndActivateInfraApplication(applicationId(), NodeType.host);
+ prepareAndActivateInfraApplication(tenantHostApp, NodeType.host);
}
public static ClusterSpec containerClusterSpec() {
diff --git a/parent/pom.xml b/parent/pom.xml
index f9155acbfd5..e6db5ff3492 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -275,7 +275,7 @@
<goal>run</goal>
</goals>
<configuration>
- <protocVersion>3.21.2</protocVersion>
+ <protocVersion>${protobuf.version}</protocVersion>
<addSources>main</addSources>
<outputDirectory>${project.build.directory}/generated-sources/protobuf</outputDirectory>
<inputDirectories>
@@ -400,7 +400,7 @@
<failOnError>${javadoc.failOnError}</failOnError>
<quiet>true</quiet>
<show>protected</show>
- <header>&lt;a href="https://docs.vespa.ai"&gt;&lt;img src="https://docs.vespa.ai/img/vespa-logo.png" width="100" height="28" style="padding-top:7px"/&gt;&lt;/a&gt;</header>
+ <header>&lt;a href="https://docs.vespa.ai"&gt;&lt;img src="https://docs.vespa.ai/assets/logos/vespa-logo-full-white.svg" width="100" height="28" style="padding-top:7px"/&gt;&lt;/a&gt;</header>
</configuration>
</plugin>
</plugins>
@@ -791,12 +791,17 @@
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>bcpkix-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcprov-jdk15on</artifactId>
+ <artifactId>bcprov-jdk18on</artifactId>
+ <version>${bouncycastle.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcutil-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
@@ -1024,7 +1029,7 @@
find zkfacade/src/main/java/org/apache/curator -name package-info.java | \
xargs perl -pi -e 's/major = [0-9]+, minor = [0-9]+, micro = [0-9]+/major = 5, minor = 3, micro = 0/g'
-->
- <bouncycastle.version>1.68</bouncycastle.version>
+ <bouncycastle.version>1.72</bouncycastle.version>
<curator.version>5.3.0</curator.version>
<commons.codec.version>1.15</commons.codec.version>
<commons.math3.version>3.6.1</commons.math3.version>
diff --git a/predicate-search-core/src/main/java/com/yahoo/document/predicate/BooleanPredicate.java b/predicate-search-core/src/main/java/com/yahoo/document/predicate/BooleanPredicate.java
index 19f6df2bf97..6317d1256f1 100644
--- a/predicate-search-core/src/main/java/com/yahoo/document/predicate/BooleanPredicate.java
+++ b/predicate-search-core/src/main/java/com/yahoo/document/predicate/BooleanPredicate.java
@@ -36,10 +36,9 @@ public class BooleanPredicate extends PredicateValue {
if (obj == this) {
return true;
}
- if (!(obj instanceof BooleanPredicate)) {
+ if (!(obj instanceof BooleanPredicate rhs)) {
return false;
}
- BooleanPredicate rhs = (BooleanPredicate)obj;
if (value != rhs.value) {
return false;
}
diff --git a/predicate-search-core/src/main/java/com/yahoo/document/predicate/FeatureRange.java b/predicate-search-core/src/main/java/com/yahoo/document/predicate/FeatureRange.java
index 496e84fd4a5..15b6d5d3d62 100644
--- a/predicate-search-core/src/main/java/com/yahoo/document/predicate/FeatureRange.java
+++ b/predicate-search-core/src/main/java/com/yahoo/document/predicate/FeatureRange.java
@@ -13,8 +13,8 @@ public class FeatureRange extends PredicateValue {
private String key;
private Long from;
private Long to;
- private List<RangePartition> partitions;
- private List<RangeEdgePartition> edgePartitions;
+ private final List<RangePartition> partitions;
+ private final List<RangeEdgePartition> edgePartitions;
public FeatureRange(String key) {
this(key, null, null);
@@ -98,10 +98,9 @@ public class FeatureRange extends PredicateValue {
if (obj == this) {
return true;
}
- if (!(obj instanceof FeatureRange)) {
+ if (!(obj instanceof FeatureRange rhs)) {
return false;
}
- FeatureRange rhs = (FeatureRange)obj;
if (!key.equals(rhs.key)) {
return false;
}
diff --git a/searchcore/src/tests/proton/attribute/attribute_manager/attribute_manager_test.cpp b/searchcore/src/tests/proton/attribute/attribute_manager/attribute_manager_test.cpp
index 95f0cff088d..0e3445d0785 100644
--- a/searchcore/src/tests/proton/attribute/attribute_manager/attribute_manager_test.cpp
+++ b/searchcore/src/tests/proton/attribute/attribute_manager/attribute_manager_test.cpp
@@ -43,8 +43,6 @@
#include <vespa/log/log.h>
LOG_SETUP("attribute_manager_test");
-namespace vespa { namespace config { namespace search {}}}
-
using std::string;
using namespace vespa::config::search;
using namespace config;
@@ -258,7 +256,7 @@ ParallelAttributeManager::ParallelAttributeManager(search::SerialNum configSeria
masterExecutor(1, 128_Ki),
master(masterExecutor),
initializer(std::make_shared<AttributeManagerInitializer>(configSerialNum, documentMetaStoreInitTask,
- documentMetaStore, baseAttrMgr, attrCfg,
+ documentMetaStore, *baseAttrMgr, attrCfg,
alloc_strategy,
fastAccessAttributesOnly, master, mgr))
{
diff --git a/searchcore/src/tests/proton/attribute/attribute_populator/attribute_populator_test.cpp b/searchcore/src/tests/proton/attribute/attribute_populator/attribute_populator_test.cpp
index c66b2dd15dc..19b8348fb7a 100644
--- a/searchcore/src/tests/proton/attribute/attribute_populator/attribute_populator_test.cpp
+++ b/searchcore/src/tests/proton/attribute/attribute_populator/attribute_populator_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/searchcore/proton/attribute/attribute_populator.h>
+#include <vespa/document/repo/documenttyperepo.h>
#include <vespa/searchcore/proton/attribute/attributemanager.h>
#include <vespa/searchcore/proton/common/hw_info.h>
#include <vespa/searchcore/proton/test/test.h>
diff --git a/searchcore/src/tests/proton/attribute/attribute_test.cpp b/searchcore/src/tests/proton/attribute/attribute_test.cpp
index ea264c506a6..a6e9f8fe7ee 100644
--- a/searchcore/src/tests/proton/attribute/attribute_test.cpp
+++ b/searchcore/src/tests/proton/attribute/attribute_test.cpp
@@ -18,12 +18,12 @@
#include <vespa/searchlib/attribute/interlock.h>
#include <vespa/searchlib/attribute/predicate_attribute.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
-#include <vespa/searchlib/index/empty_doc_builder.h>
#include <vespa/searchlib/predicate/predicate_hash.h>
#include <vespa/searchlib/predicate/predicate_index.h>
#include <vespa/searchlib/tensor/dense_tensor_attribute.h>
#include <vespa/searchlib/tensor/tensor_attribute.h>
#include <vespa/searchlib/test/directory_handler.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/searchcommon/attribute/attributecontent.h>
#include <vespa/searchcommon/attribute/iattributevector.h>
#include <vespa/searchcommon/attribute/config.h>
@@ -82,13 +82,13 @@ using search::attribute::ImportedAttributeVector;
using search::attribute::ImportedAttributeVectorFactory;
using search::attribute::ReferenceAttribute;
using search::index::DummyFileHeaderContext;
-using search::index::EmptyDocBuilder;
using search::predicate::PredicateHash;
using search::predicate::PredicateIndex;
using search::tensor::DenseTensorAttribute;
using search::tensor::PrepareResult;
using search::tensor::TensorAttribute;
using search::test::DirectoryHandler;
+using search::test::DocBuilder;
using std::string;
using vespalib::ForegroundTaskExecutor;
using vespalib::ForegroundThreadExecutor;
@@ -221,12 +221,12 @@ AttributeWriterTest::~AttributeWriterTest() = default;
TEST_F(AttributeWriterTest, handles_put)
{
- EmptyDocBuilder edb([](auto& header)
- { using namespace document::config_builder;
- header.addField("a1", DataType::T_INT)
- .addField("a2", Array(DataType::T_INT))
- .addField("a3", DataType::T_FLOAT)
- .addField("a4", DataType::T_STRING); });
+ DocBuilder db([](auto& header)
+ { using namespace document::config_builder;
+ header.addField("a1", DataType::T_INT)
+ .addField("a2", Array(DataType::T_INT))
+ .addField("a3", DataType::T_FLOAT)
+ .addField("a4", DataType::T_STRING); });
auto a1 = addAttribute("a1");
auto a2 = addAttribute({"a2", AVConfig(AVBasicType::INT32, AVCollectionType::ARRAY)});
auto a3 = addAttribute({"a3", AVConfig(AVBasicType::FLOAT)});
@@ -238,7 +238,7 @@ TEST_F(AttributeWriterTest, handles_put)
attribute::ConstCharContent sbuf;
{ // empty document should give default values
EXPECT_EQ(1u, a1->getNumDocs());
- put(1, *edb.make_document("id:ns:searchdocument::1"), 1);
+ put(1, *db.make_document("id:ns:searchdocument::1"), 1);
EXPECT_EQ(2u, a1->getNumDocs());
EXPECT_EQ(2u, a2->getNumDocs());
EXPECT_EQ(2u, a3->getNumDocs());
@@ -260,9 +260,9 @@ TEST_F(AttributeWriterTest, handles_put)
EXPECT_EQ(strcmp("", sbuf[0]), 0);
}
{ // document with single value & multi value attribute
- auto doc = edb.make_document("id:ns:searchdocument::2");
+ auto doc = db.make_document("id:ns:searchdocument::2");
doc->setValue("a1", IntFieldValue(10));
- ArrayFieldValue int_array(edb.get_data_type("Array<Int>"));
+ auto int_array = db.make_array("a2");
int_array.add(IntFieldValue(20));
int_array.add(IntFieldValue(30));
doc->setValue("a2",int_array);
@@ -282,9 +282,9 @@ TEST_F(AttributeWriterTest, handles_put)
EXPECT_EQ(30u, ibuf[1]);
}
{ // replace existing document
- auto doc = edb.make_document("id:ns:searchdocument::2");
+ auto doc = db.make_document("id:ns:searchdocument::2");
doc->setValue("a1", IntFieldValue(100));
- ArrayFieldValue int_array(edb.get_data_type("Array<Int>"));
+ auto int_array = db.make_array("a2");
int_array.add(IntFieldValue(200));
int_array.add(IntFieldValue(300));
int_array.add(IntFieldValue(400));
@@ -309,7 +309,7 @@ TEST_F(AttributeWriterTest, handles_put)
TEST_F(AttributeWriterTest, handles_predicate_put)
{
- EmptyDocBuilder edb([](auto& header) { header.addField("a1", DataType::T_PREDICATE); });
+ DocBuilder db([](auto& header) { header.addField("a1", DataType::T_PREDICATE); });
auto a1 = addAttribute({"a1", AVConfig(AVBasicType::PREDICATE)});
allocAttributeWriter();
@@ -317,14 +317,14 @@ TEST_F(AttributeWriterTest, handles_predicate_put)
// empty document should give default values
EXPECT_EQ(1u, a1->getNumDocs());
- put(1, *edb.make_document("id:ns:searchdocument::1"), 1);
+ put(1, *db.make_document("id:ns:searchdocument::1"), 1);
EXPECT_EQ(2u, a1->getNumDocs());
EXPECT_EQ(1u, a1->getStatus().getLastSyncToken());
EXPECT_EQ(0u, index.getZeroConstraintDocs().size());
// document with single value attribute
PredicateSlimeBuilder builder;
- auto doc = edb.make_document("id:ns:searchdocument::2");
+ auto doc = db.make_document("id:ns:searchdocument::2");
doc->setValue("a1", PredicateFieldValue(builder.true_predicate().build()));
put(2, *doc, 2);
EXPECT_EQ(3u, a1->getNumDocs());
@@ -335,7 +335,7 @@ TEST_F(AttributeWriterTest, handles_predicate_put)
EXPECT_FALSE(it.valid());
// replace existing document
- doc = edb.make_document("id:ns:searchdocument::2");
+ doc = db.make_document("id:ns:searchdocument::2");
doc->setValue("a1", PredicateFieldValue(builder.feature("foo").value("bar").build()));
put(3, *doc, 2);
EXPECT_EQ(3u, a1->getNumDocs());
@@ -407,10 +407,10 @@ TEST_F(AttributeWriterTest, visibility_delay_is_honoured)
auto a1 = addAttribute({"a1", AVConfig(AVBasicType::STRING)});
allocAttributeWriter();
- EmptyDocBuilder edb([](auto& header) { header.addField("a1", DataType::T_STRING); });
+ DocBuilder db([](auto& header) { header.addField("a1", DataType::T_STRING); });
EXPECT_EQ(1u, a1->getNumDocs());
EXPECT_EQ(0u, a1->getStatus().getLastSyncToken());
- auto doc = edb.make_document("id:ns:searchdocument::1");
+ auto doc = db.make_document("id:ns:searchdocument::1");
doc->setValue("a1", StringFieldValue("10"));
put(3, *doc, 1);
EXPECT_EQ(2u, a1->getNumDocs());
@@ -433,13 +433,13 @@ TEST_F(AttributeWriterTest, visibility_delay_is_honoured)
EXPECT_EQ(8u, a1->getStatus().getLastSyncToken());
verifyAttributeContent(*a1, 2, "10");
- doc = edb.make_document("id:ns:searchdocument::1");
+ doc = db.make_document("id:ns:searchdocument::1");
doc->setValue("a1", StringFieldValue("11"));
awDelayed.put(9, *doc, 2, emptyCallback);
- doc = edb.make_document("id:ns:searchdocument::1");
+ doc = db.make_document("id:ns:searchdocument::1");
doc->setValue("a1", StringFieldValue("20"));
awDelayed.put(10, *doc, 2, emptyCallback);
- doc = edb.make_document("id:ns:searchdocument::1");
+ doc = db.make_document("id:ns:searchdocument::1");
doc->setValue("a1", StringFieldValue("30"));
awDelayed.put(11, *doc, 2, emptyCallback);
EXPECT_EQ(8u, a1->getStatus().getLastSyncToken());
@@ -454,10 +454,10 @@ TEST_F(AttributeWriterTest, handles_predicate_remove)
auto a1 = addAttribute({"a1", AVConfig(AVBasicType::PREDICATE)});
allocAttributeWriter();
- EmptyDocBuilder edb([](auto& header) { header.addField("a1", DataType::T_PREDICATE); });
+ DocBuilder db([](auto& header) { header.addField("a1", DataType::T_PREDICATE); });
PredicateSlimeBuilder builder;
- auto doc = edb.make_document("id:ns:searchdocument::1");
+ auto doc = db.make_document("id:ns:searchdocument::1");
doc->setValue("a1", PredicateFieldValue(builder.true_predicate().build()));
put(1, *doc, 1);
EXPECT_EQ(2u, a1->getNumDocs());
@@ -477,10 +477,10 @@ TEST_F(AttributeWriterTest, handles_update)
fillAttribute(a1, 1, 10, 1);
fillAttribute(a2, 1, 20, 1);
- EmptyDocBuilder edb([](auto& header)
+ DocBuilder db([](auto& header)
{ header.addField("a1", DataType::T_INT)
.addField("a2", DataType::T_INT); });
- DocumentUpdate upd(edb.get_repo(), edb.get_document_type(), DocumentId("id:ns:searchdocument::1"));
+ DocumentUpdate upd(db.get_repo(), db.get_document_type(), DocumentId("id:ns:searchdocument::1"));
upd.addUpdate(FieldUpdate(upd.getType().getField("a1"))
.addUpdate(std::make_unique<ArithmeticValueUpdate>(ArithmeticValueUpdate::Add, 5)));
upd.addUpdate(FieldUpdate(upd.getType().getField("a2"))
@@ -511,14 +511,14 @@ TEST_F(AttributeWriterTest, handles_predicate_update)
{
auto a1 = addAttribute({"a1", AVConfig(AVBasicType::PREDICATE)});
allocAttributeWriter();
- EmptyDocBuilder edb([](auto& header) { header.addField("a1", DataType::T_PREDICATE); });
+ DocBuilder db([](auto& header) { header.addField("a1", DataType::T_PREDICATE); });
PredicateSlimeBuilder builder;
- auto doc = edb.make_document("id:ns:searchdocument::1");
+ auto doc = db.make_document("id:ns:searchdocument::1");
doc->setValue("a1", PredicateFieldValue(builder.true_predicate().build()));
put(1, *doc, 1);
EXPECT_EQ(2u, a1->getNumDocs());
- DocumentUpdate upd(edb.get_repo(), edb.get_document_type(), DocumentId("id:ns:searchdocument::1"));
+ DocumentUpdate upd(db.get_repo(), db.get_document_type(), DocumentId("id:ns:searchdocument::1"));
upd.addUpdate(FieldUpdate(upd.getType().getField("a1"))
.addUpdate(std::make_unique<AssignValueUpdate>(std::make_unique<PredicateFieldValue>(builder.feature("foo").value("bar").build()))));
@@ -662,7 +662,7 @@ createTensorAttribute(AttributeWriterTest &t) {
}
Document::UP
-createTensorPutDoc(EmptyDocBuilder& builder, const Value &tensor) {
+createTensorPutDoc(DocBuilder& builder, const Value &tensor) {
auto doc = builder.make_document("id:ns:searchdocument::1");
TensorFieldValue fv(*doc->getField("a1").getDataType().cast_tensor());
fv = SimpleValue::from_value(tensor);
@@ -676,7 +676,7 @@ TEST_F(AttributeWriterTest, can_write_to_tensor_attribute)
{
auto a1 = createTensorAttribute(*this);
allocAttributeWriter();
- EmptyDocBuilder builder([](auto& header) { header.addTensorField("a1", sparse_tensor); });
+ DocBuilder builder([](auto& header) { header.addTensorField("a1", sparse_tensor); });
auto tensor = make_tensor(TensorSpec(sparse_tensor)
.add({{"x", "4"}, {"y", "5"}}, 7));
Document::UP doc = createTensorPutDoc(builder, *tensor);
@@ -693,7 +693,7 @@ TEST_F(AttributeWriterTest, handles_tensor_assign_update)
{
auto a1 = createTensorAttribute(*this);
allocAttributeWriter();
- EmptyDocBuilder builder([](auto& header) { header.addTensorField("a1", sparse_tensor); });
+ DocBuilder builder([](auto& header) { header.addTensorField("a1", sparse_tensor); });
auto tensor = make_tensor(TensorSpec(sparse_tensor)
.add({{"x", "6"}, {"y", "7"}}, 9));
auto doc = createTensorPutDoc(builder, *tensor);
@@ -746,10 +746,10 @@ putAttributes(AttributeWriterTest &t, std::vector<uint32_t> expExecuteHistory)
vespalib::string a2_name = "a2x";
vespalib::string a3_name = "a3y";
- EmptyDocBuilder edb([&](auto& header)
- { header.addField(a1_name, DataType::T_INT)
- .addField(a2_name, DataType::T_INT)
- .addField(a3_name, DataType::T_INT); });
+ DocBuilder db([&](auto& header)
+ { header.addField(a1_name, DataType::T_INT)
+ .addField(a2_name, DataType::T_INT)
+ .addField(a3_name, DataType::T_INT); });
auto a1 = t.addAttribute(a1_name);
auto a2 = t.addAttribute(a2_name);
@@ -759,7 +759,7 @@ putAttributes(AttributeWriterTest &t, std::vector<uint32_t> expExecuteHistory)
EXPECT_EQ(1u, a1->getNumDocs());
EXPECT_EQ(1u, a2->getNumDocs());
EXPECT_EQ(1u, a3->getNumDocs());
- auto doc = edb.make_document("id:ns:searchdocument::1");
+ auto doc = db.make_document("id:ns:searchdocument::1");
doc->setValue(a1_name, IntFieldValue(10));
doc->setValue(a2_name, IntFieldValue(15));
doc->setValue(a3_name, IntFieldValue(20));
@@ -871,7 +871,7 @@ TEST_F(AttributeWriterTest, tensor_attributes_using_two_phase_put_are_in_separat
class TwoPhasePutTest : public AttributeWriterTest {
public:
- EmptyDocBuilder builder;
+ DocBuilder builder;
vespalib::string doc_id;
std::shared_ptr<MockDenseTensorAttribute> attr;
std::unique_ptr<Value> tensor;
diff --git a/searchcore/src/tests/proton/attribute/document_field_populator/CMakeLists.txt b/searchcore/src/tests/proton/attribute/document_field_populator/CMakeLists.txt
index dea03cbf040..41b2037b6cb 100644
--- a/searchcore/src/tests/proton/attribute/document_field_populator/CMakeLists.txt
+++ b/searchcore/src/tests/proton/attribute/document_field_populator/CMakeLists.txt
@@ -5,5 +5,6 @@ vespa_add_executable(searchcore_document_field_populator_test_app TEST
DEPENDS
searchcore_attribute
searchcore_pcommon
+ searchlib_test
)
vespa_add_test(NAME searchcore_document_field_populator_test_app COMMAND searchcore_document_field_populator_test_app)
diff --git a/searchcore/src/tests/proton/attribute/document_field_populator/document_field_populator_test.cpp b/searchcore/src/tests/proton/attribute/document_field_populator/document_field_populator_test.cpp
index b08764289e6..e831e8b2ac7 100644
--- a/searchcore/src/tests/proton/attribute/document_field_populator/document_field_populator_test.cpp
+++ b/searchcore/src/tests/proton/attribute/document_field_populator/document_field_populator_test.cpp
@@ -1,12 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/searchcommon/attribute/config.h>
#include <vespa/searchcore/proton/attribute/document_field_populator.h>
#include <vespa/searchlib/attribute/attributefactory.h>
#include <vespa/searchlib/attribute/integerbase.h>
-#include <vespa/searchlib/index/docbuilder.h>
-#include <vespa/searchcommon/common/schema.h>
-#include <vespa/searchcommon/attribute/config.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/log/log.h>
@@ -15,38 +16,22 @@ LOG_SETUP("document_field_populator_test");
using namespace document;
using namespace proton;
using namespace search;
-using namespace search::index;
+using search::test::DocBuilder;
typedef search::attribute::Config AVConfig;
typedef search::attribute::BasicType AVBasicType;
-Schema::AttributeField
-createAttributeField()
-{
- return Schema::AttributeField("a1", Schema::DataType::INT32);
-}
-
-Schema
-createSchema()
-{
- Schema schema;
- schema.addAttributeField(createAttributeField());
- return schema;
-}
-
struct DocContext
{
- Schema _schema;
DocBuilder _builder;
DocContext()
- : _schema(createSchema()),
- _builder(_schema)
+ : _builder([](auto& header) { header.addField("a1", DataType::T_INT); })
{
}
Document::UP create(uint32_t id) {
vespalib::string docId =
vespalib::make_string("id:searchdocument:searchdocument::%u", id);
- return _builder.startDocument(docId).endDocument();
+ return _builder.make_document(docId);
}
};
diff --git a/searchcore/src/tests/proton/attribute/imported_attributes_context/imported_attributes_context_test.cpp b/searchcore/src/tests/proton/attribute/imported_attributes_context/imported_attributes_context_test.cpp
index c52978261a7..7a5c76b201a 100644
--- a/searchcore/src/tests/proton/attribute/imported_attributes_context/imported_attributes_context_test.cpp
+++ b/searchcore/src/tests/proton/attribute/imported_attributes_context/imported_attributes_context_test.cpp
@@ -56,10 +56,11 @@ hasActiveEnumGuards(AttributeVector &attr)
}
void
-assertGuards(AttributeVector &attr, generation_t expCurrentGeneration, generation_t expFirstUsedGeneration, bool expHasActiveEnumGuards)
+assertGuards(AttributeVector &attr, generation_t expCurrentGeneration, generation_t exp_oldest_used_generation,
+ bool expHasActiveEnumGuards)
{
EXPECT_EQUAL(expCurrentGeneration, attr.getCurrentGeneration());
- EXPECT_EQUAL(expFirstUsedGeneration, attr.getFirstUsedGeneration());
+ EXPECT_EQUAL(exp_oldest_used_generation, attr.get_oldest_used_generation());
EXPECT_EQUAL(expHasActiveEnumGuards, hasActiveEnumGuards(attr));
}
diff --git a/searchcore/src/tests/proton/docsummary/docsummary.cpp b/searchcore/src/tests/proton/docsummary/docsummary.cpp
index e76a7f72cbb..677419d9cee 100644
--- a/searchcore/src/tests/proton/docsummary/docsummary.cpp
+++ b/searchcore/src/tests/proton/docsummary/docsummary.cpp
@@ -8,6 +8,7 @@
#include <vespa/document/annotation/spanlist.h>
#include <vespa/document/annotation/spantree.h>
#include <vespa/document/config/documenttypes_config_fwd.h>
+#include <vespa/document/datatype/annotationtype.h>
#include <vespa/document/datatype/documenttype.h>
#include <vespa/document/datatype/tensor_data_type.h>
#include <vespa/document/datatype/urldatatype.h>
@@ -51,8 +52,8 @@
#include <vespa/searchlib/attribute/interlock.h>
#include <vespa/searchlib/engine/docsumapi.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
-#include <vespa/searchlib/index/empty_doc_builder.h>
#include <vespa/searchlib/tensor/tensor_attribute.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/searchlib/transactionlog/nosyncproxy.h>
#include <vespa/searchlib/transactionlog/translogserver.h>
#include <vespa/searchsummary/docsummary/i_docsum_field_writer_factory.h>
@@ -90,7 +91,7 @@ using document::test::makeBucketSpace;
using search::TuneFileDocumentDB;
using search::index::DummyFileHeaderContext;
using search::linguistics::SPANTREE_NAME;
-using search::linguistics::TERM;
+using search::test::DocBuilder;
using storage::spi::Timestamp;
using vespa::config::search::core::ProtonConfig;
using vespa::config::content::core::BucketspacesConfig;
@@ -128,7 +129,7 @@ private:
vespalib::string _dir;
};
-class BuildContext : public EmptyDocBuilder
+class BuildContext : public DocBuilder
{
public:
DirMaker _dmk;
@@ -155,7 +156,7 @@ public:
};
BuildContext::BuildContext(AddFieldsType add_fields)
- : EmptyDocBuilder(add_fields),
+ : DocBuilder(add_fields),
_dmk("summary"),
_fixed_repo(get_repo(), get_document_type()),
_summaryExecutor(4, 128_Ki),
@@ -181,9 +182,9 @@ BuildContext::make_annotated_string()
auto span_list_up = std::make_unique<SpanList>();
auto span_list = span_list_up.get();
auto tree = std::make_unique<SpanTree>(SPANTREE_NAME, std::move(span_list_up));
- tree->annotate(span_list->add(std::make_unique<Span>(0, 3)), *TERM);
+ tree->annotate(span_list->add(std::make_unique<Span>(0, 3)), *AnnotationType::TERM);
tree->annotate(span_list->add(std::make_unique<Span>(4, 3)),
- Annotation(*TERM, std::make_unique<StringFieldValue>("baz")));
+ Annotation(*AnnotationType::TERM, std::make_unique<StringFieldValue>("baz")));
StringFieldValue value("foo bar");
StringFieldValue::SpanTrees trees;
trees.push_back(std::move(tree));
@@ -627,27 +628,27 @@ TEST("requireThatAttributesAreUsed")
doc->setValue("ba", IntFieldValue(10));
doc->setValue("bb", FloatFieldValue(10.1250));
doc->setValue("bc", StringFieldValue("foo"));
- ArrayFieldValue int_array(bc.get_data_type("Array<Int>"));
+ auto int_array = bc.make_array("bd");
int_array.add(IntFieldValue(20));
int_array.add(IntFieldValue(30));
doc->setValue("bd", int_array);
- ArrayFieldValue float_array(bc.get_data_type("Array<Float>"));
+ auto float_array = bc.make_array("be");
float_array.add(FloatFieldValue(20.25000));
float_array.add(FloatFieldValue(30.31250));
doc->setValue("be", float_array);
- ArrayFieldValue string_array(bc.get_data_type("Array<String>"));
+ auto string_array = bc.make_array("bf");
string_array.add(StringFieldValue("bar"));
string_array.add(StringFieldValue("baz"));
doc->setValue("bf", string_array);
- WeightedSetFieldValue int_wset(bc.get_data_type("WeightedSet<Int>"));
+ auto int_wset = bc.make_wset("bg");
int_wset.add(IntFieldValue(40), 2);
int_wset.add(IntFieldValue(50), 3);
doc->setValue("bg", int_wset);
- WeightedSetFieldValue float_wset(bc.get_data_type("WeightedSet<Float>"));
+ auto float_wset = bc.make_wset("bh");
float_wset.add(FloatFieldValue(40.4375), 4);
float_wset.add(FloatFieldValue(50.5625), 5);
doc->setValue("bh", float_wset);
- WeightedSetFieldValue string_wset(bc.get_data_type("WeightedSet<String>"));
+ auto string_wset = bc.make_wset("bi");
string_wset.add(StringFieldValue("quux"), 7);
string_wset.add(StringFieldValue("qux"), 6);
doc->setValue("bi", string_wset);
@@ -774,7 +775,7 @@ TEST_F("requireThatUrisAreUsed", Fixture)
.addField("uriwset", Wset(UrlDataType::getInstance().getId())); });
DBContext dc(bc.get_repo_sp(), getDocTypeName());
auto exp = bc.make_document("id:ns:searchdocument::0");
- StructFieldValue uri(bc.get_data_type("url"));
+ auto uri = bc.make_url();
uri.setValue("all", StringFieldValue("http://www.example.com:81/fluke?ab=2#4"));
uri.setValue("scheme", StringFieldValue("http"));
uri.setValue("host", StringFieldValue("www.example.com"));
@@ -783,7 +784,7 @@ TEST_F("requireThatUrisAreUsed", Fixture)
uri.setValue("query", StringFieldValue("ab=2"));
uri.setValue("fragment", StringFieldValue("4"));
exp->setValue("urisingle", uri);
- ArrayFieldValue uri_array(bc.get_data_type("Array<url>"));
+ auto uri_array = bc.make_array("uriarray");
uri.setValue("all", StringFieldValue("http://www.example.com:82/fluke?ab=2#8"));
uri.setValue("scheme", StringFieldValue("http"));
uri.setValue("host", StringFieldValue("www.example.com"));
@@ -801,7 +802,7 @@ TEST_F("requireThatUrisAreUsed", Fixture)
uri.setValue("fragment", StringFieldValue("9"));
uri_array.add(uri);
exp->setValue("uriarray", uri_array);
- WeightedSetFieldValue uri_wset(bc.get_data_type("WeightedSet<url>"));
+ auto uri_wset = bc.make_wset("uriwset");
uri.setValue("all", StringFieldValue("http://www.example.com:83/fluke?ab=2#12"));
uri.setValue("scheme", StringFieldValue("http"));
uri.setValue("host", StringFieldValue("www.example.com"));
@@ -867,11 +868,11 @@ TEST("requireThatPositionsAreUsed")
DBContext dc(bc.get_repo_sp(), getDocTypeName());
auto exp = bc.make_document("id:ns:searchdocument::1");
exp->setValue("sp2", LongFieldValue(ZCurve::encode(1002, 1003)));
- ArrayFieldValue pos_array(bc.get_data_type("Array<Long>"));
+ auto pos_array = bc.make_array("ap2");
pos_array.add(LongFieldValue(ZCurve::encode(1006, 1007)));
pos_array.add(LongFieldValue(ZCurve::encode(1008, 1009)));
exp->setValue("ap2", pos_array);
- WeightedSetFieldValue pos_wset(bc.get_data_type("WeightedSet<Long>"));
+ auto pos_wset = bc.make_wset("wp2");
pos_wset.add(LongFieldValue(ZCurve::encode(1012, 1013)), 43);
pos_wset.add(LongFieldValue(ZCurve::encode(1014, 1015)), 44);
exp->setValue("wp2", pos_wset);
@@ -928,11 +929,11 @@ TEST_F("requireThatRawFieldsWorks", Fixture)
DBContext dc(bc.get_repo_sp(), getDocTypeName());
auto exp = bc.make_document("id:ns:searchdocument::0");
exp->setValue("i", RawFieldValue(raw1s));
- ArrayFieldValue raw_array(bc.get_data_type("Array<Raw>"));
+ auto raw_array = bc.make_array("araw");
raw_array.add(RawFieldValue(raw1a0));
raw_array.add(RawFieldValue(raw1a1));
exp->setValue("araw", raw_array);
- WeightedSetFieldValue raw_wset(bc.get_data_type("WeightedSet<Raw>"));
+ auto raw_wset = bc.make_wset("wraw");
raw_wset.add(RawFieldValue(raw1w1), 46);
raw_wset.add(RawFieldValue(raw1w0), 45);
exp->setValue("wraw", raw_wset);
diff --git a/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp b/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp
index 45ec3824c11..7394ef1214c 100644
--- a/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp
@@ -1,5 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/fieldvalue/intfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/searchcore/proton/attribute/imported_attributes_repo.h>
#include <vespa/searchcore/proton/bucketdb/bucketdbhandler.h>
#include <vespa/searchcore/proton/bucketdb/bucket_db_owner.h>
@@ -27,8 +30,8 @@
#include <vespa/searchcore/proton/test/thread_utils.h>
#include <vespa/searchcore/proton/test/transport_helper.h>
#include <vespa/searchlib/attribute/interlock.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/searchlib/test/directory_handler.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/searchcommon/attribute/config.h>
#include <vespa/config-bucketspaces.h>
#include <vespa/config/subscription/sourcespec.h>
@@ -60,6 +63,7 @@ using proton::bucketdb::IBucketDBHandler;
using proton::bucketdb::IBucketDBHandlerInitializer;
using vespalib::IDestructorCallback;
using search::test::DirectoryHandler;
+using search::test::DocBuilder;
using searchcorespi::IFlushTarget;
using searchcorespi::index::IThreadingService;
using storage::spi::Timestamp;
@@ -258,6 +262,17 @@ struct TwoAttrSchema : public OneAttrSchema
}
};
+DocBuilder::AddFieldsType
+get_add_fields(bool has_attr2)
+{
+ return [has_attr2](auto& header) {
+ header.addField("attr1", DataType::T_INT);
+ if (has_attr2) {
+ header.addField("attr2", DataType::T_INT);
+ }
+ };
+}
+
struct MyConfigSnapshot
{
typedef std::unique_ptr<MyConfigSnapshot> UP;
@@ -267,15 +282,15 @@ struct MyConfigSnapshot
BootstrapConfig::SP _bootstrap;
MyConfigSnapshot(FNET_Transport & transport, const Schema &schema, const vespalib::string &cfgDir)
: _schema(schema),
- _builder(_schema),
+ _builder(get_add_fields(_schema.getNumAttributeFields() > 1)),
_cfg(),
_bootstrap()
{
- auto documenttypesConfig = std::make_shared<DocumenttypesConfig>(_builder.getDocumenttypesConfig());
+ auto documenttypesConfig = std::make_shared<DocumenttypesConfig>(_builder.get_documenttypes_config());
auto tuneFileDocumentDB = std::make_shared<TuneFileDocumentDB>();
_bootstrap = std::make_shared<BootstrapConfig>(1,
documenttypesConfig,
- _builder.getDocumentTypeRepo(),
+ _builder.get_repo_sp(),
std::make_shared<ProtonConfig>(),
std::make_shared<FiledistributorrpcConfig>(),
std::make_shared<BucketspacesConfig>(),
@@ -747,7 +762,7 @@ struct DocumentHandler
{
FixtureType &_f;
DocBuilder _builder;
- DocumentHandler(FixtureType &f) : _f(f), _builder(f._baseSchema) {}
+ DocumentHandler(FixtureType &f) : _f(f), _builder(get_add_fields(f._baseSchema.getNumAttributeFields() > 1)) {}
static constexpr uint32_t BUCKET_USED_BITS = 8;
static DocumentId createDocId(uint32_t docId)
{
@@ -755,16 +770,16 @@ struct DocumentHandler
"searchdocument::%u", docId));
}
Document::UP createEmptyDoc(uint32_t docId) {
- return _builder.startDocument
- (vespalib::make_string("id:searchdocument:searchdocument::%u",
- docId)).
- endDocument();
+ auto id = vespalib::make_string("id:searchdocument:searchdocument::%u",
+ docId);
+ return _builder.make_document(id);
}
Document::UP createDoc(uint32_t docId, int64_t attr1Value, int64_t attr2Value) {
- return _builder.startDocument
- (vespalib::make_string("id:searchdocument:searchdocument::%u", docId)).
- startAttributeField("attr1").addInt(attr1Value).endField().
- startAttributeField("attr2").addInt(attr2Value).endField().endDocument();
+ auto id = vespalib::make_string("id:searchdocument:searchdocument::%u", docId);
+ auto doc = _builder.make_document(id);
+ doc->setValue("attr1", IntFieldValue(attr1Value));
+ doc->setValue("attr2", IntFieldValue(attr2Value));
+ return doc;
}
PutOperation createPut(Document::UP doc, Timestamp timestamp, SerialNum serialNum) {
proton::test::Document testDoc(Document::SP(doc.release()), 0, timestamp);
diff --git a/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp b/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp
index e89d5eef078..abd3fba65fd 100644
--- a/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp
@@ -3,7 +3,11 @@
#include <vespa/persistence/spi/result.h>
#include <vespa/document/datatype/tensor_data_type.h>
#include <vespa/document/datatype/documenttype.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/fieldvalue/tensorfieldvalue.h>
#include <vespa/document/update/assignvalueupdate.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/document/repo/documenttyperepo.h>
#include <vespa/document/update/documentupdate.h>
#include <vespa/document/update/clearvalueupdate.h>
@@ -29,8 +33,8 @@
#include <vespa/searchcore/proton/server/ireplayconfig.h>
#include <vespa/searchcore/proton/test/dummy_feed_view.h>
#include <vespa/searchcore/proton/test/transport_helper.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/searchlib/transactionlog/translogserver.h>
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/util/lambdatask.h>
@@ -55,6 +59,7 @@ using search::SerialNum;
using search::index::schema::CollectionType;
using search::index::schema::DataType;
using vespalib::makeLambdaTask;
+using search::test::DocBuilder;
using search::transactionlog::TransLogServer;
using search::transactionlog::DomainConfig;
using storage::spi::RemoveResult;
@@ -271,20 +276,33 @@ MyFeedView::~MyFeedView() = default;
struct SchemaContext {
- Schema::SP schema;
- std::unique_ptr<DocBuilder> builder;
+ Schema::SP schema;
+ DocBuilder builder;
SchemaContext();
+ SchemaContext(bool has_i2);
~SchemaContext();
DocTypeName getDocType() const {
- return DocTypeName(builder->getDocumentType().getName());
+ return DocTypeName(builder.get_document_type().getName());
}
- const std::shared_ptr<const document::DocumentTypeRepo> &getRepo() const { return builder->getDocumentTypeRepo(); }
+ std::shared_ptr<const document::DocumentTypeRepo> getRepo() const { return builder.get_repo_sp(); }
void addField(vespalib::stringref fieldName);
};
SchemaContext::SchemaContext()
+ : SchemaContext(false)
+{
+}
+
+SchemaContext::SchemaContext(bool has_i2)
: schema(std::make_shared<Schema>()),
- builder()
+ builder([has_i2](auto& header) {
+ header.addTensorField("tensor", "tensor(x{},y{})")
+ .addTensorField("tensor2", "tensor(x{},y{})")
+ .addField("i1", document::DataType::T_STRING);
+ if (has_i2) {
+ header.addField("i2", document::DataType::T_STRING);
+ }
+ })
{
schema->addAttributeField(Schema::AttributeField("tensor", DataType::TENSOR, CollectionType::SINGLE, "tensor(x{},y{})"));
schema->addAttributeField(Schema::AttributeField("tensor2", DataType::TENSOR, CollectionType::SINGLE, "tensor(x{},y{})"));
@@ -298,14 +316,13 @@ void
SchemaContext::addField(vespalib::stringref fieldName)
{
schema->addIndexField(Schema::IndexField(fieldName, DataType::STRING, CollectionType::SINGLE));
- builder = std::make_unique<DocBuilder>(*schema);
}
struct DocumentContext {
Document::SP doc;
BucketId bucketId;
DocumentContext(const vespalib::string &docId, DocBuilder &builder) :
- doc(builder.startDocument(docId).endDocument().release()),
+ doc(builder.make_document(docId)),
bucketId(BucketFactory::getBucketId(doc->getId()))
{
}
@@ -313,7 +330,7 @@ struct DocumentContext {
struct TwoFieldsSchemaContext : public SchemaContext {
TwoFieldsSchemaContext()
- : SchemaContext()
+ : SchemaContext(true)
{
addField("i2");
}
@@ -325,7 +342,7 @@ struct UpdateContext {
DocumentUpdate::SP update;
BucketId bucketId;
UpdateContext(const vespalib::string &docId, DocBuilder &builder) :
- update(std::make_shared<DocumentUpdate>(*builder.getDocumentTypeRepo(), builder.getDocumentType(), DocumentId(docId))),
+ update(std::make_shared<DocumentUpdate>(builder.get_repo(), builder.get_document_type(), DocumentId(docId))),
bucketId(BucketFactory::getBucketId(update->getId()))
{
}
@@ -464,7 +481,7 @@ TEST_F("require that heartBeat calls FeedView's heartBeat",
TEST_F("require that outdated remove is ignored", FeedHandlerFixture)
{
- DocumentContext doc_context("id:ns:searchdocument::foo", *f.schema.builder);
+ DocumentContext doc_context("id:ns:searchdocument::foo", f.schema.builder);
auto op = std::make_unique<RemoveOperationWithDocId>(doc_context.bucketId, Timestamp(10), doc_context.doc->getId());
static_cast<DocumentOperation &>(*op).setPrevDbDocumentId(DbDocumentId(4));
static_cast<DocumentOperation &>(*op).setPrevTimestamp(Timestamp(10000));
@@ -476,7 +493,7 @@ TEST_F("require that outdated remove is ignored", FeedHandlerFixture)
TEST_F("require that outdated put is ignored", FeedHandlerFixture)
{
- DocumentContext doc_context("id:ns:searchdocument::foo", *f.schema.builder);
+ DocumentContext doc_context("id:ns:searchdocument::foo", f.schema.builder);
auto op =std::make_unique<PutOperation>(doc_context.bucketId, Timestamp(10), std::move(doc_context.doc));
static_cast<DocumentOperation &>(*op).setPrevTimestamp(Timestamp(10000));
FeedTokenContext token_context;
@@ -496,7 +513,7 @@ addLidToRemove(RemoveDocumentsOperation &op)
TEST_F("require that handleMove calls FeedView", FeedHandlerFixture)
{
- DocumentContext doc_context("id:ns:searchdocument::foo", *f.schema.builder);
+ DocumentContext doc_context("id:ns:searchdocument::foo", f.schema.builder);
MoveOperation op(doc_context.bucketId, Timestamp(2), doc_context.doc, DbDocumentId(0, 2), 1);
op.setDbDocumentId(DbDocumentId(1, 2));
f.runAsMaster([&]() { f.handler.handleMove(op, IDestructorCallback::SP()); });
@@ -556,7 +573,7 @@ TEST_F("require that flush cannot unprune", FeedHandlerFixture)
TEST_F("require that remove of unknown document with known data type stores remove", FeedHandlerFixture)
{
- DocumentContext doc_context("id:test:searchdocument::foo", *f.schema.builder);
+ DocumentContext doc_context("id:test:searchdocument::foo", f.schema.builder);
auto op = std::make_unique<RemoveOperationWithDocId>(doc_context.bucketId, Timestamp(10), doc_context.doc->getId());
FeedTokenContext token_context;
f.handler.performOperation(std::move(token_context.token), std::move(op));
@@ -566,7 +583,7 @@ TEST_F("require that remove of unknown document with known data type stores remo
TEST_F("require that partial update for non-existing document is tagged as such", FeedHandlerFixture)
{
- UpdateContext upCtx("id:test:searchdocument::foo", *f.schema.builder);
+ UpdateContext upCtx("id:test:searchdocument::foo", f.schema.builder);
auto op = std::make_unique<UpdateOperation>(upCtx.bucketId, Timestamp(10), upCtx.update);
FeedTokenContext token_context;
f.handler.performOperation(std::move(token_context.token), std::move(op));
@@ -582,7 +599,7 @@ TEST_F("require that partial update for non-existing document is tagged as such"
TEST_F("require that partial update for non-existing document is created if specified", FeedHandlerFixture)
{
f.handler.setSerialNum(15);
- UpdateContext upCtx("id:test:searchdocument::foo", *f.schema.builder);
+ UpdateContext upCtx("id:test:searchdocument::foo", f.schema.builder);
upCtx.update->setCreateIfNonExistent(true);
f.feedView.metaStore.insert(upCtx.update->getId().getGlobalId(), MyDocumentMetaStore::Entry(5, 5, Timestamp(10)));
auto op = std::make_unique<UpdateOperation>(upCtx.bucketId, Timestamp(10), upCtx.update);
@@ -605,7 +622,7 @@ TEST_F("require that put is rejected if resource limit is reached", FeedHandlerF
f.writeFilter._acceptWriteOperation = false;
f.writeFilter._message = "Attribute resource limit reached";
- DocumentContext docCtx("id:test:searchdocument::foo", *f.schema.builder);
+ DocumentContext docCtx("id:test:searchdocument::foo", f.schema.builder);
auto op = std::make_unique<PutOperation>(docCtx.bucketId, Timestamp(10), std::move(docCtx.doc));
FeedTokenContext token;
f.handler.performOperation(std::move(token.token), std::move(op));
@@ -620,7 +637,7 @@ TEST_F("require that update is rejected if resource limit is reached", FeedHandl
f.writeFilter._acceptWriteOperation = false;
f.writeFilter._message = "Attribute resource limit reached";
- UpdateContext updCtx("id:test:searchdocument::foo", *f.schema.builder);
+ UpdateContext updCtx("id:test:searchdocument::foo", f.schema.builder);
updCtx.addFieldUpdate("tensor");
auto op = std::make_unique<UpdateOperation>(updCtx.bucketId, Timestamp(10), updCtx.update);
FeedTokenContext token;
@@ -637,7 +654,7 @@ TEST_F("require that remove is NOT rejected if resource limit is reached", FeedH
f.writeFilter._acceptWriteOperation = false;
f.writeFilter._message = "Attribute resource limit reached";
- DocumentContext docCtx("id:test:searchdocument::foo", *f.schema.builder);
+ DocumentContext docCtx("id:test:searchdocument::foo", f.schema.builder);
auto op = std::make_unique<RemoveOperationWithDocId>(docCtx.bucketId, Timestamp(10), docCtx.doc->getId());
FeedTokenContext token;
f.handler.performOperation(std::move(token.token), std::move(op));
@@ -651,7 +668,7 @@ checkUpdate(FeedHandlerFixture &f, SchemaContext &schemaContext,
const vespalib::string &fieldName, bool expectReject, bool existing)
{
f.handler.setSerialNum(15);
- UpdateContext updCtx("id:test:searchdocument::foo", *schemaContext.builder);
+ UpdateContext updCtx("id:test:searchdocument::foo", schemaContext.builder);
updCtx.addFieldUpdate(fieldName);
if (existing) {
f.feedView.metaStore.insert(updCtx.update->getId().getGlobalId(), MyDocumentMetaStore::Entry(5, 5, Timestamp(9)));
@@ -733,7 +750,7 @@ TEST_F("require that tensor update with wrong tensor type fails", FeedHandlerFix
TEST_F("require that put with different document type repo is ok", FeedHandlerFixture)
{
TwoFieldsSchemaContext schema;
- DocumentContext doc_context("id:ns:searchdocument::foo", *schema.builder);
+ DocumentContext doc_context("id:ns:searchdocument::foo", schema.builder);
auto op = std::make_unique<PutOperation>(doc_context.bucketId,
Timestamp(10), std::move(doc_context.doc));
FeedTokenContext token_context;
@@ -747,7 +764,7 @@ TEST_F("require that put with different document type repo is ok", FeedHandlerFi
TEST_F("require that feed stats are updated", FeedHandlerFixture)
{
- DocumentContext doc_context("id:ns:searchdocument::foo", *f.schema.builder);
+ DocumentContext doc_context("id:ns:searchdocument::foo", f.schema.builder);
auto op =std::make_unique<PutOperation>(doc_context.bucketId, Timestamp(10), std::move(doc_context.doc));
FeedTokenContext token_context;
f.handler.performOperation(std::move(token_context.token), std::move(op));
diff --git a/searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp b/searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp
index d0b7b03c3ab..5758e69dea4 100644
--- a/searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/feedview/feedview_test.cpp
@@ -26,7 +26,7 @@
#include <vespa/searchcore/proton/test/threading_service_observer.h>
#include <vespa/searchcore/proton/test/transport_helper.h>
#include <vespa/searchlib/attribute/attributefactory.h>
-#include <vespa/searchlib/index/empty_doc_builder.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/searchcommon/attribute/config.h>
#include <vespa/vespalib/util/destructor_callbacks.h>
#include <vespa/vespalib/stllike/asciistream.h>
@@ -49,6 +49,7 @@ using vespalib::GateCallback;
using search::SearchableStats;
using search::index::schema::CollectionType;
using search::index::schema::DataType;
+using search::test::DocBuilder;
using searchcorespi::IndexSearchable;
using storage::spi::BucketChecksum;
using storage::spi::BucketInfo;
@@ -436,7 +437,7 @@ MyTransport::~MyTransport() = default;
struct SchemaContext
{
Schema::SP _schema;
- EmptyDocBuilder _builder;
+ DocBuilder _builder;
SchemaContext();
~SchemaContext();
std::shared_ptr<const document::DocumentTypeRepo> getRepo() const { return _builder.get_repo_sp(); }
@@ -466,16 +467,16 @@ struct DocumentContext
BucketId bid;
Timestamp ts;
typedef std::vector<DocumentContext> List;
- DocumentContext(const vespalib::string &docId, uint64_t timestamp, EmptyDocBuilder &builder);
+ DocumentContext(const vespalib::string &docId, uint64_t timestamp, DocBuilder &builder);
~DocumentContext();
- void addFieldUpdate(EmptyDocBuilder &builder, const vespalib::string &fieldName) {
+ void addFieldUpdate(DocBuilder &builder, const vespalib::string &fieldName) {
const document::Field &field = builder.get_document_type().getField(fieldName);
upd->addUpdate(document::FieldUpdate(field));
}
document::GlobalId gid() const { return doc->getId().getGlobalId(); }
};
-DocumentContext::DocumentContext(const vespalib::string &docId, uint64_t timestamp, EmptyDocBuilder& builder)
+DocumentContext::DocumentContext(const vespalib::string &docId, uint64_t timestamp, DocBuilder& builder)
: doc(builder.make_document(docId)),
upd(std::make_shared<DocumentUpdate>(builder.get_repo(), builder.get_document_type(), doc->getId())),
bid(BucketFactory::getNumBucketBits(), doc->getId().getGlobalId().convertToBucketId().getRawId()),
@@ -555,7 +556,7 @@ struct FixtureBase
return getMetaStore().getMetaData(doc_.doc->getId().getGlobalId());
}
- EmptyDocBuilder &getBuilder() { return sc._builder; }
+ DocBuilder &getBuilder() { return sc._builder; }
DocumentContext doc(const vespalib::string &docId, uint64_t timestamp) {
return DocumentContext(docId, timestamp, getBuilder());
diff --git a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.cpp b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.cpp
index 9c68d7d5974..b3a2e9cad83 100644
--- a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.cpp
+++ b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.cpp
@@ -127,7 +127,8 @@ MyHandler::handleCompactLidSpace(const CompactLidSpaceOperation &op, std::shared
}
MyHandler::MyHandler(bool storeMoveDoneContexts, bool bucketIdEqualLid)
- : _stats(),
+ : _builder(),
+ _stats(),
_moveFromLid(0),
_moveToLid(0),
_handleMoveCnt(0),
@@ -140,9 +141,8 @@ MyHandler::MyHandler(bool storeMoveDoneContexts, bool bucketIdEqualLid)
_rm_listener(),
_docs()
{
- DocBuilder builder = DocBuilder(Schema());
for (uint32_t i(0); i < 10; i++) {
- auto doc = builder.startDocument(fmt("%s%d", DOC_ID.c_str(), i)).endDocument();
+ auto doc = _builder.make_document(fmt("%s%d", DOC_ID.c_str(), i));
_docs.emplace_back(DocumentMetaData(i, TIMESTAMP_1, createBucketId(i), doc->getId().getGlobalId()), std::move(doc));
}
}
diff --git a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.h b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.h
index b404fc6956a..af984cb357e 100644
--- a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.h
+++ b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_common.h
@@ -17,11 +17,14 @@
#include <vespa/searchcore/proton/test/test.h>
#include <vespa/searchcore/proton/test/dummy_document_store.h>
#include <vespa/vespalib/util/idestructorcallback.h>
-#include <vespa/searchlib/index/docbuilder.h>
-using namespace document;
+using document::BucketId;
+using document::GlobalId;
+using document::Document;
+using document::DocumentId;
+using document::DocumentTypeRepo;
using namespace proton;
-using namespace search::index;
+using search::test::DocBuilder;
using namespace search;
using namespace vespalib;
using vespalib::IDestructorCallback;
@@ -60,6 +63,7 @@ struct MyScanIterator : public IDocumentScanIterator {
};
struct MyHandler : public ILidSpaceCompactionHandler {
+ DocBuilder _builder;
std::vector<LidUsageStats> _stats;
std::vector<LidVector> _lids;
mutable uint32_t _moveFromLid;
@@ -103,14 +107,14 @@ struct MyStorer : public IOperationStorer {
CommitResult startCommit(DoneCallback) override;
};
-struct MyFeedView : public test::DummyFeedView {
+struct MyFeedView : public proton::test::DummyFeedView {
explicit MyFeedView(std::shared_ptr<const DocumentTypeRepo> repo)
- : test::DummyFeedView(std::move(repo))
+ : proton::test::DummyFeedView(std::move(repo))
{
}
};
-struct MyDocumentStore : public test::DummyDocumentStore {
+struct MyDocumentStore : public proton::test::DummyDocumentStore {
Document::SP _readDoc;
mutable uint32_t _readLid;
MyDocumentStore();
diff --git a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_handler_test.cpp b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_handler_test.cpp
index bc9cd9a93fa..011de4fc298 100644
--- a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_handler_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_handler_test.cpp
@@ -15,13 +15,13 @@ struct HandlerTest : public ::testing::Test {
};
HandlerTest::HandlerTest()
- : _docBuilder(Schema()),
+ : _docBuilder(),
_bucketDB(std::make_shared<bucketdb::BucketDBOwner>()),
_docStore(),
- _subDb(_bucketDB, _docStore, _docBuilder.getDocumentTypeRepo()),
+ _subDb(_bucketDB, _docStore, _docBuilder.get_repo_sp()),
_handler(_subDb.maintenance_sub_db, "test")
{
- _docStore._readDoc = _docBuilder.startDocument(DOC_ID).endDocument();
+ _docStore._readDoc = _docBuilder.make_document(DOC_ID);
}
HandlerTest::~HandlerTest() = default;
diff --git a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.cpp b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.cpp
index 8f88d678c0c..b941161cc35 100644
--- a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.cpp
+++ b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.cpp
@@ -116,7 +116,7 @@ JobTestBase::compact() {
void
JobTestBase::notifyNodeRetired(bool nodeRetired) {
- test::BucketStateCalculator::SP calc = std::make_shared<test::BucketStateCalculator>();
+ proton::test::BucketStateCalculator::SP calc = std::make_shared<proton::test::BucketStateCalculator>();
calc->setNodeRetired(nodeRetired);
_clusterStateHandler.notifyClusterStateChanged(calc);
}
diff --git a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.h b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.h
index 5875910f4d9..fb4c6d0478a 100644
--- a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.h
+++ b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_jobtest.h
@@ -10,8 +10,8 @@
namespace storage::spi::dummy { class DummyBucketExecutor; }
struct JobTestBase : public ::testing::Test {
vespalib::MonitoredRefCount _refCount;
- test::ClusterStateHandler _clusterStateHandler;
- test::DiskMemUsageNotifier _diskMemUsageNotifier;
+ proton::test::ClusterStateHandler _clusterStateHandler;
+ proton::test::DiskMemUsageNotifier _diskMemUsageNotifier;
std::unique_ptr<storage::spi::dummy::DummyBucketExecutor> _bucketExecutor;
std::unique_ptr<vespalib::SyncableThreadExecutor> _singleExecutor;
std::unique_ptr<searchcorespi::index::ISyncableThreadService> _master;
diff --git a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp
index ea4d556c502..915402122b8 100644
--- a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp
@@ -35,7 +35,6 @@
#include <vespa/searchcore/proton/test/test.h>
#include <vespa/searchcore/proton/test/transport_helper.h>
#include <vespa/searchlib/common/idocumentmetastore.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/vespalib/data/slime/slime.h>
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/util/destructor_callbacks.h>
@@ -99,11 +98,11 @@ class MyDocumentSubDB
uint32_t _subDBId;
DocumentMetaStore::SP _metaStoreSP;
DocumentMetaStore & _metaStore;
- const std::shared_ptr<const document::DocumentTypeRepo> &_repo;
+ std::shared_ptr<const document::DocumentTypeRepo> _repo;
const DocTypeName &_docTypeName;
public:
- MyDocumentSubDB(uint32_t subDBId, SubDbType subDbType, const std::shared_ptr<const document::DocumentTypeRepo> &repo,
+ MyDocumentSubDB(uint32_t subDBId, SubDbType subDbType, std::shared_ptr<const document::DocumentTypeRepo> repo,
std::shared_ptr<bucketdb::BucketDBOwner> bucketDB, const DocTypeName &docTypeName);
~MyDocumentSubDB();
@@ -136,7 +135,7 @@ public:
const IDocumentMetaStore &getMetaStore() const { return _metaStore; }
};
-MyDocumentSubDB::MyDocumentSubDB(uint32_t subDBId, SubDbType subDbType, const std::shared_ptr<const document::DocumentTypeRepo> &repo,
+MyDocumentSubDB::MyDocumentSubDB(uint32_t subDBId, SubDbType subDbType, std::shared_ptr<const document::DocumentTypeRepo> repo,
std::shared_ptr<bucketdb::BucketDBOwner> bucketDB, const DocTypeName &docTypeName)
: _docs(),
_subDBId(subDBId),
@@ -144,7 +143,7 @@ MyDocumentSubDB::MyDocumentSubDB(uint32_t subDBId, SubDbType subDbType, const st
std::move(bucketDB), DocumentMetaStore::getFixedName(), search::GrowStrategy(),
subDbType)),
_metaStore(*_metaStoreSP),
- _repo(repo),
+ _repo(std::move(repo)),
_docTypeName(docTypeName)
{
_metaStore.constructFreeList();
diff --git a/searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp b/searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp
index 00694b6b78f..b8f5fd232de 100644
--- a/searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/storeonlyfeedview/storeonlyfeedview_test.cpp
@@ -2,6 +2,7 @@
#include <vespa/document/base/documentid.h>
#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/fieldvalue/document.h>
#include <vespa/searchcommon/common/schema.h>
#include <vespa/searchcore/proton/server/putdonecontext.h>
#include <vespa/searchcore/proton/server/removedonecontext.h>
@@ -13,7 +14,7 @@
#include <vespa/searchcore/proton/test/mock_summary_adapter.h>
#include <vespa/searchcore/proton/test/transport_helper.h>
#include <vespa/searchcore/proton/test/thread_utils.h>
-#include <vespa/searchlib/index/docbuilder.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/vespalib/util/destructor_callbacks.h>
#include <vespa/vespalib/util/size_literals.h>
#include <vespa/vespalib/testkit/testapp.h>
@@ -32,8 +33,8 @@ using namespace proton;
using search::DocumentIdT;
using vespalib::IDestructorCallback;
using search::SerialNum;
-using search::index::DocBuilder;
using search::index::Schema;
+using search::test::DocBuilder;
using storage::spi::Timestamp;
using vespalib::make_string;
@@ -59,9 +60,8 @@ public:
};
std::shared_ptr<const DocumentTypeRepo> myGetDocumentTypeRepo() {
- Schema schema;
- DocBuilder builder(schema);
- std::shared_ptr<const DocumentTypeRepo> repo = builder.getDocumentTypeRepo();
+ DocBuilder builder;
+ std::shared_ptr<const DocumentTypeRepo> repo = builder.get_repo_sp();
ASSERT_TRUE(repo.get());
return repo;
}
diff --git a/searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp b/searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp
index c62226ad363..4668b8c65ab 100644
--- a/searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp
+++ b/searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp
@@ -1802,7 +1802,7 @@ TEST(DocumentMetaStoreTest, shrink_via_flush_target_works)
ft->getApproxMemoryGain().getAfter());
g.reset();
- dms->removeAllOldGenerations();
+ dms->reclaim_unused_memory();
assertLidSpace(10, shrinkTarget, shrinkTarget - 1, true, true, *dms);
EXPECT_TRUE(ft->getApproxMemoryGain().getBefore() >
ft->getApproxMemoryGain().getAfter());
@@ -1965,6 +1965,38 @@ TEST(DocumentMetaStoreTest, multiple_lids_can_be_removed_with_removeBatch)
dms.removes_complete({1, 3});
}
+TEST(DocumentMetaStoreTest, serialize_for_sort)
+{
+ DocumentMetaStore dms(createBucketDB());
+ dms.constructFreeList();
+ addLid(dms, 1);
+ addLid(dms, 2);
+ assertLidGidFound(1, dms);
+ assertLidGidFound(2, dms);
+
+ constexpr size_t SZ = document::GlobalId::LENGTH;
+ EXPECT_EQ(12u, SZ);
+ EXPECT_EQ(SZ, dms.getFixedWidth());
+ uint8_t asc_dest[SZ];
+ EXPECT_EQ(0, dms.serializeForAscendingSort(3, asc_dest, sizeof(asc_dest), nullptr));
+ EXPECT_EQ(-1, dms.serializeForAscendingSort(1, asc_dest, sizeof(asc_dest) - 1, nullptr));
+ document::GlobalId gid;
+
+ EXPECT_EQ(SZ, dms.serializeForAscendingSort(1, asc_dest, sizeof(asc_dest), nullptr));
+ EXPECT_TRUE(dms.getGid(1, gid));
+ EXPECT_EQ(0, memcmp(asc_dest, gid.get(), SZ));
+
+ EXPECT_EQ(SZ, dms.serializeForAscendingSort(2, asc_dest, sizeof(asc_dest), nullptr));
+ EXPECT_TRUE(dms.getGid(2, gid));
+ EXPECT_EQ(0, memcmp(asc_dest, gid.get(), SZ));
+
+ uint8_t desc_dest[SZ];
+ EXPECT_EQ(SZ, dms.serializeForDescendingSort(2, desc_dest, sizeof(desc_dest), nullptr));
+ for (size_t i(0); i < SZ; i++) {
+ EXPECT_EQ(0xff - asc_dest[i], desc_dest[i]);
+ }
+}
+
class MockOperationListener : public documentmetastore::OperationListener {
public:
size_t remove_batch_cnt;
@@ -2008,7 +2040,7 @@ namespace {
void try_compact_document_meta_store(DocumentMetaStore &dms)
{
- dms.removeAllOldGenerations();
+ dms.reclaim_unused_memory();
dms.commit(true);
}
diff --git a/searchcore/src/tests/proton/documentmetastore/lid_allocator/lid_allocator_test.cpp b/searchcore/src/tests/proton/documentmetastore/lid_allocator/lid_allocator_test.cpp
index 2d675e82db2..8d8674da4f0 100644
--- a/searchcore/src/tests/proton/documentmetastore/lid_allocator/lid_allocator_test.cpp
+++ b/searchcore/src/tests/proton/documentmetastore/lid_allocator/lid_allocator_test.cpp
@@ -28,7 +28,7 @@ protected:
~LidAllocatorTest()
{
- _gen_hold.clearHoldLists();
+ _gen_hold.reclaim_all();
}
uint32_t get_size() { return _allocator.getActiveLids().size(); }
@@ -66,8 +66,8 @@ protected:
_allocator.holdLids(lids, get_size(), 0);
}
- void trim_hold_lists() {
- _allocator.trimHoldLists(1);
+ void reclaim_memory() {
+ _allocator.reclaim_memory(1);
}
std::vector<uint32_t> get_valid_lids() {
@@ -117,7 +117,7 @@ TEST_F(LidAllocatorTest, unregister_lids)
assert_valid_lids({2, 4, 6});
assert_active_lids({4, 6});
hold_lids({1, 3, 5});
- trim_hold_lists();
+ reclaim_memory();
EXPECT_EQ((std::vector<uint32_t>{1, 3, 5, 7, 8}), alloc_lids(5));
}
diff --git a/searchcore/src/tests/proton/documentmetastore/lid_state_vector/lid_state_vector_test.cpp b/searchcore/src/tests/proton/documentmetastore/lid_state_vector/lid_state_vector_test.cpp
index ab45cca0971..cbc11126b25 100644
--- a/searchcore/src/tests/proton/documentmetastore/lid_state_vector/lid_state_vector_test.cpp
+++ b/searchcore/src/tests/proton/documentmetastore/lid_state_vector/lid_state_vector_test.cpp
@@ -22,7 +22,7 @@ protected:
~LidStateVectorTest()
{
- _gen_hold.clearHoldLists();
+ _gen_hold.reclaim_all();
}
};
@@ -47,7 +47,7 @@ TEST_F(LidStateVectorTest, basic_free_list_is_working)
EXPECT_EQ(0u, freeLids.count());
EXPECT_EQ(3u, list.size());
- list.trimHoldLists(20, freeLids);
+ list.reclaim_memory(20, freeLids);
EXPECT_FALSE(freeLids.empty());
EXPECT_EQ(1u, freeLids.count());
@@ -57,7 +57,7 @@ TEST_F(LidStateVectorTest, basic_free_list_is_working)
EXPECT_EQ(0u, freeLids.count());
EXPECT_EQ(2u, list.size());
- list.trimHoldLists(31, freeLids);
+ list.reclaim_memory(31, freeLids);
EXPECT_FALSE(freeLids.empty());
EXPECT_EQ(2u, freeLids.count());
diff --git a/searchcore/src/tests/proton/feed_and_search/CMakeLists.txt b/searchcore/src/tests/proton/feed_and_search/CMakeLists.txt
index a565d791988..24588d21d99 100644
--- a/searchcore/src/tests/proton/feed_and_search/CMakeLists.txt
+++ b/searchcore/src/tests/proton/feed_and_search/CMakeLists.txt
@@ -3,5 +3,6 @@ vespa_add_executable(searchcore_feed_and_search_test_app TEST
SOURCES
feed_and_search.cpp
DEPENDS
+ searchlib_test
)
vespa_add_test(NAME searchcore_feed_and_search_test_app COMMAND searchcore_feed_and_search_test_app)
diff --git a/searchcore/src/tests/proton/feed_and_search/feed_and_search.cpp b/searchcore/src/tests/proton/feed_and_search/feed_and_search.cpp
index ac540ad2e2d..3ae7a55aabd 100644
--- a/searchcore/src/tests/proton/feed_and_search/feed_and_search.cpp
+++ b/searchcore/src/tests/proton/feed_and_search/feed_and_search.cpp
@@ -3,6 +3,8 @@
#include <vespa/document/datatype/datatype.h>
#include <vespa/document/fieldvalue/document.h>
#include <vespa/document/fieldvalue/fieldvalue.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/searchlib/common/documentsummary.h>
#include <vespa/vespalib/util/sequencedtaskexecutor.h>
#include <vespa/searchlib/common/flush_token.h>
@@ -10,9 +12,10 @@
#include <vespa/searchlib/diskindex/fusion.h>
#include <vespa/searchlib/diskindex/indexbuilder.h>
#include <vespa/searchlib/fef/fef.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
#include <vespa/searchlib/memoryindex/memory_index.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/test/index/mock_field_length_inspector.h>
#include <vespa/searchlib/query/base.h>
#include <vespa/searchlib/query/tree/simplequery.h>
@@ -31,6 +34,7 @@ LOG_SETUP("feed_and_search_test");
using document::DataType;
using document::Document;
using document::FieldValue;
+using document::StringFieldValue;
using search::DocumentIdT;
using search::FlushToken;
using search::TuneFileIndexing;
@@ -44,7 +48,6 @@ using search::fef::MatchData;
using search::fef::MatchDataLayout;
using search::fef::TermFieldHandle;
using search::fef::TermFieldMatchData;
-using search::index::DocBuilder;
using search::index::DummyFileHeaderContext;
using search::index::Schema;
using search::index::test::MockFieldLengthInspector;
@@ -56,6 +59,8 @@ using search::queryeval::FieldSpec;
using search::queryeval::FieldSpecList;
using search::queryeval::SearchIterator;
using search::queryeval::Searchable;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using std::ostringstream;
using vespalib::string;
@@ -117,10 +122,9 @@ Document::UP buildDocument(DocBuilder & doc_builder, int id,
const string &word) {
ostringstream ost;
ost << "id:ns:searchdocument::" << id;
- doc_builder.startDocument(ost.str());
- doc_builder.startIndexField(field_name)
- .addStr(noise).addStr(word).endField();
- return doc_builder.endDocument();
+ auto doc = doc_builder.make_document(ost.str());
+ doc->setValue(field_name, StringFieldBuilder(doc_builder).word(noise).space().word(word).build());
+ return doc;
}
// Performs a search using a Searchable.
@@ -165,7 +169,7 @@ void Test::requireThatMemoryIndexCanBeDumpedAndSearched() {
auto indexFieldInverter = vespalib::SequencedTaskExecutor::create(invert_executor, 2);
auto indexFieldWriter = vespalib::SequencedTaskExecutor::create(write_executor, 2);
MemoryIndex memory_index(schema, MockFieldLengthInspector(), *indexFieldInverter, *indexFieldWriter);
- DocBuilder doc_builder(schema);
+ DocBuilder doc_builder([](auto& header) { header.addField(field_name, DataType::T_STRING); });
Document::UP doc = buildDocument(doc_builder, doc_id1, word1);
memory_index.insertDocument(doc_id1, *doc, {});
diff --git a/searchcore/src/tests/proton/index/fusionrunner_test.cpp b/searchcore/src/tests/proton/index/fusionrunner_test.cpp
index 850f8a8f0d1..0475d37b32c 100644
--- a/searchcore/src/tests/proton/index/fusionrunner_test.cpp
+++ b/searchcore/src/tests/proton/index/fusionrunner_test.cpp
@@ -1,15 +1,19 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/searchcorespi/index/fusionrunner.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/searchcore/proton/index/indexmanager.h>
#include <vespa/searchcore/proton/test/transport_helper.h>
-#include <vespa/searchcorespi/index/fusionrunner.h>
#include <vespa/vespalib/util/isequencedtaskexecutor.h>
#include <vespa/searchlib/common/flush_token.h>
#include <vespa/searchlib/diskindex/diskindex.h>
#include <vespa/searchlib/diskindex/indexbuilder.h>
#include <vespa/searchlib/fef/matchdatalayout.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/memoryindex/memory_index.h>
#include <vespa/searchlib/query/tree/simplequery.h>
#include <vespa/searchlib/test/index/mock_field_length_inspector.h>
@@ -25,6 +29,7 @@
using document::Document;
using document::FieldValue;
+using document::StringFieldValue;
using proton::ExecutorThreadingService;
using proton::index::IndexManager;
using search::FixedSourceSelector;
@@ -38,7 +43,6 @@ using search::fef::MatchData;
using search::fef::MatchDataLayout;
using search::fef::TermFieldHandle;
using search::fef::TermFieldMatchData;
-using search::index::DocBuilder;
using search::index::DummyFileHeaderContext;
using search::index::Schema;
using search::index::schema::DataType;
@@ -51,6 +55,8 @@ using search::queryeval::FieldSpec;
using search::queryeval::FieldSpecList;
using search::queryeval::ISourceSelector;
using search::queryeval::SearchIterator;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using searchcorespi::index::FusionRunner;
using searchcorespi::index::FusionSpec;
using std::set;
@@ -152,9 +158,9 @@ void Test::tearDown() {
Document::UP buildDocument(DocBuilder & doc_builder, int id, const string &word) {
vespalib::asciistream ost;
ost << "id:ns:searchdocument::" << id;
- doc_builder.startDocument(ost.str());
- doc_builder.startIndexField(field_name).addStr(word).endField();
- return doc_builder.endDocument();
+ auto doc = doc_builder.make_document(ost.str());
+ doc->setValue(field_name, StringFieldBuilder(doc_builder).word(word).build());
+ return doc;
}
void addDocument(DocBuilder & doc_builder, MemoryIndex &index, ISourceSelector &selector,
@@ -181,7 +187,7 @@ void Test::createIndex(const string &dir, uint32_t id, bool fusion) {
_selector->setDefaultSource(id - _selector->getBaseId());
Schema schema = getSchema();
- DocBuilder doc_builder(schema);
+ DocBuilder doc_builder([](auto& header) { header.addField(field_name, document::DataType::T_STRING); });
MemoryIndex memory_index(schema, MockFieldLengthInspector(),
_service.write().indexFieldInverter(),
_service.write().indexFieldWriter());
diff --git a/searchcore/src/tests/proton/index/index_writer/index_writer_test.cpp b/searchcore/src/tests/proton/index/index_writer/index_writer_test.cpp
index 75e6b01b46f..7a135e46937 100644
--- a/searchcore/src/tests/proton/index/index_writer/index_writer_test.cpp
+++ b/searchcore/src/tests/proton/index/index_writer/index_writer_test.cpp
@@ -1,10 +1,12 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include <vespa/vespalib/testkit/testapp.h>
-
#include <vespa/searchcore/proton/index/index_writer.h>
+#include <vespa/document/fieldvalue/document.h>
#include <vespa/searchcore/proton/test/mock_index_manager.h>
-#include <vespa/searchlib/index/docbuilder.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/stringfmt.h>
+
#include <vespa/log/log.h>
LOG_SETUP("index_writer_test");
@@ -12,6 +14,7 @@ using namespace proton;
using namespace search;
using namespace search::index;
using namespace searchcorespi;
+using search::test::DocBuilder;
using vespalib::IDestructorCallback;
using document::Document;
@@ -27,7 +30,7 @@ toString(const std::vector<SerialNum> &vec)
return oss.str();
}
-struct MyIndexManager : public test::MockIndexManager
+struct MyIndexManager : public proton::test::MockIndexManager
{
typedef std::map<uint32_t, std::vector<SerialNum> > LidMap;
LidMap puts;
@@ -80,21 +83,18 @@ struct Fixture
IIndexManager::SP iim;
MyIndexManager &mim;
IndexWriter iw;
- Schema schema;
- DocBuilder builder;
+ DocBuilder builder;
Document::UP dummyDoc;
Fixture()
: iim(new MyIndexManager()),
mim(static_cast<MyIndexManager &>(*iim)),
iw(iim),
- schema(),
- builder(schema),
+ builder(),
dummyDoc(createDoc(1234)) // This content of this is not used
{
}
Document::UP createDoc(uint32_t lid) {
- builder.startDocument(vespalib::make_string("id:ns:searchdocument::%u", lid));
- return builder.endDocument();
+ return builder.make_document(vespalib::make_string("id:ns:searchdocument::%u", lid));
}
void put(SerialNum serialNum, const search::DocumentIdT lid) {
iw.put(serialNum, *dummyDoc, lid, {});
diff --git a/searchcore/src/tests/proton/index/indexmanager_test.cpp b/searchcore/src/tests/proton/index/indexmanager_test.cpp
index b427daa4ad1..68f1d4d0d0e 100644
--- a/searchcore/src/tests/proton/index/indexmanager_test.cpp
+++ b/searchcore/src/tests/proton/index/indexmanager_test.cpp
@@ -1,6 +1,10 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/searchcore/proton/index/indexmanager.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/fieldvalue/document.h>
#include <vespa/searchcore/proton/test/transport_helper.h>
#include <vespa/searchcorespi/index/index_manager_stats.h>
#include <vespa/searchcorespi/index/indexcollection.h>
@@ -9,8 +13,9 @@
#include <vespa/vespalib/util/sequencedtaskexecutor.h>
#include <vespa/searchlib/common/flush_token.h>
#include <vespa/searchlib/common/serialnum.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/memoryindex/compact_words_store.h>
#include <vespa/searchlib/memoryindex/document_inverter.h>
#include <vespa/searchlib/memoryindex/document_inverter_context.h>
@@ -34,6 +39,7 @@ LOG_SETUP("indexmanager_test");
using document::Document;
using document::FieldValue;
+using document::StringFieldValue;
using proton::index::IndexConfig;
using proton::index::IndexManager;
using vespalib::SequencedTaskExecutor;
@@ -42,7 +48,6 @@ using search::TuneFileAttributes;
using search::TuneFileIndexManager;
using search::TuneFileIndexing;
using vespalib::datastore::EntryRef;
-using search::index::DocBuilder;
using search::index::DummyFileHeaderContext;
using search::index::FieldLengthInfo;
using search::index::Schema;
@@ -51,6 +56,8 @@ using search::index::test::MockFieldLengthInspector;
using search::memoryindex::CompactWordsStore;
using search::memoryindex::FieldIndexCollection;
using search::queryeval::Source;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using std::set;
using std::string;
using vespalib::makeLambdaTask;
@@ -92,9 +99,9 @@ Document::UP buildDocument(DocBuilder &doc_builder, int id,
const string &word) {
vespalib::asciistream ost;
ost << "id:ns:searchdocument::" << id;
- doc_builder.startDocument(ost.str());
- doc_builder.startIndexField(field_name).addStr(word).endField();
- return doc_builder.endDocument();
+ auto doc = doc_builder.make_document(ost.str());
+ doc->setValue(field_name, StringFieldBuilder(doc_builder).word(word).build());
+ return doc;
}
void push_documents_and_wait(search::memoryindex::DocumentInverter &inverter) {
@@ -119,7 +126,7 @@ struct IndexManagerTest : public ::testing::Test {
_service(1),
_index_manager(),
_schema(getSchema()),
- _builder(_schema)
+ _builder([](auto& header) { header.addField(field_name, document::DataType::T_STRING); })
{
removeTestData();
std::filesystem::create_directory(std::filesystem::path(index_dir));
diff --git a/searchcore/src/tests/proton/reference/gid_to_lid_mapper/gid_to_lid_mapper_test.cpp b/searchcore/src/tests/proton/reference/gid_to_lid_mapper/gid_to_lid_mapper_test.cpp
index 2f4c26094c7..5152d09fae5 100644
--- a/searchcore/src/tests/proton/reference/gid_to_lid_mapper/gid_to_lid_mapper_test.cpp
+++ b/searchcore/src/tests/proton/reference/gid_to_lid_mapper/gid_to_lid_mapper_test.cpp
@@ -126,11 +126,11 @@ struct Fixture
return std::make_shared<GidToLidMapperFactory>(_dmsContext);
}
- void assertGenerations(generation_t currentGeneration, generation_t firstUsedGeneration)
+ void assertGenerations(generation_t currentGeneration, generation_t oldest_used_generation)
{
const GenerationHandler &handler = _dms->getGenerationHandler();
EXPECT_EQUAL(currentGeneration, handler.getCurrentGeneration());
- EXPECT_EQUAL(firstUsedGeneration, handler.getFirstUsedGeneration());
+ EXPECT_EQUAL(oldest_used_generation, handler.get_oldest_used_generation());
}
template <typename Function>
diff --git a/searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/CMakeLists.txt b/searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/CMakeLists.txt
index f776734757d..e980ae817f1 100644
--- a/searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/CMakeLists.txt
+++ b/searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/CMakeLists.txt
@@ -4,5 +4,6 @@ vespa_add_executable(searchcore_document_reprocessing_handler_test_app TEST
document_reprocessing_handler_test.cpp
DEPENDS
searchcore_reprocessing
+ searchlib_test
)
vespa_add_test(NAME searchcore_document_reprocessing_handler_test_app COMMAND searchcore_document_reprocessing_handler_test_app)
diff --git a/searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/document_reprocessing_handler_test.cpp b/searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/document_reprocessing_handler_test.cpp
index da645f9a94b..0755a172945 100644
--- a/searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/document_reprocessing_handler_test.cpp
+++ b/searchcore/src/tests/proton/reprocessing/document_reprocessing_handler/document_reprocessing_handler_test.cpp
@@ -3,12 +3,12 @@
LOG_SETUP("document_reprocessing_handler_test");
#include <vespa/searchcore/proton/reprocessing/document_reprocessing_handler.h>
-#include <vespa/searchlib/index/docbuilder.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/vespalib/testkit/testapp.h>
using namespace document;
using namespace proton;
-using namespace search::index;
+using search::test::DocBuilder;
template <typename ReprocessingType>
struct MyProcessor : public ReprocessingType
@@ -36,13 +36,13 @@ struct FixtureBase
FixtureBase(uint32_t docIdLimit);
~FixtureBase();
std::shared_ptr<Document> createDoc() {
- return _docBuilder.startDocument(DOC_ID).endDocument();
+ return _docBuilder.make_document(DOC_ID);
}
};
FixtureBase::FixtureBase(uint32_t docIdLimit)
: _handler(docIdLimit),
- _docBuilder(Schema())
+ _docBuilder()
{ }
FixtureBase::~FixtureBase() {}
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.cpp
index fb99ca51e3a..3d6d522dd44 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.cpp
@@ -30,7 +30,7 @@ public:
DocumentMetaStore::SP documentMetaStore,
InitializedAttributesResult &result)
: _initializer(std::move(initializer)),
- _documentMetaStore(documentMetaStore),
+ _documentMetaStore(std::move(documentMetaStore)),
_result(result)
{}
@@ -74,8 +74,8 @@ AttributeManagerInitializerTask::AttributeManagerInitializerTask(std::promise<vo
InitializedAttributesResult &attributesResult)
: _promise(std::move(promise)),
_configSerialNum(configSerialNum),
- _documentMetaStore(documentMetaStore),
- _attrMgr(attrMgr),
+ _documentMetaStore(std::move(documentMetaStore)),
+ _attrMgr(std::move(attrMgr)),
_attributesResult(attributesResult)
{
}
@@ -104,7 +104,7 @@ public:
InitializerTask::SP documentMetaStoreInitTask,
DocumentMetaStore::SP documentMetaStore,
InitializedAttributesResult &attributesResult);
- ~AttributeInitializerTasksBuilder();
+ ~AttributeInitializerTasksBuilder() override;
void add(AttributeInitializer::UP initializer) override;
};
@@ -113,8 +113,8 @@ AttributeInitializerTasksBuilder::AttributeInitializerTasksBuilder(InitializerTa
DocumentMetaStore::SP documentMetaStore,
InitializedAttributesResult &attributesResult)
: _attrMgrInitTask(attrMgrInitTask),
- _documentMetaStoreInitTask(documentMetaStoreInitTask),
- _documentMetaStore(documentMetaStore),
+ _documentMetaStoreInitTask(std::move(documentMetaStoreInitTask)),
+ _documentMetaStore(std::move(documentMetaStore)),
_attributesResult(attributesResult)
{ }
@@ -143,7 +143,7 @@ AttributeManagerInitializer::createAttributeSpec() const
AttributeManagerInitializer::AttributeManagerInitializer(SerialNum configSerialNum,
initializer::InitializerTask::SP documentMetaStoreInitTask,
DocumentMetaStore::SP documentMetaStore,
- AttributeManager::SP baseAttrMgr,
+ const AttributeManager & baseAttrMgr,
const AttributesConfig &attrCfg,
const AllocStrategy& alloc_strategy,
bool fastAccessAttributesOnly,
@@ -157,12 +157,12 @@ AttributeManagerInitializer::AttributeManagerInitializer(SerialNum configSerialN
_fastAccessAttributesOnly(fastAccessAttributesOnly),
_master(master),
_attributesResult(),
- _attrMgrResult(attrMgrResult)
+ _attrMgrResult(std::move(attrMgrResult))
{
addDependency(documentMetaStoreInitTask);
- AttributeInitializerTasksBuilder tasksBuilder(*this, documentMetaStoreInitTask, documentMetaStore, _attributesResult);
+ AttributeInitializerTasksBuilder tasksBuilder(*this, std::move(documentMetaStoreInitTask), std::move(documentMetaStore), _attributesResult);
std::unique_ptr<AttributeCollectionSpec> attrSpec = createAttributeSpec();
- _attrMgr = std::make_shared<AttributeManager>(*baseAttrMgr, std::move(*attrSpec), tasksBuilder);
+ _attrMgr = std::make_shared<AttributeManager>(baseAttrMgr, std::move(*attrSpec), tasksBuilder);
}
AttributeManagerInitializer::~AttributeManagerInitializer() = default;
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.h b/searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.h
index 99b6b026f8f..79203696ea1 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.h
+++ b/searchcore/src/vespa/searchcore/proton/attribute/attribute_manager_initializer.h
@@ -10,7 +10,7 @@
#include <vespa/searchlib/common/serialnum.h>
#include <vespa/config-attributes.h>
-namespace searchcorespi { namespace index { struct IThreadService; } }
+namespace searchcorespi::index { struct IThreadService; }
namespace proton {
@@ -36,7 +36,7 @@ public:
AttributeManagerInitializer(search::SerialNum configSerialNum,
initializer::InitializerTask::SP documentMetaStoreInitTask,
DocumentMetaStore::SP documentMetaStore,
- AttributeManager::SP baseAttrMgr,
+ const AttributeManager & baseAttrMgr,
const vespa::config::search::AttributesConfig &attrCfg,
const AllocStrategy& alloc_strategy,
bool fastAccessAttributesOnly,
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attribute_vector_explorer.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attribute_vector_explorer.cpp
index eeb8cafb859..c153f873480 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/attribute_vector_explorer.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/attribute_vector_explorer.cpp
@@ -47,7 +47,7 @@ convertStatusToSlime(const Status &status, Cursor &object)
void
convertGenerationToSlime(const AttributeVector &attr, Cursor &object)
{
- object.setLong("firstUsed", attr.getFirstUsedGeneration());
+ object.setLong("oldest_used", attr.get_oldest_used_generation());
object.setLong("current", attr.getCurrentGeneration());
}
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp
index 69ef42ef9a8..418615058ce 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/attribute_writer.cpp
@@ -239,7 +239,7 @@ applyReplayDone(uint32_t docIdLimit, AttributeVector &attr)
void
applyHeartBeat(SerialNum serialNum, AttributeVector &attr)
{
- attr.removeAllOldGenerations();
+ attr.reclaim_unused_memory();
if (attr.getStatus().getLastSyncToken() <= serialNum) {
attr.commit(search::CommitParam(serialNum));
}
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attributemanager.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attributemanager.cpp
index be8bcd8e66a..939ae196de8 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/attributemanager.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/attributemanager.cpp
@@ -79,8 +79,8 @@ allocShrinker(const AttributeVector::SP &attr, vespalib::ISequencedTaskExecutor
}
-AttributeManager::AttributeWrap::AttributeWrap(const AttributeVectorSP & a, bool isExtra_)
- : _attr(a),
+AttributeManager::AttributeWrap::AttributeWrap(AttributeVectorSP a, bool isExtra_)
+ : _attr(std::move(a)),
_isExtra(isExtra_)
{
}
@@ -94,15 +94,15 @@ AttributeManager::AttributeWrap::AttributeWrap()
AttributeManager::AttributeWrap::~AttributeWrap() = default;
AttributeManager::AttributeWrap
-AttributeManager::AttributeWrap::extraAttribute(const AttributeVectorSP &a)
+AttributeManager::AttributeWrap::extraAttribute(AttributeVectorSP a)
{
- return AttributeWrap(a, true);
+ return {std::move(a), true};
}
AttributeManager::AttributeWrap
-AttributeManager::AttributeWrap::normalAttribute(const AttributeVectorSP &a)
+AttributeManager::AttributeWrap::normalAttribute(AttributeVectorSP a)
{
- return AttributeWrap(a, false);
+ return {std::move(a), false};
}
AttributeManager::FlushableWrap::FlushableWrap()
@@ -136,18 +136,20 @@ AttributeManager::internalAddAttribute(AttributeSpec && spec,
}
void
-AttributeManager::addAttribute(const AttributeWrap &attribute, const ShrinkerSP &shrinker)
-{
- LOG(debug, "Adding attribute vector '%s'", attribute.getAttribute()->getName().c_str());
- _attributes[attribute.getAttribute()->getName()] = attribute;
- assert(attribute.getAttribute()->getInterlock() == _interlock);
- if ( ! attribute.isExtra() ) {
+AttributeManager::addAttribute(AttributeWrap attributeWrap, const ShrinkerSP &shrinker)
+{
+ AttributeVector::SP attribute = attributeWrap.getAttribute();
+ bool isExtra = attributeWrap.isExtra();
+ const vespalib::string &name = attribute->getName();
+ LOG(debug, "Adding attribute vector '%s'", name.c_str());
+ _attributes[name] = std::move(attributeWrap);
+ assert(attribute->getInterlock() == _interlock);
+ if ( ! isExtra ) {
// Flushing of extra attributes is handled elsewhere
- auto attr = attribute.getAttribute();
- const vespalib::string &name = attr->getName();
- auto flusher = std::make_shared<FlushableAttribute>(attr, _diskLayout->createAttributeDir(name), _tuneFileAttributes, _fileHeaderContext, _attributeFieldWriter, _hwInfo);
- _flushables[attribute.getAttribute()->getName()] = FlushableWrap(flusher, shrinker);
- _writableAttributes.push_back(attribute.getAttribute().get());
+ AttributeVector * attributeP = attribute.get();
+ auto flusher = std::make_shared<FlushableAttribute>(std::move(attribute), _diskLayout->createAttributeDir(name), _tuneFileAttributes, _fileHeaderContext, _attributeFieldWriter, _hwInfo);
+ _flushables[name] = FlushableWrap(flusher, shrinker);
+ _writableAttributes.push_back(attributeP);
}
}
@@ -234,7 +236,7 @@ AttributeManager::transferExtraAttributes(const AttributeManager &currMgr)
{
for (const auto &kv : currMgr._attributes) {
if (kv.second.isExtra()) {
- addAttribute(kv.second, 0);
+ addAttribute(kv.second, nullptr);
}
}
}
@@ -271,7 +273,7 @@ AttributeManager::AttributeManager(const vespalib::string &baseDir,
std::shared_ptr<search::attribute::Interlock> interlock,
vespalib::ISequencedTaskExecutor &attributeFieldWriter,
vespalib::Executor& shared_executor,
- const IAttributeFactory::SP &factory,
+ IAttributeFactory::SP factory,
const HwInfo &hwInfo)
: proton::IAttributeManager(),
_attributes(),
@@ -281,7 +283,7 @@ AttributeManager::AttributeManager(const vespalib::string &baseDir,
_documentSubDbName(documentSubDbName),
_tuneFileAttributes(tuneFileAttributes),
_fileHeaderContext(fileHeaderContext),
- _factory(factory),
+ _factory(std::move(factory)),
_interlock(std::move(interlock)),
_attributeFieldWriter(attributeFieldWriter),
_shared_executor(shared_executor),
@@ -329,7 +331,7 @@ AttributeManager::addInitializedAttributes(const std::vector<AttributeInitialize
auto attr = result.getAttribute();
attr->setInterlock(_interlock);
auto shrinker = allocShrinker(attr, _attributeFieldWriter, *_diskLayout);
- addAttribute(AttributeWrap::normalAttribute(attr), shrinker);
+ addAttribute(AttributeWrap::normalAttribute(std::move(attr)), shrinker);
}
}
@@ -414,7 +416,7 @@ AttributeManager::getAttributeReadGuard(const string &name, bool stableEnumGuard
if (attribute) {
return attribute->makeReadGuard(stableEnumGuard);
} else {
- return std::unique_ptr<search::attribute::AttributeReadGuard>();
+ return {};
}
}
@@ -424,7 +426,7 @@ AttributeManager::getAttributeList(std::vector<AttributeGuard> &list) const
list.reserve(_attributes.size());
for (const auto &kv : _attributes) {
if (!kv.second.isExtra()) {
- list.push_back(AttributeGuard(kv.second.getAttribute()));
+ list.emplace_back(kv.second.getAttribute());
}
}
}
@@ -541,7 +543,7 @@ AttributeManager::getAttributeListAll(std::vector<AttributeGuard> &list) const
{
list.reserve(_attributes.size());
for (const auto &kv : _attributes) {
- list.push_back(AttributeGuard(kv.second.getAttribute()));
+ list.emplace_back(kv.second.getAttribute());
}
}
@@ -629,7 +631,7 @@ AttributeManager::getExclusiveReadAccessor(const vespalib::string &name) const
if (attribute) {
return std::make_unique<ExclusiveAttributeReadAccessor>(attribute, _attributeFieldWriter);
}
- return ExclusiveAttributeReadAccessor::UP();
+ return {};
}
void
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attributemanager.h b/searchcore/src/vespa/searchcore/proton/attribute/attributemanager.h
index 2d5b75e71f4..b74e7e72a0e 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/attributemanager.h
+++ b/searchcore/src/vespa/searchcore/proton/attribute/attributemanager.h
@@ -45,12 +45,16 @@ private:
private:
AttributeVectorSP _attr;
bool _isExtra;
- AttributeWrap(const AttributeVectorSP & a, bool isExtra_);
+ AttributeWrap(AttributeVectorSP a, bool isExtra_);
public:
AttributeWrap();
+ AttributeWrap(const AttributeWrap &) = default;
+ AttributeWrap & operator=(const AttributeWrap &) = delete;
+ AttributeWrap(AttributeWrap &&) noexcept = default;
+ AttributeWrap & operator=(AttributeWrap &&) noexcept = default;
~AttributeWrap();
- static AttributeWrap extraAttribute(const AttributeVectorSP &a);
- static AttributeWrap normalAttribute(const AttributeVectorSP &a);
+ static AttributeWrap extraAttribute(AttributeVectorSP a);
+ static AttributeWrap normalAttribute(AttributeVectorSP a);
bool isExtra() const { return _isExtra; }
const AttributeVectorSP & getAttribute() const { return _attr; }
};
@@ -85,20 +89,12 @@ private:
std::unique_ptr<ImportedAttributesRepo> _importedAttributes;
AttributeVectorSP internalAddAttribute(AttributeSpec && spec, uint64_t serialNum, const IAttributeFactory &factory);
-
- void addAttribute(const AttributeWrap &attribute, const ShrinkerSP &shrinker);
-
+ void addAttribute(AttributeWrap attribute, const ShrinkerSP &shrinker);
AttributeVectorSP findAttribute(const vespalib::string &name) const;
-
const FlushableWrap *findFlushable(const vespalib::string &name) const;
-
Spec::AttributeList transferExistingAttributes(const AttributeManager &currMgr, Spec::AttributeList && newAttributes);
-
- void addNewAttributes(const Spec &newSpec, Spec::AttributeList && toBeAdded,
- IAttributeInitializerRegistry &initializerRegistry);
-
+ void addNewAttributes(const Spec &newSpec, Spec::AttributeList && toBeAdded, IAttributeInitializerRegistry &initializerRegistry);
void transferExtraAttributes(const AttributeManager &currMgr);
-
public:
using SP = std::shared_ptr<AttributeManager>;
@@ -118,7 +114,7 @@ public:
std::shared_ptr<search::attribute::Interlock> interlock,
vespalib::ISequencedTaskExecutor &attributeFieldWriter,
vespalib::Executor& shared_executor,
- const IAttributeFactory::SP &factory,
+ IAttributeFactory::SP factory,
const HwInfo &hwInfo);
AttributeManager(const AttributeManager &currMgr, Spec && newSpec,
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.cpp b/searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.cpp
index 93663637e75..f1b7eac3712 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.cpp
@@ -146,7 +146,7 @@ FlushableAttribute::Flusher::run()
}
}
-FlushableAttribute::FlushableAttribute(const AttributeVectorSP attr,
+FlushableAttribute::FlushableAttribute(AttributeVectorSP attr,
const std::shared_ptr<AttributeDirectory> &attrDir,
const TuneFileAttributes &
tuneFileAttributes,
@@ -207,7 +207,7 @@ IFlushTarget::Task::UP
FlushableAttribute::internalInitFlush(SerialNum currentSerial)
{
// Called by attribute field writer thread while document db executor waits
- _attr->removeAllOldGenerations();
+ _attr->reclaim_unused_memory();
SerialNum syncToken = std::max(currentSerial, _attr->getStatus().getLastSyncToken());
auto writer = _attrDir->tryGetWriter();
if (!writer) {
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.h b/searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.h
index e1054a19b6c..39d79372f25 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.h
+++ b/searchcore/src/vespa/searchcore/proton/attribute/flushableattribute.h
@@ -51,7 +51,7 @@ public:
*
* fileHeaderContext must be kept alive by caller.
**/
- FlushableAttribute(const AttributeVectorSP attr,
+ FlushableAttribute(AttributeVectorSP attr,
const std::shared_ptr<AttributeDirectory> &attrDir,
const search::TuneFileAttributes &tuneFileAttributes,
const search::common::FileHeaderContext &
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/document_meta_store_adapter.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/document_meta_store_adapter.h
index 843a9eaaeaa..a4f744df6f9 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/document_meta_store_adapter.h
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/document_meta_store_adapter.h
@@ -24,7 +24,7 @@ public:
DocId getCommittedDocIdLimit() const override {
return doGetCommittedDocIdLimit();
}
- void removeAllOldGenerations() override {
+ void reclaim_unused_memory() override {
doRemoveAllOldGenerations();
}
uint64_t getCurrentGeneration() const override {
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
index b1b5f45a8fa..77452b60f21 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
@@ -72,17 +72,8 @@ private:
uint32_t _version;
public:
- Reader(std::unique_ptr<FastOS_FileInterface> datFile)
- : _datFile(std::move(datFile)),
- _lidReader(&_datFile.file()),
- _gidReader(&_datFile.file()),
- _bucketUsedBitsReader(&_datFile.file()),
- _timestampReader(&_datFile.file()),
- _docIdLimit(0)
- {
- _docIdLimit = _datFile.header().getTag(DOCID_LIMIT).asInteger();
- _version = _datFile.header().getTag(VERSION).asInteger();
- }
+ explicit Reader(std::unique_ptr<FastOS_FileInterface> datFile);
+ ~Reader();
uint32_t getDocIdLimit() const { return _docIdLimit; }
@@ -126,6 +117,19 @@ public:
}
};
+Reader::Reader(std::unique_ptr<FastOS_FileInterface> datFile)
+ : _datFile(std::move(datFile)),
+ _lidReader(&_datFile.file()),
+ _gidReader(&_datFile.file()),
+ _bucketUsedBitsReader(&_datFile.file()),
+ _timestampReader(&_datFile.file()),
+ _docIdLimit(0)
+{
+ _docIdLimit = _datFile.header().getTag(DOCID_LIMIT).asInteger();
+ _version = _datFile.header().getTag(VERSION).asInteger();
+}
+Reader::~Reader() = default;
+
}
namespace {
@@ -134,12 +138,12 @@ class ShrinkBlockHeld : public GenerationHeldBase
DocumentMetaStore &_dms;
public:
- ShrinkBlockHeld(DocumentMetaStore &dms)
+ explicit ShrinkBlockHeld(DocumentMetaStore &dms)
: GenerationHeldBase(0),
_dms(dms)
{ }
- ~ShrinkBlockHeld() {
+ ~ShrinkBlockHeld() override {
_dms.unblockShrinkLidSpace();
}
};
@@ -224,7 +228,7 @@ DocumentMetaStore::onUpdateStat()
{
auto &compaction_strategy = getConfig().getCompactionStrategy();
vespalib::MemoryUsage usage = _metaDataStore.getMemoryUsage();
- usage.incAllocatedBytesOnHold(getGenerationHolder().getHeldBytes());
+ usage.incAllocatedBytesOnHold(getGenerationHolder().get_held_bytes());
size_t bvSize = _lidAlloc.getUsedLidsSize();
usage.incAllocatedBytes(bvSize);
usage.incUsedBytes(bvSize);
@@ -241,20 +245,20 @@ DocumentMetaStore::onUpdateStat()
}
void
-DocumentMetaStore::onGenerationChange(generation_t generation)
+DocumentMetaStore::before_inc_generation(generation_t current_gen)
{
_gidToLidMap.getAllocator().freeze();
- _gidToLidMap.getAllocator().transferHoldLists(generation - 1);
- getGenerationHolder().transferHoldLists(generation - 1);
+ _gidToLidMap.getAllocator().assign_generation(current_gen);
+ getGenerationHolder().assign_generation(current_gen);
updateStat(false);
}
void
-DocumentMetaStore::removeOldGenerations(generation_t firstUsed)
+DocumentMetaStore::reclaim_memory(generation_t oldest_used_gen)
{
- _gidToLidMap.getAllocator().trimHoldLists(firstUsed);
- _lidAlloc.trimHoldLists(firstUsed);
- getGenerationHolder().trimHoldLists(firstUsed);
+ _gidToLidMap.getAllocator().reclaim_memory(oldest_used_gen);
+ _lidAlloc.reclaim_memory(oldest_used_gen);
+ getGenerationHolder().reclaim(oldest_used_gen);
}
std::unique_ptr<search::AttributeSaver>
@@ -318,7 +322,7 @@ DocumentMetaStore::onLoad(vespalib::Executor *)
_gidToLidMap.assign(treeBuilder);
_gidToLidMap.getAllocator().freeze(); // create initial frozen tree
generation_t generation = getGenerationHandler().getCurrentGeneration();
- _gidToLidMap.getAllocator().transferHoldLists(generation);
+ _gidToLidMap.getAllocator().assign_generation(generation);
setNumDocs(_metaDataStore.size());
setCommittedDocIdLimit(_metaDataStore.size());
@@ -433,7 +437,7 @@ DocumentMetaStore::DocumentMetaStore(BucketDBOwnerSP bucketDB,
setCommittedDocIdLimit(1u); // lid 0 is reserved
_gidToLidMap.getAllocator().freeze(); // create initial frozen tree
generation_t generation = getGenerationHandler().getCurrentGeneration();
- _gidToLidMap.getAllocator().transferHoldLists(generation);
+ _gidToLidMap.getAllocator().assign_generation(generation);
updateStat(true);
}
@@ -442,7 +446,7 @@ DocumentMetaStore::~DocumentMetaStore()
// TODO: Properly notify about modified buckets when using shared bucket db
// between document types
unload();
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
assert(get_shrink_lid_space_blockers() == 0);
}
@@ -748,12 +752,12 @@ DocumentMetaStore::getMetaData(const GlobalId &gid) const
{
DocId lid = 0;
if (!getLid(gid, lid) || !validLid(lid)) {
- return search::DocumentMetaData();
+ return {};
}
const RawDocumentMetaData &raw = getRawMetaData(lid);
Timestamp timestamp(raw.getTimestamp());
std::atomic_thread_fence(std::memory_order_acquire);
- return search::DocumentMetaData(lid, timestamp, raw.getBucketId(), raw.getGid(), _subDbType == SubDbType::REMOVED);
+ return {lid, timestamp, raw.getBucketId(), raw.getGid(), _subDbType == SubDbType::REMOVED};
}
void
@@ -779,14 +783,7 @@ DocumentMetaStore::getMetaData(const BucketId &bucketId,
LidUsageStats
DocumentMetaStore::getLidUsageStats() const
{
- uint32_t docIdLimit = getCommittedDocIdLimit();
- uint32_t numDocs = getNumUsedLids();
- uint32_t lowestFreeLid = _lidAlloc.getLowestFreeLid();
- uint32_t highestUsedLid = _lidAlloc.getHighestUsedLid();
- return LidUsageStats(docIdLimit,
- numDocs,
- lowestFreeLid,
- highestUsedLid);
+ return {getCommittedDocIdLimit(), getNumUsedLids(), _lidAlloc.getLowestFreeLid(), _lidAlloc.getHighestUsedLid()};
}
Blueprint::UP
@@ -1009,7 +1006,7 @@ DocumentMetaStore::holdUnblockShrinkLidSpace()
{
assert(get_shrink_lid_space_blockers() > 0);
auto hold = std::make_unique<ShrinkBlockHeld>(*this);
- getGenerationHolder().hold(std::move(hold));
+ getGenerationHolder().insert(std::move(hold));
incGeneration();
}
@@ -1064,7 +1061,7 @@ DocumentMetaStore::getBucketOf(const vespalib::GenerationHandler::Guard &, uint3
if (__builtin_expect(validLidFast(lid, getCommittedDocIdLimit()), true)) {
return getRawMetaData(lid).getBucketId();
}
- return BucketId();
+ return {};
}
vespalib::GenerationHandler::Guard
@@ -1094,6 +1091,26 @@ DocumentMetaStore::foreach(const search::IGidToLidMapperVisitor &visitor) const
{ visitor.visit(getRawMetaData(key.get_lid()).getGid(), key.get_lid()); });
}
+long
+DocumentMetaStore::onSerializeForAscendingSort(DocId lid, void * serTo, long available, const search::common::BlobConverter *) const {
+ if ( ! validLid(lid)) return 0;
+ if (available < document::GlobalId::LENGTH) return -1;
+ memcpy(serTo, getRawMetaData(lid).getGid().get(), document::GlobalId::LENGTH);
+ return document::GlobalId::LENGTH;
+}
+
+long
+DocumentMetaStore::onSerializeForDescendingSort(DocId lid, void * serTo, long available, const search::common::BlobConverter *) const {
+ if ( ! validLid(lid)) return 0;
+ if (available < document::GlobalId::LENGTH) return -1;
+ const auto * src(static_cast<const uint8_t *>(getRawMetaData(lid).getGid().get()));
+ auto * dst = static_cast<uint8_t *>(serTo);
+ for (size_t i(0); i < document::GlobalId::LENGTH; ++i) {
+ dst[i] = 0xff - src[i];
+ }
+ return document::GlobalId::LENGTH;
+}
+
} // namespace proton
namespace vespalib::btree {
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h
index c4010a07709..ba944e3493d 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h
@@ -51,7 +51,7 @@ public:
// the ones with the same signature in proton::IDocumentMetaStore.
using DocumentMetaStoreAttribute::commit;
using DocumentMetaStoreAttribute::getCommittedDocIdLimit;
- using DocumentMetaStoreAttribute::removeAllOldGenerations;
+ using DocumentMetaStoreAttribute::reclaim_unused_memory;
using DocumentMetaStoreAttribute::getCurrentGeneration;
private:
@@ -93,8 +93,8 @@ private:
void onUpdateStat() override;
// Implements AttributeVector
- void onGenerationChange(generation_t generation) override;
- void removeOldGenerations(generation_t firstUsed) override;
+ void before_inc_generation(generation_t current_gen) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
std::unique_ptr<search::AttributeSaver> onInitSave(vespalib::stringref fileName) override;
bool onLoad(vespalib::Executor *executor) override;
@@ -122,7 +122,7 @@ private:
return getCommittedDocIdLimit();
}
void doRemoveAllOldGenerations() override {
- removeAllOldGenerations();
+ reclaim_unused_memory();
}
uint64_t doGetCurrentGeneration() const override {
return getCurrentGeneration();
@@ -272,6 +272,8 @@ public:
uint32_t getVersion() const override;
void setTrackDocumentSizes(bool trackDocumentSizes) { _trackDocumentSizes = trackDocumentSizes; }
void foreach(const search::IGidToLidMapperVisitor &visitor) const override;
+ long onSerializeForAscendingSort(DocId, void *, long, const search::common::BlobConverter *) const override;
+ long onSerializeForDescendingSort(DocId, void *, long, const search::common::BlobConverter *) const override;
};
}
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.cpp
index 57c3159645b..5deeaf924ae 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.cpp
@@ -7,14 +7,14 @@ namespace proton {
namespace {
-const vespalib::string _G_documentMetaStoreName("[documentmetastore]");
+const vespalib::string documentMetaStoreName("[documentmetastore]");
}
const vespalib::string &
DocumentMetaStoreAttribute::getFixedName()
{
- return _G_documentMetaStoreName;
+ return documentMetaStoreName;
}
DocumentMetaStoreAttribute::DocumentMetaStoreAttribute(const vespalib::string &name)
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.h
index 1a5f9c0077e..36408a01c17 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.h
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.h
@@ -14,12 +14,11 @@ namespace proton {
class DocumentMetaStoreAttribute : public search::NotImplementedAttribute
{
public:
- DocumentMetaStoreAttribute(const vespalib::string &name=getFixedName());
+ explicit DocumentMetaStoreAttribute(const vespalib::string &name);
~DocumentMetaStoreAttribute() override;
static const vespalib::string &getFixedName();
- // Implements IAttributeVector
size_t getFixedWidth() const override {
return document::GlobalId::LENGTH;
}
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreflushtarget.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreflushtarget.cpp
index 7786ead49b2..609ee585a6c 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreflushtarget.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastoreflushtarget.cpp
@@ -203,7 +203,7 @@ IFlushTarget::Task::UP
DocumentMetaStoreFlushTarget::initFlush(SerialNum currentSerial, std::shared_ptr<search::IFlushToken>)
{
// Called by document db executor
- _dms->removeAllOldGenerations();
+ _dms->reclaim_unused_memory();
SerialNum syncToken = std::max(currentSerial, _dms->getStatus().getLastSyncToken());
auto writer = _dmsDir->tryGetWriter();
if (!writer) {
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h
index a03b6325797..942d3e21da5 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h
@@ -68,7 +68,7 @@ struct IDocumentMetaStore : public search::IDocumentMetaStore,
// Functions that are also defined search::AttributeVector
virtual void commit(const CommitParam & param) = 0;
- virtual void removeAllOldGenerations() = 0;
+ virtual void reclaim_unused_memory() = 0;
virtual bool canShrinkLidSpace() const = 0;
virtual SerialNum getLastSerialNum() const = 0;
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h
index 6118701b0dc..95a8cf85279 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.h
@@ -41,8 +41,8 @@ public:
void unregisterLid(DocId lid);
void unregister_lids(const std::vector<DocId>& lids);
size_t getUsedLidsSize() const { return _usedLids.byteSize(); }
- void trimHoldLists(generation_t firstUsed) {
- _holdLids.trimHoldLists(firstUsed, _freeLids);
+ void reclaim_memory(generation_t oldest_used_gen) {
+ _holdLids.reclaim_memory(oldest_used_gen, _freeLids);
}
void moveLidBegin(DocId fromLid, DocId toLid);
void moveLidEnd(DocId fromLid, DocId toLid);
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.cpp
index 7157a40c5d5..ef0a244fc37 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.cpp
@@ -23,9 +23,9 @@ LidHoldList::clear() {
}
void
-LidHoldList::trimHoldLists(generation_t firstUsed, LidStateVector &freeLids)
+LidHoldList::reclaim_memory(generation_t oldest_used_gen, LidStateVector &freeLids)
{
- while (!_holdList.empty() && _holdList.front().second < firstUsed) {
+ while (!_holdList.empty() && _holdList.front().second < oldest_used_gen) {
uint32_t lid = _holdList.front().first;
freeLids.setBit(lid);
_holdList.pop_front();
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.h
index fc32fcb7510..565d8bf25e1 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.h
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_hold_list.h
@@ -43,9 +43,9 @@ public:
void clear();
/**
- * Frees up elements with generation < first used generation for reuse.
+ * Frees up elements with generation < oldest used generation for reuse.
**/
- void trimHoldLists(generation_t firstUsed, LidStateVector &freeLids);
+ void reclaim_memory(generation_t oldest_used_gen, LidStateVector &freeLids);
};
diff --git a/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp b/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp
index f332ca5ec26..9b85ddbdde8 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp
@@ -3,6 +3,7 @@
#include "result_processor.h"
#include "partial_result.h"
#include "sessionmanager.h"
+#include <vespa/searchcore/proton/documentmetastore/documentmetastoreattribute.h>
#include <vespa/searchcore/grouping/groupingmanager.h>
#include <vespa/searchcore/grouping/groupingcontext.h>
#include <vespa/searchlib/uca/ucaconverter.h>
@@ -28,7 +29,7 @@ ResultProcessor::Result::~Result() = default;
ResultProcessor::Sort::Sort(uint32_t partitionId, const vespalib::Doom & doom, IAttributeContext &ac, const vespalib::string &ss)
: sorter(FastS_DefaultResultSorter::instance()),
_ucaFactory(std::make_unique<search::uca::UcaConverterFactory>()),
- sortSpec(partitionId, doom, *_ucaFactory)
+ sortSpec(DocumentMetaStoreAttribute::getFixedName(), partitionId, doom, *_ucaFactory)
{
if (!ss.empty() && sortSpec.Init(ss.c_str(), ac)) {
sorter = &sortSpec;
@@ -46,9 +47,9 @@ ResultProcessor::Context::~Context() = default;
void
ResultProcessor::GroupingSource::merge(Source &s) {
- GroupingSource &rhs = static_cast<GroupingSource&>(s);
- assert((ctx == 0) == (rhs.ctx == 0));
- if (ctx != 0) {
+ auto &rhs = dynamic_cast<GroupingSource&>(s);
+ assert((ctx == nullptr) == (rhs.ctx == nullptr));
+ if (ctx != nullptr) {
search::grouping::GroupingManager man(*ctx);
man.merge(*rhs.ctx);
}
@@ -112,7 +113,7 @@ ResultProcessor::extract_docid_ordering(const PartialResult &result) const
}
std::sort(list.begin(), list.end(), [](const auto &a, const auto &b){ return (a.first < b.first); });
return list;
-};
+}
ResultProcessor::Result::UP
ResultProcessor::makeReply(PartialResultUP full_result)
diff --git a/searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp b/searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp
index 3ec1e23693e..ebe20f24d92 100644
--- a/searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/fast_access_doc_subdb.cpp
@@ -79,7 +79,7 @@ FastAccessDocSubDB::createAttributeManagerInitializer(const DocumentDBConfig &co
return std::make_shared<AttributeManagerInitializer>(configSerialNum,
documentMetaStoreInitTask,
documentMetaStore,
- baseAttrMgr,
+ *baseAttrMgr,
(_hasAttributes ? configSnapshot.getAttributesConfig() : AttributesConfig()),
alloc_strategy,
_fastAccessAttributesOnly,
diff --git a/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp b/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp
index a9850b5c2b7..533b270c20a 100644
--- a/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/storeonlyfeedview.cpp
@@ -763,7 +763,7 @@ void
StoreOnlyFeedView::heartBeat(SerialNum serialNum, DoneCallback onDone)
{
assert(_writeService.master().isCurrentThread());
- _metaStore.removeAllOldGenerations();
+ _metaStore.reclaim_unused_memory();
_metaStore.commit(CommitParam(serialNum));
heartBeatSummary(serialNum, onDone);
heartBeatIndexedFields(serialNum, onDone);
diff --git a/searchcore/src/vespa/searchcore/proton/test/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/test/CMakeLists.txt
index 25570c4ad0c..ad86ce64d8e 100644
--- a/searchcore/src/vespa/searchcore/proton/test/CMakeLists.txt
+++ b/searchcore/src/vespa/searchcore/proton/test/CMakeLists.txt
@@ -20,4 +20,5 @@ vespa_add_library(searchcore_test STATIC
searchcore_server
searchcore_fconfig
searchcorespi
+ searchlib_test
)
diff --git a/searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h b/searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h
index 60682ae90e5..4092f548016 100644
--- a/searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h
+++ b/searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h
@@ -162,8 +162,8 @@ struct DocumentMetaStoreObserver : public IDocumentMetaStore
DocId getCommittedDocIdLimit() const override {
return _store.getCommittedDocIdLimit();
}
- void removeAllOldGenerations() override {
- _store.removeAllOldGenerations();
+ void reclaim_unused_memory() override {
+ _store.reclaim_unused_memory();
}
bool canShrinkLidSpace() const override {
return _store.canShrinkLidSpace();
diff --git a/searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.cpp b/searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.cpp
index 2cdf1c45485..f9f98705144 100644
--- a/searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.cpp
+++ b/searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.cpp
@@ -5,8 +5,7 @@
namespace proton::test {
UserDocumentsBuilder::UserDocumentsBuilder()
- : _schema(),
- _builder(_schema),
+ : _builder(),
_docs()
{
}
@@ -17,7 +16,7 @@ UserDocumentsBuilder &
UserDocumentsBuilder::createDoc(uint32_t userId, search::DocumentIdT lid)
{
vespalib::string docId = vespalib::make_string("id:test:searchdocument:n=%u:%u", userId, lid);
- document::Document::SP doc(_builder.startDocument(docId).endDocument().release());
+ document::Document::SP doc(_builder.make_document(docId));
_docs.addDoc(userId, Document(doc, lid, storage::spi::Timestamp(lid)));
return *this;
}
diff --git a/searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.h b/searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.h
index f05b6da11de..1c7a82ef5ba 100644
--- a/searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.h
+++ b/searchcore/src/vespa/searchcore/proton/test/userdocumentsbuilder.h
@@ -2,7 +2,7 @@
#pragma once
#include "userdocuments.h"
-#include <vespa/searchlib/index/docbuilder.h>
+#include <vespa/searchlib/test/doc_builder.h>
#include <vespa/vespalib/util/stringfmt.h>
namespace proton::test {
@@ -13,14 +13,13 @@ namespace proton::test {
class UserDocumentsBuilder
{
private:
- search::index::Schema _schema;
- search::index::DocBuilder _builder;
+ search::test::DocBuilder _builder;
UserDocuments _docs;
public:
UserDocumentsBuilder();
~UserDocumentsBuilder();
- const std::shared_ptr<const document::DocumentTypeRepo> &getRepo() const {
- return _builder.getDocumentTypeRepo();
+ std::shared_ptr<const document::DocumentTypeRepo> getRepo() const {
+ return _builder.get_repo_sp();
}
UserDocumentsBuilder &createDoc(uint32_t userId, search::DocumentIdT lid);
UserDocumentsBuilder &createDocs(uint32_t userId, search::DocumentIdT begin,
diff --git a/searchlib/CMakeLists.txt b/searchlib/CMakeLists.txt
index a7d831aa623..e8f43a6037d 100644
--- a/searchlib/CMakeLists.txt
+++ b/searchlib/CMakeLists.txt
@@ -168,8 +168,6 @@ vespa_define_module(
src/tests/grouping
src/tests/groupingengine
src/tests/hitcollector
- src/tests/index/docbuilder
- src/tests/index/doctypebuilder
src/tests/index/field_length_calculator
src/tests/indexmetainfo
src/tests/ld-library-path
@@ -225,6 +223,8 @@ vespa_define_module(
src/tests/tensor/hnsw_saver
src/tests/tensor/tensor_buffer_operations
src/tests/tensor/tensor_buffer_store
+ src/tests/tensor/tensor_buffer_type_mapper
+ src/tests/test/string_field_builder
src/tests/transactionlog
src/tests/transactionlogstress
src/tests/true
diff --git a/searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp b/searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp
index 7f0a88c9f86..3fa74b78d2a 100644
--- a/searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp
+++ b/searchlib/src/tests/attribute/attributemanager/attributemanager_test.cpp
@@ -38,8 +38,7 @@ public:
generation_t getGen() const { return getCurrentGeneration(); }
uint32_t getRefCount(generation_t gen) const { return getGenerationRefCount(gen); }
void incGen() { incGeneration(); }
- void updateFirstUsedGen() { updateFirstUsedGeneration(); }
- generation_t getFirstUsedGen() const { return getFirstUsedGeneration(); }
+ generation_t oldest_used_gen() const { return get_oldest_used_generation(); }
};
@@ -49,35 +48,35 @@ TEST("Test attribute guards")
TestAttribute * v = static_cast<TestAttribute *> (vec.get());
EXPECT_EQUAL(v->getGen(), unsigned(0));
EXPECT_EQUAL(v->getRefCount(0), unsigned(0));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(0));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(0));
{
AttributeGuard g0(vec);
EXPECT_EQUAL(v->getGen(), unsigned(0));
EXPECT_EQUAL(v->getRefCount(0), unsigned(1));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(0));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(0));
{
AttributeGuard g1(vec);
EXPECT_EQUAL(v->getGen(), unsigned(0));
EXPECT_EQUAL(v->getRefCount(0), unsigned(2));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(0));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(0));
}
EXPECT_EQUAL(v->getRefCount(0), unsigned(1));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(0));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(0));
}
EXPECT_EQUAL(v->getRefCount(0), unsigned(0));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(0));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(0));
v->incGen();
EXPECT_EQUAL(v->getGen(), unsigned(1));
EXPECT_EQUAL(v->getRefCount(0), unsigned(0));
EXPECT_EQUAL(v->getRefCount(1), unsigned(0));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(1));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(1));
{
AttributeGuard g0(vec);
EXPECT_EQUAL(v->getGen(), unsigned(1));
EXPECT_EQUAL(v->getRefCount(0), unsigned(0));
EXPECT_EQUAL(v->getRefCount(1), unsigned(1));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(1));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(1));
{
v->incGen();
AttributeGuard g1(vec);
@@ -85,19 +84,19 @@ TEST("Test attribute guards")
EXPECT_EQUAL(v->getRefCount(0), unsigned(0));
EXPECT_EQUAL(v->getRefCount(1), unsigned(1));
EXPECT_EQUAL(v->getRefCount(2), unsigned(1));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(1));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(1));
}
EXPECT_EQUAL(v->getRefCount(0), unsigned(0));
EXPECT_EQUAL(v->getRefCount(1), unsigned(1));
EXPECT_EQUAL(v->getRefCount(2), unsigned(0));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(1));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(1));
}
EXPECT_EQUAL(v->getRefCount(0), unsigned(0));
EXPECT_EQUAL(v->getRefCount(1), unsigned(0));
EXPECT_EQUAL(v->getRefCount(2), unsigned(0));
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(1));
- v->updateFirstUsedGeneration();
- EXPECT_EQUAL(v->getFirstUsedGen(), unsigned(2));
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(1));
+ v->update_oldest_used_generation();
+ EXPECT_EQUAL(v->oldest_used_gen(), unsigned(2));
EXPECT_EQUAL(v->getGen(), unsigned(2));
}
diff --git a/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp b/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp
index e27065f1c25..b9c70d76934 100644
--- a/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp
+++ b/searchlib/src/tests/attribute/document_weight_or_filter_search/document_weight_or_filter_search_test.cpp
@@ -135,7 +135,7 @@ DocumentWeightOrFilterSearchTest::~DocumentWeightOrFilterSearchTest()
_postings.clear(tree);
}
_postings.clearBuilder();
- _postings.clearHoldLists();
+ _postings.reclaim_all_memory();
inc_generation();
}
@@ -143,10 +143,9 @@ void
DocumentWeightOrFilterSearchTest::inc_generation()
{
_postings.freeze();
- _postings.transferHoldLists(_gens.getCurrentGeneration());
+ _postings.assign_generation(_gens.getCurrentGeneration());
_gens.incGeneration();
- _gens.updateFirstUsedGeneration();
- _postings.trimHoldLists(_gens.getFirstUsedGeneration());
+ _postings.reclaim_memory(_gens.get_oldest_used_generation());
}
TEST_F(DocumentWeightOrFilterSearchTest, daat_or)
diff --git a/searchlib/src/tests/attribute/enum_comparator/enum_comparator_test.cpp b/searchlib/src/tests/attribute/enum_comparator/enum_comparator_test.cpp
index 1d76473754f..9d717202551 100644
--- a/searchlib/src/tests/attribute/enum_comparator/enum_comparator_test.cpp
+++ b/searchlib/src/tests/attribute/enum_comparator/enum_comparator_test.cpp
@@ -147,9 +147,9 @@ TEST("requireThatComparatorWithTreeIsWorking")
EXPECT_EQUAL(101, exp);
t.clear(m);
m.freeze();
- m.transferHoldLists(g.getCurrentGeneration());
+ m.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- m.trimHoldLists(g.getFirstUsedGeneration());
+ m.reclaim_memory(g.get_oldest_used_generation());
}
TEST("requireThatFoldedLessIsWorking")
diff --git a/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp b/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
index 02ff01043b0..0542a253cc5 100644
--- a/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
+++ b/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
@@ -345,8 +345,8 @@ TEST(EnumStoreTest, test_hold_lists_and_generation)
// check readers again
checkReaders(ses, readers);
- ses.transfer_hold_lists(sesGen);
- ses.trim_hold_lists(sesGen + 1);
+ ses.assign_generation(sesGen);
+ ses.reclaim_memory(sesGen + 1);
}
void
@@ -357,8 +357,8 @@ dec_ref_count(NumericEnumStore& store, NumericEnumStore::Index idx)
updater.commit();
generation_t gen = 5;
- store.transfer_hold_lists(gen);
- store.trim_hold_lists(gen + 1);
+ store.assign_generation(gen);
+ store.reclaim_memory(gen + 1);
}
TEST(EnumStoreTest, address_space_usage_is_reported)
@@ -882,9 +882,9 @@ namespace {
void inc_generation(generation_t &gen, NumericEnumStore &store)
{
store.freeze_dictionary();
- store.transfer_hold_lists(gen);
+ store.assign_generation(gen);
++gen;
- store.trim_hold_lists(gen);
+ store.reclaim_memory(gen);
}
}
diff --git a/searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp b/searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp
index b9f3c23213e..0d2ce048111 100644
--- a/searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp
+++ b/searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp
@@ -73,14 +73,14 @@ TEST_F("makeReadGuard(false) acquires guards on both target and reference attrib
EXPECT_EQUAL(2u, f.target_attr->getCurrentGeneration());
EXPECT_EQUAL(2u, f.reference_attr->getCurrentGeneration());
// Should still be holding guard for first generation of writes for both attributes
- EXPECT_EQUAL(1u, f.target_attr->getFirstUsedGeneration());
- EXPECT_EQUAL(1u, f.reference_attr->getFirstUsedGeneration());
+ EXPECT_EQUAL(1u, f.target_attr->get_oldest_used_generation());
+ EXPECT_EQUAL(1u, f.reference_attr->get_oldest_used_generation());
}
// Force a generation handler update
add_n_docs_with_undefined_values(*f.reference_attr, 1);
add_n_docs_with_undefined_values(*f.target_attr, 1);
- EXPECT_EQUAL(3u, f.target_attr->getFirstUsedGeneration());
- EXPECT_EQUAL(3u, f.reference_attr->getFirstUsedGeneration());
+ EXPECT_EQUAL(3u, f.target_attr->get_oldest_used_generation());
+ EXPECT_EQUAL(3u, f.reference_attr->get_oldest_used_generation());
}
TEST_F("makeReadGuard(true) acquires enum guard on target and regular guard on reference attribute", Fixture) {
@@ -95,15 +95,15 @@ TEST_F("makeReadGuard(true) acquires enum guard on target and regular guard on r
EXPECT_EQUAL(5u, f.target_attr->getCurrentGeneration());
EXPECT_EQUAL(2u, f.reference_attr->getCurrentGeneration());
- EXPECT_EQUAL(3u, f.target_attr->getFirstUsedGeneration());
- EXPECT_EQUAL(1u, f.reference_attr->getFirstUsedGeneration());
+ EXPECT_EQUAL(3u, f.target_attr->get_oldest_used_generation());
+ EXPECT_EQUAL(1u, f.reference_attr->get_oldest_used_generation());
EXPECT_TRUE(has_active_enum_guards(*f.target_attr));
}
// Force a generation handler update
add_n_docs_with_undefined_values(*f.reference_attr, 1);
add_n_docs_with_undefined_values(*f.target_attr, 1);
- EXPECT_EQUAL(7u, f.target_attr->getFirstUsedGeneration());
- EXPECT_EQUAL(3u, f.reference_attr->getFirstUsedGeneration());
+ EXPECT_EQUAL(7u, f.target_attr->get_oldest_used_generation());
+ EXPECT_EQUAL(3u, f.reference_attr->get_oldest_used_generation());
EXPECT_FALSE(has_active_enum_guards(*f.target_attr));
}
diff --git a/searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp b/searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp
index 735ebcff6cf..8b8f4d2c4d4 100644
--- a/searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp
+++ b/searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp
@@ -41,11 +41,11 @@ class MyAttribute : public search::NotImplementedAttribute
_mvMapping.shrink(committedDocIdLimit);
setNumDocs(committedDocIdLimit);
}
- virtual void removeOldGenerations(generation_t firstUsed) override {
- _mvMapping.trimHoldLists(firstUsed);
+ virtual void reclaim_memory(generation_t oldest_used_gen) override {
+ _mvMapping.reclaim_memory(oldest_used_gen);
}
- virtual void onGenerationChange(generation_t generation) override {
- _mvMapping.transferHoldLists(generation - 1);
+ virtual void before_inc_generation(generation_t current_gen) override {
+ _mvMapping.assign_generation(current_gen);
}
public:
@@ -115,8 +115,8 @@ public:
ConstArrayRef act = get(docId);
EXPECT_EQ(exp, std::vector<EntryT>(act.cbegin(), act.cend()));
}
- void transferHoldLists(generation_t generation) { _mvMapping->transferHoldLists(generation); }
- void trimHoldLists(generation_t firstUsed) { _mvMapping->trimHoldLists(firstUsed); }
+ void assign_generation(generation_t current_gen) { _mvMapping->assign_generation(current_gen); }
+ void reclaim_memory(generation_t oldest_used_gen) { _mvMapping->reclaim_memory(oldest_used_gen); }
void addDocs(uint32_t numDocs) {
for (uint32_t i = 0; i < numDocs; ++i) {
uint32_t doc = 0;
@@ -245,12 +245,12 @@ TEST_F(IntMappingTest, test_that_old_value_is_not_overwritten_while_held)
auto old3 = get(3);
assertArray({5}, old3);
set(3, {7});
- transferHoldLists(10);
+ assign_generation(10);
assertArray({5}, old3);
assertGet(3, {7});
- trimHoldLists(10);
+ reclaim_memory(10);
assertArray({5}, old3);
- trimHoldLists(11);
+ reclaim_memory(11);
assertArray({0}, old3);
}
diff --git a/searchlib/src/tests/attribute/posting_store/posting_store_test.cpp b/searchlib/src/tests/attribute/posting_store/posting_store_test.cpp
index 36babec6a89..75e7faf0227 100644
--- a/searchlib/src/tests/attribute/posting_store/posting_store_test.cpp
+++ b/searchlib/src/tests/attribute/posting_store/posting_store_test.cpp
@@ -64,11 +64,11 @@ protected:
{
_value_store.freeze_dictionary();
_store.freeze();
- _value_store.transfer_hold_lists(_gen_handler.getCurrentGeneration());
- _store.transferHoldLists(_gen_handler.getCurrentGeneration());
+ _value_store.assign_generation(_gen_handler.getCurrentGeneration());
+ _store.assign_generation(_gen_handler.getCurrentGeneration());
_gen_handler.incGeneration();
- _value_store.trim_hold_lists(_gen_handler.getFirstUsedGeneration());
- _store.trimHoldLists(_gen_handler.getFirstUsedGeneration());
+ _value_store.reclaim_memory(_gen_handler.get_oldest_used_generation());
+ _store.reclaim_memory(_gen_handler.get_oldest_used_generation());
}
EntryRef add_sequence(int start_key, int end_key)
diff --git a/searchlib/src/tests/attribute/postinglist/postinglist.cpp b/searchlib/src/tests/attribute/postinglist/postinglist.cpp
index 54efb3261c8..1eed3a015e1 100644
--- a/searchlib/src/tests/attribute/postinglist/postinglist.cpp
+++ b/searchlib/src/tests/attribute/postinglist/postinglist.cpp
@@ -201,7 +201,7 @@ private:
PostingListNodeAllocator &postingsAlloc);
void
- removeOldGenerations(Tree &tree,
+ reclaim_memory(Tree &tree,
ValueHandle &valueHandle,
PostingList &postings,
PostingListNodeAllocator &postingsAlloc);
@@ -259,12 +259,12 @@ AttributePostingListTest::freeTree(bool verbose)
static_cast<uint64_t>(_intNodeAlloc->getMemoryUsage().allocatedBytesOnHold()));
_intNodeAlloc->freeze();
_intPostings->freeze();
- _intNodeAlloc->transferHoldLists(_handler.getCurrentGeneration());
+ _intNodeAlloc->assign_generation(_handler.getCurrentGeneration());
_intPostings->clearBuilder();
- _intPostings->transferHoldLists(_handler.getCurrentGeneration());
+ _intPostings->assign_generation(_handler.getCurrentGeneration());
_handler.incGeneration();
- _intNodeAlloc->trimHoldLists(_handler.getFirstUsedGeneration());
- _intPostings->trimHoldLists(_handler.getFirstUsedGeneration());
+ _intNodeAlloc->reclaim_memory(_handler.get_oldest_used_generation());
+ _intPostings->reclaim_memory(_handler.get_oldest_used_generation());
LOG(info,
"freeTree after unhold: %" PRIu64 " (%" PRIu64 " held)",
static_cast<uint64_t>(_intNodeAlloc->getMemoryUsage().allocatedBytes()),
@@ -613,9 +613,9 @@ AttributePostingListTest::doCompactEnumStore(Tree &tree,
valueHandle.holdBuffer(*it);
}
generation_t generation = _handler.getCurrentGeneration();
- valueHandle.transferHoldLists(generation);
+ valueHandle.assign_generation(generation);
_handler.incGeneration();
- valueHandle.trimHoldLists(_handler.getFirstUsedGeneration());
+ valueHandle.reclaim_memory(_handler.get_oldest_used_generation());
LOG(info,
"doCompactEnumStore done");
@@ -658,22 +658,22 @@ bumpGeneration(Tree &tree,
(void) tree;
(void) valueHandle;
postingsAlloc.freeze();
- postingsAlloc.transferHoldLists(_handler.getCurrentGeneration());
- postings.transferHoldLists(_handler.getCurrentGeneration());
+ postingsAlloc.assign_generation(_handler.getCurrentGeneration());
+ postings.assign_generation(_handler.getCurrentGeneration());
_handler.incGeneration();
}
void
AttributePostingListTest::
-removeOldGenerations(Tree &tree,
+reclaim_memory(Tree &tree,
ValueHandle &valueHandle,
PostingList &postings,
PostingListNodeAllocator &postingsAlloc)
{
(void) tree;
(void) valueHandle;
- postingsAlloc.trimHoldLists(_handler.getFirstUsedGeneration());
- postings.trimHoldLists(_handler.getFirstUsedGeneration());
+ postingsAlloc.reclaim_memory(_handler.get_oldest_used_generation());
+ postings.reclaim_memory(_handler.get_oldest_used_generation());
}
int
@@ -689,7 +689,7 @@ AttributePostingListTest::Main()
lookupRandomValues(*_intTree, *_intNodeAlloc, *_intKeyStore, *_intPostings,
_stlTree, _randomValues);
_intNodeAlloc->freeze();
- _intNodeAlloc->transferHoldLists(_handler.getCurrentGeneration());
+ _intNodeAlloc->assign_generation(_handler.getCurrentGeneration());
doCompactEnumStore(*_intTree, *_intNodeAlloc, *_intKeyStore);
removeRandomValues(*_intTree, *_intNodeAlloc, *_intKeyStore, *_intPostings,
_stlTree, _randomValues);
diff --git a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
index 3dda2eb6d95..9127c4b59fc 100644
--- a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
+++ b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
@@ -221,11 +221,11 @@ public:
auto vector = _vectors.get_vector(docid).typify<double>();
_removes.emplace_back(docid, DoubleVector(vector.begin(), vector.end()));
}
- void transfer_hold_lists(generation_t current_gen) override {
+ void assign_generation(generation_t current_gen) override {
_transfer_gen = current_gen;
}
- void trim_hold_lists(generation_t first_used_gen) override {
- _trim_gen = first_used_gen;
+ void reclaim_memory(generation_t oldest_used_gen) override {
+ _trim_gen = oldest_used_gen;
}
bool consider_compact(const CompactionStrategy&) override {
return false;
@@ -350,28 +350,11 @@ struct Fixture {
vespalib::ThreadStackExecutor _executor;
bool _denseTensors;
FixtureTraits _traits;
+ vespalib::string _mmap_allocator_base_dir;
- Fixture(const vespalib::string &typeSpec,
- FixtureTraits traits = FixtureTraits())
- : _dir_handler(test_dir),
- _cfg(BasicType::TENSOR, CollectionType::SINGLE),
- _name(attr_name),
- _typeSpec(typeSpec),
- _index_factory(),
- _tensorAttr(),
- _attr(),
- _executor(1, 0x10000),
- _denseTensors(false),
- _traits(traits)
- {
- if (traits.enable_hnsw_index) {
- _cfg.set_distance_metric(DistanceMetric::Euclidean);
- _cfg.set_hnsw_index_params(HnswIndexParams(4, 20, DistanceMetric::Euclidean));
- }
- setup();
- }
+ Fixture(const vespalib::string &typeSpec, FixtureTraits traits = FixtureTraits());
- ~Fixture() {}
+ ~Fixture();
void setup() {
_cfg.setTensorType(ValueType::from_spec(_typeSpec));
@@ -545,8 +528,35 @@ struct Fixture {
void testEmptyTensor();
void testOnHoldAccounting();
void test_populate_address_space_usage();
+ void test_mmap_file_allocator();
};
+Fixture::Fixture(const vespalib::string &typeSpec, FixtureTraits traits)
+ : _dir_handler(test_dir),
+ _cfg(BasicType::TENSOR, CollectionType::SINGLE),
+ _name(attr_name),
+ _typeSpec(typeSpec),
+ _index_factory(),
+ _tensorAttr(),
+ _attr(),
+ _executor(1, 0x10000),
+ _denseTensors(false),
+ _traits(traits),
+ _mmap_allocator_base_dir("mmap-file-allocator-factory-dir")
+{
+ if (traits.enable_hnsw_index) {
+ _cfg.set_distance_metric(DistanceMetric::Euclidean);
+ _cfg.set_hnsw_index_params(HnswIndexParams(4, 20, DistanceMetric::Euclidean));
+ }
+ vespalib::alloc::MmapFileAllocatorFactory::instance().setup(_mmap_allocator_base_dir);
+ setup();
+}
+
+Fixture::~Fixture()
+{
+ vespalib::alloc::MmapFileAllocatorFactory::instance().setup("");
+ std::filesystem::remove_all(std::filesystem::path(_mmap_allocator_base_dir));
+}
void
Fixture::set_example_tensors()
@@ -750,6 +760,23 @@ Fixture::test_populate_address_space_usage()
}
}
+void
+Fixture::test_mmap_file_allocator()
+{
+ std::filesystem::path allocator_dir(_mmap_allocator_base_dir + "/0.my_attr");
+ if (!_traits.use_mmap_file_allocator) {
+ EXPECT_FALSE(std::filesystem::is_directory(allocator_dir));
+ } else {
+ EXPECT_TRUE(std::filesystem::is_directory(allocator_dir));
+ int entry_cnt = 0;
+ for (auto& entry : std::filesystem::directory_iterator(allocator_dir)) {
+ EXPECT_LESS(0u, entry.file_size());
+ ++entry_cnt;
+ }
+ EXPECT_LESS(0, entry_cnt);
+ }
+}
+
template <class MakeFixture>
void testAll(MakeFixture &&f)
{
@@ -761,6 +788,7 @@ void testAll(MakeFixture &&f)
TEST_DO(f()->testEmptyTensor());
TEST_DO(f()->testOnHoldAccounting());
TEST_DO(f()->test_populate_address_space_usage());
+ TEST_DO(f()->test_mmap_file_allocator());
}
TEST("Test sparse tensors with generic tensor attribute")
@@ -768,6 +796,11 @@ TEST("Test sparse tensors with generic tensor attribute")
testAll([]() { return std::make_shared<Fixture>(sparseSpec); });
}
+TEST("Test sparse tensors with generic tensor attribute, paged")
+{
+ testAll([]() { return std::make_shared<Fixture>(sparseSpec, FixtureTraits().mmap_file_allocator()); });
+}
+
TEST("Test sparse tensors with direct tensor attribute")
{
testAll([]() { return std::make_shared<Fixture>(sparseSpec, FixtureTraits().direct()); });
@@ -778,11 +811,21 @@ TEST("Test dense tensors with generic tensor attribute")
testAll([]() { return std::make_shared<Fixture>(denseSpec); });
}
+TEST("Test dense tensors with generic tensor attribute, paged")
+{
+ testAll([]() { return std::make_shared<Fixture>(denseSpec, FixtureTraits().mmap_file_allocator()); });
+}
+
TEST("Test dense tensors with dense tensor attribute")
{
testAll([]() { return std::make_shared<Fixture>(denseSpec, FixtureTraits().dense()); });
}
+TEST("Test dense tensors with dense tensor attribute, paged")
+{
+ testAll([]() { return std::make_shared<Fixture>(denseSpec, FixtureTraits().dense().mmap_file_allocator()); });
+}
+
TEST_F("Hnsw index is NOT instantiated in dense tensor attribute by default",
Fixture(vec_2d_spec, FixtureTraits().dense()))
{
@@ -1176,17 +1219,4 @@ TEST_F("NN blueprint do NOT want global filter when NOT having index (implicit b
EXPECT_FALSE(bp->getState().want_global_filter());
}
-TEST("Dense tensor attribute with paged flag uses mmap file allocator")
-{
- vespalib::string basedir("mmap-file-allocator-factory-dir");
- vespalib::alloc::MmapFileAllocatorFactory::instance().setup(basedir);
- {
- Fixture f(vec_2d_spec, FixtureTraits().dense().mmap_file_allocator());
- vespalib::string allocator_dir(basedir + "/0.my_attr");
- EXPECT_TRUE(std::filesystem::is_directory(std::filesystem::path(allocator_dir)));
- }
- vespalib::alloc::MmapFileAllocatorFactory::instance().setup("");
- std::filesystem::remove_all(std::filesystem::path(basedir));
-}
-
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/common/bitvector/bitvector_test.cpp b/searchlib/src/tests/common/bitvector/bitvector_test.cpp
index 79af28d20be..7a26202682b 100644
--- a/searchlib/src/tests/common/bitvector/bitvector_test.cpp
+++ b/searchlib/src/tests/common/bitvector/bitvector_test.cpp
@@ -654,8 +654,8 @@ TEST("requireThatGrowWorks")
EXPECT_EQUAL(4095u, v.writer().capacity());
EXPECT_EQUAL(3u, v.writer().countTrueBits());
- g.transferHoldLists(1);
- g.trimHoldLists(2);
+ g.assign_generation(1);
+ g.reclaim(2);
}
TEST("require that growable bit vectors keeps memory allocator")
@@ -676,8 +676,8 @@ TEST("require that growable bit vectors keeps memory allocator")
EXPECT_EQUAL(AllocStats(4, 1), stats);
v.writer().resize(1); // DO NOT TRY THIS AT HOME
EXPECT_EQUAL(AllocStats(5, 2), stats);
- g.transferHoldLists(1);
- g.trimHoldLists(2);
+ g.assign_generation(1);
+ g.reclaim(2);
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/diskindex/fusion/CMakeLists.txt b/searchlib/src/tests/diskindex/fusion/CMakeLists.txt
index 367f71783f9..6b9cc1b495a 100644
--- a/searchlib/src/tests/diskindex/fusion/CMakeLists.txt
+++ b/searchlib/src/tests/diskindex/fusion/CMakeLists.txt
@@ -3,7 +3,7 @@ vespa_add_executable(searchlib_fusion_test_app TEST
SOURCES
fusion_test.cpp
DEPENDS
- searchlib
+ searchlib_test
GTest::GTest
AFTER
searchlib_vespa-index-inspect_app
diff --git a/searchlib/src/tests/diskindex/fusion/fusion_test.cpp b/searchlib/src/tests/diskindex/fusion/fusion_test.cpp
index 6e60d14b8ff..54d34c849a0 100644
--- a/searchlib/src/tests/diskindex/fusion/fusion_test.cpp
+++ b/searchlib/src/tests/diskindex/fusion/fusion_test.cpp
@@ -1,14 +1,20 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/searchlib/diskindex/fusion.h>
+#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/fieldvalue/weightedsetfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/searchlib/common/flush_token.h>
#include <vespa/searchlib/diskindex/diskindex.h>
-#include <vespa/searchlib/diskindex/fusion.h>
#include <vespa/searchlib/diskindex/indexbuilder.h>
#include <vespa/searchlib/diskindex/zcposoccrandread.h>
#include <vespa/searchlib/fef/fieldpositionsiterator.h>
#include <vespa/searchlib/fef/termfieldmatchdata.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/index/schemautil.h>
#include <vespa/searchlib/memoryindex/document_inverter.h>
#include <vespa/searchlib/memoryindex/document_inverter_context.h>
@@ -31,7 +37,10 @@ LOG_SETUP("fusion_test");
namespace search {
+using document::ArrayFieldValue;
using document::Document;
+using document::StringFieldValue;
+using document::WeightedSetFieldValue;
using fef::FieldPositionsIterator;
using fef::TermFieldMatchData;
using fef::TermFieldMatchDataArray;
@@ -43,6 +52,8 @@ using search::common::FileHeaderContext;
using search::index::schema::CollectionType;
using search::index::schema::DataType;
using search::index::test::MockFieldLengthInspector;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using vespalib::SequencedTaskExecutor;
using namespace index;
@@ -112,24 +123,18 @@ toString(FieldPositionsIterator posItr, bool hasElements = false, bool hasWeight
std::unique_ptr<Document>
make_doc10(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::10");
- b.startIndexField("f0").
- addStr("a").addStr("b").addStr("c").addStr("d").
- addStr("e").addStr("f").addStr("z").
- endField();
- b.startIndexField("f1").
- addStr("w").addStr("x").
- addStr("y").addStr("z").
- endField();
- b.startIndexField("f2").
- startElement(4).addStr("ax").addStr("ay").addStr("z").endElement().
- startElement(5).addStr("ax").endElement().
- endField();
- b.startIndexField("f3").
- startElement(4).addStr("wx").addStr("z").endElement().
- endField();
-
- return b.endDocument();
+ auto doc = b.make_document("id:ns:searchdocument::10");
+ StringFieldBuilder sfb(b);
+ doc->setValue("f0", sfb.tokenize("a b c d e f z").build());
+ doc->setValue("f1", sfb.tokenize("w x y z").build());
+ auto string_array = b.make_array("f2");
+ string_array.add(sfb.tokenize("ax ay z").build());
+ string_array.add(sfb.tokenize("ax").build());
+ doc->setValue("f2", string_array);
+ auto string_wset = b.make_wset("f3");
+ string_wset.add(sfb.tokenize("wx z").build(), 4);
+ doc->setValue("f3", string_wset);
+ return doc;
}
Schema::IndexField
@@ -151,6 +156,18 @@ make_schema(bool interleaved_features)
return schema;
}
+DocBuilder::AddFieldsType
+make_add_fields()
+{
+ return [](auto& header) { using namespace document::config_builder;
+ using DataType = document::DataType;
+ header.addField("f0", DataType::T_STRING)
+ .addField("f1", DataType::T_STRING)
+ .addField("f2", Array(DataType::T_STRING))
+ .addField("f3", Wset(DataType::T_STRING));
+ };
+}
+
void
assert_interleaved_features(DiskIndex &d, const vespalib::string &field, const vespalib::string &term, uint32_t doc_id, uint32_t exp_num_occs, uint32_t exp_field_length)
{
@@ -327,7 +344,8 @@ FusionTest::requireThatFusionIsWorking(const vespalib::string &prefix, bool dire
addField("f2").addField("f3").
addField("f4"));
FieldIndexCollection fic(schema, MockFieldLengthInspector());
- DocBuilder b(schema);
+ DocBuilder b(make_add_fields());
+ StringFieldBuilder sfb(b);
auto invertThreads = SequencedTaskExecutor::create(invert_executor, 2);
auto pushThreads = SequencedTaskExecutor::create(push_executor, 2);
DocumentInverterContext inv_context(schema, *invertThreads, *pushThreads, fic);
@@ -338,19 +356,21 @@ FusionTest::requireThatFusionIsWorking(const vespalib::string &prefix, bool dire
inv.invertDocument(10, *doc, {});
myPushDocument(inv);
- b.startDocument("id:ns:searchdocument::11").
- startIndexField("f3").
- startElement(-27).addStr("zz").endElement().
- endField();
- doc = b.endDocument();
+ doc = b.make_document("id:ns:searchdocument::11");
+ {
+ auto string_wset = b.make_wset("f3");
+ string_wset.add(sfb.word("zz").build(), -27);
+ doc->setValue("f3", string_wset);
+ }
inv.invertDocument(11, *doc, {});
myPushDocument(inv);
- b.startDocument("id:ns:searchdocument::12").
- startIndexField("f3").
- startElement(0).addStr("zz0").endElement().
- endField();
- doc = b.endDocument();
+ doc = b.make_document("id:ns:searchdocument::12");
+ {
+ auto string_wset = b.make_wset("f3");
+ string_wset.add(sfb.word("zz0").build(), 0);
+ doc->setValue("f3", string_wset);
+ }
inv.invertDocument(12, *doc, {});
myPushDocument(inv);
@@ -468,7 +488,7 @@ FusionTest::make_simple_index(const vespalib::string &dump_dir, const IFieldLeng
FieldIndexCollection fic(_schema, field_length_inspector);
uint32_t numDocs = 20;
uint32_t numWords = 1000;
- DocBuilder b(_schema);
+ DocBuilder b(make_add_fields());
auto invertThreads = SequencedTaskExecutor::create(invert_executor, 2);
auto pushThreads = SequencedTaskExecutor::create(push_executor, 2);
DocumentInverterContext inv_context(_schema, *invertThreads, *pushThreads, fic);
diff --git a/searchlib/src/tests/index/docbuilder/.gitignore b/searchlib/src/tests/index/docbuilder/.gitignore
deleted file mode 100644
index 999644fce87..00000000000
--- a/searchlib/src/tests/index/docbuilder/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-*_test
-.depend
-Makefile
-docbuilder_test
-searchlib_docbuilder_test_app
diff --git a/searchlib/src/tests/index/docbuilder/CMakeLists.txt b/searchlib/src/tests/index/docbuilder/CMakeLists.txt
deleted file mode 100644
index 7a969f602ea..00000000000
--- a/searchlib/src/tests/index/docbuilder/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_executable(searchlib_docbuilder_test_app TEST
- SOURCES
- docbuilder_test.cpp
- DEPENDS
- searchlib
-)
-vespa_add_test(NAME searchlib_docbuilder_test_app COMMAND searchlib_docbuilder_test_app)
diff --git a/searchlib/src/tests/index/docbuilder/docbuilder_test.cpp b/searchlib/src/tests/index/docbuilder/docbuilder_test.cpp
deleted file mode 100644
index f76b61dcb78..00000000000
--- a/searchlib/src/tests/index/docbuilder/docbuilder_test.cpp
+++ /dev/null
@@ -1,437 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <vespa/log/log.h>
-LOG_SETUP("docbuilder_test");
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/split.hpp>
-#include <vespa/searchlib/index/docbuilder.h>
-#include <vespa/vespalib/encoding/base64.h>
-#include <vespa/vespalib/testkit/testapp.h>
-#include <vespa/document/repo/fixedtyperepo.h>
-#include <iostream>
-
-using namespace document;
-using search::index::schema::CollectionType;
-
-namespace search::index {
-
-namespace
-{
-std::string empty;
-}
-
-namespace linguistics
-{
-const vespalib::string SPANTREE_NAME("linguistics");
-}
-
-
-TEST("test docBuilder")
-{
- Schema s;
- s.addIndexField(Schema::IndexField("ia", schema::DataType::STRING));
- s.addIndexField(Schema::IndexField("ib", schema::DataType::STRING, CollectionType::ARRAY));
- s.addIndexField(Schema::IndexField("ic", schema::DataType::STRING, CollectionType::WEIGHTEDSET));
- s.addUriIndexFields(Schema::IndexField("iu", schema::DataType::STRING));
- s.addUriIndexFields(Schema::IndexField("iau", schema::DataType::STRING, CollectionType::ARRAY));
- s.addUriIndexFields(Schema::IndexField("iwu", schema::DataType::STRING, CollectionType::WEIGHTEDSET));
- s.addAttributeField(Schema::AttributeField("aa", schema::DataType::INT32));
- s.addAttributeField(Schema::AttributeField("ab", schema::DataType::FLOAT));
- s.addAttributeField(Schema::AttributeField("ac", schema::DataType::STRING));
- s.addAttributeField(Schema::AttributeField("ad", schema::DataType::INT32, CollectionType::ARRAY));
- s.addAttributeField(Schema::AttributeField("ae", schema::DataType::FLOAT, CollectionType::ARRAY));
- s.addAttributeField(Schema::AttributeField("af", schema::DataType::STRING, CollectionType::ARRAY));
- s.addAttributeField(Schema::AttributeField("ag", schema::DataType::INT32, CollectionType::WEIGHTEDSET));
- s.addAttributeField(Schema::AttributeField("ah", schema::DataType::FLOAT, CollectionType::WEIGHTEDSET));
- s.addAttributeField(Schema::AttributeField("ai", schema::DataType::STRING, CollectionType::WEIGHTEDSET));
- s.addAttributeField(Schema::AttributeField("asp1", schema::DataType::INT32));
- s.addAttributeField(Schema::AttributeField("asp2", schema::DataType::INT64));
- s.addAttributeField(Schema::AttributeField("aap1", schema::DataType::INT32, CollectionType::ARRAY));
- s.addAttributeField(Schema::AttributeField("aap2", schema::DataType::INT64, CollectionType::ARRAY));
- s.addAttributeField(Schema::AttributeField("awp1", schema::DataType::INT32, CollectionType::WEIGHTEDSET));
- s.addAttributeField(Schema::AttributeField("awp2", schema::DataType::INT64, CollectionType::WEIGHTEDSET));
-
- DocBuilder b(s);
- Document::UP doc;
- std::vector<std::string> lines;
- std::vector<std::string>::const_iterator itr;
- std::string xml;
-
- { // empty
- doc = b.startDocument("id:ns:searchdocument::0").endDocument();
- xml = doc->toXml("");
- boost::split(lines, xml, boost::is_any_of("\n"));
- itr = lines.begin();
- EXPECT_EQUAL("<document documenttype=\"searchdocument\" documentid=\"id:ns:searchdocument::0\"/>", *itr++);
- EXPECT_EQUAL("", *itr++);
- EXPECT_TRUE(itr == lines.end());
- }
- { // all fields set
- std::vector<char> binaryBlob;
- binaryBlob.push_back('\0');
- binaryBlob.push_back('\2');
- binaryBlob.push_back('\1');
- std::string raw1s("Single Raw Element");
- std::string raw1a0("Array Raw Element 0");
- std::string raw1a1("Array Raw Element 1");
- std::string raw1w0("Weighted Set Raw Element 0");
- std::string raw1w1("Weighted Set Raw Element 1");
- raw1s += std::string(&binaryBlob[0],
- &binaryBlob[0] + binaryBlob.size());
- raw1a0 += std::string(&binaryBlob[0],
- &binaryBlob[0] + binaryBlob.size());
- raw1a1 += std::string(&binaryBlob[0],
- &binaryBlob[0] + binaryBlob.size());
- raw1w0 += std::string(&binaryBlob[0],
- &binaryBlob[0] + binaryBlob.size());
- raw1w1 += std::string(&binaryBlob[0],
- &binaryBlob[0] + binaryBlob.size());
- b.startDocument("id:ns:searchdocument::1");
- b.startIndexField("ia").addStr("foo").addStr("bar").addStr("baz").addTermAnnotation("altbaz").endField();
- b.startIndexField("ib").startElement().addStr("foo").endElement().
- startElement(1).addStr("bar").addStr("baz").endElement().endField();
- b. startIndexField("ic").
- startElement(20).addStr("bar").addStr("baz").endElement().
- startElement().addStr("foo").endElement().
- endField();
- b.startIndexField("iu").
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:81/fluke?ab=2#4").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("81").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("4").
- endSubField().
- endField();
- b.startIndexField("iau").
- startElement(1).
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:82/fluke?ab=2#8").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("82").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("8").
- endSubField().
- endElement().
- startElement(1).
- startSubField("all").
- addUrlTokenizedString("http://www.flickr.com:82/fluke?ab=2#9").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.flickr.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("82").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("9").
- endSubField().
- endElement().
- endField();
- b.startIndexField("iwu").
- startElement(4).
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:83/fluke?ab=2#12").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("83").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("12").
- endSubField().
- endElement().
- startElement(7).
- startSubField("all").
- addUrlTokenizedString("http://www.flickr.com:85/fluke?ab=2#13").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.flickr.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("85").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("13").
- endSubField().
- endElement().
- endField();
- b.startAttributeField("aa").addInt(2147483647).endField();
- b.startAttributeField("ab").addFloat(1234.56).endField();
- b.startAttributeField("ac").addStr("foo baz").endField();
- b.startAttributeField("ad").startElement().addInt(10).endElement().endField();
- b.startAttributeField("ae").startElement().addFloat(10.5).endElement().endField();
- b.startAttributeField("af").startElement().addStr("foo").endElement().endField();
- b.startAttributeField("ag").startElement(2).addInt(20).endElement().endField();
- b.startAttributeField("ah").startElement(3).addFloat(20.5).endElement().endField();
- b.startAttributeField("ai").startElement(4).addStr("bar").endElement().endField();
- b.startAttributeField("asp1").addInt(1001).endField();
- b.startAttributeField("asp2").addPosition(1002, 1003).endField();
- b.startAttributeField("aap1").
- startElement().addInt(1004).endElement().
- startElement().addInt(1005).endElement().
- endField();
- b.startAttributeField("aap2").
- startElement().addPosition(1006, 1007).endElement().
- startElement().addPosition(1008, 1009).endElement().
- endField();
- b.startAttributeField("awp1").
- startElement(41).addInt(1010).endElement().
- startElement(42).addInt(1011).endElement().
- endField();
- b.startAttributeField("awp2").
- startElement(43).addPosition(1012, 1013).endElement().
- startElement(44).addPosition(1014, 1015).endElement().
- endField();
- doc = b.endDocument();
- xml = doc->toXml("");
- boost::split(lines, xml, boost::is_any_of("\n"));
- itr = lines.begin();
- EXPECT_EQUAL("<document documenttype=\"searchdocument\" documentid=\"id:ns:searchdocument::1\">", *itr++);
- EXPECT_EQUAL("<iu>", *itr++);
- EXPECT_EQUAL("<all>http://www.example.com:81/fluke?ab=2#4</all>", *itr++);
- EXPECT_EQUAL("<host>www.example.com</host>", *itr++);
- EXPECT_EQUAL("<scheme>http</scheme>", *itr++);
- EXPECT_EQUAL("<path>/fluke</path>", *itr++);
- EXPECT_EQUAL("<port>81</port>", *itr++);
- EXPECT_EQUAL("<query>ab=2</query>", *itr++);
- EXPECT_EQUAL("<fragment>4</fragment>", *itr++);
- EXPECT_EQUAL("</iu>", *itr++);
- EXPECT_EQUAL("<aa>2147483647</aa>", *itr++);
- EXPECT_EQUAL("<aap2>", *itr++);
- EXPECT_EQUAL("<item>1047806</item>", *itr++);
- EXPECT_EQUAL("<item>1048322</item>", *itr++);
- EXPECT_EQUAL("</aap2>", *itr++);
- EXPECT_EQUAL("<ia>foo bar baz</ia>", *itr++);
- EXPECT_EQUAL("<ae>", *itr++);
- EXPECT_EQUAL("<item>10.5</item>", *itr++);
- EXPECT_EQUAL("</ae>", *itr++);
- EXPECT_EQUAL("<ib>", *itr++);
- EXPECT_EQUAL("<item>foo</item>", *itr++);
- EXPECT_EQUAL("<item>bar baz</item>", *itr++);
- EXPECT_EQUAL("</ib>", *itr++);
- EXPECT_EQUAL("<ah>", *itr++);
- EXPECT_EQUAL("<item weight=\"3\">20.5</item>", *itr++);
- EXPECT_EQUAL("</ah>", *itr++);
- EXPECT_EQUAL("<ic>", *itr++);
- EXPECT_EQUAL("<item weight=\"20\">bar baz</item>", *itr++);
- EXPECT_EQUAL("<item weight=\"1\">foo</item>", *itr++);
- EXPECT_EQUAL("</ic>", *itr++);
- EXPECT_EQUAL("<ac>foo baz</ac>", *itr++);
- EXPECT_EQUAL("<awp2>", *itr++);
- EXPECT_EQUAL("<item weight=\"43\">1048370</item>", *itr++);
- EXPECT_EQUAL("<item weight=\"44\">1048382</item>", *itr++);
- EXPECT_EQUAL("</awp2>", *itr++);
- EXPECT_EQUAL("<iau>", *itr++);
- EXPECT_EQUAL("<item>", *itr++);
- EXPECT_EQUAL("<all>http://www.example.com:82/fluke?ab=2#8</all>", *itr++);
- EXPECT_EQUAL("<host>www.example.com</host>", *itr++);
- EXPECT_EQUAL("<scheme>http</scheme>", *itr++);
- EXPECT_EQUAL("<path>/fluke</path>", *itr++);
- EXPECT_EQUAL("<port>82</port>", *itr++);
- EXPECT_EQUAL("<query>ab=2</query>", *itr++);
- EXPECT_EQUAL("<fragment>8</fragment>", *itr++);
- EXPECT_EQUAL("</item>", *itr++);
- EXPECT_EQUAL("<item>", *itr++);
- EXPECT_EQUAL("<all>http://www.flickr.com:82/fluke?ab=2#9</all>", *itr++);
- EXPECT_EQUAL("<host>www.flickr.com</host>", *itr++);
- EXPECT_EQUAL("<scheme>http</scheme>", *itr++);
- EXPECT_EQUAL("<path>/fluke</path>", *itr++);
- EXPECT_EQUAL("<port>82</port>", *itr++);
- EXPECT_EQUAL("<query>ab=2</query>", *itr++);
- EXPECT_EQUAL("<fragment>9</fragment>", *itr++);
- EXPECT_EQUAL("</item>", *itr++);
- EXPECT_EQUAL("</iau>", *itr++);
- EXPECT_EQUAL("<asp2>1047758</asp2>", *itr++);
- EXPECT_EQUAL("<ai>", *itr++);
- EXPECT_EQUAL("<item weight=\"4\">bar</item>", *itr++);
- EXPECT_EQUAL("</ai>", *itr++);
- EXPECT_EQUAL("<asp1>1001</asp1>", *itr++);
- EXPECT_EQUAL("<ad>", *itr++);
- EXPECT_EQUAL("<item>10</item>", *itr++);
- EXPECT_EQUAL("</ad>", *itr++);
- EXPECT_EQUAL("<iwu>", *itr++);
- EXPECT_EQUAL("<item weight=\"4\">", *itr++);
- EXPECT_EQUAL("<all>http://www.example.com:83/fluke?ab=2#12</all>", *itr++);
- EXPECT_EQUAL("<host>www.example.com</host>", *itr++);
- EXPECT_EQUAL("<scheme>http</scheme>", *itr++);
- EXPECT_EQUAL("<path>/fluke</path>", *itr++);
- EXPECT_EQUAL("<port>83</port>", *itr++);
- EXPECT_EQUAL("<query>ab=2</query>", *itr++);
- EXPECT_EQUAL("<fragment>12</fragment>", *itr++);
- EXPECT_EQUAL("</item>", *itr++);
- EXPECT_EQUAL("<item weight=\"7\">", *itr++);
- EXPECT_EQUAL("<all>http://www.flickr.com:85/fluke?ab=2#13</all>", *itr++);
- EXPECT_EQUAL("<host>www.flickr.com</host>", *itr++);
- EXPECT_EQUAL("<scheme>http</scheme>", *itr++);
- EXPECT_EQUAL("<path>/fluke</path>", *itr++);
- EXPECT_EQUAL("<port>85</port>", *itr++);
- EXPECT_EQUAL("<query>ab=2</query>", *itr++);
- EXPECT_EQUAL("<fragment>13</fragment>", *itr++);
- EXPECT_EQUAL("</item>", *itr++);
- EXPECT_EQUAL("</iwu>", *itr++);
- EXPECT_EQUAL("<ab>1234.56</ab>", *itr++);
- EXPECT_EQUAL("<ag>", *itr++);
- EXPECT_EQUAL("<item weight=\"2\">20</item>", *itr++);
- EXPECT_EQUAL("</ag>", *itr++);
- EXPECT_EQUAL("<awp1>", *itr++);
- EXPECT_EQUAL("<item weight=\"41\">1010</item>", *itr++);
- EXPECT_EQUAL("<item weight=\"42\">1011</item>", *itr++);
- EXPECT_EQUAL("</awp1>", *itr++);
- EXPECT_EQUAL("<aap1>", *itr++);
- EXPECT_EQUAL("<item>1004</item>", *itr++);
- EXPECT_EQUAL("<item>1005</item>", *itr++);
- EXPECT_EQUAL("</aap1>", *itr++);
- EXPECT_EQUAL("<af>", *itr++);
- EXPECT_EQUAL("<item>foo</item>", *itr++);
- EXPECT_EQUAL("</af>", *itr++);
- EXPECT_EQUAL("</document>", *itr++);
- EXPECT_TRUE(itr == lines.end());
-#if 0
- std::cout << "onedoc xml start -----" << std::endl <<
- xml << std::endl <<
- "-------" << std::endl;
- std::cout << "onedoc toString start ----" << std::endl <<
- doc->toString(true) << std::endl <<
- "-------" << std::endl;
-#endif
- }
- { // create one more to see that everything is cleared
- b.startDocument("id:ns:searchdocument::2");
- b.startIndexField("ia").addStr("yes").endField();
- b.startAttributeField("aa").addInt(20).endField();
- doc = b.endDocument();
- xml = doc->toXml("");
- boost::split(lines, xml, boost::is_any_of("\n"));
- itr = lines.begin();
- EXPECT_EQUAL("<document documenttype=\"searchdocument\" documentid=\"id:ns:searchdocument::2\">", *itr++);
- EXPECT_EQUAL("<aa>20</aa>", *itr++);
- EXPECT_EQUAL("<ia>yes</ia>", *itr++);
- EXPECT_EQUAL("</document>", *itr++);
- EXPECT_TRUE(itr == lines.end());
- }
- { // create field with cjk chars
- b.startDocument("id:ns:searchdocument::3");
- b.startIndexField("ia").
- addStr("我就是那个").
- setAutoSpace(false).
- addStr("大灰狼").
- setAutoSpace(true).
- endField();
- doc = b.endDocument();
- xml = doc->toXml("");
- boost::split(lines, xml, boost::is_any_of("\n"));
- itr = lines.begin();
- EXPECT_EQUAL("<document documenttype=\"searchdocument\" documentid=\"id:ns:searchdocument::3\">", *itr++);
- EXPECT_EQUAL("<ia>我就是那个大灰狼</ia>", *itr++);
- EXPECT_EQUAL("</document>", *itr++);
- EXPECT_TRUE(itr == lines.end());
- const FieldValue::UP iaval = doc->getValue("ia");
- ASSERT_TRUE(iaval.get() != NULL);
- const StringFieldValue *iasval = dynamic_cast<const StringFieldValue *>
- (iaval.get());
- ASSERT_TRUE(iasval != NULL);
- StringFieldValue::SpanTrees trees = iasval->getSpanTrees();
- const SpanTree *tree = StringFieldValue::findTree(trees, linguistics::SPANTREE_NAME);
- ASSERT_TRUE(tree != NULL);
- std::vector<Span> spans;
- std::vector<Span> expSpans;
- for (SpanTree::const_iterator i = tree->begin(), ie = tree->end();
- i != ie; ++i) {
- Annotation &ann = const_cast<Annotation &>(*i);
- const Span *span = dynamic_cast<const Span *>(ann.getSpanNode());
- if (span == NULL)
- continue;
- spans.push_back(*span);
- }
- expSpans.push_back(Span(0, 15));
- expSpans.push_back(Span(0, 15));
- expSpans.push_back(Span(15, 9));
- expSpans.push_back(Span(15, 9));
- ASSERT_TRUE(expSpans == spans);
-#if 0
- std::cout << "onedoc xml start -----" << std::endl <<
- xml << std::endl <<
- "-------" << std::endl;
- std::cout << "onedoc toString start ----" << std::endl <<
- doc->toString(true) << std::endl <<
- "-------" << std::endl;
-#endif
- }
-}
-
-TEST("test if index names are valid uri parts") {
- EXPECT_FALSE(UriField::mightBePartofUri("all"));
- EXPECT_FALSE(UriField::mightBePartofUri("fragment"));
- EXPECT_FALSE(UriField::mightBePartofUri(".all"));
- EXPECT_FALSE(UriField::mightBePartofUri("all.b"));
- EXPECT_TRUE(UriField::mightBePartofUri("b.all"));
- EXPECT_TRUE(UriField::mightBePartofUri("b.scheme"));
- EXPECT_TRUE(UriField::mightBePartofUri("b.host"));
- EXPECT_TRUE(UriField::mightBePartofUri("b.port"));
- EXPECT_TRUE(UriField::mightBePartofUri("b.hostname"));
- EXPECT_TRUE(UriField::mightBePartofUri("b.path"));
- EXPECT_TRUE(UriField::mightBePartofUri("b.query"));
- EXPECT_TRUE(UriField::mightBePartofUri("b.fragment"));
-}
-
-}
-
-TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/index/doctypebuilder/.gitignore b/searchlib/src/tests/index/doctypebuilder/.gitignore
deleted file mode 100644
index f15be1efcfe..00000000000
--- a/searchlib/src/tests/index/doctypebuilder/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-*_test
-.depend
-Makefile
-doctypebuilder_test
-searchlib_doctypebuilder_test_app
diff --git a/searchlib/src/tests/index/doctypebuilder/CMakeLists.txt b/searchlib/src/tests/index/doctypebuilder/CMakeLists.txt
deleted file mode 100644
index 348ecde5a7c..00000000000
--- a/searchlib/src/tests/index/doctypebuilder/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_executable(searchlib_doctypebuilder_test_app TEST
- SOURCES
- doctypebuilder_test.cpp
- DEPENDS
- searchlib
-)
-vespa_add_test(NAME searchlib_doctypebuilder_test_app COMMAND searchlib_doctypebuilder_test_app)
diff --git a/searchlib/src/tests/index/doctypebuilder/doctypebuilder_test.cpp b/searchlib/src/tests/index/doctypebuilder/doctypebuilder_test.cpp
deleted file mode 100644
index 95854fa11b2..00000000000
--- a/searchlib/src/tests/index/doctypebuilder/doctypebuilder_test.cpp
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include <vespa/document/repo/documenttyperepo.h>
-#include <vespa/searchlib/index/doctypebuilder.h>
-#include <vespa/document/datatype/documenttype.h>
-#include <vespa/vespalib/testkit/testapp.h>
-
-using namespace document;
-
-namespace search {
-namespace index {
-
-using schema::CollectionType;
-using schema::DataType;
-
-TEST("testSearchDocType") {
- Schema s;
- s.addIndexField(Schema::IndexField("ia", DataType::STRING));
- s.addIndexField(Schema::IndexField("ib", DataType::STRING, CollectionType::ARRAY));
- s.addIndexField(Schema::IndexField("ic", DataType::STRING, CollectionType::WEIGHTEDSET));
- s.addUriIndexFields(Schema::IndexField("iu", DataType::STRING));
- s.addUriIndexFields(Schema::IndexField("iau", DataType::STRING, CollectionType::ARRAY));
- s.addUriIndexFields(Schema::IndexField("iwu", DataType::STRING, CollectionType::WEIGHTEDSET));
- s.addAttributeField(Schema::AttributeField("aa", DataType::INT32));
- s.addAttributeField(Schema::AttributeField("spos", DataType::INT64));
- s.addAttributeField(Schema::AttributeField("apos", DataType::INT64, CollectionType::ARRAY));
- s.addAttributeField(Schema::AttributeField("wpos", DataType::INT64, CollectionType::WEIGHTEDSET));
-
- DocTypeBuilder docTypeBuilder(s);
- document::config::DocumenttypesConfig config = docTypeBuilder.makeConfig();
- DocumentTypeRepo repo(config);
- const DocumentType *docType = repo.getDocumentType("searchdocument");
- ASSERT_TRUE(docType);
- EXPECT_EQUAL(10u, docType->getFieldCount());
-
- EXPECT_EQUAL("String", docType->getField("ia").getDataType().getName());
- EXPECT_EQUAL("Array<String>",
- docType->getField("ib").getDataType().getName());
- EXPECT_EQUAL("WeightedSet<String>",
- docType->getField("ic").getDataType().getName());
- EXPECT_EQUAL("url", docType->getField("iu").getDataType().getName());
- EXPECT_EQUAL("Array<url>",
- docType->getField("iau").getDataType().getName());
- EXPECT_EQUAL("WeightedSet<url>",
- docType->getField("iwu").getDataType().getName());
-
- EXPECT_EQUAL("Int", docType->getField("aa").getDataType().getName());
- EXPECT_EQUAL("Long", docType->getField("spos").getDataType().getName());
- EXPECT_EQUAL("Array<Long>",
- docType->getField("apos").getDataType().getName());
- EXPECT_EQUAL("WeightedSet<Long>",
- docType->getField("wpos").getDataType().getName());
-}
-
-TEST("require that multiple fields can have the same type") {
- Schema s;
- s.addIndexField(Schema::IndexField("array1", DataType::STRING, CollectionType::ARRAY));
- s.addIndexField(Schema::IndexField("array2", DataType::STRING, CollectionType::ARRAY));
- DocTypeBuilder docTypeBuilder(s);
- document::config::DocumenttypesConfig config = docTypeBuilder.makeConfig();
- DocumentTypeRepo repo(config);
- const DocumentType *docType = repo.getDocumentType("searchdocument");
- ASSERT_TRUE(docType);
- EXPECT_EQUAL(2u, docType->getFieldCount());
-
- EXPECT_EQUAL("Array<String>",
- docType->getField("array1").getDataType().getName());
- EXPECT_EQUAL("Array<String>",
- docType->getField("array2").getDataType().getName());
-}
-
-} // namespace index
-} // namespace search
-
-TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/memoryindex/document_inverter/document_inverter_test.cpp b/searchlib/src/tests/memoryindex/document_inverter/document_inverter_test.cpp
index 3f8a04d9460..85b8fc64304 100644
--- a/searchlib/src/tests/memoryindex/document_inverter/document_inverter_test.cpp
+++ b/searchlib/src/tests/memoryindex/document_inverter/document_inverter_test.cpp
@@ -1,8 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include <vespa/searchlib/index/docbuilder.h>
-#include <vespa/searchlib/index/field_length_calculator.h>
#include <vespa/searchlib/memoryindex/document_inverter.h>
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/searchlib/index/field_length_calculator.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/memoryindex/document_inverter_context.h>
#include <vespa/searchlib/memoryindex/field_index_remover.h>
#include <vespa/searchlib/memoryindex/field_inverter.h>
@@ -19,74 +24,79 @@
namespace search::memoryindex {
using document::Document;
-using index::DocBuilder;
using index::FieldLengthCalculator;
using index::Schema;
using index::schema::CollectionType;
using index::schema::DataType;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using vespalib::SequencedTaskExecutor;
using vespalib::ISequencedTaskExecutor;
namespace {
+DocBuilder::AddFieldsType
+make_add_fields()
+{
+ return [](auto& header) { using namespace document::config_builder;
+ using DataType = document::DataType;
+ header.addField("f0", DataType::T_STRING)
+ .addField("f1", DataType::T_STRING)
+ .addField("f2", Array(DataType::T_STRING))
+ .addField("f3", Wset(DataType::T_STRING));
+ };
+}
+
Document::UP
makeDoc10(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::10");
- b.startIndexField("f0").
- addStr("a").addStr("b").addStr("c").addStr("d").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::10");
+ doc->setValue("f0", sfb.tokenize("a b c d").build());
+ return doc;
}
Document::UP
makeDoc11(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::11");
- b.startIndexField("f0").
- addStr("a").addStr("b").addStr("e").addStr("f").
- endField();
- b.startIndexField("f1").
- addStr("a").addStr("g").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::11");
+ doc->setValue("f0", sfb.tokenize("a b e f").build());
+ doc->setValue("f1", sfb.tokenize("a g").build());
+ return doc;
}
Document::UP
makeDoc12(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::12");
- b.startIndexField("f0").
- addStr("h").addStr("doc12").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::12");
+ doc->setValue("f0", sfb.tokenize("h doc12").build());
+ return doc;
}
Document::UP
makeDoc13(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::13");
- b.startIndexField("f0").
- addStr("i").addStr("doc13").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::13");
+ doc->setValue("f0", sfb.tokenize("i doc13").build());
+ return doc;
}
Document::UP
makeDoc14(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::14");
- b.startIndexField("f0").
- addStr("j").addStr("doc14").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::14");
+ doc->setValue("f0", sfb.tokenize("j doc14").build());
+ return doc;
}
Document::UP
makeDoc15(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::15");
- return b.endDocument();
+ return b.make_document("id:ns:searchdocument::15");
}
}
@@ -118,7 +128,7 @@ struct DocumentInverterTest : public ::testing::Test {
DocumentInverterTest()
: _schema(makeSchema()),
- _b(_schema),
+ _b(make_add_fields()),
_invertThreads(SequencedTaskExecutor::create(invert_executor, 1)),
_pushThreads(SequencedTaskExecutor::create(push_executor, 1)),
_word_store(),
diff --git a/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp b/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp
index 23d96f7b81c..1e6cb61d3f4 100644
--- a/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp
+++ b/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp
@@ -1,11 +1,18 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/datatype/urldatatype.h>
+#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/fieldvalue/structfieldvalue.h>
+#include <vespa/document/fieldvalue/weightedsetfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/searchlib/diskindex/fusion.h>
#include <vespa/searchlib/diskindex/indexbuilder.h>
#include <vespa/searchlib/diskindex/zcposoccrandread.h>
#include <vespa/searchlib/fef/fieldpositionsiterator.h>
#include <vespa/searchlib/fef/termfieldmatchdata.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/searchlib/index/docidandfeatures.h>
#include <vespa/searchlib/index/dummyfileheadercontext.h>
#include <vespa/searchlib/memoryindex/document_inverter.h>
@@ -15,6 +22,8 @@
#include <vespa/searchlib/memoryindex/ordered_field_index_inserter.h>
#include <vespa/searchlib/memoryindex/posting_iterator.h>
#include <vespa/searchlib/queryeval/iterators.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/test/index/mock_field_length_inspector.h>
#include <vespa/searchlib/test/memoryindex/wrap_inserter.h>
#include <vespa/vespalib/btree/btreenodeallocator.hpp>
@@ -37,12 +46,18 @@ namespace search {
using namespace fef;
using namespace index;
+using document::ArrayFieldValue;
using document::Document;
+using document::StructFieldValue;
+using document::UrlDataType;
+using document::WeightedSetFieldValue;
using queryeval::RankedSearchIteratorBase;
using queryeval::SearchIterator;
using search::index::schema::CollectionType;
using search::index::schema::DataType;
using search::index::test::MockFieldLengthInspector;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using vespalib::GenerationHandler;
using vespalib::ISequencedTaskExecutor;
using vespalib::SequencedTaskExecutor;
@@ -451,14 +466,13 @@ featureStoreRef(const FieldIndexCollection &fieldIndexes, uint32_t fieldId)
return fieldIndexes.getFieldIndex(fieldId)->getFeatureStore();
}
-DataStoreBase::MemStats
+MemoryStats
getFeatureStoreMemStats(const FieldIndexCollection &fieldIndexes)
{
- DataStoreBase::MemStats res;
+ MemoryStats res;
uint32_t numFields = fieldIndexes.getNumFields();
for (uint32_t fieldId = 0; fieldId < numFields; ++fieldId) {
- DataStoreBase::MemStats stats =
- fieldIndexes.getFieldIndex(fieldId)->getFeatureStore().getMemStats();
+ auto stats = fieldIndexes.getFieldIndex(fieldId)->getFeatureStore().getMemStats();
res += stats;
}
return res;
@@ -506,6 +520,12 @@ make_single_field_schema()
return result;
}
+DocBuilder::AddFieldsType
+make_single_add_fields()
+{
+ return [](auto& header) { header.addField("f0", document::DataType::T_STRING); };
+}
+
template <typename FieldIndexType>
struct FieldIndexTest : public ::testing::Test {
Schema schema;
@@ -707,6 +727,18 @@ make_multi_field_schema()
return result;
}
+DocBuilder::AddFieldsType
+make_multi_field_add_fields()
+{
+ return [](auto& header) { using namespace document::config_builder;
+ using DataType = document::DataType;
+ header.addField("f0", DataType::T_STRING)
+ .addField("f1", DataType::T_STRING)
+ .addField("f2", Array(DataType::T_STRING))
+ .addField("f3", Wset(DataType::T_STRING));
+ };
+}
+
struct FieldIndexCollectionTest : public ::testing::Test {
Schema schema;
FieldIndexCollection fic;
@@ -914,10 +946,10 @@ public:
DocumentInverterContext _inv_context;
DocumentInverter _inv;
- InverterTest(const Schema& schema)
+ InverterTest(const Schema& schema, DocBuilder::AddFieldsType add_fields)
: _schema(schema),
_fic(_schema, MockFieldLengthInspector()),
- _b(_schema),
+ _b(add_fields),
_invertThreads(SequencedTaskExecutor::create(invert_executor, 2)),
_pushThreads(SequencedTaskExecutor::create(push_executor, 2)),
_inv_context(_schema, *_invertThreads, *_pushThreads, _fic),
@@ -939,97 +971,69 @@ public:
class BasicInverterTest : public InverterTest {
public:
- BasicInverterTest() : InverterTest(make_multi_field_schema()) {}
+ BasicInverterTest() : InverterTest(make_multi_field_schema(), make_multi_field_add_fields()) {}
};
TEST_F(BasicInverterTest, require_that_inversion_is_working)
{
Document::UP doc;
+ StringFieldBuilder sfb(_b);
- _b.startDocument("id:ns:searchdocument::10");
- _b.startIndexField("f0").
- addStr("a").addStr("b").addStr("c").addStr("d").
- endField();
- doc = _b.endDocument();
+ doc = _b.make_document("id:ns:searchdocument::10");
+ doc->setValue("f0", sfb.tokenize("a b c d").build());
_inv.invertDocument(10, *doc, {});
myPushDocument(_inv);
- _b.startDocument("id:ns:searchdocument::20");
- _b.startIndexField("f0").
- addStr("a").addStr("a").addStr("b").addStr("c").addStr("d").
- endField();
- doc = _b.endDocument();
+ doc = _b.make_document("id:ns:searchdocument::20");
+ doc->setValue("f0", sfb.tokenize("a a b c d").build());
_inv.invertDocument(20, *doc, {});
myPushDocument(_inv);
- _b.startDocument("id:ns:searchdocument::30");
- _b.startIndexField("f0").
- addStr("a").addStr("b").addStr("c").addStr("d").
- addStr("e").addStr("f").
- endField();
- _b.startIndexField("f1").
- addStr("\nw2").addStr("w").addStr("x").
- addStr("\nw3").addStr("y").addStr("z").
- endField();
- _b.startIndexField("f2").
- startElement(4).
- addStr("w").addStr("x").
- endElement().
- startElement(5).
- addStr("y").addStr("z").
- endElement().
- endField();
- _b.startIndexField("f3").
- startElement(6).
- addStr("w").addStr("x").
- endElement().
- startElement(7).
- addStr("y").addStr("z").
- endElement().
- endField();
- doc = _b.endDocument();
+ doc = _b.make_document("id:ns:searchdocument::30");
+ doc->setValue("f0", sfb.tokenize("a b c d e f").build());
+ doc->setValue("f1", sfb.word("\nw2").tokenize(" w x ").
+ word("\nw3").tokenize(" y z").build());
+ {
+ auto string_array = _b.make_array("f2");
+ string_array.add(sfb.tokenize("w x").build());
+ string_array.add(sfb.tokenize("y z").build());
+ doc->setValue("f2", string_array);
+ }
+ {
+ auto string_wset = _b.make_wset("f3");
+ string_wset.add(sfb.tokenize("w x").build(), 6);
+ string_wset.add(sfb.tokenize("y z").build(), 7);
+ doc->setValue("f3", string_wset);
+ }
_inv.invertDocument(30, *doc, {});
myPushDocument(_inv);
- _b.startDocument("id:ns:searchdocument::40");
- _b.startIndexField("f0").
- addStr("a").addStr("a").addStr("b").addStr("c").addStr("a").
- addStr("e").addStr("f").
- endField();
- doc = _b.endDocument();
+ doc = _b.make_document("id:ns:searchdocument::40");
+ doc->setValue("f0", sfb.tokenize("a a b c a e f").build());
_inv.invertDocument(40, *doc, {});
myPushDocument(_inv);
- _b.startDocument("id:ns:searchdocument::999");
- _b.startIndexField("f0").
- addStr("this").addStr("is").addStr("_a_").addStr("test").
- addStr("for").addStr("insertion").addStr("speed").addStr("with").
- addStr("more").addStr("than").addStr("just").addStr("__a__").
- addStr("few").addStr("words").addStr("present").addStr("in").
- addStr("some").addStr("of").addStr("the").addStr("fields").
- endField();
- _b.startIndexField("f1").
- addStr("the").addStr("other").addStr("field").addStr("also").
- addStr("has").addStr("some").addStr("content").
- endField();
- _b.startIndexField("f2").
- startElement(1).
- addStr("strange").addStr("things").addStr("here").
- addStr("has").addStr("some").addStr("content").
- endElement().
- endField();
- _b.startIndexField("f3").
- startElement(3).
- addStr("not").addStr("a").addStr("weighty").addStr("argument").
- endElement().
- endField();
- doc = _b.endDocument();
+ doc = _b.make_document("id:ns:searchdocument::999");
+ doc->setValue("f0", sfb.tokenize("this is ").word("_a_").
+ tokenize(" test for insertion speed with more than just ").
+ word("__a__").tokenize(" few words present in some of the fields").build());
+ doc->setValue("f1", sfb.tokenize("the other field also has some content").build());
+ {
+ auto string_array = _b.make_array("f2");
+ string_array.add(sfb.tokenize("strange things here has some content").build());
+ doc->setValue("f2", string_array);
+ }
+ {
+ auto string_wset = _b.make_wset("f3");
+ string_wset.add(sfb.tokenize("not a weighty argument").build(), 3);
+ doc->setValue("f3", string_wset);
+ }
for (uint32_t docId = 10000; docId < 20000; ++docId) {
_inv.invertDocument(docId, *doc, {});
myPushDocument(_inv);
}
- DataStoreBase::MemStats beforeStats = getFeatureStoreMemStats(_fic);
+ auto beforeStats = getFeatureStoreMemStats(_fic);
LOG(info,
"Before feature compaction: allocElems=%zu, usedElems=%zu"
", deadElems=%zu, holdElems=%zu"
@@ -1049,7 +1053,7 @@ TEST_F(BasicInverterTest, require_that_inversion_is_working)
(fieldIndex->takeGenerationGuard()));
}
myCommit(_fic, *_pushThreads);
- DataStoreBase::MemStats duringStats = getFeatureStoreMemStats(_fic);
+ auto duringStats = getFeatureStoreMemStats(_fic);
LOG(info,
"During feature compaction: allocElems=%zu, usedElems=%zu"
", deadElems=%zu, holdElems=%zu"
@@ -1064,7 +1068,7 @@ TEST_F(BasicInverterTest, require_that_inversion_is_working)
duringStats._holdBuffers);
guards.clear();
myCommit(_fic, *_pushThreads);
- DataStoreBase::MemStats afterStats = getFeatureStoreMemStats(_fic);
+ auto afterStats = getFeatureStoreMemStats(_fic);
LOG(info,
"After feature compaction: allocElems=%zu, usedElems=%zu"
", deadElems=%zu, holdElems=%zu"
@@ -1133,19 +1137,17 @@ TEST_F(BasicInverterTest, require_that_inversion_is_working)
TEST_F(BasicInverterTest, require_that_inverter_handles_remove_via_document_remover)
{
- Document::UP doc;
+ StringFieldBuilder sfb(_b);
- _b.startDocument("id:ns:searchdocument::1");
- _b.startIndexField("f0").addStr("a").addStr("b").endField();
- _b.startIndexField("f1").addStr("a").addStr("c").endField();
- Document::UP doc1 = _b.endDocument();
- _inv.invertDocument(1, *doc1.get(), {});
+ auto doc1 = _b.make_document("id:ns:searchdocument::1");
+ doc1->setValue("f0", sfb.tokenize("a b").build());
+ doc1->setValue("f1", sfb.tokenize("a c").build());
+ _inv.invertDocument(1, *doc1, {});
myPushDocument(_inv);
- _b.startDocument("id:ns:searchdocument::2");
- _b.startIndexField("f0").addStr("b").addStr("c").endField();
- Document::UP doc2 = _b.endDocument();
- _inv.invertDocument(2, *doc2.get(), {});
+ auto doc2 = _b.make_document("id:ns:searchdocument::2");
+ doc2->setValue("f0", sfb.tokenize("b c").build());
+ _inv.invertDocument(2, *doc2, {});
myPushDocument(_inv);
EXPECT_TRUE(assertPostingList("[1]", find("a", 0)));
@@ -1173,136 +1175,71 @@ make_uri_schema()
return result;
}
+DocBuilder::AddFieldsType
+make_uri_add_fields()
+{
+ return [](auto& header) { using namespace document::config_builder;
+ header.addField("iu", UrlDataType::getInstance().getId())
+ .addField("iau", Array(UrlDataType::getInstance().getId()))
+ .addField("iwu", Wset(UrlDataType::getInstance().getId()));
+ };
+}
+
class UriInverterTest : public InverterTest {
public:
- UriInverterTest() : InverterTest(make_uri_schema()) {}
+ UriInverterTest() : InverterTest(make_uri_schema(), make_uri_add_fields()) {}
};
TEST_F(UriInverterTest, require_that_uri_indexing_is_working)
{
Document::UP doc;
-
- _b.startDocument("id:ns:searchdocument::10");
- _b.startIndexField("iu").
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:81/fluke?ab=2#4").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("81").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("4").
- endSubField().
- endField();
- _b.startIndexField("iau").
- startElement(1).
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:82/fluke?ab=2#8").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("82").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("8").
- endSubField().
- endElement().
- startElement(1).
- startSubField("all").
- addUrlTokenizedString("http://www.flickr.com:82/fluke?ab=2#9").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.flickr.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("82").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("9").
- endSubField().
- endElement().
- endField();
- _b.startIndexField("iwu").
- startElement(4).
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:83/fluke?ab=2#12").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("83").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("12").
- endSubField().
- endElement().
- startElement(7).
- startSubField("all").
- addUrlTokenizedString("http://www.flickr.com:85/fluke?ab=2#13").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.flickr.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("85").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("13").
- endSubField().
- endElement().
- endField();
- doc = _b.endDocument();
+ StringFieldBuilder sfb(_b);
+ sfb.url_mode(true);
+ auto url_value = _b.make_url();
+
+ doc = _b.make_document("id:ns:searchdocument::10");
+ url_value.setValue("all", sfb.tokenize("http://www.example.com:81/fluke?ab=2#4").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.example.com").build());
+ url_value.setValue("port", sfb.tokenize("81").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").build());
+ url_value.setValue("query", sfb.tokenize("ab=2").build());
+ url_value.setValue("fragment", sfb.tokenize("4").build());
+ doc->setValue("iu", url_value);
+ auto url_array = _b.make_array("iau");
+ url_value.setValue("all", sfb.tokenize("http://www.example.com:82/fluke?ab=2#8").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.example.com").build());
+ url_value.setValue("port", sfb.tokenize("82").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").build());
+ url_value.setValue("query", sfb.tokenize("ab=2").build());
+ url_value.setValue("fragment", sfb.tokenize("8").build());
+ url_array.add(url_value);
+ url_value.setValue("all", sfb.tokenize("http://www.flickr.com:82/fluke?ab=2#9").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.flickr.com").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").build());
+ url_value.setValue("fragment", sfb.tokenize("9").build());
+ url_array.add(url_value);
+ doc->setValue("iau", url_array);
+ auto url_wset = _b.make_wset("iwu");
+ url_value.setValue("all", sfb.tokenize("http://www.example.com:83/fluke?ab=2#12").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.example.com").build());
+ url_value.setValue("port", sfb.tokenize("83").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").alt_word("altfluke").build());
+ url_value.setValue("query", sfb.tokenize("ab=2").build());
+ url_value.setValue("fragment", sfb.tokenize("12").build());
+ url_wset.add(url_value, 4);
+ url_value.setValue("all", sfb.tokenize("http://www.flickr.com:85/fluke?ab=2#13").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.flickr.com").build());
+ url_value.setValue("port", sfb.tokenize("85").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").build());
+ url_value.setValue("query", sfb.tokenize("ab=2").build());
+ url_value.setValue("fragment", sfb.tokenize("13").build());
+ url_wset.add(url_value, 7);
+ doc->setValue("iwu", url_wset);
_inv.invertDocument(10, *doc, {});
myPushDocument(_inv);
@@ -1361,21 +1298,16 @@ TEST_F(UriInverterTest, require_that_uri_indexing_is_working)
class CjkInverterTest : public InverterTest {
public:
- CjkInverterTest() : InverterTest(make_single_field_schema()) {}
+ CjkInverterTest() : InverterTest(make_single_field_schema(), make_single_add_fields()) {}
};
TEST_F(CjkInverterTest, require_that_cjk_indexing_is_working)
{
Document::UP doc;
+ StringFieldBuilder sfb(_b);
- _b.startDocument("id:ns:searchdocument::10");
- _b.startIndexField("f0").
- addStr("我就是那个").
- setAutoSpace(false).
- addStr("大灰狼").
- setAutoSpace(true).
- endField();
- doc = _b.endDocument();
+ doc = _b.make_document("id:ns:searchdocument::10");
+ doc->setValue("f0", sfb.word("我就是那个").word("大灰狼").build());
_inv.invertDocument(10, *doc, {});
myPushDocument(_inv);
diff --git a/searchlib/src/tests/memoryindex/field_inverter/field_inverter_test.cpp b/searchlib/src/tests/memoryindex/field_inverter/field_inverter_test.cpp
index ed049a82c42..db97846dc30 100644
--- a/searchlib/src/tests/memoryindex/field_inverter/field_inverter_test.cpp
+++ b/searchlib/src/tests/memoryindex/field_inverter/field_inverter_test.cpp
@@ -1,8 +1,14 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include <vespa/document/repo/fixedtyperepo.h>
-#include <vespa/searchlib/index/docbuilder.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/fieldvalue/weightedsetfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/searchcommon/common/schema.h>
#include <vespa/searchlib/index/field_length_calculator.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/memoryindex/field_index_remover.h>
#include <vespa/searchlib/memoryindex/field_inverter.h>
#include <vespa/searchlib/memoryindex/word_store.h>
@@ -13,11 +19,14 @@
namespace search {
+using document::ArrayFieldValue;
using document::Document;
-using index::DocBuilder;
+using document::WeightedSetFieldValue;
using index::Schema;
using index::schema::CollectionType;
using index::schema::DataType;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using namespace index;
@@ -28,81 +37,79 @@ namespace {
Document::UP
makeDoc10(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::10");
- b.startIndexField("f0").
- addStr("a").addStr("b").addStr("c").addStr("d").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::10");
+ doc->setValue("f0", sfb.tokenize("a b c d").build());
+ return doc;
}
Document::UP
makeDoc11(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::11");
- b.startIndexField("f0").
- addStr("a").addStr("b").addStr("e").addStr("f").
- endField();
- b.startIndexField("f1").
- addStr("a").addStr("g").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::11");
+ doc->setValue("f0", sfb.tokenize("a b e f").build());
+ doc->setValue("f1", sfb.tokenize("a g").build());
+ return doc;
}
Document::UP
makeDoc12(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::12");
- b.startIndexField("f0").
- addStr("h").addStr("doc12").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::12");
+ doc->setValue("f0", sfb.tokenize("h doc12").build());
+ return doc;
}
Document::UP
makeDoc13(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::13");
- b.startIndexField("f0").
- addStr("i").addStr("doc13").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::13");
+ doc->setValue("f0", sfb.tokenize("i doc13").build());
+ return doc;
}
Document::UP
makeDoc14(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::14");
- b.startIndexField("f0").
- addStr("j").addStr("doc14").
- endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::14");
+ doc->setValue("f0", sfb.tokenize("j doc14").build());
+ return doc;
}
Document::UP
makeDoc15(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::15");
- return b.endDocument();
+ return b.make_document("id:ns:searchdocument::15");
}
Document::UP
makeDoc16(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::16");
- b.startIndexField("f0").addStr("foo").addStr("bar").addStr("baz").
- addTermAnnotation("altbaz").addStr("y").addTermAnnotation("alty").
- addStr("z").endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::16");
+ doc->setValue("f0", sfb.tokenize("foo bar baz").alt_word("altbaz").tokenize(" y").alt_word("alty").tokenize(" z").build());
+ return doc;
}
Document::UP
makeDoc17(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::17");
- b.startIndexField("f1").addStr("foo0").addStr("bar0").endField();
- b.startIndexField("f2").startElement(1).addStr("foo").addStr("bar").endElement().startElement(1).addStr("bar").endElement().endField();
- b.startIndexField("f3").startElement(3).addStr("foo2").addStr("bar2").endElement().startElement(4).addStr("bar2").endElement().endField();
- return b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::17");
+ doc->setValue("f1", sfb.tokenize("foo0 bar0").build());
+ auto string_array = b.make_array("f2");
+ string_array.add(sfb.tokenize("foo bar").build());
+ string_array.add(sfb.tokenize("bar").build());
+ doc->setValue("f2", string_array);
+ auto string_wset = b.make_wset("f3");
+ string_wset.add(sfb.tokenize("foo2 bar2").build(), 3);
+ string_wset.add(sfb.tokenize("bar2").build(), 4);
+ doc->setValue("f3", string_wset);
+ return doc;
}
vespalib::string corruptWord = "corruptWord";
@@ -110,9 +117,9 @@ vespalib::string corruptWord = "corruptWord";
Document::UP
makeCorruptDocument(DocBuilder &b, size_t wordOffset)
{
- b.startDocument("id:ns:searchdocument::18");
- b.startIndexField("f0").addStr("before").addStr(corruptWord).addStr("after").addStr("z").endField();
- auto doc = b.endDocument();
+ StringFieldBuilder sfb(b);
+ auto doc = b.make_document("id:ns:searchdocument::18");
+ doc->setValue("f0", sfb.tokenize("before ").word(corruptWord).tokenize(" after z").build());
vespalib::nbostream stream;
doc->serialize(stream);
std::vector<char> raw;
@@ -127,7 +134,7 @@ makeCorruptDocument(DocBuilder &b, size_t wordOffset)
}
vespalib::nbostream badstream;
badstream.write(&raw[0], raw.size());
- return std::make_unique<Document>(*b.getDocumentTypeRepo(), badstream);
+ return std::make_unique<Document>(b.get_repo(), badstream);
}
}
@@ -151,9 +158,21 @@ struct FieldInverterTest : public ::testing::Test {
return schema;
}
+ static DocBuilder::AddFieldsType
+ make_add_fields()
+ {
+ return [](auto& header) { using namespace document::config_builder;
+ using DataType = document::DataType;
+ header.addField("f0", DataType::T_STRING)
+ .addField("f1", DataType::T_STRING)
+ .addField("f2", Array(DataType::T_STRING))
+ .addField("f3", Wset(DataType::T_STRING));
+ };
+ }
+
FieldInverterTest()
: _schema(makeSchema()),
- _b(_schema),
+ _b(make_add_fields()),
_word_store(),
_remover(_word_store),
_inserter_backend(),
diff --git a/searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt b/searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt
index e5915cca6f3..0a771d98b90 100644
--- a/searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt
+++ b/searchlib/src/tests/memoryindex/memory_index/CMakeLists.txt
@@ -3,7 +3,7 @@ vespa_add_executable(searchlib_memory_index_test_app TEST
SOURCES
memory_index_test.cpp
DEPENDS
- searchlib
+ searchlib_test
GTest::GTest
)
vespa_add_test(NAME searchlib_memory_index_test_app COMMAND searchlib_memory_index_test_app)
diff --git a/searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp b/searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp
index b3ea948dfa7..b5b8e0c7a7c 100644
--- a/searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp
+++ b/searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp
@@ -1,11 +1,15 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/searchlib/common/scheduletaskcallback.h>
#include <vespa/searchlib/fef/matchdata.h>
#include <vespa/searchlib/fef/matchdatalayout.h>
#include <vespa/searchlib/fef/termfieldmatchdata.h>
-#include <vespa/searchlib/index/docbuilder.h>
#include <vespa/searchlib/index/i_field_length_inspector.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/memoryindex/memory_index.h>
#include <vespa/searchlib/query/tree/simplequery.h>
#include <vespa/searchlib/queryeval/booleanmatchiteratorwrapper.h>
@@ -32,6 +36,8 @@ using vespalib::makeLambdaTask;
using search::query::Node;
using search::query::SimplePhrase;
using search::query::SimpleStringTerm;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using vespalib::ISequencedTaskExecutor;
using vespalib::SequencedTaskExecutor;
using namespace search::fef;
@@ -59,6 +65,12 @@ struct MySetup : public IFieldLengthInspector {
}
return FieldLengthInfo();
}
+ void add_fields(document::config_builder::Struct& header) const {
+ for (uint32_t i = 0; i < schema.getNumIndexFields(); ++i) {
+ auto& field = schema.getIndexField(i);
+ header.addField(field.getName(), document::DataType::T_STRING);
+ }
+ }
};
@@ -71,30 +83,37 @@ struct Index {
std::unique_ptr<ISequencedTaskExecutor> _pushThreads;
MemoryIndex index;
DocBuilder builder;
+ StringFieldBuilder sfb;
+ std::unique_ptr<Document> builder_doc;
uint32_t docid;
std::string currentField;
+ bool add_space;
Index(const MySetup &setup);
~Index();
void closeField() {
if (!currentField.empty()) {
- builder.endField();
+ builder_doc->setValue(currentField, sfb.build());
currentField.clear();
}
}
Index &doc(uint32_t id) {
docid = id;
- builder.startDocument(vespalib::make_string("id:ns:searchdocument::%u", id));
+ builder_doc = builder.make_document(vespalib::make_string("id:ns:searchdocument::%u", id));
return *this;
}
Index &field(const std::string &name) {
closeField();
- builder.startIndexField(name);
currentField = name;
+ add_space = false;
return *this;
}
Index &add(const std::string &token) {
- builder.addStr(token);
+ if (add_space) {
+ sfb.space();
+ }
+ add_space = true;
+ sfb.word(token);
return *this;
}
void internalSyncCommit() {
@@ -106,7 +125,7 @@ struct Index {
}
Document::UP commit() {
closeField();
- Document::UP d = builder.endDocument();
+ Document::UP d = std::move(builder_doc);
index.insertDocument(docid, *d, {});
internalSyncCommit();
return d;
@@ -133,9 +152,12 @@ Index::Index(const MySetup &setup)
_invertThreads(SequencedTaskExecutor::create(invert_executor, 2)),
_pushThreads(SequencedTaskExecutor::create(push_executor, 2)),
index(schema, setup, *_invertThreads, *_pushThreads),
- builder(schema),
+ builder([&setup](auto& header) { setup.add_fields(header); }),
+ sfb(builder),
+ builder_doc(),
docid(1),
- currentField()
+ currentField(),
+ add_space(false)
{
}
Index::~Index() = default;
diff --git a/searchlib/src/tests/memoryindex/url_field_inverter/url_field_inverter_test.cpp b/searchlib/src/tests/memoryindex/url_field_inverter/url_field_inverter_test.cpp
index 969f483eef6..b3892d5d69a 100644
--- a/searchlib/src/tests/memoryindex/url_field_inverter/url_field_inverter_test.cpp
+++ b/searchlib/src/tests/memoryindex/url_field_inverter/url_field_inverter_test.cpp
@@ -1,12 +1,22 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/searchlib/memoryindex/url_field_inverter.h>
+#include <vespa/document/datatype/urldatatype.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/fieldvalue/structfieldvalue.h>
+#include <vespa/document/fieldvalue/weightedsetfieldvalue.h>
+#include <vespa/document/repo/configbuilder.h>
#include <vespa/document/repo/fixedtyperepo.h>
-#include <vespa/searchlib/index/docbuilder.h>
+#include <vespa/searchcommon/common/schema.h>
#include <vespa/searchlib/index/field_length_calculator.h>
+#include <vespa/searchlib/index/schema_index_fields.h>
#include <vespa/searchlib/memoryindex/field_index_remover.h>
#include <vespa/searchlib/memoryindex/field_inverter.h>
-#include <vespa/searchlib/memoryindex/url_field_inverter.h>
#include <vespa/searchlib/memoryindex/word_store.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/searchlib/test/string_field_builder.h>
#include <vespa/searchlib/test/memoryindex/ordered_field_index_inserter.h>
#include <vespa/searchlib/test/memoryindex/ordered_field_index_inserter_backend.h>
#include <vespa/vespalib/gtest/gtest.h>
@@ -14,8 +24,14 @@
namespace search {
using document::Document;
+using document::ArrayFieldValue;
+using document::StructFieldValue;
+using document::UrlDataType;
+using document::WeightedSetFieldValue;
using index::schema::CollectionType;
using index::schema::DataType;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
using namespace index;
@@ -28,151 +44,79 @@ const vespalib::string url = "url";
Document::UP
makeDoc10Single(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::10");
- b.startIndexField("url").
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:81/fluke?ab=2#4").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("81").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- addTermAnnotation("altfluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("4").
- endSubField().
- endField();
- return b.endDocument();
+ auto doc = b.make_document("id:ns:searchdocument::10");
+ auto url_value = b.make_struct("url");
+ StringFieldBuilder sfb(b);
+ sfb.url_mode(true);
+ url_value.setValue("all", sfb.tokenize("http://www.example.com:81/fluke?ab=2#4").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.example.com").build());
+ url_value.setValue("port", sfb.tokenize("81").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").alt_word("altfluke").build());
+ url_value.setValue("query", sfb.tokenize("ab=2").build());
+ url_value.setValue("fragment", sfb.tokenize("4").build());
+ doc->setValue("url", url_value);
+ return doc;
}
Document::UP
makeDoc10Array(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::10");
- b.startIndexField("url").
- startElement(1).
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:82/fluke?ab=2#8").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("82").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- addTermAnnotation("altfluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("8").
- endSubField().
- endElement().
- startElement(1).
- startSubField("all").
- addUrlTokenizedString("http://www.flickr.com:82/fluke?ab=2#9").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.flickr.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("82").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("9").
- endSubField().
- endElement().
- endField();
- return b.endDocument();
+ auto doc = b.make_document("id:ns:searchdocument::10");
+ StringFieldBuilder sfb(b);
+ sfb.url_mode(true);
+ auto url_array = b.make_array("url");
+ auto url_value = b.make_url();
+ url_value.setValue("all", sfb.tokenize("http://www.example.com:82/fluke?ab=2#8").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.example.com").build());
+ url_value.setValue("port", sfb.tokenize("82").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").alt_word("altfluke").build());
+ url_value.setValue("query", sfb.tokenize("ab=2").build());
+ url_value.setValue("fragment", sfb.tokenize("8").build());
+ url_array.add(url_value);
+ url_value.setValue("all", sfb.tokenize("http://www.flickr.com:82/fluke?ab=2#9").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.flickr.com").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").build());
+ url_value.setValue("fragment", sfb.tokenize("9").build());
+ url_array.add(url_value);
+ doc->setValue("url", url_array);
+ return doc;
}
Document::UP
makeDoc10WeightedSet(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::10");
- b.startIndexField("url").
- startElement(4).
- startSubField("all").
- addUrlTokenizedString("http://www.example.com:83/fluke?ab=2#12").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.example.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("83").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- addTermAnnotation("altfluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("12").
- endSubField().
- endElement().
- startElement(7).
- startSubField("all").
- addUrlTokenizedString("http://www.flickr.com:85/fluke?ab=2#13").
- endSubField().
- startSubField("scheme").
- addUrlTokenizedString("http").
- endSubField().
- startSubField("host").
- addUrlTokenizedString("www.flickr.com").
- endSubField().
- startSubField("port").
- addUrlTokenizedString("85").
- endSubField().
- startSubField("path").
- addUrlTokenizedString("/fluke").
- endSubField().
- startSubField("query").
- addUrlTokenizedString("ab=2").
- endSubField().
- startSubField("fragment").
- addUrlTokenizedString("13").
- endSubField().
- endElement().
- endField();
- return b.endDocument();
+ auto doc = b.make_document("id:ns:searchdocument::10");
+ StringFieldBuilder sfb(b);
+ sfb.url_mode(true);
+ auto url_wset = b.make_wset("url");
+ auto url_value = b.make_url();
+ url_value.setValue("all", sfb.tokenize("http://www.example.com:83/fluke?ab=2#12").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.example.com").build());
+ url_value.setValue("port", sfb.tokenize("83").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").alt_word("altfluke").build());
+ url_value.setValue("query", sfb.tokenize("ab=2").build());
+ url_value.setValue("fragment", sfb.tokenize("12").build());
+ url_wset.add(url_value, 4);
+ url_value.setValue("all", sfb.tokenize("http://www.flickr.com:85/fluke?ab=2#13").build());
+ url_value.setValue("scheme", sfb.tokenize("http").build());
+ url_value.setValue("host", sfb.tokenize("www.flickr.com").build());
+ url_value.setValue("port", sfb.tokenize("85").build());
+ url_value.setValue("path", sfb.tokenize("/fluke").build());
+ url_value.setValue("query", sfb.tokenize("ab=2").build());
+ url_value.setValue("fragment", sfb.tokenize("13").build());
+ url_wset.add(url_value, 7);
+ doc->setValue("url", url_wset);
+ return doc;
}
Document::UP
makeDoc10Empty(DocBuilder &b)
{
- b.startDocument("id:ns:searchdocument::10");
- return b.endDocument();
+ return b.make_document("id:ns:searchdocument::10");
}
}
@@ -195,9 +139,10 @@ struct UrlFieldInverterTest : public ::testing::Test {
return schema;
}
- UrlFieldInverterTest(Schema::CollectionType collectionType)
+ UrlFieldInverterTest(Schema::CollectionType collectionType,
+ DocBuilder::AddFieldsType add_fields)
: _schema(makeSchema(collectionType)),
- _b(_schema),
+ _b(add_fields),
_word_store(),
_remover(_word_store),
_inserter_backend(),
@@ -250,16 +195,32 @@ struct UrlFieldInverterTest : public ::testing::Test {
UrlFieldInverterTest::~UrlFieldInverterTest() = default;
+DocBuilder::AddFieldsType
+add_single_url = [](auto& header) {
+ header.addField("url", UrlDataType::getInstance().getId()); };
+
+DocBuilder::AddFieldsType
+add_array_url = [](auto& header) {
+ using namespace document::config_builder;
+ header.addField("url", Array(UrlDataType::getInstance().getId())); };
+
+DocBuilder::AddFieldsType
+add_wset_url = [](auto& header) {
+ using namespace document::config_builder;
+ header.addField("url", Wset(UrlDataType::getInstance().getId())); };
+
+
+
struct SingleInverterTest : public UrlFieldInverterTest {
- SingleInverterTest() : UrlFieldInverterTest(CollectionType::SINGLE) {}
+ SingleInverterTest() : UrlFieldInverterTest(CollectionType::SINGLE, add_single_url) {}
};
struct ArrayInverterTest : public UrlFieldInverterTest {
- ArrayInverterTest() : UrlFieldInverterTest(CollectionType::ARRAY) {}
+ ArrayInverterTest() : UrlFieldInverterTest(CollectionType::ARRAY, add_array_url) {}
};
struct WeightedSetInverterTest : public UrlFieldInverterTest {
- WeightedSetInverterTest() : UrlFieldInverterTest(CollectionType::WEIGHTEDSET) {}
+ WeightedSetInverterTest() : UrlFieldInverterTest(CollectionType::WEIGHTEDSET, add_wset_url) {}
};
diff --git a/searchlib/src/tests/predicate/simple_index_test.cpp b/searchlib/src/tests/predicate/simple_index_test.cpp
index dfa8c12deec..7bf52680782 100644
--- a/searchlib/src/tests/predicate/simple_index_test.cpp
+++ b/searchlib/src/tests/predicate/simple_index_test.cpp
@@ -74,7 +74,7 @@ struct Fixture {
Fixture() : _generation_holder(), _limit_provider(),
_index(_generation_holder, _limit_provider, config) {}
~Fixture() {
- _generation_holder.clearHoldLists();
+ _generation_holder.reclaim_all();
}
SimpleIndex<MyData> &index() {
return _index;
diff --git a/searchlib/src/tests/sortspec/multilevelsort.cpp b/searchlib/src/tests/sortspec/multilevelsort.cpp
index 82bdf99ab2c..87dc6608c3f 100644
--- a/searchlib/src/tests/sortspec/multilevelsort.cpp
+++ b/searchlib/src/tests/sortspec/multilevelsort.cpp
@@ -16,11 +16,6 @@ LOG_SETUP("multilevelsort_test");
using namespace search;
-typedef FastS_SortSpec::VectorRef VectorRef;
-typedef IntegerAttributeTemplate<int8_t> Int8;
-typedef IntegerAttributeTemplate<int16_t> Int16;
-typedef IntegerAttributeTemplate<int32_t> Int32;
-typedef IntegerAttributeTemplate<int64_t> Int64;
typedef FloatingPointAttributeTemplate<float> Float;
typedef FloatingPointAttributeTemplate<double> Double;
typedef std::map<std::string, AttributeVector::SP > VectorMap;
@@ -53,16 +48,16 @@ public:
};
private:
template<typename T>
- T getRandomValue() {
+ static T getRandomValue() {
T min = std::numeric_limits<T>::min();
T max = std::numeric_limits<T>::max();
return static_cast<T>(double(min) + (double(max) - double(min)) * (double(rand()) / double(RAND_MAX)));
}
template<typename T>
- void fill(IntegerAttribute *attr, uint32_t size, uint32_t unique = 0);
+ static void fill(IntegerAttribute *attr, uint32_t size, uint32_t unique = 0);
template<typename T>
- void fill(FloatingPointAttribute *attr, uint32_t size, uint32_t unique = 0);
- void fill(StringAttribute *attr, uint32_t size, const std::vector<std::string> &values);
+ static void fill(FloatingPointAttribute *attr, uint32_t size, uint32_t unique = 0);
+ static void fill(StringAttribute *attr, uint32_t size, const std::vector<std::string> &values);
template <typename V>
int compareTemplate(AttributeVector *vector, uint32_t a, uint32_t b);
int compare(AttributeVector *vector, AttrType type, uint32_t a, uint32_t b);
@@ -181,7 +176,7 @@ MultilevelSortTest::compare(AttributeVector *vector, AttrType type, uint32_t a,
} else if (type == DOUBLE) {
return compareTemplate<double>(vector, a, b);
} else if (type == STRING) {
- StringAttribute *vString = static_cast<StringAttribute*>(vector);
+ StringAttribute *vString = dynamic_cast<StringAttribute*>(vector);
const char *va = vString->get(a);
const char *vb = vString->get(b);
std::string sa(va);
@@ -199,106 +194,99 @@ MultilevelSortTest::compare(AttributeVector *vector, AttrType type, uint32_t a,
}
void
-MultilevelSortTest::sortAndCheck(const std::vector<Spec> &spec, uint32_t num,
+MultilevelSortTest::sortAndCheck(const std::vector<Spec> &specs, uint32_t num,
uint32_t unique, const std::vector<std::string> &strValues)
{
VectorMap vec;
// generate attribute vectors
- for (uint32_t i = 0; i < spec.size(); ++i) {
- std::string name = spec[i]._name;
- AttrType type = spec[i]._type;
+ for (const auto & spec : specs) {
+ std::string name = spec._name;
+ AttrType type = spec._type;
if (type == INT8) {
Config cfg(BasicType::INT8, CollectionType::SINGLE);
vec[name] = AttributeFactory::createAttribute(name, cfg);
- fill<int8_t>(static_cast<IntegerAttribute *>(vec[name].get()), num, unique);
+ fill<int8_t>(dynamic_cast<IntegerAttribute *>(vec[name].get()), num, unique);
} else if (type == INT16) {
Config cfg(BasicType::INT16, CollectionType::SINGLE);
vec[name] = AttributeFactory::createAttribute(name, cfg);
- fill<int16_t>(static_cast<IntegerAttribute *>(vec[name].get()), num, unique);
+ fill<int16_t>(dynamic_cast<IntegerAttribute *>(vec[name].get()), num, unique);
} else if (type == INT32) {
Config cfg(BasicType::INT32, CollectionType::SINGLE);
vec[name] = AttributeFactory::createAttribute(name, cfg);
- fill<int32_t>(static_cast<IntegerAttribute *>(vec[name].get()), num, unique);
+ fill<int32_t>(dynamic_cast<IntegerAttribute *>(vec[name].get()), num, unique);
} else if (type == INT64) {
Config cfg(BasicType::INT64, CollectionType::SINGLE);
vec[name] = AttributeFactory::createAttribute(name, cfg);
- fill<int64_t>(static_cast<IntegerAttribute *>(vec[name].get()), num, unique);
+ fill<int64_t>(dynamic_cast<IntegerAttribute *>(vec[name].get()), num, unique);
} else if (type == FLOAT) {
Config cfg(BasicType::FLOAT, CollectionType::SINGLE);
vec[name] = AttributeFactory::createAttribute(name, cfg);
- fill<float>(static_cast<FloatingPointAttribute *>(vec[name].get()), num, unique);
+ fill<float>(dynamic_cast<FloatingPointAttribute *>(vec[name].get()), num, unique);
} else if (type == DOUBLE) {
Config cfg(BasicType::DOUBLE, CollectionType::SINGLE);
vec[name] = AttributeFactory::createAttribute(name, cfg);
- fill<double>(static_cast<FloatingPointAttribute *>(vec[name].get()), num, unique);
+ fill<double>(dynamic_cast<FloatingPointAttribute *>(vec[name].get()), num, unique);
} else if (type == STRING) {
Config cfg(BasicType::STRING, CollectionType::SINGLE);
vec[name] = AttributeFactory::createAttribute(name, cfg);
- fill(static_cast<StringAttribute *>(vec[name].get()), num, strValues);
+ fill(dynamic_cast<StringAttribute *>(vec[name].get()), num, strValues);
}
if (vec[name])
vec[name]->commit();
}
- RankedHit *hits = new RankedHit[num];
+ std::vector<RankedHit> hits;
+ hits.reserve(num);
for (uint32_t i = 0; i < num; ++i) {
- hits[i]._docId = i;
- hits[i]._rankValue = getRandomValue<uint32_t>();
+ hits.emplace_back(i, getRandomValue<uint32_t>());
}
vespalib::TestClock clock;
vespalib::Doom doom(clock.clock(), vespalib::steady_time::max());
search::uca::UcaConverterFactory ucaFactory;
- FastS_SortSpec sorter(7, doom, ucaFactory);
+ FastS_SortSpec sorter("no-metastore", 7, doom, ucaFactory);
// init sorter with sort data
- for(uint32_t i = 0; i < spec.size(); ++i) {
+ for (const auto & spec : specs) {
AttributeGuard ag;
- if (spec[i]._type == RANK) {
- sorter._vectors.push_back
- (VectorRef(spec[i]._asc ? FastS_SortSpec::ASC_RANK :
- FastS_SortSpec::DESC_RANK, nullptr, nullptr));
- } else if (spec[i]._type == DOCID) {
- sorter._vectors.push_back
- (VectorRef(spec[i]._asc ? FastS_SortSpec::ASC_DOCID :
- FastS_SortSpec::DESC_DOCID, nullptr, nullptr));
+ if (spec._type == RANK) {
+ sorter._vectors.emplace_back(spec._asc ? FastS_SortSpec::ASC_RANK : FastS_SortSpec::DESC_RANK, nullptr, nullptr);
+ } else if (spec._type == DOCID) {
+ sorter._vectors.emplace_back(spec._asc ? FastS_SortSpec::ASC_DOCID : FastS_SortSpec::DESC_DOCID, nullptr, nullptr);
} else {
- const search::attribute::IAttributeVector * v = vec[spec[i]._name].get();
- sorter._vectors.push_back
- (VectorRef(spec[i]._asc ? FastS_SortSpec::ASC_VECTOR :
- FastS_SortSpec::DESC_VECTOR, v, nullptr));
+ const search::attribute::IAttributeVector * v = vec[spec._name].get();
+ sorter._vectors.emplace_back(spec._asc ? FastS_SortSpec::ASC_VECTOR : FastS_SortSpec::DESC_VECTOR, v, nullptr);
}
}
vespalib::Timer timer;
- sorter.sortResults(hits, num, num);
+ sorter.sortResults(&hits[0], num, num);
LOG(info, "sort time = %" PRId64 " ms", vespalib::count_ms(timer.elapsed()));
- uint32_t *offsets = new uint32_t[num + 1];
- char *buf = new char[sorter.getSortDataSize(0, num)];
- sorter.copySortData(0, num, offsets, buf);
+ std::vector<uint32_t> offsets(num + 1, 0);
+ auto buf = std::make_unique<char []>(sorter.getSortDataSize(0, num));
+ sorter.copySortData(0, num, &offsets[0], buf.get());
// check results
for (uint32_t i = 0; i < num - 1; ++i) {
- for (uint32_t j = 0; j < spec.size(); ++j) {
+ for (const Spec & spec : specs) {
int cmp = 0;
- if (spec[j]._type == RANK) {
+ if (spec._type == RANK) {
if (hits[i].getRank() < hits[i+1].getRank()) {
cmp = -1;
} else if (hits[i].getRank() > hits[i+1].getRank()) {
cmp = 1;
}
- } else if (spec[j]._type == DOCID) {
+ } else if (spec._type == DOCID) {
if (hits[i].getDocId() < hits[i+1].getDocId()) {
cmp = -1;
} else if (hits[i].getDocId() > hits[i+1].getDocId()) {
cmp = 1;
}
} else {
- AttributeVector *av = vec[spec[j]._name].get();
- cmp = compare(av, spec[j]._type,
- hits[i].getDocId(), hits[i+1].getDocId());
+ AttributeVector *av = vec[spec._name].get();
+ cmp = compare(av, spec._type, hits[i].getDocId(), hits[i+1].getDocId());
}
- if (spec[j]._asc) {
+ if (spec._asc) {
EXPECT_TRUE(cmp <= 0);
if (cmp < 0) {
break;
@@ -311,56 +299,51 @@ MultilevelSortTest::sortAndCheck(const std::vector<Spec> &spec, uint32_t num,
}
}
// check binary sort data
- uint32_t minLen = std::min(sorter._sortDataArray[i]._len,
- sorter._sortDataArray[i+1]._len);
+ uint32_t minLen = std::min(sorter._sortDataArray[i]._len, sorter._sortDataArray[i+1]._len);
int cmp = memcmp(&sorter._binarySortData[0] + sorter._sortDataArray[i]._idx,
&sorter._binarySortData[0] + sorter._sortDataArray[i+1]._idx,
minLen);
EXPECT_TRUE(cmp <= 0);
EXPECT_TRUE(sorter._sortDataArray[i]._len == (offsets[i+1] - offsets[i]));
cmp = memcmp(&sorter._binarySortData[0] + sorter._sortDataArray[i]._idx,
- buf + offsets[i], sorter._sortDataArray[i]._len);
+ buf.get() + offsets[i], sorter._sortDataArray[i]._len);
EXPECT_TRUE(cmp == 0);
}
EXPECT_TRUE(sorter._sortDataArray[num-1]._len == (offsets[num] - offsets[num-1]));
int cmp = memcmp(&sorter._binarySortData[0] + sorter._sortDataArray[num-1]._idx,
- buf + offsets[num-1], sorter._sortDataArray[num-1]._len);
+ buf.get() + offsets[num-1], sorter._sortDataArray[num-1]._len);
EXPECT_TRUE(cmp == 0);
-
- delete [] hits;
- delete [] offsets;
- delete [] buf;
}
void MultilevelSortTest::testSort()
{
{
std::vector<Spec> spec;
- spec.push_back(Spec("int8", INT8));
- spec.push_back(Spec("int16", INT16));
- spec.push_back(Spec("int32", INT32));
- spec.push_back(Spec("int64", INT64));
- spec.push_back(Spec("float", FLOAT));
- spec.push_back(Spec("double", DOUBLE));
- spec.push_back(Spec("string", STRING));
- spec.push_back(Spec("rank", RANK));
- spec.push_back(Spec("docid", DOCID));
+ spec.emplace_back("int8", INT8);
+ spec.emplace_back("int16", INT16);
+ spec.emplace_back("int32", INT32);
+ spec.emplace_back("int64", INT64);
+ spec.emplace_back("float", FLOAT);
+ spec.emplace_back("double", DOUBLE);
+ spec.emplace_back("string", STRING);
+ spec.emplace_back("rank", RANK);
+ spec.emplace_back("docid", DOCID);
std::vector<std::string> strValues;
- strValues.push_back("applications");
- strValues.push_back("places");
- strValues.push_back("system");
- strValues.push_back("vespa search core");
+ strValues.emplace_back("applications");
+ strValues.emplace_back("places");
+ strValues.emplace_back("system");
+ strValues.emplace_back("vespa search core");
srand(12345);
sortAndCheck(spec, 5000, 4, strValues);
srand(time(nullptr));
sortAndCheck(spec, 5000, 4, strValues);
- strValues.push_back("multilevelsort");
- strValues.push_back("trondheim");
- strValues.push_back("ubuntu");
- strValues.push_back("fastserver4");
+ strValues.emplace_back("multilevelsort");
+ strValues.emplace_back("trondheim");
+ strValues.emplace_back("ubuntu");
+ strValues.emplace_back("fastserver4");
srand(56789);
sortAndCheck(spec, 5000, 8, strValues);
@@ -403,7 +386,7 @@ TEST("test that [docid] translates to [lid][paritionid]") {
vespalib::TestClock clock;
vespalib::Doom doom(clock.clock(), vespalib::steady_time::max());
search::uca::UcaConverterFactory ucaFactory;
- FastS_SortSpec asc(7, doom, ucaFactory);
+ FastS_SortSpec asc("no-metastore", 7, doom, ucaFactory);
RankedHit hits[2] = {RankedHit(91, 0.0), RankedHit(3, 2.0)};
search::AttributeManager mgr;
search::AttributeContext ac(mgr);
@@ -420,7 +403,7 @@ TEST("test that [docid] translates to [lid][paritionid]") {
EXPECT_EQUAL(6u, sr2.second);
EXPECT_EQUAL(0, memcmp(SECOND_ASC, sr2.first, 6));
- FastS_SortSpec desc(7, doom, ucaFactory);
+ FastS_SortSpec desc("no-metastore", 7, doom, ucaFactory);
desc.Init("-[docid]", ac);
desc.initWithoutSorting(hits, 2);
sr1 = desc.getSortRef(0);
@@ -431,4 +414,45 @@ TEST("test that [docid] translates to [lid][paritionid]") {
EXPECT_EQUAL(0, memcmp(SECOND_DESC, sr2.first, 6));
}
+TEST("test that [docid] uses attribute when one exists") {
+ vespalib::TestClock clock;
+ vespalib::Doom doom(clock.clock(), vespalib::steady_time::max());
+ search::uca::UcaConverterFactory ucaFactory;
+ FastS_SortSpec asc("metastore", 7, doom, ucaFactory);
+ RankedHit hits[2] = {RankedHit(91, 0.0), RankedHit(3, 2.0)};
+ Config cfg(BasicType::INT64, CollectionType::SINGLE);
+ auto metastore = AttributeFactory::createAttribute("metastore", cfg);
+ ASSERT_TRUE(metastore->addDocs(100));
+ auto * iattr = dynamic_cast<IntegerAttribute *>(metastore.get());
+ for (uint32_t lid(0); lid < 100; lid++) {
+ iattr->update(lid, lid);
+ }
+ metastore->commit();
+ search::AttributeManager mgr;
+ mgr.add(metastore);
+ search::AttributeContext ac(mgr);
+ EXPECT_TRUE(asc.Init("+[docid]", ac));
+ asc.initWithoutSorting(hits, 2);
+ constexpr uint8_t FIRST_ASC[8] = {0x80,0,0,0,0,0,0,91};
+ constexpr uint8_t SECOND_ASC[8] = {0x80,0,0,0,0,0,0,3};
+ constexpr uint8_t FIRST_DESC[8] = {0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff - 91};
+ constexpr uint8_t SECOND_DESC[8] = {0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff - 3};
+ auto sr1 = asc.getSortRef(0);
+ EXPECT_EQUAL(8u, sr1.second);
+ EXPECT_EQUAL(0, memcmp(FIRST_ASC, sr1.first, 8));
+ auto sr2 = asc.getSortRef(1);
+ EXPECT_EQUAL(8u, sr2.second);
+ EXPECT_EQUAL(0, memcmp(SECOND_ASC, sr2.first, 8));
+
+ FastS_SortSpec desc("metastore", 7, doom, ucaFactory);
+ desc.Init("-[docid]", ac);
+ desc.initWithoutSorting(hits, 2);
+ sr1 = desc.getSortRef(0);
+ EXPECT_EQUAL(8u, sr1.second);
+ EXPECT_EQUAL(0, memcmp(FIRST_DESC, sr1.first, 8));
+ sr2 = desc.getSortRef(1);
+ EXPECT_EQUAL(8u, sr2.second);
+ EXPECT_EQUAL(0, memcmp(SECOND_DESC, sr2.first, 8));
+}
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/tensor/dense_tensor_store/dense_tensor_store_test.cpp b/searchlib/src/tests/tensor/dense_tensor_store/dense_tensor_store_test.cpp
index 149662cd266..26ef57aab65 100644
--- a/searchlib/src/tests/tensor/dense_tensor_store/dense_tensor_store_test.cpp
+++ b/searchlib/src/tests/tensor/dense_tensor_store/dense_tensor_store_test.cpp
@@ -32,15 +32,15 @@ struct Fixture
{}
void assertSetAndGetTensor(const TensorSpec &tensorSpec) {
Value::UP expTensor = makeTensor(tensorSpec);
- EntryRef ref = store.setTensor(*expTensor);
- Value::UP actTensor = store.getTensor(ref);
+ EntryRef ref = store.store_tensor(*expTensor);
+ Value::UP actTensor = store.get_tensor(ref);
EXPECT_EQUAL(*expTensor, *actTensor);
assertTensorView(ref, *expTensor);
}
void assertEmptyTensor(const TensorSpec &tensorSpec) {
Value::UP expTensor = makeTensor(tensorSpec);
EntryRef ref;
- Value::UP actTensor = store.getTensor(ref);
+ Value::UP actTensor = store.get_tensor(ref);
EXPECT_TRUE(actTensor.get() == nullptr);
assertTensorView(ref, *expTensor);
}
diff --git a/searchlib/src/tests/tensor/direct_tensor_store/direct_tensor_store_test.cpp b/searchlib/src/tests/tensor/direct_tensor_store/direct_tensor_store_test.cpp
index 1574e7d38f1..cb9fa8522a8 100644
--- a/searchlib/src/tests/tensor/direct_tensor_store/direct_tensor_store_test.cpp
+++ b/searchlib/src/tests/tensor/direct_tensor_store/direct_tensor_store_test.cpp
@@ -58,11 +58,11 @@ public:
DirectTensorStoreTest() : store() {}
virtual ~DirectTensorStoreTest() {
- store.clearHoldLists();
+ store.reclaim_all_memory();
}
void expect_tensor(const Value* exp, EntryRef ref) {
- const auto* act = store.get_tensor(ref);
+ const auto* act = store.get_tensor_ptr(ref);
ASSERT_TRUE(act);
EXPECT_EQ(exp, act);
}
@@ -81,7 +81,7 @@ TEST_F(DirectTensorStoreTest, heap_allocated_memory_is_tracked)
store.store_tensor(make_tensor(5));
auto mem_1 = store.getMemoryUsage();
auto ref = store.store_tensor(make_tensor(10));
- auto tensor_mem_usage = store.get_tensor(ref)->get_memory_usage();
+ auto tensor_mem_usage = store.get_tensor_ptr(ref)->get_memory_usage();
auto mem_2 = store.getMemoryUsage();
EXPECT_GT(tensor_mem_usage.usedBytes(), 500);
EXPECT_LT(tensor_mem_usage.usedBytes(), 50000);
@@ -93,21 +93,21 @@ TEST_F(DirectTensorStoreTest, heap_allocated_memory_is_tracked)
TEST_F(DirectTensorStoreTest, invalid_ref_returns_nullptr)
{
- const auto* t = store.get_tensor(EntryRef());
+ const auto* t = store.get_tensor_ptr(EntryRef());
EXPECT_FALSE(t);
}
TEST_F(DirectTensorStoreTest, hold_adds_entry_to_hold_list)
{
auto ref = store.store_tensor(make_tensor(5));
- auto tensor_mem_usage = store.get_tensor(ref)->get_memory_usage();
+ auto tensor_mem_usage = store.get_tensor_ptr(ref)->get_memory_usage();
auto mem_1 = store.getMemoryUsage();
store.holdTensor(ref);
auto mem_2 = store.getMemoryUsage();
EXPECT_GT(mem_2.allocatedBytesOnHold(), mem_1.allocatedBytesOnHold() + tensor_mem_usage.allocatedBytes());
}
-TEST_F(DirectTensorStoreTest, move_allocates_new_entry_and_puts_old_entry_on_hold)
+TEST_F(DirectTensorStoreTest, move_on_compact_allocates_new_entry_and_leaves_old_entry_alone)
{
auto t = make_tensor(5);
auto* exp = t.get();
@@ -115,12 +115,13 @@ TEST_F(DirectTensorStoreTest, move_allocates_new_entry_and_puts_old_entry_on_hol
auto ref_1 = store.store_tensor(std::move(t));
auto mem_1 = store.getMemoryUsage();
- auto ref_2 = store.move(ref_1);
+ auto ref_2 = store.move_on_compact(ref_1);
auto mem_2 = store.getMemoryUsage();
EXPECT_NE(ref_1, ref_2);
expect_tensor(exp, ref_1);
expect_tensor(exp, ref_2);
- EXPECT_GT(mem_2.allocatedBytesOnHold(), mem_1.allocatedBytesOnHold() + tensor_mem_usage.allocatedBytes());
+ EXPECT_EQ(0, mem_2.allocatedBytesOnHold());
+ EXPECT_GT(mem_2.usedBytes(), mem_1.usedBytes() + tensor_mem_usage.allocatedBytes());
}
GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp b/searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp
index 7877b488065..9fccad1d2d4 100644
--- a/searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp
+++ b/searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp
@@ -99,10 +99,9 @@ public:
commit();
}
void commit() {
- index->transfer_hold_lists(gen_handler.getCurrentGeneration());
+ index->assign_generation(gen_handler.getCurrentGeneration());
gen_handler.incGeneration();
- gen_handler.updateFirstUsedGeneration();
- index->trim_hold_lists(gen_handler.getFirstUsedGeneration());
+ index->reclaim_memory(gen_handler.get_oldest_used_generation());
}
void set_filter(std::vector<uint32_t> docids) {
uint32_t sz = 10;
diff --git a/searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp b/searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp
index d559fa592ad..81b56909d57 100644
--- a/searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp
+++ b/searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp
@@ -267,10 +267,9 @@ public:
ASSERT_EQ(r.get(), nullptr);
}
void commit(uint32_t docid) {
- index->transfer_hold_lists(gen_handler.getCurrentGeneration());
+ index->assign_generation(gen_handler.getCurrentGeneration());
gen_handler.incGeneration();
- gen_handler.updateFirstUsedGeneration();
- index->trim_hold_lists(gen_handler.getFirstUsedGeneration());
+ index->reclaim_memory(gen_handler.get_oldest_used_generation());
std::lock_guard<std::mutex> guard(in_progress_lock);
in_progress->clearBit(docid);
// printf("commit: %u\n", docid);
diff --git a/searchlib/src/tests/tensor/tensor_buffer_operations/tensor_buffer_operations_test.cpp b/searchlib/src/tests/tensor/tensor_buffer_operations/tensor_buffer_operations_test.cpp
index 55598a1b11f..bda229f8074 100644
--- a/searchlib/src/tests/tensor/tensor_buffer_operations/tensor_buffer_operations_test.cpp
+++ b/searchlib/src/tests/tensor/tensor_buffer_operations/tensor_buffer_operations_test.cpp
@@ -137,10 +137,9 @@ TensorBufferOperationsTest::assert_store_copy_load(const TensorSpec& tensor_spec
{
auto buf = store_tensor(tensor_spec);
auto buf2 = buf;
- _ops.copied_labels(buf2);
- EXPECT_EQ(buf, buf2);
- _ops.reclaim_labels(buf);
+ _ops.copied_labels(buf);
EXPECT_NE(buf, buf2);
+ _ops.reclaim_labels(buf);
buf.clear();
auto loaded_spec = load_tensor_spec(buf2);
_ops.reclaim_labels(buf2);
diff --git a/searchlib/src/tests/tensor/tensor_buffer_store/tensor_buffer_store_test.cpp b/searchlib/src/tests/tensor/tensor_buffer_store/tensor_buffer_store_test.cpp
index 101b84e01aa..3bbb6cd334e 100644
--- a/searchlib/src/tests/tensor/tensor_buffer_store/tensor_buffer_store_test.cpp
+++ b/searchlib/src/tests/tensor/tensor_buffer_store/tensor_buffer_store_test.cpp
@@ -29,7 +29,7 @@ protected:
vespalib::nbostream encode_stored_tensor(EntryRef ref);
void assert_store_load(const TensorSpec& tensor_spec);
void assert_store_load_many(const TensorSpec& tensor_spec);
- void assert_store_move_load(const TensorSpec& tensor_spec);
+ void assert_store_move_on_compact_load(const TensorSpec& tensor_spec);
void assert_store_encode_store_encoded_load(const TensorSpec& tensor_spec);
};
@@ -102,10 +102,10 @@ TensorBufferStoreTest::assert_store_load_many(const TensorSpec& tensor_spec)
}
void
-TensorBufferStoreTest::assert_store_move_load(const TensorSpec& tensor_spec)
+TensorBufferStoreTest::assert_store_move_on_compact_load(const TensorSpec& tensor_spec)
{
auto ref = store_tensor(tensor_spec);
- auto ref2 = _store.move(ref);
+ auto ref2 = _store.move_on_compact(ref);
EXPECT_NE(ref, ref2);
auto loaded_spec = load_tensor_spec(ref2);
_store.holdTensor(ref2);
@@ -147,10 +147,10 @@ TEST_F(TensorBufferStoreTest, tensor_can_be_stored_and_loaded_many_times)
}
}
-TEST_F(TensorBufferStoreTest, stored_tensor_can_be_copied)
+TEST_F(TensorBufferStoreTest, stored_tensor_can_be_moved_on_compact)
{
for (auto& tensor_spec : tensor_specs) {
- assert_store_move_load(tensor_spec);
+ assert_store_move_on_compact_load(tensor_spec);
}
}
diff --git a/searchlib/src/tests/tensor/tensor_buffer_type_mapper/CMakeLists.txt b/searchlib/src/tests/tensor/tensor_buffer_type_mapper/CMakeLists.txt
new file mode 100644
index 00000000000..e219b17ebd1
--- /dev/null
+++ b/searchlib/src/tests/tensor/tensor_buffer_type_mapper/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_tensor_buffer_type_mapper_test_app TEST
+ SOURCES
+ tensor_buffer_type_mapper_test.cpp
+ DEPENDS
+ searchlib
+ GTest::GTest
+)
+vespa_add_test(NAME searchlib_tensor_buffer_type_mapper_test_app COMMAND searchlib_tensor_buffer_type_mapper_test_app)
diff --git a/searchlib/src/tests/tensor/tensor_buffer_type_mapper/tensor_buffer_type_mapper_test.cpp b/searchlib/src/tests/tensor/tensor_buffer_type_mapper/tensor_buffer_type_mapper_test.cpp
new file mode 100644
index 00000000000..8e88c103516
--- /dev/null
+++ b/searchlib/src/tests/tensor/tensor_buffer_type_mapper/tensor_buffer_type_mapper_test.cpp
@@ -0,0 +1,121 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/searchlib/tensor/tensor_buffer_type_mapper.h>
+#include <vespa/searchlib/tensor/tensor_buffer_operations.h>
+#include <vespa/eval/eval/value_type.h>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using search::tensor::TensorBufferOperations;
+using search::tensor::TensorBufferTypeMapper;
+using vespalib::eval::ValueType;
+
+const vespalib::string tensor_type_sparse_spec("tensor(x{})");
+const vespalib::string tensor_type_2d_spec("tensor(x{},y{})");
+const vespalib::string tensor_type_2d_mixed_spec("tensor(x{},y[2])");
+const vespalib::string float_tensor_type_spec("tensor<float>(y{})");
+const vespalib::string tensor_type_dense_spec("tensor(x[2])");
+
+struct TestParam
+{
+ vespalib::string _name;
+ std::vector<size_t> _array_sizes;
+ vespalib::string _tensor_type_spec;
+ TestParam(vespalib::string name, std::vector<size_t> array_sizes, const vespalib::string& tensor_type_spec)
+ : _name(std::move(name)),
+ _array_sizes(std::move(array_sizes)),
+ _tensor_type_spec(tensor_type_spec)
+ {
+ }
+ TestParam(const TestParam&);
+ ~TestParam();
+};
+
+TestParam::TestParam(const TestParam&) = default;
+
+TestParam::~TestParam() = default;
+
+std::ostream& operator<<(std::ostream& os, const TestParam& param)
+{
+ os << param._name;
+ return os;
+}
+
+class TensorBufferTypeMapperTest : public testing::TestWithParam<TestParam>
+{
+protected:
+ ValueType _tensor_type;
+ TensorBufferOperations _ops;
+ TensorBufferTypeMapper _mapper;
+ TensorBufferTypeMapperTest();
+ ~TensorBufferTypeMapperTest() override;
+ std::vector<size_t> get_array_sizes();
+ void select_type_ids();
+};
+
+TensorBufferTypeMapperTest::TensorBufferTypeMapperTest()
+ : testing::TestWithParam<TestParam>(),
+ _tensor_type(ValueType::from_spec(GetParam()._tensor_type_spec)),
+ _ops(_tensor_type),
+ _mapper(GetParam()._array_sizes.size(), &_ops)
+{
+}
+
+TensorBufferTypeMapperTest::~TensorBufferTypeMapperTest() = default;
+
+std::vector<size_t>
+TensorBufferTypeMapperTest::get_array_sizes()
+{
+ uint32_t max_small_subspaces_type_id = GetParam()._array_sizes.size();
+ std::vector<size_t> array_sizes;
+ for (uint32_t type_id = 1; type_id <= max_small_subspaces_type_id; ++type_id) {
+ auto num_subspaces = type_id - 1;
+ array_sizes.emplace_back(_mapper.get_array_size(type_id));
+ EXPECT_EQ(_ops.get_array_size(num_subspaces), array_sizes.back());
+ }
+ return array_sizes;
+}
+
+void
+TensorBufferTypeMapperTest::select_type_ids()
+{
+ auto& array_sizes = GetParam()._array_sizes;
+ uint32_t type_id = 0;
+ for (auto array_size : array_sizes) {
+ ++type_id;
+ EXPECT_EQ(type_id, _mapper.get_type_id(array_size));
+ EXPECT_EQ(type_id, _mapper.get_type_id(array_size - 1));
+ if (array_size == array_sizes.back()) {
+ // Fallback to indirect storage, using type id 0
+ EXPECT_EQ(0u, _mapper.get_type_id(array_size + 1));
+ } else {
+ EXPECT_EQ(type_id + 1, _mapper.get_type_id(array_size + 1));
+ }
+ }
+}
+
+/*
+ * For "dense" case, array size for type id 1 is irrelevant, since
+ * type ids 0 and 1 are not used when storing dense tensors in
+ * TensorBufferStore.
+ */
+
+VESPA_GTEST_INSTANTIATE_TEST_SUITE_P(TensorBufferTypeMapperMultiTest,
+ TensorBufferTypeMapperTest,
+ testing::Values(TestParam("1d", {8, 16, 32, 40, 64}, tensor_type_sparse_spec),
+ TestParam("1dfloat", {4, 12, 20, 28, 36}, float_tensor_type_spec),
+ TestParam("2d", {8, 24, 40, 56, 80}, tensor_type_2d_spec),
+ TestParam("2dmixed", {8, 24, 48, 64, 96}, tensor_type_2d_mixed_spec),
+ TestParam("dense", {8, 24}, tensor_type_dense_spec)),
+ testing::PrintToStringParamName());
+
+TEST_P(TensorBufferTypeMapperTest, array_sizes_are_calculated)
+{
+ EXPECT_EQ(GetParam()._array_sizes, get_array_sizes());
+}
+
+TEST_P(TensorBufferTypeMapperTest, type_ids_are_selected)
+{
+ select_type_ids();
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchlib/src/tests/test/string_field_builder/CMakeLists.txt b/searchlib/src/tests/test/string_field_builder/CMakeLists.txt
new file mode 100644
index 00000000000..6cd9c5e36f1
--- /dev/null
+++ b/searchlib/src/tests/test/string_field_builder/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_string_field_builder_test_app TEST
+ SOURCES
+ string_field_builder_test.cpp
+ DEPENDS
+ searchlib_test
+ GTest::GTest
+)
+vespa_add_test(NAME searchlib_string_field_builder_test_app COMMAND searchlib_string_field_builder_test_app)
diff --git a/searchlib/src/tests/test/string_field_builder/string_field_builder_test.cpp b/searchlib/src/tests/test/string_field_builder/string_field_builder_test.cpp
new file mode 100644
index 00000000000..9d886e6cde7
--- /dev/null
+++ b/searchlib/src/tests/test/string_field_builder/string_field_builder_test.cpp
@@ -0,0 +1,141 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/searchlib/test/string_field_builder.h>
+#include <vespa/document/annotation/annotation.h>
+#include <vespa/document/annotation/span.h>
+#include <vespa/document/annotation/spanlist.h>
+#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/datatype/annotationtype.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/searchlib/test/doc_builder.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <cassert>
+#include <iostream>
+
+using document::Annotation;
+using document::AnnotationType;
+using document::Span;
+using document::SpanNode;
+using document::SpanTree;
+using document::StringFieldValue;
+using search::test::DocBuilder;
+using search::test::StringFieldBuilder;
+
+namespace
+{
+
+const vespalib::string SPANTREE_NAME("linguistics");
+
+struct MyAnnotation {
+ int32_t start;
+ int32_t length;
+ std::optional<vespalib::string> label;
+
+ MyAnnotation(int32_t start_in, int32_t length_in) noexcept
+ : start(start_in),
+ length(length_in),
+ label()
+ {
+ }
+
+ MyAnnotation(int32_t start_in, int32_t length_in, vespalib::string label_in) noexcept
+ : start(start_in),
+ length(length_in),
+ label(label_in)
+ {
+ }
+
+ bool operator==(const MyAnnotation& rhs) const noexcept;
+};
+
+bool
+MyAnnotation::operator==(const MyAnnotation& rhs) const noexcept
+{
+ return start == rhs.start &&
+ length == rhs.length &&
+ label == rhs.label;
+}
+
+
+std::ostream& operator<<(std::ostream& os, const MyAnnotation& ann) {
+ os << "[" << ann.start << "," << ann.length << "]";
+ if (ann.label.has_value()) {
+ os << "(\"" << ann.label.value() << "\")";
+ }
+ return os;
+}
+
+}
+
+class StringFieldBuilderTest : public testing::Test
+{
+protected:
+ DocBuilder db;
+ StringFieldBuilder sfb;
+ StringFieldBuilderTest();
+ ~StringFieldBuilderTest();
+ std::vector<MyAnnotation> get_annotations(const StringFieldValue& val);
+ void assert_annotations(std::vector<MyAnnotation> exp, const vespalib::string& plain, const StringFieldValue& val);
+};
+
+StringFieldBuilderTest::StringFieldBuilderTest()
+ : testing::Test(),
+ db(),
+ sfb(db)
+{
+}
+
+StringFieldBuilderTest::~StringFieldBuilderTest() = default;
+
+std::vector<MyAnnotation>
+StringFieldBuilderTest::get_annotations(const StringFieldValue& val)
+{
+ std::vector<MyAnnotation> result;
+ StringFieldValue::SpanTrees trees = val.getSpanTrees();
+ const auto* tree = StringFieldValue::findTree(trees, SPANTREE_NAME);
+ if (tree != nullptr) {
+ for (auto& ann : *tree) {
+ assert(ann.getType() == *AnnotationType::TERM);
+ auto span = dynamic_cast<const Span *>(ann.getSpanNode());
+ if (span == nullptr) {
+ continue;
+ }
+ auto ann_fv = ann.getFieldValue();
+ if (ann_fv == nullptr) {
+ result.emplace_back(span->from(), span->length());
+ } else {
+ result.emplace_back(span->from(), span->length(), dynamic_cast<const StringFieldValue &>(*ann_fv).getValue());
+ }
+ }
+ }
+ return result;
+}
+
+void
+StringFieldBuilderTest::assert_annotations(std::vector<MyAnnotation> exp, const vespalib::string& plain, const StringFieldValue& val)
+{
+ EXPECT_EQ(exp, get_annotations(val));
+ EXPECT_EQ(plain, val.getValue());
+}
+
+TEST_F(StringFieldBuilderTest, no_annotations)
+{
+ assert_annotations({}, "foo", StringFieldValue("foo"));
+}
+
+TEST_F(StringFieldBuilderTest, single_word)
+{
+ assert_annotations({{0, 4}}, "word", sfb.word("word").build());
+}
+
+TEST_F(StringFieldBuilderTest, tokenize)
+{
+ assert_annotations({{0, 4}, {5, 2}, {8, 1}, {10, 4}}, "this is a test", sfb.tokenize("this is a test").build());
+}
+
+TEST_F(StringFieldBuilderTest, alt_word)
+{
+ assert_annotations({{0, 3}, {4, 3}, {4, 3, "baz"}}, "foo bar", sfb.word("foo").space().word("bar").alt_word("baz").build());
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchlib/src/vespa/searchcommon/common/growstrategy.h b/searchlib/src/vespa/searchcommon/common/growstrategy.h
index bc04047aa3c..8766989ded0 100644
--- a/searchlib/src/vespa/searchcommon/common/growstrategy.h
+++ b/searchlib/src/vespa/searchcommon/common/growstrategy.h
@@ -24,7 +24,7 @@ public:
}
static GrowStrategy make(uint32_t docsInitialCapacity, float docsGrowFactor, uint32_t docsGrowDelta) {
- return GrowStrategy(docsInitialCapacity, docsGrowFactor, docsGrowDelta, 0, 0.2);
+ return {docsInitialCapacity, docsGrowFactor, docsGrowDelta, 0, 0.2};
}
float getMultiValueAllocGrowFactor() const { return _multiValueAllocGrowFactor; }
diff --git a/searchlib/src/vespa/searchcommon/common/iblobconverter.h b/searchlib/src/vespa/searchcommon/common/iblobconverter.h
index 6581c3e5ccb..4cb79a2547c 100644
--- a/searchlib/src/vespa/searchcommon/common/iblobconverter.h
+++ b/searchlib/src/vespa/searchcommon/common/iblobconverter.h
@@ -13,7 +13,7 @@ public:
using SP = std::shared_ptr<BlobConverter>;
using UP = std::unique_ptr<BlobConverter>;
using ConstBufferRef = vespalib::ConstBufferRef;
- virtual ~BlobConverter() { }
+ virtual ~BlobConverter() = default;
ConstBufferRef convert(const ConstBufferRef & src) const { return onConvert(src); }
private:
virtual ConstBufferRef onConvert(const ConstBufferRef & src) const = 0;
diff --git a/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt b/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
index 5e86c350b55..704db67aa03 100644
--- a/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
@@ -73,6 +73,7 @@ vespa_add_library(searchlib_attribute OBJECT
loadedenumvalue.cpp
loadednumericvalue.cpp
loadedvalue.cpp
+ multi_enum_search_context.cpp
multi_numeric_enum_search_context.cpp
multi_numeric_flag_search_context.cpp
multi_numeric_search_context.cpp
@@ -119,6 +120,7 @@ vespa_add_library(searchlib_attribute OBJECT
singlesmallnumericattribute.cpp
singlestringattribute.cpp
singlestringpostattribute.cpp
+ single_enum_search_context.cpp
single_numeric_enum_search_context.cpp
single_numeric_search_context.cpp
single_small_numeric_search_context.cpp
diff --git a/searchlib/src/vespa/searchlib/attribute/attributevector.cpp b/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
index dd7af1e8c4b..7f8f3f92f9e 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
@@ -64,7 +64,7 @@ allow_paged(const search::attribute::Config& config)
return false;
}
if (config.basicType() == Type::TENSOR) {
- return (!config.tensorType().is_error() && config.tensorType().is_dense());
+ return (!config.tensorType().is_error() && (config.tensorType().is_dense() || !config.fastSearch()));
}
return true;
}
@@ -184,10 +184,10 @@ void
AttributeVector::incGeneration()
{
// Freeze trees etc, to stop new readers from accessing currently held data
- onGenerationChange(_genHandler.getNextGeneration());
+ before_inc_generation(_genHandler.getCurrentGeneration());
_genHandler.incGeneration();
// Remove old data on hold lists that can no longer be reached by readers
- removeAllOldGenerations();
+ reclaim_unused_memory();
}
void
@@ -237,8 +237,8 @@ AttributeVector::headerTypeOK(const vespalib::GenericHeader &header) const
getConfig().collectionType().asString();
}
-void AttributeVector::removeOldGenerations(generation_t firstUsed) { (void) firstUsed; }
-void AttributeVector::onGenerationChange(generation_t generation) { (void) generation; }
+void AttributeVector::reclaim_memory(generation_t oldest_used_gen) { (void) oldest_used_gen; }
+void AttributeVector::before_inc_generation(generation_t current_gen) { (void) current_gen; }
const IEnumStore* AttributeVector::getEnumStoreBase() const { return nullptr; }
IEnumStore* AttributeVector::getEnumStoreBase() { return nullptr; }
const attribute::MultiValueMappingBase * AttributeVector::getMultiValueBase() const { return nullptr; }
@@ -408,9 +408,9 @@ bool AttributeVector::applyWeight(DocId, const FieldValue &, const ArithmeticVal
bool AttributeVector::applyWeight(DocId, const FieldValue&, const AssignValueUpdate&) { return false; }
void
-AttributeVector::removeAllOldGenerations() {
- _genHandler.updateFirstUsedGeneration();
- removeOldGenerations(_genHandler.getFirstUsedGeneration());
+AttributeVector::reclaim_unused_memory() {
+ _genHandler.update_oldest_used_generation();
+ reclaim_memory(_genHandler.get_oldest_used_generation());
}
@@ -483,19 +483,17 @@ AttributeVector::compactLidSpace(uint32_t wantedLidLimit) {
incGeneration();
}
-
bool
AttributeVector::canShrinkLidSpace() const {
return wantShrinkLidSpace() &&
- _compactLidSpaceGeneration.load(std::memory_order_relaxed) < getFirstUsedGeneration();
+ _compactLidSpaceGeneration.load(std::memory_order_relaxed) < get_oldest_used_generation();
}
-
void
AttributeVector::shrinkLidSpace()
{
commit();
- removeAllOldGenerations();
+ reclaim_unused_memory();
if (!canShrinkLidSpace()) {
return;
}
@@ -717,7 +715,7 @@ AttributeVector::drain_hold(uint64_t hold_limit)
{
incGeneration();
for (int retry = 0; retry < 40; ++retry) {
- removeAllOldGenerations();
+ reclaim_unused_memory();
updateStat(true);
if (_status.getOnHold() <= hold_limit) {
return;
diff --git a/searchlib/src/vespa/searchlib/attribute/attributevector.h b/searchlib/src/vespa/searchlib/attribute/attributevector.h
index f245a216aeb..261290247ad 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributevector.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributevector.h
@@ -156,10 +156,10 @@ protected:
public:
void incGeneration();
- void removeAllOldGenerations();
+ void reclaim_unused_memory();
- generation_t getFirstUsedGeneration() const {
- return _genHandler.getFirstUsedGeneration();
+ generation_t get_oldest_used_generation() const {
+ return _genHandler.get_oldest_used_generation();
}
generation_t getCurrentGeneration() const {
@@ -446,8 +446,8 @@ private:
GenerationHandler::Guard takeGenerationGuard() { return _genHandler.takeGuard(); }
/// Clean up [0, firstUsed>
- virtual void removeOldGenerations(generation_t firstUsed);
- virtual void onGenerationChange(generation_t generation);
+ virtual void reclaim_memory(generation_t oldest_used_gen);
+ virtual void before_inc_generation(generation_t current_gen);
virtual void onUpdateStat() = 0;
/**
* Used to regulate access to critical resources. Apply the
@@ -466,8 +466,8 @@ public:
/**
* Should be called by the writer thread.
*/
- void updateFirstUsedGeneration() {
- _genHandler.updateFirstUsedGeneration();
+ void update_oldest_used_generation() {
+ _genHandler.update_oldest_used_generation();
}
/**
diff --git a/searchlib/src/vespa/searchlib/attribute/attrvector.cpp b/searchlib/src/vespa/searchlib/attribute/attrvector.cpp
index d5ef41243e7..4a0bdafae8f 100644
--- a/searchlib/src/vespa/searchlib/attribute/attrvector.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attrvector.cpp
@@ -203,4 +203,7 @@ bool StringDirectAttribute::addDoc(DocId & doc)
return false;
}
+template class NumericDirectAttribute<IntegerAttributeTemplate<int64_t>>;
+template class NumericDirectAttribute<FloatingPointAttributeTemplate<double>>;
+
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/attribute/enumstore.h b/searchlib/src/vespa/searchlib/attribute/enumstore.h
index 52f42ed368e..0a0b2040b2a 100644
--- a/searchlib/src/vespa/searchlib/attribute/enumstore.h
+++ b/searchlib/src/vespa/searchlib/attribute/enumstore.h
@@ -96,8 +96,8 @@ public:
vespalib::AddressSpace get_values_address_space_usage() const override;
- void transfer_hold_lists(generation_t generation);
- void trim_hold_lists(generation_t first_used);
+ void assign_generation(generation_t current_gen);
+ void reclaim_memory(generation_t first_used);
ssize_t load_unique_values(const void* src, size_t available, IndexVector& idx) override;
diff --git a/searchlib/src/vespa/searchlib/attribute/enumstore.hpp b/searchlib/src/vespa/searchlib/attribute/enumstore.hpp
index 1ef194f6812..b863e56fb4a 100644
--- a/searchlib/src/vespa/searchlib/attribute/enumstore.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/enumstore.hpp
@@ -104,17 +104,17 @@ EnumStoreT<EntryT>::get_values_address_space_usage() const
template <typename EntryT>
void
-EnumStoreT<EntryT>::transfer_hold_lists(generation_t generation)
+EnumStoreT<EntryT>::assign_generation(generation_t current_gen)
{
- _store.transferHoldLists(generation);
+ _store.assign_generation(current_gen);
}
template <typename EntryT>
void
-EnumStoreT<EntryT>::trim_hold_lists(generation_t firstUsed)
+EnumStoreT<EntryT>::reclaim_memory(generation_t oldest_used_gen)
{
// remove generations in the range [0, firstUsed>
- _store.trimHoldLists(firstUsed);
+ _store.reclaim_memory(oldest_used_gen);
}
template <typename EntryT>
diff --git a/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp b/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
index ef796d3f3d2..f8cf742bdb2 100644
--- a/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
@@ -223,16 +223,16 @@ FlagAttributeT<B>::resizeBitVectors(uint32_t neededSize)
}
}
_bitVectorSize = newSize;
- _bitVectorHolder.transferHoldLists(this->getCurrentGeneration());
+ _bitVectorHolder.assign_generation(this->getCurrentGeneration());
}
template <typename B>
void
-FlagAttributeT<B>::removeOldGenerations(vespalib::GenerationHandler::generation_t firstUsed)
+FlagAttributeT<B>::reclaim_memory(vespalib::GenerationHandler::generation_t oldest_used_gen)
{
- B::removeOldGenerations(firstUsed);
- _bitVectorHolder.trimHoldLists(firstUsed);
+ B::reclaim_memory(oldest_used_gen);
+ _bitVectorHolder.reclaim(oldest_used_gen);
}
template class FlagAttributeT<FlagBaseImpl>;
diff --git a/searchlib/src/vespa/searchlib/attribute/flagattribute.h b/searchlib/src/vespa/searchlib/attribute/flagattribute.h
index 796c1493cc9..df75e7afa04 100644
--- a/searchlib/src/vespa/searchlib/attribute/flagattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/flagattribute.h
@@ -33,7 +33,7 @@ private:
void ensureGuardBit();
void clearGuardBit(DocId doc);
void resizeBitVectors(uint32_t neededSize);
- void removeOldGenerations(vespalib::GenerationHandler::generation_t firstUsed) override;
+ void reclaim_memory(vespalib::GenerationHandler::generation_t oldest_used_gen) override;
uint32_t getOffset(int8_t value) const { return value + 128; }
using AtomicBitVectorPtr = vespalib::datastore::AtomicValueWrapper<BitVector *>;
diff --git a/searchlib/src/vespa/searchlib/attribute/load_utils.hpp b/searchlib/src/vespa/searchlib/attribute/load_utils.hpp
index 62d645326ce..463a62ab01a 100644
--- a/searchlib/src/vespa/searchlib/attribute/load_utils.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/load_utils.hpp
@@ -68,7 +68,7 @@ loadFromEnumeratedSingleValue(Vector &vector,
using ValueType = typename Vector::ValueType;
using NonAtomicValueType = atomic_utils::NonAtomicValue_t<ValueType>;
uint32_t numDocs = attrReader.getEnumCount();
- genHolder.clearHoldLists();
+ genHolder.reclaim_all();
vector.reset();
vector.unsafe_reserve(numDocs);
for (uint32_t doc = 0; doc < numDocs; ++doc) {
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.cpp b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.cpp
new file mode 100644
index 00000000000..566d8e37d89
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.cpp
@@ -0,0 +1,15 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "multi_enum_search_context.hpp"
+#include "string_search_context.h"
+
+using ValueRef = vespalib::datastore::AtomicEntryRef;
+using WeightedValueRef = search::multivalue::WeightedValue<vespalib::datastore::AtomicEntryRef>;
+
+namespace search::attribute {
+
+template class MultiEnumSearchContext<const char *, StringSearchContext, ValueRef>;
+
+template class MultiEnumSearchContext<const char *, StringSearchContext, WeightedValueRef>;
+
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
index 61798959f3e..98587baadd2 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
@@ -48,8 +48,8 @@ public:
*/
ReadView make_read_view(size_t read_size) const { return ReadView(_indices.make_read_view(read_size), &_store); }
// Pass on hold list management to underlying store
- void transferHoldLists(generation_t generation) { _store.transferHoldLists(generation); }
- void trimHoldLists(generation_t firstUsed) { _store.trimHoldLists(firstUsed); }
+ void assign_generation(generation_t current_gen) { _store.assign_generation(current_gen); }
+ void reclaim_memory(generation_t oldest_used_gen) { _store.reclaim_memory(oldest_used_gen); }
void prepareLoadFromMultiValue() { _store.setInitializing(true); }
void doneLoadFromMultiValue() { _store.setInitializing(false); }
diff --git a/searchlib/src/vespa/searchlib/attribute/multienumattribute.h b/searchlib/src/vespa/searchlib/attribute/multienumattribute.h
index ee8f3181fd9..a073060afc5 100644
--- a/searchlib/src/vespa/searchlib/attribute/multienumattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/multienumattribute.h
@@ -63,8 +63,8 @@ public:
void onCommit() override;
void onUpdateStat() override;
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
//-----------------------------------------------------------------------------------------------------------------
// Attribute read API
diff --git a/searchlib/src/vespa/searchlib/attribute/multienumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multienumattribute.hpp
index ec948882312..e4e451ffcda 100644
--- a/searchlib/src/vespa/searchlib/attribute/multienumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multienumattribute.hpp
@@ -147,7 +147,7 @@ MultiValueEnumAttribute<B, M>::onCommit()
updater.commit();
this->freezeEnumDictionary();
std::atomic_thread_fence(std::memory_order_release);
- this->removeAllOldGenerations();
+ this->reclaim_unused_memory();
if (this->_mvMapping.considerCompact(this->getConfig().getCompactionStrategy())) {
this->incGeneration();
this->updateStat(true);
@@ -194,15 +194,15 @@ MultiValueEnumAttribute<B, M>::onUpdateStat()
template <typename B, typename M>
void
-MultiValueEnumAttribute<B, M>::removeOldGenerations(generation_t firstUsed)
+MultiValueEnumAttribute<B, M>::reclaim_memory(generation_t oldest_used_gen)
{
- this->_enumStore.trim_hold_lists(firstUsed);
- this->_mvMapping.trimHoldLists(firstUsed);
+ this->_enumStore.reclaim_memory(oldest_used_gen);
+ this->_mvMapping.reclaim_memory(oldest_used_gen);
}
template <typename B, typename M>
void
-MultiValueEnumAttribute<B, M>::onGenerationChange(generation_t generation)
+MultiValueEnumAttribute<B, M>::before_inc_generation(generation_t current_gen)
{
/*
* Freeze tree before generation is increased in attribute vector
@@ -211,8 +211,8 @@ MultiValueEnumAttribute<B, M>::onGenerationChange(generation_t generation)
* sufficiently new frozen tree.
*/
freezeEnumDictionary();
- this->_mvMapping.transferHoldLists(generation - 1);
- this->_enumStore.transfer_hold_lists(generation - 1);
+ this->_mvMapping.assign_generation(current_gen);
+ this->_enumStore.assign_generation(current_gen);
}
template <typename B, typename M>
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericattribute.h b/searchlib/src/vespa/searchlib/attribute/multinumericattribute.h
index 0a29b4af48d..ed78f7776f1 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericattribute.h
@@ -60,9 +60,9 @@ public:
uint32_t getValueCount(DocId doc) const override;
void onCommit() override;
void onUpdateStat() override;
- void removeOldGenerations(generation_t firstUsed) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
- void onGenerationChange(generation_t generation) override;
+ void before_inc_generation(generation_t current_gen) override;
bool onLoad(vespalib::Executor *executor) override;
virtual bool onLoadEnumerated(ReaderBase &attrReader);
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
index 8cabd8483bf..6dde909821e 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
@@ -63,7 +63,7 @@ MultiValueNumericAttribute<B, M>::onCommit()
}
std::atomic_thread_fence(std::memory_order_release);
- this->removeAllOldGenerations();
+ this->reclaim_unused_memory();
this->_changes.clear();
if (this->_mvMapping.considerCompact(this->getConfig().getCompactionStrategy())) {
@@ -96,16 +96,16 @@ void MultiValueNumericAttribute<B, M>::setNewValues(DocId doc, const std::vector
}
template <typename B, typename M>
-void MultiValueNumericAttribute<B, M>::removeOldGenerations(generation_t firstUsed)
+void MultiValueNumericAttribute<B, M>::reclaim_memory(generation_t oldest_used_gen)
{
- this->_mvMapping.trimHoldLists(firstUsed);
+ this->_mvMapping.reclaim_memory(oldest_used_gen);
}
template <typename B, typename M>
-void MultiValueNumericAttribute<B, M>::onGenerationChange(generation_t generation)
+void MultiValueNumericAttribute<B, M>::before_inc_generation(generation_t current_gen)
{
- this->_mvMapping.transferHoldLists(generation - 1);
+ this->_mvMapping.assign_generation(current_gen);
}
template <typename B, typename M>
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h
index 4bd8ad6e99f..a22a6241ab2 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h
@@ -80,8 +80,8 @@ public:
MultiValueNumericPostingAttribute(const vespalib::string & name, const AttributeVector::Config & cfg);
~MultiValueNumericPostingAttribute();
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
std::unique_ptr<attribute::SearchContext>
getSearch(QueryTermSimpleUP term, const attribute::SearchContextParams & params) const override;
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
index 9a8c9738bc0..deee72dcf39 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
@@ -56,19 +56,19 @@ MultiValueNumericPostingAttribute<B, M>::~MultiValueNumericPostingAttribute()
template <typename B, typename M>
void
-MultiValueNumericPostingAttribute<B, M>::removeOldGenerations(generation_t firstUsed)
+MultiValueNumericPostingAttribute<B, M>::reclaim_memory(generation_t oldest_used_gen)
{
- MultiValueNumericEnumAttribute<B, M>::removeOldGenerations(firstUsed);
- _postingList.trimHoldLists(firstUsed);
+ MultiValueNumericEnumAttribute<B, M>::reclaim_memory(oldest_used_gen);
+ _postingList.reclaim_memory(oldest_used_gen);
}
template <typename B, typename M>
void
-MultiValueNumericPostingAttribute<B, M>::onGenerationChange(generation_t generation)
+MultiValueNumericPostingAttribute<B, M>::before_inc_generation(generation_t current_gen)
{
_postingList.freeze();
- MultiValueNumericEnumAttribute<B, M>::onGenerationChange(generation);
- _postingList.transferHoldLists(generation - 1);
+ MultiValueNumericEnumAttribute<B, M>::before_inc_generation(current_gen);
+ _postingList.assign_generation(current_gen);
}
template <typename B, typename M>
diff --git a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h
index 4deb71e9759..2e355a9aed2 100644
--- a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h
@@ -77,8 +77,8 @@ public:
MultiValueStringPostingAttributeT(const vespalib::string & name);
~MultiValueStringPostingAttributeT();
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
std::unique_ptr<attribute::SearchContext>
getSearch(QueryTermSimpleUP term, const attribute::SearchContextParams & params) const override;
diff --git a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
index fef3db582c8..cfd00f84636 100644
--- a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
@@ -75,19 +75,19 @@ MultiValueStringPostingAttributeT<B, T>::mergeMemoryStats(vespalib::MemoryUsage
template <typename B, typename T>
void
-MultiValueStringPostingAttributeT<B, T>::removeOldGenerations(generation_t firstUsed)
+MultiValueStringPostingAttributeT<B, T>::reclaim_memory(generation_t oldest_used_gen)
{
- MultiValueStringAttributeT<B, T>::removeOldGenerations(firstUsed);
- _postingList.trimHoldLists(firstUsed);
+ MultiValueStringAttributeT<B, T>::reclaim_memory(oldest_used_gen);
+ _postingList.reclaim_memory(oldest_used_gen);
}
template <typename B, typename T>
void
-MultiValueStringPostingAttributeT<B, T>::onGenerationChange(generation_t generation)
+MultiValueStringPostingAttributeT<B, T>::before_inc_generation(generation_t current_gen)
{
_postingList.freeze();
- MultiValueStringAttributeT<B, T>::onGenerationChange(generation);
- _postingList.transferHoldLists(generation - 1);
+ MultiValueStringAttributeT<B, T>::before_inc_generation(current_gen);
+ _postingList.assign_generation(current_gen);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multivalueattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multivalueattribute.hpp
index 2066b6fa845..2456c57140c 100644
--- a/searchlib/src/vespa/searchlib/attribute/multivalueattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multivalueattribute.hpp
@@ -245,7 +245,7 @@ MultiValueAttribute<B, M>::addDoc(DocId & doc)
if (incGen) {
this->incGeneration();
} else
- this->removeAllOldGenerations();
+ this->reclaim_unused_memory();
return true;
}
diff --git a/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp b/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
index c1897c71366..f34099de758 100644
--- a/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
@@ -89,7 +89,7 @@ PredicateAttribute::PredicateAttribute(const vespalib::string &base_file_name, c
PredicateAttribute::~PredicateAttribute()
{
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
}
void PredicateAttribute::populateIfNeeded() {
@@ -118,24 +118,24 @@ PredicateAttribute::onUpdateStat()
combined.merge(_min_feature.getMemoryUsage());
combined.merge(_interval_range_vector.getMemoryUsage());
combined.merge(_index->getMemoryUsage());
- combined.mergeGenerationHeldBytes(getGenerationHolder().getHeldBytes());
+ combined.mergeGenerationHeldBytes(getGenerationHolder().get_held_bytes());
this->updateStatistics(_min_feature.size(), _min_feature.size(),
combined.allocatedBytes(), combined.usedBytes(),
combined.deadBytes(), combined.allocatedBytesOnHold());
}
void
-PredicateAttribute::removeOldGenerations(generation_t firstUsed)
+PredicateAttribute::reclaim_memory(generation_t oldest_used_gen)
{
- getGenerationHolder().trimHoldLists(firstUsed);
- _index->trimHoldLists(firstUsed);
+ getGenerationHolder().reclaim(oldest_used_gen);
+ _index->reclaim_memory(oldest_used_gen);
}
void
-PredicateAttribute::onGenerationChange(generation_t generation)
+PredicateAttribute::before_inc_generation(generation_t current_gen)
{
- getGenerationHolder().transferHoldLists(generation - 1);
- _index->transferHoldLists(generation - 1);
+ getGenerationHolder().assign_generation(current_gen);
+ _index->assign_generation(current_gen);
}
void
diff --git a/searchlib/src/vespa/searchlib/attribute/predicate_attribute.h b/searchlib/src/vespa/searchlib/attribute/predicate_attribute.h
index f5d789298a0..159e71e99e3 100644
--- a/searchlib/src/vespa/searchlib/attribute/predicate_attribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/predicate_attribute.h
@@ -48,8 +48,8 @@ public:
void onSave(IAttributeSaveTarget & saveTarget) override;
bool onLoad(vespalib::Executor *executor) override;
void onCommit() override;
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
void onUpdateStat() override;
bool addDoc(DocId &doc_id) override;
uint32_t clearDoc(DocId doc_id) override;
diff --git a/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp b/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
index 0ebef4af8b0..fcee60ddac5 100644
--- a/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
@@ -87,7 +87,7 @@ ReferenceAttribute::addDoc(DocId &doc)
if (incGen) {
incGeneration();
} else {
- removeAllOldGenerations();
+ reclaim_unused_memory();
}
return true;
}
@@ -161,21 +161,21 @@ ReferenceAttribute::clearDoc(DocId doc)
}
void
-ReferenceAttribute::removeOldGenerations(generation_t firstUsed)
+ReferenceAttribute::reclaim_memory(generation_t oldest_used_gen)
{
- _referenceMappings.trimHoldLists(firstUsed);
- _store.trimHoldLists(firstUsed);
- getGenerationHolder().trimHoldLists(firstUsed);
+ _referenceMappings.reclaim_memory(oldest_used_gen);
+ _store.reclaim_memory(oldest_used_gen);
+ getGenerationHolder().reclaim(oldest_used_gen);
}
void
-ReferenceAttribute::onGenerationChange(generation_t generation)
+ReferenceAttribute::before_inc_generation(generation_t current_gen)
{
_referenceMappings.freeze();
_store.freeze();
- _referenceMappings.transferHoldLists(generation - 1);
- _store.transferHoldLists(generation - 1);
- getGenerationHolder().transferHoldLists(generation - 1);
+ _referenceMappings.assign_generation(current_gen);
+ _store.assign_generation(current_gen);
+ getGenerationHolder().assign_generation(current_gen);
}
void
@@ -203,7 +203,7 @@ ReferenceAttribute::onUpdateStat()
_compaction_spec = ReferenceAttributeCompactionSpec(compaction_strategy.should_compact_memory(total),
compaction_strategy.should_compact_memory(dictionary_memory_usage));
total.merge(dictionary_memory_usage);
- total.mergeGenerationHeldBytes(getGenerationHolder().getHeldBytes());
+ total.mergeGenerationHeldBytes(getGenerationHolder().get_held_bytes());
total.merge(_indices.getMemoryUsage());
total.merge(_referenceMappings.getMemoryUsage());
updateStatistics(getTotalValueCount(), getUniqueValueCount(),
diff --git a/searchlib/src/vespa/searchlib/attribute/reference_attribute.h b/searchlib/src/vespa/searchlib/attribute/reference_attribute.h
index dc3e2ad729a..e0ae906eb23 100644
--- a/searchlib/src/vespa/searchlib/attribute/reference_attribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/reference_attribute.h
@@ -50,8 +50,8 @@ private:
ReferenceMappings _referenceMappings;
void onAddDocs(DocId docIdLimit) override;
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
void onCommit() override;
void onUpdateStat() override;
std::unique_ptr<AttributeSaver> onInitSave(vespalib::stringref fileName) override;
diff --git a/searchlib/src/vespa/searchlib/attribute/reference_mappings.h b/searchlib/src/vespa/searchlib/attribute/reference_mappings.h
index 2ccc164bf08..cf26b424208 100644
--- a/searchlib/src/vespa/searchlib/attribute/reference_mappings.h
+++ b/searchlib/src/vespa/searchlib/attribute/reference_mappings.h
@@ -59,9 +59,9 @@ public:
void clearMapping(const Reference &entry);
// Hold list management & freezing
- void trimHoldLists(generation_t usedGen) { _reverseMapping.trimHoldLists(usedGen); }
+ void reclaim_memory(generation_t oldest_used_gen) { _reverseMapping.reclaim_memory(oldest_used_gen); }
void freeze() { _reverseMapping.freeze(); }
- void transferHoldLists(generation_t generation) { _reverseMapping.transferHoldLists(generation); }
+ void assign_generation(generation_t current_gen) { _reverseMapping.assign_generation(current_gen); }
// Handle mapping changes
void notifyReferencedPut(const Reference &entry, uint32_t targetLid);
diff --git a/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.cpp b/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.cpp
new file mode 100644
index 00000000000..c7faeaba977
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.cpp
@@ -0,0 +1,18 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "single_enum_search_context.hpp"
+#include "string_search_context.h"
+#include "numeric_range_matcher.h"
+#include "numeric_search_context.h"
+
+namespace search::attribute {
+
+template class SingleEnumSearchContext<const char*, StringSearchContext>;
+template class SingleEnumSearchContext<int8_t, NumericSearchContext<NumericRangeMatcher<int8_t>>>;
+template class SingleEnumSearchContext<int16_t, NumericSearchContext<NumericRangeMatcher<int16_t>>>;
+template class SingleEnumSearchContext<int32_t, NumericSearchContext<NumericRangeMatcher<int32_t>>>;
+template class SingleEnumSearchContext<int64_t, NumericSearchContext<NumericRangeMatcher<int64_t>>>;
+template class SingleEnumSearchContext<float, NumericSearchContext<NumericRangeMatcher<float>>>;
+template class SingleEnumSearchContext<double, NumericSearchContext<NumericRangeMatcher<double>>>;
+
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp b/searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp
index 7574508517d..15fc819300c 100644
--- a/searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp
@@ -29,7 +29,7 @@ SingleBoolAttribute(const vespalib::string &baseFileName, const GrowStrategy & g
SingleBoolAttribute::~SingleBoolAttribute()
{
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
}
void
@@ -53,7 +53,7 @@ SingleBoolAttribute::addDoc(DocId & doc) {
incNumDocs();
doc = getNumDocs() - 1;
updateUncommittedDocIdLimit(doc);
- removeAllOldGenerations();
+ reclaim_unused_memory();
return true;
}
@@ -80,7 +80,7 @@ SingleBoolAttribute::onCommit() {
}
std::atomic_thread_fence(std::memory_order_release);
- removeAllOldGenerations();
+ reclaim_unused_memory();
_changes.clear();
}
@@ -95,7 +95,7 @@ SingleBoolAttribute::onUpdateStat() {
vespalib::MemoryUsage usage;
usage.setAllocatedBytes(_bv.writer().extraByteSize());
usage.setUsedBytes(_bv.writer().sizeBytes());
- usage.mergeGenerationHeldBytes(getGenerationHolder().getHeldBytes());
+ usage.mergeGenerationHeldBytes(getGenerationHolder().get_held_bytes());
usage.merge(this->getChangeVectorMemoryUsage());
this->updateStatistics(_bv.writer().size(), _bv.writer().size(), usage.allocatedBytes(), usage.usedBytes(),
usage.deadBytes(), usage.allocatedBytesOnHold());
@@ -191,7 +191,7 @@ SingleBoolAttribute::onLoad(vespalib::Executor *)
bool ok(attrReader.hasData());
if (ok) {
setCreateSerialNum(attrReader.getCreateSerialNum());
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
_bv.writer().clear();
uint32_t numDocs = attrReader.getNextData();
_bv.extend(numDocs);
@@ -257,13 +257,13 @@ SingleBoolAttribute::getEstimatedSaveByteSize() const
}
void
-SingleBoolAttribute::removeOldGenerations(generation_t firstUsed) {
- getGenerationHolder().trimHoldLists(firstUsed);
+SingleBoolAttribute::reclaim_memory(generation_t oldest_used_gen) {
+ getGenerationHolder().reclaim(oldest_used_gen);
}
void
-SingleBoolAttribute::onGenerationChange(generation_t generation) {
- getGenerationHolder().transferHoldLists(generation - 1);
+SingleBoolAttribute::before_inc_generation(generation_t current_gen) {
+ getGenerationHolder().assign_generation(current_gen);
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/singleboolattribute.h b/searchlib/src/vespa/searchlib/attribute/singleboolattribute.h
index 7868c228e77..a02d5c7d80d 100644
--- a/searchlib/src/vespa/searchlib/attribute/singleboolattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singleboolattribute.h
@@ -28,8 +28,8 @@ public:
void onSave(IAttributeSaveTarget &saveTarget) override;
void clearDocs(DocId lidLow, DocId lidLimit, bool in_shrink_lid_space) override;
void onShrinkLidSpace() override;
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
uint64_t getEstimatedSaveByteSize() const override;
std::unique_ptr<attribute::SearchContext>
diff --git a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.h b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.h
index 6e46c697fbc..dbf3e4e7c58 100644
--- a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.h
@@ -111,8 +111,8 @@ public:
uint32_t getValueCount(DocId doc) const override;
void onCommit() override;
void onUpdateStat() override;
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
EnumHandle getEnum(DocId doc) const override {
return getE(doc);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp
index c4abcfbc25a..52e2d914af1 100644
--- a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp
@@ -25,7 +25,7 @@ SingleValueEnumAttribute(const vespalib::string &baseFileName,
template <typename B>
SingleValueEnumAttribute<B>::~SingleValueEnumAttribute()
{
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
}
template <typename B>
@@ -66,7 +66,7 @@ SingleValueEnumAttribute<B>::addDoc(DocId & doc)
if (incGen) {
this->incGeneration();
} else
- this->removeAllOldGenerations();
+ this->reclaim_unused_memory();
return true;
}
@@ -95,7 +95,7 @@ SingleValueEnumAttribute<B>::onCommit()
updater.commit();
freezeEnumDictionary();
std::atomic_thread_fence(std::memory_order_release);
- this->removeAllOldGenerations();
+ this->reclaim_unused_memory();
auto remapper = this->_enumStore.consider_compact_values(this->getConfig().getCompactionStrategy());
if (remapper) {
remap_enum_store_refs(*remapper, *this);
@@ -128,7 +128,7 @@ SingleValueEnumAttribute<B>::onUpdateStat()
// update statistics
vespalib::MemoryUsage total = _enumIndices.getMemoryUsage();
auto& compaction_strategy = this->getConfig().getCompactionStrategy();
- total.mergeGenerationHeldBytes(getGenerationHolder().getHeldBytes());
+ total.mergeGenerationHeldBytes(getGenerationHolder().get_held_bytes());
total.merge(this->_enumStore.update_stat(compaction_strategy));
total.merge(this->getChangeVectorMemoryUsage());
mergeMemoryStats(total);
@@ -218,7 +218,7 @@ SingleValueEnumAttribute<B>::fillValues(LoadedVector & loaded)
{
if constexpr (!std::is_same_v<LoadedVector, NoLoadedVector>) {
uint32_t numDocs = this->getNumDocs();
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
_enumIndices.reset();
_enumIndices.unsafe_reserve(numDocs);
for (DocId doc = 0; doc < numDocs; ++doc, loaded.next()) {
@@ -264,15 +264,15 @@ SingleValueEnumAttribute<B>::load_enumerated_data(ReaderBase& attrReader,
template <typename B>
void
-SingleValueEnumAttribute<B>::removeOldGenerations(generation_t firstUsed)
+SingleValueEnumAttribute<B>::reclaim_memory(generation_t oldest_used_gen)
{
- this->_enumStore.trim_hold_lists(firstUsed);
- getGenerationHolder().trimHoldLists(firstUsed);
+ this->_enumStore.reclaim_memory(oldest_used_gen);
+ getGenerationHolder().reclaim(oldest_used_gen);
}
template <typename B>
void
-SingleValueEnumAttribute<B>::onGenerationChange(generation_t generation)
+SingleValueEnumAttribute<B>::before_inc_generation(generation_t current_gen)
{
/*
* Freeze tree before generation is increased in attribute vector
@@ -281,8 +281,8 @@ SingleValueEnumAttribute<B>::onGenerationChange(generation_t generation)
* sufficiently new frozen tree.
*/
freezeEnumDictionary();
- getGenerationHolder().transferHoldLists(generation - 1);
- this->_enumStore.transfer_hold_lists(generation - 1);
+ getGenerationHolder().assign_generation(current_gen);
+ this->_enumStore.assign_generation(current_gen);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.h b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.h
index fd2767eaee1..c6387323fea 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.h
@@ -55,8 +55,8 @@ public:
void onCommit() override;
void onAddDocs(DocId lidLimit) override;
void onUpdateStat() override;
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
bool addDoc(DocId & doc) override;
bool onLoad(vespalib::Executor *executor) override;
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
index b9c1c3686de..a105d980986 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
@@ -32,7 +32,7 @@ SingleValueNumericAttribute(const vespalib::string & baseFileName, const Attribu
template <typename B>
SingleValueNumericAttribute<B>::~SingleValueNumericAttribute()
{
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
}
template <typename B>
@@ -55,7 +55,7 @@ SingleValueNumericAttribute<B>::onCommit()
}
}
- this->removeAllOldGenerations();
+ this->reclaim_unused_memory();
this->_changes.clear();
}
@@ -65,7 +65,7 @@ void
SingleValueNumericAttribute<B>::onUpdateStat()
{
vespalib::MemoryUsage usage = _data.getMemoryUsage();
- usage.mergeGenerationHeldBytes(getGenerationHolder().getHeldBytes());
+ usage.mergeGenerationHeldBytes(getGenerationHolder().get_held_bytes());
usage.merge(this->getChangeVectorMemoryUsage());
this->updateStatistics(_data.size(), _data.size(),
usage.allocatedBytes(), usage.usedBytes(), usage.deadBytes(), usage.allocatedBytesOnHold());
@@ -89,22 +89,22 @@ SingleValueNumericAttribute<B>::addDoc(DocId & doc) {
if (incGen) {
this->incGeneration();
} else
- this->removeAllOldGenerations();
+ this->reclaim_unused_memory();
return true;
}
template <typename B>
void
-SingleValueNumericAttribute<B>::removeOldGenerations(generation_t firstUsed)
+SingleValueNumericAttribute<B>::reclaim_memory(generation_t oldest_used_gen)
{
- getGenerationHolder().trimHoldLists(firstUsed);
+ getGenerationHolder().reclaim(oldest_used_gen);
}
template <typename B>
void
-SingleValueNumericAttribute<B>::onGenerationChange(generation_t generation)
+SingleValueNumericAttribute<B>::before_inc_generation(generation_t current_gen)
{
- getGenerationHolder().transferHoldLists(generation - 1);
+ getGenerationHolder().assign_generation(current_gen);
}
template <typename B>
@@ -143,7 +143,7 @@ SingleValueNumericAttribute<B>::onLoad(vespalib::Executor *)
return onLoadEnumerated(attrReader);
const size_t sz(attrReader.getDataCount());
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
_data.reset();
_data.unsafe_reserve(sz);
for (uint32_t i = 0; i < sz; ++i) {
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h
index 720fb211e1a..f2343c1a57c 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h
@@ -69,8 +69,8 @@ public:
SingleValueNumericPostingAttribute(const vespalib::string & name, const AttributeVector::Config & cfg);
~SingleValueNumericPostingAttribute();
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
std::unique_ptr<attribute::SearchContext>
getSearch(QueryTermSimpleUP term, const attribute::SearchContextParams & params) const override;
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
index 2050f887c33..1775774171d 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
@@ -127,19 +127,19 @@ SingleValueNumericPostingAttribute<B>::applyValueChanges(EnumStoreBatchUpdater&
template <typename B>
void
-SingleValueNumericPostingAttribute<B>::removeOldGenerations(generation_t firstUsed)
+SingleValueNumericPostingAttribute<B>::reclaim_memory(generation_t oldest_used_gen)
{
- SingleValueNumericEnumAttribute<B>::removeOldGenerations(firstUsed);
- _postingList.trimHoldLists(firstUsed);
+ SingleValueNumericEnumAttribute<B>::reclaim_memory(oldest_used_gen);
+ _postingList.reclaim_memory(oldest_used_gen);
}
template <typename B>
void
-SingleValueNumericPostingAttribute<B>::onGenerationChange(generation_t generation)
+SingleValueNumericPostingAttribute<B>::before_inc_generation(generation_t current_gen)
{
_postingList.freeze();
- SingleValueNumericEnumAttribute<B>::onGenerationChange(generation);
- _postingList.transferHoldLists(generation - 1);
+ SingleValueNumericEnumAttribute<B>::before_inc_generation(current_gen);
+ _postingList.assign_generation(current_gen);
}
template <typename B>
diff --git a/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp b/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp
index b69ed017b52..13bf2f932e8 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp
@@ -36,7 +36,7 @@ SingleValueSmallNumericAttribute(const vespalib::string & baseFileName,
SingleValueSmallNumericAttribute::~SingleValueSmallNumericAttribute()
{
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
}
void
@@ -67,7 +67,7 @@ SingleValueSmallNumericAttribute::onCommit()
}
std::atomic_thread_fence(std::memory_order_release);
- removeAllOldGenerations();
+ reclaim_unused_memory();
_changes.clear();
}
@@ -84,7 +84,7 @@ SingleValueSmallNumericAttribute::addDoc(DocId & doc) {
if (incGen) {
this->incGeneration();
} else
- this->removeAllOldGenerations();
+ this->reclaim_unused_memory();
} else {
B::incNumDocs();
doc = B::getNumDocs() - 1;
@@ -97,7 +97,7 @@ void
SingleValueSmallNumericAttribute::onUpdateStat()
{
vespalib::MemoryUsage usage = _wordData.getMemoryUsage();
- usage.mergeGenerationHeldBytes(getGenerationHolder().getHeldBytes());
+ usage.mergeGenerationHeldBytes(getGenerationHolder().get_held_bytes());
uint32_t numDocs = B::getNumDocs();
updateStatistics(numDocs, numDocs,
usage.allocatedBytes(), usage.usedBytes(),
@@ -106,16 +106,16 @@ SingleValueSmallNumericAttribute::onUpdateStat()
void
-SingleValueSmallNumericAttribute::removeOldGenerations(generation_t firstUsed)
+SingleValueSmallNumericAttribute::reclaim_memory(generation_t oldest_used_gen)
{
- getGenerationHolder().trimHoldLists(firstUsed);
+ getGenerationHolder().reclaim(oldest_used_gen);
}
void
-SingleValueSmallNumericAttribute::onGenerationChange(generation_t generation)
+SingleValueSmallNumericAttribute::before_inc_generation(generation_t current_gen)
{
- getGenerationHolder().transferHoldLists(generation - 1);
+ getGenerationHolder().assign_generation(current_gen);
}
@@ -127,7 +127,7 @@ SingleValueSmallNumericAttribute::onLoad(vespalib::Executor *)
if (ok) {
setCreateSerialNum(attrReader.getCreateSerialNum());
const size_t sz(attrReader.getDataCount());
- getGenerationHolder().clearHoldLists();
+ getGenerationHolder().reclaim_all();
_wordData.reset();
_wordData.unsafe_reserve(sz - 1);
Word numDocs = attrReader.getNextData();
diff --git a/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.h b/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.h
index 4bf120d7952..b2af8752fa4 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.h
@@ -72,8 +72,8 @@ public:
void onCommit() override;
void onAddDocs(DocId docIdLimit) override;
void onUpdateStat() override;
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
bool addDoc(DocId & doc) override;
bool onLoad(vespalib::Executor *executor) override;
void onSave(IAttributeSaveTarget &saveTarget) override;
diff --git a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h
index 30549f3048a..358c95f65dc 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h
@@ -71,8 +71,8 @@ public:
SingleValueStringPostingAttributeT(const vespalib::string & name);
~SingleValueStringPostingAttributeT();
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
std::unique_ptr<attribute::SearchContext>
getSearch(QueryTermSimpleUP term, const attribute::SearchContextParams & params) const override;
diff --git a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
index f340d9f70c3..eef72984e79 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
@@ -127,19 +127,19 @@ SingleValueStringPostingAttributeT<B>::applyValueChanges(EnumStoreBatchUpdater&
template <typename B>
void
-SingleValueStringPostingAttributeT<B>::removeOldGenerations(generation_t firstUsed)
+SingleValueStringPostingAttributeT<B>::reclaim_memory(generation_t oldest_used_gen)
{
- SingleValueStringAttributeT<B>::removeOldGenerations(firstUsed);
- _postingList.trimHoldLists(firstUsed);
+ SingleValueStringAttributeT<B>::reclaim_memory(oldest_used_gen);
+ _postingList.reclaim_memory(oldest_used_gen);
}
template <typename B>
void
-SingleValueStringPostingAttributeT<B>::onGenerationChange(generation_t generation)
+SingleValueStringPostingAttributeT<B>::before_inc_generation(generation_t current_gen)
{
_postingList.freeze();
- SingleValueStringAttributeT<B>::onGenerationChange(generation);
- _postingList.transferHoldLists(generation - 1);
+ SingleValueStringAttributeT<B>::before_inc_generation(current_gen);
+ _postingList.assign_generation(current_gen);
}
template <typename B>
diff --git a/searchlib/src/vespa/searchlib/attribute/stringbase.cpp b/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
index b262b74a468..d27c61d6ff0 100644
--- a/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
@@ -6,11 +6,9 @@
#include "readerbase.h"
#include "enum_store_loaders.h"
#include <vespa/searchlib/common/sort.h>
-#include <vespa/document/fieldvalue/fieldvalue.h>
#include <vespa/searchlib/query/query_term_ucs4.h>
#include <vespa/searchcommon/attribute/config.h>
#include <vespa/vespalib/locale/c.h>
-#include <vespa/vespalib/util/array.hpp>
#include <vespa/log/log.h>
LOG_SETUP(".searchlib.attribute.stringbase");
@@ -61,7 +59,7 @@ StringAttribute::~StringAttribute() = default;
uint32_t
StringAttribute::get(DocId doc, WeightedInt * v, uint32_t sz) const
{
- WeightedConstChar * s = new WeightedConstChar[sz];
+ auto * s = new WeightedConstChar[sz];
uint32_t n = static_cast<const AttributeVector *>(this)->get(doc, s, sz);
for(uint32_t i(0),m(std::min(n,sz)); i<m; i++) {
v[i] = WeightedInt(strtoll(s[i].getValue(), nullptr, 0), s[i].getWeight());
@@ -73,7 +71,7 @@ StringAttribute::get(DocId doc, WeightedInt * v, uint32_t sz) const
uint32_t
StringAttribute::get(DocId doc, WeightedFloat * v, uint32_t sz) const
{
- WeightedConstChar * s = new WeightedConstChar[sz];
+ auto * s = new WeightedConstChar[sz];
uint32_t n = static_cast<const AttributeVector *>(this)->get(doc, s, sz);
for(uint32_t i(0),m(std::min(n,sz)); i<m; i++) {
v[i] = WeightedFloat(vespalib::locale::c::strtod(s[i].getValue(), nullptr), s[i].getWeight());
@@ -114,11 +112,11 @@ StringAttribute::get(DocId doc, largeint_t * v, uint32_t sz) const
long
StringAttribute::onSerializeForAscendingSort(DocId doc, void * serTo, long available, const common::BlobConverter * bc) const
{
- unsigned char *dst = static_cast<unsigned char *>(serTo);
+ auto *dst = static_cast<unsigned char *>(serTo);
const char *value(get(doc));
int size = strlen(value) + 1;
vespalib::ConstBufferRef buf(value, size);
- if (bc != 0) {
+ if (bc != nullptr) {
buf = bc->convert(buf);
}
if (available >= (long)buf.size()) {
@@ -133,15 +131,15 @@ long
StringAttribute::onSerializeForDescendingSort(DocId doc, void * serTo, long available, const common::BlobConverter * bc) const
{
(void) bc;
- unsigned char *dst = static_cast<unsigned char *>(serTo);
+ auto *dst = static_cast<unsigned char *>(serTo);
const char *value(get(doc));
int size = strlen(value) + 1;
vespalib::ConstBufferRef buf(value, size);
- if (bc != 0) {
+ if (bc != nullptr) {
buf = bc->convert(buf);
}
if (available >= (long)buf.size()) {
- const uint8_t * src(static_cast<const uint8_t *>(buf.data()));
+ const auto * src(static_cast<const uint8_t *>(buf.data()));
for (size_t i(0), m(buf.size()); i < m; ++i) {
dst[i] = 0xff - src[i];
}
diff --git a/searchlib/src/vespa/searchlib/common/growablebitvector.cpp b/searchlib/src/vespa/searchlib/common/growablebitvector.cpp
index e3334be3fd9..5f971e21cd3 100644
--- a/searchlib/src/vespa/searchlib/common/growablebitvector.cpp
+++ b/searchlib/src/vespa/searchlib/common/growablebitvector.cpp
@@ -72,7 +72,7 @@ bool
GrowableBitVector::hold(GenerationHeldBase::UP v)
{
if (v) {
- _generationHolder.hold(std::move(v));
+ _generationHolder.insert(std::move(v));
return true;
}
return false;
diff --git a/searchlib/src/vespa/searchlib/common/sortresults.cpp b/searchlib/src/vespa/searchlib/common/sortresults.cpp
index 1add3501f61..4eed49defc5 100644
--- a/searchlib/src/vespa/searchlib/common/sortresults.cpp
+++ b/searchlib/src/vespa/searchlib/common/sortresults.cpp
@@ -3,9 +3,9 @@
#include "sortresults.h"
#include "sort.h"
#include <vespa/searchcommon/attribute/iattributecontext.h>
-#include <vespa/vespalib/util/array.hpp>
-
+#include <vespa/vespalib/util/array.h>
#include <vespa/vespalib/util/issue.h>
+
using vespalib::Issue;
#include <vespa/log/log.h>
@@ -34,28 +34,25 @@ public:
}
};
-} // namespace <unnamed>
-
-
-inline void
-FastS_insertion_sort(RankedHit a[], uint32_t n)
-{
+void
+insertion_sort(RankedHit a[], uint32_t n) {
uint32_t i, j;
RankedHit swap;
typedef RadixHelper<search::HitRank> RT;
RT R;
- for (i=1; i<n ; i++) {
+ for (i = 1; i < n; i++) {
swap = a[i];
j = i;
- while (R(swap.getRank()) > R(a[j-1].getRank())) {
- a[j] = a[j-1];
- if (!(--j)) break;;
+ while (R(swap.getRank()) > R(a[j - 1].getRank())) {
+ a[j] = a[j - 1];
+ if (!(--j)) break;
}
a[j] = swap;
}
}
+}
template<int SHIFT>
void
@@ -133,14 +130,12 @@ FastS_radixsort(RankedHit a[], uint32_t n, uint32_t ntop)
if ((last[i]-cnt[i])<ntop) {
if (cnt[i]>INSERT_SORT_LEVEL) {
if (last[i]<ntop) {
- FastS_radixsort<SHIFT - 8>(&a[last[i]-cnt[i]], cnt[i],
- cnt[i]);
+ FastS_radixsort<SHIFT - 8>(&a[last[i]-cnt[i]], cnt[i], cnt[i]);
} else {
- FastS_radixsort<SHIFT - 8>(&a[last[i]-cnt[i]], cnt[i],
- cnt[i]+ntop-last[i]);
+ FastS_radixsort<SHIFT - 8>(&a[last[i]-cnt[i]], cnt[i], cnt[i]+ntop-last[i]);
}
} else if (cnt[i]>1) {
- FastS_insertion_sort(&a[last[i]-cnt[i]], cnt[i]);
+ insertion_sort(&a[last[i]-cnt[i]], cnt[i]);
}
}
}
@@ -156,13 +151,13 @@ FastS_SortResults(RankedHit a[], uint32_t n, uint32_t ntop)
if (n > INSERT_SORT_LEVEL) {
FastS_radixsort<sizeof(search::HitRank)*8 - 8>(a, n, ntop);
} else {
- FastS_insertion_sort(a, n);
+ insertion_sort(a, n);
}
}
//-----------------------------------------------------------------------------
-FastS_DefaultResultSorter FastS_DefaultResultSorter::__instance;
+FastS_DefaultResultSorter FastS_DefaultResultSorter::_instance;
//-----------------------------------------------------------------------------
@@ -173,12 +168,13 @@ FastS_SortSpec::Add(IAttributeContext & vecMan, const SortInfo & sInfo)
return false;
uint32_t type = ASC_VECTOR;
- const IAttributeVector * vector(NULL);
+ const IAttributeVector * vector(nullptr);
if ((sInfo._field.size() == 6) && (sInfo._field == "[rank]")) {
type = (sInfo._ascending) ? ASC_RANK : DESC_RANK;
} else if ((sInfo._field.size() == 7) && (sInfo._field == "[docid]")) {
type = (sInfo._ascending) ? ASC_DOCID : DESC_DOCID;
+ vector = vecMan.getAttribute(_documentmetastore);
} else {
type = (sInfo._ascending) ? ASC_VECTOR : DESC_VECTOR;
vector = vecMan.getAttribute(sInfo._field);
@@ -220,16 +216,18 @@ FastS_SortSpec::initSortData(const RankedHit *hits, uint32_t n)
freeSortData();
size_t fixedWidth = 0;
size_t variableWidth = 0;
- for (auto iter = _vectors.begin(); iter != _vectors.end(); ++iter) {
- if (iter->_type >= ASC_DOCID) { // doc id
- fixedWidth += sizeof(uint32_t) + sizeof(uint16_t);
- }else if (iter->_type >= ASC_RANK) { // rank value
+ for (const auto & vec : _vectors) {
+ if (vec._type >= ASC_DOCID) { // doc id
+ fixedWidth = (vec._vector != nullptr)
+ ? vec._vector->getFixedWidth()
+ : sizeof(uint32_t) + sizeof(uint16_t);
+ } else if (vec._type >= ASC_RANK) { // rank value
fixedWidth += sizeof(search::HitRank);
} else {
- size_t numBytes = iter->_vector->getFixedWidth();
+ size_t numBytes = vec._vector->getFixedWidth();
if (numBytes == 0) { // string
variableWidth += 11;
- } else if (!iter->_vector->hasMultiValue()) {
+ } else if (!vec._vector->hasMultiValue()) {
fixedWidth += numBytes;
}
}
@@ -243,22 +241,30 @@ FastS_SortSpec::initSortData(const RankedHit *hits, uint32_t n)
for (uint32_t i(0), idx(0); (i < n) && !_doom.hard_doom(); ++i) {
uint32_t len = 0;
- for (auto iter = _vectors.begin(); iter != _vectors.end(); ++iter) {
- int written(0);
+ for (const auto & vec : _vectors) {
+ long written(0);
if (available < std::max(sizeof(hits->_docId) + sizeof(_partitionId), sizeof(hits->_rankValue))) {
mySortData = realloc(n, variableWidth, available, dataSize, mySortData);
}
do {
- switch (iter->_type) {
+ switch (vec._type) {
case ASC_DOCID:
- serializeForSort<convertForSort<uint32_t, true> >(hits[i].getDocId(), mySortData);
- serializeForSort<convertForSort<uint16_t, true> >(_partitionId, mySortData + sizeof(hits->_docId));
- written = sizeof(hits->_docId) + sizeof(_partitionId);
+ if (vec._vector != nullptr) {
+ written = vec._vector->serializeForAscendingSort(hits[i].getDocId(), mySortData, available, vec._converter);
+ } else {
+ serializeForSort<convertForSort<uint32_t, true> >(hits[i].getDocId(), mySortData);
+ serializeForSort<convertForSort<uint16_t, true> >(_partitionId, mySortData + sizeof(hits->_docId));
+ written = sizeof(hits->_docId) + sizeof(_partitionId);
+ }
break;
case DESC_DOCID:
- serializeForSort<convertForSort<uint32_t, false> >(hits[i].getDocId(), mySortData);
- serializeForSort<convertForSort<uint16_t, false> >(_partitionId, mySortData + sizeof(hits->_docId));
- written = sizeof(hits->_docId) + sizeof(_partitionId);
+ if (vec._vector != nullptr) {
+ written = vec._vector->serializeForDescendingSort(hits[i].getDocId(), mySortData, available, vec._converter);
+ } else {
+ serializeForSort<convertForSort<uint32_t, false> >(hits[i].getDocId(), mySortData);
+ serializeForSort<convertForSort<uint16_t, false> >(_partitionId, mySortData + sizeof(hits->_docId));
+ written = sizeof(hits->_docId) + sizeof(_partitionId);
+ }
break;
case ASC_RANK:
serializeForSort<convertForSort<search::HitRank, true> >(hits[i].getRank(), mySortData);
@@ -269,10 +275,10 @@ FastS_SortSpec::initSortData(const RankedHit *hits, uint32_t n)
written = sizeof(hits->_rankValue);
break;
case ASC_VECTOR:
- written = iter->_vector->serializeForAscendingSort(hits[i].getDocId(), mySortData, available, iter->_converter);
+ written = vec._vector->serializeForAscendingSort(hits[i].getDocId(), mySortData, available, vec._converter);
break;
case DESC_VECTOR:
- written = iter->_vector->serializeForDescendingSort(hits[i].getDocId(), mySortData, available, iter->_converter);
+ written = vec._vector->serializeForDescendingSort(hits[i].getDocId(), mySortData, available, vec._converter);
break;
}
if (written == -1) {
@@ -293,13 +299,13 @@ FastS_SortSpec::initSortData(const RankedHit *hits, uint32_t n)
}
}
-
-FastS_SortSpec::FastS_SortSpec(uint32_t partitionId, const Doom & doom, const ConverterFactory & ucaFactory) :
- _partitionId(partitionId),
- _doom(doom),
- _ucaFactory(ucaFactory),
- _sortSpec(),
- _vectors()
+FastS_SortSpec::FastS_SortSpec(vespalib::stringref documentmetastore, uint32_t partitionId, const Doom & doom, const ConverterFactory & ucaFactory)
+ : _documentmetastore(documentmetastore),
+ _partitionId(partitionId),
+ _doom(doom),
+ _ucaFactory(ucaFactory),
+ _sortSpec(),
+ _vectors()
{ }
@@ -308,7 +314,6 @@ FastS_SortSpec::~FastS_SortSpec()
freeSortData();
}
-
bool
FastS_SortSpec::Init(const string & sortStr, IAttributeContext & vecMan)
{
@@ -316,7 +321,7 @@ FastS_SortSpec::Init(const string & sortStr, IAttributeContext & vecMan)
bool retval(true);
try {
_sortSpec = SortSpec(sortStr, _ucaFactory);
- for (SortSpec::const_iterator it(_sortSpec.begin()), mt(_sortSpec.end()); retval && (it < mt); it++) {
+ for (auto it(_sortSpec.begin()); retval && (it != _sortSpec.end()); it++) {
retval = Add(vecMan, *it);
}
} catch (const std::exception & e) {
@@ -374,26 +379,11 @@ FastS_SortSpec::initWithoutSorting(const RankedHit * hits, uint32_t hitCnt)
initSortData(hits, hitCnt);
}
-inline int
-FastS_SortSpec::Compare(const FastS_SortSpec *self, const SortData &a,
- const SortData &b)
-{
- const uint8_t * ref = self->_binarySortData.data();
- uint32_t len = a._len < b._len ? a._len : b._len;
- int retval = memcmp(ref + a._idx,
- ref + b._idx, len);
- if (retval < 0) {
- return -1;
- } else if (retval > 0) {
- return 1;
- }
- return 0;
-}
class StdSortDataCompare
{
public:
- StdSortDataCompare(const uint8_t * s) : _sortSpec(s) { }
+ explicit StdSortDataCompare(const uint8_t * s) : _sortSpec(s) { }
bool operator() (const FastS_SortSpec::SortData & x, const FastS_SortSpec::SortData & y) const {
return cmp(x, y) < 0;
}
@@ -409,7 +399,7 @@ private:
class SortDataRadix
{
public:
- SortDataRadix(const uint8_t * s) : _data(s) { }
+ explicit SortDataRadix(const uint8_t * s) : _data(s) { }
uint32_t operator () (FastS_SortSpec::SortData & a) const {
uint32_t r(0);
uint32_t left(a._len - a._pos);
@@ -448,10 +438,11 @@ void
FastS_SortSpec::sortResults(RankedHit a[], uint32_t n, uint32_t topn)
{
initSortData(a, n);
- SortData * sortData = _sortDataArray.data();
{
+ SortData * sortData = _sortDataArray.data();
+ const uint8_t * binary = _binarySortData.data();
Array<uint32_t> radixScratchPad(n, Alloc::alloc(0, MMAP_LIMIT));
- search::radix_sort(SortDataRadix(_binarySortData.data()), StdSortDataCompare(_binarySortData.data()), SortDataEof(), 1, sortData, n, radixScratchPad.data(), 0, 96, topn);
+ search::radix_sort(SortDataRadix(binary), StdSortDataCompare(binary), SortDataEof(), 1, sortData, n, radixScratchPad.data(), 0, 96, topn);
}
for (uint32_t i(0), m(_sortDataArray.size()); i < m; ++i) {
a[i]._rankValue = _sortDataArray[i]._rankValue;
diff --git a/searchlib/src/vespa/searchlib/common/sortresults.h b/searchlib/src/vespa/searchlib/common/sortresults.h
index 3e64aca269d..337863601d5 100644
--- a/searchlib/src/vespa/searchlib/common/sortresults.h
+++ b/searchlib/src/vespa/searchlib/common/sortresults.h
@@ -4,7 +4,7 @@
#include "rankedhit.h"
#include "sortspec.h"
-#include <vespa/vespalib/util/array.h>
+#include <vespa/vespalib/stllike/allocator.h>
#include <vespa/vespalib/util/doom.h>
#define INSERT_SORT_LEVEL 80
@@ -28,7 +28,7 @@ struct FastS_IResultSorter {
/**
* Destructor. No cleanup needed for base class.
*/
- virtual ~FastS_IResultSorter() {}
+ virtual ~FastS_IResultSorter() = default;
/**
* Sort the given array of results.
@@ -45,10 +45,10 @@ struct FastS_IResultSorter {
class FastS_DefaultResultSorter : public FastS_IResultSorter
{
private:
- static FastS_DefaultResultSorter __instance;
+ static FastS_DefaultResultSorter _instance;
public:
- static FastS_DefaultResultSorter *instance() { return &__instance; }
+ static FastS_DefaultResultSorter *instance() { return &_instance; }
void sortResults(search::RankedHit a[], uint32_t n, uint32_t ntop) override {
return FastS_SortResults(a, n, ntop);
}
@@ -72,7 +72,7 @@ public:
struct VectorRef
{
- VectorRef(uint32_t type, const search::attribute::IAttributeVector * vector, const search::common::BlobConverter *converter)
+ VectorRef(uint32_t type, const search::attribute::IAttributeVector * vector, const search::common::BlobConverter *converter) noexcept
: _type(type),
_vector(vector),
_converter(converter)
@@ -84,16 +84,18 @@ public:
struct SortData : public search::RankedHit
{
+ SortData() noexcept : RankedHit(), _idx(0u), _len(0u), _pos(0u) {}
uint32_t _idx;
uint32_t _len;
uint32_t _pos;
};
private:
- typedef std::vector<VectorRef> VectorRefList;
- typedef vespalib::Array<uint8_t> BinarySortData;
- typedef vespalib::Array<SortData> SortDataArray;
+ using VectorRefList = std::vector<VectorRef>;
+ using BinarySortData = std::vector<uint8_t, vespalib::allocator_large<uint8_t>>;
+ using SortDataArray = std::vector<SortData, vespalib::allocator_large<SortData>>;
using ConverterFactory = search::common::ConverterFactory;
+ vespalib::string _documentmetastore;
uint16_t _partitionId;
vespalib::Doom _doom;
const ConverterFactory & _ucaFactory;
@@ -109,12 +111,11 @@ private:
public:
FastS_SortSpec(const FastS_SortSpec &) = delete;
FastS_SortSpec & operator = (const FastS_SortSpec &) = delete;
- FastS_SortSpec(uint32_t partitionId, const vespalib::Doom & doom, const ConverterFactory & ucaFactory);
- ~FastS_SortSpec();
+ FastS_SortSpec(vespalib::stringref documentmetastore, uint32_t partitionId, const vespalib::Doom & doom, const ConverterFactory & ucaFactory);
+ ~FastS_SortSpec() override;
std::pair<const char *, size_t> getSortRef(size_t i) const {
- return std::pair<const char *, size_t>((const char*)(&_binarySortData[0] + _sortDataArray[i]._idx),
- _sortDataArray[i]._len);
+ return {(const char*)(&_binarySortData[0] + _sortDataArray[i]._idx), _sortDataArray[i]._len };
}
bool Init(const vespalib::string & sortSpec, search::attribute::IAttributeContext & vecMan);
void sortResults(search::RankedHit a[], uint32_t n, uint32_t topn) override;
@@ -122,7 +123,6 @@ public:
void copySortData(uint32_t offset, uint32_t n, uint32_t *idx, char *buf);
void freeSortData();
void initWithoutSorting(const search::RankedHit * hits, uint32_t hitCnt);
- static int Compare(const FastS_SortSpec *self, const SortData &a, const SortData &b);
};
//-----------------------------------------------------------------------------
diff --git a/searchlib/src/vespa/searchlib/common/sortspec.cpp b/searchlib/src/vespa/searchlib/common/sortspec.cpp
index 99def167460..16f0c884535 100644
--- a/searchlib/src/vespa/searchlib/common/sortspec.cpp
+++ b/searchlib/src/vespa/searchlib/common/sortspec.cpp
@@ -36,8 +36,8 @@ LowercaseConverter::onConvert(const ConstBufferRef & src) const
return {_buffer.begin(), _buffer.size()};
}
-SortInfo::SortInfo(const vespalib::string & field, bool ascending, const BlobConverter::SP & converter)
- : _field(field), _ascending(ascending), _converter(converter)
+SortInfo::SortInfo(vespalib::stringref field, bool ascending, BlobConverter::SP converter) noexcept
+ : _field(field), _ascending(ascending), _converter(std::move(converter))
{ }
SortInfo::~SortInfo() = default;
@@ -72,13 +72,13 @@ SortSpec::SortSpec(const vespalib::string & spec, const ConverterFactory & ucaFa
for(; (p < e) && (*p != ')'); p++);
if (*p == ')') {
vespalib::string strength(strengthName, p - strengthName);
- push_back(SortInfo(attr, ascending, BlobConverter::SP(ucaFactory.create(locale, strength))));
+ emplace_back(attr, ascending, ucaFactory.create(locale, strength));
} else {
throw std::runtime_error(make_string("Missing ')' at %s attr=%s locale=%s strength=%s", p, attr.c_str(), localeName, strengthName));
}
} else if (*p == ')') {
vespalib::string locale(localeName, p-localeName);
- push_back(SortInfo(attr, ascending, BlobConverter::SP(ucaFactory.create(locale, ""))));
+ emplace_back(attr, ascending, ucaFactory.create(locale, ""));
} else {
throw std::runtime_error(make_string("Missing ')' or ',' at %s attr=%s locale=%s", p, attr.c_str(), localeName));
}
@@ -91,7 +91,7 @@ SortSpec::SortSpec(const vespalib::string & spec, const ConverterFactory & ucaFa
for(; (p < e) && (*p != ')'); p++);
if (*p == ')') {
vespalib::string attr(attrName, p-attrName);
- push_back(SortInfo(attr, ascending, BlobConverter::SP(new LowercaseConverter())));
+ emplace_back(attr, ascending, std::make_shared<LowercaseConverter>());
} else {
throw std::runtime_error("Missing ')'");
}
@@ -99,7 +99,7 @@ SortSpec::SortSpec(const vespalib::string & spec, const ConverterFactory & ucaFa
throw std::runtime_error("Unknown func " + vespalib::string(func, p-func));
}
} else {
- push_back(SortInfo(funcSpec, ascending, BlobConverter::SP()));
+ emplace_back(funcSpec, ascending, std::shared_ptr<search::common::BlobConverter>());
}
}
}
diff --git a/searchlib/src/vespa/searchlib/common/sortspec.h b/searchlib/src/vespa/searchlib/common/sortspec.h
index da98d791734..682612afd43 100644
--- a/searchlib/src/vespa/searchlib/common/sortspec.h
+++ b/searchlib/src/vespa/searchlib/common/sortspec.h
@@ -11,7 +11,7 @@
namespace search::common {
struct SortInfo {
- SortInfo(const vespalib::string & field, bool ascending, const BlobConverter::SP & converter);
+ SortInfo(vespalib::stringref field, bool ascending, BlobConverter::SP converter) noexcept;
~SortInfo();
vespalib::string _field;
bool _ascending;
diff --git a/searchlib/src/vespa/searchlib/common/threaded_compactable_lid_space.cpp b/searchlib/src/vespa/searchlib/common/threaded_compactable_lid_space.cpp
index cd4069ac959..80f5f1ac5e8 100644
--- a/searchlib/src/vespa/searchlib/common/threaded_compactable_lid_space.cpp
+++ b/searchlib/src/vespa/searchlib/common/threaded_compactable_lid_space.cpp
@@ -8,7 +8,7 @@ namespace search::common {
ThreadedCompactableLidSpace::ThreadedCompactableLidSpace(std::shared_ptr<ICompactableLidSpace> target,
ISequencedTaskExecutor &executor,
ISequencedTaskExecutor::ExecutorId id)
- : _target(target),
+ : _target(std::move(target)),
_executor(executor),
_executorId(id)
{
diff --git a/searchlib/src/vespa/searchlib/docstore/logdatastore.cpp b/searchlib/src/vespa/searchlib/docstore/logdatastore.cpp
index 7036ef238b6..381bf0715b0 100644
--- a/searchlib/src/vespa/searchlib/docstore/logdatastore.cpp
+++ b/searchlib/src/vespa/searchlib/docstore/logdatastore.cpp
@@ -109,8 +109,8 @@ LogDataStore::~LogDataStore()
{
// Must be called before ending threads as there are sanity checks.
_fileChunks.clear();
- _genHandler.updateFirstUsedGeneration();
- _lidInfo.removeOldGenerations(_genHandler.getFirstUsedGeneration());
+ _genHandler.update_oldest_used_generation();
+ _lidInfo.reclaim_memory(_genHandler.get_oldest_used_generation());
}
void
@@ -485,8 +485,8 @@ void LogDataStore::compactFile(FileId fileId)
FileChunk::UP toDie;
for (;;) {
MonitorGuard guard(_updateLock);
- _genHandler.updateFirstUsedGeneration();
- if (currentGeneration < _genHandler.getFirstUsedGeneration()) {
+ _genHandler.update_oldest_used_generation();
+ if (currentGeneration < _genHandler.get_oldest_used_generation()) {
if (_holdFileChunks[fc->getFileId().getId()] == 0u) {
toDie = std::move(fc);
break;
@@ -939,8 +939,8 @@ LogDataStore::setLid(const MonitorGuard &guard, uint32_t lid, const LidInfo &met
{
(void) guard;
if (lid < _lidInfo.size()) {
- _genHandler.updateFirstUsedGeneration();
- _lidInfo.removeOldGenerations(_genHandler.getFirstUsedGeneration());
+ _genHandler.update_oldest_used_generation();
+ _lidInfo.reclaim_memory(_genHandler.get_oldest_used_generation());
const LidInfo prev = vespalib::atomic::load_ref_relaxed(_lidInfo[lid]);
if (prev.valid()) {
_fileChunks[prev.getFileId()]->remove(lid, prev.size());
@@ -958,8 +958,7 @@ LogDataStore::incGeneration()
{
_lidInfo.setGeneration(_genHandler.getNextGeneration());
_genHandler.incGeneration();
- _genHandler.updateFirstUsedGeneration();
- _lidInfo.removeOldGenerations(_genHandler.getFirstUsedGeneration());
+ _lidInfo.reclaim_memory(_genHandler.get_oldest_used_generation());
}
size_t
@@ -1213,7 +1212,7 @@ LogDataStore::canShrinkLidSpace(const MonitorGuard &) const
{
// Update lock is held, allowing call to _lidInfo.get_size()
return getDocIdLimit() < _lidInfo.get_size() &&
- _compactLidSpaceGeneration < _genHandler.getFirstUsedGeneration();
+ _compactLidSpaceGeneration < _genHandler.get_oldest_used_generation();
}
size_t
diff --git a/searchlib/src/vespa/searchlib/index/CMakeLists.txt b/searchlib/src/vespa/searchlib/index/CMakeLists.txt
index 958614844d1..0bf912a8a11 100644
--- a/searchlib/src/vespa/searchlib/index/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/index/CMakeLists.txt
@@ -2,11 +2,8 @@
vespa_add_library(searchlib_searchlib_index OBJECT
SOURCES
dictionaryfile.cpp
- docbuilder.cpp
docidandfeatures.cpp
- doctypebuilder.cpp
dummyfileheadercontext.cpp
- empty_doc_builder.cpp
indexbuilder.cpp
postinglisthandle.cpp
postinglistcounts.cpp
diff --git a/searchlib/src/vespa/searchlib/index/docbuilder.cpp b/searchlib/src/vespa/searchlib/index/docbuilder.cpp
deleted file mode 100644
index d6169f2f396..00000000000
--- a/searchlib/src/vespa/searchlib/index/docbuilder.cpp
+++ /dev/null
@@ -1,814 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "docbuilder.h"
-#include <vespa/document/datatype/urldatatype.h>
-#include <vespa/document/datatype/documenttype.h>
-#include <vespa/document/repo/documenttyperepo.h>
-#include <vespa/fastlib/text/unicodeutil.h>
-#include <vespa/vespalib/geo/zcurve.h>
-#include <vespa/vespalib/text/utf8.h>
-#include <vespa/eval/eval/value.h>
-#include <vespa/vespalib/data/slime/slime.h>
-
-using namespace document;
-using namespace search::index;
-
-using search::index::schema::CollectionType;
-using vespalib::Utf8Reader;
-using vespalib::Utf8Writer;
-using vespalib::geo::ZCurve;
-
-namespace {
-
-void
-insertStr(const Schema::Field & sfield, document::FieldValue * fvalue, const vespalib::string & val)
-{
- if (sfield.getDataType() == schema::DataType::STRING ||
- sfield.getDataType() == schema::DataType::RAW)
- {
- (dynamic_cast<LiteralFieldValueB *>(fvalue))->setValue(val);
- } else {
- throw DocBuilder::Error(vespalib::make_string("Field '%s' not compatible", sfield.getName().c_str()));
- }
-}
-
-void
-insertInt(const Schema::Field & sfield, document::FieldValue * fvalue, int64_t val)
-{
- if (sfield.getDataType() == schema::DataType::INT8) {
- (dynamic_cast<ByteFieldValue *>(fvalue))->setValue((uint8_t)val);
- } else if (sfield.getDataType() == schema::DataType::INT16) {
- (dynamic_cast<ShortFieldValue *>(fvalue))->setValue((int16_t)val);
- } else if (sfield.getDataType() == schema::DataType::INT32) {
- (dynamic_cast<IntFieldValue *>(fvalue))->setValue((int32_t)val);
- } else if (sfield.getDataType() == schema::DataType::INT64) {
- (dynamic_cast<LongFieldValue *>(fvalue))->setValue(val);
- } else {
- throw DocBuilder::Error(vespalib::make_string("Field '%s' not compatible", sfield.getName().c_str()));
- }
-}
-
-void
-insertFloat(const Schema::Field & sfield, document::FieldValue * fvalue, double val)
-{
- if (sfield.getDataType() == schema::DataType::FLOAT) {
- (dynamic_cast<FloatFieldValue *>(fvalue))->setValue((float)val);
- } else if (sfield.getDataType() == schema::DataType::DOUBLE) {
- (dynamic_cast<DoubleFieldValue *>(fvalue))->setValue(val);
- } else {
- throw DocBuilder::Error(vespalib::make_string("Field '%s' not compatible", sfield.getName().c_str()));
- }
-}
-
-void insertPredicate(const Schema::Field &sfield,
- document::FieldValue *fvalue,
- std::unique_ptr<vespalib::Slime> val) {
- if (sfield.getDataType() == schema::DataType::BOOLEANTREE) {
- *(dynamic_cast<PredicateFieldValue *>(fvalue)) =
- PredicateFieldValue(std::move(val));
- } else {
- throw DocBuilder::Error(vespalib::make_string(
- "Field '%s' not compatible",
- sfield.getName().c_str()));
- }
-}
-
-void insertTensor(const Schema::Field &schemaField,
- document::FieldValue *fvalue,
- std::unique_ptr<vespalib::eval::Value> val) {
- if (schemaField.getDataType() == schema::DataType::TENSOR) {
- *(dynamic_cast<TensorFieldValue *>(fvalue)) = std::move(val);
- } else {
- throw DocBuilder::Error(vespalib::make_string(
- "Field '%s' not compatible",
- schemaField.getName().c_str()));
- }
-}
-
-void
-insertPosition(const Schema::Field & sfield,
- document::FieldValue * fvalue, int32_t xpos, int32_t ypos)
-{
- assert(*fvalue->getDataType() == *DataType::LONG);
- assert(sfield.getDataType() == schema::DataType::INT64);
- (void) sfield;
- int64_t zpos = ZCurve::encode(xpos, ypos);
- document::LongFieldValue *zvalue =
- dynamic_cast<LongFieldValue *>(fvalue);
- zvalue->setValue(zpos);
-}
-
-}
-
-namespace docbuilderkludge
-{
-
-namespace linguistics
-{
-
-const vespalib::string SPANTREE_NAME("linguistics");
-
-enum TokenType {
- UNKNOWN = 0,
- SPACE = 1,
- PUNCTUATION = 2,
- SYMBOL = 3,
- ALPHABETIC = 4,
- NUMERIC = 5,
- MARKER = 6
-};
-
-}
-
-}
-
-using namespace docbuilderkludge;
-
-namespace {
-
-Annotation
-makeTokenType(linguistics::TokenType type)
-{
- return Annotation(*AnnotationType::TOKEN_TYPE, std::make_unique<IntFieldValue>(type));
-}
-
-}
-
-namespace search::index {
-
-VESPA_IMPLEMENT_EXCEPTION(DocBuilderError, vespalib::Exception);
-
-DocBuilder::FieldHandle::FieldHandle(const document::Field & dfield, const Schema::Field & field) :
- _sfield(field),
- _value(),
- _element()
-{
- _value = dfield.createValue();
-}
-
-DocBuilder::CollectionFieldHandle::CollectionFieldHandle(const document::Field & dfield, const Schema::Field & field) :
- FieldHandle(dfield, field),
- _elementWeight(1)
-{
-}
-
-void
-DocBuilder::CollectionFieldHandle::startElement(int32_t weight)
-{
- assert(!_element);
- _elementWeight = weight;
- const CollectionFieldValue * value = dynamic_cast<CollectionFieldValue *>(_value.get());
- _element = value->createNested();
-}
-
-void
-DocBuilder::CollectionFieldHandle::endElement()
-{
- if (_sfield.getCollectionType() == CollectionType::ARRAY) {
- onEndElement();
- ArrayFieldValue * value = dynamic_cast<ArrayFieldValue *>(_value.get());
- value->add(*_element);
- } else if (_sfield.getCollectionType() == CollectionType::WEIGHTEDSET) {
- onEndElement();
- WeightedSetFieldValue * value = dynamic_cast<WeightedSetFieldValue *>(_value.get());
- value->add(*_element, _elementWeight);
- } else {
- throw Error(vespalib::make_string("Field '%s' not compatible", _sfield.getName().c_str()));
- }
- _element.reset();
-}
-
-DocBuilder::IndexFieldHandle::IndexFieldHandle(const FixedTypeRepo & repo, const document::Field & dfield, const Schema::Field & sfield)
- : CollectionFieldHandle(dfield, sfield),
- _str(),
- _strSymbols(0u),
- _spanList(nullptr),
- _spanTree(),
- _lastSpan(nullptr),
- _spanStart(0u),
- _autoAnnotate(true),
- _autoSpace(true),
- _skipAutoSpace(true),
- _uriField(false),
- _subField(),
- _repo(repo)
-{
- _str.reserve(1023);
-
- if (_sfield.getCollectionType() == CollectionType::SINGLE) {
- if (*_value->getDataType() == document::UrlDataType::getInstance()) {
- _uriField = true;
- }
- } else {
- const CollectionFieldValue * value = dynamic_cast<CollectionFieldValue *>(_value.get());
- if (value->getNestedType() == document::UrlDataType::getInstance()) {
- _uriField = true;
- }
- }
- startAnnotate();
-}
-
-void
-DocBuilder::IndexFieldHandle::append(const vespalib::string &val)
-{
- _strSymbols += val.size();
- _str += val;
-}
-
-void
-DocBuilder::IndexFieldHandle::addStr(const vespalib::string &val)
-{
- assert(_spanTree);
- if (val.empty()) {
- return;
- }
- if (!_skipAutoSpace && _autoSpace) {
- addSpace();
- }
- _skipAutoSpace = false;
- _spanStart = _strSymbols;
- append(val);
- if (_autoAnnotate) {
- addSpan();
- addTermAnnotation();
- if (val[0] >= '0' && val[0] <= '9') {
- addNumericTokenAnnotation();
- } else {
- addAlphabeticTokenAnnotation();
- }
- }
-}
-
-void
-DocBuilder::IndexFieldHandle::addSpace()
-{
- addNoWordStr(" ");
-}
-
-void
-DocBuilder::IndexFieldHandle::addNoWordStr(const vespalib::string &val)
-{
- assert(_spanTree);
- if (val.empty()) {
- return;
- }
- _spanStart = _strSymbols;
- append(val);
- if (_autoAnnotate) {
- addSpan();
- if (val[0] == ' ' || val[0] == '\t') {
- addSpaceTokenAnnotation();
- } else if (val[0] >= '0' && val[0] <= '9') {
- addNumericTokenAnnotation();
- } else {
- addAlphabeticTokenAnnotation();
- }
-
- }
- _skipAutoSpace = true;
-}
-
-void
-DocBuilder::IndexFieldHandle::addTokenizedString(const vespalib::string &val,
- bool urlMode)
-{
- Utf8Reader r(val);
- vespalib::string sbuf;
- Utf8Writer w(sbuf);
- uint32_t c = 0u;
- bool oldWord = false;
- assert(_uriField == urlMode);
- assert(_uriField != _subField.empty());
-
- while (r.hasMore()) {
- c = r.getChar();
- bool newWord = Fast_UnicodeUtil::IsWordChar(c) ||
- (urlMode && (c == '-' || c == '_'));
- if (oldWord != newWord) {
- if (!sbuf.empty()) {
- if (oldWord) {
- addStr(sbuf);
- } else {
- addNoWordStr(sbuf);
- }
- sbuf.clear();
- }
- oldWord = newWord;
- }
- w.putChar(c);
- }
- if (!sbuf.empty()) {
- if (oldWord) {
- addStr(sbuf);
- } else {
- addNoWordStr(sbuf);
- }
- }
-}
-
-void
-DocBuilder::IndexFieldHandle::addSpan(size_t start, size_t len)
-{
- const SpanNode &span = _spanList->add(std::make_unique<Span>(start, len));
- _lastSpan = &span;
-}
-
-void
-DocBuilder::IndexFieldHandle::addSpan()
-{
- size_t endPos = _strSymbols;
- assert(endPos > _spanStart);
- addSpan(_spanStart, endPos - _spanStart);
- _spanStart = endPos;
-}
-
-void
-DocBuilder::IndexFieldHandle::addSpaceTokenAnnotation()
-{
- assert(_spanTree);
- assert(_lastSpan != nullptr);
- _spanTree->annotate(*_lastSpan, makeTokenType(linguistics::SPACE));
-}
-
-void
-DocBuilder::IndexFieldHandle::addNumericTokenAnnotation()
-{
- assert(_spanTree);
- assert(_lastSpan != nullptr);
- _spanTree->annotate(*_lastSpan, makeTokenType(linguistics::NUMERIC));
-}
-
-void
-DocBuilder::IndexFieldHandle::addAlphabeticTokenAnnotation()
-{
- assert(_spanTree);
- assert(_lastSpan != nullptr);
- _spanTree->annotate(*_lastSpan, makeTokenType(linguistics::ALPHABETIC));
-}
-
-void
-DocBuilder::IndexFieldHandle::addTermAnnotation()
-{
- assert(_spanTree);
- assert(_lastSpan != nullptr);
- _spanTree->annotate(*_lastSpan, *AnnotationType::TERM);
-}
-
-void
-DocBuilder::IndexFieldHandle::addTermAnnotation(const vespalib::string &val)
-{
- assert(_spanTree);
- assert(_lastSpan != nullptr);
- _spanTree->annotate(*_lastSpan,
- Annotation(*AnnotationType::TERM,
- std::make_unique<StringFieldValue>(val)));
-}
-
-void
-DocBuilder::IndexFieldHandle::onEndElement()
-{
- // Flush data for index field.
- assert(_subField.empty());
- if (_uriField) {
- return;
- }
- StringFieldValue * value;
- if (_sfield.getCollectionType() != CollectionType::SINGLE) {
- value = dynamic_cast<StringFieldValue *>(_element.get());
- } else {
- value = dynamic_cast<StringFieldValue *>(_value.get());
- }
- value->setValue(_str);
- // Also drop all spans no annotation for now
- if (_spanTree->numAnnotations() > 0u) {
- StringFieldValue::SpanTrees trees;
- trees.emplace_back(std::move(_spanTree));
- value->setSpanTrees(trees, _repo);
- } else {
- _spanTree.reset();
- }
- _spanList = nullptr;
- _lastSpan = nullptr;
- _spanStart = 0u;
- _strSymbols = 0u;
- _str.clear();
- _skipAutoSpace = true;
- startAnnotate();
-}
-
-void
-DocBuilder::IndexFieldHandle::onEndField()
-{
- if (_sfield.getCollectionType() == CollectionType::SINGLE) {
- onEndElement();
- }
-}
-
-void
-DocBuilder::IndexFieldHandle::startAnnotate()
-{
- SpanList::UP span_list(new SpanList);
- _spanList = span_list.get();
- _spanTree.reset(new SpanTree(linguistics::SPANTREE_NAME, std::move(span_list)));
-}
-
-void
-DocBuilder::IndexFieldHandle::setAutoAnnotate(bool autoAnnotate)
-{
- _autoAnnotate = autoAnnotate;
-}
-
-void
-DocBuilder::IndexFieldHandle::setAutoSpace(bool autoSpace)
-{
- _autoSpace = autoSpace;
-}
-
-void
-DocBuilder::IndexFieldHandle::startSubField(const vespalib::string &subField)
-{
- assert(_subField.empty());
- assert(_uriField);
- _subField = subField;
-}
-
-void
-DocBuilder::IndexFieldHandle::endSubField()
-{
- assert(!_subField.empty());
- assert(_uriField);
- StructuredFieldValue *sValue;
- if (_sfield.getCollectionType() != CollectionType::SINGLE) {
- sValue = dynamic_cast<StructFieldValue *>(_element.get());
- } else {
- sValue = dynamic_cast<StructFieldValue *>(_value.get());
- }
- const Field &f = sValue->getField(_subField);
- FieldValue::UP fval(f.getDataType().createFieldValue());
- *fval = _str;
- StringFieldValue *value = dynamic_cast<StringFieldValue *>(fval.get());
- StringFieldValue::SpanTrees trees;
- trees.emplace_back(std::move(_spanTree));
- value->setSpanTrees(trees, _repo);
- sValue->setValue(f, *fval);
- _spanList = nullptr;
- _lastSpan = nullptr;
- _spanStart = 0u;
- _strSymbols = 0u;
- _str.clear();
- _skipAutoSpace = true;
- startAnnotate();
- _subField.clear();
-}
-
-DocBuilder::AttributeFieldHandle::
-AttributeFieldHandle(const document::Field &dfield,
- const Schema::Field &sfield)
- : CollectionFieldHandle(dfield, sfield)
-{
-}
-
-void
-DocBuilder::AttributeFieldHandle::addStr(const vespalib::string & val)
-{
- if (_element) {
- insertStr(_sfield, _element.get(), val);
- } else {
- insertStr(_sfield, _value.get(), val);
- }
-}
-
-void
-DocBuilder::AttributeFieldHandle::addInt(int64_t val)
-{
- if (_element) {
- insertInt(_sfield, _element.get(), val);
- } else {
- insertInt(_sfield, _value.get(), val);
- }
-}
-
-void
-DocBuilder::AttributeFieldHandle::addFloat(double val)
-{
- if (_element) {
- insertFloat(_sfield, _element.get(), val);
- } else {
- insertFloat(_sfield, _value.get(), val);
- }
-}
-
-void
-DocBuilder::AttributeFieldHandle::addPredicate(
- std::unique_ptr<vespalib::Slime> val)
-{
- if (_element) {
- insertPredicate(_sfield, _element.get(), std::move(val));
- } else {
- insertPredicate(_sfield, _value.get(), std::move(val));
- }
-}
-
-void
-DocBuilder::AttributeFieldHandle::addTensor(
- std::unique_ptr<vespalib::eval::Value> val)
-{
- if (_element) {
- insertTensor(_sfield, _element.get(), std::move(val));
- } else {
- insertTensor(_sfield, _value.get(), std::move(val));
- }
-}
-
-void
-DocBuilder::AttributeFieldHandle::addPosition(int32_t xpos, int32_t ypos)
-{
- if (_element) {
- insertPosition(_sfield, _element.get(), xpos, ypos);
- } else {
- insertPosition(_sfield, _value.get(), xpos, ypos);
- }
-}
-
-DocBuilder::DocumentHandle::DocumentHandle(document::Document &doc, const vespalib::string & docId)
- : _type(&doc.getType()),
- _doc(&doc),
- _fieldHandle(),
- _repo(*_doc->getRepo(), *_type)
-{
- (void) docId;
-}
-
-DocBuilder::DocumentHandle::~DocumentHandle() = default;
-
-void
-DocBuilder::DocumentHandle::startIndexField(const Schema::Field & sfield) {
- _fieldHandle.reset(new IndexFieldHandle(_repo, _type->getField(sfield.getName()), sfield));
-}
-void
-DocBuilder::DocumentHandle::startAttributeField(const Schema::Field & sfield) {
- _fieldHandle.reset(new AttributeFieldHandle(_type->getField(sfield.getName()), sfield));
-}
-
-void
-DocBuilder::DocumentHandle::endField() {
- _fieldHandle->onEndField();
- _doc->setValue(_type->getField(_fieldHandle->getField().getName()), *_fieldHandle->getValue());
- _fieldHandle.reset();
-}
-
-DocBuilder::DocBuilder(const Schema &schema)
- : _schema(schema),
- _doctypes_config(DocTypeBuilder(schema).makeConfig()),
- _repo(std::make_shared<DocumentTypeRepo>(_doctypes_config)),
- _docType(*_repo->getDocumentType("searchdocument")),
- _doc(),
- _handleDoc(),
- _currDoc()
-{
-}
-
-DocBuilder::~DocBuilder() = default;
-
-DocBuilder &
-DocBuilder::startDocument(const vespalib::string & docId)
-{
- _doc = std::make_unique<Document>(_docType, DocumentId(docId));
- _doc->setRepo(*_repo);
- _handleDoc = std::make_shared<DocumentHandle>(*_doc, docId);
- return *this;
-}
-
-document::Document::UP
-DocBuilder::endDocument()
-{
- _handleDoc->endDocument(_doc);
- return std::move(_doc);
-}
-
-DocBuilder &
-DocBuilder::startIndexField(const vespalib::string & name)
-{
- assert(!_handleDoc->getFieldHandle());
- uint32_t field_id = _schema.getIndexFieldId(name);
- assert(field_id != Schema::UNKNOWN_FIELD_ID);
- _handleDoc->startIndexField(_schema.getIndexField(field_id));
- _currDoc = _handleDoc.get();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::startAttributeField(const vespalib::string & name)
-{
- assert(!_handleDoc->getFieldHandle());
- uint32_t field_id = _schema.getIndexFieldId(name);
- assert(field_id == Schema::UNKNOWN_FIELD_ID);
- field_id = _schema.getAttributeFieldId(name);
- assert(field_id != Schema::UNKNOWN_FIELD_ID);
- _handleDoc->startAttributeField(_schema.getAttributeField(field_id));
- _currDoc = _handleDoc.get();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::endField()
-{
- assert(_currDoc != nullptr);
- _currDoc->endField();
- _currDoc = nullptr;
- return *this;
-}
-
-DocBuilder &
-DocBuilder::startElement(int32_t weight)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->startElement(weight);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::endElement()
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->endElement();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addStr(const vespalib::string & str)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addStr(str);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addSpace()
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addSpace();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addNoWordStr(const vespalib::string & str)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addNoWordStr(str);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addTokenizedString(const vespalib::string &str)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addTokenizedString(str, false);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addUrlTokenizedString(const vespalib::string &str)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addTokenizedString(str, true);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addInt(int64_t val)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addInt(val);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addFloat(double val)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addFloat(val);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addPredicate(std::unique_ptr<vespalib::Slime> val)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addPredicate(std::move(val));
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addTensor(std::unique_ptr<vespalib::eval::Value> val)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addTensor(std::move(val));
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addSpan(size_t start, size_t len)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addSpan(start, len);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addSpan()
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addSpan();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addSpaceTokenAnnotation()
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addSpaceTokenAnnotation();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addNumericTokenAnnotation()
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addNumericTokenAnnotation();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addAlphabeticTokenAnnotation()
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addAlphabeticTokenAnnotation();
- return *this;
-}
-
-DocBuilder&
-DocBuilder::addTermAnnotation()
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addTermAnnotation();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addTermAnnotation(const vespalib::string &val)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addTermAnnotation(val);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addPosition(int32_t xpos, int32_t ypos)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addPosition(xpos, ypos);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::addRaw(const void *buf, size_t len)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->addRaw(buf, len);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::startSubField(const vespalib::string &subField)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->startSubField(subField);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::endSubField()
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->endSubField();
- return *this;
-}
-
-DocBuilder &
-DocBuilder::setAutoAnnotate(bool autoAnnotate)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->setAutoAnnotate(autoAnnotate);
- return *this;
-}
-
-DocBuilder &
-DocBuilder::setAutoSpace(bool autoSpace)
-{
- assert(_currDoc != nullptr);
- _currDoc->getFieldHandle()->setAutoSpace(autoSpace);
- return *this;
-}
-
-}
diff --git a/searchlib/src/vespa/searchlib/index/docbuilder.h b/searchlib/src/vespa/searchlib/index/docbuilder.h
deleted file mode 100644
index a8a37b57070..00000000000
--- a/searchlib/src/vespa/searchlib/index/docbuilder.h
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#pragma once
-
-#include "doctypebuilder.h"
-#include <vespa/document/repo/fixedtyperepo.h>
-#include <vespa/document/fieldvalue/fieldvalues.h>
-#include <vespa/document/annotation/annotation.h>
-#include <vespa/document/annotation/span.h>
-#include <vespa/document/annotation/spanlist.h>
-#include <vespa/document/annotation/spantree.h>
-#include <vespa/vespalib/util/exception.h>
-#include <vespa/vespalib/util/stringfmt.h>
-
-namespace vespalib::eval { struct Value; }
-
-namespace search::index {
-
-VESPA_DEFINE_EXCEPTION(DocBuilderError, vespalib::Exception);
-
-/**
- * Builder class used to generate a search document that corresponds
- * to an index schema.
- **/
-class DocBuilder {
-public:
- typedef DocBuilderError Error;
-
-private:
- /**
- * Base class for handling the construction of a field.
- **/
- class FieldHandle {
- public:
- typedef std::shared_ptr<FieldHandle> SP;
- protected:
- const Schema::Field & _sfield;
- document::FieldValue::UP _value;
- document::FieldValue::UP _element;
- public:
- FieldHandle(const document::Field & dfield, const Schema::Field & field);
- virtual ~FieldHandle() {}
- virtual void startElement(int32_t weight) { (void) weight; throw Error("Function not supported"); }
- virtual void endElement() { throw Error("Function not supported"); }
- virtual void addStr(const vespalib::string & val) { (void) val; throw Error("Function not supported"); }
-
- virtual void addSpace() {
- throw Error("Function not supported");
- }
-
- virtual void addNoWordStr(const vespalib::string & val) {
- (void) val;
- throw Error("Function not supported");
- }
-
- virtual void addTokenizedString(const vespalib::string &val, bool urlMode) {
- (void) val;
- (void) urlMode;
- throw Error("Function not supported");
- }
-
- virtual void addSpan(size_t start, size_t len) {
- (void) start;
- (void) len;
- throw Error("Function not supported");
- }
-
- virtual void addSpan() {
- throw Error("Function not supported");
- }
-
- virtual void addSpaceTokenAnnotation() {
- throw Error("Function not supported");
- }
-
- virtual void addNumericTokenAnnotation() {
- throw Error("Function not supported");
- }
-
- virtual void addAlphabeticTokenAnnotation() {
- throw Error("Function not supported");
- }
-
- virtual void addTermAnnotation() {
- throw Error("Function not supported");
- }
-
- virtual void addTermAnnotation(const vespalib::string &val) {
- (void) val;
- throw Error("Function not supported");
- }
-
- virtual void addInt(int64_t val) { (void) val; throw Error("Function not supported"); }
- virtual void addFloat(double val) { (void) val; throw Error("Function not supported"); }
- virtual void addPredicate(std::unique_ptr<vespalib::Slime>) {
- throw Error("Function not supported");
- }
- virtual void addTensor(std::unique_ptr<vespalib::eval::Value>) {
- throw Error("Function not supported");
- }
- const document::FieldValue::UP & getValue() const { return _value; }
- const Schema::Field & getField() const { return _sfield; }
-
- virtual void onEndElement() {}
- virtual void onEndField() {}
-
- virtual void setAutoAnnotate(bool autoAnnotate) {
- (void) autoAnnotate;
- throw Error("Function not supported");
- }
-
- virtual void setAutoSpace(bool autoSpace) {
- (void) autoSpace;
- throw Error("Function not supported");
- }
-
- virtual void addPosition(int32_t xpos, int32_t ypos) {
- (void) xpos;
- (void) ypos;
- throw Error("Function not supported");
- }
-
- virtual void addRaw(const void *buf, size_t len) {
- (void) buf;
- (void) len;
- throw Error("Function not supported");
- }
-
- virtual void startSubField(const vespalib::string &subField) {
- (void) subField;
- throw Error("Function not supported");
- }
-
- virtual void endSubField() {
- throw Error("Function not supported");
- }
- };
-
- /**
- * Class that can handle multi value fields.
- **/
- class CollectionFieldHandle : public FieldHandle {
- private:
- int32_t _elementWeight;
- public:
- CollectionFieldHandle(const document::Field & dfield, const Schema::Field & sfield);
- void startElement(int32_t weight) override;
- void endElement() override;
- };
-
- /**
- * Class for handling the construction of the content of an index field.
- **/
- class IndexFieldHandle : public CollectionFieldHandle {
- vespalib::string _str; // adjusted as word comes along
- size_t _strSymbols; // symbols in string, assuming UTF8
- document::SpanList *_spanList; // owned by _spanTree
- document::SpanTree::UP _spanTree;
- const document::SpanNode *_lastSpan;
- size_t _spanStart; // start of span
- bool _autoAnnotate; // Add annotation when adding strings
- bool _autoSpace; // Add space before strings
- bool _skipAutoSpace; // one shot skip of adding space
- bool _uriField; // URI handling (special struct case)
- vespalib::string _subField;
- const document::FixedTypeRepo & _repo;
-
- void append(const vespalib::string &val);
-
- public:
- IndexFieldHandle(const document::FixedTypeRepo & repo,
- const document::Field &dfield,
- const Schema::Field &sfield);
-
- void addStr(const vespalib::string & val) override;
- void addSpace() override;
- void addNoWordStr(const vespalib::string & val) override;
- void addTokenizedString(const vespalib::string &val, bool urlMode) override;
- void addSpan(size_t start, size_t len) override;
- void addSpan() override;
- void addSpaceTokenAnnotation() override;
- void addNumericTokenAnnotation() override;
- void addAlphabeticTokenAnnotation() override;
- void addTermAnnotation() override;
- void addTermAnnotation(const vespalib::string &val) override;
- void onEndElement() override;
- void onEndField() override;
- void startAnnotate();
- void setAutoAnnotate(bool autoAnnotate) override;
- void setAutoSpace(bool autoSpace) override;
- void startSubField(const vespalib::string &subField) override;
- void endSubField() override;
- };
-
- /**
- * Class for handling the construction of the content of an attribute field.
- **/
- class AttributeFieldHandle : public CollectionFieldHandle {
- public:
- AttributeFieldHandle(const document::Field & dfield, const Schema::Field & sfield);
- void addStr(const vespalib::string & val) override;
- void addInt(int64_t val) override;
- void addFloat(double val) override;
- void addPredicate(std::unique_ptr<vespalib::Slime> val) override;
- void addTensor(std::unique_ptr<vespalib::eval::Value> val) override;
- void addPosition(int32_t xpos, int32_t ypos) override;
- };
-
- /**
- * Class for handling the construction of a document (set of fields).
- **/
- class DocumentHandle {
- public:
- typedef std::shared_ptr<DocumentHandle> SP;
- private:
- const document::DocumentType * _type;
- document::Document *const _doc;
- FieldHandle::SP _fieldHandle;
- document::FixedTypeRepo _repo;
- public:
- DocumentHandle(document::Document &doc, const vespalib::string & docId);
- ~DocumentHandle();
- const FieldHandle::SP & getFieldHandle() const { return _fieldHandle; }
- void startIndexField(const Schema::Field & sfield);
- void startAttributeField(const Schema::Field & sfield);
- void endField();
- void endDocument(const document::Document::UP & doc) {
- (void) doc;
- }
- };
-
- const Schema & _schema;
- document::config::DocumenttypesConfig _doctypes_config;
- std::shared_ptr<const document::DocumentTypeRepo> _repo;
- const document::DocumentType &_docType;
- document::Document::UP _doc; // the document we are about to generate
-
- DocumentHandle::SP _handleDoc; // handle for all fields
- DocumentHandle * _currDoc; // the current document handle
-
-public:
- DocBuilder(const Schema & schema);
- ~DocBuilder();
-
- DocBuilder & startDocument(const vespalib::string & docId);
- document::Document::UP endDocument();
-
- DocBuilder & startIndexField(const vespalib::string & name);
- DocBuilder & startAttributeField(const vespalib::string & name);
- DocBuilder & endField();
- DocBuilder & startElement(int32_t weight = 1);
- DocBuilder & endElement();
- DocBuilder & addStr(const vespalib::string & val);
- DocBuilder & addSpace();
- DocBuilder & addNoWordStr(const vespalib::string & val);
- DocBuilder & addInt(int64_t val);
- DocBuilder & addFloat(double val);
- DocBuilder & addPredicate(std::unique_ptr<vespalib::Slime> val);
- DocBuilder & addTensor(std::unique_ptr<vespalib::eval::Value> val);
- DocBuilder &addTokenizedString(const vespalib::string &val);
- DocBuilder &addUrlTokenizedString(const vespalib::string &val);
- DocBuilder &addSpan(size_t start, size_t len);
- DocBuilder &addSpan();
- DocBuilder &addSpaceTokenAnnotation();
- DocBuilder &addNumericTokenAnnotation();
- DocBuilder &addAlphabeticTokenAnnotation();
- DocBuilder &addTermAnnotation();
- DocBuilder &addTermAnnotation(const vespalib::string &val);
- DocBuilder &setAutoAnnotate(bool autoAnnotate);
- DocBuilder &setAutoSpace(bool autoSpace);
- DocBuilder &addPosition(int32_t xpos, int32_t ypos);
- DocBuilder &addRaw(const void *buf, size_t len);
- DocBuilder &startSubField(const vespalib::string &subField);
- DocBuilder &endSubField();
- static bool hasAnnotations() { return true; }
-
- const document::DocumentType &getDocumentType() const { return _docType; }
- const std::shared_ptr<const document::DocumentTypeRepo> &getDocumentTypeRepo() const { return _repo; }
- document::config::DocumenttypesConfig getDocumenttypesConfig() const { return _doctypes_config; }
-};
-
-}
diff --git a/searchlib/src/vespa/searchlib/index/doctypebuilder.cpp b/searchlib/src/vespa/searchlib/index/doctypebuilder.cpp
deleted file mode 100644
index 5f655419471..00000000000
--- a/searchlib/src/vespa/searchlib/index/doctypebuilder.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "doctypebuilder.h"
-#include <vespa/document/datatype/urldatatype.h>
-#include <vespa/document/datatype/tensor_data_type.h>
-#include <vespa/document/datatype/documenttype.h>
-#include <vespa/document/repo/configbuilder.h>
-#include <set>
-
-using namespace document;
-
-namespace search::index {
-namespace {
-
-DataType::Type convert(Schema::DataType type) {
- switch (type) {
- case schema::DataType::BOOL:
- case schema::DataType::UINT2:
- case schema::DataType::UINT4:
- case schema::DataType::INT8:
- return DataType::T_BYTE;
- case schema::DataType::INT16:
- return DataType::T_SHORT;
- case schema::DataType::INT32:
- return DataType::T_INT;
- case schema::DataType::INT64:
- return DataType::T_LONG;
- case schema::DataType::FLOAT:
- return DataType::T_FLOAT;
- case schema::DataType::DOUBLE:
- return DataType::T_DOUBLE;
- case schema::DataType::STRING:
- return DataType::T_STRING;
- case schema::DataType::RAW:
- return DataType::T_RAW;
- case schema::DataType::BOOLEANTREE:
- return DataType::T_PREDICATE;
- case schema::DataType::TENSOR:
- return DataType::T_TENSOR;
- default:
- break;
- }
- assert(!"Unknown datatype in schema");
- return DataType::MAX;
-}
-
-void
-insertStructType(document::config::DocumenttypesConfig::Documenttype & cfg, const StructDataType & structType)
-{
- typedef document::config::DocumenttypesConfig DTC;
- DTC::Documenttype::Datatype::Sstruct cfgStruct;
- cfgStruct.name = structType.getName();
- Field::Set fieldSet = structType.getFieldSet();
- for (const Field * field : fieldSet) {
- DTC::Documenttype::Datatype::Sstruct::Field sField;
- sField.name = field->getName();
- sField.datatype = field->getDataType().getId();
- sField.id = field->getId();
- cfgStruct.field.push_back(sField);
- }
- cfg.datatype.push_back(DTC::Documenttype::Datatype());
- cfg.datatype.back().sstruct = cfgStruct;
- cfg.datatype.back().id = structType.getId();
-}
-
-using namespace document::config_builder;
-
-TypeOrId makeCollection(TypeOrId datatype, Schema::CollectionType collection_type) {
- switch (collection_type) {
- case schema::CollectionType::ARRAY:
- return Array(datatype);
- case schema::CollectionType::WEIGHTEDSET:
- // TODO: consider using array of struct<primitive,int32> to keep order
- return Wset(datatype);
- default:
- return datatype;
- }
-}
-
-struct TypeCache {
- std::map<std::pair<int, Schema::CollectionType>, TypeOrId> types;
-
- TypeOrId getType(TypeOrId datatype, Schema::CollectionType c_type) {
- TypeOrId type = makeCollection(datatype, c_type);
- std::pair<int, Schema::CollectionType> key = std::make_pair(datatype.id, c_type);
- if (types.find(key) == types.end()) {
- types.insert(std::make_pair(key, type));
- }
- return types.find(key)->second;
- }
-};
-
-}
-
-DocTypeBuilder::DocTypeBuilder(const Schema &schema)
- : _schema(schema),
- _iFields()
-{
- _iFields.setup(schema);
-}
-
-document::config::DocumenttypesConfig DocTypeBuilder::makeConfig() const {
- using namespace document::config_builder;
- TypeCache type_cache;
-
- typedef std::set<vespalib::string> UsedFields;
- UsedFields usedFields;
-
- Struct header_struct("searchdocument.header");
- header_struct.setId(-1505212454);
-
- for (size_t i = 0; i < _iFields._textFields.size(); ++i) {
- const Schema::IndexField &field =
- _schema.getIndexField(_iFields._textFields[i]);
-
- // only handles string fields for now
- assert(field.getDataType() == schema::DataType::STRING);
- header_struct.addField(field.getName(), type_cache.getType(
- DataType::T_STRING, field.getCollectionType()));
- usedFields.insert(field.getName());
- }
-
- const int32_t uri_type = document::UrlDataType::getInstance().getId();
- for (size_t i = 0; i < _iFields._uriFields.size(); ++i) {
- const Schema::IndexField &field =
- _schema.getIndexField(_iFields._uriFields[i]._all);
-
- // only handles string fields for now
- assert(field.getDataType() == schema::DataType::STRING);
- header_struct.addField(field.getName(), type_cache.getType(
- uri_type, field.getCollectionType()));
- usedFields.insert(field.getName());
- }
-
- for (uint32_t i = 0; i < _schema.getNumAttributeFields(); ++i) {
- const Schema::AttributeField &field = _schema.getAttributeField(i);
- UsedFields::const_iterator usf = usedFields.find(field.getName());
- if (usf != usedFields.end()) {
- continue; // taken as index field
- }
- auto type_id = convert(field.getDataType());
- if (type_id == DataType::T_TENSOR) {
- header_struct.addTensorField(field.getName(), field.get_tensor_spec());
- } else {
- header_struct.addField(field.getName(), type_cache.getType(
- type_id, field.getCollectionType()));
- }
- usedFields.insert(field.getName());
- }
-
- DocumenttypesConfigBuilderHelper builder;
- builder.document(-645763131, "searchdocument",
- header_struct, Struct("searchdocument.body"));
- return builder.config();
-}
-
-document::config::DocumenttypesConfig
-DocTypeBuilder::makeConfig(const DocumentType &docType)
-{
- typedef document::config::DocumenttypesConfigBuilder DTC;
- DTC cfg;
- { // document type
- DTC::Documenttype dtype;
- dtype.id = docType.getId();
- dtype.name = docType.getName();
- // TODO(vekterli): remove header/body config
- dtype.headerstruct = docType.getFieldsType().getId();
- dtype.bodystruct = docType.getFieldsType().getId();
- cfg.documenttype.push_back(dtype);
- }
- insertStructType(cfg.documenttype[0], docType.getFieldsType());
- return cfg;
-}
-
-}
diff --git a/searchlib/src/vespa/searchlib/index/doctypebuilder.h b/searchlib/src/vespa/searchlib/index/doctypebuilder.h
deleted file mode 100644
index 4db0ba5b0e3..00000000000
--- a/searchlib/src/vespa/searchlib/index/doctypebuilder.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#pragma once
-
-#include "schema_index_fields.h"
-#include <vespa/document/config/config-documenttypes.h>
-#include <vespa/document/fieldvalue/fieldvalues.h>
-#include <vespa/vespalib/util/exception.h>
-#include <vespa/vespalib/util/stringfmt.h>
-
-namespace search::index {
-
-/**
- * Builder for the indexingdocument document type based on an index schema.
- **/
-class DocTypeBuilder {
- const Schema &_schema;
- SchemaIndexFields _iFields;
-
-public:
- DocTypeBuilder(const Schema & schema);
- document::config::DocumenttypesConfig makeConfig() const;
-
- static document::config::DocumenttypesConfig
- makeConfig(const document::DocumentType &docType);
-};
-
-}
diff --git a/searchlib/src/vespa/searchlib/index/empty_doc_builder.cpp b/searchlib/src/vespa/searchlib/index/empty_doc_builder.cpp
deleted file mode 100644
index 45588791926..00000000000
--- a/searchlib/src/vespa/searchlib/index/empty_doc_builder.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "empty_doc_builder.h"
-#include <vespa/document/datatype/documenttype.h>
-#include <vespa/document/fieldvalue/document.h>
-#include <vespa/document/repo/documenttyperepo.h>
-#include <vespa/document/repo/configbuilder.h>
-#include <cassert>
-
-using document::DataType;
-using document::Document;
-using document::DocumentId;
-using document::DocumentTypeRepo;
-
-namespace search::index {
-
-namespace {
-
-DocumenttypesConfig
-get_document_types_config(EmptyDocBuilder::AddFieldsType add_fields)
-{
- using namespace document::config_builder;
- DocumenttypesConfigBuilderHelper builder;
- Struct header("searchdocument.header");
- add_fields(header);
- builder.document(42, "searchdocument",
- header,
- Struct("searchdocument.body"));
- return builder.config();
-}
-
-}
-
-EmptyDocBuilder::EmptyDocBuilder(AddFieldsType add_fields)
- : _repo(std::make_shared<const DocumentTypeRepo>(get_document_types_config(add_fields))),
- _document_type(_repo->getDocumentType("searchdocument"))
-{
-}
-
-EmptyDocBuilder::~EmptyDocBuilder() = default;
-
-
-std::unique_ptr<Document>
-EmptyDocBuilder::make_document(vespalib::string document_id)
-{
- auto doc = std::make_unique<Document>(get_document_type(), DocumentId(document_id));
- doc->setRepo(get_repo());
- return doc;
-}
-
-const DataType&
-EmptyDocBuilder::get_data_type(const vespalib::string &name) const
-{
- const DataType *type = _repo->getDataType(*_document_type, name);
- assert(type);
- return *type;
-}
-
-}
diff --git a/searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp b/searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp
index d2a28ebc04a..72f4a7ae579 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp
+++ b/searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp
@@ -2,6 +2,9 @@
#include "feature_store.h"
#include <vespa/searchlib/index/schemautil.h>
+#include <vespa/vespalib/datastore/compacting_buffers.h>
+#include <vespa/vespalib/datastore/compaction_spec.h>
+#include <vespa/vespalib/datastore/compaction_strategy.h>
#include <vespa/vespalib/datastore/datastore.hpp>
namespace search::memoryindex {
@@ -9,6 +12,8 @@ namespace search::memoryindex {
constexpr size_t MIN_BUFFER_ARRAYS = 1024u;
using index::SchemaUtil;
+using vespalib::datastore::CompactionSpec;
+using vespalib::datastore::CompactionStrategy;
using vespalib::datastore::EntryRef;
uint64_t
@@ -63,8 +68,6 @@ FeatureStore::moveFeatures(EntryRef ref, uint64_t bitLen)
const uint8_t *src = getBits(ref);
uint64_t byteLen = (bitLen + 7) / 8;
EntryRef newRef = addFeatures(src, byteLen);
- // Mark old features as dead
- _store.incDead(ref, byteLen + Aligner::pad(byteLen));
return newRef;
}
@@ -112,7 +115,6 @@ FeatureStore::add_features_guard_bytes()
uint32_t pad = Aligner::pad(len);
auto result = _store.rawAllocator<uint8_t>(_typeId).alloc(len + pad);
memset(result.data, 0, len + pad);
- _store.incDead(result.ref, len + pad);
}
void
@@ -143,4 +145,13 @@ FeatureStore::moveFeatures(uint32_t packedIndex, EntryRef ref)
return moveFeatures(ref, bitLen);
}
+std::unique_ptr<vespalib::datastore::CompactingBuffers>
+FeatureStore::start_compact()
+{
+ // Use a compaction strategy that will compact all active buffers
+ auto compaction_strategy = CompactionStrategy::make_compact_all_active_buffers_strategy();
+ CompactionSpec compaction_spec(true, false);
+ return _store.start_compact_worst_buffers(compaction_spec, compaction_strategy);
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/memoryindex/feature_store.h b/searchlib/src/vespa/searchlib/memoryindex/feature_store.h
index 3ecb61f1cd1..5f5e782a382 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/feature_store.h
+++ b/searchlib/src/vespa/searchlib/memoryindex/feature_store.h
@@ -205,13 +205,12 @@ public:
const std::vector<PosOccFieldsParams> &getFieldsParams() const { return _fieldsParams; }
- void trimHoldLists(generation_t usedGen) { _store.trimHoldLists(usedGen); }
- void transferHoldLists(generation_t generation) { _store.transferHoldLists(generation); }
- void clearHoldLists() { _store.clearHoldLists();}
- std::vector<uint32_t> startCompact() { return _store.startCompact(_typeId); }
- void finishCompact(const std::vector<uint32_t> & toHold) { _store.finishCompact(toHold); }
+ void reclaim_memory(generation_t oldest_used_gen) { _store.reclaim_memory(oldest_used_gen); }
+ void assign_generation(generation_t current_gen) { _store.assign_generation(current_gen); }
+ void reclaim_all_memory() { _store.reclaim_all_memory();}
+ std::unique_ptr<vespalib::datastore::CompactingBuffers> start_compact();
vespalib::MemoryUsage getMemoryUsage() const { return _store.getMemoryUsage(); }
- vespalib::datastore::DataStoreBase::MemStats getMemStats() const { return _store.getMemStats(); }
+ vespalib::datastore::MemoryStats getMemStats() const { return _store.getMemStats(); }
};
}
diff --git a/searchlib/src/vespa/searchlib/memoryindex/field_index.cpp b/searchlib/src/vespa/searchlib/memoryindex/field_index.cpp
index d731be7fe22..4be3031303e 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/field_index.cpp
+++ b/searchlib/src/vespa/searchlib/memoryindex/field_index.cpp
@@ -69,12 +69,12 @@ FieldIndex<interleaved_features>::~FieldIndex()
}
_postingListStore.clearBuilder();
freeze(); // Flush all pending posting list tree freezes
- transferHoldLists();
+ assign_generation();
_dict.clear(); // Clear dictionary
freeze(); // Flush pending freeze for dictionary tree.
- transferHoldLists();
+ assign_generation();
incGeneration();
- trimHoldLists();
+ reclaim_memory();
}
template <bool interleaved_features>
@@ -103,9 +103,7 @@ template <bool interleaved_features>
void
FieldIndex<interleaved_features>::compactFeatures()
{
- std::vector<uint32_t> toHold;
-
- toHold = _featureStore.startCompact();
+ auto compacting_buffers = _featureStore.start_compact();
auto itr = _dict.begin();
uint32_t packedIndex = _fieldId;
for (; itr.valid(); ++itr) {
@@ -143,9 +141,9 @@ FieldIndex<interleaved_features>::compactFeatures()
}
}
using generation_t = GenerationHandler::generation_t;
- _featureStore.finishCompact(toHold);
+ compacting_buffers->finish();
generation_t generation = _generationHandler.getCurrentGeneration();
- _featureStore.transferHoldLists(generation);
+ _featureStore.assign_generation(generation);
}
template <bool interleaved_features>
diff --git a/searchlib/src/vespa/searchlib/memoryindex/field_index.h b/searchlib/src/vespa/searchlib/memoryindex/field_index.h
index fb02ed880b4..187ec5ee971 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/field_index.h
+++ b/searchlib/src/vespa/searchlib/memoryindex/field_index.h
@@ -52,20 +52,20 @@ private:
_dict.getAllocator().freeze();
}
- void trimHoldLists() {
- GenerationHandler::generation_t usedGen =
- _generationHandler.getFirstUsedGeneration();
- _postingListStore.trimHoldLists(usedGen);
- _dict.getAllocator().trimHoldLists(usedGen);
- _featureStore.trimHoldLists(usedGen);
+ void reclaim_memory() {
+ GenerationHandler::generation_t oldest_used_gen =
+ _generationHandler.get_oldest_used_generation();
+ _postingListStore.reclaim_memory(oldest_used_gen);
+ _dict.getAllocator().reclaim_memory(oldest_used_gen);
+ _featureStore.reclaim_memory(oldest_used_gen);
}
- void transferHoldLists() {
+ void assign_generation() {
GenerationHandler::generation_t generation =
_generationHandler.getCurrentGeneration();
- _postingListStore.transferHoldLists(generation);
- _dict.getAllocator().transferHoldLists(generation);
- _featureStore.transferHoldLists(generation);
+ _postingListStore.assign_generation(generation);
+ _dict.getAllocator().assign_generation(generation);
+ _featureStore.assign_generation(generation);
}
void incGeneration() {
@@ -90,9 +90,9 @@ public:
void commit() override {
_remover.flush();
freeze();
- transferHoldLists();
+ assign_generation();
incGeneration();
- trimHoldLists();
+ reclaim_memory();
}
/**
diff --git a/searchlib/src/vespa/searchlib/predicate/predicate_index.cpp b/searchlib/src/vespa/searchlib/predicate/predicate_index.cpp
index c64c490039b..f21ca1b11cc 100644
--- a/searchlib/src/vespa/searchlib/predicate/predicate_index.cpp
+++ b/searchlib/src/vespa/searchlib/predicate/predicate_index.cpp
@@ -213,19 +213,19 @@ PredicateIndex::commit() {
}
void
-PredicateIndex::trimHoldLists(generation_t used_generation) {
- _interval_index.trimHoldLists(used_generation);
- _bounds_index.trimHoldLists(used_generation);
- _interval_store.trimHoldLists(used_generation);
- _zero_constraint_docs.getAllocator().trimHoldLists(used_generation);
+PredicateIndex::reclaim_memory(generation_t oldest_used_gen) {
+ _interval_index.reclaim_memory(oldest_used_gen);
+ _bounds_index.reclaim_memory(oldest_used_gen);
+ _interval_store.reclaim_memory(oldest_used_gen);
+ _zero_constraint_docs.getAllocator().reclaim_memory(oldest_used_gen);
}
void
-PredicateIndex::transferHoldLists(generation_t generation) {
- _interval_index.transferHoldLists(generation);
- _bounds_index.transferHoldLists(generation);
- _interval_store.transferHoldLists(generation);
- _zero_constraint_docs.getAllocator().transferHoldLists(generation);
+PredicateIndex::assign_generation(generation_t current_gen) {
+ _interval_index.assign_generation(current_gen);
+ _bounds_index.assign_generation(current_gen);
+ _interval_store.assign_generation(current_gen);
+ _zero_constraint_docs.getAllocator().assign_generation(current_gen);
}
vespalib::MemoryUsage
diff --git a/searchlib/src/vespa/searchlib/predicate/predicate_index.h b/searchlib/src/vespa/searchlib/predicate/predicate_index.h
index 1bad95c6aa9..238314e17f9 100644
--- a/searchlib/src/vespa/searchlib/predicate/predicate_index.h
+++ b/searchlib/src/vespa/searchlib/predicate/predicate_index.h
@@ -73,8 +73,8 @@ public:
void indexDocument(uint32_t doc_id, const PredicateTreeAnnotations &annotations);
void removeDocument(uint32_t doc_id);
void commit();
- void trimHoldLists(generation_t used_generation);
- void transferHoldLists(generation_t generation);
+ void reclaim_memory(generation_t oldest_used_gen);
+ void assign_generation(generation_t current_gen);
vespalib::MemoryUsage getMemoryUsage() const;
int getArity() const { return _arity; }
diff --git a/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp b/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp
index 379c859f6c3..af809b2fa69 100644
--- a/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp
+++ b/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp
@@ -100,13 +100,13 @@ PredicateIntervalStore::remove(EntryRef ref) {
}
void
-PredicateIntervalStore::trimHoldLists(generation_t used_generation) {
- _store.trimHoldLists(used_generation);
+PredicateIntervalStore::reclaim_memory(generation_t oldest_used_gen) {
+ _store.reclaim_memory(oldest_used_gen);
}
void
-PredicateIntervalStore::transferHoldLists(generation_t generation) {
- _store.transferHoldLists(generation);
+PredicateIntervalStore::assign_generation(generation_t current_gen) {
+ _store.assign_generation(current_gen);
}
}
diff --git a/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.h b/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.h
index 0b3e32ec6b7..a96c208393d 100644
--- a/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.h
+++ b/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.h
@@ -71,9 +71,9 @@ public:
*/
void remove(vespalib::datastore::EntryRef ref);
- void trimHoldLists(generation_t used_generation);
+ void reclaim_memory(generation_t oldest_used_gen);
- void transferHoldLists(generation_t generation);
+ void assign_generation(generation_t current_gen);
/**
* Return memory usage (only the data store is included)
diff --git a/searchlib/src/vespa/searchlib/predicate/simple_index.h b/searchlib/src/vespa/searchlib/predicate/simple_index.h
index 78805820a30..d49e42a1e35 100644
--- a/searchlib/src/vespa/searchlib/predicate/simple_index.h
+++ b/searchlib/src/vespa/searchlib/predicate/simple_index.h
@@ -187,8 +187,8 @@ public:
// (and after doc id limits values are determined) to promote posting lists to vectors.
void promoteOverThresholdVectors();
void commit();
- void trimHoldLists(generation_t used_generation);
- void transferHoldLists(generation_t generation);
+ void reclaim_memory(generation_t oldest_used_gen);
+ void assign_generation(generation_t current_gen);
vespalib::MemoryUsage getMemoryUsage() const;
template <typename FunctionType>
void foreach_frozen_key(vespalib::datastore::EntryRef ref, Key key, FunctionType func) const;
diff --git a/searchlib/src/vespa/searchlib/predicate/simple_index.hpp b/searchlib/src/vespa/searchlib/predicate/simple_index.hpp
index cb37fec26ea..c6f640d72ed 100644
--- a/searchlib/src/vespa/searchlib/predicate/simple_index.hpp
+++ b/searchlib/src/vespa/searchlib/predicate/simple_index.hpp
@@ -54,17 +54,17 @@ SimpleIndex<Posting, Key, DocId>::~SimpleIndex() {
_vector_posting_lists.disableElemHoldList();
_vector_posting_lists.clear();
_vector_posting_lists.getAllocator().freeze();
- _vector_posting_lists.getAllocator().clearHoldLists();
+ _vector_posting_lists.getAllocator().reclaim_all_memory();
_dictionary.disableFreeLists();
_dictionary.disableElemHoldList();
_dictionary.clear();
_dictionary.getAllocator().freeze();
- _dictionary.getAllocator().clearHoldLists();
+ _dictionary.getAllocator().reclaim_all_memory();
_btree_posting_lists.clearBuilder();
_btree_posting_lists.freeze();
- _btree_posting_lists.clearHoldLists();
+ _btree_posting_lists.reclaim_all_memory();
}
template <typename Posting, typename Key, typename DocId>
@@ -291,19 +291,19 @@ SimpleIndex<Posting, Key, DocId>::commit() {
template <typename Posting, typename Key, typename DocId>
void
-SimpleIndex<Posting, Key, DocId>::trimHoldLists(generation_t used_generation) {
- _btree_posting_lists.trimHoldLists(used_generation);
- _dictionary.getAllocator().trimHoldLists(used_generation);
- _vector_posting_lists.getAllocator().trimHoldLists(used_generation);
+SimpleIndex<Posting, Key, DocId>::reclaim_memory(generation_t oldest_used_gen) {
+ _btree_posting_lists.reclaim_memory(oldest_used_gen);
+ _dictionary.getAllocator().reclaim_memory(oldest_used_gen);
+ _vector_posting_lists.getAllocator().reclaim_memory(oldest_used_gen);
}
template <typename Posting, typename Key, typename DocId>
void
-SimpleIndex<Posting, Key, DocId>::transferHoldLists(generation_t generation) {
- _dictionary.getAllocator().transferHoldLists(generation);
- _btree_posting_lists.transferHoldLists(generation);
- _vector_posting_lists.getAllocator().transferHoldLists(generation);
+SimpleIndex<Posting, Key, DocId>::assign_generation(generation_t current_gen) {
+ _dictionary.getAllocator().assign_generation(current_gen);
+ _btree_posting_lists.assign_generation(current_gen);
+ _vector_posting_lists.getAllocator().assign_generation(current_gen);
}
template <typename Posting, typename Key, typename DocId>
diff --git a/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt b/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
index 46bfc0909aa..0d87881711c 100644
--- a/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
@@ -9,7 +9,6 @@ vespa_add_library(searchlib_tensor OBJECT
dense_tensor_attribute_saver.cpp
dense_tensor_store.cpp
direct_tensor_attribute.cpp
- direct_tensor_saver.cpp
direct_tensor_store.cpp
distance_calculator.cpp
distance_function_factory.cpp
@@ -29,14 +28,13 @@ vespa_add_library(searchlib_tensor OBJECT
nearest_neighbor_index_saver.cpp
serialized_fast_value_attribute.cpp
small_subspaces_buffer_type.cpp
- streamed_value_saver.cpp
- streamed_value_store.cpp
tensor_attribute.cpp
tensor_buffer_operations.cpp
tensor_buffer_store.cpp
tensor_buffer_type_mapper.cpp
tensor_deserialize.cpp
tensor_store.cpp
+ tensor_store_saver.cpp
reusable_set_visited_tracker.cpp
DEPENDS
)
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
index d4353532309..56b9473b6e6 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
@@ -112,7 +112,7 @@ void
DenseTensorAttribute::internal_set_tensor(DocId docid, const vespalib::eval::Value& tensor)
{
consider_remove_from_index(docid);
- EntryRef ref = _denseTensorStore.setTensor(tensor);
+ EntryRef ref = _denseTensorStore.store_tensor(tensor);
setTensorRef(docid, ref);
}
@@ -172,8 +172,8 @@ DenseTensorAttribute::DenseTensorAttribute(vespalib::stringref baseFileName, con
DenseTensorAttribute::~DenseTensorAttribute()
{
- getGenerationHolder().clearHoldLists();
- _tensorStore.clearHoldLists();
+ getGenerationHolder().reclaim_all();
+ _tensorStore.reclaim_all_memory();
}
uint32_t
@@ -229,10 +229,7 @@ DenseTensorAttribute::getTensor(DocId docId) const
if (docId < getCommittedDocIdLimit()) {
ref = acquire_entry_ref(docId);
}
- if (!ref.valid()) {
- return {};
- }
- return _denseTensorStore.getTensor(ref);
+ return _denseTensorStore.get_tensor(ref);
}
vespalib::eval::TypedCells
@@ -453,22 +450,20 @@ DenseTensorAttribute::onCommit()
}
void
-DenseTensorAttribute::onGenerationChange(generation_t next_gen)
+DenseTensorAttribute::before_inc_generation(generation_t current_gen)
{
- // TODO: Change onGenerationChange() to send current generation instead of next generation.
- // This applies for entire attribute vector code.
- TensorAttribute::onGenerationChange(next_gen);
+ TensorAttribute::before_inc_generation(current_gen);
if (_index) {
- _index->transfer_hold_lists(next_gen - 1);
+ _index->assign_generation(current_gen);
}
}
void
-DenseTensorAttribute::removeOldGenerations(generation_t first_used_gen)
+DenseTensorAttribute::reclaim_memory(generation_t oldest_used_gen)
{
- TensorAttribute::removeOldGenerations(first_used_gen);
+ TensorAttribute::reclaim_memory(oldest_used_gen);
if (_index) {
- _index->trim_hold_lists(first_used_gen);
+ _index->reclaim_memory(oldest_used_gen);
}
}
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.h b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.h
index b0991aa57aa..3aa52fe622a 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.h
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.h
@@ -47,8 +47,8 @@ public:
std::unique_ptr<AttributeSaver> onInitSave(vespalib::stringref fileName) override;
uint32_t getVersion() const override;
void onCommit() override;
- void onGenerationChange(generation_t next_gen) override;
- void removeOldGenerations(generation_t first_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
void get_state(const vespalib::slime::Inserter& inserter) const override;
void onShrinkLidSpace() override;
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
index 1bc84a7216d..60a3546578a 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
@@ -11,6 +11,7 @@
using vespalib::datastore::CompactionContext;
using vespalib::datastore::CompactionSpec;
using vespalib::datastore::CompactionStrategy;
+using vespalib::datastore::EntryRef;
using vespalib::datastore::Handle;
using vespalib::datastore::ICompactionContext;
using vespalib::eval::CellType;
@@ -120,7 +121,7 @@ DenseTensorStore::holdTensor(EntryRef ref)
}
TensorStore::EntryRef
-DenseTensorStore::move(EntryRef ref)
+DenseTensorStore::move_on_compact(EntryRef ref)
{
if (!ref.valid()) {
return RefType();
@@ -128,7 +129,6 @@ DenseTensorStore::move(EntryRef ref)
auto oldraw = getRawBuffer(ref);
auto newraw = allocRawBuffer();
memcpy(newraw.data, static_cast<const char *>(oldraw), getBufSize());
- _concreteStore.holdElem(ref, _tensorSizeCalc.alignedSize());
return newraw.ref;
}
@@ -147,19 +147,8 @@ DenseTensorStore::start_compact(const CompactionStrategy& compaction_strategy)
return std::make_unique<CompactionContext>(*this, std::move(compacting_buffers));
}
-std::unique_ptr<Value>
-DenseTensorStore::getTensor(EntryRef ref) const
-{
- if (!ref.valid()) {
- return {};
- }
- vespalib::eval::TypedCells cells_ref(getRawBuffer(ref), _type.cell_type(), getNumCells());
- return std::make_unique<vespalib::eval::DenseValueView>(_type, cells_ref);
-}
-
-template <class TensorType>
-TensorStore::EntryRef
-DenseTensorStore::setDenseTensor(const TensorType &tensor)
+EntryRef
+DenseTensorStore::store_tensor(const Value& tensor)
{
assert(tensor.type() == _type);
auto cells = tensor.cells();
@@ -170,10 +159,29 @@ DenseTensorStore::setDenseTensor(const TensorType &tensor)
return raw.ref;
}
-TensorStore::EntryRef
-DenseTensorStore::setTensor(const vespalib::eval::Value &tensor)
+EntryRef
+DenseTensorStore::store_encoded_tensor(vespalib::nbostream& encoded)
+{
+ (void) encoded;
+ abort();
+}
+
+std::unique_ptr<Value>
+DenseTensorStore::get_tensor(EntryRef ref) const
+{
+ if (!ref.valid()) {
+ return {};
+ }
+ vespalib::eval::TypedCells cells_ref(getRawBuffer(ref), _type.cell_type(), getNumCells());
+ return std::make_unique<vespalib::eval::DenseValueView>(_type, cells_ref);
+}
+
+bool
+DenseTensorStore::encode_stored_tensor(EntryRef ref, vespalib::nbostream& target) const
{
- return setDenseTensor(tensor);
+ (void) ref;
+ (void) target;
+ abort();
}
}
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
index bd83772ee55..298e58ee410 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
@@ -51,10 +51,6 @@ private:
BufferType _bufferType;
ValueType _type; // type of dense tensor
std::vector<char> _emptySpace;
-
- template <class TensorType>
- TensorStore::EntryRef
- setDenseTensor(const TensorType &tensor);
public:
DenseTensorStore(const ValueType &type, std::shared_ptr<vespalib::alloc::MemoryAllocator> allocator);
~DenseTensorStore() override;
@@ -67,15 +63,18 @@ public:
}
vespalib::datastore::Handle<char> allocRawBuffer();
void holdTensor(EntryRef ref) override;
- EntryRef move(EntryRef ref) override;
+ EntryRef move_on_compact(EntryRef ref) override;
vespalib::MemoryUsage update_stat(const vespalib::datastore::CompactionStrategy& compaction_strategy) override;
std::unique_ptr<vespalib::datastore::ICompactionContext> start_compact(const vespalib::datastore::CompactionStrategy& compaction_strategy) override;
- std::unique_ptr<vespalib::eval::Value> getTensor(EntryRef ref) const;
+ EntryRef store_tensor(const vespalib::eval::Value &tensor) override;
+ EntryRef store_encoded_tensor(vespalib::nbostream &encoded) override;
+ std::unique_ptr<vespalib::eval::Value> get_tensor(EntryRef ref) const override;
+ bool encode_stored_tensor(EntryRef ref, vespalib::nbostream &target) const override;
+
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/direct_tensor_attribute.cpp b/searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.cpp
index e2271e63425..22db5dc5b47 100644
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.cpp
@@ -1,16 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "direct_tensor_attribute.h"
-#include "direct_tensor_saver.h"
-
#include <vespa/eval/eval/fast_value.h>
#include <vespa/eval/eval/value.h>
-#include <vespa/searchlib/attribute/readerbase.h>
-#include <vespa/searchlib/util/fileutil.h>
-#include <vespa/vespalib/util/array.h>
-
-#include "blob_sequence_reader.h"
-#include "tensor_deserialize.h"
using vespalib::eval::FastValueBuilderFactory;
@@ -23,41 +15,8 @@ DirectTensorAttribute::DirectTensorAttribute(stringref name, const Config &cfg)
DirectTensorAttribute::~DirectTensorAttribute()
{
- getGenerationHolder().clearHoldLists();
- _tensorStore.clearHoldLists();
-}
-
-bool
-DirectTensorAttribute::onLoad(vespalib::Executor *)
-{
- BlobSequenceReader tensorReader(*this);
- if (!tensorReader.hasData()) {
- return false;
- }
- setCreateSerialNum(tensorReader.getCreateSerialNum());
- assert(tensorReader.getVersion() == getVersion());
- uint32_t numDocs = tensorReader.getDocIdLimit();
- _refVector.reset();
- _refVector.unsafe_reserve(numDocs);
- vespalib::Array<char> buffer(1024);
- for (uint32_t lid = 0; lid < numDocs; ++lid) {
- uint32_t tensorSize = tensorReader.getNextSize();
- if (tensorSize != 0) {
- if (tensorSize > buffer.size()) {
- buffer.resize(tensorSize + 1024);
- }
- tensorReader.readBlob(&buffer[0], tensorSize);
- auto tensor = deserialize_tensor(&buffer[0], tensorSize);
- EntryRef ref = _direct_store.store_tensor(std::move(tensor));
- _refVector.push_back(AtomicEntryRef(ref));
- } else {
- EntryRef invalid;
- _refVector.push_back(AtomicEntryRef(invalid));
- }
- }
- setNumDocs(numDocs);
- setCommittedDocIdLimit(numDocs);
- return true;
+ getGenerationHolder().reclaim_all();
+ _tensorStore.reclaim_all_memory();
}
void
@@ -84,7 +43,7 @@ DirectTensorAttribute::update_tensor(DocId docId,
ref = _refVector[docId].load_relaxed();
}
if (ref.valid()) {
- auto ptr = _direct_store.get_tensor(ref);
+ auto ptr = _direct_store.get_tensor_ptr(ref);
if (ptr) {
auto new_value = update.apply_to(*ptr, FastValueBuilderFactory::get());
if (new_value) {
@@ -109,7 +68,7 @@ DirectTensorAttribute::getTensor(DocId docId) const
ref = acquire_entry_ref(docId);
}
if (ref.valid()) {
- auto ptr = _direct_store.get_tensor(ref);
+ auto ptr = _direct_store.get_tensor_ptr(ref);
if (ptr) {
return FastValueBuilderFactory::get().copy(*ptr);
}
@@ -123,21 +82,10 @@ DirectTensorAttribute::get_tensor_ref(DocId docId) const
{
if (docId >= getCommittedDocIdLimit()) { return *_emptyTensor; }
- auto ptr = _direct_store.get_tensor(acquire_entry_ref(docId));
+ auto ptr = _direct_store.get_tensor_ptr(acquire_entry_ref(docId));
if ( ptr == nullptr) { return *_emptyTensor; }
return *ptr;
}
-std::unique_ptr<AttributeSaver>
-DirectTensorAttribute::onInitSave(vespalib::stringref fileName)
-{
- vespalib::GenerationHandler::Guard guard(getGenerationHandler().takeGuard());
- return std::make_unique<DirectTensorAttributeSaver>
- (std::move(guard),
- this->createAttributeHeader(fileName),
- getRefCopy(),
- _direct_store);
-}
-
} // namespace
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.h b/searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.h
index 2dfb5c1efcd..6466c6f7537 100644
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.h
+++ b/searchlib/src/vespa/searchlib/tensor/direct_tensor_attribute.h
@@ -21,8 +21,6 @@ public:
const document::TensorUpdate &update,
bool create_empty_if_non_existing) override;
std::unique_ptr<vespalib::eval::Value> getTensor(DocId docId) const override;
- bool onLoad(vespalib::Executor *executor) override;
- std::unique_ptr<AttributeSaver> onInitSave(vespalib::stringref fileName) override;
void set_tensor(DocId docId, std::unique_ptr<vespalib::eval::Value> tensor);
const vespalib::eval::Value &get_tensor_ref(DocId docId) const override;
bool supports_get_tensor_ref() const override { return true; }
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_saver.cpp b/searchlib/src/vespa/searchlib/tensor/direct_tensor_saver.cpp
deleted file mode 100644
index 024b2fe5467..00000000000
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_saver.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "direct_tensor_saver.h"
-#include "direct_tensor_store.h"
-
-#include <vespa/eval/eval/value_codec.h>
-#include <vespa/searchlib/attribute/iattributesavetarget.h>
-#include <vespa/searchlib/util/bufferwriter.h>
-#include <vespa/vespalib/objects/nbostream.h>
-
-using vespalib::GenerationHandler;
-
-namespace search::tensor {
-
-DirectTensorAttributeSaver::
-DirectTensorAttributeSaver(GenerationHandler::Guard &&guard,
- const attribute::AttributeHeader &header,
- RefCopyVector &&refs,
- const DirectTensorStore &tensorStore)
- : AttributeSaver(std::move(guard), header),
- _refs(std::move(refs)),
- _tensorStore(tensorStore)
-{
-}
-
-
-DirectTensorAttributeSaver::~DirectTensorAttributeSaver()
-{
-}
-
-bool
-DirectTensorAttributeSaver::onSave(IAttributeSaveTarget &saveTarget)
-{
- auto datWriter = saveTarget.datWriter().allocBufferWriter();
- const uint32_t docIdLimit(_refs.size());
- vespalib::nbostream stream;
- for (uint32_t lid = 0; lid < docIdLimit; ++lid) {
- const vespalib::eval::Value *tensor = _tensorStore.get_tensor(_refs[lid]);
- if (tensor) {
- stream.clear();
- encode_value(*tensor, stream);
- uint32_t sz = stream.size();
- datWriter->write(&sz, sizeof(sz));
- datWriter->write(stream.peek(), stream.size());
- } else {
- uint32_t sz = 0;
- datWriter->write(&sz, sizeof(sz));
- }
- }
- datWriter->flush();
- return true;
-}
-
-} // namespace search::tensor
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_saver.h b/searchlib/src/vespa/searchlib/tensor/direct_tensor_saver.h
deleted file mode 100644
index 132e1570f0f..00000000000
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_saver.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#pragma once
-
-#include <vespa/searchlib/attribute/attributesaver.h>
-#include "tensor_attribute.h"
-
-namespace search::tensor {
-
-class DirectTensorStore;
-
-/*
- * Class for saving a tensor attribute.
- */
-class DirectTensorAttributeSaver : public AttributeSaver
-{
-public:
- using RefCopyVector = TensorAttribute::RefCopyVector;
-private:
- using GenerationHandler = vespalib::GenerationHandler;
-
- RefCopyVector _refs;
- const DirectTensorStore &_tensorStore;
-
- bool onSave(IAttributeSaveTarget &saveTarget) override;
-public:
- DirectTensorAttributeSaver(GenerationHandler::Guard &&guard,
- const attribute::AttributeHeader &header,
- RefCopyVector &&refs,
- const DirectTensorStore &tensorStore);
-
- virtual ~DirectTensorAttributeSaver();
-};
-
-} // namespace search::tensor
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
index fa3b1486c84..013e7dedeba 100644
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
@@ -1,7 +1,10 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "direct_tensor_store.h"
+#include "tensor_deserialize.h"
+#include <vespa/eval/eval/fast_value.h>
#include <vespa/eval/eval/value.h>
+#include <vespa/eval/eval/value_codec.h>
#include <vespa/vespalib/datastore/compacting_buffers.h>
#include <vespa/vespalib/datastore/compaction_context.h>
#include <vespa/vespalib/datastore/compaction_strategy.h>
@@ -14,6 +17,8 @@ using vespalib::datastore::CompactionSpec;
using vespalib::datastore::CompactionStrategy;
using vespalib::datastore::EntryRef;
using vespalib::datastore::ICompactionContext;
+using vespalib::eval::FastValueBuilderFactory;
+using vespalib::eval::Value;
namespace search::tensor {
@@ -41,7 +46,7 @@ DirectTensorStore::add_entry(TensorSP tensor)
{
auto ref = _tensor_store.addEntry(tensor);
auto& state = _tensor_store.getBufferState(RefType(ref).bufferId());
- state.incExtraUsedBytes(tensor->get_memory_usage().allocatedBytes());
+ state.stats().inc_extra_used_bytes(tensor->get_memory_usage().allocatedBytes());
return ref;
}
@@ -54,13 +59,6 @@ DirectTensorStore::DirectTensorStore()
DirectTensorStore::~DirectTensorStore() = default;
-EntryRef
-DirectTensorStore::store_tensor(std::unique_ptr<vespalib::eval::Value> tensor)
-{
- assert(tensor);
- return add_entry(TensorSP(std::move(tensor)));
-}
-
void
DirectTensorStore::holdTensor(EntryRef ref)
{
@@ -73,16 +71,14 @@ DirectTensorStore::holdTensor(EntryRef ref)
}
EntryRef
-DirectTensorStore::move(EntryRef ref)
+DirectTensorStore::move_on_compact(EntryRef ref)
{
if (!ref.valid()) {
return EntryRef();
}
const auto& old_tensor = _tensor_store.getEntry(ref);
assert(old_tensor);
- auto new_ref = add_entry(old_tensor);
- _tensor_store.holdElem(ref, 1, old_tensor->get_memory_usage().allocatedBytes());
- return new_ref;
+ return add_entry(old_tensor);
}
vespalib::MemoryUsage
@@ -100,4 +96,42 @@ DirectTensorStore::start_compact(const CompactionStrategy& compaction_strategy)
return std::make_unique<CompactionContext>(*this, std::move(compacting_buffers));
}
+EntryRef
+DirectTensorStore::store_tensor(std::unique_ptr<Value> tensor)
+{
+ assert(tensor);
+ return add_entry(std::move(tensor));
+}
+
+EntryRef
+DirectTensorStore::store_tensor(const Value& tensor)
+{
+ return add_entry(FastValueBuilderFactory::get().copy(tensor));
+}
+
+EntryRef
+DirectTensorStore::store_encoded_tensor(vespalib::nbostream& encoded)
+{
+ return add_entry(deserialize_tensor(encoded));
+}
+
+std::unique_ptr<Value>
+DirectTensorStore::get_tensor(EntryRef ref) const
+{
+ if (!ref.valid()) {
+ return {};
+ }
+ return FastValueBuilderFactory::get().copy(*_tensor_store.getEntry(ref));
+}
+
+bool
+DirectTensorStore::encode_stored_tensor(EntryRef ref, vespalib::nbostream& target) const
+{
+ if (!ref.valid()) {
+ return false;
+ }
+ vespalib::eval::encode_value(*_tensor_store.getEntry(ref), target);
+ return true;
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
index 57e7453ff99..c55dda5646a 100644
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
@@ -40,7 +40,7 @@ public:
~DirectTensorStore() override;
using RefType = TensorStoreType::RefType;
- const vespalib::eval::Value * get_tensor(EntryRef ref) const {
+ const vespalib::eval::Value * get_tensor_ptr(EntryRef ref) const {
if (!ref.valid()) {
return nullptr;
}
@@ -49,9 +49,13 @@ public:
EntryRef store_tensor(std::unique_ptr<vespalib::eval::Value> tensor);
void holdTensor(EntryRef ref) override;
- EntryRef move(EntryRef ref) override;
+ EntryRef move_on_compact(EntryRef ref) override;
vespalib::MemoryUsage update_stat(const vespalib::datastore::CompactionStrategy& compaction_strategy) override;
std::unique_ptr<vespalib::datastore::ICompactionContext> start_compact(const vespalib::datastore::CompactionStrategy& compaction_strategy) override;
+ EntryRef store_tensor(const vespalib::eval::Value& tensor) override;
+ EntryRef store_encoded_tensor(vespalib::nbostream& encoded) override;
+ std::unique_ptr<vespalib::eval::Value> get_tensor(EntryRef ref) const override;
+ bool encode_stored_tensor(EntryRef ref, vespalib::nbostream& target) const override;
};
}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp b/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
index d23bbcfbed4..10f06a1e1ec 100644
--- a/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
@@ -511,21 +511,21 @@ HnswIndex::remove_document(uint32_t docid)
}
void
-HnswIndex::transfer_hold_lists(generation_t current_gen)
+HnswIndex::assign_generation(generation_t current_gen)
{
// Note: RcuVector transfers hold lists as part of reallocation based on current generation.
// We need to set the next generation here, as it is incremented on a higher level right after this call.
_graph.node_refs.setGeneration(current_gen + 1);
- _graph.nodes.transferHoldLists(current_gen);
- _graph.links.transferHoldLists(current_gen);
+ _graph.nodes.assign_generation(current_gen);
+ _graph.links.assign_generation(current_gen);
}
void
-HnswIndex::trim_hold_lists(generation_t first_used_gen)
+HnswIndex::reclaim_memory(generation_t oldest_used_gen)
{
- _graph.node_refs.removeOldGenerations(first_used_gen);
- _graph.nodes.trimHoldLists(first_used_gen);
- _graph.links.trimHoldLists(first_used_gen);
+ _graph.node_refs.reclaim_memory(oldest_used_gen);
+ _graph.nodes.reclaim_memory(oldest_used_gen);
+ _graph.links.reclaim_memory(oldest_used_gen);
}
void
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index.h b/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
index e3ffada1fc2..8a7422907ea 100644
--- a/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
@@ -187,8 +187,8 @@ public:
vespalib::GenerationHandler::Guard read_guard) const override;
void complete_add_document(uint32_t docid, std::unique_ptr<PrepareResult> prepare_result) override;
void remove_document(uint32_t docid) override;
- void transfer_hold_lists(generation_t current_gen) override;
- void trim_hold_lists(generation_t first_used_gen) override;
+ void assign_generation(generation_t current_gen) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
void compact_level_arrays(CompactionSpec compaction_spec, const CompactionStrategy& compaction_strategy);
void compact_link_arrays(CompactionSpec compaction_spec, const CompactionStrategy& compaction_strategy);
bool consider_compact_level_arrays(const CompactionStrategy& compaction_strategy);
diff --git a/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp b/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp
index cdd4d35c1df..cb074348f08 100644
--- a/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp
@@ -56,7 +56,7 @@ LargeSubspacesBufferType::fallbackCopy(void *newBuffer, const void *oldBuffer, E
auto& old_elem = old_elems[i];
new (new_elems + i) ArrayType(old_elem);
if (!old_elem.empty()) {
- _ops.copied_labels({old_elem.data(), old_elem.size()});
+ _ops.copied_labels(unconstify(vespalib::ConstArrayRef<char>(old_elem.data(), old_elem.size())));
}
}
}
diff --git a/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
index 51d66fdd14d..d40803dcafd 100644
--- a/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
+++ b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
@@ -68,8 +68,8 @@ public:
virtual void complete_add_document(uint32_t docid, std::unique_ptr<PrepareResult> prepare_result) = 0;
virtual void remove_document(uint32_t docid) = 0;
- virtual void transfer_hold_lists(generation_t current_gen) = 0;
- virtual void trim_hold_lists(generation_t first_used_gen) = 0;
+ virtual void assign_generation(generation_t current_gen) = 0;
+ virtual void reclaim_memory(generation_t first_used_gen) = 0;
virtual bool consider_compact(const CompactionStrategy& compaction_strategy) = 0;
virtual vespalib::MemoryUsage update_stat(const CompactionStrategy& compaction_strategy) = 0;
virtual vespalib::MemoryUsage memory_usage() const = 0;
diff --git a/searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.cpp b/searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.cpp
index 94bf3f1a37b..bb1c1a3d880 100644
--- a/searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.cpp
@@ -1,17 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "serialized_fast_value_attribute.h"
-#include "streamed_value_saver.h"
#include <vespa/eval/eval/value.h>
-#include <vespa/fastlib/io/bufferedfile.h>
#include <vespa/searchcommon/attribute/config.h>
#include <vespa/log/log.h>
LOG_SETUP(".searchlib.tensor.serialized_fast_value_attribute");
-#include "blob_sequence_reader.h"
-
using namespace vespalib;
using namespace vespalib::eval;
@@ -20,15 +16,15 @@ namespace search::tensor {
SerializedFastValueAttribute::SerializedFastValueAttribute(stringref name, const Config &cfg)
: TensorAttribute(name, cfg, _tensorBufferStore),
_tensor_type(cfg.tensorType()),
- _tensorBufferStore(_tensor_type, {}, 1000u)
+ _tensorBufferStore(_tensor_type, get_memory_allocator(), 1000u)
{
}
SerializedFastValueAttribute::~SerializedFastValueAttribute()
{
- getGenerationHolder().clearHoldLists();
- _tensorStore.clearHoldLists();
+ getGenerationHolder().reclaim_all();
+ _tensorStore.reclaim_all_memory();
}
void
@@ -50,50 +46,4 @@ SerializedFastValueAttribute::getTensor(DocId docId) const
return _tensorBufferStore.get_tensor(ref);
}
-bool
-SerializedFastValueAttribute::onLoad(vespalib::Executor *)
-{
- BlobSequenceReader tensorReader(*this);
- if (!tensorReader.hasData()) {
- return false;
- }
- setCreateSerialNum(tensorReader.getCreateSerialNum());
- assert(tensorReader.getVersion() == getVersion());
- uint32_t numDocs(tensorReader.getDocIdLimit());
- _refVector.reset();
- _refVector.unsafe_reserve(numDocs);
- vespalib::Array<char> buffer(1024);
- for (uint32_t lid = 0; lid < numDocs; ++lid) {
- uint32_t tensorSize = tensorReader.getNextSize();
- if (tensorSize != 0) {
- if (tensorSize > buffer.size()) {
- buffer.resize(tensorSize + 1024);
- }
- tensorReader.readBlob(&buffer[0], tensorSize);
- vespalib::nbostream source(&buffer[0], tensorSize);
- EntryRef ref = _tensorBufferStore.store_encoded_tensor(source);
- _refVector.push_back(AtomicEntryRef(ref));
- } else {
- EntryRef invalid;
- _refVector.push_back(AtomicEntryRef(invalid));
- }
- }
- setNumDocs(numDocs);
- setCommittedDocIdLimit(numDocs);
- return true;
-}
-
-
-std::unique_ptr<AttributeSaver>
-SerializedFastValueAttribute::onInitSave(vespalib::stringref fileName)
-{
- vespalib::GenerationHandler::Guard guard(getGenerationHandler().
- takeGuard());
- return std::make_unique<StreamedValueSaver>
- (std::move(guard),
- this->createAttributeHeader(fileName),
- getRefCopy(),
- _tensorBufferStore);
-}
-
}
diff --git a/searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.h b/searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.h
index 5dd49a2bbc4..2124ddeb70a 100644
--- a/searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.h
+++ b/searchlib/src/vespa/searchlib/tensor/serialized_fast_value_attribute.h
@@ -24,8 +24,6 @@ public:
~SerializedFastValueAttribute() override;
void setTensor(DocId docId, const vespalib::eval::Value &tensor) override;
std::unique_ptr<vespalib::eval::Value> getTensor(DocId docId) const override;
- bool onLoad(vespalib::Executor *executor) override;
- std::unique_ptr<AttributeSaver> onInitSave(vespalib::stringref fileName) override;
};
}
diff --git a/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp b/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp
index adbd3dee2b7..ba27f017022 100644
--- a/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp
@@ -46,7 +46,7 @@ SmallSubspacesBufferType::fallbackCopy(void *newBuffer, const void *oldBuffer, E
memcpy(newBuffer, oldBuffer, numElems);
const char *elem = static_cast<const char *>(oldBuffer);
while (numElems >= getArraySize()) {
- _ops.copied_labels(vespalib::ConstArrayRef<char>(elem, getArraySize()));
+ _ops.copied_labels(unconstify(vespalib::ConstArrayRef<char>(elem, getArraySize())));
elem += getArraySize();
numElems -= getArraySize();
}
diff --git a/searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp b/searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp
deleted file mode 100644
index b4fddec25b3..00000000000
--- a/searchlib/src/vespa/searchlib/tensor/streamed_value_store.cpp
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "streamed_value_store.h"
-#include <vespa/eval/eval/value.h>
-#include <vespa/eval/eval/value_codec.h>
-#include <vespa/eval/eval/fast_value.hpp>
-#include <vespa/eval/streamed/streamed_value_builder_factory.h>
-#include <vespa/eval/streamed/streamed_value_view.h>
-#include <vespa/vespalib/datastore/buffer_type.hpp>
-#include <vespa/vespalib/datastore/compacting_buffers.h>
-#include <vespa/vespalib/datastore/compaction_context.h>
-#include <vespa/vespalib/datastore/compaction_strategy.h>
-#include <vespa/vespalib/datastore/datastore.hpp>
-#include <vespa/vespalib/objects/nbostream.h>
-#include <vespa/vespalib/util/size_literals.h>
-#include <vespa/vespalib/util/typify.h>
-#include <vespa/log/log.h>
-
-LOG_SETUP(".searchlib.tensor.streamed_value_store");
-
-using vespalib::datastore::CompactionContext;
-using vespalib::datastore::CompactionSpec;
-using vespalib::datastore::CompactionStrategy;
-using vespalib::datastore::EntryRef;
-using vespalib::datastore::Handle;
-using vespalib::datastore::ICompactionContext;
-using namespace vespalib::eval;
-using vespalib::ConstArrayRef;
-using vespalib::MemoryUsage;
-using vespalib::string_id;
-using vespalib::StringIdVector;
-
-namespace search::tensor {
-
-//-----------------------------------------------------------------------------
-
-namespace {
-
-template <typename CT, typename F>
-void each_subspace(const Value &value, size_t num_mapped, size_t dense_size, F f) {
- size_t subspace;
- std::vector<string_id> addr(num_mapped);
- std::vector<string_id*> refs;
- refs.reserve(addr.size());
- for (string_id &label: addr) {
- refs.push_back(&label);
- }
- auto cells = value.cells().typify<CT>();
- auto view = value.index().create_view({});
- view->lookup({});
- while (view->next_result(refs, subspace)) {
- size_t offset = subspace * dense_size;
- f(ConstArrayRef<string_id>(addr), ConstArrayRef<CT>(cells.begin() + offset, dense_size));
- }
-}
-
-using TensorEntry = StreamedValueStore::TensorEntry;
-
-struct CreateTensorEntry {
- template <typename CT>
- static TensorEntry::SP invoke(const Value &value, size_t num_mapped, size_t dense_size) {
- using EntryImpl = StreamedValueStore::TensorEntryImpl<CT>;
- return std::make_shared<EntryImpl>(value, num_mapped, dense_size);
- }
-};
-
-struct MyFastValueView final : Value {
- const ValueType &my_type;
- FastValueIndex my_index;
- TypedCells my_cells;
- MyFastValueView(const ValueType &type_ref, const StringIdVector &handle_view, TypedCells cells, size_t num_mapped, size_t num_spaces)
- : my_type(type_ref),
- my_index(num_mapped, handle_view, num_spaces),
- my_cells(cells)
- {
- const StringIdVector &labels = handle_view;
- for (size_t i = 0; i < num_spaces; ++i) {
- ConstArrayRef<string_id> addr(labels.data() + (i * num_mapped), num_mapped);
- my_index.map.add_mapping(FastAddrMap::hash_labels(addr));
- }
- assert(my_index.map.size() == num_spaces);
- }
- const ValueType &type() const override { return my_type; }
- const Value::Index &index() const override { return my_index; }
- TypedCells cells() const override { return my_cells; }
- MemoryUsage get_memory_usage() const override {
- MemoryUsage usage = self_memory_usage<MyFastValueView>();
- usage.merge(my_index.map.estimate_extra_memory_usage());
- return usage;
- }
-};
-
-} // <unnamed>
-
-//-----------------------------------------------------------------------------
-
-StreamedValueStore::TensorEntry::~TensorEntry() = default;
-
-StreamedValueStore::TensorEntry::SP
-StreamedValueStore::TensorEntry::create_shared_entry(const Value &value)
-{
- size_t num_mapped = value.type().count_mapped_dimensions();
- size_t dense_size = value.type().dense_subspace_size();
- return vespalib::typify_invoke<1,TypifyCellType,CreateTensorEntry>(value.type().cell_type(), value, num_mapped, dense_size);
-}
-
-template <typename CT>
-StreamedValueStore::TensorEntryImpl<CT>::TensorEntryImpl(const Value &value, size_t num_mapped, size_t dense_size)
- : handles(),
- cells()
-{
- handles.reserve(num_mapped * value.index().size());
- cells.reserve(dense_size * value.index().size());
- auto store_subspace = [&](auto addr, auto data) {
- for (string_id label: addr) {
- handles.push_back(label);
- }
- for (CT entry: data) {
- cells.push_back(entry);
- }
- };
- each_subspace<CT>(value, num_mapped, dense_size, store_subspace);
-}
-
-template <typename CT>
-Value::UP
-StreamedValueStore::TensorEntryImpl<CT>::create_fast_value_view(const ValueType &type_ref) const
-{
- size_t num_mapped = type_ref.count_mapped_dimensions();
- size_t dense_size = type_ref.dense_subspace_size();
- size_t num_spaces = cells.size() / dense_size;
- assert(dense_size * num_spaces == cells.size());
- assert(num_mapped * num_spaces == handles.view().size());
- return std::make_unique<MyFastValueView>(type_ref, handles.view(), TypedCells(cells), num_mapped, num_spaces);
-}
-
-template <typename CT>
-void
-StreamedValueStore::TensorEntryImpl<CT>::encode_value(const ValueType &type, vespalib::nbostream &target) const
-{
- size_t num_mapped = type.count_mapped_dimensions();
- size_t dense_size = type.dense_subspace_size();
- size_t num_spaces = cells.size() / dense_size;
- assert(dense_size * num_spaces == cells.size());
- assert(num_mapped * num_spaces == handles.view().size());
- StreamedValueView my_value(type, num_mapped, TypedCells(cells), num_spaces, handles.view());
- ::vespalib::eval::encode_value(my_value, target);
-}
-
-template <typename CT>
-MemoryUsage
-StreamedValueStore::TensorEntryImpl<CT>::get_memory_usage() const
-{
- MemoryUsage usage = self_memory_usage<TensorEntryImpl<CT>>();
- usage.merge(vector_extra_memory_usage(handles.view()));
- usage.merge(vector_extra_memory_usage(cells));
- return usage;
-}
-
-template <typename CT>
-StreamedValueStore::TensorEntryImpl<CT>::~TensorEntryImpl() = default;
-
-//-----------------------------------------------------------------------------
-
-constexpr size_t MIN_BUFFER_ARRAYS = 8_Ki;
-
-StreamedValueStore::TensorBufferType::TensorBufferType() noexcept
- : ParentType(1, MIN_BUFFER_ARRAYS, TensorStoreType::RefType::offsetSize())
-{
-}
-
-void
-StreamedValueStore::TensorBufferType::cleanHold(void* buffer, size_t offset, ElemCount num_elems, CleanContext clean_ctx)
-{
- TensorEntry::SP* elem = static_cast<TensorEntry::SP*>(buffer) + offset;
- const auto& empty = empty_entry();
- for (size_t i = 0; i < num_elems; ++i) {
- clean_ctx.extraBytesCleaned((*elem)->get_memory_usage().allocatedBytes());
- *elem = empty;
- ++elem;
- }
-}
-
-StreamedValueStore::StreamedValueStore(const ValueType &tensor_type)
- : TensorStore(_concrete_store),
- _concrete_store(std::make_unique<TensorBufferType>()),
- _tensor_type(tensor_type)
-{
- _concrete_store.enableFreeLists();
-}
-
-StreamedValueStore::~StreamedValueStore() = default;
-
-EntryRef
-StreamedValueStore::add_entry(TensorEntry::SP tensor)
-{
- auto ref = _concrete_store.addEntry(tensor);
- auto& state = _concrete_store.getBufferState(RefType(ref).bufferId());
- state.incExtraUsedBytes(tensor->get_memory_usage().allocatedBytes());
- return ref;
-}
-
-const StreamedValueStore::TensorEntry *
-StreamedValueStore::get_tensor_entry(EntryRef ref) const
-{
- if (!ref.valid()) {
- return nullptr;
- }
- const auto& entry = _concrete_store.getEntry(ref);
- assert(entry);
- return entry.get();
-}
-
-std::unique_ptr<vespalib::eval::Value>
-StreamedValueStore::get_tensor(EntryRef ref) const
-{
- if (const auto * ptr = get_tensor_entry(ref)) {
- return ptr->create_fast_value_view(_tensor_type);
- }
- return {};
-}
-
-void
-StreamedValueStore::holdTensor(EntryRef ref)
-{
- if (!ref.valid()) {
- return;
- }
- const auto& tensor = _concrete_store.getEntry(ref);
- assert(tensor);
- _concrete_store.holdElem(ref, 1, tensor->get_memory_usage().allocatedBytes());
-}
-
-TensorStore::EntryRef
-StreamedValueStore::move(EntryRef ref)
-{
- if (!ref.valid()) {
- return EntryRef();
- }
- const auto& old_tensor = _concrete_store.getEntry(ref);
- assert(old_tensor);
- auto new_ref = add_entry(old_tensor);
- _concrete_store.holdElem(ref, 1, old_tensor->get_memory_usage().allocatedBytes());
- return new_ref;
-}
-
-vespalib::MemoryUsage
-StreamedValueStore::update_stat(const CompactionStrategy& compaction_strategy)
-{
- auto memory_usage = _store.getMemoryUsage();
- _compaction_spec = CompactionSpec(compaction_strategy.should_compact_memory(memory_usage), false);
- return memory_usage;
-}
-
-std::unique_ptr<ICompactionContext>
-StreamedValueStore::start_compact(const CompactionStrategy& compaction_strategy)
-{
- auto compacting_buffers = _store.start_compact_worst_buffers(_compaction_spec, compaction_strategy);
- return std::make_unique<CompactionContext>(*this, std::move(compacting_buffers));
-}
-
-bool
-StreamedValueStore::encode_stored_tensor(EntryRef ref, vespalib::nbostream &target) const
-{
- if (const auto * entry = get_tensor_entry(ref)) {
- entry->encode_value(_tensor_type, target);
- return true;
- } else {
- return false;
- }
-}
-
-TensorStore::EntryRef
-StreamedValueStore::store_tensor(const Value &tensor)
-{
- assert(tensor.type() == _tensor_type);
- return add_entry(TensorEntry::create_shared_entry(tensor));
-}
-
-TensorStore::EntryRef
-StreamedValueStore::store_encoded_tensor(vespalib::nbostream &encoded)
-{
- const auto &factory = StreamedValueBuilderFactory::get();
- auto val = vespalib::eval::decode_value(encoded, factory);
- return store_tensor(*val);
-}
-
-}
diff --git a/searchlib/src/vespa/searchlib/tensor/streamed_value_store.h b/searchlib/src/vespa/searchlib/tensor/streamed_value_store.h
deleted file mode 100644
index 58137e316dd..00000000000
--- a/searchlib/src/vespa/searchlib/tensor/streamed_value_store.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#pragma once
-
-#include "tensor_store.h"
-#include <vespa/eval/eval/value_type.h>
-#include <vespa/eval/eval/value.h>
-#include <vespa/eval/streamed/streamed_value.h>
-#include <vespa/vespalib/datastore/datastore.h>
-#include <vespa/vespalib/objects/nbostream.h>
-#include <vespa/vespalib/util/shared_string_repo.h>
-
-namespace search::tensor {
-
-/**
- * Class for StreamedValue tensors in memory.
- */
-class StreamedValueStore : public TensorStore {
-public:
- using Value = vespalib::eval::Value;
- using ValueType = vespalib::eval::ValueType;
- using Handles = vespalib::SharedStringRepo::Handles;
- using MemoryUsage = vespalib::MemoryUsage;
-
- // interface for tensor entries
- struct TensorEntry {
- using SP = std::shared_ptr<TensorEntry>;
- virtual Value::UP create_fast_value_view(const ValueType &type_ref) const = 0;
- virtual void encode_value(const ValueType &type, vespalib::nbostream &target) const = 0;
- virtual MemoryUsage get_memory_usage() const = 0;
- virtual ~TensorEntry();
- static TensorEntry::SP create_shared_entry(const Value &value);
- };
-
- // implementation of tensor entries
- template <typename CT>
- struct TensorEntryImpl : public TensorEntry {
- Handles handles;
- std::vector<CT> cells;
- TensorEntryImpl(const Value &value, size_t num_mapped, size_t dense_size);
- Value::UP create_fast_value_view(const ValueType &type_ref) const override;
- void encode_value(const ValueType &type, vespalib::nbostream &target) const override;
- MemoryUsage get_memory_usage() const override;
- ~TensorEntryImpl() override;
- };
-
-private:
- // Note: Must use SP (instead of UP) because of fallbackCopy() and initializeReservedElements() in BufferType,
- // and implementation of move().
- using TensorStoreType = vespalib::datastore::DataStore<TensorEntry::SP>;
-
- class TensorBufferType : public vespalib::datastore::BufferType<TensorEntry::SP> {
- private:
- using ParentType = BufferType<TensorEntry::SP>;
- using ParentType::empty_entry;
- using CleanContext = typename ParentType::CleanContext;
- public:
- TensorBufferType() noexcept;
- void cleanHold(void* buffer, size_t offset, ElemCount num_elems, CleanContext clean_ctx) override;
- };
- TensorStoreType _concrete_store;
- const vespalib::eval::ValueType _tensor_type;
- EntryRef add_entry(TensorEntry::SP tensor);
- const TensorEntry* get_tensor_entry(EntryRef ref) const;
-public:
- StreamedValueStore(const vespalib::eval::ValueType &tensor_type);
- ~StreamedValueStore() override;
-
- using RefType = TensorStoreType::RefType;
-
- void holdTensor(EntryRef ref) override;
- EntryRef move(EntryRef ref) override;
- vespalib::MemoryUsage update_stat(const vespalib::datastore::CompactionStrategy& compaction_strategy) override;
- std::unique_ptr<vespalib::datastore::ICompactionContext> start_compact(const vespalib::datastore::CompactionStrategy& compaction_strategy) override;
-
- std::unique_ptr<vespalib::eval::Value> get_tensor(EntryRef ref) const;
- bool encode_stored_tensor(EntryRef ref, vespalib::nbostream &target) const;
-
- EntryRef store_tensor(const vespalib::eval::Value &tensor);
- EntryRef store_encoded_tensor(vespalib::nbostream &encoded);
-};
-
-
-}
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp b/searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp
index cdaea07176a..f29751cdabe 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp
@@ -1,6 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "tensor_attribute.h"
+#include "blob_sequence_reader.h"
+#include "tensor_store_saver.h"
#include <vespa/document/base/exceptions.h>
#include <vespa/document/datatype/tensor_data_type.h>
#include <vespa/searchlib/attribute/address_space_components.h>
@@ -108,17 +110,17 @@ TensorAttribute::onUpdateStat()
}
void
-TensorAttribute::removeOldGenerations(generation_t firstUsed)
+TensorAttribute::reclaim_memory(generation_t oldest_used_gen)
{
- _tensorStore.trimHoldLists(firstUsed);
- getGenerationHolder().trimHoldLists(firstUsed);
+ _tensorStore.reclaim_memory(oldest_used_gen);
+ getGenerationHolder().reclaim(oldest_used_gen);
}
void
-TensorAttribute::onGenerationChange(generation_t generation)
+TensorAttribute::before_inc_generation(generation_t current_gen)
{
- getGenerationHolder().transferHoldLists(generation - 1);
- _tensorStore.transferHoldLists(generation - 1);
+ getGenerationHolder().assign_generation(current_gen);
+ _tensorStore.assign_generation(current_gen);
}
bool
@@ -132,7 +134,7 @@ TensorAttribute::addDoc(DocId &docId)
if (incGen) {
incGeneration();
} else {
- removeAllOldGenerations();
+ reclaim_unused_memory();
}
return true;
}
@@ -167,7 +169,7 @@ TensorAttribute::update_stat()
{
vespalib::MemoryUsage result = _refVector.getMemoryUsage();
result.merge(_tensorStore.update_stat(getConfig().getCompactionStrategy()));
- result.mergeGenerationHeldBytes(getGenerationHolder().getHeldBytes());
+ result.mergeGenerationHeldBytes(getGenerationHolder().get_held_bytes());
return result;
}
@@ -176,7 +178,7 @@ TensorAttribute::memory_usage() const
{
vespalib::MemoryUsage result = _refVector.getMemoryUsage();
result.merge(_tensorStore.getMemoryUsage());
- result.mergeGenerationHeldBytes(getGenerationHolder().getHeldBytes());
+ result.mergeGenerationHeldBytes(getGenerationHolder().get_held_bytes());
return result;
}
@@ -277,6 +279,51 @@ TensorAttribute::getRefCopy() const
return result;
}
+bool
+TensorAttribute::onLoad(vespalib::Executor*)
+{
+ BlobSequenceReader tensorReader(*this);
+ if (!tensorReader.hasData()) {
+ return false;
+ }
+ setCreateSerialNum(tensorReader.getCreateSerialNum());
+ assert(tensorReader.getVersion() == getVersion());
+ uint32_t numDocs = tensorReader.getDocIdLimit();
+ _refVector.reset();
+ _refVector.unsafe_reserve(numDocs);
+ vespalib::Array<char> buffer(1024);
+ for (uint32_t lid = 0; lid < numDocs; ++lid) {
+ uint32_t tensorSize = tensorReader.getNextSize();
+ if (tensorSize != 0) {
+ if (tensorSize > buffer.size()) {
+ buffer.resize(tensorSize + 1024);
+ }
+ tensorReader.readBlob(&buffer[0], tensorSize);
+ vespalib::nbostream source(&buffer[0], tensorSize);
+ EntryRef ref = _tensorStore.store_encoded_tensor(source);
+ _refVector.push_back(AtomicEntryRef(ref));
+ } else {
+ EntryRef invalid;
+ _refVector.push_back(AtomicEntryRef(invalid));
+ }
+ }
+ setNumDocs(numDocs);
+ setCommittedDocIdLimit(numDocs);
+ return true;
+}
+
+std::unique_ptr<AttributeSaver>
+TensorAttribute::onInitSave(vespalib::stringref fileName)
+{
+ vespalib::GenerationHandler::Guard guard(getGenerationHandler().
+ takeGuard());
+ return std::make_unique<TensorStoreSaver>
+ (std::move(guard),
+ this->createAttributeHeader(fileName),
+ getRefCopy(),
+ _tensorStore);
+}
+
void
TensorAttribute::update_tensor(DocId docId,
const document::TensorUpdate &update,
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_attribute.h b/searchlib/src/vespa/searchlib/tensor/tensor_attribute.h
index 411efcd8fea..b7bac35d1b7 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_attribute.h
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_attribute.h
@@ -36,6 +36,8 @@ protected:
void populate_state(vespalib::slime::Cursor& object) const;
void populate_address_space_usage(AddressSpaceUsage& usage) const override;
EntryRef acquire_entry_ref(DocId doc_id) const noexcept { return _refVector.acquire_elem_ref(doc_id).load_acquire(); }
+ bool onLoad(vespalib::Executor *executor) override;
+ std::unique_ptr<AttributeSaver> onInitSave(vespalib::stringref fileName) override;
public:
using RefCopyVector = vespalib::Array<EntryRef>;
@@ -46,8 +48,8 @@ public:
uint32_t clearDoc(DocId docId) override;
void onCommit() override;
void onUpdateStat() override;
- void removeOldGenerations(generation_t firstUsed) override;
- void onGenerationChange(generation_t generation) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
+ void before_inc_generation(generation_t current_gen) override;
bool addDoc(DocId &docId) override;
std::unique_ptr<vespalib::eval::Value> getEmptyTensor() const override;
vespalib::eval::TypedCells extract_cells_ref(uint32_t docid) const override;
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.cpp b/searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.cpp
index 22298354444..d13d3f24b5b 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.cpp
@@ -6,7 +6,7 @@
#include <vespa/eval/eval/value_codec.h>
#include <vespa/eval/eval/value_type.h>
#include <vespa/eval/streamed/streamed_value_view.h>
-#include <vespa/vespalib/util/arrayref.h>
+#include <vespa/vespalib/util/atomic.h>
#include <vespa/vespalib/util/shared_string_repo.h>
#include <algorithm>
@@ -85,16 +85,25 @@ TensorBufferOperations::TensorBufferOperations(const vespalib::eval::ValueType&
TensorBufferOperations::~TensorBufferOperations() = default;
uint32_t
-TensorBufferOperations::get_num_subspaces(ConstArrayRef<char> buf) const noexcept
+TensorBufferOperations::get_num_subspaces_and_flag(ConstArrayRef<char> buf) const noexcept
{
assert(buf.size() >= get_num_subspaces_size());
- return *reinterpret_cast<const uint32_t*>(buf.data());
+ const uint32_t& num_subspaces_and_flag_ref = *reinterpret_cast<const uint32_t*>(buf.data());
+ return vespalib::atomic::load_ref_relaxed(num_subspaces_and_flag_ref);
+}
+
+void
+TensorBufferOperations::set_skip_reclaim_labels(ArrayRef<char> buf, uint32_t num_subspaces_and_flag) const noexcept
+{
+ uint32_t& num_subspaces_and_flag_ref = *reinterpret_cast<uint32_t*>(buf.data());
+ vespalib::atomic::store_ref_relaxed(num_subspaces_and_flag_ref, (num_subspaces_and_flag | skip_reclaim_labels_mask));
}
void
TensorBufferOperations::store_tensor(ArrayRef<char> buf, const vespalib::eval::Value& tensor)
{
uint32_t num_subspaces = tensor.index().size();
+ assert(num_subspaces <= num_subspaces_mask);
auto labels_end_offset = get_labels_offset() + get_labels_mem_size(num_subspaces);
auto cells_size = num_subspaces * _dense_subspace_size;
auto cells_mem_size = cells_size * _cell_mem_size; // Size measured in bytes
@@ -148,23 +157,26 @@ TensorBufferOperations::make_fast_view(ConstArrayRef<char> buf, const vespalib::
}
void
-TensorBufferOperations::copied_labels(ConstArrayRef<char> buf) const
+TensorBufferOperations::copied_labels(ArrayRef<char> buf) const
{
- auto num_subspaces = get_num_subspaces(buf);
- ConstArrayRef<string_id> labels(reinterpret_cast<const string_id*>(buf.data() + get_labels_offset()), num_subspaces * _num_mapped_dimensions);
- for (auto& label : labels) {
- SharedStringRepo::unsafe_copy(label); // Source buffer has an existing ref
+ auto num_subspaces_and_flag = get_num_subspaces_and_flag(buf);
+ if (!get_skip_reclaim_labels(num_subspaces_and_flag)) {
+ set_skip_reclaim_labels(buf, num_subspaces_and_flag);
}
}
void
TensorBufferOperations::reclaim_labels(ArrayRef<char> buf) const
{
- auto num_subspaces = get_num_subspaces(buf);
+ auto num_subspaces_and_flag = get_num_subspaces_and_flag(buf);
+ if (get_skip_reclaim_labels(num_subspaces_and_flag)) {
+ return;
+ }
+ set_skip_reclaim_labels(buf, num_subspaces_and_flag);
+ auto num_subspaces = get_num_subspaces(num_subspaces_and_flag);
ArrayRef<string_id> labels(reinterpret_cast<string_id*>(buf.data() + get_labels_offset()), num_subspaces * _num_mapped_dimensions);
for (auto& label : labels) {
SharedStringRepo::unsafe_reclaim(label);
- label = string_id(); // Clear label to avoid double reclaim
}
}
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.h b/searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.h
index 18c7bc84ab2..7b6d089f8f2 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.h
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_buffer_operations.h
@@ -4,15 +4,12 @@
#include <vespa/eval/eval/cell_type.h>
#include <vespa/vespalib/datastore/aligner.h>
+#include <vespa/vespalib/util/arrayref.h>
#include <vespa/vespalib/util/string_id.h>
#include <cstddef>
#include <memory>
-namespace vespalib {
-template <typename T> class ArrayRef;
-template <typename T> class ConstArrayRef;
-class nbostream;
-}
+namespace vespalib { class nbostream; }
namespace vespalib::eval {
struct Value;
@@ -27,7 +24,10 @@ namespace search::tensor {
*
* Layout of buffer is:
*
- * num_subspaces - number of subspaces
+ * num_subspaces_and_flag
+ * 31 least significant bits is num_subspaces - number of subspaces
+ * 1 bit to signal that reclaim_labels should be a noop (i.e. buffer has been copied
+ * as part of compaction or fallback copy (due to datastore buffer fallback resize)).
* labels[num_subspaces * _num_mapped_dimensions] - array of labels for sparse dimensions
* padding - to align start of cells
* cells[num_subspaces * _dense_subspaces_size] - array of tensor cell values
@@ -50,6 +50,8 @@ class TensorBufferOperations
static constexpr size_t CELLS_ALIGNMENT = 16;
static constexpr size_t CELLS_ALIGNMENT_MEM_SIZE_MIN = 32;
+ static constexpr uint32_t num_subspaces_mask = ((1u << 31) - 1);
+ static constexpr uint32_t skip_reclaim_labels_mask = (1u << 31);
static constexpr size_t get_num_subspaces_size() noexcept { return sizeof(uint32_t); }
static constexpr size_t get_labels_offset() noexcept { return get_num_subspaces_size(); }
@@ -65,7 +67,17 @@ class TensorBufferOperations
size_t get_cells_offset(uint32_t num_subspaces, auto aligner) const noexcept {
return aligner.align(get_labels_offset() + get_labels_mem_size(num_subspaces));
}
- uint32_t get_num_subspaces(vespalib::ConstArrayRef<char> buf) const noexcept;
+ uint32_t get_num_subspaces_and_flag(vespalib::ConstArrayRef<char> buf) const noexcept;
+ void set_skip_reclaim_labels(vespalib::ArrayRef<char> buf, uint32_t num_subspaces_and_flag) const noexcept;
+ static uint32_t get_num_subspaces(uint32_t num_subspaces_and_flag) noexcept {
+ return num_subspaces_and_flag & num_subspaces_mask;
+ }
+ static bool get_skip_reclaim_labels(uint32_t num_subspaces_and_flag) noexcept {
+ return (num_subspaces_and_flag & skip_reclaim_labels_mask) != 0;
+ }
+ uint32_t get_num_subspaces(vespalib::ConstArrayRef<char> buf) const noexcept {
+ return get_num_subspaces(get_num_subspaces_and_flag(buf));
+ }
public:
size_t get_array_size(uint32_t num_subspaces) const noexcept {
auto cells_mem_size = get_cells_mem_size(num_subspaces);
@@ -81,9 +93,9 @@ public:
void store_tensor(vespalib::ArrayRef<char> buf, const vespalib::eval::Value& tensor);
std::unique_ptr<vespalib::eval::Value> make_fast_view(vespalib::ConstArrayRef<char> buf, const vespalib::eval::ValueType& tensor_type) const;
- // Increase reference counts for labels after copying tensor buffer
- void copied_labels(vespalib::ConstArrayRef<char> buf) const;
- // Decrease reference counts for labels and invalidate them
+ // Mark that reclaim_labels should be skipped for old buffer after copying tensor buffer
+ void copied_labels(vespalib::ArrayRef<char> buf) const;
+ // Decrease reference counts for labels and set skip flag unless skip flag is set.
void reclaim_labels(vespalib::ArrayRef<char> buf) const;
// Serialize stored tensor to target (used when saving attribute)
void encode_stored_tensor(vespalib::ConstArrayRef<char> buf, const vespalib::eval::ValueType& type, vespalib::nbostream& target) const;
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.cpp b/searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.cpp
index eff6ac9f374..09d9ac7dd31 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "tensor_buffer_store.h"
+#include <vespa/document/util/serializableexceptions.h>
#include <vespa/eval/eval/value_codec.h>
#include <vespa/eval/streamed/streamed_value_builder_factory.h>
#include <vespa/vespalib/datastore/array_store.hpp>
@@ -10,6 +11,7 @@
#include <vespa/vespalib/datastore/datastore.hpp>
#include <vespa/vespalib/util/size_literals.h>
+using document::DeserializeException;
using vespalib::alloc::MemoryAllocator;
using vespalib::datastore::CompactionContext;
using vespalib::datastore::CompactionStrategy;
@@ -46,15 +48,14 @@ TensorBufferStore::holdTensor(EntryRef ref)
}
EntryRef
-TensorBufferStore::move(EntryRef ref)
+TensorBufferStore::move_on_compact(EntryRef ref)
{
if (!ref.valid()) {
return EntryRef();
}
- auto buf = _array_store.get(ref);
+ auto buf = _array_store.get_writable(ref);
auto new_ref = _array_store.add(buf);
_ops.copied_labels(buf);
- _array_store.remove(ref);
return new_ref;
}
@@ -90,6 +91,9 @@ TensorBufferStore::store_encoded_tensor(vespalib::nbostream &encoded)
{
const auto &factory = StreamedValueBuilderFactory::get();
auto val = vespalib::eval::decode_value(encoded, factory);
+ if (!encoded.empty()) {
+ throw DeserializeException("Leftover bytes deserializing tensor attribute value.", VESPA_STRLOC);
+ }
return store_tensor(*val);
}
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.h b/searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.h
index 585bbd7a0c3..1b5520233e1 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_buffer_store.h
@@ -27,13 +27,13 @@ public:
TensorBufferStore(const vespalib::eval::ValueType& tensor_type, std::shared_ptr<vespalib::alloc::MemoryAllocator> allocator, uint32_t max_small_subspaces_type_id);
~TensorBufferStore();
void holdTensor(EntryRef ref) override;
- EntryRef move(EntryRef ref) override;
+ EntryRef move_on_compact(EntryRef ref) override;
vespalib::MemoryUsage update_stat(const vespalib::datastore::CompactionStrategy& compaction_strategy) override;
std::unique_ptr<vespalib::datastore::ICompactionContext> start_compact(const vespalib::datastore::CompactionStrategy& compaction_strategy) override;
- EntryRef store_tensor(const vespalib::eval::Value &tensor);
- EntryRef store_encoded_tensor(vespalib::nbostream &encoded);
- std::unique_ptr<vespalib::eval::Value> get_tensor(EntryRef ref) const;
- bool encode_stored_tensor(EntryRef ref, vespalib::nbostream &target) const;
+ EntryRef store_tensor(const vespalib::eval::Value& tensor) override;
+ EntryRef store_encoded_tensor(vespalib::nbostream& encoded) override;
+ std::unique_ptr<vespalib::eval::Value> get_tensor(EntryRef ref) const override;
+ bool encode_stored_tensor(EntryRef ref, vespalib::nbostream& target) const override;
};
}
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_deserialize.cpp b/searchlib/src/vespa/searchlib/tensor/tensor_deserialize.cpp
index a8399d9ddeb..791662caed7 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_deserialize.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_deserialize.cpp
@@ -5,6 +5,7 @@
#include <vespa/eval/eval/fast_value.h>
#include <vespa/eval/eval/value_codec.h>
#include <vespa/eval/eval/value.h>
+#include <vespa/vespalib/objects/nbostream.h>
using document::DeserializeException;
using vespalib::eval::FastValueBuilderFactory;
@@ -25,10 +26,4 @@ std::unique_ptr<Value> deserialize_tensor(vespalib::nbostream &buffer)
}
}
-std::unique_ptr<Value> deserialize_tensor(const void *data, size_t size)
-{
- vespalib::nbostream wrapStream(data, size);
- return deserialize_tensor(wrapStream);
-}
-
} // namespace
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_deserialize.h b/searchlib/src/vespa/searchlib/tensor/tensor_deserialize.h
index 7d1ede29167..18b9731b30b 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_deserialize.h
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_deserialize.h
@@ -1,12 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include <vespa/eval/eval/value.h>
-#include <vespa/vespalib/objects/nbostream.h>
+#pragma once
-namespace search::tensor {
+#include <memory>
-extern std::unique_ptr<vespalib::eval::Value>
-deserialize_tensor(const void *data, size_t size);
+namespace vespalib { class nbostream; }
+namespace vespalib::eval { struct Value; }
+
+namespace search::tensor {
extern std::unique_ptr<vespalib::eval::Value>
deserialize_tensor(vespalib::nbostream &stream);
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_store.h b/searchlib/src/vespa/searchlib/tensor/tensor_store.h
index 90bc82c4fde..53551bc48fa 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_store.h
@@ -8,6 +8,7 @@
#include <vespa/vespalib/datastore/i_compactable.h>
#include <vespa/vespalib/util/generationhandler.h>
+namespace vespalib { class nbostream; }
namespace vespalib::datastore { struct ICompactionContext; }
namespace vespalib::eval { struct Value; }
@@ -41,18 +42,23 @@ public:
virtual std::unique_ptr<vespalib::datastore::ICompactionContext> start_compact(const vespalib::datastore::CompactionStrategy& compaction_strategy) = 0;
+ virtual EntryRef store_tensor(const vespalib::eval::Value& tensor) = 0;
+ virtual EntryRef store_encoded_tensor(vespalib::nbostream& encoded) = 0;
+ virtual std::unique_ptr<vespalib::eval::Value> get_tensor(EntryRef ref) const = 0;
+ virtual bool encode_stored_tensor(EntryRef ref, vespalib::nbostream& target) const = 0;
+
// Inherit doc from DataStoreBase
- void trimHoldLists(generation_t usedGen) {
- _store.trimHoldLists(usedGen);
+ void reclaim_memory(generation_t oldest_used_gen) {
+ _store.reclaim_memory(oldest_used_gen);
}
// Inherit doc from DataStoreBase
- void transferHoldLists(generation_t generation) {
- _store.transferHoldLists(generation);
+ void assign_generation(generation_t current_gen) {
+ _store.assign_generation(current_gen);
}
- void clearHoldLists() {
- _store.clearHoldLists();
+ void reclaim_all_memory() {
+ _store.reclaim_all_memory();
}
vespalib::MemoryUsage getMemoryUsage() const {
diff --git a/searchlib/src/vespa/searchlib/tensor/streamed_value_saver.cpp b/searchlib/src/vespa/searchlib/tensor/tensor_store_saver.cpp
index 4c188bb3370..0963e79b0dd 100644
--- a/searchlib/src/vespa/searchlib/tensor/streamed_value_saver.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_store_saver.cpp
@@ -1,7 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "streamed_value_saver.h"
-#include "tensor_buffer_store.h"
+#include "tensor_store_saver.h"
+#include "tensor_store.h"
#include <vespa/searchlib/attribute/iattributesavetarget.h>
#include <vespa/searchlib/util/bufferwriter.h>
@@ -11,21 +11,21 @@ using vespalib::GenerationHandler;
namespace search::tensor {
-StreamedValueSaver::
-StreamedValueSaver(GenerationHandler::Guard &&guard,
- const attribute::AttributeHeader &header,
- RefCopyVector &&refs,
- const TensorBufferStore &tensorStore)
+TensorStoreSaver::
+TensorStoreSaver(GenerationHandler::Guard &&guard,
+ const attribute::AttributeHeader &header,
+ RefCopyVector &&refs,
+ const TensorStore &tensorStore)
: AttributeSaver(std::move(guard), header),
_refs(std::move(refs)),
_tensorStore(tensorStore)
{
}
-StreamedValueSaver::~StreamedValueSaver() = default;
+TensorStoreSaver::~TensorStoreSaver() = default;
bool
-StreamedValueSaver::onSave(IAttributeSaveTarget &saveTarget)
+TensorStoreSaver::onSave(IAttributeSaveTarget &saveTarget)
{
auto datWriter = saveTarget.datWriter().allocBufferWriter();
const uint32_t docIdLimit(_refs.size());
diff --git a/searchlib/src/vespa/searchlib/tensor/streamed_value_saver.h b/searchlib/src/vespa/searchlib/tensor/tensor_store_saver.h
index 0ce864769f7..a4bf6e07519 100644
--- a/searchlib/src/vespa/searchlib/tensor/streamed_value_saver.h
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_store_saver.h
@@ -7,12 +7,10 @@
namespace search::tensor {
-class TensorBufferStore;
-
/*
* Class for saving a tensor attribute.
*/
-class StreamedValueSaver : public AttributeSaver
+class TensorStoreSaver : public AttributeSaver
{
public:
using RefCopyVector = TensorAttribute::RefCopyVector;
@@ -20,16 +18,16 @@ private:
using GenerationHandler = vespalib::GenerationHandler;
RefCopyVector _refs;
- const TensorBufferStore &_tensorStore;
+ const TensorStore& _tensorStore;
bool onSave(IAttributeSaveTarget &saveTarget) override;
public:
- StreamedValueSaver(GenerationHandler::Guard &&guard,
- const attribute::AttributeHeader &header,
- RefCopyVector &&refs,
- const TensorBufferStore &tensorStore);
+ TensorStoreSaver(GenerationHandler::Guard &&guard,
+ const attribute::AttributeHeader &header,
+ RefCopyVector &&refs,
+ const TensorStore &tensorStore);
- virtual ~StreamedValueSaver();
+ virtual ~TensorStoreSaver();
};
} // namespace search::tensor
diff --git a/searchlib/src/vespa/searchlib/test/CMakeLists.txt b/searchlib/src/vespa/searchlib/test/CMakeLists.txt
index ed884a46217..2eb207d0d43 100644
--- a/searchlib/src/vespa/searchlib/test/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/test/CMakeLists.txt
@@ -2,12 +2,14 @@
vespa_add_library(searchlib_test
SOURCES
document_weight_attribute_helper.cpp
+ doc_builder.cpp
imported_attribute_fixture.cpp
initrange.cpp
make_attribute_map_lookup_node.cpp
mock_attribute_context.cpp
mock_attribute_manager.cpp
searchiteratorverifier.cpp
+ string_field_builder.cpp
$<TARGET_OBJECTS:searchlib_test_fakedata>
$<TARGET_OBJECTS:searchlib_searchlib_test_diskindex>
$<TARGET_OBJECTS:searchlib_test_gtest_migration>
diff --git a/searchlib/src/vespa/searchlib/test/doc_builder.cpp b/searchlib/src/vespa/searchlib/test/doc_builder.cpp
new file mode 100644
index 00000000000..2312bf1d6bf
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/test/doc_builder.cpp
@@ -0,0 +1,117 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "doc_builder.h"
+#include <vespa/document/datatype/documenttype.h>
+#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/mapfieldvalue.h>
+#include <vespa/document/fieldvalue/structfieldvalue.h>
+#include <vespa/document/fieldvalue/weightedsetfieldvalue.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/document/repo/document_type_repo_factory.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <cassert>
+
+using document::ArrayFieldValue;
+using document::DataType;
+using document::Document;
+using document::DocumentId;
+using document::DocumentTypeRepo;
+using document::DocumentTypeRepoFactory;
+using document::MapFieldValue;
+using document::StructFieldValue;
+using document::WeightedSetFieldValue;
+
+namespace search::test {
+
+namespace {
+
+DocumenttypesConfig
+get_document_types_config(DocBuilder::AddFieldsType add_fields)
+{
+ using namespace document::config_builder;
+ DocumenttypesConfigBuilderHelper builder;
+ Struct header("searchdocument.header");
+ add_fields(header);
+ builder.document(42, "searchdocument",
+ header,
+ Struct("searchdocument.body"));
+ return builder.config();
+}
+
+}
+
+DocBuilder::DocBuilder()
+ : DocBuilder([](auto&) noexcept {})
+{
+}
+
+DocBuilder::DocBuilder(AddFieldsType add_fields)
+ : _document_types_config(std::make_shared<const DocumenttypesConfig>(get_document_types_config(add_fields))),
+ _repo(DocumentTypeRepoFactory::make(*_document_types_config)),
+ _document_type(_repo->getDocumentType("searchdocument"))
+{
+}
+
+DocBuilder::~DocBuilder() = default;
+
+
+std::unique_ptr<Document>
+DocBuilder::make_document(vespalib::string document_id)
+{
+ auto doc = std::make_unique<Document>(get_document_type(), DocumentId(document_id));
+ doc->setRepo(get_repo());
+ return doc;
+}
+
+const DataType&
+DocBuilder::get_data_type(const vespalib::string &name) const
+{
+ const DataType *type = _repo->getDataType(*_document_type, name);
+ assert(type);
+ return *type;
+}
+
+ArrayFieldValue
+DocBuilder::make_array(vespalib::stringref field_name)
+{
+ auto& field = _document_type->getField(field_name);
+ auto& field_type = field.getDataType();
+ assert(field_type.isArray());
+ return {field_type};
+}
+MapFieldValue
+DocBuilder::make_map(vespalib::stringref field_name)
+{
+ auto& field = _document_type->getField(field_name);
+ auto& field_type = field.getDataType();
+ assert(field_type.isMap());
+ return {field_type};
+
+}
+
+WeightedSetFieldValue
+DocBuilder::make_wset(vespalib::stringref field_name)
+{
+ auto& field = _document_type->getField(field_name);
+ auto& field_type = field.getDataType();
+ assert(field_type.isWeightedSet());
+ return {field_type};
+}
+
+StructFieldValue
+DocBuilder::make_struct(vespalib::stringref field_name)
+{
+ auto& field = _document_type->getField(field_name);
+ auto& field_type = field.getDataType();
+ assert(field_type.isStructured());
+ return {field_type};
+}
+
+StructFieldValue
+DocBuilder::make_url()
+{
+ return {get_data_type("url")};
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/index/empty_doc_builder.h b/searchlib/src/vespa/searchlib/test/doc_builder.h
index d4b54359f87..75dbc30a0fb 100644
--- a/searchlib/src/vespa/searchlib/index/empty_doc_builder.h
+++ b/searchlib/src/vespa/searchlib/test/doc_builder.h
@@ -2,35 +2,50 @@
#pragma once
+#include <vespa/document/config/documenttypes_config_fwd.h>
#include <vespa/vespalib/stllike/string.h>
#include <functional>
#include <memory>
namespace document {
+class ArrayFieldValue;
class DataType;
class Document;
class DocumentType;
class DocumentTypeRepo;
+class MapFieldValue;
+class StructFieldValue;
+class WeightedSetFieldValue;
}
+namespace document::config::internal { class InternalDocumenttypesType; }
namespace document::config_builder { struct Struct; }
-namespace search::index {
+namespace search::test {
/*
* Class used to make empty search documents.
*/
-class EmptyDocBuilder {
+class DocBuilder {
+ using DocumenttypesConfig = const document::config::internal::InternalDocumenttypesType;
+ std::shared_ptr<const DocumenttypesConfig> _document_types_config;
std::shared_ptr<const document::DocumentTypeRepo> _repo;
const document::DocumentType* _document_type;
public:
using AddFieldsType = std::function<void(document::config_builder::Struct&)>;
- explicit EmptyDocBuilder(AddFieldsType add_fields);
- ~EmptyDocBuilder();
+ DocBuilder();
+ explicit DocBuilder(AddFieldsType add_fields);
+ ~DocBuilder();
const document::DocumentTypeRepo& get_repo() const noexcept { return *_repo; }
std::shared_ptr<const document::DocumentTypeRepo> get_repo_sp() const noexcept { return _repo; }
const document::DocumentType& get_document_type() const noexcept { return *_document_type; }
std::unique_ptr<document::Document> make_document(vespalib::string document_id);
const document::DataType &get_data_type(const vespalib::string &name) const;
+ const DocumenttypesConfig& get_documenttypes_config() const noexcept { return *_document_types_config; }
+ document::ArrayFieldValue make_array(vespalib::stringref field_name);
+ document::MapFieldValue make_map(vespalib::stringref field_name);
+ document::WeightedSetFieldValue make_wset(vespalib::stringref field_name);
+ document::StructFieldValue make_struct(vespalib::stringref field_name);
+ document::StructFieldValue make_url();
};
}
diff --git a/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.cpp b/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.cpp
index 2597aec3dd7..e918c523fcf 100644
--- a/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.cpp
+++ b/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.cpp
@@ -12,12 +12,14 @@
#include <vespa/vespalib/btree/btreenodeallocator.hpp>
#include <vespa/vespalib/btree/btreenodestore.hpp>
#include <vespa/vespalib/btree/btreeroot.hpp>
+#include <vespa/vespalib/datastore/compaction_strategy.h>
#include <vespa/log/log.h>
LOG_SETUP(".fakememtreeocc");
using search::fef::TermFieldMatchData;
using search::fef::TermFieldMatchDataPosition;
+using vespalib::datastore::CompactionStrategy;
namespace search::fakedata {
@@ -167,9 +169,9 @@ FakeMemTreeOccMgr::freeze()
void
-FakeMemTreeOccMgr::transferHoldLists()
+FakeMemTreeOccMgr::assign_generation()
{
- _allocator.transferHoldLists(_generationHandler.getCurrentGeneration());
+ _allocator.assign_generation(_generationHandler.getCurrentGeneration());
}
void
@@ -180,9 +182,9 @@ FakeMemTreeOccMgr::incGeneration()
void
-FakeMemTreeOccMgr::trimHoldLists()
+FakeMemTreeOccMgr::reclaim_memory()
{
- _allocator.trimHoldLists(_generationHandler.getFirstUsedGeneration());
+ _allocator.reclaim_memory(_generationHandler.get_oldest_used_generation());
}
@@ -190,9 +192,9 @@ void
FakeMemTreeOccMgr::sync()
{
freeze();
- transferHoldLists();
+ assign_generation();
incGeneration();
- trimHoldLists();
+ reclaim_memory();
}
@@ -280,7 +282,9 @@ FakeMemTreeOccMgr::compactTrees()
{
// compact full trees by calling incremental compaction methods in a loop
- std::vector<uint32_t> toHold = _allocator.startCompact();
+ // Use a compaction strategy that will compact all active buffers
+ auto compaction_strategy = CompactionStrategy::make_compact_all_active_buffers_strategy();
+ auto compacting_buffers = _allocator.start_compact_worst(compaction_strategy);
for (uint32_t wordIdx = 0; wordIdx < _postingIdxs.size(); ++wordIdx) {
PostingIdx &pidx(*_postingIdxs[wordIdx].get());
Tree &tree = pidx._tree;
@@ -291,7 +295,7 @@ FakeMemTreeOccMgr::compactTrees()
itr.moveNextLeafNode();
}
}
- _allocator.finishCompact(toHold);
+ compacting_buffers->finish();
sync();
}
diff --git a/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h b/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h
index d0a75930ed5..290ba1cf140 100644
--- a/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h
+++ b/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h
@@ -94,9 +94,9 @@ public:
~FakeMemTreeOccMgr();
void freeze();
- void transferHoldLists();
+ void assign_generation();
void incGeneration();
- void trimHoldLists();
+ void reclaim_memory();
void sync();
void add(uint32_t wordIdx, index::DocIdAndFeatures &features) override;
void remove(uint32_t wordIdx, uint32_t docId) override;
diff --git a/searchlib/src/vespa/searchlib/test/string_field_builder.cpp b/searchlib/src/vespa/searchlib/test/string_field_builder.cpp
new file mode 100644
index 00000000000..1510a306875
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/test/string_field_builder.cpp
@@ -0,0 +1,140 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "string_field_builder.h"
+#include "doc_builder.h"
+#include <vespa/document/annotation/annotation.h>
+#include <vespa/document/annotation/span.h>
+#include <vespa/document/annotation/spanlist.h>
+#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/fastlib/text/unicodeutil.h>
+#include <vespa/vespalib/text/utf8.h>
+
+#include <cassert>
+
+using document::Annotation;
+using document::AnnotationType;
+using document::FixedTypeRepo;
+using document::StringFieldValue;
+using document::Span;
+using document::SpanList;
+using document::SpanNode;
+using document::SpanTree;
+using vespalib::Utf8Reader;
+using vespalib::Utf8Writer;
+
+namespace search::test {
+
+namespace {
+
+const vespalib::string SPANTREE_NAME("linguistics");
+
+}
+
+StringFieldBuilder::StringFieldBuilder(const DocBuilder& doc_builder)
+ : _value(),
+ _span_start(0u),
+ _span_list(nullptr),
+ _span_tree(),
+ _last_span(nullptr),
+ _url_mode(false),
+ _repo(doc_builder.get_repo(), doc_builder.get_document_type())
+{
+}
+
+StringFieldBuilder::~StringFieldBuilder() = default;
+
+void
+StringFieldBuilder::start_annotate()
+{
+ auto span_list_up = std::make_unique<SpanList>();
+ _span_list = span_list_up.get();
+ _span_tree = std::make_unique<SpanTree>(SPANTREE_NAME, std::move(span_list_up));
+}
+
+void
+StringFieldBuilder::add_span()
+{
+ assert(_value.size() > _span_start);
+ const SpanNode &span = _span_list->add(std::make_unique<Span>(_span_start, _value.size() - _span_start));
+ _last_span = &span;
+ _span_start = _value.size();
+}
+
+StringFieldBuilder&
+StringFieldBuilder::token(const vespalib::string& val, bool is_word)
+{
+ if (val.empty()) {
+ return *this;
+ }
+ if (!_span_tree) {
+ start_annotate();
+ }
+ _span_start = _value.size();
+ _value.append(val);
+ add_span();
+ if (is_word) {
+ _span_tree->annotate(*_last_span, *AnnotationType::TERM);
+ }
+ return *this;
+}
+
+StringFieldBuilder&
+StringFieldBuilder::alt_word(const vespalib::string& val)
+{
+ assert(_last_span != nullptr);
+ _span_tree->annotate(*_last_span,
+ Annotation(*AnnotationType::TERM,
+ std::make_unique<StringFieldValue>(val)));
+ return *this;
+}
+
+StringFieldBuilder&
+StringFieldBuilder::tokenize(const vespalib::string& val)
+{
+ Utf8Reader reader(val);
+ vespalib::string token_buffer;
+ Utf8Writer writer(token_buffer);
+ uint32_t c = 0u;
+ bool old_word = false;
+
+ while (reader.hasMore()) {
+ c = reader.getChar();
+ bool new_word = Fast_UnicodeUtil::IsWordChar(c) ||
+ (_url_mode && (c == '-' || c == '_'));
+ if (old_word != new_word) {
+ if (!token_buffer.empty()) {
+ token(token_buffer, old_word);
+ token_buffer.clear();
+ }
+ old_word = new_word;
+ }
+ writer.putChar(c);
+ }
+ if (!token_buffer.empty()) {
+ token(token_buffer, old_word);
+ }
+ return *this;
+}
+
+
+document::StringFieldValue
+StringFieldBuilder::build()
+{
+ StringFieldValue value(_value);
+ // Also drop all spans no annotation for now
+ if (_span_tree && _span_tree->numAnnotations() > 0u) {
+ StringFieldValue::SpanTrees trees;
+ trees.emplace_back(std::move(_span_tree));
+ value.setSpanTrees(trees, _repo);
+ } else {
+ _span_tree.reset();
+ }
+ _span_list = nullptr;
+ _last_span = nullptr;
+ _span_start = 0u;
+ _value.clear();
+ return value;
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/test/string_field_builder.h b/searchlib/src/vespa/searchlib/test/string_field_builder.h
new file mode 100644
index 00000000000..94c2bfc2fe8
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/test/string_field_builder.h
@@ -0,0 +1,45 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/stllike/string.h>
+#include <vespa/document/repo/fixedtyperepo.h>
+#include <memory>
+
+namespace document {
+class SpanList;
+struct SpanNode;
+class SpanTree;
+class StringFieldValue;
+}
+
+namespace search::test {
+
+class DocBuilder;
+
+/*
+ * Helper class to build annotated string field.
+ */
+class StringFieldBuilder {
+ vespalib::string _value;
+ size_t _span_start;
+ document::SpanList* _span_list; // owned by _span_tree
+ std::unique_ptr<document::SpanTree> _span_tree;
+ const document::SpanNode* _last_span;
+ bool _url_mode;
+ const document::FixedTypeRepo _repo;
+ void start_annotate();
+ void add_span();
+public:
+ StringFieldBuilder(const DocBuilder& doc_builder);
+ ~StringFieldBuilder();
+ StringFieldBuilder& url_mode(bool url_mode_) noexcept { _url_mode = url_mode_; return *this; }
+ StringFieldBuilder& token(const vespalib::string& val, bool is_word);
+ StringFieldBuilder& word(const vespalib::string& val) { return token(val, true); }
+ StringFieldBuilder& space() { return token(" ", false); }
+ StringFieldBuilder& tokenize(const vespalib::string& val);
+ StringFieldBuilder& alt_word(const vespalib::string& val);
+ document::StringFieldValue build();
+};
+
+}
diff --git a/searchsummary/src/tests/docsummary/annotation_converter/annotation_converter_test.cpp b/searchsummary/src/tests/docsummary/annotation_converter/annotation_converter_test.cpp
index 753ae8d9044..17a7457711d 100644
--- a/searchsummary/src/tests/docsummary/annotation_converter/annotation_converter_test.cpp
+++ b/searchsummary/src/tests/docsummary/annotation_converter/annotation_converter_test.cpp
@@ -4,6 +4,7 @@
#include <vespa/document/annotation/span.h>
#include <vespa/document/annotation/spanlist.h>
#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/datatype/annotationtype.h>
#include <vespa/document/fieldvalue/stringfieldvalue.h>
#include <vespa/document/repo/configbuilder.h>
#include <vespa/document/repo/fixedtyperepo.h>
@@ -16,6 +17,7 @@
#include <vespa/vespalib/stllike/asciistream.h>
using document::Annotation;
+using document::AnnotationType;
using document::DocumentType;
using document::DocumentTypeRepo;
using document::Span;
@@ -25,7 +27,6 @@ using document::StringFieldValue;
using search::docsummary::AnnotationConverter;
using search::docsummary::IJuniperConverter;
using search::linguistics::SPANTREE_NAME;
-using search::linguistics::TERM;
using vespalib::Slime;
using vespalib::slime::SlimeInserter;
@@ -95,9 +96,9 @@ AnnotationConverterTest::make_annotated_string()
auto span_list_up = std::make_unique<SpanList>();
auto span_list = span_list_up.get();
auto tree = std::make_unique<SpanTree>(SPANTREE_NAME, std::move(span_list_up));
- tree->annotate(span_list->add(std::make_unique<Span>(0, 3)), *TERM);
+ tree->annotate(span_list->add(std::make_unique<Span>(0, 3)), *AnnotationType::TERM);
tree->annotate(span_list->add(std::make_unique<Span>(4, 3)),
- Annotation(*TERM, std::make_unique<StringFieldValue>("baz")));
+ Annotation(*AnnotationType::TERM, std::make_unique<StringFieldValue>("baz")));
StringFieldValue value("foo bar");
set_span_tree(value, std::move(tree));
return value;
@@ -110,8 +111,8 @@ AnnotationConverterTest::make_annotated_chinese_string()
auto span_list = span_list_up.get();
auto tree = std::make_unique<SpanTree>(SPANTREE_NAME, std::move(span_list_up));
// These chinese characters each use 3 bytes in their UTF8 encoding.
- tree->annotate(span_list->add(std::make_unique<Span>(0, 15)), *TERM);
- tree->annotate(span_list->add(std::make_unique<Span>(15, 9)), *TERM);
+ tree->annotate(span_list->add(std::make_unique<Span>(0, 15)), *AnnotationType::TERM);
+ tree->annotate(span_list->add(std::make_unique<Span>(15, 9)), *AnnotationType::TERM);
StringFieldValue value("我就是那个大灰狼");
set_span_tree(value, std::move(tree));
return value;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/annotation_converter.cpp b/searchsummary/src/vespa/searchsummary/docsummary/annotation_converter.cpp
index b36a2f8383e..f4594cba4f4 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/annotation_converter.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/annotation_converter.cpp
@@ -7,6 +7,7 @@
#include <vespa/document/annotation/annotation.h>
#include <vespa/document/annotation/spantree.h>
#include <vespa/document/annotation/spantreevisitor.h>
+#include <vespa/document/datatype/annotationtype.h>
#include <vespa/document/fieldvalue/stringfieldvalue.h>
#include <vespa/juniper/juniper_separators.h>
#include <vespa/vespalib/stllike/asciistream.h>
@@ -15,6 +16,7 @@
using document::AlternateSpanList;
using document::Annotation;
+using document::AnnotationType;
using document::FieldValue;
using document::SimpleSpanList;
using document::Span;
@@ -139,7 +141,7 @@ AnnotationConverter::handleIndexingTerms(const StringFieldValue& value)
// For now, skip any composite spans.
const auto *span = dynamic_cast<const Span*>(annotation.getSpanNode());
if ((span != nullptr) && annotation.valid() &&
- (annotation.getType() == *linguistics::TERM)) {
+ (annotation.getType() == *AnnotationType::TERM)) {
terms.push_back(std::make_pair(getSpan(*span),
annotation.getFieldValue()));
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.cpp b/searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.cpp
index 8a847a90ddb..4b4cb2d9602 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.cpp
@@ -1,27 +1,9 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "linguisticsannotation.h"
-#include <vespa/document/datatype/primitivedatatype.h>
-
-using document::AnnotationType;
-using document::DataType;
-using document::PrimitiveDataType;
-using vespalib::string;
namespace search::linguistics {
-namespace {
-AnnotationType makeType(int id, string name, const DataType &type) {
- AnnotationType annotation_type(id, name);
- annotation_type.setDataType(type);
- return annotation_type;
-}
-
-const PrimitiveDataType STRING_OBJ(DataType::T_STRING);
-AnnotationType TERM_OBJ(makeType(1, "term", STRING_OBJ));
-} // namespace
-
-const string SPANTREE_NAME("linguistics");
-const AnnotationType *const TERM(&TERM_OBJ);
+const vespalib::string SPANTREE_NAME("linguistics");
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.h b/searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.h
index d27c0da151f..e6395fe5b68 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/linguisticsannotation.h
@@ -2,11 +2,10 @@
#pragma once
-#include <vespa/document/datatype/annotationtype.h>
+#include <vespa/vespalib/stllike/string.h>
namespace search::linguistics {
extern const vespalib::string SPANTREE_NAME;
-extern const document::AnnotationType *const TERM;
}
diff --git a/security-utils/pom.xml b/security-utils/pom.xml
index 71920327fbb..a6f0040509c 100644
--- a/security-utils/pom.xml
+++ b/security-utils/pom.xml
@@ -24,7 +24,7 @@
<!-- compile scope -->
<dependency>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>bcpkix-jdk18on</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
diff --git a/security-utils/src/main/java/com/yahoo/security/HKDF.java b/security-utils/src/main/java/com/yahoo/security/HKDF.java
new file mode 100644
index 00000000000..3aff89d71c2
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/HKDF.java
@@ -0,0 +1,221 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Objects;
+
+/**
+ * Implementation of RFC-5869 HMAC-based Extract-and-Expand Key Derivation Function (HKDF).
+ *
+ * <p>The HKDF is initialized ("extracted") from a (non-secret) salt and a secret key.
+ * From this, any number of secret keys can be derived ("expanded") deterministically.</p>
+ *
+ * <p>When multiple keys are to be derived from the same initial keying/salting material,
+ * each separate key should use a distinct "context" in the {@link #expand(int, byte[])}
+ * call. This ensures that there exists a domain separation between the keys.
+ * Using the same context as another key on a HKDF initialized with the same salt+key
+ * results in the exact same derived key material as that key.</p>
+ *
+ * <p>This implementation only offers HMAC-SHA256-based key derivation.</p>
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc5869">RFC-5869</a>
+ * @see <a href="https://en.wikipedia.org/wiki/HKDF">HKDF on Wikipedia</a>
+ *
+ * @author vekterli
+ */
+public class HKDF {
+
+ private static final int HASH_LEN = 32; // Fixed output size of HMAC-SHA256. Corresponds to HashLen in the spec
+ private static final byte[] EMPTY_BYTES = new byte[0];
+ private static final byte[] ALL_ZEROS_SALT = new byte[HASH_LEN];
+ public static final int MAX_OUTPUT_SIZE = 255 * HASH_LEN;
+
+ private final byte[] pseudoRandomKey; // Corresponds to "PRK" in spec
+
+ private HKDF(byte[] pseudoRandomKey) {
+ this.pseudoRandomKey = pseudoRandomKey;
+ }
+
+ private static Mac createHmacSha256() {
+ try {
+ return Mac.getInstance("HmacSHA256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static SecretKeySpec hmacKeyFrom(byte[] rawKey) {
+ return new SecretKeySpec(rawKey, "HmacSHA256");
+ }
+
+ private static Mac createKeyedHmacSha256(byte[] rawKey) {
+ var hmac = createHmacSha256();
+ try {
+ hmac.init(hmacKeyFrom(rawKey));
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ return hmac;
+ }
+
+ private static void validateExtractionParams(byte[] salt, byte[] ikm) {
+ Objects.requireNonNull(salt);
+ Objects.requireNonNull(ikm);
+ if (ikm.length == 0) {
+ throw new IllegalArgumentException("HKDF extraction IKM array can not be empty");
+ }
+ if (salt.length == 0) {
+ throw new IllegalArgumentException("HKDF extraction salt array can not be empty");
+ }
+ }
+
+ /**
+ * Creates and returns a new HKDF instance extracted from the given salt and key.
+ *
+ * <p>Both the salt and input key value may be of arbitrary size, but it is recommended
+ * to have both be at least 16 bytes in size.</p>
+ *
+ * @param salt a non-secret salt value. Should ideally be high entropy and functionally
+ * "as if random". May not be empty, use {@link #unsaltedExtractedFrom(byte[])}
+ * if unsalted extraction is desired (though this is not recommended).
+ * @param ikm secret initial Input Keying Material value.
+ * @return a new HDFK instance ready for deriving keys based on the salt and IKM.
+ */
+ public static HKDF extractedFrom(byte[] salt, byte[] ikm) {
+ validateExtractionParams(salt, ikm);
+ /*
+ RFC-5869, Step 2.2, Extract:
+
+ HKDF-Extract(salt, IKM) -> PRK
+
+ Options:
+ Hash a hash function; HashLen denotes the length of the
+ hash function output in octets
+
+ Inputs:
+ salt optional salt value (a non-secret random value);
+ if not provided, it is set to a string of HashLen zeros.
+ IKM input keying material
+
+ Output:
+ PRK a pseudorandom key (of HashLen octets)
+
+ The output PRK is calculated as follows:
+
+ PRK = HMAC-Hash(salt, IKM)
+ */
+ var mac = createKeyedHmacSha256(salt); // Note: HDFK is initially keyed on the salt, _not_ on ikm!
+ mac.update(ikm);
+ return new HKDF(/*PRK = */ mac.doFinal());
+ }
+
+ /**
+ * Creates and returns a new <em>unsalted</em> HKDF instance extracted from the given key.
+ *
+ * <p>Prefer using the salted {@link #extractedFrom(byte[], byte[])} method if possible.</p>
+ *
+ * @param ikm secret initial Input Keying Material value.
+ * @return a new HDFK instance ready for deriving keys based on the IKM and an all-zero salt.
+ */
+ public static HKDF unsaltedExtractedFrom(byte[] ikm) {
+ return extractedFrom(ALL_ZEROS_SALT, ikm);
+ }
+
+ /**
+ * Derives a key with a given number of bytes for a particular context. The returned
+ * key is always deterministic for a given unique context and a HKDF initialized with
+ * a specific salt+IKM pair.
+ *
+ * <p>Thread safety: multiple threads can safely call <code>expand()</code> simultaneously
+ * on the same HKDF object.</p>
+ *
+ * @param wantedBytes Positive number of output bytes. Must be less than or equal to {@link #MAX_OUTPUT_SIZE}
+ * @param context Context for key derivation. Derivation is deterministic for a given context.
+ * Note: this maps to the "info" field in RFC-5869.
+ * @return A byte buffer of size wantedBytes filled with derived key material
+ */
+ public byte[] expand(int wantedBytes, byte[] context) {
+ Objects.requireNonNull(context);
+ verifyWantedBytesWithinBounds(wantedBytes);
+ return expandImpl(wantedBytes, context);
+ }
+
+ /**
+ * Derives a key with a given number of bytes. The returned key is always deterministic
+ * for a HKDF initialized with a specific salt+IKM pair.
+ *
+ * <p>If more than one key is to be derived, use {@link #expand(int, byte[])}</p>
+ *
+ * <p>Thread safety: multiple threads can safely call <code>expand()</code> simultaneously
+ * on the same HKDF object.</p>
+ *
+ * @param wantedBytes Positive number of output bytes. Must be less than or equal to {@link #MAX_OUTPUT_SIZE}
+ * @return A byte buffer of size wantedBytes filled with derived key material
+ */
+ public byte[] expand(int wantedBytes) {
+ return expand(wantedBytes, EMPTY_BYTES);
+ }
+
+ private void verifyWantedBytesWithinBounds(int wantedBytes) {
+ if (wantedBytes <= 0) {
+ throw new IllegalArgumentException("Requested negative or zero number of HKDF output bytes");
+ }
+ if (wantedBytes > MAX_OUTPUT_SIZE) {
+ throw new IllegalArgumentException("Too many requested HKDF output bytes (max %d, got %d)"
+ .formatted(MAX_OUTPUT_SIZE, wantedBytes));
+ }
+ }
+
+ private byte[] expandImpl(int wantedBytes, byte[] context) {
+ /*
+ RFC-5869, Step 2.3, Expand:
+
+ HKDF-Expand(PRK, info, L) -> OKM
+
+ Inputs:
+ PRK a pseudorandom key of at least HashLen octets
+ (usually, the output from the extract step)
+ info optional context and application specific information
+ (can be a zero-length string)
+ L length of output keying material in octets
+ (<= 255*HashLen)
+
+ Output:
+ OKM output keying material (of L octets)
+
+ The output OKM is calculated as follows:
+
+ N = ceil(L/HashLen)
+ T = T(1) | T(2) | T(3) | ... | T(N)
+ OKM = first L octets of T
+
+ where:
+ T(0) = empty string (zero length)
+ T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
+ T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
+ T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
+ ...
+ */
+ var prkHmac = createKeyedHmacSha256(pseudoRandomKey);
+ int blocks = (wantedBytes / HASH_LEN) + ((wantedBytes % HASH_LEN) != 0 ? 1 : 0); // N
+ var buffer = ByteBuffer.allocate(blocks * HASH_LEN); // T
+ byte[] lastBlock = EMPTY_BYTES; // initially T(0)
+ for (int i = 0; i < blocks; ++i) {
+ prkHmac.update(lastBlock);
+ prkHmac.update(context);
+ prkHmac.update((byte)(i + 1)); // Number of blocks shall never exceed 255
+ // HMAC instance can be reused across doFinal() calls; resets back to initially keyed state.
+ lastBlock = prkHmac.doFinal();
+ buffer.put(lastBlock);
+ }
+ buffer.flip();
+ byte[] outputKeyingMaterial = new byte[wantedBytes]; // OKM
+ buffer.get(outputKeyingMaterial);
+ return outputKeyingMaterial;
+ }
+
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java b/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java
new file mode 100644
index 00000000000..237c4976c7c
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java
@@ -0,0 +1,73 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security;
+
+import java.nio.ByteBuffer;
+import java.util.Base64;
+
+/**
+ * A SealedSharedKey represents the public part of a secure one-way ephemeral key exchange.
+ *
+ * It is "sealed" in the sense that it is expected to be computationally infeasible
+ * for anyone to derive the correct shared key from the sealed key without holding
+ * the correct private key.
+ *
+ * A SealedSharedKey can be converted to--and from--an opaque string token representation.
+ * This token representation is expected to be used as a convenient serialization
+ * form when communicating shared keys.
+ */
+public record SealedSharedKey(int keyId, byte[] eciesPayload, byte[] iv) {
+
+ /** Current encoding version of opaque sealed key tokens. Must be less than 256. */
+ public static final int CURRENT_TOKEN_VERSION = 1;
+
+ private static final int ECIES_AES_IV_LENGTH = SharedKeyGenerator.ECIES_AES_CBC_IV_BITS / 8;
+
+ /**
+ * Creates an opaque URL-safe string token that contains enough information to losslessly
+ * reconstruct the SealedSharedKey instance when passed verbatim to fromTokenString().
+ */
+ public String toTokenString() {
+ if (keyId >= (1 << 24)) {
+ throw new IllegalArgumentException("Key id is too large to be encoded");
+ }
+ if (iv.length != ECIES_AES_IV_LENGTH) {
+ throw new IllegalStateException("Expected a %d byte IV, got %d bytes".formatted(ECIES_AES_IV_LENGTH, iv.length));
+ }
+
+ ByteBuffer encoded = ByteBuffer.allocate(4 + ECIES_AES_IV_LENGTH + eciesPayload.length);
+ encoded.putInt((CURRENT_TOKEN_VERSION << 24) | keyId);
+ encoded.put(iv);
+ encoded.put(eciesPayload);
+ encoded.flip();
+
+ byte[] encBytes = new byte[encoded.remaining()];
+ encoded.get(encBytes);
+ return Base64.getUrlEncoder().withoutPadding().encodeToString(encBytes);
+ }
+
+ /**
+ * Attempts to unwrap a SealedSharedKey opaque token representation that was previously
+ * created by a call to toTokenString().
+ */
+ public static SealedSharedKey fromTokenString(String tokenString) {
+ byte[] rawTokenBytes = Base64.getUrlDecoder().decode(tokenString);
+ if (rawTokenBytes.length < 4) {
+ throw new IllegalArgumentException("Decoded token too small to contain a header");
+ }
+ ByteBuffer decoded = ByteBuffer.wrap(rawTokenBytes);
+ int versionAndKeyId = decoded.getInt();
+ int version = versionAndKeyId >>> 24;
+ if (version != CURRENT_TOKEN_VERSION) {
+ throw new IllegalArgumentException("Token had unexpected version. Expected %d, was %d"
+ .formatted(CURRENT_TOKEN_VERSION, version));
+ }
+ byte[] iv = new byte[ECIES_AES_IV_LENGTH];
+ decoded.get(iv);
+ byte[] eciesPayload = new byte[decoded.remaining()];
+ decoded.get(eciesPayload);
+
+ int keyId = versionAndKeyId & 0xffffff;
+ return new SealedSharedKey(keyId, eciesPayload, iv);
+ }
+
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/SecretSharedKey.java b/security-utils/src/main/java/com/yahoo/security/SecretSharedKey.java
new file mode 100644
index 00000000000..3e90711d57f
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/SecretSharedKey.java
@@ -0,0 +1,24 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security;
+
+import javax.crypto.SecretKey;
+
+/**
+ * A SecretSharedKey represents a pairing of both the secret and public parts of
+ * a secure one-way ephemeral key exchange.
+ *
+ * The underlying SealedSharedKey may be made public, generally as a token.
+ *
+ * It should not come as a surprise that the underlying SecretKey must NOT be
+ * made public.
+ */
+public record SecretSharedKey(SecretKey secretKey, SealedSharedKey sealedSharedKey) {
+
+ // Explicitly override toString to ensure we can't leak any SecretKey contents
+ // via an implicitly generated method. Only print the sealed key (which is entirely public).
+ @Override
+ public String toString() {
+ return "SharedSecretKey(sealed: %s)".formatted(sealedSharedKey.toTokenString());
+ }
+
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java b/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java
new file mode 100644
index 00000000000..07e8243ec09
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java
@@ -0,0 +1,129 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security;
+
+import org.bouncycastle.jcajce.provider.util.BadBlockException;
+import org.bouncycastle.jce.spec.IESParameterSpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+
+/**
+ * Implements both the sender and receiver sides of a secure, anonymous one-way
+ * key generation and exchange protocol implemented using ECIES; a hybrid crypto
+ * scheme built around elliptic curves.
+ *
+ * A shared key, once generated, may have its sealed component sent over a public
+ * channel without revealing anything about the underlying secret key. Only a
+ * recipient holding the private key corresponding to the public used for shared
+ * key creation may derive the same secret key as the sender.
+ *
+ * Every generated key is globally unique (with extremely high probability).
+ *
+ * The secret key is intended to be used <em>only once</em>. It MUST NOT be used to
+ * produce more than a single ciphertext. Using the secret key to produce multiple
+ * ciphertexts completely breaks the security model due to using a fixed Initialization
+ * Vector (IV).
+ */
+public class SharedKeyGenerator {
+
+ private static final int AES_GCM_KEY_BITS = 256;
+ private static final int AES_GCM_AUTH_TAG_BITS = 128;
+ private static final String AES_GCM_ALGO_SPEC = "AES/GCM/NoPadding";
+ private static final String ECIES_CIPHER_NAME = "ECIESwithSHA256andAES-CBC";
+ protected static final int ECIES_AES_CBC_IV_BITS = 128;
+ private static final int ECIES_HMAC_BITS = 256;
+ private static final int ECIES_AES_KEY_BITS = 256;
+ private static final SecureRandom SHARED_CSPRNG = new SecureRandom();
+
+ public static SecretSharedKey generateForReceiverPublicKey(PublicKey receiverPublicKey, int keyId) {
+ try {
+ var keyGen = KeyGenerator.getInstance("AES");
+ keyGen.init(AES_GCM_KEY_BITS, SHARED_CSPRNG);
+ var secretKey = keyGen.generateKey();
+
+ var cipher = Cipher.getInstance(ECIES_CIPHER_NAME, BouncyCastleProviderHolder.getInstance());
+ byte[] iv = new byte[ECIES_AES_CBC_IV_BITS / 8];
+ SHARED_CSPRNG.nextBytes(iv);
+ var iesParamSpec = new IESParameterSpec(null, null, ECIES_HMAC_BITS, ECIES_AES_KEY_BITS, iv);
+
+ cipher.init(Cipher.ENCRYPT_MODE, receiverPublicKey, iesParamSpec);
+ byte[] eciesPayload = cipher.doFinal(secretKey.getEncoded());
+
+ var sealedSharedKey = new SealedSharedKey(keyId, eciesPayload, iv);
+ return new SecretSharedKey(secretKey, sealedSharedKey);
+ } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException
+ | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static SecretSharedKey fromSealedKey(SealedSharedKey sealedKey, PrivateKey receiverPrivateKey) {
+ try {
+ var cipher = Cipher.getInstance(ECIES_CIPHER_NAME, BouncyCastleProviderHolder.getInstance());
+ var iesParamSpec = new IESParameterSpec(null, null, ECIES_HMAC_BITS, ECIES_AES_KEY_BITS, sealedKey.iv());
+ cipher.init(Cipher.DECRYPT_MODE, receiverPrivateKey, iesParamSpec);
+ byte[] secretKey = cipher.doFinal(sealedKey.eciesPayload());
+
+ return new SecretSharedKey(new SecretKeySpec(secretKey, "AES"), sealedKey);
+ } catch (BadBlockException e) {
+ throw new IllegalArgumentException("Token integrity check failed; token is either corrupt or was " +
+ "generated for a different public key");
+ } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException
+ | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // A given key+IV pair can only be used for one single encryption session, ever.
+ // Since our keys are intended to be inherently single-use, we can satisfy that
+ // requirement even with a fixed IV. This avoids the need for explicitly including
+ // the IV with the token, and also avoids tying the encryption to a particular
+ // token recipient (which would be the case if the IV were deterministically derived
+ // from the recipient key and ephemeral ECDH public key), as that would preclude
+ // support for delegated key forwarding.
+ private static byte[] fixed96BitIvForSingleUseKey() {
+ // Nothing up my sleeve!
+ return new byte[] { 'h', 'e', 'r', 'e', 'B', 'd', 'r', 'a', 'g', 'o', 'n', 's' };
+ }
+
+ private static Cipher makeAesGcmCipher(SecretSharedKey secretSharedKey, int cipherMode) {
+ try {
+ var cipher = Cipher.getInstance(AES_GCM_ALGO_SPEC);
+ var gcmSpec = new GCMParameterSpec(AES_GCM_AUTH_TAG_BITS, fixed96BitIvForSingleUseKey());
+ cipher.init(cipherMode, secretSharedKey.secretKey(), gcmSpec);
+ return cipher;
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidKeyException | InvalidAlgorithmParameterException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates an AES-GCM Cipher that can be used to encrypt arbitrary plaintext.
+ *
+ * The given secret key MUST NOT be used to encrypt more than one plaintext.
+ */
+ public static Cipher makeAesGcmEncryptionCipher(SecretSharedKey secretSharedKey) {
+ return makeAesGcmCipher(secretSharedKey, Cipher.ENCRYPT_MODE);
+ }
+
+ /**
+ * Creates an AES-GCM Cipher that can be used to decrypt ciphertext that was previously
+ * encrypted with the given secret key.
+ */
+ public static Cipher makeAesGcmDecryptionCipher(SecretSharedKey secretSharedKey) {
+ return makeAesGcmCipher(secretSharedKey, Cipher.DECRYPT_MODE);
+ }
+
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java b/security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java
new file mode 100644
index 00000000000..1f160d94c6a
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java
@@ -0,0 +1,54 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security;
+
+/**
+ * Utility functions for comparing the contents of arrays without leaking information about the
+ * data contained within them via timing side-channels. This is done by avoiding any branches
+ * that depend on the array elements themselves. This inherently means that all operations have
+ * both an upper and a lower bound in processing time that is O(n) for an array of size n, as there
+ * can be no early exits.
+ *
+ * @author vekterli
+ */
+public class SideChannelSafe {
+
+ /**
+ * @return true iff all bytes in the array are zero. An empty array always returns false
+ * since it technically can't contain any zeros at all.
+ */
+ public static boolean allZeros(byte[] buf) {
+ if (buf.length == 0) {
+ return false;
+ }
+ byte accu = 0;
+ for (byte b : buf) {
+ accu |= b;
+ }
+ return (accu == 0);
+ }
+
+ /**
+ * Compare two byte arrays without the use of data-dependent branching that may leak information
+ * about the contents of either of the arrays.
+ *
+ * <strong>Important:</strong> the <em>length</em> of the arrays is not considered secret, and
+ * will be leaked if arrays of differing sizes are given.
+ *
+ * @param lhs first array of bytes to compare
+ * @param rhs second array of bytes to compare
+ * @return true iff both arrays have the same size and are element-wise identical
+ */
+ public static boolean arraysEqual(byte[] lhs, byte[] rhs) {
+ if (lhs.length != rhs.length) {
+ return false;
+ }
+ // Only use constant time bitwise ops. `accu` will be non-zero if at least one bit
+ // differed in any byte compared between the two arrays.
+ byte accu = 0;
+ for (int i = 0; i < lhs.length; ++i) {
+ accu |= (lhs[i] ^ rhs[i]);
+ }
+ return (accu == 0);
+ }
+
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java b/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java
index c01de58987c..e184d982790 100644
--- a/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java
+++ b/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java
@@ -2,7 +2,7 @@
package com.yahoo.security;
import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.ASN1IA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.GeneralName;
@@ -60,7 +60,7 @@ public class SubjectAlternativeName {
case GeneralName.rfc822Name:
case GeneralName.dNSName:
case GeneralName.uniformResourceIdentifier:
- return DERIA5String.getInstance(name).getString();
+ return ASN1IA5String.getInstance(name).getString();
case GeneralName.directoryName:
return X500Name.getInstance(name).toString();
case GeneralName.iPAddress:
diff --git a/security-utils/src/test/java/com/yahoo/security/HKDFTest.java b/security-utils/src/test/java/com/yahoo/security/HKDFTest.java
new file mode 100644
index 00000000000..bf000cbf8d2
--- /dev/null
+++ b/security-utils/src/test/java/com/yahoo/security/HKDFTest.java
@@ -0,0 +1,298 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security;
+
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.jupiter.api.Test;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * HKDF tests that ensure that the output of our own implementation matches the test
+ * vectors given in <a href="https://tools.ietf.org/html/rfc5869">RFC-5869</a>.
+ *
+ * We don't expose the internal PRK (pseudo-random key) value of the HKDF itself,
+ * so we don't test it explicitly. The actual OKM (output keying material) inherently
+ * depends on it, so its correctness is verified transitively.
+ *
+ * @author vekterli
+ */
+public class HKDFTest {
+
+ private static byte[] fromHex(String hex) {
+ return Hex.decode(hex);
+ }
+
+ private static String toHex(byte[] bytes) {
+ return Hex.toHexString(bytes);
+ }
+
+ private static byte[] sha256DigestOf(byte[]... buffers) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ for (byte[] buf : buffers) {
+ digest.update(buf);
+ }
+ return digest.digest();
+ } catch (NoSuchAlgorithmException e) {
+ // SHA-256 should always be present, so this should never be reached in practice
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ A.1. Test Case 1
+
+ Basic test case with SHA-256
+
+ Hash = SHA-256
+ IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets)
+ salt = 0x000102030405060708090a0b0c (13 octets)
+ info = 0xf0f1f2f3f4f5f6f7f8f9 (10 octets)
+ L = 42
+
+ PRK = 0x077709362c2e32df0ddc3f0dc47bba63
+ 90b6c73bb50f9c3122ec844ad7c2b3e5 (32 octets)
+ OKM = 0x3cb25f25faacd57a90434f64d0362f2a
+ 2d2d0a90cf1a5a4c5db02d56ecc4c5bf
+ 34007208d5b887185865 (42 octets)
+ */
+ @Test
+ void rfc_5869_test_vector_case_1() {
+ var ikm = fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+ var salt = fromHex("000102030405060708090a0b0c");
+ var info = fromHex("f0f1f2f3f4f5f6f7f8f9");
+
+ var hkdf = HKDF.extractedFrom(salt, ikm);
+ var okm = hkdf.expand(42, info);
+ assertEquals("3cb25f25faacd57a90434f64d0362f2a" +
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
+ "34007208d5b887185865",
+ toHex(okm));
+ }
+
+ @Test
+ void rfc_5869_test_vector_case_1_block_boundary_edge_cases() {
+ var ikm = fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+ var salt = fromHex("000102030405060708090a0b0c");
+ var info = fromHex("f0f1f2f3f4f5f6f7f8f9");
+
+ var hkdf = HKDF.extractedFrom(salt, ikm);
+ var okm = hkdf.expand(31, info); // One less than block size
+ assertEquals("3cb25f25faacd57a90434f64d0362f2a" +
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5",
+ toHex(okm));
+
+ okm = hkdf.expand(32, info); // Exactly equal to block size
+ assertEquals("3cb25f25faacd57a90434f64d0362f2a" +
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf",
+ toHex(okm));
+
+ okm = hkdf.expand(33, info); // One more than block size
+ assertEquals("3cb25f25faacd57a90434f64d0362f2a" +
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
+ "34",
+ toHex(okm));
+ }
+
+ /*
+ A.2. Test Case 2
+
+ Test with SHA-256 and longer inputs/outputs
+
+ Hash = SHA-256
+ IKM = 0x000102030405060708090a0b0c0d0e0f
+ 101112131415161718191a1b1c1d1e1f
+ 202122232425262728292a2b2c2d2e2f
+ 303132333435363738393a3b3c3d3e3f
+ 404142434445464748494a4b4c4d4e4f (80 octets)
+ salt = 0x606162636465666768696a6b6c6d6e6f
+ 707172737475767778797a7b7c7d7e7f
+ 808182838485868788898a8b8c8d8e8f
+ 909192939495969798999a9b9c9d9e9f
+ a0a1a2a3a4a5a6a7a8a9aaabacadaeaf (80 octets)
+ info = 0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf
+ c0c1c2c3c4c5c6c7c8c9cacbcccdcecf
+ d0d1d2d3d4d5d6d7d8d9dadbdcdddedf
+ e0e1e2e3e4e5e6e7e8e9eaebecedeeef
+ f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff (80 octets)
+ L = 82
+
+ PRK = 0x06a6b88c5853361a06104c9ceb35b45c
+ ef760014904671014a193f40c15fc244 (32 octets)
+ OKM = 0xb11e398dc80327a1c8e7f78c596a4934
+ 4f012eda2d4efad8a050cc4c19afa97c
+ 59045a99cac7827271cb41c65e590e09
+ da3275600c2f09b8367793a9aca3db71
+ cc30c58179ec3e87c14c01d5c1f3434f
+ 1d87 (82 octets)
+ */
+ @Test
+ void rfc_5869_test_vector_case_2() {
+ var ikm = fromHex("000102030405060708090a0b0c0d0e0f" +
+ "101112131415161718191a1b1c1d1e1f" +
+ "202122232425262728292a2b2c2d2e2f" +
+ "303132333435363738393a3b3c3d3e3f" +
+ "404142434445464748494a4b4c4d4e4f");
+ var salt = fromHex("606162636465666768696a6b6c6d6e6f" +
+ "707172737475767778797a7b7c7d7e7f" +
+ "808182838485868788898a8b8c8d8e8f" +
+ "909192939495969798999a9b9c9d9e9f" +
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf");
+ var info = fromHex("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" +
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" +
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
+
+ var hkdf = HKDF.extractedFrom(salt, ikm);
+ var okm = hkdf.expand(82, info);
+ assertEquals("b11e398dc80327a1c8e7f78c596a4934" +
+ "4f012eda2d4efad8a050cc4c19afa97c" +
+ "59045a99cac7827271cb41c65e590e09" +
+ "da3275600c2f09b8367793a9aca3db71" +
+ "cc30c58179ec3e87c14c01d5c1f3434f" +
+ "1d87",
+ toHex(okm));
+ }
+
+ /*
+ A.3. Test Case 3
+
+ Test with SHA-256 and zero-length salt/info
+
+ Hash = SHA-256
+ IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets)
+ salt = (0 octets)
+ info = (0 octets)
+ L = 42
+
+ PRK = 0x19ef24a32c717b167f33a91d6f648bdf
+ 96596776afdb6377ac434c1c293ccb04 (32 octets)
+ OKM = 0x8da4e775a563c18f715f802a063c5a31
+ b8a11f5c5ee1879ec3454e5f3c738d2d
+ 9d201395faa4b61a96c8 (42 octets)
+ */
+ @Test
+ void rfc_5869_test_vector_case_3() {
+ var ikm = fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+ var info = new byte[0];
+
+ // We don't allow empty salt to the salted factory function, so this is equivalent.
+ var hkdf = HKDF.unsaltedExtractedFrom(ikm);
+ var okm = hkdf.expand(42, info);
+ var expectedOkm = "8da4e775a563c18f715f802a063c5a31" +
+ "b8a11f5c5ee1879ec3454e5f3c738d2d" +
+ "9d201395faa4b61a96c8";
+ assertEquals(expectedOkm, toHex(okm));
+
+ // expand() without explicit context should return as if an empty context array was passed
+ okm = hkdf.expand(42);
+ assertEquals(expectedOkm, toHex(okm));
+ }
+
+ @Test
+ void requested_key_size_is_bounded_and_checked() {
+ var ikm = fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+ var salt = fromHex("000102030405060708090a0b0c");
+ var hkdf = HKDF.extractedFrom(salt, ikm);
+
+ assertThrows(IllegalArgumentException.class, () -> hkdf.expand(-1)); // Can't request negative output size
+
+ assertThrows(IllegalArgumentException.class, () -> hkdf.expand(0)); // Need at least 1 key byte
+
+ assertThrows(IllegalArgumentException.class, () -> hkdf.expand(HKDF.MAX_OUTPUT_SIZE + 1)); // 1 too large
+ }
+
+ @Test
+ void missing_salt_to_salted_factory_function_throws_exception() {
+ var ikm = fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+ assertThrows(NullPointerException.class, () -> HKDF.extractedFrom(null, ikm));
+ assertThrows(IllegalArgumentException.class, () -> HKDF.extractedFrom(new byte[0], ikm));
+ }
+
+ @Test
+ void ikm_can_not_be_null_or_empty() {
+ var salt = fromHex("000102030405060708090a0b0c");
+ assertThrows(NullPointerException.class, () -> HKDF.extractedFrom(salt, null));
+ assertThrows(IllegalArgumentException.class, () -> HKDF.extractedFrom(salt, new byte[0]));
+ assertThrows(NullPointerException.class, () -> HKDF.unsaltedExtractedFrom(null));
+ assertThrows(IllegalArgumentException.class, () -> HKDF.unsaltedExtractedFrom(new byte[0]));
+ }
+
+ //
+ // Subset of Wycheproof test vectors for specific named edge cases
+ // From https://github.com/google/wycheproof/blob/master/testvectors/hkdf_sha256_test.json
+ //
+
+ @Test
+ void maximal_output_size() {
+ var ikm = fromHex("bdd9c30b5fab7f22d859db774779b41cc124daf3ce872f6e80951c0edd8f8214");
+ var salt = fromHex("90983ed74912c6173d0f7cf8164b525361b89bda04d085341a057bde9083b5af");
+ var info = fromHex("e6483e923d37e4ba");
+
+ var hkdf = HKDF.extractedFrom(salt, ikm);
+ assertEquals(8160, HKDF.MAX_OUTPUT_SIZE);
+ var okm = hkdf.expand(HKDF.MAX_OUTPUT_SIZE, info);
+ // To avoid shoving an 8K sized hex string into the source code, check against the pre-hashed
+ // value of the expected OKM output. It's hashes all the way down!
+ var expectedOkmSha256Digest = "c17ce0403e133570191dd1d2ca46f6b62623d62e4f0def8de23a51d65d40a009";
+ var okmDigest = sha256DigestOf(okm);
+ assertEquals(expectedOkmSha256Digest, toHex(okmDigest));
+ }
+
+ @Test
+ void output_collision_for_different_salts() {
+ var ikm = fromHex("5943c65bc33bf05a205b04be8ae0ab2e");
+ var info = fromHex("be082f301a03f87787a80fbea88941214d50c42b");
+ var hkdf = HKDF.unsaltedExtractedFrom(ikm);
+
+ var okm = hkdf.expand(32, info);
+ var expectedOkm = "e7f384df2eae32addabd068a758dec84ed7fcfd87a5fcceb37b70c51422d7387";
+ assertEquals(expectedOkm, toHex(okm));
+
+ var salt = fromHex("0000000000000000000000000000000000000000000000000000000000000000");
+ hkdf = HKDF.extractedFrom(salt, ikm);
+ okm = hkdf.expand(32, info);
+ assertEquals(expectedOkm, toHex(okm));
+ }
+
+ @Test
+ void salt_longer_than_block_size_is_equivalent_to_hash_of_the_salt() {
+ var ikm = fromHex("624a5b59c2be55cbe29ea90c0020a7e8c60f2501");
+ var info = fromHex("5447e595250d02165aae3e61fa90313e25509a7b");
+ var salts = List.of("c737d7278df1ec7c0a549ce964abd51c3df1d3584d49e77208cd3f9f5bbfb32e",
+ "1a08959149f4b073bcd902c9bc4ed0324c21c95590773afc77037d610b9584806aeeeda8b5" +
+ "d588d0cd79e7c12211b8e394067516ce12946d61111a52042b539353");
+ var expectedOkm = "d45c3909269f4b5f9de1fb2eeb0593a7cb9175c8835aba37e0ee0c4cb3bd87c4";
+ for (var salt : salts) {
+ var hkdf = HKDF.extractedFrom(fromHex(salt), ikm);
+ var okm = hkdf.expand(32, info);
+ assertEquals(expectedOkm, toHex(okm));
+ }
+ }
+
+ @Test
+ void salt_shorter_than_the_block_size_is_padded_with_zeros() {
+ var ikm = fromHex("5943c65bc33bf05a205b04be8ae0ab2e");
+ var info = fromHex("be082f301a03f87787a80fbea88941214d50c42b");
+ var expectedOkm = "43e371354001617abb70454751059625ef1a64e0f818469c2f886b27140a0166";
+ var salts = List.of("e69dcaad55fb0536",
+ "e69dcaad55fb05360000000000000000",
+ "e69dcaad55fb053600000000000000000000000000000000",
+ "e69dcaad55fb0536000000000000000000000000000000000000000000000000",
+ "e69dcaad55fb05360000000000000000000000000000000000000000000000000000000000000000",
+ "e69dcaad55fb053600000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "e69dcaad55fb0536000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
+ for (var salt : salts) {
+ var hkdf = HKDF.extractedFrom(fromHex(salt), ikm);
+ var okm = hkdf.expand(32, info);
+ assertEquals(expectedOkm, toHex(okm), "Failed for salt %s".formatted(salt));
+ }
+ }
+
+}
diff --git a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
new file mode 100644
index 00000000000..26a506015c5
--- /dev/null
+++ b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
@@ -0,0 +1,136 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security;
+
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.jupiter.api.Test;
+
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class SharedKeyTest {
+
+ @Test
+ void generated_secret_key_is_256_bit_aes() {
+ var receiverKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
+ var shared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), 1);
+ var secret = shared.secretKey();
+ assertEquals(secret.getAlgorithm(), "AES");
+ assertEquals(secret.getEncoded().length, 32);
+ }
+
+ @Test
+ void sealed_shared_key_can_be_exchanged_via_token_and_computes_identical_secret_key_at_receiver() {
+ var receiverKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
+
+ var myShared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), 1);
+ var publicToken = myShared.sealedSharedKey().toTokenString();
+
+ var theirSealed = SealedSharedKey.fromTokenString(publicToken);
+ var theirShared = SharedKeyGenerator.fromSealedKey(theirSealed, receiverKeyPair.getPrivate());
+
+ assertArrayEquals(myShared.secretKey().getEncoded(), theirShared.secretKey().getEncoded());
+ }
+
+ @Test
+ void token_v1_representation_is_stable() {
+ var receiverPrivate = KeyUtils.fromPemEncodedPrivateKey(
+ """
+ -----BEGIN EC PRIVATE KEY-----
+ MHcCAQEEIO+CkAccoU9jPjX64mwU54Ar9DNZSLBBTYRSINerSW8EoAoGCCqGSM49
+ AwEHoUQDQgAE3FA2VSuOn0vVhtQgNe13H2UE0Vx5A41demyX8nkHTCO4BDXSEPca
+ vejY7YaVcNSvFUbzDvia51X4pxbr1pe56g==
+ -----END EC PRIVATE KEY-----
+ """);
+ var receiverPublic = KeyUtils.fromPemEncodedPublicKey(
+ """
+ -----BEGIN PUBLIC KEY-----
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3FA2VSuOn0vVhtQgNe13H2UE0Vx5
+ A41demyX8nkHTCO4BDXSEPcavejY7YaVcNSvFUbzDvia51X4pxbr1pe56g==
+ -----END PUBLIC KEY-----
+ """
+ );
+ var receiverKeyPair = new KeyPair(receiverPublic, receiverPrivate);
+
+ // Token generated for the above receiver public key, with the below expected shared secret (in hex)
+ var publicToken = "AQAAAUfvuJpugUV3knQXwyP7afgEpDXT4JxaF-x7Ykirty2iwUqJv5UsGx78is5Vu4Mdln_mOVbAUv4dj" +
+ "da7hvzKYNC3IpSMjFrTQ8ab-bEkMpc5tjss_Z7DaJzY4fUlw31Lhx39BMB5yQX0pVLMdFGp5F-_8z8CE" +
+ "-7d9lkCDP9hPKiD77besjrBt_mEBadCd4oNONqc6zzhuQj4O5T9k_RC5VRV";
+ var expectedSharedSecret = "64e01295e736cb827e86cf0281385d5a0dcca217ec1b59f6609a06e2e9debf78";
+
+ var theirSealed = SealedSharedKey.fromTokenString(publicToken);
+ var theirShared = SharedKeyGenerator.fromSealedKey(theirSealed, receiverKeyPair.getPrivate());
+
+ assertEquals(expectedSharedSecret, Hex.toHexString(theirShared.secretKey().getEncoded()));
+ }
+
+ @Test
+ void unrelated_private_key_cannot_decrypt_shared_secret_key() {
+ var aliceKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
+ var eveKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
+ var bobShared = SharedKeyGenerator.generateForReceiverPublicKey(aliceKeyPair.getPublic(), 1);
+ assertThrows(IllegalArgumentException.class, // TODO consider distinct exception class
+ () -> SharedKeyGenerator.fromSealedKey(bobShared.sealedSharedKey(), eveKeyPair.getPrivate()));
+ }
+
+ @Test
+ void token_carries_key_id_as_metadata() {
+ int keyId = 12345;
+ var keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
+ var myShared = SharedKeyGenerator.generateForReceiverPublicKey(keyPair.getPublic(), keyId);
+ var publicToken = myShared.sealedSharedKey().toTokenString();
+ var theirShared = SealedSharedKey.fromTokenString(publicToken);
+ assertEquals(theirShared.keyId(), keyId);
+ }
+
+ static byte[] streamEncryptString(String data, SecretSharedKey secretSharedKey) throws IOException {
+ var cipher = SharedKeyGenerator.makeAesGcmEncryptionCipher(secretSharedKey);
+ var outStream = new ByteArrayOutputStream();
+ try (var cipherStream = new CipherOutputStream(outStream, cipher)) {
+ cipherStream.write(data.getBytes(StandardCharsets.UTF_8));
+ cipherStream.flush();
+ }
+ return outStream.toByteArray();
+ }
+
+ static String streamDecryptString(byte[] encrypted, SecretSharedKey secretSharedKey) throws IOException {
+ var cipher = SharedKeyGenerator.makeAesGcmDecryptionCipher(secretSharedKey);
+ var inStream = new ByteArrayInputStream(encrypted);
+ var total = ByteBuffer.allocate(encrypted.length); // Assume decrypted form can't be _longer_
+ byte[] tmp = new byte[8]; // short buf to test chunking
+ try (var cipherStream = new CipherInputStream(inStream, cipher)) {
+ while (true) {
+ int read = cipherStream.read(tmp);
+ if (read == -1) {
+ break;
+ }
+ total.put(tmp, 0, read);
+ }
+ }
+ total.flip();
+ byte[] strBytes = new byte[total.remaining()];
+ total.get(strBytes);
+ return new String(strBytes, StandardCharsets.UTF_8);
+ }
+
+ @Test
+ void can_create_symmetric_ciphers_from_shared_secret_key_and_public_keys() throws Exception {
+ var receiverKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
+ var myShared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), 1);
+
+ String terrifyingSecret = "birds are not real D:";
+ byte[] encrypted = streamEncryptString(terrifyingSecret, myShared);
+ String decrypted = streamDecryptString(encrypted, myShared);
+ assertEquals(terrifyingSecret, decrypted);
+ }
+
+}
diff --git a/security-utils/src/test/java/com/yahoo/security/SideChannelSafeTest.java b/security-utils/src/test/java/com/yahoo/security/SideChannelSafeTest.java
new file mode 100644
index 00000000000..7a66ed6eb7f
--- /dev/null
+++ b/security-utils/src/test/java/com/yahoo/security/SideChannelSafeTest.java
@@ -0,0 +1,38 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * _Functional_ test of side channel safe utility functions. Testing that they're actually
+ * (probably) side channel safe would be too flaky since it's inherently timing-dependent.
+ */
+public class SideChannelSafeTest {
+
+ @Test
+ void all_zeros_checks_length_and_array_contents() {
+ assertFalse(SideChannelSafe.allZeros(new byte[0]));
+ assertFalse(SideChannelSafe.allZeros(new byte[]{ 1 }));
+ assertTrue(SideChannelSafe.allZeros(new byte[]{ 0 }));
+ assertFalse(SideChannelSafe.allZeros(new byte[]{ 0, 0, 127, 0 }));
+ assertFalse(SideChannelSafe.allZeros(new byte[]{ 0, 0, -1, 0 }));
+ assertTrue(SideChannelSafe.allZeros(new byte[]{ 0, 0, 0 }));
+ }
+
+ @Test
+ void arrays_equal_checks_length_and_array_contents() {
+ assertTrue(SideChannelSafe.arraysEqual(new byte[0], new byte[0]));
+ assertFalse(SideChannelSafe.arraysEqual(new byte[] { 0 }, new byte[0]));
+ assertFalse(SideChannelSafe.arraysEqual(new byte[0], new byte[]{ 0 }));
+ assertTrue(SideChannelSafe.arraysEqual(new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }));
+ assertTrue(SideChannelSafe.arraysEqual(new byte[] { 0x7, 0xe }, new byte[] { 0x7, 0xe }));
+ assertFalse(SideChannelSafe.arraysEqual(new byte[] { 0xe, 0x7 }, new byte[] { 0x7, 0xe }));
+ assertFalse(SideChannelSafe.arraysEqual(new byte[] { -1, 127 }, new byte[] { 127, -1 }));
+ assertFalse(SideChannelSafe.arraysEqual(new byte[] { -1, -1, 1 }, new byte[] { -1, -1, 2 }));
+ assertFalse(SideChannelSafe.arraysEqual(new byte[] { 0, -1, 1 }, new byte[] { 0, -1, 3 }));
+ }
+
+}
diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/health/ApplicationHealthMonitor.java b/service-monitor/src/main/java/com/yahoo/vespa/service/health/ApplicationHealthMonitor.java
index a209b4111ed..b7bf023eebd 100644
--- a/service-monitor/src/main/java/com/yahoo/vespa/service/health/ApplicationHealthMonitor.java
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/health/ApplicationHealthMonitor.java
@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
* @author hakon
*/
class ApplicationHealthMonitor implements ServiceStatusProvider, AutoCloseable {
+
private final ApplicationId applicationId;
private final StateV1HealthModel healthModel;
@@ -86,4 +87,5 @@ class ApplicationHealthMonitor implements ServiceStatusProvider, AutoCloseable {
monitors.clear();
}
}
+
}
diff --git a/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.hpp b/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.hpp
index 9db36e96fc0..125882f7fe7 100644
--- a/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.hpp
+++ b/storage/src/vespa/storage/bucketdb/generic_btree_bucket_database.hpp
@@ -38,14 +38,14 @@ void GenericBTreeBucketDatabase<DataStoreTraitsT>::commit_tree_changes() {
_tree.getAllocator().freeze();
auto current_gen = _generation_handler.getCurrentGeneration();
- _store.transferHoldLists(current_gen);
- _tree.getAllocator().transferHoldLists(current_gen);
+ _store.assign_generation(current_gen);
+ _tree.getAllocator().assign_generation(current_gen);
_generation_handler.incGeneration();
- auto used_gen = _generation_handler.getFirstUsedGeneration();
- _store.trimHoldLists(used_gen);
- _tree.getAllocator().trimHoldLists(used_gen);
+ auto used_gen = _generation_handler.get_oldest_used_generation();
+ _store.reclaim_memory(used_gen);
+ _tree.getAllocator().reclaim_memory(used_gen);
}
template <typename DataStoreTraitsT>
diff --git a/valgrind-suppressions.txt b/valgrind-suppressions.txt
index 63fd7857e76..241c7ef32f7 100644
--- a/valgrind-suppressions.txt
+++ b/valgrind-suppressions.txt
@@ -18,6 +18,16 @@
NPTL keeps a cache of thread stacks, and metadata for thread local storage is not freed for threads in that cache
Memcheck:Leak
fun:calloc
+ fun:UnknownInlinedFun
+ fun:allocate_dtv
+ fun:_dl_allocate_tls
+ fun:allocate_stack
+ fun:pthread_create@@GLIBC_2.2.5
+}
+{
+ NPTL keeps a cache of thread stacks, and metadata for thread local storage is not freed for threads in that cache
+ Memcheck:Leak
+ fun:calloc
fun:allocate_dtv
fun:_dl_allocate_tls
fun:allocate_stack
diff --git a/vespa-athenz/pom.xml b/vespa-athenz/pom.xml
index 55482dd1fed..758a99b38b9 100644
--- a/vespa-athenz/pom.xml
+++ b/vespa-athenz/pom.xml
@@ -92,7 +92,7 @@
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>*</artifactId>
</exclusion>
<!--Exclude all Jackson bundles provided by JDisc -->
<exclusion>
@@ -148,7 +148,7 @@
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>*</artifactId>
</exclusion>
<!--Exclude all Jackson bundles provided by JDisc -->
<exclusion>
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java
index d5b772e5bab..aaf9038208f 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java
@@ -205,6 +205,18 @@ public class DefaultZmsClient extends ClientBase implements ZmsClient {
}
@Override
+ public List<AthenzDomain> getDomainListByAccount(String account) {
+ HttpUriRequest request = RequestBuilder.get()
+ .setUri(zmsUrl.resolve("domain"))
+ .addParameter("account", account)
+ .build();
+ return execute(request, response -> {
+ DomainListResponseEntity result = readEntity(response, DomainListResponseEntity.class);
+ return result.domains.stream().map(AthenzDomain::new).collect(toList());
+ });
+ }
+
+ @Override
public boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity) {
URI uri = zmsUrl.resolve(String.format("access/%s/%s?principal=%s",
action, resource.toResourceNameString(), identity.getFullName()));
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java
index 0dd0d30200c..be4c6c7ba3b 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java
@@ -50,6 +50,8 @@ public interface ZmsClient extends Closeable {
List<AthenzDomain> getDomainList(String prefix);
+ List<AthenzDomain> getDomainListByAccount(String id);
+
boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity);
void createPolicy(AthenzDomain athenzDomain, String athenzPolicy);
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java
index a032b23bfb3..0415bca1670 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identity/SiaIdentityProvider.java
@@ -70,6 +70,10 @@ public class SiaIdentityProvider extends AbstractComponent implements ServiceIde
@Override public Path certificatePath() { return certificateFile; }
@Override public Path privateKeyPath() { return privateKeyFile; }
+ public SSLContext createIdentitySslContextWithTrustStore(Path trustStoreFile) {
+ return createIdentitySslContext(keyManager, trustStoreFile);
+ }
+
private static SSLContext createIdentitySslContext(AutoReloadingX509KeyManager keyManager, Path trustStoreFile) {
return new SslContextBuilder()
.withTrustStore(trustStoreFile)
diff --git a/vespa-feed-client/pom.xml b/vespa-feed-client/pom.xml
index 8b7b82573c4..1cc2f2adee1 100644
--- a/vespa-feed-client/pom.xml
+++ b/vespa-feed-client/pom.xml
@@ -21,7 +21,7 @@
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
+ <artifactId>bcpkix-jdk18on</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
diff --git a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java
index aaf67f6b8ea..ce85b7d6f32 100644
--- a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java
+++ b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java
@@ -43,6 +43,9 @@ public abstract class AbstractVespaMojo extends AbstractMojo {
@Parameter(property = "instance")
protected String instance;
+ @Parameter(property = "tags")
+ protected String tags;
+
@Parameter(property = "apiKey")
protected String apiKey;
diff --git a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java
index 556af8b6f85..377975b3d01 100644
--- a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java
+++ b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java
@@ -3,6 +3,7 @@ package ai.vespa.hosted.plugin;
import com.yahoo.config.application.XmlPreProcessor;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
@@ -44,16 +45,17 @@ public class EffectiveServicesMojo extends AbstractVespaDeploymentMojo {
ZoneId zone = zoneOf(environment, region);
Path output = Paths.get(outputDirectory).resolve("services-" + zone.environment().value() + "-" + zone.region().value() + ".xml");
- Files.write(output, effectiveServices(services, zone, InstanceName.from(instance)).getBytes(StandardCharsets.UTF_8));
+ Files.write(output, effectiveServices(services, zone, InstanceName.from(instance), Tags.fromString(tags)).getBytes(StandardCharsets.UTF_8));
getLog().info("Effective services for " + zone + " written to " + output);
}
- static String effectiveServices(File servicesFile, ZoneId zone, InstanceName instance) throws Exception {
+ static String effectiveServices(File servicesFile, ZoneId zone, InstanceName instance, Tags tags) throws Exception {
Document processedServicesXml = new XmlPreProcessor(servicesFile.getParentFile(),
servicesFile,
instance,
zone.environment(),
- zone.region())
+ zone.region(),
+ tags)
.run();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
diff --git a/vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java b/vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java
index 3cb08f5f2b6..fb7376b13cb 100644
--- a/vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java
+++ b/vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java
@@ -2,6 +2,7 @@
package ai.vespa.hosted.plugin;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -26,21 +27,21 @@ class EffectiveServicesMojoTest {
@DisplayName("when zone matches environment-only directive")
void devServices() throws Exception {
assertEquals(Files.readString(Paths.get("src/test/resources/effective-services/dev.xml")),
- effectiveServices(servicesFile, ZoneId.from("dev", "us-east-3"), InstanceName.defaultName()));
+ effectiveServices(servicesFile, ZoneId.from("dev", "us-east-3"), InstanceName.defaultName(), Tags.empty()));
}
@Test
@DisplayName("when zone matches region-and-environment directive")
void prodUsEast3() throws Exception {
assertEquals(Files.readString(Paths.get("src/test/resources/effective-services/prod_us-east-3.xml")),
- effectiveServices(servicesFile, ZoneId.from("prod", "us-east-3"), InstanceName.defaultName()));
+ effectiveServices(servicesFile, ZoneId.from("prod", "us-east-3"), InstanceName.defaultName(), Tags.empty()));
}
@Test
@DisplayName("when zone doesn't match any directives")
void prodUsWest1Services() throws Exception {
assertEquals(Files.readString(Paths.get("src/test/resources/effective-services/prod_us-west-1.xml")),
- effectiveServices(servicesFile, ZoneId.from("prod", "us-west-1"), InstanceName.defaultName()));
+ effectiveServices(servicesFile, ZoneId.from("prod", "us-west-1"), InstanceName.defaultName(), Tags.empty()));
}
}
diff --git a/vespabase/CMakeLists.txt b/vespabase/CMakeLists.txt
index bf7ea3dfcc7..ce19dbb56b3 100644
--- a/vespabase/CMakeLists.txt
+++ b/vespabase/CMakeLists.txt
@@ -29,6 +29,7 @@ install(DIRECTORY DESTINATION var/db/vespa)
install(DIRECTORY DESTINATION var/db/vespa/config_server)
install(DIRECTORY DESTINATION var/db/vespa/config_server/serverdb)
install(DIRECTORY DESTINATION var/db/vespa/config_server/serverdb/tenants)
+install(DIRECTORY DESTINATION var/db/vespa/download)
install(DIRECTORY DESTINATION var/db/vespa/filedistribution)
install(DIRECTORY DESTINATION var/db/vespa/index)
install(DIRECTORY DESTINATION var/db/vespa/search)
diff --git a/vespabase/src/rhel-prestart.sh b/vespabase/src/rhel-prestart.sh
index ff28b31ca2b..79a8e61848c 100755
--- a/vespabase/src/rhel-prestart.sh
+++ b/vespabase/src/rhel-prestart.sh
@@ -116,6 +116,7 @@ fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa
fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/config_server
fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/config_server/serverdb
fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/config_server/serverdb/tenants
+fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/download
fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/filedistribution
fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/index
fixdir ${VESPA_USER} ${VESPA_GROUP} 755 var/db/vespa/logcontrol
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
index c72bc1ef4c5..66154ec1c28 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
@@ -12,7 +12,6 @@ import com.yahoo.container.core.documentapi.VespaDocumentAccess;
import com.yahoo.container.jdisc.ContentChannelOutputStream;
import com.yahoo.document.Document;
import com.yahoo.document.DocumentId;
-import com.yahoo.document.DocumentOperation;
import com.yahoo.document.DocumentPut;
import com.yahoo.document.DocumentRemove;
import com.yahoo.document.DocumentTypeManager;
@@ -26,6 +25,7 @@ import com.yahoo.document.idstring.IdIdString;
import com.yahoo.document.json.DocumentOperationType;
import com.yahoo.document.json.JsonReader;
import com.yahoo.document.json.JsonWriter;
+import com.yahoo.document.json.ParsedDocumentOperation;
import com.yahoo.document.restapi.DocumentOperationExecutorConfig;
import com.yahoo.document.select.parser.ParseException;
import com.yahoo.documentapi.AckToken;
@@ -38,6 +38,7 @@ import com.yahoo.documentapi.ProgressToken;
import com.yahoo.documentapi.Response.Outcome;
import com.yahoo.documentapi.Result;
import com.yahoo.documentapi.VisitorControlHandler;
+import com.yahoo.documentapi.VisitorControlSession;
import com.yahoo.documentapi.VisitorDataHandler;
import com.yahoo.documentapi.VisitorParameters;
import com.yahoo.documentapi.VisitorSession;
@@ -67,6 +68,7 @@ import com.yahoo.restapi.Path;
import com.yahoo.search.query.ParameterParser;
import com.yahoo.text.Text;
import com.yahoo.vespa.config.content.AllClustersBucketSpacesConfig;
+import com.yahoo.vespa.http.server.Headers;
import com.yahoo.vespa.http.server.MetricNames;
import com.yahoo.yolean.Exceptions;
import com.yahoo.yolean.Exceptions.RunnableThrowingIOException;
@@ -215,7 +217,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
this.operations = new ConcurrentLinkedDeque<>();
long resendDelayMS = SystemTimer.adjustTimeoutByDetectedHz(Duration.ofMillis(executorConfig.resendDelayMillis())).toMillis();
- // TODO: Here it would be better do have dedicated threads with different wait depending on blocked or empty.
+ // TODO: Here it would be better to have dedicated threads with different wait depending on blocked or empty.
this.dispatcher.scheduleWithFixedDelay(this::dispatchEnqueued, resendDelayMS, resendDelayMS, MILLISECONDS);
this.visitDispatcher.scheduleWithFixedDelay(this::dispatchVisitEnqueued, resendDelayMS, resendDelayMS, MILLISECONDS);
}
@@ -238,7 +240,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
MILLISECONDS);
Path requestPath = Path.withoutValidation(request.getUri()); // No segment validation here, as document IDs can be anything.
- for (String path : handlers.keySet())
+ for (String path : handlers.keySet()) {
if (requestPath.matches(path)) {
Map<Method, Handler> methods = handlers.get(path);
if (methods.containsKey(request.getMethod()))
@@ -249,6 +251,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
methodNotAllowed(request, methods.keySet(), responseHandler);
}
+ }
notFound(request, handlers.keySet(), responseHandler);
}
catch (IllegalArgumentException e) {
@@ -398,10 +401,10 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
parameters.setFieldSet(DocIdOnly.NAME);
String type = path.documentType().orElseThrow(() -> new IllegalStateException("Document type must be specified for mass updates"));
IdIdString dummyId = new IdIdString("dummy", type, "", "");
- DocumentUpdate update = parser.parseUpdate(in, dummyId.toString());
- update.setCondition(new TestAndSetCondition(requireProperty(request, SELECTION)));
+ ParsedDocumentOperation update = parser.parseUpdate(in, dummyId.toString());
+ update.operation().setCondition(new TestAndSetCondition(requireProperty(request, SELECTION)));
return () -> {
- visitAndUpdate(request, parameters, handler, update, cluster.name());
+ visitAndUpdate(request, parameters, update.fullyApplied(), handler, (DocumentUpdate)update.operation(), cluster.name());
return true; // VisitorSession has its own throttle handling.
};
});
@@ -448,21 +451,21 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private ContentChannel postDocument(HttpRequest request, DocumentPath path, ResponseHandler rawHandler) {
ResponseHandler handler = new MeasuringResponseHandler(rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.PUT, clock.instant());
if (getProperty(request, DRY_RUN, booleanParser).orElse(false)) {
- handleFeedOperation(path, handler, new com.yahoo.documentapi.Response(-1));
+ handleFeedOperation(path, true, handler, new com.yahoo.documentapi.Response(-1));
return ignoredContent;
}
return new ForwardingContentChannel(in -> {
enqueueAndDispatch(request, handler, () -> {
- DocumentPut put = parser.parsePut(in, path.id().toString());
- getProperty(request, CONDITION).map(TestAndSetCondition::new).ifPresent(put::setCondition);
+ ParsedDocumentOperation put = parser.parsePut(in, path.id().toString());
+ getProperty(request, CONDITION).map(TestAndSetCondition::new).ifPresent(c -> put.operation().setCondition(c));
DocumentOperationParameters parameters = parametersFromRequest(request, ROUTE)
.withResponseHandler(response -> {
outstanding.decrementAndGet();
updatePutMetrics(response.outcome());
- handleFeedOperation(path, handler, response);
+ handleFeedOperation(path, put.fullyApplied(), handler, response);
});
- return () -> dispatchOperation(() -> asyncSession.put(put, parameters));
+ return () -> dispatchOperation(() -> asyncSession.put((DocumentPut)put.operation(), parameters));
});
});
}
@@ -470,20 +473,21 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private ContentChannel putDocument(HttpRequest request, DocumentPath path, ResponseHandler rawHandler) {
ResponseHandler handler = new MeasuringResponseHandler(rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.UPDATE, clock.instant());
if (getProperty(request, DRY_RUN, booleanParser).orElse(false)) {
- handleFeedOperation(path, handler, new com.yahoo.documentapi.Response(-1));
+ handleFeedOperation(path, true, handler, new com.yahoo.documentapi.Response(-1));
return ignoredContent;
}
return new ForwardingContentChannel(in -> {
enqueueAndDispatch(request, handler, () -> {
- DocumentUpdate update = parser.parseUpdate(in, path.id().toString());
+ ParsedDocumentOperation parsed = parser.parseUpdate(in, path.id().toString());
+ DocumentUpdate update = (DocumentUpdate)parsed.operation();
getProperty(request, CONDITION).map(TestAndSetCondition::new).ifPresent(update::setCondition);
getProperty(request, CREATE, booleanParser).ifPresent(update::setCreateIfNonExistent);
DocumentOperationParameters parameters = parametersFromRequest(request, ROUTE)
.withResponseHandler(response -> {
outstanding.decrementAndGet();
updateUpdateMetrics(response.outcome(), update.getCreateIfNonExistent());
- handleFeedOperation(path, handler, response);
+ handleFeedOperation(path, parsed.fullyApplied(), handler, response);
});
return () -> dispatchOperation(() -> asyncSession.update(update, parameters));
});
@@ -493,7 +497,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private ContentChannel deleteDocument(HttpRequest request, DocumentPath path, ResponseHandler rawHandler) {
ResponseHandler handler = new MeasuringResponseHandler(rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.REMOVE, clock.instant());
if (getProperty(request, DRY_RUN, booleanParser).orElse(false)) {
- handleFeedOperation(path, handler, new com.yahoo.documentapi.Response(-1));
+ handleFeedOperation(path, true, handler, new com.yahoo.documentapi.Response(-1));
return ignoredContent;
}
@@ -504,7 +508,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
.withResponseHandler(response -> {
outstanding.decrementAndGet();
updateRemoveMetrics(response.outcome());
- handleFeedOperation(path, handler, response);
+ handleFeedOperation(path, true, handler, response);
});
return () -> dispatchOperation(() -> asyncSession.remove(remove, parameters));
});
@@ -659,10 +663,16 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
return response;
}
- /** Commits a response with the given status code and some default headers, and writes whatever content is buffered. */
synchronized void commit(int status) throws IOException {
+ commit(status, true);
+ }
+
+ /** Commits a response with the given status code and some default headers, and writes whatever content is buffered. */
+ synchronized void commit(int status, boolean fullyApplied) throws IOException {
Response response = new Response(status);
- response.headers().addAll(Map.of("Content-Type", List.of("application/json; charset=UTF-8")));
+ response.headers().add("Content-Type", List.of("application/json; charset=UTF-8"));
+ if (! fullyApplied)
+ response.headers().add(Headers.IGNORED_FIELDS, "true");
try {
channel = handler.handleResponse(response);
buffer.connectTo(channel);
@@ -1023,16 +1033,16 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
this.manager = new DocumentTypeManager(config);
}
- DocumentPut parsePut(InputStream inputStream, String docId) {
- return (DocumentPut) parse(inputStream, docId, DocumentOperationType.PUT);
+ ParsedDocumentOperation parsePut(InputStream inputStream, String docId) {
+ return parse(inputStream, docId, DocumentOperationType.PUT);
}
- DocumentUpdate parseUpdate(InputStream inputStream, String docId) {
- return (DocumentUpdate) parse(inputStream, docId, DocumentOperationType.UPDATE);
+ ParsedDocumentOperation parseUpdate(InputStream inputStream, String docId) {
+ return parse(inputStream, docId, DocumentOperationType.UPDATE);
}
- private DocumentOperation parse(InputStream inputStream, String docId, DocumentOperationType operation) {
- return new JsonReader(manager, inputStream, jsonFactory).readSingleDocument(operation, docId);
+ private ParsedDocumentOperation parse(InputStream inputStream, String docId, DocumentOperationType operation) {
+ return new JsonReader(manager, inputStream, jsonFactory).readOperation(operation, docId);
}
}
@@ -1041,7 +1051,11 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
void onSuccess(Document document, JsonResponse response) throws IOException;
}
- private static void handle(DocumentPath path, HttpRequest request, ResponseHandler handler, com.yahoo.documentapi.Response response, SuccessCallback callback) {
+ private static void handle(DocumentPath path,
+ HttpRequest request,
+ ResponseHandler handler,
+ com.yahoo.documentapi.Response response,
+ SuccessCallback callback) {
try (JsonResponse jsonResponse = JsonResponse.create(path, handler, request)) {
jsonResponse.writeTrace(response.getTrace());
if (response.isSuccess())
@@ -1049,25 +1063,18 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
else {
jsonResponse.writeMessage(response.getTextMessage());
switch (response.outcome()) {
- case NOT_FOUND:
- jsonResponse.commit(Response.Status.NOT_FOUND);
- break;
- case CONDITION_FAILED:
- jsonResponse.commit(Response.Status.PRECONDITION_FAILED);
- break;
- case INSUFFICIENT_STORAGE:
- jsonResponse.commit(Response.Status.INSUFFICIENT_STORAGE);
- break;
- case TIMEOUT:
- jsonResponse.commit(Response.Status.GATEWAY_TIMEOUT);
- break;
- case ERROR:
+ case NOT_FOUND -> jsonResponse.commit(Response.Status.NOT_FOUND);
+ case CONDITION_FAILED -> jsonResponse.commit(Response.Status.PRECONDITION_FAILED);
+ case INSUFFICIENT_STORAGE -> jsonResponse.commit(Response.Status.INSUFFICIENT_STORAGE);
+ case TIMEOUT -> jsonResponse.commit(Response.Status.GATEWAY_TIMEOUT);
+ case ERROR -> {
log.log(FINE, () -> "Exception performing document operation: " + response.getTextMessage());
jsonResponse.commit(Response.Status.BAD_GATEWAY);
- break;
- default:
+ }
+ default -> {
log.log(WARNING, "Unexpected document API operation outcome '" + response.outcome() + "' " + response.getTextMessage());
jsonResponse.commit(Response.Status.BAD_GATEWAY);
+ }
}
}
}
@@ -1076,8 +1083,11 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
}
- private static void handleFeedOperation(DocumentPath path, ResponseHandler handler, com.yahoo.documentapi.Response response) {
- handle(path, null, handler, response, (document, jsonResponse) -> jsonResponse.commit(Response.Status.OK));
+ private static void handleFeedOperation(DocumentPath path,
+ boolean fullyApplied,
+ ResponseHandler handler,
+ com.yahoo.documentapi.Response response) {
+ handle(path, null, handler, response, (document, jsonResponse) -> jsonResponse.commit(Response.Status.OK, fullyApplied));
}
private void updatePutMetrics(Outcome outcome) {
@@ -1166,6 +1176,8 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
StringJoiner::merge)
.toString());
+ getProperty(request, TRACELEVEL, integerParser).ifPresent(parameters::setTraceLevel);
+
getProperty(request, CONTINUATION).map(ProgressToken::fromSerializedString).ifPresent(parameters::setResumeToken);
parameters.setPriority(DocumentProtocol.Priority.NORMAL_4);
@@ -1188,7 +1200,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private interface VisitCallback {
/** Called at the start of response rendering. */
- default void onStart(JsonResponse response) throws IOException { }
+ default void onStart(JsonResponse response, boolean fullyApplied) throws IOException { }
/** Called for every document received from backend visitors—must call the ack for these to proceed. */
default void onDocument(JsonResponse response, Document document, Runnable ack, Consumer<String> onError) { }
@@ -1199,25 +1211,26 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private void visitAndDelete(HttpRequest request, VisitorParameters parameters, ResponseHandler handler,
TestAndSetCondition condition, String route) {
- visitAndProcess(request, parameters, handler, route, (id, operationParameters) -> {
+ visitAndProcess(request, parameters, true, handler, route, (id, operationParameters) -> {
DocumentRemove remove = new DocumentRemove(id);
remove.setCondition(condition);
return asyncSession.remove(remove, operationParameters);
});
}
- private void visitAndUpdate(HttpRequest request, VisitorParameters parameters, ResponseHandler handler,
- DocumentUpdate protoUpdate, String route) {
- visitAndProcess(request, parameters, handler, route, (id, operationParameters) -> {
+ private void visitAndUpdate(HttpRequest request, VisitorParameters parameters, boolean fullyApplied,
+ ResponseHandler handler, DocumentUpdate protoUpdate, String route) {
+ visitAndProcess(request, parameters, fullyApplied, handler, route, (id, operationParameters) -> {
DocumentUpdate update = new DocumentUpdate(protoUpdate);
update.setId(id);
return asyncSession.update(update, operationParameters);
});
}
- private void visitAndProcess(HttpRequest request, VisitorParameters parameters, ResponseHandler handler,
+ private void visitAndProcess(HttpRequest request, VisitorParameters parameters, boolean fullyApplied,
+ ResponseHandler handler,
String route, BiFunction<DocumentId, DocumentOperationParameters, Result> operation) {
- visit(request, parameters, false, handler, new VisitCallback() {
+ visit(request, parameters, false, fullyApplied, handler, new VisitCallback() {
@Override public void onDocument(JsonResponse response, Document document, Runnable ack, Consumer<String> onError) {
DocumentOperationParameters operationParameters = parameters().withRoute(route)
.withResponseHandler(operationResponse -> {
@@ -1255,10 +1268,10 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
private void visitAndWrite(HttpRequest request, VisitorParameters parameters, ResponseHandler handler, boolean streamed) {
- visit(request, parameters, streamed, handler, new VisitCallback() {
- @Override public void onStart(JsonResponse response) throws IOException {
+ visit(request, parameters, streamed, true, handler, new VisitCallback() {
+ @Override public void onStart(JsonResponse response, boolean fullyApplied) throws IOException {
if (streamed)
- response.commit(Response.Status.OK);
+ response.commit(Response.Status.OK, fullyApplied);
response.writeDocumentsArrayStart();
}
@@ -1288,18 +1301,23 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
private void visitWithRemote(HttpRequest request, VisitorParameters parameters, ResponseHandler handler) {
- visit(request, parameters, false, handler, new VisitCallback() { });
+ visit(request, parameters, false, true, handler, new VisitCallback() { });
}
@SuppressWarnings("fallthrough")
- private void visit(HttpRequest request, VisitorParameters parameters, boolean streaming, ResponseHandler handler, VisitCallback callback) {
+ private void visit(HttpRequest request, VisitorParameters parameters, boolean streaming, boolean fullyApplied, ResponseHandler handler, VisitCallback callback) {
try {
JsonResponse response = JsonResponse.create(request, handler);
Phaser phaser = new Phaser(2); // Synchronize this thread (dispatch) with the visitor callback thread.
AtomicReference<String> error = new AtomicReference<>(); // Set if error occurs during processing of visited documents.
- callback.onStart(response);
+ callback.onStart(response, fullyApplied);
VisitorControlHandler controller = new VisitorControlHandler() {
final ScheduledFuture<?> abort = streaming ? visitDispatcher.schedule(this::abort, request.getTimeout(MILLISECONDS), MILLISECONDS) : null;
+ final AtomicReference<VisitorSession> session = new AtomicReference<>();
+ @Override public void setSession(VisitorControlSession session) { // Workaround for broken session API ಠ_ಠ
+ super.setSession(session);
+ if (session instanceof VisitorSession visitorSession) this.session.set(visitorSession);
+ }
@Override public void onDone(CompletionCode code, String message) {
super.onDone(code, message);
loggingException(() -> {
@@ -1309,6 +1327,9 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
if (getVisitorStatistics() != null)
response.writeDocumentCount(getVisitorStatistics().getDocumentsVisited());
+ if (session.get() != null)
+ response.writeTrace(session.get().getTrace());
+
int status = Response.Status.BAD_GATEWAY;
switch (code) {
case TIMEOUT:
@@ -1332,7 +1353,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
response.writeMessage(error.get() != null ? error.get() : message != null ? message : "Visiting failed");
}
if ( ! streaming)
- response.commit(status);
+ response.commit(status, fullyApplied);
}
});
if (abort != null) abort.cancel(false); // Avoid keeping scheduled future alive if this completes in any other fashion.
@@ -1340,7 +1361,6 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
phaser.arriveAndAwaitAdvance(); // We may get here while dispatching thread is still putting us in the map.
visits.remove(this).destroy();
});
-
}
};
if (parameters.getRemoteDataHandler() == null) {
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
index 8ea9234009d..438248f31a7 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.http.server;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.document.DocumentTypeManager;
-import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.ReferencedResource;
import com.yahoo.jdisc.ResourceReference;
@@ -53,13 +52,12 @@ class ClientFeederV3 {
private final AtomicInteger ongoingRequests = new AtomicInteger(0);
private final String hostName;
- ClientFeederV3(
- ReferencedResource<SharedSourceSession> sourceSession,
- FeedReaderFactory feedReaderFactory,
- DocumentTypeManager docTypeManager,
- String clientId,
- Metric metric,
- ReplyHandler feedReplyHandler) {
+ ClientFeederV3(ReferencedResource<SharedSourceSession> sourceSession,
+ FeedReaderFactory feedReaderFactory,
+ DocumentTypeManager docTypeManager,
+ String clientId,
+ Metric metric,
+ ReplyHandler feedReplyHandler) {
this.sourceSession = sourceSession;
this.clientId = clientId;
this.feedReplyHandler = feedReplyHandler;
@@ -220,10 +218,7 @@ class ClientFeederV3 {
// This is a bit hard to set up while testing, so we accept that things are not perfect.
if (sourceSession.getResource().session() != null) {
- metric.set(
- MetricNames.PENDING,
- Double.valueOf(sourceSession.getResource().session().getPendingCount()),
- null);
+ metric.set(MetricNames.PENDING, (double) sourceSession.getResource().session().getPendingCount(), null);
}
DocumentOperationMessageV3 message = DocumentOperationMessageV3.create(operation, operationId, metric);
@@ -231,7 +226,7 @@ class ClientFeederV3 {
// typical end of feed
return null;
}
- metric.add(MetricNames.NUM_OPERATIONS, 1, null /*metricContext*/);
+ metric.add(MetricNames.NUM_OPERATIONS, 1, null);
log(Level.FINE, "Successfully deserialized document id: ", message.getOperationId());
return message;
}
@@ -241,14 +236,6 @@ class ClientFeederV3 {
if (settings.traceLevel != null) {
msg.getMessage().getTrace().setLevel(settings.traceLevel);
}
- if (settings.priority != null) {
- try {
- DocumentProtocol.Priority priority = DocumentProtocol.Priority.valueOf(settings.priority);
- }
- catch (IllegalArgumentException i) {
- log.severe(i.getMessage());
- }
- }
}
private void setRoute(DocumentOperationMessageV3 msg, FeederSettings settings) {
@@ -272,7 +259,7 @@ class ClientFeederV3 {
if (now.plusSeconds(1).isAfter(prevOpsPerSecTime)) {
Duration duration = Duration.between(now, prevOpsPerSecTime);
double opsPerSec = operationsForOpsPerSec / (duration.toMillis() / 1000.);
- metric.set(MetricNames.OPERATIONS_PER_SEC, opsPerSec, null /*metricContext*/);
+ metric.set(MetricNames.OPERATIONS_PER_SEC, opsPerSec, null);
operationsForOpsPerSec = 1.0d;
prevOpsPerSecTime = now;
} else {
@@ -280,4 +267,5 @@ class ClientFeederV3 {
}
}
}
+
}
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/DocumentOperationMessageV3.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/DocumentOperationMessageV3.java
index 25bf5815907..a12fe4efa8b 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/DocumentOperationMessageV3.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/DocumentOperationMessageV3.java
@@ -14,8 +14,6 @@ import com.yahoo.vespaxmlparser.FeedOperation;
/**
* Keeps an operation with its message.
*
- * This implementation is based on V2, but the code is restructured.
- *
* @author dybis
*/
class DocumentOperationMessageV3 {
@@ -66,13 +64,13 @@ class DocumentOperationMessageV3 {
static DocumentOperationMessageV3 create(FeedOperation operation, String operationId, Metric metric) {
switch (operation.getType()) {
case DOCUMENT:
- metric.add(MetricNames.NUM_PUTS, 1, null /*metricContext*/);
+ metric.add(MetricNames.NUM_PUTS, 1, null);
return newPutMessage(operation, operationId);
case REMOVE:
- metric.add(MetricNames.NUM_REMOVES, 1, null /*metricContext*/);
+ metric.add(MetricNames.NUM_REMOVES, 1, null);
return newRemoveMessage(operation, operationId);
case UPDATE:
- metric.add(MetricNames.NUM_UPDATES, 1, null /*metricContext*/);
+ metric.add(MetricNames.NUM_UPDATES, 1, null);
return newUpdateMessage(operation, operationId);
default:
// typical end of feed
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandler.java
index 74665d60a04..3c1f376b4eb 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandler.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandler.java
@@ -29,14 +29,14 @@ import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
/**
- * Accept feeds from outside of the Vespa cluster.
+ * Accept feeds from outside the Vespa cluster.
*
* @author Steinar Knutsen
*/
public class FeedHandler extends ThreadedHttpRequestHandler {
protected final ReplyHandler feedReplyHandler;
- private static final List<Integer> serverSupportedVersions = Collections.unmodifiableList(Arrays.asList(3));
+ private static final List<Integer> serverSupportedVersions = List.of(3);
private static final Pattern USER_AGENT_PATTERN = Pattern.compile("vespa-http-client \\((.+)\\)");
private final FeedHandlerV3 feedHandlerV3;
private final DocumentApiMetrics metricsHelper;
@@ -144,4 +144,5 @@ public class FeedHandler extends ThreadedHttpRequestHandler {
}
@Override protected void destroy() { feedHandlerV3.destroy(); }
+
}
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandlerV3.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandlerV3.java
index f9ae04623e6..4de3eebec2d 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandlerV3.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedHandlerV3.java
@@ -24,9 +24,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
- * This code is based on v2 code, however, in v3, one client has one ClientFeederV3 shared between all client threads.
- * The new API has more logic for shutting down cleanly as the server is more likely to be upgraded.
- * The code is restructured a bit.
+ * One client has one ClientFeederV3 shared between all client threads.
+ * Contains logic for shutting down cleanly as the server is upgraded.
*
* @author dybis
*/
@@ -60,7 +59,7 @@ public class FeedHandlerV3 extends ThreadedHttpRequestHandler {
}
// TODO: If this is set up to run without first invoking the old FeedHandler code, we should
- // verify the version header first. This is done in the old code.
+ // verify the version header first.
@Override
public HttpResponse handle(HttpRequest request) {
String clientId = clientId(request);
@@ -70,7 +69,7 @@ public class FeedHandlerV3 extends ThreadedHttpRequestHandler {
SourceSessionParams sourceSessionParams = sourceSessionParams(request);
clientFeederByClientId.put(clientId,
new ClientFeederV3(retainSource(sessionCache, sourceSessionParams),
- new FeedReaderFactory(true), //TODO make error debugging configurable
+ new FeedReaderFactory(true), // TODO: Make error debugging configurable
docTypeManager,
clientId,
metric,
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java
index 9bb8a58d6f6..a8175a48a39 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeederSettings.java
@@ -17,14 +17,12 @@ public class FeederSettings {
public final boolean drain; // TODO: Implement drain=true
public final Route route;
public final FeedParams.DataFormat dataFormat;
- public final String priority;
public final Integer traceLevel;
public FeederSettings(HttpRequest request) {
this.drain = Optional.ofNullable(request.getHeader(Headers.DRAIN)).map(Boolean::parseBoolean).orElse(false);
this.route = Optional.ofNullable(request.getHeader(Headers.ROUTE)).map(Route::parse).orElse(DEFAULT_ROUTE);
this.dataFormat = Optional.ofNullable(request.getHeader(Headers.DATA_FORMAT)).map(FeedParams.DataFormat::valueOf).orElse(FeedParams.DataFormat.JSON_UTF8);
- this.priority = request.getHeader(Headers.PRIORITY);
this.traceLevel = Optional.ofNullable(request.getHeader(Headers.TRACE_LEVEL)).map(Integer::valueOf).orElse(null);
}
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Headers.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Headers.java
index 16bff38af4b..657c22ba7ee 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Headers.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Headers.java
@@ -6,7 +6,7 @@ package com.yahoo.vespa.http.server;
*
* @author Steinar Knutsen
*/
-final class Headers {
+public final class Headers {
private Headers() {
}
@@ -23,7 +23,6 @@ final class Headers {
// This value can be used to route the request to a specific server when using
// several servers. It is a random value that is the same for the whole session.
public static final String SHARDING_KEY = "X-Yahoo-Feed-Sharding-Key";
- public static final String PRIORITY = "X-Yahoo-Feed-Priority";
public static final String TRACE_LEVEL = "X-Yahoo-Feed-Trace-Level";
public static final int HTTP_NOT_ACCEPTABLE = 406;
@@ -34,4 +33,8 @@ final class Headers {
public static final String HOSTNAME = "X-Yahoo-Hostname";
public static final String SILENTUPGRADE = "X-Yahoo-Silent-Upgrade";
+ // A response header present and set to "true" onlynif any fields of a document operation were ignored
+ // because they were not declared in the target document type.
+ public static final String IGNORED_FIELDS = "X-Vespa-Ignored-Fields";
+
}
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/StreamReaderV3.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/StreamReaderV3.java
index c2c6d00fa25..3d82919d503 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/StreamReaderV3.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/StreamReaderV3.java
@@ -10,17 +10,15 @@ import com.yahoo.vespaxmlparser.FeedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
-import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
/**
* This code is based on v2 code, but restructured so stream reading code is in one dedicated class.
+ *
* @author dybis
*/
public class StreamReaderV3 {
- protected static final Logger log = Logger.getLogger(StreamReaderV3.class.getName());
-
private final FeedReaderFactory feedReaderFactory;
private final DocumentTypeManager docTypeManager;
@@ -30,15 +28,11 @@ public class StreamReaderV3 {
}
public FeedOperation getNextOperation(InputStream requestInputStream, FeederSettings settings) throws Exception {
- FeedOperation op = null;
-
int length = readByteLength(requestInputStream);
-
try (InputStream limitedInputStream = new ByteLimitedInputStream(requestInputStream, length)){
FeedReader reader = feedReaderFactory.createReader(limitedInputStream, docTypeManager, settings.dataFormat);
- op = reader.read();
+ return reader.read();
}
- return op;
}
public Optional<String> getNextOperationId(InputStream requestInputStream) throws IOException {
@@ -48,7 +42,7 @@ public class StreamReaderV3 {
if (c == 32) {
break;
}
- idBuf.append((char) c); //it's ASCII
+ idBuf.append((char) c); // it's ASCII
}
if (idBuf.length() == 0) {
return Optional.empty();
@@ -63,7 +57,7 @@ public class StreamReaderV3 {
if (c == 10) {
break;
}
- lenBuf.append((char) c); //it's ASCII
+ lenBuf.append((char) c); // it's ASCII
}
if (lenBuf.length() == 0) {
throw new IllegalStateException("Operation length missing.");
@@ -71,9 +65,8 @@ public class StreamReaderV3 {
return Integer.valueOf(lenBuf.toString(), 16);
}
- public static InputStream unzipStreamIfNeeded(final HttpRequest httpRequest)
- throws IOException {
- final String contentEncodingHeader = httpRequest.getHeader("content-encoding");
+ public static InputStream unzipStreamIfNeeded(final HttpRequest httpRequest) throws IOException {
+ String contentEncodingHeader = httpRequest.getHeader("content-encoding");
if ("gzip".equals(contentEncodingHeader)) {
return new GZIPInputStream(httpRequest.getData());
} else {
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/package-info.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/package-info.java
index ea01137d9af..3678a0b9fac 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/package-info.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/package-info.java
@@ -1,6 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
- * Server side of programmatic API for feeding into Vespa from outside of the
+ * Server side of programmatic API for feeding into Vespa from outside the
* clusters. Not a public API, not meant for direct use.
*/
@com.yahoo.api.annotations.PackageMarker
diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
index 7f77ce9d0d5..973d0a98b24 100644
--- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
+++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
@@ -113,7 +113,8 @@ public class DocumentV1ApiTest {
.maxThrottled(2)
.resendDelayMillis(1 << 30)
.build();
- final DocumentmanagerConfig docConfig = Deriver.getDocumentManagerConfig("src/test/cfg/music.sd").build();
+ final DocumentmanagerConfig docConfig = Deriver.getDocumentManagerConfig("src/test/cfg/music.sd")
+ .ignoreundefinedfields(true).build();
final DocumentTypeManager manager = new DocumentTypeManager(docConfig);
final Document doc1 = new Document(manager.getDocumentType("music"), "id:space:music::one");
final Document doc2 = new Document(manager.getDocumentType("music"), "id:space:music:n=1:two");
@@ -207,6 +208,12 @@ public class DocumentV1ApiTest {
// GET at root is a visit. Numeric parameters have an upper bound.
access.expect(tokens);
+ Trace visitorTrace = new Trace(9);
+ visitorTrace.trace(7, "Tracy Chapman", false);
+ visitorTrace.getRoot().addChild(new TraceNode().setStrict(false)
+ .addChild("Fast Car")
+ .addChild("Baby Can I Hold You"));
+ access.visitorTrace = visitorTrace;
access.expect(parameters -> {
assertEquals("content", parameters.getRoute().toString());
assertEquals("default", parameters.getBucketSpace());
@@ -215,6 +222,7 @@ public class DocumentV1ApiTest {
assertEquals("[id]", parameters.getFieldSet());
assertEquals("(all the things)", parameters.getDocumentSelection());
assertEquals(6000, parameters.getSessionTimeoutMs());
+ assertEquals(9, parameters.getTraceLevel());
// Put some documents in the response
parameters.getLocalDataHandler().onMessage(new PutDocumentMessage(new DocumentPut(doc1)), tokens.get(0));
parameters.getLocalDataHandler().onMessage(new PutDocumentMessage(new DocumentPut(doc2)), tokens.get(1));
@@ -226,32 +234,43 @@ public class DocumentV1ApiTest {
parameters.getControlHandler().onDone(VisitorControlHandler.CompletionCode.TIMEOUT, "timeout is OK");
});
response = driver.sendRequest("http://localhost/document/v1?cluster=content&bucketSpace=default&wantedDocumentCount=1025&concurrency=123" +
- "&selection=all%20the%20things&fieldSet=[id]&timeout=6");
- assertSameJson("{" +
- " \"pathId\": \"/document/v1\"," +
- " \"documents\": [" +
- " {" +
- " \"id\": \"id:space:music::one\"," +
- " \"fields\": {" +
- " \"artist\": \"Tom Waits\", " +
- " \"embedding\": { \"type\": \"tensor(x[3])\", \"values\": [1.0,2.0,3.0] } " +
- " }" +
- " }," +
- " {" +
- " \"id\": \"id:space:music:n=1:two\"," +
- " \"fields\": {" +
- " \"artist\": \"Asa-Chan & Jun-Ray\", " +
- " \"embedding\": { \"type\": \"tensor(x[3])\", \"values\": [4.0,5.0,6.0] } " +
- " }" +
- " }," +
- " {" +
- " \"id\": \"id:space:music:g=a:three\"," +
- " \"fields\": {}" +
- " }" +
- " ]," +
- " \"documentCount\": 3" +
- "}", response.readAll());
+ "&selection=all%20the%20things&fieldSet=[id]&timeout=6&tracelevel=9");
+ assertSameJson("""
+ {
+ "pathId": "/document/v1",
+ "documents": [
+ {
+ "id": "id:space:music::one",
+ "fields": {
+ "artist": "Tom Waits",\s
+ "embedding": { "type": "tensor(x[3])", "values": [1.0,2.0,3.0] }\s
+ }
+ },
+ {
+ "id": "id:space:music:n=1:two",
+ "fields": {
+ "artist": "Asa-Chan & Jun-Ray",\s
+ "embedding": { "type": "tensor(x[3])", "values": [4.0,5.0,6.0] }\s
+ }
+ },
+ {
+ "id": "id:space:music:g=a:three",
+ "fields": {}
+ }
+ ],
+ "documentCount": 3,
+ "trace": [
+ { "message": "Tracy Chapman" },
+ {
+ "fork": [
+ { "message": "Fast Car" },
+ { "message": "Baby Can I Hold You" }
+ ]
+ }
+ ]
+ }""", response.readAll());
assertEquals(200, response.getStatus());
+ access.visitorTrace = null;
// GET at root is a visit. Streaming mode can be specified with &stream=true
access.expect(tokens);
@@ -330,6 +349,7 @@ public class DocumentV1ApiTest {
" \"message\": \"failure?\"" +
"}", response.readAll());
assertEquals(200, response.getStatus());
+ assertNull(response.getResponse().headers().get("X-Vespa-Ignored-Fields"));
// POST with namespace and document type is a restricted visit with a required destination cluster ("destinationCluster")
access.expect(parameters -> {
@@ -376,13 +396,15 @@ public class DocumentV1ApiTest {
response = driver.sendRequest("http://localhost/document/v1/space/music/docid?selection=true&cluster=content&timeChunk=10", PUT,
"{" +
" \"fields\": {" +
- " \"artist\": { \"assign\": \"Lisa Ekdahl\" }" +
+ " \"artist\": { \"assign\": \"Lisa Ekdahl\" }, " +
+ " \"nonexisting\": { \"assign\": \"Ignored\" }" +
" }" +
"}");
assertSameJson("{" +
" \"pathId\": \"/document/v1/space/music/docid\"" +
"}", response.readAll());
assertEquals(200, response.getStatus());
+ assertEquals("true", response.getResponse().headers().get("X-Vespa-Ignored-Fields").get(0).toString());
// PUT with namespace, document type and group is also a restricted visit which requires a cluster.
access.expect(parameters -> {
@@ -907,6 +929,7 @@ public class DocumentV1ApiTest {
private final AtomicReference<Consumer<VisitorParameters>> expectations = new AtomicReference<>();
private final Set<AckToken> outstanding = new CopyOnWriteArraySet<>();
private final MockAsyncSession session = new MockAsyncSession();
+ private Trace visitorTrace;
MockDocumentAccess(DocumentmanagerConfig config) {
super(new DocumentAccessParams().setDocumentmanagerConfig(config));
@@ -932,7 +955,7 @@ public class DocumentV1ApiTest {
}
@Override public boolean isDone() { return false; }
@Override public ProgressToken getProgress() { return null; }
- @Override public Trace getTrace() { return null; }
+ @Override public Trace getTrace() { return visitorTrace; }
@Override public boolean waitUntilDone(long timeoutMs) { return false; }
@Override public void ack(AckToken token) { assertTrue(outstanding.remove(token)); }
@Override public void abort() { }
diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/vespa/http/server/FeedHandlerV3Test.java b/vespaclient-container-plugin/src/test/java/com/yahoo/vespa/http/server/FeedHandlerV3Test.java
index 5b8b5b1827f..dbbe664c9f8 100644
--- a/vespaclient-container-plugin/src/test/java/com/yahoo/vespa/http/server/FeedHandlerV3Test.java
+++ b/vespaclient-container-plugin/src/test/java/com/yahoo/vespa/http/server/FeedHandlerV3Test.java
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class FeedHandlerV3Test {
+
final CollectingMetric metric = new CollectingMetric();
private final Executor simpleThreadpool = Executors.newCachedThreadPool();
@@ -101,7 +102,6 @@ public class FeedHandlerV3Test {
request.getJDiscRequest().headers().add(Headers.DATA_FORMAT, FeedParams.DataFormat.JSON_UTF8.name());
request.getJDiscRequest().headers().add(Headers.TIMEOUT, "1000000000");
request.getJDiscRequest().headers().add(Headers.CLIENT_ID, "client123");
- request.getJDiscRequest().headers().add(Headers.PRIORITY, "LOWEST");
request.getJDiscRequest().headers().add(Headers.TRACE_LEVEL, "4");
request.getJDiscRequest().headers().add(Headers.DRAIN, "true");
return request;
diff --git a/vespaclient-core/src/main/java/com/yahoo/feedapi/Feeder.java b/vespaclient-core/src/main/java/com/yahoo/feedapi/Feeder.java
index cd888b11d64..d8b69bd4a85 100644
--- a/vespaclient-core/src/main/java/com/yahoo/feedapi/Feeder.java
+++ b/vespaclient-core/src/main/java/com/yahoo/feedapi/Feeder.java
@@ -52,9 +52,8 @@ public abstract class Feeder {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
- message = "(no message) " + sw.toString();
+ message = "(no message) " + sw;
}
-
addError("ERROR: " + message);
}
@@ -84,17 +83,9 @@ public abstract class Feeder {
if (createIfNonExistent && op.getDocumentUpdate() != null) {
op.getDocumentUpdate().setCreateIfNonExistent(true);
}
-
- // Done feeding.
- if (op.getType() == FeedOperation.Type.INVALID) {
- break;
- } else {
- sender.sendOperation(op);
- }
- } catch (XMLStreamException e) {
- addException(e);
- break;
- } catch (NullPointerException e) {
+ if (op.getType() == FeedOperation.Type.INVALID) break; // Done feeding
+ sender.sendOperation(op);
+ } catch (XMLStreamException | NullPointerException e) {
addException(e);
break;
} catch (Exception e) {
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java b/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java
index 42c23c4a961..9a5df96b705 100644
--- a/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java
+++ b/vespaclient-java/src/main/java/com/yahoo/vespavisit/VdsVisit.java
@@ -25,6 +25,7 @@ import org.apache.commons.cli.Options;
import java.io.*;
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Collectors;
@@ -649,7 +650,7 @@ public class VdsVisit {
out.println("Adding the following library specific parameters:");
for (Map.Entry<String, byte[]> entry : params.getLibraryParameters().entrySet()) {
out.println(" " + entry.getKey() + " = " +
- new String(entry.getValue(), Charset.forName("utf-8")));
+ new String(entry.getValue(), StandardCharsets.UTF_8));
}
}
if (params.getPriority() != DocumentProtocol.Priority.NORMAL_3) {
diff --git a/vespajlib/src/main/java/com/yahoo/io/GrowableByteBuffer.java b/vespajlib/src/main/java/com/yahoo/io/GrowableByteBuffer.java
index cf728d69d18..479375d6b74 100644
--- a/vespajlib/src/main/java/com/yahoo/io/GrowableByteBuffer.java
+++ b/vespajlib/src/main/java/com/yahoo/io/GrowableByteBuffer.java
@@ -90,7 +90,7 @@ public class GrowableByteBuffer implements Comparable<GrowableByteBuffer> {
//ByteBuffers and keep track of global position etc., much like
//GrowableBufferOutputStream does it.
- protected void grow(int newSize) {
+ public void grow(int newSize) {
//create new buffer:
ByteBuffer newByteBuf;
if (buffer.isDirect()) {
@@ -104,7 +104,7 @@ public class GrowableByteBuffer implements Comparable<GrowableByteBuffer> {
//copy old contents and set correct position:
int oldPos = buffer.position();
newByteBuf.position(0);
- buffer.position(0);
+ buffer.flip();
newByteBuf.put(buffer);
newByteBuf.position(oldPos);
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java
index 4ec5b196dbc..1009177761b 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/Join.java
@@ -304,7 +304,7 @@ public class Join<NAMETYPE extends Name> extends PrimitiveTensorFunction<NAMETYP
for (Iterator<Tensor.Cell> bIterator = b.cellIterator(); bIterator.hasNext(); ) {
Map.Entry<TensorAddress, Double> bCell = bIterator.next();
TensorAddress combinedAddress = joinAddresses(aCell.getKey(), aToIndexes,
- bCell.getKey(), bToIndexes, joinedType);
+ bCell.getKey(), bToIndexes, joinedType);
if (combinedAddress == null) continue; // not combinable
builder.cell(combinedAddress, combinator.applyAsDouble(aCell.getValue(), bCell.getValue()));
}
@@ -347,7 +347,7 @@ public class Join<NAMETYPE extends Name> extends PrimitiveTensorFunction<NAMETYP
TensorAddress partialCommonAddress = partialCommonAddress(bCell, bIndexesInCommon);
for (Tensor.Cell aCell : aCellsByCommonAddress.getOrDefault(partialCommonAddress, Collections.emptyList())) {
TensorAddress combinedAddress = joinAddresses(aCell.getKey(), aIndexesInJoined,
- bCell.getKey(), bIndexesInJoined, joinedType);
+ bCell.getKey(), bIndexesInJoined, joinedType);
if (combinedAddress == null) continue; // not combinable
double combinedValue = swapTensors ?
combinator.applyAsDouble(bCell.getValue(), aCell.getValue()) :
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/serialization/TypedBinaryFormat.java b/vespajlib/src/main/java/com/yahoo/tensor/serialization/TypedBinaryFormat.java
index eddb90ea84c..28cbf4ef64b 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/serialization/TypedBinaryFormat.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/serialization/TypedBinaryFormat.java
@@ -2,7 +2,6 @@
package com.yahoo.tensor.serialization;
import com.yahoo.io.GrowableByteBuffer;
-import com.yahoo.tensor.IndexedTensor;
import com.yahoo.tensor.MixedTensor;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.TensorType;
@@ -34,9 +33,12 @@ public class TypedBinaryFormat {
public static byte[] encode(Tensor tensor) {
GrowableByteBuffer buffer = new GrowableByteBuffer();
+ return asByteArray(encode(tensor, buffer));
+ }
+ public static GrowableByteBuffer encode(Tensor tensor, GrowableByteBuffer buffer) {
BinaryFormat encoder = getFormatEncoder(buffer, tensor);
encoder.encode(buffer, tensor);
- return asByteArray(buffer);
+ return buffer;
}
/**
@@ -53,8 +55,8 @@ public class TypedBinaryFormat {
}
private static BinaryFormat getFormatEncoder(GrowableByteBuffer buffer, Tensor tensor) {
- boolean hasMappedDimensions = tensor.type().dimensions().stream().anyMatch(d -> d.isMapped());
- boolean hasIndexedDimensions = tensor.type().dimensions().stream().anyMatch(d -> d.isIndexed());
+ boolean hasMappedDimensions = tensor.type().dimensions().stream().anyMatch(TensorType.Dimension::isMapped);
+ boolean hasIndexedDimensions = tensor.type().dimensions().stream().anyMatch(TensorType.Dimension::isIndexed);
boolean isMixed = hasMappedDimensions && hasIndexedDimensions;
// TODO: Encoding as indexed if the implementation is mixed is not yet supported so use mixed format instead
@@ -113,12 +115,11 @@ public class TypedBinaryFormat {
private static void encodeValueType(GrowableByteBuffer buffer, TensorType.Value valueType) {
switch (valueType) {
- case DOUBLE: buffer.putInt1_4Bytes(DOUBLE_VALUE_TYPE); break;
- case FLOAT: buffer.putInt1_4Bytes(FLOAT_VALUE_TYPE); break;
- case BFLOAT16: buffer.putInt1_4Bytes(BFLOAT16_VALUE_TYPE); break;
- case INT8: buffer.putInt1_4Bytes(INT8_VALUE_TYPE); break;
- default:
- throw new IllegalArgumentException("Attempt to encode unknown tensor value type: " + valueType);
+ case DOUBLE -> buffer.putInt1_4Bytes(DOUBLE_VALUE_TYPE);
+ case FLOAT -> buffer.putInt1_4Bytes(FLOAT_VALUE_TYPE);
+ case BFLOAT16 -> buffer.putInt1_4Bytes(BFLOAT16_VALUE_TYPE);
+ case INT8 -> buffer.putInt1_4Bytes(INT8_VALUE_TYPE);
+ default -> throw new IllegalArgumentException("Attempt to encode unknown tensor value type: " + valueType);
}
}
diff --git a/vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java b/vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java
index d3317b5fb26..934a1b17c70 100644
--- a/vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java
+++ b/vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java
@@ -17,7 +17,7 @@ public class UncheckedInterruptedException extends RuntimeException {
this(cause.toString(), cause, restoreInterruptFlags);
}
- public UncheckedInterruptedException(String message, boolean restoreInterruptFlag) { this(message, null, false); }
+ public UncheckedInterruptedException(String message, boolean restoreInterruptFlag) { this(message, null, restoreInterruptFlag); }
public UncheckedInterruptedException(String message, InterruptedException cause) { this(message, cause, false); }
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt
index 7aafb7c364e..6ecac23d5fa 100644
--- a/vespalib/CMakeLists.txt
+++ b/vespalib/CMakeLists.txt
@@ -42,6 +42,8 @@ vespa_define_module(
src/tests/component
src/tests/compress
src/tests/compression
+ src/tests/coro/detached
+ src/tests/coro/lazy
src/tests/cpu_usage
src/tests/crc
src/tests/crypto
@@ -54,6 +56,7 @@ vespa_define_module(
src/tests/data/smart_buffer
src/tests/datastore/array_store
src/tests/datastore/array_store_config
+ src/tests/datastore/buffer_stats
src/tests/datastore/buffer_type
src/tests/datastore/compact_buffer_candidates
src/tests/datastore/datastore
@@ -180,9 +183,9 @@ vespa_define_module(
src/tests/util/bfloat16
src/tests/util/cgroup_resource_limits
src/tests/util/file_area_freelist
+ src/tests/util/generation_hold_list
src/tests/util/generationhandler
src/tests/util/generationhandler_stress
- src/tests/util/generation_holder
src/tests/util/hamming
src/tests/util/md5
src/tests/util/mmap_file_allocator
@@ -201,9 +204,13 @@ vespa_define_module(
src/tests/fastlib/text
LIBS
+ src/vespa/fastlib/io
+ src/vespa/fastlib/text
+ src/vespa/fastlib/text/apps
src/vespa/vespalib
src/vespa/vespalib/btree
src/vespa/vespalib/component
+ src/vespa/vespalib/coro
src/vespa/vespalib/crypto
src/vespa/vespalib/data
src/vespa/vespalib/data/slime
@@ -230,7 +237,4 @@ vespa_define_module(
src/vespa/vespalib/time
src/vespa/vespalib/trace
src/vespa/vespalib/util
- src/vespa/fastlib/io
- src/vespa/fastlib/text
- src/vespa/fastlib/text/apps
)
diff --git a/vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp b/vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp
index c68ff07491e..caed5c3543c 100644
--- a/vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp
+++ b/vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp
@@ -59,15 +59,13 @@ public:
AtomicEntryRef add_relaxed(uint32_t value) { return AtomicEntryRef(add(value)); }
void hold(const AtomicEntryRef& ref) { _store.holdElem(ref.load_relaxed(), 1); }
EntryRef move(EntryRef ref);
- void transfer_hold_lists(generation_t gen) { _store.transferHoldLists(gen); }
- void trim_hold_lists(generation_t gen) { _store.trimHoldLists(gen); }
+ void assign_generation(generation_t current_gen) { _store.assign_generation(current_gen); }
+ void reclaim_memory(generation_t gen) { _store.reclaim_memory(gen); }
uint32_t get(EntryRef ref) const { return _store.getEntry(ref); }
uint32_t get_acquire(const AtomicEntryRef& ref) const { return get(ref.load_acquire()); }
uint32_t get_relaxed(const AtomicEntryRef& ref) const { return get(ref.load_relaxed()); }
std::unique_ptr<vespalib::datastore::CompactingBuffers> start_compact();
static constexpr bool is_indirect = true;
- static uint32_t get_offset_bits() { return StoreRefType::offset_bits; }
- static uint32_t get_num_buffers() { return StoreRefType::numBuffers(); }
bool has_held_buffers() const noexcept { return _store.has_held_buffers(); }
};
@@ -82,7 +80,7 @@ std::unique_ptr<vespalib::datastore::CompactingBuffers>
RealIntStore::start_compact()
{
// Use a compaction strategy that will compact all active buffers
- CompactionStrategy compaction_strategy(0.0, 0.0, get_num_buffers(), 1.0);
+ auto compaction_strategy = CompactionStrategy::make_compact_all_active_buffers_strategy();
CompactionSpec compaction_spec(true, false);
return _store.start_compact_worst_buffers(compaction_spec, compaction_strategy);
}
@@ -120,8 +118,8 @@ public:
static uint32_t add(uint32_t value) noexcept { return value; }
static uint32_t add_relaxed(uint32_t value) noexcept { return value; }
static void hold(uint32_t) noexcept { }
- static void transfer_hold_lists(generation_t) noexcept { }
- static void trim_hold_lists(generation_t) noexcept { }
+ static void assign_generation(generation_t) noexcept { }
+ static void reclaim_memory(generation_t) noexcept { }
static uint32_t get(uint32_t value) noexcept { return value; }
static uint32_t get_acquire(uint32_t value) noexcept { return value; }
static uint32_t get_relaxed(uint32_t value) noexcept { return value; }
@@ -276,15 +274,15 @@ Fixture<Params>::commit()
auto &allocator = _tree.getAllocator();
allocator.freeze();
auto current_gen = _generationHandler.getCurrentGeneration();
- allocator.transferHoldLists(current_gen);
- _keys.transfer_hold_lists(current_gen);
- _values.transfer_hold_lists(current_gen);
- allocator.transferHoldLists(_generationHandler.getCurrentGeneration());
+ allocator.assign_generation(current_gen);
+ _keys.assign_generation(current_gen);
+ _values.assign_generation(current_gen);
+ allocator.assign_generation(_generationHandler.getCurrentGeneration());
_generationHandler.incGeneration();
- auto first_used_gen = _generationHandler.getFirstUsedGeneration();
- allocator.trimHoldLists(first_used_gen);
- _keys.trim_hold_lists(first_used_gen);
- _values.trim_hold_lists(first_used_gen);
+ auto oldest_used_gen = _generationHandler.get_oldest_used_generation();
+ allocator.reclaim_memory(oldest_used_gen);
+ _keys.reclaim_memory(oldest_used_gen);
+ _values.reclaim_memory(oldest_used_gen);
}
template <typename Params>
@@ -329,7 +327,7 @@ void
Fixture<Params>::compact_tree()
{
// Use a compaction strategy that will compact all active buffers
- CompactionStrategy compaction_strategy(0.0, 0.0, RefType::numBuffers(), 1.0);
+ auto compaction_strategy = CompactionStrategy::make_compact_all_active_buffers_strategy();
_tree.compact_worst(compaction_strategy);
_writeItr = _tree.begin();
_compact_tree.track_compacted();
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 4da34c64ed9..0370b1ce2eb 100644
--- a/vespalib/src/tests/btree/btree_store/btree_store_test.cpp
+++ b/vespalib/src/tests/btree/btree_store/btree_store_test.cpp
@@ -31,9 +31,9 @@ protected:
void inc_generation()
{
_store.freeze();
- _store.transferHoldLists(_gen_handler.getCurrentGeneration());
+ _store.assign_generation(_gen_handler.getCurrentGeneration());
_gen_handler.incGeneration();
- _store.trimHoldLists(_gen_handler.getFirstUsedGeneration());
+ _store.reclaim_memory(_gen_handler.get_oldest_used_generation());
}
EntryRef add_sequence(int start_key, int end_key)
diff --git a/vespalib/src/tests/btree/btree_test.cpp b/vespalib/src/tests/btree/btree_test.cpp
index 92f55681c0f..f2896cb783c 100644
--- a/vespalib/src/tests/btree/btree_test.cpp
+++ b/vespalib/src/tests/btree/btree_test.cpp
@@ -163,9 +163,9 @@ void
cleanup(GenerationHandler & g, ManagerType & m)
{
m.freeze();
- m.transferHoldLists(g.getCurrentGeneration());
+ m.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- m.trimHoldLists(g.getFirstUsedGeneration());
+ m.reclaim_memory(g.get_oldest_used_generation());
}
template <typename ManagerType, typename NodeType>
@@ -862,19 +862,21 @@ TEST_F(BTreeTest, require_that_we_can_insert_and_remove_from_tree)
}
// compact full tree by calling incremental compaction methods in a loop
{
+ // Use a compaction strategy that will compact all active buffers
+ auto compaction_strategy = CompactionStrategy::make_compact_all_active_buffers_strategy();
MyTree::NodeAllocatorType &manager = tree.getAllocator();
- std::vector<uint32_t> toHold = manager.startCompact();
+ auto compacting_buffers = manager.start_compact_worst(compaction_strategy);
MyTree::Iterator itr = tree.begin();
tree.setRoot(itr.moveFirstLeafNode(tree.getRoot()));
while (itr.valid()) {
// LOG(info, "Leaf moved to %d", UNWRAP(itr.getKey()));
itr.moveNextLeafNode();
}
- manager.finishCompact(toHold);
+ compacting_buffers->finish();
manager.freeze();
- manager.transferHoldLists(g.getCurrentGeneration());
+ manager.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- manager.trimHoldLists(g.getFirstUsedGeneration());
+ manager.reclaim_memory(g.get_oldest_used_generation());
}
// remove entries
for (size_t i = 0; i < numEntries; ++i) {
@@ -1104,9 +1106,9 @@ TEST_F(BTreeTest, require_that_memory_usage_is_calculated)
EXPECT_TRUE(assertMemoryUsage(mu, tm.getMemoryUsage()));
// trim hold lists
- tm.transferHoldLists(gh.getCurrentGeneration());
+ tm.assign_generation(gh.getCurrentGeneration());
gh.incGeneration();
- tm.trimHoldLists(gh.getFirstUsedGeneration());
+ tm.reclaim_memory(gh.get_oldest_used_generation());
mu = vespalib::MemoryUsage();
mu.incAllocatedBytes(adjustAllocatedBytes(initialInternalNodes, sizeof(INode)));
mu.incAllocatedBytes(adjustAllocatedBytes(initialLeafNodes, sizeof(LNode)));
@@ -1280,9 +1282,9 @@ TEST_F(BTreeTest, require_that_small_nodes_works)
s.clear(root);
s.clearBuilder();
s.freeze();
- s.transferHoldLists(g.getCurrentGeneration());
+ s.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- s.trimHoldLists(g.getFirstUsedGeneration());
+ s.reclaim_memory(g.get_oldest_used_generation());
}
namespace {
@@ -1414,9 +1416,9 @@ TEST_F(BTreeTest, require_that_apply_works)
s.clear(root);
s.clearBuilder();
s.freeze();
- s.transferHoldLists(g.getCurrentGeneration());
+ s.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- s.trimHoldLists(g.getFirstUsedGeneration());
+ s.reclaim_memory(g.get_oldest_used_generation());
}
class MyTreeTestIterator : public MyTree::Iterator
@@ -1551,9 +1553,9 @@ inc_generation(GenerationHandler &g, Tree &t)
{
auto &s = t.getAllocator();
s.freeze();
- s.transferHoldLists(g.getCurrentGeneration());
+ s.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- s.trimHoldLists(g.getFirstUsedGeneration());
+ s.reclaim_memory(g.get_oldest_used_generation());
}
template <typename Tree>
diff --git a/vespalib/src/tests/btree/btreeaggregation_test.cpp b/vespalib/src/tests/btree/btreeaggregation_test.cpp
index f4300499fcd..fb394df9861 100644
--- a/vespalib/src/tests/btree/btreeaggregation_test.cpp
+++ b/vespalib/src/tests/btree/btreeaggregation_test.cpp
@@ -15,6 +15,7 @@
#include <vespa/vespalib/btree/btreestore.hpp>
#include <vespa/vespalib/btree/btreeaggregator.hpp>
#include <vespa/vespalib/datastore/buffer_type.hpp>
+#include <vespa/vespalib/datastore/compaction_strategy.h>
#include <vespa/vespalib/test/btree/btree_printer.h>
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/util/rand48.h>
@@ -28,6 +29,7 @@
LOG_SETUP("btreeaggregation_test");
using vespalib::GenerationHandler;
+using vespalib::datastore::CompactionStrategy;
using vespalib::datastore::EntryRef;
namespace vespalib::btree {
@@ -270,9 +272,9 @@ void
freezeTree(GenerationHandler &g, ManagerType &m)
{
m.freeze();
- m.transferHoldLists(g.getCurrentGeneration());
+ m.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- m.trimHoldLists(g.getFirstUsedGeneration());
+ m.reclaim_memory(g.get_oldest_used_generation());
}
template <typename ManagerType>
@@ -877,19 +879,21 @@ Test::requireThatWeCanInsertAndRemoveFromTree()
}
// compact full tree by calling incremental compaction methods in a loop
{
+ // Use a compaction strategy that will compact all active buffers
+ auto compaction_strategy = CompactionStrategy::make_compact_all_active_buffers_strategy();
MyTree::NodeAllocatorType &manager = tree.getAllocator();
- std::vector<uint32_t> toHold = manager.startCompact();
+ auto compacting_buffers = manager.start_compact_worst(compaction_strategy);
MyTree::Iterator itr = tree.begin();
tree.setRoot(itr.moveFirstLeafNode(tree.getRoot()));
while (itr.valid()) {
// LOG(info, "Leaf moved to %d", UNWRAP(itr.getKey()));
itr.moveNextLeafNode();
}
- manager.finishCompact(toHold);
+ compacting_buffers->finish();
manager.freeze();
- manager.transferHoldLists(g.getCurrentGeneration());
+ manager.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- manager.trimHoldLists(g.getFirstUsedGeneration());
+ manager.reclaim_memory(g.get_oldest_used_generation());
}
// remove entries
for (size_t i = 0; i < numEntries; ++i) {
@@ -1186,9 +1190,9 @@ Test::requireThatSmallNodesWorks()
s.clear(root);
s.clearBuilder();
s.freeze();
- s.transferHoldLists(g.getCurrentGeneration());
+ s.assign_generation(g.getCurrentGeneration());
g.incGeneration();
- s.trimHoldLists(g.getFirstUsedGeneration());
+ s.reclaim_memory(g.get_oldest_used_generation());
}
void
diff --git a/vespalib/src/tests/btree/frozenbtree_test.cpp b/vespalib/src/tests/btree/frozenbtree_test.cpp
index 01748b9edeb..3471d5dc3df 100644
--- a/vespalib/src/tests/btree/frozenbtree_test.cpp
+++ b/vespalib/src/tests/btree/frozenbtree_test.cpp
@@ -114,7 +114,7 @@ FrozenBTreeTest::freeTree(bool verbose)
static_cast<uint64_t>(_intTree->getUsedMemory()),
static_cast<uint64_t>(_intTree->getHeldMemory()));
_intTree->dropFrozen();
- _intTree->removeOldGenerations(_intTree->getGeneration() + 1);
+ _intTree->reclaim_memory(_intTree->getGeneration() + 1);
LOG(info,
"freeTree after unhold: %" PRIu64 " (%" PRIu64 " held)",
static_cast<uint64_t>(_intTree->getUsedMemory()),
@@ -134,9 +134,9 @@ FrozenBTreeTest::freeTree(bool verbose)
(void) verbose;
_tree->clear(*_allocator);
_allocator->freeze();
- _allocator->transferHoldLists(_generationHandler->getCurrentGeneration());
+ _allocator->assign_generation(_generationHandler->getCurrentGeneration());
_generationHandler->incGeneration();
- _allocator->trimHoldLists(_generationHandler->getFirstUsedGeneration());
+ _allocator->reclaim_memory(_generationHandler->get_oldest_used_generation());
delete _tree;
_tree = NULL;
delete _allocator;
@@ -425,7 +425,7 @@ FrozenBTreeTest::Main()
EXPECT_TRUE(_tree->getFrozenView(*_allocator).empty());
_allocator->freeze();
EXPECT_FALSE(_tree->getFrozenView(*_allocator).empty());
- _allocator->transferHoldLists(_generationHandler->getCurrentGeneration());
+ _allocator->assign_generation(_generationHandler->getCurrentGeneration());
lookupFrozenRandomValues(*_tree, *_allocator, _randomValues);
traverseTreeIterator(*_tree,
*_allocator,
diff --git a/vespalib/src/tests/coro/detached/CMakeLists.txt b/vespalib/src/tests/coro/detached/CMakeLists.txt
new file mode 100644
index 00000000000..237b8615fec
--- /dev/null
+++ b/vespalib/src/tests/coro/detached/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_detached_test_app TEST
+ SOURCES
+ detached_test.cpp
+ DEPENDS
+ vespalib
+ GTest::GTest
+)
+vespa_add_test(NAME vespalib_detached_test_app COMMAND vespalib_detached_test_app)
diff --git a/vespalib/src/tests/coro/detached/detached_test.cpp b/vespalib/src/tests/coro/detached/detached_test.cpp
new file mode 100644
index 00000000000..f23d16cc75c
--- /dev/null
+++ b/vespalib/src/tests/coro/detached/detached_test.cpp
@@ -0,0 +1,19 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/coro/detached.h>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using vespalib::coro::Detached;
+
+Detached set_result(int &res, int value) {
+ res = value;
+ co_return;
+}
+
+TEST(DetachedTest, call_detached_coroutine) {
+ int result = 0;
+ set_result(result, 42);
+ EXPECT_EQ(result, 42);
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/vespalib/src/tests/coro/lazy/CMakeLists.txt b/vespalib/src/tests/coro/lazy/CMakeLists.txt
new file mode 100644
index 00000000000..daa11eb3576
--- /dev/null
+++ b/vespalib/src/tests/coro/lazy/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_lazy_test_app TEST
+ SOURCES
+ lazy_test.cpp
+ DEPENDS
+ vespalib
+ GTest::GTest
+)
+vespa_add_test(NAME vespalib_lazy_test_app COMMAND vespalib_lazy_test_app)
diff --git a/vespalib/src/tests/coro/lazy/lazy_test.cpp b/vespalib/src/tests/coro/lazy/lazy_test.cpp
new file mode 100644
index 00000000000..b838152249e
--- /dev/null
+++ b/vespalib/src/tests/coro/lazy/lazy_test.cpp
@@ -0,0 +1,121 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/coro/lazy.h>
+#include <vespa/vespalib/coro/sync_wait.h>
+#include <vespa/vespalib/util/require.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <mutex>
+
+#include <thread>
+
+using vespalib::coro::Lazy;
+using vespalib::coro::sync_wait;
+
+std::mutex thread_lock;
+std::vector<std::thread> threads;
+struct JoinThreads {
+ ~JoinThreads() {
+ for (auto &thread: threads) {
+ thread.join();
+ }
+ threads.clear();
+ }
+};
+
+auto run_in_other_thread() {
+ struct awaiter {
+ bool await_ready() const noexcept { return false; }
+ void await_suspend(std::coroutine_handle<> handle) const {
+ auto guard = std::lock_guard(thread_lock);
+ threads.push_back(std::thread(handle));
+ }
+ void await_resume() const noexcept {}
+ };
+ return awaiter();
+}
+
+Lazy<int> make_lazy(int value) {
+ co_return value;
+}
+
+Lazy<int> async_add_values(int a, int b) {
+ auto lazy_a = make_lazy(a);
+ auto lazy_b = make_lazy(b);
+ co_return (co_await lazy_a + co_await lazy_b);
+}
+
+Lazy<int> async_sum(Lazy<int> a, Lazy<int> b) {
+ co_return (co_await a + co_await b);
+}
+
+Lazy<std::unique_ptr<int>> move_only_int() {
+ co_return std::make_unique<int>(123);
+}
+
+Lazy<int> extract_rvalue() {
+ auto res = co_await move_only_int();
+ co_return *res;
+}
+
+Lazy<int> will_throw() {
+ REQUIRE_FAILED("failed on purpose");
+ co_return 123;
+}
+
+template<typename T>
+Lazy<T> forward_value(Lazy<T> value) {
+ co_return co_await std::move(value);
+}
+
+template <typename T>
+Lazy<T> switch_thread(Lazy<T> value) {
+ std::cerr << "switching from thread " << std::this_thread::get_id() << std::endl;
+ co_await run_in_other_thread();
+ std::cerr << "........... to thread " << std::this_thread::get_id() << std::endl;
+ co_return co_await value;
+}
+
+TEST(LazyTest, simple_lazy_value) {
+ auto lazy = make_lazy(42);
+ auto result = sync_wait(lazy);
+ EXPECT_EQ(result, 42);
+}
+
+TEST(LazyTest, async_sum_of_async_values) {
+ auto lazy = async_add_values(10, 20);
+ auto result = sync_wait(lazy);
+ EXPECT_EQ(result, 30);
+}
+
+TEST(LazyTest, async_sum_of_external_async_values) {
+ auto a = make_lazy(100);
+ auto b = make_lazy(200);
+ auto lazy = async_sum(std::move(a), std::move(b));
+ auto result = sync_wait(lazy);
+ EXPECT_EQ(result, 300);
+}
+
+TEST(LazyTest, extract_rvalue_from_lazy_in_coroutine) {
+ auto lazy = extract_rvalue();
+ auto result = sync_wait(lazy);
+ EXPECT_EQ(result, 123);
+}
+
+TEST(LazyTest, extract_rvalue_from_lazy_in_sync_wait) {
+ auto result = sync_wait(move_only_int());
+ EXPECT_EQ(*result, 123);
+}
+
+TEST(LazyTest, calculate_result_in_another_thread) {
+ JoinThreads thread_guard;
+ auto result = sync_wait(switch_thread(make_lazy(7)));
+ EXPECT_EQ(result, 7);
+}
+
+TEST(LazyTest, exceptions_are_propagated) {
+ JoinThreads thread_guard;
+ auto lazy = switch_thread(forward_value(will_throw()));
+ EXPECT_THROW(sync_wait(lazy), vespalib::RequireFailedException);
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/vespalib/src/tests/datastore/array_store/array_store_test.cpp b/vespalib/src/tests/datastore/array_store/array_store_test.cpp
index 1e8632aee95..e9ea1a67156 100644
--- a/vespalib/src/tests/datastore/array_store/array_store_test.cpp
+++ b/vespalib/src/tests/datastore/array_store/array_store_test.cpp
@@ -20,7 +20,7 @@ using vespalib::alloc::MemoryAllocator;
using vespalib::alloc::test::MemoryAllocatorObserver;
using AllocStats = MemoryAllocatorObserver::Stats;
-using BufferStats = vespalib::datastore::test::BufferStats;
+using TestBufferStats = vespalib::datastore::test::BufferStats;
using MemStats = vespalib::datastore::test::MemStats;
namespace {
@@ -98,16 +98,16 @@ struct ArrayStoreTest : public TestT
}
void assertBufferState(EntryRef ref, const MemStats& expStats) const {
EXPECT_EQ(expStats._used, store.bufferState(ref).size());
- EXPECT_EQ(expStats._hold, store.bufferState(ref).getHoldElems());
- EXPECT_EQ(expStats._dead, store.bufferState(ref).getDeadElems());
+ EXPECT_EQ(expStats._hold, store.bufferState(ref).stats().hold_elems());
+ EXPECT_EQ(expStats._dead, store.bufferState(ref).stats().dead_elems());
}
- void assert_buffer_stats(EntryRef ref, const BufferStats& exp_stats) const {
+ void assert_buffer_stats(EntryRef ref, const TestBufferStats& exp_stats) const {
auto& state = store.bufferState(ref);
EXPECT_EQ(exp_stats._used, state.size());
- EXPECT_EQ(exp_stats._hold, state.getHoldElems());
- EXPECT_EQ(exp_stats._dead, state.getDeadElems());
- EXPECT_EQ(exp_stats._extra_used, state.getExtraUsedBytes());
- EXPECT_EQ(exp_stats._extra_hold, state.getExtraHoldBytes());
+ EXPECT_EQ(exp_stats._hold, state.stats().hold_elems());
+ EXPECT_EQ(exp_stats._dead, state.stats().dead_elems());
+ EXPECT_EQ(exp_stats._extra_used, state.stats().extra_used_bytes());
+ EXPECT_EQ(exp_stats._extra_hold, state.stats().extra_hold_bytes());
}
void assertMemoryUsage(const MemStats expStats) const {
MemoryUsage act = store.getMemoryUsage();
@@ -123,7 +123,7 @@ struct ArrayStoreTest : public TestT
void assert_ref_reused(const EntryVector& first, const EntryVector& second, bool should_reuse) {
EntryRef ref1 = add(first);
remove(ref1);
- trimHoldLists();
+ reclaim_memory();
EntryRef ref2 = add(second);
EXPECT_EQ(should_reuse, (ref2 == ref1));
assertGet(ref2, second);
@@ -136,9 +136,9 @@ struct ArrayStoreTest : public TestT
}
return EntryRef();
}
- void trimHoldLists() {
- store.transferHoldLists(generation++);
- store.trimHoldLists(generation);
+ void reclaim_memory() {
+ store.assign_generation(generation++);
+ store.reclaim_memory(generation);
}
void compactWorst(bool compactMemory, bool compactAddressSpace) {
CompactionSpec compaction_spec(compactMemory, compactAddressSpace);
@@ -205,10 +205,10 @@ VESPA_GTEST_INSTANTIATE_TEST_SUITE_P(NumberStoreFreeListsDisabledMultiTest,
TEST_P(NumberStoreTest, control_static_sizes) {
#ifdef _LIBCPP_VERSION
- EXPECT_EQ(464u, sizeof(store));
+ EXPECT_EQ(472u, sizeof(store));
EXPECT_EQ(304u, sizeof(NumberStoreTest::ArrayStoreType::DataStoreType));
#else
- EXPECT_EQ(496u, sizeof(store));
+ EXPECT_EQ(504u, sizeof(store));
EXPECT_EQ(336u, sizeof(NumberStoreTest::ArrayStoreType::DataStoreType));
#endif
EXPECT_EQ(112u, sizeof(NumberStoreTest::ArrayStoreType::SmallBufferType));
@@ -280,13 +280,13 @@ TEST_P(NumberStoreFreeListsDisabledTest, large_arrays_are_NOT_allocated_from_fre
TEST_P(NumberStoreTest, track_size_of_large_array_allocations_with_free_lists_enabled) {
EntryRef ref = add({1,2,3,4});
- assert_buffer_stats(ref, BufferStats().used(2).hold(0).dead(1).extra_used(16));
+ assert_buffer_stats(ref, TestBufferStats().used(2).hold(0).dead(1).extra_used(16));
remove({1,2,3,4});
- assert_buffer_stats(ref, BufferStats().used(2).hold(1).dead(1).extra_hold(16).extra_used(16));
- trimHoldLists();
- assert_buffer_stats(ref, BufferStats().used(2).hold(0).dead(2).extra_used(0));
+ assert_buffer_stats(ref, TestBufferStats().used(2).hold(1).dead(1).extra_hold(16).extra_used(16));
+ reclaim_memory();
+ assert_buffer_stats(ref, TestBufferStats().used(2).hold(0).dead(2).extra_used(0));
add({5,6,7,8,9});
- assert_buffer_stats(ref, BufferStats().used(2).hold(0).dead(1).extra_used(20));
+ assert_buffer_stats(ref, TestBufferStats().used(2).hold(0).dead(1).extra_used(20));
}
TEST_F(SmallOffsetNumberStoreTest, new_underlying_buffer_is_allocated_when_current_is_full)
@@ -316,7 +316,7 @@ test_compaction(NumberStoreBasicTest &f)
EntryRef size2Ref = f.add({2,2});
EntryRef size3Ref = f.add({3,3,3});
f.remove(f.add({5,5}));
- f.trimHoldLists();
+ f.reclaim_memory();
f.assertBufferState(size1Ref, MemStats().used(1).dead(0));
f.assertBufferState(size2Ref, MemStats().used(4).dead(2));
f.assertBufferState(size3Ref, MemStats().used(2).dead(1)); // Note: First element is reserved
@@ -335,7 +335,7 @@ test_compaction(NumberStoreBasicTest &f)
EXPECT_NE(size2BufferId, f.getBufferId(f.getEntryRef({2,2})));
f.assertGet(size2Ref, {2,2}); // Old ref should still point to data.
EXPECT_TRUE(f.store.bufferState(size2Ref).isOnHold());
- f.trimHoldLists();
+ f.reclaim_memory();
EXPECT_TRUE(f.store.bufferState(size2Ref).isFree());
}
@@ -360,7 +360,7 @@ void testCompaction(NumberStoreTest &f, bool compactMemory, bool compactAddressS
f.remove(f.add({5,5,5}));
f.remove(f.add({6}));
f.remove(f.add({7}));
- f.trimHoldLists();
+ f.reclaim_memory();
f.assertBufferState(size1Ref, MemStats().used(3).dead(2));
f.assertBufferState(size2Ref, MemStats().used(2).dead(0));
f.assertBufferState(size3Ref, MemStats().used(6).dead(3));
@@ -397,7 +397,7 @@ void testCompaction(NumberStoreTest &f, bool compactMemory, bool compactAddressS
EXPECT_FALSE(f.store.bufferState(size1Ref).isOnHold());
}
EXPECT_FALSE(f.store.bufferState(size2Ref).isOnHold());
- f.trimHoldLists();
+ f.reclaim_memory();
if (compactMemory) {
EXPECT_TRUE(f.store.bufferState(size3Ref).isFree());
} else {
@@ -436,7 +436,7 @@ TEST_P(NumberStoreTest, used_onHold_and_dead_memory_usage_is_tracked_for_small_a
assertMemoryUsage(exp.used(entrySize() * 3));
remove({1,2,3});
assertMemoryUsage(exp.hold(entrySize() * 3));
- trimHoldLists();
+ reclaim_memory();
assertMemoryUsage(exp.holdToDead(entrySize() * 3));
}
@@ -447,7 +447,7 @@ TEST_P(NumberStoreTest, used_onHold_and_dead_memory_usage_is_tracked_for_large_a
assertMemoryUsage(exp.used(largeArraySize() + entrySize() * 4));
remove({1,2,3,4});
assertMemoryUsage(exp.hold(largeArraySize() + entrySize() * 4));
- trimHoldLists();
+ reclaim_memory();
assertMemoryUsage(exp.decUsed(entrySize() * 4).decHold(largeArraySize() + entrySize() * 4).
dead(largeArraySize()));
}
diff --git a/vespalib/src/tests/datastore/buffer_stats/CMakeLists.txt b/vespalib/src/tests/datastore/buffer_stats/CMakeLists.txt
new file mode 100644
index 00000000000..2463f584133
--- /dev/null
+++ b/vespalib/src/tests/datastore/buffer_stats/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_datastore_buffer_stats_test_app TEST
+ SOURCES
+ buffer_stats_test.cpp
+ DEPENDS
+ vespalib
+ GTest::GTest
+)
+vespa_add_test(NAME vespalib_datastore_buffer_stats_test_app COMMAND vespalib_datastore_buffer_stats_test_app)
diff --git a/vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp b/vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp
new file mode 100644
index 00000000000..09b2590a5f3
--- /dev/null
+++ b/vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp
@@ -0,0 +1,34 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/datastore/buffer_stats.h>
+#include <vespa/vespalib/datastore/memory_stats.h>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using namespace vespalib::datastore;
+
+TEST(BufferStatsTest, buffer_stats_to_memory_stats)
+{
+ InternalBufferStats buf;
+ buf.set_alloc_elems(17);
+ buf.pushed_back(7);
+ buf.set_dead_elems(5);
+ buf.set_hold_elems(3);
+ buf.inc_extra_used_bytes(13);
+ buf.inc_extra_hold_bytes(11);
+
+ MemoryStats mem;
+ constexpr size_t es = 8;
+ buf.add_to_mem_stats(es, mem);
+
+ EXPECT_EQ(17, mem._allocElems);
+ EXPECT_EQ(7, mem._usedElems);
+ EXPECT_EQ(5, mem._deadElems);
+ EXPECT_EQ(3, mem._holdElems);
+ EXPECT_EQ(17 * es + 13, mem._allocBytes);
+ EXPECT_EQ(7 * es + 13, mem._usedBytes);
+ EXPECT_EQ(5 * es, mem._deadBytes);
+ EXPECT_EQ(3 * es + 11, mem._holdBytes);
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
+
diff --git a/vespalib/src/tests/datastore/datastore/datastore_test.cpp b/vespalib/src/tests/datastore/datastore/datastore_test.cpp
index 9522aa1e0dc..645871d3ef6 100644
--- a/vespalib/src/tests/datastore/datastore/datastore_test.cpp
+++ b/vespalib/src/tests/datastore/datastore/datastore_test.cpp
@@ -17,7 +17,6 @@ using vespalib::alloc::MemoryAllocator;
class MyStore : public DataStore<int, EntryRefT<3, 2> > {
private:
using ParentType = DataStore<int, EntryRefT<3, 2> >;
- using ParentType::_primary_buffer_ids;
public:
MyStore() {}
explicit MyStore(std::unique_ptr<BufferType<int>> type)
@@ -29,14 +28,11 @@ public:
void holdElem(EntryRef ref, uint64_t len) {
ParentType::holdElem(ref, len);
}
- void transferHoldLists(generation_t generation) {
- ParentType::transferHoldLists(generation);
+ void assign_generation(generation_t current_gen) {
+ ParentType::assign_generation(current_gen);
}
- void trimElemHoldList(generation_t usedGen) override {
- ParentType::trimElemHoldList(usedGen);
- }
- void incDead(EntryRef ref, uint64_t dead) {
- ParentType::incDead(ref, dead);
+ void reclaim_entry_refs(generation_t oldest_used_gen) override {
+ ParentType::reclaim_entry_refs(oldest_used_gen);
}
void ensureBufferCapacity(size_t sizeNeeded) {
ParentType::ensureBufferCapacity(0, sizeNeeded);
@@ -47,7 +43,7 @@ public:
void switch_primary_buffer() {
ParentType::switch_primary_buffer(0, 0u);
}
- size_t primary_buffer_id() const { return _primary_buffer_ids[0]; }
+ size_t primary_buffer_id() const { return get_primary_buffer_id(0); }
BufferState& get_active_buffer_state() {
return ParentType::getBufferState(primary_buffer_id());
}
@@ -55,7 +51,7 @@ public:
using GrowthStats = std::vector<int>;
-using BufferStats = std::vector<int>;
+using BufferIds = std::vector<int>;
constexpr float ALLOC_GROW_FACTOR = 0.4;
constexpr size_t HUGE_PAGE_ARRAY_SIZE = (MemoryAllocator::HUGEPAGE_SIZE / sizeof(int));
@@ -124,8 +120,8 @@ public:
++i;
}
}
- BufferStats getBuffers(size_t bufs) {
- BufferStats buffers;
+ BufferIds getBuffers(size_t bufs) {
+ BufferIds buffers;
while (buffers.size() < bufs) {
RefType iRef = (_type.getArraySize() == 1) ?
(_store.template allocator<DataType>(_typeId).alloc().ref) :
@@ -143,8 +139,8 @@ public:
using MyRef = MyStore::RefType;
void
-assertMemStats(const DataStoreBase::MemStats &exp,
- const DataStoreBase::MemStats &act)
+assertMemStats(const MemoryStats &exp,
+ const MemoryStats &act)
{
EXPECT_EQ(exp._allocElems, act._allocElems);
EXPECT_EQ(exp._usedElems, act._usedElems);
@@ -265,29 +261,29 @@ TEST(DataStoreTest, require_that_we_can_hold_and_trim_buffers)
s.switch_primary_buffer();
EXPECT_EQ(1u, s.primary_buffer_id());
s.holdBuffer(0); // hold last buffer
- s.transferHoldLists(10);
+ s.assign_generation(10);
EXPECT_EQ(1u, MyRef(s.addEntry(2)).bufferId());
s.switch_primary_buffer();
EXPECT_EQ(2u, s.primary_buffer_id());
s.holdBuffer(1); // hold last buffer
- s.transferHoldLists(20);
+ s.assign_generation(20);
EXPECT_EQ(2u, MyRef(s.addEntry(3)).bufferId());
s.switch_primary_buffer();
EXPECT_EQ(3u, s.primary_buffer_id());
s.holdBuffer(2); // hold last buffer
- s.transferHoldLists(30);
+ s.assign_generation(30);
EXPECT_EQ(3u, MyRef(s.addEntry(4)).bufferId());
s.holdBuffer(3); // hold current buffer
- s.transferHoldLists(40);
+ s.assign_generation(40);
EXPECT_TRUE(s.getBufferState(0).size() != 0);
EXPECT_TRUE(s.getBufferState(1).size() != 0);
EXPECT_TRUE(s.getBufferState(2).size() != 0);
EXPECT_TRUE(s.getBufferState(3).size() != 0);
- s.trimHoldLists(11);
+ s.reclaim_memory(11);
EXPECT_TRUE(s.getBufferState(0).size() == 0);
EXPECT_TRUE(s.getBufferState(1).size() != 0);
EXPECT_TRUE(s.getBufferState(2).size() != 0);
@@ -296,7 +292,7 @@ TEST(DataStoreTest, require_that_we_can_hold_and_trim_buffers)
s.switch_primary_buffer();
EXPECT_EQ(0u, s.primary_buffer_id());
EXPECT_EQ(0u, MyRef(s.addEntry(5)).bufferId());
- s.trimHoldLists(41);
+ s.reclaim_memory(41);
EXPECT_TRUE(s.getBufferState(0).size() != 0);
EXPECT_TRUE(s.getBufferState(1).size() == 0);
EXPECT_TRUE(s.getBufferState(2).size() == 0);
@@ -308,21 +304,21 @@ TEST(DataStoreTest, require_that_we_can_hold_and_trim_elements)
MyStore s;
MyRef r1 = s.addEntry(1);
s.holdElem(r1, 1);
- s.transferHoldLists(10);
+ s.assign_generation(10);
MyRef r2 = s.addEntry(2);
s.holdElem(r2, 1);
- s.transferHoldLists(20);
+ s.assign_generation(20);
MyRef r3 = s.addEntry(3);
s.holdElem(r3, 1);
- s.transferHoldLists(30);
+ s.assign_generation(30);
EXPECT_EQ(1, s.getEntry(r1));
EXPECT_EQ(2, s.getEntry(r2));
EXPECT_EQ(3, s.getEntry(r3));
- s.trimElemHoldList(11);
+ s.reclaim_entry_refs(11);
EXPECT_EQ(0, s.getEntry(r1));
EXPECT_EQ(2, s.getEntry(r2));
EXPECT_EQ(3, s.getEntry(r3));
- s.trimElemHoldList(31);
+ s.reclaim_entry_refs(31);
EXPECT_EQ(0, s.getEntry(r1));
EXPECT_EQ(0, s.getEntry(r2));
EXPECT_EQ(0, s.getEntry(r3));
@@ -362,17 +358,17 @@ TEST(DataStoreTest, require_that_we_can_use_free_lists)
s.enableFreeLists();
auto r1 = s.addEntry(1);
s.holdElem(r1, 1);
- s.transferHoldLists(10);
+ s.assign_generation(10);
auto r2 = s.addEntry(2);
expect_successive_refs(r1, r2);
s.holdElem(r2, 1);
- s.transferHoldLists(20);
- s.trimElemHoldList(11);
+ s.assign_generation(20);
+ s.reclaim_entry_refs(11);
auto r3 = s.addEntry(3); // reuse r1
EXPECT_EQ(r1, r3);
auto r4 = s.addEntry(4);
expect_successive_refs(r2, r4);
- s.trimElemHoldList(21);
+ s.reclaim_entry_refs(21);
auto r5 = s.addEntry(5); // reuse r2
EXPECT_EQ(r2, r5);
auto r6 = s.addEntry(6);
@@ -397,8 +393,8 @@ TEST(DataStoreTest, require_that_we_can_use_free_lists_with_raw_allocator)
expect_successive_handles(h1, h2);
s.holdElem(h1.ref, 3);
s.holdElem(h2.ref, 3);
- s.transferHoldLists(10);
- s.trimElemHoldList(11);
+ s.assign_generation(10);
+ s.reclaim_entry_refs(11);
auto h3 = allocator.alloc(3); // reuse h2.ref from free list
EXPECT_EQ(h2, h3);
@@ -414,7 +410,7 @@ TEST(DataStoreTest, require_that_we_can_use_free_lists_with_raw_allocator)
TEST(DataStoreTest, require_that_memory_stats_are_calculated)
{
MyStore s;
- DataStoreBase::MemStats m;
+ MemoryStats m;
m._allocElems = MyRef::offsetSize();
m._usedElems = 1; // ref = 0 is reserved
m._deadElems = 1; // ref = 0 is reserved
@@ -429,16 +425,11 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
m._usedElems++;
assertMemStats(m, s.getMemStats());
- // inc dead
- s.incDead(r, 1);
- m._deadElems++;
- assertMemStats(m, s.getMemStats());
-
// hold buffer
s.addEntry(20);
s.addEntry(30);
s.holdBuffer(r.bufferId());
- s.transferHoldLists(100);
+ s.assign_generation(100);
m._usedElems += 2;
m._holdElems = m._usedElems;
m._deadElems = 0;
@@ -455,7 +446,7 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
m._freeBuffers--;
// trim hold buffer
- s.trimHoldLists(101);
+ s.reclaim_memory(101);
m._allocElems -= MyRef::offsetSize();
m._usedElems = 1;
m._deadElems = 0;
@@ -466,7 +457,7 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
{ // increase extra used bytes
auto prev_stats = s.getMemStats();
- s.get_active_buffer_state().incExtraUsedBytes(50);
+ s.get_active_buffer_state().stats().inc_extra_used_bytes(50);
auto curr_stats = s.getMemStats();
EXPECT_EQ(prev_stats._allocBytes + 50, curr_stats._allocBytes);
EXPECT_EQ(prev_stats._usedBytes + 50, curr_stats._usedBytes);
@@ -474,7 +465,7 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
{ // increase extra hold bytes
auto prev_stats = s.getMemStats();
- s.get_active_buffer_state().incExtraHoldBytes(30);
+ s.get_active_buffer_state().hold_elems(0, 30);
auto curr_stats = s.getMemStats();
EXPECT_EQ(prev_stats._holdBytes + 30, curr_stats._holdBytes);
}
@@ -487,15 +478,14 @@ TEST(DataStoreTest, require_that_memory_usage_is_calculated)
s.addEntry(20);
s.addEntry(30);
s.addEntry(40);
- s.incDead(r, 1);
s.holdBuffer(r.bufferId());
- s.transferHoldLists(100);
+ s.assign_generation(100);
vespalib::MemoryUsage m = s.getMemoryUsage();
EXPECT_EQ(MyRef::offsetSize() * sizeof(int), m.allocatedBytes());
EXPECT_EQ(5 * sizeof(int), m.usedBytes());
EXPECT_EQ(0 * sizeof(int), m.deadBytes());
EXPECT_EQ(5 * sizeof(int), m.allocatedBytesOnHold());
- s.trimHoldLists(101);
+ s.reclaim_memory(101);
}
TEST(DataStoreTest, require_that_we_can_disable_elemement_hold_list)
@@ -523,8 +513,8 @@ TEST(DataStoreTest, require_that_we_can_disable_elemement_hold_list)
EXPECT_EQ(4 * sizeof(int), m.usedBytes());
EXPECT_EQ(2 * sizeof(int), m.deadBytes());
EXPECT_EQ(1 * sizeof(int), m.allocatedBytesOnHold());
- s.transferHoldLists(100);
- s.trimHoldLists(101);
+ s.assign_generation(100);
+ s.reclaim_memory(101);
}
using IntGrowStore = GrowStore<int, EntryRefT<24>>;
@@ -644,9 +634,9 @@ TEST(DataStoreTest, can_set_memory_allocator)
s.switch_primary_buffer();
EXPECT_EQ(AllocStats(3, 0), stats);
s.holdBuffer(0);
- s.transferHoldLists(10);
+ s.assign_generation(10);
EXPECT_EQ(AllocStats(3, 0), stats);
- s.trimHoldLists(11);
+ s.reclaim_memory(11);
EXPECT_EQ(AllocStats(3, 2), stats);
}
EXPECT_EQ(AllocStats(3, 3), stats);
@@ -655,7 +645,7 @@ TEST(DataStoreTest, can_set_memory_allocator)
namespace {
void
-assertBuffers(BufferStats exp_buffers, size_t num_arrays_for_new_buffer)
+assertBuffers(BufferIds exp_buffers, size_t num_arrays_for_new_buffer)
{
EXPECT_EQ(exp_buffers, IntGrowStore(1, 1, 1024, num_arrays_for_new_buffer).getBuffers(exp_buffers.size()));
}
@@ -680,7 +670,7 @@ TEST(DataStoreTest, control_static_sizes) {
namespace {
-void test_free_element_to_held_buffer(bool direct, bool before_hold_buffer)
+void test_free_element_to_held_buffer(bool before_hold_buffer)
{
MyStore s;
auto ref = s.addEntry(1);
@@ -689,45 +679,27 @@ void test_free_element_to_held_buffer(bool direct, bool before_hold_buffer)
EXPECT_EQ(1u, s.primary_buffer_id());
if (before_hold_buffer) {
- if (direct) {
- s.freeElem(ref, 1);
- } else {
- s.holdElem(ref, 1);
- }
+ s.holdElem(ref, 1);
}
s.holdBuffer(0); // hold last buffer
if (!before_hold_buffer) {
- if (direct) {
- ASSERT_DEATH({ s.freeElem(ref, 1); }, "state.isOnHold\\(\\) && was_held");
- } else {
- ASSERT_DEATH({ s.holdElem(ref, 1); }, "state.isActive\\(\\)");
- }
+ ASSERT_DEATH({ s.holdElem(ref, 1); }, "isActive\\(\\)");
}
- s.transferHoldLists(100);
- s.trimHoldLists(101);
+ s.assign_generation(100);
+ s.reclaim_memory(101);
}
}
-TEST(DataStoreTest, free_to_active_then_held_buffer_is_ok)
-{
- test_free_element_to_held_buffer(true, true);
-}
-
TEST(DataStoreTest, hold_to_active_then_held_buffer_is_ok)
{
- test_free_element_to_held_buffer(false, true);
+ test_free_element_to_held_buffer(true);
}
#ifndef NDEBUG
-TEST(DataStoreDeathTest, free_to_held_buffer_is_not_ok)
-{
- test_free_element_to_held_buffer(true, false);
-}
-
TEST(DataStoreDeathTest, hold_to_held_buffer_is_not_ok)
{
- test_free_element_to_held_buffer(false, false);
+ test_free_element_to_held_buffer(false);
}
#endif
diff --git a/vespalib/src/tests/datastore/fixed_size_hash_map/fixed_size_hash_map_test.cpp b/vespalib/src/tests/datastore/fixed_size_hash_map/fixed_size_hash_map_test.cpp
index 599cb209e6c..4f4c3ac94eb 100644
--- a/vespalib/src/tests/datastore/fixed_size_hash_map/fixed_size_hash_map_test.cpp
+++ b/vespalib/src/tests/datastore/fixed_size_hash_map/fixed_size_hash_map_test.cpp
@@ -88,13 +88,13 @@ DataStoreFixedSizeHashTest::~DataStoreFixedSizeHashTest()
void
DataStoreFixedSizeHashTest::commit()
{
- _store.transferHoldLists(_generation_handler.getCurrentGeneration());
- _hash_map->transfer_hold_lists(_generation_handler.getCurrentGeneration());
- _generation_holder.transferHoldLists(_generation_handler.getCurrentGeneration());
+ _store.assign_generation(_generation_handler.getCurrentGeneration());
+ _hash_map->assign_generation(_generation_handler.getCurrentGeneration());
+ _generation_holder.assign_generation(_generation_handler.getCurrentGeneration());
_generation_handler.incGeneration();
- _store.trimHoldLists(_generation_handler.getFirstUsedGeneration());
- _hash_map->trim_hold_lists(_generation_handler.getFirstUsedGeneration());
- _generation_holder.trimHoldLists(_generation_handler.getFirstUsedGeneration());
+ _store.reclaim_memory(_generation_handler.get_oldest_used_generation());
+ _hash_map->reclaim_memory(_generation_handler.get_oldest_used_generation());
+ _generation_holder.reclaim(_generation_handler.get_oldest_used_generation());
}
size_t
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 13f9ae251b6..4c3fe1756c5 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
@@ -73,8 +73,8 @@ public:
}
~MyCompactable() override = default;
- EntryRef move(EntryRef ref) override {
- auto new_ref = _allocator.move(ref);
+ EntryRef move_on_compact(EntryRef ref) override {
+ auto new_ref = _allocator.move_on_compact(ref);
_allocator.hold(ref);
_new_refs.emplace_back(new_ref);
return new_ref;
@@ -168,11 +168,11 @@ DataStoreShardedHashTest::~DataStoreShardedHashTest()
void
DataStoreShardedHashTest::commit()
{
- _store.transferHoldLists(_generationHandler.getCurrentGeneration());
- _hash_map.transfer_hold_lists(_generationHandler.getCurrentGeneration());
+ _store.assign_generation(_generationHandler.getCurrentGeneration());
+ _hash_map.assign_generation(_generationHandler.getCurrentGeneration());
_generationHandler.incGeneration();
- _store.trimHoldLists(_generationHandler.getFirstUsedGeneration());
- _hash_map.trim_hold_lists(_generationHandler.getFirstUsedGeneration());
+ _store.reclaim_memory(_generationHandler.get_oldest_used_generation());
+ _hash_map.reclaim_memory(_generationHandler.get_oldest_used_generation());
}
void
@@ -395,7 +395,7 @@ TEST_F(DataStoreShardedHashTest, foreach_key_works)
}
}
-TEST_F(DataStoreShardedHashTest, move_keys_works)
+TEST_F(DataStoreShardedHashTest, move_keys_on_compact_works)
{
populate_sample_data(small_population);
std::vector<EntryRef> refs;
@@ -403,7 +403,7 @@ TEST_F(DataStoreShardedHashTest, move_keys_works)
std::vector<EntryRef> new_refs;
MyCompactable my_compactable(_allocator, new_refs);
auto filter = make_entry_ref_filter<RefT>(false);
- _hash_map.move_keys(my_compactable, filter);
+ _hash_map.move_keys_on_compact(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(small_population, refs.size());
diff --git a/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp b/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp
index 92bd5502406..48a0ecafbc6 100644
--- a/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp
+++ b/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp
@@ -21,10 +21,10 @@ enum class DictionaryType { BTREE, HASH, BTREE_AND_HASH };
using namespace vespalib::datastore;
using vespalib::ArrayRef;
using generation_t = vespalib::GenerationHandler::generation_t;
-using vespalib::datastore::test::BufferStats;
using vespalib::alloc::MemoryAllocator;
using vespalib::alloc::test::MemoryAllocatorObserver;
using AllocStats = MemoryAllocatorObserver::Stats;
+using TestBufferStats = vespalib::datastore::test::BufferStats;
template <typename UniqueStoreT>
struct TestBaseValues {
@@ -94,10 +94,10 @@ struct TestBase : public ::testing::Test {
uint32_t getBufferId(EntryRef ref) const {
return EntryRefType(ref).bufferId();
}
- void assertBufferState(EntryRef ref, const BufferStats expStats) const {
+ void assertBufferState(EntryRef ref, const TestBufferStats expStats) const {
EXPECT_EQ(expStats._used, store.bufferState(ref).size());
- EXPECT_EQ(expStats._hold, store.bufferState(ref).getHoldElems());
- EXPECT_EQ(expStats._dead, store.bufferState(ref).getDeadElems());
+ EXPECT_EQ(expStats._hold, store.bufferState(ref).stats().hold_elems());
+ EXPECT_EQ(expStats._dead, store.bufferState(ref).stats().dead_elems());
}
void assertStoreContent() const {
for (const auto &elem : refStore) {
@@ -112,15 +112,15 @@ struct TestBase : public ::testing::Test {
}
return EntryRef();
}
- void trimHoldLists() {
+ void reclaim_memory() {
store.freeze();
- store.transferHoldLists(generation++);
- store.trimHoldLists(generation);
+ store.assign_generation(generation++);
+ store.reclaim_memory(generation);
}
void compactWorst() {
CompactionSpec compaction_spec(true, true);
// Use a compaction strategy that will compact all active buffers
- CompactionStrategy compaction_strategy(0.0, 0.0, EntryRefType::numBuffers(), 1.0);
+ auto compaction_strategy = CompactionStrategy::make_compact_all_active_buffers_strategy();
auto remapper = store.compact_worst(compaction_spec, compaction_strategy);
std::vector<AtomicEntryRef> refs;
for (const auto &elem : refStore) {
@@ -320,9 +320,9 @@ TYPED_TEST(TestBase, elements_are_put_on_hold_when_value_is_removed)
EntryRef ref = this->add(this->values()[0]);
size_t reserved = this->get_reserved(ref);
size_t array_size = this->get_array_size(ref);
- this->assertBufferState(ref, BufferStats().used(array_size + reserved).hold(0).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(0).dead(reserved));
this->store.remove(ref);
- this->assertBufferState(ref, BufferStats().used(array_size + reserved).hold(array_size).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(array_size).dead(reserved));
}
TYPED_TEST(TestBase, elements_are_reference_counted)
@@ -333,11 +333,11 @@ TYPED_TEST(TestBase, elements_are_reference_counted)
// Note: The first buffer have the first element reserved -> we expect 2 elements used here.
size_t reserved = this->get_reserved(ref);
size_t array_size = this->get_array_size(ref);
- this->assertBufferState(ref, BufferStats().used(array_size + reserved).hold(0).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(0).dead(reserved));
this->store.remove(ref);
- this->assertBufferState(ref, BufferStats().used(array_size + reserved).hold(0).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(0).dead(reserved));
this->store.remove(ref);
- this->assertBufferState(ref, BufferStats().used(array_size + reserved).hold(array_size).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(array_size).dead(reserved));
}
TEST_F(SmallOffsetNumberTest, new_underlying_buffer_is_allocated_when_current_is_full)
@@ -364,10 +364,10 @@ TYPED_TEST(TestBase, store_can_be_compacted)
EntryRef val0Ref = this->add(this->values()[0]);
EntryRef val1Ref = this->add(this->values()[1]);
this->remove(this->add(this->values()[2]));
- this->trimHoldLists();
+ this->reclaim_memory();
size_t reserved = this->get_reserved(val0Ref);
size_t array_size = this->get_array_size(val0Ref);
- this->assertBufferState(val0Ref, BufferStats().used(reserved + 3 * array_size).dead(reserved + array_size));
+ this->assertBufferState(val0Ref, TestBufferStats().used(reserved + 3 * array_size).dead(reserved + array_size));
uint32_t val1BufferId = this->getBufferId(val0Ref);
EXPECT_EQ(2u, this->refStore.size());
@@ -381,7 +381,7 @@ TYPED_TEST(TestBase, store_can_be_compacted)
this->assertGet(val0Ref, this->values()[0]);
this->assertGet(val1Ref, this->values()[1]);
EXPECT_TRUE(this->store.bufferState(val0Ref).isOnHold());
- this->trimHoldLists();
+ this->reclaim_memory();
EXPECT_TRUE(this->store.bufferState(val0Ref).isFree());
this->assertStoreContent();
}
@@ -396,7 +396,7 @@ TYPED_TEST(TestBase, store_can_be_instantiated_with_builder)
EntryRef val1Ref = builder.mapEnumValueToEntryRef(2);
size_t reserved = this->get_reserved(val0Ref);
size_t array_size = this->get_array_size(val0Ref);
- this->assertBufferState(val0Ref, BufferStats().used(2 * array_size + reserved).dead(reserved)); // Note: First element is reserved
+ this->assertBufferState(val0Ref, TestBufferStats().used(2 * array_size + reserved).dead(reserved)); // Note: First element is reserved
EXPECT_TRUE(val0Ref.valid());
EXPECT_TRUE(val1Ref.valid());
EXPECT_NE(val0Ref.ref(), val1Ref.ref());
@@ -415,7 +415,7 @@ TYPED_TEST(TestBase, store_can_be_enumerated)
EntryRef val0Ref = this->add(this->values()[0]);
EntryRef val1Ref = this->add(this->values()[1]);
this->remove(this->add(this->values()[2]));
- this->trimHoldLists();
+ this->reclaim_memory();
auto enumerator = this->getEnumerator(true);
std::vector<uint32_t> refs;
@@ -460,7 +460,7 @@ TEST_F(DoubleTest, nan_is_handled)
for (auto &value : myvalues) {
refs.emplace_back(add(value));
}
- trimHoldLists();
+ reclaim_memory();
EXPECT_TRUE(std::isnan(store.get(refs[1])));
EXPECT_TRUE(std::signbit(store.get(refs[1])));
EXPECT_TRUE(std::isinf(store.get(refs[2])));
diff --git a/vespalib/src/tests/datastore/unique_store_dictionary/unique_store_dictionary_test.cpp b/vespalib/src/tests/datastore/unique_store_dictionary/unique_store_dictionary_test.cpp
index d0fede5c550..496bc814d0d 100644
--- a/vespalib/src/tests/datastore/unique_store_dictionary/unique_store_dictionary_test.cpp
+++ b/vespalib/src/tests/datastore/unique_store_dictionary/unique_store_dictionary_test.cpp
@@ -62,9 +62,9 @@ struct UniqueStoreDictionaryTest : public ::testing::Test {
}
void inc_generation() {
dict.freeze();
- dict.transfer_hold_lists(gen_handler.getCurrentGeneration());
+ dict.assign_generation(gen_handler.getCurrentGeneration());
gen_handler.incGeneration();
- dict.trim_hold_lists(gen_handler.getFirstUsedGeneration());
+ dict.reclaim_memory(gen_handler.get_oldest_used_generation());
}
void take_snapshot() {
dict.freeze();
diff --git a/vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp b/vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp
index 777da0c2b16..e865239787b 100644
--- a/vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp
+++ b/vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp
@@ -11,7 +11,7 @@
using namespace vespalib::datastore;
using vespalib::MemoryUsage;
using generation_t = vespalib::GenerationHandler::generation_t;
-using BufferStats = vespalib::datastore::test::BufferStats;
+using TestBufferStats = vespalib::datastore::test::BufferStats;
using vespalib::alloc::MemoryAllocator;
using vespalib::alloc::test::MemoryAllocatorObserver;
using AllocStats = MemoryAllocatorObserver::Stats;
@@ -51,8 +51,8 @@ struct TestBase : public ::testing::Test {
void remove(EntryRef ref) {
allocator.hold(ref);
}
- EntryRef move(EntryRef ref) {
- return allocator.move(ref);
+ EntryRef move_on_compact(EntryRef ref) {
+ return allocator.move_on_compact(ref);
}
uint32_t get_buffer_id(EntryRef ref) const {
return EntryRefType(ref).bufferId();
@@ -60,16 +60,16 @@ struct TestBase : public ::testing::Test {
const BufferState &buffer_state(EntryRef ref) const {
return allocator.get_data_store().getBufferState(get_buffer_id(ref));
}
- void assert_buffer_state(EntryRef ref, const BufferStats expStats) const {
+ void assert_buffer_state(EntryRef ref, const TestBufferStats expStats) const {
EXPECT_EQ(expStats._used, buffer_state(ref).size());
- EXPECT_EQ(expStats._hold, buffer_state(ref).getHoldElems());
- EXPECT_EQ(expStats._dead, buffer_state(ref).getDeadElems());
- EXPECT_EQ(expStats._extra_used, buffer_state(ref).getExtraUsedBytes());
- EXPECT_EQ(expStats._extra_hold, buffer_state(ref).getExtraHoldBytes());
+ EXPECT_EQ(expStats._hold, buffer_state(ref).stats().hold_elems());
+ EXPECT_EQ(expStats._dead, buffer_state(ref).stats().dead_elems());
+ EXPECT_EQ(expStats._extra_used, buffer_state(ref).stats().extra_used_bytes());
+ EXPECT_EQ(expStats._extra_hold, buffer_state(ref).stats().extra_hold_bytes());
}
- void trim_hold_lists() {
- allocator.get_data_store().transferHoldLists(generation++);
- allocator.get_data_store().trimHoldLists(generation);
+ void reclaim_memory() {
+ allocator.get_data_store().assign_generation(generation++);
+ allocator.get_data_store().reclaim_memory(generation);
}
};
@@ -86,32 +86,32 @@ TEST_F(StringTest, can_add_and_get_values)
TEST_F(StringTest, elements_are_put_on_hold_when_value_is_removed)
{
EntryRef ref = add(small.c_str());
- assert_buffer_state(ref, BufferStats().used(16).hold(0).dead(0));
+ assert_buffer_state(ref, TestBufferStats().used(16).hold(0).dead(0));
remove(ref);
- assert_buffer_state(ref, BufferStats().used(16).hold(16).dead(0));
- trim_hold_lists();
- assert_buffer_state(ref, BufferStats().used(16).hold(0).dead(16));
+ assert_buffer_state(ref, TestBufferStats().used(16).hold(16).dead(0));
+ reclaim_memory();
+ assert_buffer_state(ref, TestBufferStats().used(16).hold(0).dead(16));
}
TEST_F(StringTest, extra_bytes_used_is_tracked)
{
EntryRef ref = add(spaces1000.c_str());
// Note: The first buffer have the first element reserved -> we expect 2 elements used here.
- assert_buffer_state(ref, BufferStats().used(2).hold(0).dead(1).extra_used(1001));
+ assert_buffer_state(ref, TestBufferStats().used(2).hold(0).dead(1).extra_used(1001));
remove(ref);
- assert_buffer_state(ref, BufferStats().used(2).hold(1).dead(1).extra_used(1001).extra_hold(1001));
- trim_hold_lists();
- assert_buffer_state(ref, BufferStats().used(2).hold(0).dead(2));
+ assert_buffer_state(ref, TestBufferStats().used(2).hold(1).dead(1).extra_used(1001).extra_hold(1001));
+ reclaim_memory();
+ assert_buffer_state(ref, TestBufferStats().used(2).hold(0).dead(2));
ref = add(spaces1000.c_str());
- assert_buffer_state(ref, BufferStats().used(2).hold(0).dead(1).extra_used(1001));
- EntryRef ref2 = move(ref);
+ assert_buffer_state(ref, TestBufferStats().used(2).hold(0).dead(1).extra_used(1001));
+ EntryRef ref2 = move_on_compact(ref);
assert_get(ref2, spaces1000.c_str());
- assert_buffer_state(ref, BufferStats().used(3).hold(0).dead(1).extra_used(2002));
+ assert_buffer_state(ref, TestBufferStats().used(3).hold(0).dead(1).extra_used(2002));
remove(ref);
remove(ref2);
- assert_buffer_state(ref, BufferStats().used(3).hold(2).dead(1).extra_used(2002).extra_hold(2002));
- trim_hold_lists();
- assert_buffer_state(ref, BufferStats().used(3).hold(0).dead(3));
+ assert_buffer_state(ref, TestBufferStats().used(3).hold(2).dead(1).extra_used(2002).extra_hold(2002));
+ reclaim_memory();
+ assert_buffer_state(ref, TestBufferStats().used(3).hold(0).dead(3));
}
TEST_F(StringTest, string_length_determines_buffer)
@@ -134,13 +134,13 @@ TEST_F(StringTest, free_list_is_used_when_enabled)
EntryRef ref2 = add(spaces1000.c_str());
remove(ref1);
remove(ref2);
- trim_hold_lists();
+ reclaim_memory();
EntryRef ref3 = add(small.c_str());
EntryRef ref4 = add(spaces1000.c_str());
EXPECT_EQ(ref1, ref3);
EXPECT_EQ(ref2, ref4);
- assert_buffer_state(ref1, BufferStats().used(16).hold(0).dead(0));
- assert_buffer_state(ref2, BufferStats().used(2).hold(0).dead(1).extra_used(1001));
+ assert_buffer_state(ref1, TestBufferStats().used(16).hold(0).dead(0));
+ assert_buffer_state(ref2, TestBufferStats().used(2).hold(0).dead(1).extra_used(1001));
}
TEST_F(StringTest, free_list_is_not_used_when_disabled)
@@ -150,16 +150,16 @@ TEST_F(StringTest, free_list_is_not_used_when_disabled)
EntryRef ref2 = add(spaces1000.c_str());
remove(ref1);
remove(ref2);
- trim_hold_lists();
+ reclaim_memory();
EntryRef ref3 = add(small.c_str());
EntryRef ref4 = add(spaces1000.c_str());
EXPECT_NE(ref1, ref3);
EXPECT_NE(ref2, ref4);
- assert_buffer_state(ref1, BufferStats().used(32).hold(0).dead(16));
- assert_buffer_state(ref2, BufferStats().used(3).hold(0).dead(2).extra_used(1001));
+ assert_buffer_state(ref1, TestBufferStats().used(32).hold(0).dead(16));
+ assert_buffer_state(ref2, TestBufferStats().used(3).hold(0).dead(2).extra_used(1001));
}
-TEST_F(StringTest, free_list_is_never_used_for_move)
+TEST_F(StringTest, free_list_is_never_used_for_move_on_compact)
{
// Free lists are default enabled for UniqueStoreStringAllocator
EntryRef ref1 = add(small.c_str());
@@ -168,13 +168,13 @@ TEST_F(StringTest, free_list_is_never_used_for_move)
EntryRef ref4 = add(spaces1000.c_str());
remove(ref3);
remove(ref4);
- trim_hold_lists();
- EntryRef ref5 = move(ref1);
- EntryRef ref6 = move(ref2);
+ reclaim_memory();
+ EntryRef ref5 = move_on_compact(ref1);
+ EntryRef ref6 = move_on_compact(ref2);
EXPECT_NE(ref5, ref3);
EXPECT_NE(ref6, ref4);
- assert_buffer_state(ref1, BufferStats().used(48).hold(0).dead(16));
- assert_buffer_state(ref2, BufferStats().used(4).hold(0).dead(2).extra_used(2002));
+ assert_buffer_state(ref1, TestBufferStats().used(48).hold(0).dead(16));
+ assert_buffer_state(ref2, TestBufferStats().used(4).hold(0).dead(2).extra_used(2002));
}
TEST_F(StringTest, provided_memory_allocator_is_used)
diff --git a/vespalib/src/tests/util/generation_hold_list/CMakeLists.txt b/vespalib/src/tests/util/generation_hold_list/CMakeLists.txt
new file mode 100644
index 00000000000..c85b2537745
--- /dev/null
+++ b/vespalib/src/tests/util/generation_hold_list/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_generation_hold_list_test_app TEST
+ SOURCES
+ generation_hold_list_test.cpp
+ DEPENDS
+ vespalib
+ GTest::GTest
+)
+vespa_add_test(NAME vespalib_generation_hold_list_test_app COMMAND vespalib_generation_hold_list_test_app)
diff --git a/vespalib/src/tests/util/generation_hold_list/generation_hold_list_test.cpp b/vespalib/src/tests/util/generation_hold_list/generation_hold_list_test.cpp
new file mode 100644
index 00000000000..7e56e17990c
--- /dev/null
+++ b/vespalib/src/tests/util/generation_hold_list/generation_hold_list_test.cpp
@@ -0,0 +1,95 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/gtest/gtest.h>
+#include <vespa/vespalib/util/generation_hold_list.hpp>
+#include <vespa/vespalib/util/generationholder.h>
+#include <cstdint>
+
+using namespace vespalib;
+
+using MyElem = GenerationHeldBase;
+using generation_t = GenerationHandler::generation_t;
+
+TEST(GenerationHolderTest, holding_of_unique_ptr_elements_with_tracking_of_held_bytes)
+{
+ GenerationHolder h;
+ h.insert(std::make_unique<MyElem>(3));
+ h.assign_generation(0);
+ h.insert(std::make_unique<MyElem>(5));
+ h.assign_generation(1);
+ h.insert(std::make_unique<MyElem>(7));
+ h.assign_generation(2);
+ h.insert(std::make_unique<MyElem>(11));
+ h.assign_generation(4);
+ EXPECT_EQ(3 + 5 + 7 + 11, h.get_held_bytes());
+
+ h.reclaim(0);
+ EXPECT_EQ(3 + 5 + 7 + 11, h.get_held_bytes());
+ h.reclaim(1);
+ EXPECT_EQ(5 + 7 + 11, h.get_held_bytes());
+ h.reclaim(2);
+ EXPECT_EQ(7 + 11, h.get_held_bytes());
+
+ h.insert(std::make_unique<MyElem>(13));
+ h.assign_generation(6);
+ EXPECT_EQ(7 + 11 + 13, h.get_held_bytes());
+
+ h.reclaim(6);
+ EXPECT_EQ(13, h.get_held_bytes());
+ h.reclaim(7);
+ EXPECT_EQ(0, h.get_held_bytes());
+ h.reclaim(7);
+ EXPECT_EQ(0, h.get_held_bytes());
+}
+
+TEST(GenerationHolderTest, reclaim_all_clears_everything)
+{
+ GenerationHolder h;
+ h.insert(std::make_unique<MyElem>(3));
+ h.insert(std::make_unique<MyElem>(5));
+ h.assign_generation(1);
+ h.reclaim_all();
+ EXPECT_EQ(0, h.get_held_bytes());
+}
+
+using IntVector = std::vector<int32_t>;
+using IntHoldList = GenerationHoldList<int32_t, false, true>;
+
+struct IntHoldListTest : public testing::Test {
+ IntHoldList h;
+ IntHoldListTest() : h() {}
+ void assert_reclaim(const IntVector& exp, generation_t oldest_used_gen) {
+ IntVector act;
+ h.reclaim(oldest_used_gen, [&](int elem){ act.push_back(elem); });
+ EXPECT_EQ(exp, act);
+ }
+ void assert_reclaim_all(const IntVector& exp) {
+ IntVector act;
+ h.reclaim_all([&](int elem){ act.push_back(elem); });
+ EXPECT_EQ(exp, act);
+ }
+};
+
+TEST_F(IntHoldListTest, reclaim_calls_callback_for_reclaimed_elements)
+{
+ h.insert(3);
+ h.assign_generation(1);
+ h.insert(5);
+ h.insert(7);
+ h.assign_generation(2);
+
+ assert_reclaim({}, 1);
+ assert_reclaim({3}, 2);
+ assert_reclaim({5, 7}, 3);
+}
+
+TEST_F(IntHoldListTest, reclaim_all_calls_callback_for_all_elements)
+{
+ h.insert(3);
+ h.insert(5);
+ h.assign_generation(2);
+ assert_reclaim_all({3, 5});
+ assert_reclaim_all({});
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/vespalib/src/tests/util/generation_holder/CMakeLists.txt b/vespalib/src/tests/util/generation_holder/CMakeLists.txt
deleted file mode 100644
index 8acf9fadaff..00000000000
--- a/vespalib/src/tests/util/generation_holder/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-vespa_add_executable(vespalib_generation_holder_test_app TEST
- SOURCES
- generation_holder_test.cpp
- DEPENDS
- vespalib
- GTest::GTest
-)
-vespa_add_test(NAME vespalib_generation_holder_test_app COMMAND vespalib_generation_holder_test_app)
diff --git a/vespalib/src/tests/util/generation_holder/generation_holder_test.cpp b/vespalib/src/tests/util/generation_holder/generation_holder_test.cpp
deleted file mode 100644
index 97c3330ac9e..00000000000
--- a/vespalib/src/tests/util/generation_holder/generation_holder_test.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <vespa/vespalib/gtest/gtest.h>
-#include <vespa/vespalib/util/generationholder.h>
-
-using vespalib::GenerationHolder;
-using MyHeld = vespalib::GenerationHeldBase;
-
-TEST(GenerationHolderTest, basic_tracking)
-{
- GenerationHolder gh;
- gh.hold(std::make_unique<MyHeld>(sizeof(int32_t)));
- gh.transferHoldLists(0);
- gh.hold(std::make_unique<MyHeld>(sizeof(int32_t)));
- gh.transferHoldLists(1);
- gh.hold(std::make_unique<MyHeld>(sizeof(int32_t)));
- gh.transferHoldLists(2);
- gh.hold(std::make_unique<MyHeld>(sizeof(int32_t)));
- gh.transferHoldLists(4);
- EXPECT_EQ(4u * sizeof(int32_t), gh.getHeldBytes());
- gh.trimHoldLists(0);
- EXPECT_EQ(4u * sizeof(int32_t), gh.getHeldBytes());
- gh.trimHoldLists(1);
- EXPECT_EQ(3u * sizeof(int32_t), gh.getHeldBytes());
- gh.trimHoldLists(2);
- EXPECT_EQ(2u * sizeof(int32_t), gh.getHeldBytes());
- gh.hold(std::make_unique<MyHeld>(sizeof(int32_t)));
- gh.transferHoldLists(6);
- EXPECT_EQ(3u * sizeof(int32_t), gh.getHeldBytes());
- gh.trimHoldLists(6);
- EXPECT_EQ(1u * sizeof(int32_t), gh.getHeldBytes());
- gh.trimHoldLists(7);
- EXPECT_EQ(0u * sizeof(int32_t), gh.getHeldBytes());
- gh.trimHoldLists(7);
- EXPECT_EQ(0u * sizeof(int32_t), gh.getHeldBytes());
-}
-
-GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/vespalib/src/tests/util/generationhandler/generationhandler_test.cpp b/vespalib/src/tests/util/generationhandler/generationhandler_test.cpp
index 00da752a749..0bc72f93a9d 100644
--- a/vespalib/src/tests/util/generationhandler/generationhandler_test.cpp
+++ b/vespalib/src/tests/util/generationhandler/generationhandler_test.cpp
@@ -26,10 +26,10 @@ GenerationHandlerTest::~GenerationHandlerTest() = default;
TEST_F(GenerationHandlerTest, require_that_generation_can_be_increased)
{
EXPECT_EQ(0u, gh.getCurrentGeneration());
- EXPECT_EQ(0u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(0u, gh.get_oldest_used_generation());
gh.incGeneration();
EXPECT_EQ(1u, gh.getCurrentGeneration());
- EXPECT_EQ(1u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(1u, gh.get_oldest_used_generation());
}
TEST_F(GenerationHandlerTest, require_that_readers_can_take_guards)
@@ -87,34 +87,34 @@ TEST_F(GenerationHandlerTest, require_that_guards_can_be_copied)
TEST_F(GenerationHandlerTest, require_that_the_first_used_generation_is_correct)
{
- EXPECT_EQ(0u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(0u, gh.get_oldest_used_generation());
gh.incGeneration();
- EXPECT_EQ(1u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(1u, gh.get_oldest_used_generation());
{
GenGuard g1 = gh.takeGuard();
gh.incGeneration();
EXPECT_EQ(1u, gh.getGenerationRefCount());
- EXPECT_EQ(1u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(1u, gh.get_oldest_used_generation());
}
- EXPECT_EQ(1u, gh.getFirstUsedGeneration());
- gh.updateFirstUsedGeneration(); // Only writer should call this
+ EXPECT_EQ(1u, gh.get_oldest_used_generation());
+ gh.update_oldest_used_generation(); // Only writer should call this
EXPECT_EQ(0u, gh.getGenerationRefCount());
- EXPECT_EQ(2u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(2u, gh.get_oldest_used_generation());
{
GenGuard g1 = gh.takeGuard();
gh.incGeneration();
gh.incGeneration();
EXPECT_EQ(1u, gh.getGenerationRefCount());
- EXPECT_EQ(2u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(2u, gh.get_oldest_used_generation());
{
GenGuard g2 = gh.takeGuard();
- EXPECT_EQ(2u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(2u, gh.get_oldest_used_generation());
}
}
- EXPECT_EQ(2u, gh.getFirstUsedGeneration());
- gh.updateFirstUsedGeneration(); // Only writer should call this
+ EXPECT_EQ(2u, gh.get_oldest_used_generation());
+ gh.update_oldest_used_generation(); // Only writer should call this
EXPECT_EQ(0u, gh.getGenerationRefCount());
- EXPECT_EQ(4u, gh.getFirstUsedGeneration());
+ EXPECT_EQ(4u, gh.get_oldest_used_generation());
}
TEST_F(GenerationHandlerTest, require_that_generation_can_grow_large)
@@ -124,7 +124,7 @@ TEST_F(GenerationHandlerTest, require_that_generation_can_grow_large)
EXPECT_EQ(i, gh.getCurrentGeneration());
guards.push_back(gh.takeGuard()); // take guard on current generation
if (i >= 128) {
- EXPECT_EQ(i - 128, gh.getFirstUsedGeneration());
+ EXPECT_EQ(i - 128, gh.get_oldest_used_generation());
guards.pop_front();
EXPECT_EQ(128u, gh.getGenerationRefCount());
}
diff --git a/vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp b/vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp
index 74af25b54a8..fd2769fd8b1 100644
--- a/vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp
+++ b/vespalib/src/tests/util/generationhandler_stress/generation_handler_stress_test.cpp
@@ -238,7 +238,7 @@ Fixture::write_indirect_work(uint64_t cnt, IndirectContext& context)
ReadStopper read_stopper(_stopRead);
uint32_t sleep_cnt = 0;
ASSERT_EQ(0, _generationHandler.getCurrentGeneration());
- auto oldest_gen = _generationHandler.getFirstUsedGeneration();
+ auto oldest_gen = _generationHandler.get_oldest_used_generation();
for (uint64_t i = 0; i < cnt; ++i) {
auto gen = _generationHandler.getCurrentGeneration();
// Hold data for gen, write new data for next_gen
@@ -248,7 +248,7 @@ Fixture::write_indirect_work(uint64_t cnt, IndirectContext& context)
*v_ptr = next_gen;
context._value_ptr.store(v_ptr, std::memory_order_release);
_generationHandler.incGeneration();
- auto first_used_gen = _generationHandler.getFirstUsedGeneration();
+ auto first_used_gen = _generationHandler.get_oldest_used_generation();
while (oldest_gen < first_used_gen) {
// Clear data that readers should no longer have access to.
*context.calc_value_ptr(oldest_gen) = 0;
@@ -258,8 +258,8 @@ Fixture::write_indirect_work(uint64_t cnt, IndirectContext& context)
// Sleep if writer gets too much ahead of readers.
std::this_thread::sleep_for(1ms);
++sleep_cnt;
- _generationHandler.updateFirstUsedGeneration();
- first_used_gen = _generationHandler.getFirstUsedGeneration();
+ _generationHandler.update_oldest_used_generation();
+ first_used_gen = _generationHandler.get_oldest_used_generation();
}
}
_doneWriteWork += cnt;
diff --git a/vespalib/src/tests/util/rcuvector/rcuvector_test.cpp b/vespalib/src/tests/util/rcuvector/rcuvector_test.cpp
index eb2b00f9e20..5d6ec3050da 100644
--- a/vespalib/src/tests/util/rcuvector/rcuvector_test.cpp
+++ b/vespalib/src/tests/util/rcuvector/rcuvector_test.cpp
@@ -102,15 +102,15 @@ TEST(RcuVectorTest, resize)
RcuVectorBase<int8_t> v(growStrategy(16, 1.0, 0), g);
v.push_back(1);
v.push_back(2);
- g.transferHoldLists(0);
- g.trimHoldLists(1);
+ g.assign_generation(0);
+ g.reclaim(1);
const int8_t *old = &v[0];
EXPECT_EQ(16u, v.capacity());
EXPECT_EQ(2u, v.size());
v.ensure_size(32, 3);
v[0] = 3;
v[1] = 3;
- g.transferHoldLists(1);
+ g.assign_generation(1);
EXPECT_EQ(1, old[0]);
EXPECT_EQ(2, old[1]);
EXPECT_EQ(3, v[0]);
@@ -119,7 +119,7 @@ TEST(RcuVectorTest, resize)
EXPECT_EQ(3, v[31]);
EXPECT_EQ(64u, v.capacity());
EXPECT_EQ(32u, v.size());
- g.trimHoldLists(2);
+ g.reclaim(2);
}
}
@@ -140,7 +140,7 @@ TEST(RcuVectorTest, generation_handling)
v.setGeneration(2);
v.push_back(50);
- v.removeOldGenerations(3);
+ v.reclaim_memory(3);
EXPECT_EQ(0u, v.getMemoryUsage().allocatedBytesOnHold());
v.push_back(60); // new array
EXPECT_EQ(24u, v.getMemoryUsage().allocatedBytesOnHold());
@@ -184,7 +184,7 @@ TEST(RcuVectorTest, memory_usage)
EXPECT_TRUE(assertUsage(MemoryUsage(6,6,0,2), v.getMemoryUsage()));
v.push_back(4);
EXPECT_TRUE(assertUsage(MemoryUsage(12,11,0,6), v.getMemoryUsage()));
- v.removeOldGenerations(1);
+ v.reclaim_memory(1);
EXPECT_TRUE(assertUsage(MemoryUsage(6,5,0,0), v.getMemoryUsage()));
}
@@ -197,11 +197,11 @@ void verify_shrink_with_buffer_copying(size_t initial_size, size_t absolute_mini
v.push_back(2);
v.push_back(3);
v.push_back(4);
- g.transferHoldLists(0);
- g.trimHoldLists(1);
+ g.assign_generation(0);
+ g.reclaim(1);
MemoryUsage mu;
mu = v.getMemoryUsage();
- mu.incAllocatedBytesOnHold(g.getHeldBytes());
+ mu.incAllocatedBytesOnHold(g.get_held_bytes());
EXPECT_TRUE(assertUsage(MemoryUsage(initial_capacity, 4, 0, 0), mu));
EXPECT_EQ(4u, v.size());
EXPECT_EQ(initial_capacity, v.capacity());
@@ -211,18 +211,18 @@ void verify_shrink_with_buffer_copying(size_t initial_size, size_t absolute_mini
EXPECT_EQ(4, v[3]);
const int8_t *old = &v[0];
v.shrink(2);
- g.transferHoldLists(1);
+ g.assign_generation(1);
EXPECT_EQ(2u, v.size());
EXPECT_EQ(minimal_capacity, v.capacity());
EXPECT_EQ(1, v[0]);
EXPECT_EQ(2, v[1]);
EXPECT_EQ(1, old[0]);
EXPECT_EQ(2, old[1]);
- g.trimHoldLists(2);
+ g.reclaim(2);
EXPECT_EQ(1, v[0]);
EXPECT_EQ(2, v[1]);
mu = v.getMemoryUsage();
- mu.incAllocatedBytesOnHold(g.getHeldBytes());
+ mu.incAllocatedBytesOnHold(g.get_held_bytes());
EXPECT_TRUE(assertUsage(MemoryUsage(minimal_capacity, 2, 0, 0), mu));
}
@@ -256,7 +256,7 @@ struct ShrinkFixture {
EXPECT_EQ(oldPtr, &vec[0]);
}
void assertEmptyHoldList() {
- EXPECT_EQ(0u, g.getHeldBytes());
+ EXPECT_EQ(0u, g.get_held_bytes());
}
static size_t page_ints() { return round_up_to_page_size(1) / sizeof(int); }
};
@@ -294,8 +294,8 @@ TEST(RcuVectorTest, small_expand)
v.push_back(2);
EXPECT_EQ(2u, v.capacity());
EXPECT_EQ(2u, v.size());
- g.transferHoldLists(1);
- g.trimHoldLists(2);
+ g.assign_generation(1);
+ g.reclaim(2);
}
struct FixtureBase {
@@ -325,10 +325,10 @@ struct Fixture : public FixtureBase {
Fixture();
~Fixture();
- void transfer_and_trim(generation_t transfer_gen, generation_t trim_gen)
+ void assign_and_reclaim(generation_t assign_gen, generation_t reclaim_gen)
{
- g.transferHoldLists(transfer_gen);
- g.trimHoldLists(trim_gen);
+ g.assign_generation(assign_gen);
+ g.reclaim(reclaim_gen);
}
};
@@ -345,7 +345,7 @@ TEST(RcuVectorTest, memory_allocator_can_be_set)
{
Fixture f;
EXPECT_EQ(AllocStats(2, 0), f.stats);
- f.transfer_and_trim(1, 2);
+ f.assign_and_reclaim(1, 2);
EXPECT_EQ(AllocStats(2, 1), f.stats);
}
@@ -355,7 +355,7 @@ TEST(RcuVectorTest, memory_allocator_is_preserved_across_reset)
f.arr.reset();
f.arr.reserve(100);
EXPECT_EQ(AllocStats(4, 1), f.stats);
- f.transfer_and_trim(1, 2);
+ f.assign_and_reclaim(1, 2);
EXPECT_EQ(AllocStats(4, 3), f.stats);
}
@@ -366,7 +366,7 @@ TEST(RcuVectorTest, created_replacement_vector_uses_same_memory_allocator)
EXPECT_EQ(AllocStats(2, 0), f.stats);
arr2.reserve(100);
EXPECT_EQ(AllocStats(3, 0), f.stats);
- f.transfer_and_trim(1, 2);
+ f.assign_and_reclaim(1, 2);
EXPECT_EQ(AllocStats(3, 1), f.stats);
}
@@ -377,7 +377,7 @@ TEST(RcuVectorTest, ensure_size_and_shrink_use_same_memory_allocator)
EXPECT_EQ(AllocStats(3, 0), f.stats);
f.arr.shrink(1000);
EXPECT_EQ(AllocStats(4, 0), f.stats);
- f.transfer_and_trim(1, 2);
+ f.assign_and_reclaim(1, 2);
EXPECT_EQ(AllocStats(4, 3), f.stats);
}
@@ -432,10 +432,10 @@ void
StressFixture::commit()
{
auto current_gen = generation_handler.getCurrentGeneration();
- g.transferHoldLists(current_gen);
+ g.assign_generation(current_gen);
generation_handler.incGeneration();
- auto first_used_gen = generation_handler.getFirstUsedGeneration();
- g.trimHoldLists(first_used_gen);
+ auto first_used_gen = generation_handler.get_oldest_used_generation();
+ g.reclaim(first_used_gen);
}
void
diff --git a/vespalib/src/vespa/vespalib/btree/btree.hpp b/vespalib/src/vespa/vespalib/btree/btree.hpp
index c6d8886254d..81687b6e62d 100644
--- a/vespalib/src/vespa/vespalib/btree/btree.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btree.hpp
@@ -20,7 +20,7 @@ BTree<KeyT, DataT, AggrT, CompareT, TraitsT, AggrCalcT>::~BTree()
{
clear();
_alloc.freeze();
- _alloc.clearHoldLists();
+ _alloc.reclaim_all_memory();
}
template <typename KeyT, typename DataT, typename AggrT, typename CompareT,
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h
index 86c9621f869..77900edf848 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h
+++ b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h
@@ -101,7 +101,7 @@ public:
/**
* Try to free held nodes if nobody can be referencing them.
*/
- void trimHoldLists(generation_t usedGen);
+ void reclaim_memory(generation_t oldest_used_gen);
/**
* Transfer nodes from hold1 lists to hold2 lists, they are no
@@ -109,9 +109,9 @@ public:
* older versions of the frozen structure must leave before elements
* can be unheld.
*/
- void transferHoldLists(generation_t generation);
+ void assign_generation(generation_t current_gen);
- void clearHoldLists();
+ void reclaim_all_memory();
static bool isValidRef(BTreeNode::Ref ref) { return NodeStore::isValidRef(ref); }
@@ -164,14 +164,9 @@ public:
vespalib::string toString(const BTreeNode * node) const;
bool getCompacting(EntryRef ref) const { return _nodeStore.getCompacting(ref); }
- std::vector<uint32_t> startCompact() { return _nodeStore.startCompact(); }
std::unique_ptr<vespalib::datastore::CompactingBuffers> start_compact_worst(const CompactionStrategy& compaction_strategy) { return _nodeStore.start_compact_worst(compaction_strategy); }
- void finishCompact(const std::vector<uint32_t> &toHold) {
- return _nodeStore.finishCompact(toHold);
- }
-
template <typename FunctionType>
void foreach_key(EntryRef ref, FunctionType func) const {
_nodeStore.foreach_key(ref, func);
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp
index 81262f560c7..a38b68afe73 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp
@@ -34,7 +34,7 @@ BTreeNodeAllocator<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS>::
assert(_treeToFreeze.empty());
assert(_internalHoldUntilFreeze.empty());
assert(_leafHoldUntilFreeze.empty());
- DataStoreBase::MemStats stats = _nodeStore.getMemStats();
+ auto stats = _nodeStore.getMemStats();
assert(stats._usedBytes == stats._deadBytes);
assert(stats._holdBytes == 0);
(void) stats;
@@ -235,7 +235,7 @@ freeze()
InternalNodeType *inode = mapInternalRef(i);
(void) inode;
assert(inode->getFrozen());
- _nodeStore.freeElem(i);
+ _nodeStore.holdElem(i);
}
_internalHoldUntilFreeze.clear();
}
@@ -245,7 +245,7 @@ freeze()
LeafNodeType *lnode = mapLeafRef(i);
(void) lnode;
assert(lnode->getFrozen());
- _nodeStore.freeElem(i);
+ _nodeStore.holdElem(i);
}
_leafHoldUntilFreeze.clear();
}
@@ -266,18 +266,18 @@ template <typename KeyT, typename DataT, typename AggrT,
size_t INTERNAL_SLOTS, size_t LEAF_SLOTS>
void
BTreeNodeAllocator<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS>::
-trimHoldLists(generation_t usedGen)
+reclaim_memory(generation_t oldest_used_gen)
{
- _nodeStore.trimHoldLists(usedGen);
+ _nodeStore.reclaim_memory(oldest_used_gen);
}
template <typename KeyT, typename DataT, typename AggrT,
size_t INTERNAL_SLOTS, size_t LEAF_SLOTS>
void
BTreeNodeAllocator<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS>::
-transferHoldLists(generation_t generation)
+assign_generation(generation_t current_gen)
{
- _nodeStore.transferHoldLists(generation);
+ _nodeStore.assign_generation(current_gen);
}
@@ -285,9 +285,9 @@ template <typename KeyT, typename DataT, typename AggrT,
size_t INTERNAL_SLOTS, size_t LEAF_SLOTS>
void
BTreeNodeAllocator<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS>::
-clearHoldLists()
+reclaim_all_memory()
{
- _nodeStore.clearHoldLists();
+ _nodeStore.reclaim_all_memory();
}
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodestore.h b/vespalib/src/vespa/vespalib/btree/btreenodestore.h
index d05ec840f83..a63a4d20170 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodestore.h
+++ b/vespalib/src/vespa/vespalib/btree/btreenodestore.h
@@ -156,32 +156,24 @@ public:
_store.holdElem(ref, 1);
}
- void freeElem(EntryRef ref) {
- _store.freeElem(ref, 1);
- }
-
- std::vector<uint32_t> startCompact();
-
std::unique_ptr<vespalib::datastore::CompactingBuffers> start_compact_worst(const CompactionStrategy& compaction_strategy);
- void finishCompact(const std::vector<uint32_t> &toHold);
-
- void transferHoldLists(generation_t generation) {
- _store.transferHoldLists(generation);
+ void assign_generation(generation_t current_gen) {
+ _store.assign_generation(current_gen);
}
// Inherit doc from DataStoreBase
- datastore::DataStoreBase::MemStats getMemStats() const {
+ datastore::MemoryStats getMemStats() const {
return _store.getMemStats();
}
// Inherit doc from DataStoreBase
- void trimHoldLists(generation_t usedGen) {
- _store.trimHoldLists(usedGen);
+ void reclaim_memory(generation_t oldest_used_gen) {
+ _store.reclaim_memory(oldest_used_gen);
}
- void clearHoldLists() {
- _store.clearHoldLists();
+ void reclaim_all_memory() {
+ _store.reclaim_all_memory();
}
// Inherit doc from DataStoreBase
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodestore.hpp b/vespalib/src/vespa/vespalib/btree/btreenodestore.hpp
index 0f9eeb9daec..a1ffb4d445d 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodestore.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btreenodestore.hpp
@@ -55,20 +55,6 @@ BTreeNodeStore<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS>::
_store.dropBuffers(); // Drop buffers before type handlers are dropped
}
-
-template <typename KeyT, typename DataT, typename AggrT,
- size_t INTERNAL_SLOTS, size_t LEAF_SLOTS>
-std::vector<uint32_t>
-BTreeNodeStore<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS>::
-startCompact()
-{
- std::vector<uint32_t> iToHold = _store.startCompact(NODETYPE_INTERNAL);
- std::vector<uint32_t> lToHold = _store.startCompact(NODETYPE_LEAF);
- std::vector<uint32_t> ret = iToHold;
- ret.insert(ret.end(), lToHold.begin(), lToHold.end());
- return ret;
-}
-
template <typename KeyT, typename DataT, typename AggrT,
size_t INTERNAL_SLOTS, size_t LEAF_SLOTS>
std::unique_ptr<vespalib::datastore::CompactingBuffers>
@@ -78,15 +64,6 @@ start_compact_worst(const CompactionStrategy &compaction_strategy)
return _store.start_compact_worst_buffers(datastore::CompactionSpec(true, false), compaction_strategy);
}
-template <typename KeyT, typename DataT, typename AggrT,
- size_t INTERNAL_SLOTS, size_t LEAF_SLOTS>
-void
-BTreeNodeStore<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS>::
-finishCompact(const std::vector<uint32_t> &toHold)
-{
- _store.finishCompact(toHold);
-}
-
}
#define VESPALIB_DATASTORE_INSTANTIATE_BUFFERTYPE_INTERNALNODE(K, A, S) \
diff --git a/vespalib/src/vespa/vespalib/btree/btreestore.h b/vespalib/src/vespa/vespalib/btree/btreestore.h
index 54bc397175d..e5c55d5775d 100644
--- a/vespalib/src/vespa/vespalib/btree/btreestore.h
+++ b/vespalib/src/vespa/vespalib/btree/btreestore.h
@@ -332,25 +332,25 @@ public:
// Inherit doc from DataStoreBase
void
- trimHoldLists(generation_t usedGen)
+ reclaim_memory(generation_t oldest_used_gen)
{
- _allocator.trimHoldLists(usedGen);
- _store.trimHoldLists(usedGen);
+ _allocator.reclaim_memory(oldest_used_gen);
+ _store.reclaim_memory(oldest_used_gen);
}
// Inherit doc from DataStoreBase
void
- transferHoldLists(generation_t generation)
+ assign_generation(generation_t current_gen)
{
- _allocator.transferHoldLists(generation);
- _store.transferHoldLists(generation);
+ _allocator.assign_generation(current_gen);
+ _store.assign_generation(current_gen);
}
void
- clearHoldLists()
+ reclaim_all_memory()
{
- _allocator.clearHoldLists();
- _store.clearHoldLists();
+ _allocator.reclaim_all_memory();
+ _store.reclaim_all_memory();
}
diff --git a/vespalib/src/vespa/vespalib/coro/CMakeLists.txt b/vespalib/src/vespa/vespalib/coro/CMakeLists.txt
new file mode 100644
index 00000000000..d190c2e8ddc
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/coro/CMakeLists.txt
@@ -0,0 +1,5 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(vespalib_vespalib_coro OBJECT
+ SOURCES
+ DEPENDS
+)
diff --git a/vespalib/src/vespa/vespalib/coro/detached.h b/vespalib/src/vespa/vespalib/coro/detached.h
new file mode 100644
index 00000000000..5e3fa1452fa
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/coro/detached.h
@@ -0,0 +1,32 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <coroutine>
+#include <exception>
+
+namespace vespalib::coro {
+
+/**
+ * coroutine return type
+ *
+ * The coroutine is eager (will not suspend in initial_suspend) and
+ * self destroying (will not suspend in final_suspend). The return
+ * value gives no way of interacting with the coroutine. Without any
+ * co_await operations this acts similar to a normal subroutine. Note
+ * that letting a detached coroutine wait for a Lazy<T> will
+ * essentially attach it to the Lazy<T> as a continuation and resume
+ * it, but will require the Lazy<T> not to be deleted mid flight
+ * (started but not completed).
+ **/
+struct Detached {
+ struct promise_type {
+ Detached get_return_object() { return {}; }
+ static std::suspend_never initial_suspend() noexcept { return {}; }
+ static std::suspend_never final_suspend() noexcept { return {}; }
+ static void unhandled_exception() { std::terminate(); }
+ void return_void() noexcept {};
+ };
+};
+
+}
diff --git a/vespalib/src/vespa/vespalib/coro/lazy.h b/vespalib/src/vespa/vespalib/coro/lazy.h
new file mode 100644
index 00000000000..5a10c05bc24
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/coro/lazy.h
@@ -0,0 +1,111 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <concepts>
+#include <coroutine>
+#include <optional>
+#include <exception>
+#include <utility>
+
+namespace vespalib::coro {
+
+/**
+ * coroutine return type
+ *
+ * The coroutine is lazy (will suspend in initial_suspend) and
+ * destroyed from the outside (will suspend in final_suspend). Waiting
+ * for a Lazy<T> using co_await will use symmetric transfer to suspend
+ * the waiting coroutine and resume this one. The waiting coroutine
+ * is registered as a continuation and will be resumed again once the
+ * result is available (also using symmetric transfer). The result is
+ * assumed to be produced asynchronously. If you need to access it
+ * from the outside (in that specific thread); use sync_wait.
+ **/
+template <std::movable T>
+class [[nodiscard]] Lazy {
+public:
+ struct promise_type {
+ Lazy<T> get_return_object() { return Lazy(Handle::from_promise(*this)); }
+ static std::suspend_always initial_suspend() noexcept { return {}; }
+ static auto final_suspend() noexcept {
+ struct awaiter {
+ bool await_ready() const noexcept { return false; }
+ std::coroutine_handle<> await_suspend(Handle handle) const noexcept {
+ return handle.promise().waiter;
+ }
+ void await_resume() const noexcept {}
+ };
+ return awaiter();
+ }
+ template <typename RET>
+ requires std::is_convertible_v<RET&&,T>
+ void return_value(RET &&ret_value) noexcept(std::is_nothrow_constructible_v<T,RET&&>) {
+ value = std::forward<RET>(ret_value);
+ }
+ void unhandled_exception() noexcept {
+ exception = std::current_exception();
+ }
+ std::optional<T> value;
+ std::exception_ptr exception;
+ std::coroutine_handle<> waiter;
+ promise_type(promise_type &&) = delete;
+ promise_type(const promise_type &) = delete;
+ promise_type() noexcept : value(std::nullopt), exception(), waiter(std::noop_coroutine()) {}
+ T &result() & {
+ if (exception) {
+ std::rethrow_exception(exception);
+ }
+ return *value;
+ }
+ T &&result() && {
+ if (exception) {
+ std::rethrow_exception(exception);
+ }
+ return std::move(*value);
+ }
+ };
+ using Handle = std::coroutine_handle<promise_type>;
+
+private:
+ Handle _handle;
+
+ struct awaiter_base {
+ Handle handle;
+ awaiter_base(Handle handle_in) noexcept : handle(handle_in) {}
+ bool await_ready() const noexcept { return handle.done(); }
+ Handle await_suspend(std::coroutine_handle<> waiter) const noexcept {
+ handle.promise().waiter = waiter;
+ return handle;
+ }
+ };
+
+public:
+ Lazy(const Lazy &) = delete;
+ Lazy &operator=(const Lazy &) = delete;
+ explicit Lazy(Handle handle_in) noexcept : _handle(handle_in) {}
+ Lazy(Lazy &&rhs) noexcept : _handle(std::exchange(rhs._handle, nullptr)) {}
+ auto operator co_await() & noexcept {
+ struct awaiter : awaiter_base {
+ using awaiter_base::handle;
+ awaiter(Handle handle_in) noexcept : awaiter_base(handle_in) {}
+ decltype(auto) await_resume() const { return handle.promise().result(); }
+ };
+ return awaiter(_handle);
+ }
+ auto operator co_await() && noexcept {
+ struct awaiter : awaiter_base {
+ using awaiter_base::handle;
+ awaiter(Handle handle_in) noexcept : awaiter_base(handle_in) {}
+ decltype(auto) await_resume() const { return std::move(handle.promise()).result(); }
+ };
+ return awaiter(_handle);
+ }
+ ~Lazy() {
+ if (_handle) {
+ _handle.destroy();
+ }
+ }
+};
+
+}
diff --git a/vespalib/src/vespa/vespalib/coro/sync_wait.h b/vespalib/src/vespa/vespalib/coro/sync_wait.h
new file mode 100644
index 00000000000..bdea2dfc7f0
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/coro/sync_wait.h
@@ -0,0 +1,59 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "detached.h"
+#include "lazy.h"
+#include <vespa/vespalib/util/gate.h>
+
+#include <coroutine>
+#include <exception>
+
+namespace vespalib::coro {
+
+template <typename T, typename S>
+Detached signal_when_done(Lazy<T> &value, S &sink) {
+ try {
+ sink(co_await value);
+ } catch (...) {
+ sink(std::current_exception());
+ }
+}
+
+/**
+ * Wait for a lazy value to be calculated (note that waiting for a
+ * value will also start calculating it). Make sure the thread waiting
+ * is not needed in the calculation of the value, or you will end up
+ * with a deadlock.
+ **/
+template <typename T>
+T &sync_wait(Lazy<T> &value) {
+ struct MySink {
+ Gate gate;
+ T *result;
+ std::exception_ptr exception;
+ void operator()(T &result_in) {
+ result = &result_in;
+ gate.countDown();
+ }
+ void operator()(std::exception_ptr exception_in) {
+ exception = exception_in;
+ gate.countDown();
+ }
+ MySink() : gate(), result(nullptr), exception() {}
+ };
+ MySink sink;
+ signal_when_done(value, sink);
+ sink.gate.await();
+ if (sink.exception) {
+ std::rethrow_exception(sink.exception);
+ }
+ return *sink.result;
+}
+
+template <typename T>
+T &&sync_wait(Lazy<T> &&value) {
+ return std::move(sync_wait(value));
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/datastore/CMakeLists.txt b/vespalib/src/vespa/vespalib/datastore/CMakeLists.txt
index 9990e3f5764..f11004363f8 100644
--- a/vespalib/src/vespa/vespalib/datastore/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/datastore/CMakeLists.txt
@@ -5,6 +5,7 @@ vespa_add_library(vespalib_vespalib_datastore OBJECT
array_store_config.cpp
atomic_entry_ref.cpp
buffer_free_list.cpp
+ buffer_stats.cpp
buffer_type.cpp
bufferstate.cpp
compact_buffer_candidates.cpp
@@ -19,6 +20,7 @@ vespa_add_library(vespalib_vespalib_datastore OBJECT
fixed_size_hash_map.cpp
free_list.cpp
large_array_buffer_type.cpp
+ memory_stats.cpp
sharded_hash_map.cpp
small_array_buffer_type.cpp
unique_store.cpp
diff --git a/vespalib/src/vespa/vespalib/datastore/allocator.hpp b/vespalib/src/vespa/vespalib/datastore/allocator.hpp
index 9b69be49b8e..a65fd8a2352 100644
--- a/vespalib/src/vespa/vespalib/datastore/allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/allocator.hpp
@@ -28,7 +28,7 @@ Allocator<EntryT, RefT>::alloc(Args && ... args)
RefT ref(oldBufferSize, buffer_id);
EntryT *entry = _store.getEntry<EntryT>(ref);
new (static_cast<void *>(entry)) EntryT(std::forward<Args>(args)...);
- state.pushed_back(1);
+ state.stats().pushed_back(1);
return HandleType(ref, entry);
}
@@ -48,7 +48,7 @@ Allocator<EntryT, RefT>::allocArray(ConstArrayRef array)
for (size_t i = 0; i < array.size(); ++i) {
new (static_cast<void *>(buf + i)) EntryT(array[i]);
}
- state.pushed_back(array.size());
+ state.stats().pushed_back(array.size());
return HandleType(ref, buf);
}
@@ -68,7 +68,7 @@ Allocator<EntryT, RefT>::allocArray(size_t size)
for (size_t i = 0; i < size; ++i) {
new (static_cast<void *>(buf + i)) EntryT();
}
- state.pushed_back(size);
+ state.stats().pushed_back(size);
return HandleType(ref, buf);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store.h b/vespalib/src/vespa/vespalib/datastore/array_store.h
index db037ee12fb..95d632d9603 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store.h
+++ b/vespalib/src/vespa/vespalib/datastore/array_store.h
@@ -10,6 +10,7 @@
#include "entryref.h"
#include "atomic_entry_ref.h"
#include "i_compaction_context.h"
+#include "i_compactable.h"
#include "large_array_buffer_type.h"
#include "small_array_buffer_type.h"
#include <vespa/vespalib/util/array.h>
@@ -28,7 +29,7 @@ namespace vespalib::datastore {
* The max value of maxSmallArrayTypeId is (2^bufferBits - 1).
*/
template <typename EntryT, typename RefT = EntryRefT<19>, typename TypeMapperT = ArrayStoreTypeMapper<EntryT> >
-class ArrayStore
+class ArrayStore : public ICompactable
{
public:
using AllocSpec = ArrayStoreConfig::AllocSpec;
@@ -66,7 +67,7 @@ private:
public:
ArrayStore(const ArrayStoreConfig &cfg, std::shared_ptr<alloc::MemoryAllocator> memory_allocator);
ArrayStore(const ArrayStoreConfig &cfg, std::shared_ptr<alloc::MemoryAllocator> memory_allocator, TypeMapper&& mapper);
- ~ArrayStore();
+ ~ArrayStore() override;
EntryRef add(const ConstArrayRef &array);
ConstArrayRef get(EntryRef ref) const {
if (!ref.valid()) {
@@ -104,6 +105,7 @@ public:
}
void remove(EntryRef ref);
+ EntryRef move_on_compact(EntryRef ref) override;
ICompactionContext::UP compactWorst(CompactionSpec compaction_spec, const CompactionStrategy& compaction_strategy);
vespalib::MemoryUsage getMemoryUsage() const { return _store.getMemoryUsage(); }
@@ -114,8 +116,8 @@ public:
vespalib::AddressSpace addressSpaceUsage() const;
// Pass on hold list management to underlying store
- void transferHoldLists(generation_t generation) { _store.transferHoldLists(generation); }
- void trimHoldLists(generation_t firstUsed) { _store.trimHoldLists(firstUsed); }
+ void assign_generation(generation_t current_gen) { _store.assign_generation(current_gen); }
+ void reclaim_memory(generation_t oldest_used_gen) { _store.reclaim_memory(oldest_used_gen); }
vespalib::GenerationHolder &getGenerationHolder() { return _store.getGenerationHolder(); }
void setInitializing(bool initializing) { _store.setInitializing(initializing); }
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store.hpp b/vespalib/src/vespa/vespalib/datastore/array_store.hpp
index e79398271fb..95f4a3c4155 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/array_store.hpp
@@ -4,13 +4,14 @@
#include "array_store.h"
#include "compacting_buffers.h"
+#include "compaction_context.h"
#include "compaction_spec.h"
-#include "entry_ref_filter.h"
#include "datastore.hpp"
+#include "entry_ref_filter.h"
#include "large_array_buffer_type.hpp"
#include "small_array_buffer_type.hpp"
-#include <atomic>
#include <algorithm>
+#include <atomic>
namespace vespalib::datastore {
@@ -57,7 +58,7 @@ ArrayStore<EntryT, RefT, TypeMapperT>::ArrayStore(const ArrayStoreConfig &cfg, s
template <typename EntryT, typename RefT, typename TypeMapperT>
ArrayStore<EntryT, RefT, TypeMapperT>::~ArrayStore()
{
- _store.clearHoldLists();
+ _store.reclaim_all_memory();
_store.dropBuffers();
}
@@ -114,7 +115,7 @@ ArrayStore<EntryT, RefT, TypeMapperT>::addLargeArray(const ConstArrayRef &array)
auto handle = _store.template freeListAllocator<LargeArray, NoOpReclaimer>(_largeArrayTypeId)
.alloc(array.cbegin(), array.cend());
auto& state = _store.getBufferState(RefT(handle.ref).bufferId());
- state.incExtraUsedBytes(sizeof(EntryT) * array.size());
+ state.stats().inc_extra_used_bytes(sizeof(EntryT) * array.size());
return handle.ref;
}
@@ -125,7 +126,7 @@ ArrayStore<EntryT, RefT, TypeMapperT>::allocate_large_array(size_t array_size)
using NoOpReclaimer = DefaultReclaimer<LargeArray>;
auto handle = _store.template freeListAllocator<LargeArray, NoOpReclaimer>(_largeArrayTypeId).alloc(array_size);
auto& state = _store.getBufferState(RefT(handle.ref).bufferId());
- state.incExtraUsedBytes(sizeof(EntryT) * array_size);
+ state.stats().inc_extra_used_bytes(sizeof(EntryT) * array_size);
return handle.ref;
}
@@ -145,38 +146,11 @@ ArrayStore<EntryT, RefT, TypeMapperT>::remove(EntryRef ref)
}
}
-namespace arraystore {
-
template <typename EntryT, typename RefT, typename TypeMapperT>
-class CompactionContext : public ICompactionContext {
-private:
- using ArrayStoreType = ArrayStore<EntryT, RefT, TypeMapperT>;
- ArrayStoreType &_store;
- std::unique_ptr<vespalib::datastore::CompactingBuffers> _compacting_buffers;
- EntryRefFilter _filter;
-
-public:
- CompactionContext(ArrayStoreType &store,
- std::unique_ptr<vespalib::datastore::CompactingBuffers> compacting_buffers)
- : _store(store),
- _compacting_buffers(std::move(compacting_buffers)),
- _filter(_compacting_buffers->make_entry_ref_filter())
- {
- }
- ~CompactionContext() override {
- _compacting_buffers->finish();
- }
- void compact(vespalib::ArrayRef<AtomicEntryRef> refs) override {
- 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));
- atomic_entry_ref.store_release(newRef);
- }
- }
- }
-};
-
+EntryRef
+ArrayStore<EntryT, RefT, TypeMapperT>::move_on_compact(EntryRef ref)
+{
+ return add(get(ref));
}
template <typename EntryT, typename RefT, typename TypeMapperT>
@@ -184,8 +158,7 @@ ICompactionContext::UP
ArrayStore<EntryT, RefT, TypeMapperT>::compactWorst(CompactionSpec compaction_spec, const CompactionStrategy &compaction_strategy)
{
auto compacting_buffers = _store.start_compact_worst_buffers(compaction_spec, compaction_strategy);
- return std::make_unique<arraystore::CompactionContext<EntryT, RefT, TypeMapperT>>
- (*this, std::move(compacting_buffers));
+ return std::make_unique<CompactionContext>(*this, std::move(compacting_buffers));
}
template <typename EntryT, typename RefT, typename TypeMapperT>
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp b/vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp
new file mode 100644
index 00000000000..8d97414626e
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "buffer_stats.h"
+#include <cassert>
+
+namespace vespalib::datastore {
+
+BufferStats::BufferStats()
+ : _alloc_elems(0),
+ _used_elems(0),
+ _hold_elems(0),
+ _dead_elems(0),
+ _extra_used_bytes(0),
+ _extra_hold_bytes(0)
+{
+}
+
+void
+BufferStats::add_to_mem_stats(size_t element_size, MemoryStats& stats) const
+{
+ size_t extra_used = extra_used_bytes();
+ stats._allocElems += capacity();
+ stats._usedElems += size();
+ stats._deadElems += dead_elems();
+ stats._holdElems += hold_elems();
+ stats._allocBytes += (capacity() * element_size) + extra_used;
+ stats._usedBytes += (size() * element_size) + extra_used;
+ stats._deadBytes += dead_elems() * element_size;
+ stats._holdBytes += (hold_elems() * element_size) + extra_hold_bytes();
+}
+
+InternalBufferStats::InternalBufferStats()
+ : BufferStats()
+{
+}
+
+void
+InternalBufferStats::clear()
+{
+ _alloc_elems.store(0, std::memory_order_relaxed);
+ _used_elems.store(0, std::memory_order_relaxed);
+ _hold_elems.store(0, std::memory_order_relaxed);
+ _dead_elems.store(0, std::memory_order_relaxed);
+ _extra_used_bytes.store(0, std::memory_order_relaxed);
+ _extra_hold_bytes.store(0, std::memory_order_relaxed);
+}
+
+void
+InternalBufferStats::dec_hold_elems(size_t value)
+{
+ ElemCount elems = hold_elems();
+ assert(elems >= value);
+ _hold_elems.store(elems - value, std::memory_order_relaxed);
+}
+
+}
+
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_stats.h b/vespalib/src/vespa/vespalib/datastore/buffer_stats.h
new file mode 100644
index 00000000000..66f8b532c41
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_stats.h
@@ -0,0 +1,76 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "buffer_type.h"
+#include "memory_stats.h"
+#include <atomic>
+
+namespace vespalib::datastore {
+
+/**
+ * Represents statistics for a given buffer in a data store.
+ */
+class BufferStats {
+protected:
+ // The number of elements that are allocated in the buffer.
+ std::atomic<ElemCount> _alloc_elems;
+ // The number of elements (of the allocated) that are used: _used_elems <= _alloc_elems.
+ std::atomic<ElemCount> _used_elems;
+ // The number of elements (of the used) that are on hold: _hold_elems <= _used_elems.
+ // "On hold" is a transitionary state used when removing elements.
+ std::atomic<ElemCount> _hold_elems;
+ // The number of elements (of the used) that are dead: _dead_elems <= _used_elems.
+ // A dead element was first on hold, and is now available for reuse in the free list (if enabled).
+ std::atomic<ElemCount> _dead_elems;
+
+ // Number of bytes that are heap allocated (and used) by elements that are stored in this buffer.
+ // For simple types this is always 0.
+ std::atomic<size_t> _extra_used_bytes;
+ // Number of bytes that are heap allocated (and used) by elements that are stored in this buffer and is now on hold.
+ // For simple types this is always 0.
+ std::atomic<size_t> _extra_hold_bytes;
+
+public:
+ BufferStats();
+
+ size_t size() const { return _used_elems.load(std::memory_order_relaxed); }
+ size_t capacity() const { return _alloc_elems.load(std::memory_order_relaxed); }
+ size_t remaining() const { return capacity() - size(); }
+
+ void pushed_back(size_t num_elems) {
+ _used_elems.store(size() + num_elems, std::memory_order_relaxed);
+ }
+
+ size_t dead_elems() const { return _dead_elems.load(std::memory_order_relaxed); }
+ size_t hold_elems() const { return _hold_elems.load(std::memory_order_relaxed); }
+ size_t extra_used_bytes() const { return _extra_used_bytes.load(std::memory_order_relaxed); }
+ size_t extra_hold_bytes() const { return _extra_hold_bytes.load(std::memory_order_relaxed); }
+
+ void inc_extra_used_bytes(size_t value) { _extra_used_bytes.store(extra_used_bytes() + value, std::memory_order_relaxed); }
+
+ void add_to_mem_stats(size_t element_size, MemoryStats& stats) const;
+};
+
+/**
+ * Provides low-level access to buffer stats for integration in BufferState.
+ */
+class InternalBufferStats : public BufferStats {
+public:
+ InternalBufferStats();
+ void clear();
+ void set_alloc_elems(size_t value) { _alloc_elems.store(value, std::memory_order_relaxed); }
+ void set_dead_elems(size_t value) { _dead_elems.store(value, std::memory_order_relaxed); }
+ void set_hold_elems(size_t value) { _hold_elems.store(value, std::memory_order_relaxed); }
+ void inc_dead_elems(size_t value) { _dead_elems.store(dead_elems() + value, std::memory_order_relaxed); }
+ void inc_hold_elems(size_t value) { _hold_elems.store(hold_elems() + value, std::memory_order_relaxed); }
+ void dec_hold_elems(size_t value);
+ void inc_extra_hold_bytes(size_t value) { _extra_hold_bytes.store(extra_hold_bytes() + value, std::memory_order_relaxed); }
+ std::atomic<ElemCount>& used_elems_ref() { return _used_elems; }
+ std::atomic<ElemCount>& dead_elems_ref() { return _dead_elems; }
+ std::atomic<size_t>& extra_used_bytes_ref() { return _extra_used_bytes; }
+ std::atomic<size_t>& extra_hold_bytes_ref() { return _extra_hold_bytes; }
+};
+
+}
+
diff --git a/vespalib/src/vespa/vespalib/datastore/bufferstate.cpp b/vespalib/src/vespa/vespalib/datastore/bufferstate.cpp
index d24d8336131..45a94693eeb 100644
--- a/vespalib/src/vespa/vespalib/datastore/bufferstate.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/bufferstate.cpp
@@ -11,13 +11,8 @@ using vespalib::alloc::MemoryAllocator;
namespace vespalib::datastore {
BufferState::BufferState()
- : _usedElems(0),
- _allocElems(0),
- _deadElems(0u),
- _holdElems(0u),
- _extraUsedBytes(0),
- _extraHoldBytes(0),
- _free_list(_deadElems),
+ : _stats(),
+ _free_list(_stats.dead_elems_ref()),
_typeHandler(nullptr),
_buffer(Alloc::alloc(0, MemoryAllocator::HUGEPAGE_SIZE)),
_arraySize(0),
@@ -33,14 +28,7 @@ BufferState::~BufferState()
assert(getState() == State::FREE);
assert(!_free_list.enabled());
assert(_free_list.empty());
- assert(_holdElems == 0);
-}
-
-void
-BufferState::decHoldElems(size_t value) {
- ElemCount hold_elems = getHoldElems();
- assert(hold_elems >= value);
- _holdElems.store(hold_elems - value, std::memory_order_relaxed);
+ assert(_stats.hold_elems() == 0);
}
namespace {
@@ -100,10 +88,10 @@ BufferState::onActive(uint32_t bufferId, uint32_t typeId,
assert(_typeHandler == nullptr);
assert(capacity() == 0);
assert(size() == 0);
- assert(getDeadElems() == 0u);
- assert(getHoldElems() == 0);
- assert(getExtraUsedBytes() == 0);
- assert(getExtraHoldBytes() == 0);
+ assert(_stats.dead_elems() == 0u);
+ assert(_stats.hold_elems() == 0);
+ assert(_stats.extra_used_bytes() == 0);
+ assert(_stats.extra_hold_bytes() == 0);
assert(_free_list.empty());
size_t reservedElements = typeHandler->getReservedElements(bufferId);
@@ -115,14 +103,15 @@ BufferState::onActive(uint32_t bufferId, uint32_t typeId,
_buffer.create(alloc.bytes).swap(_buffer);
assert(_buffer.get() != nullptr || alloc.elements == 0u);
buffer.store(_buffer.get(), std::memory_order_release);
- _allocElems.store(alloc.elements, std::memory_order_relaxed);
+ _stats.set_alloc_elems(alloc.elements);
_typeHandler.store(typeHandler, std::memory_order_release);
assert(typeId <= std::numeric_limits<uint16_t>::max());
_typeId = typeId;
_arraySize = typeHandler->getArraySize();
_free_list.set_array_size(_arraySize);
_state.store(State::ACTIVE, std::memory_order_release);
- typeHandler->onActive(bufferId, &_usedElems, &_deadElems, buffer.load(std::memory_order::relaxed));
+ typeHandler->onActive(bufferId, &_stats.used_elems_ref(), &_stats.dead_elems_ref(),
+ buffer.load(std::memory_order::relaxed));
}
void
@@ -132,11 +121,11 @@ BufferState::onHold(uint32_t buffer_id)
assert(getTypeHandler() != nullptr);
_state.store(State::HOLD, std::memory_order_release);
_compacting = false;
- assert(getDeadElems() <= size());
- assert(getHoldElems() <= (size() - getDeadElems()));
- _deadElems.store(0, std::memory_order_relaxed);
- _holdElems.store(size(), std::memory_order_relaxed); // Put everyting on hold
- getTypeHandler()->onHold(buffer_id, &_usedElems, &_deadElems);
+ assert(_stats.dead_elems() <= size());
+ assert(_stats.hold_elems() <= (size() - _stats.dead_elems()));
+ _stats.set_dead_elems(0);
+ _stats.set_hold_elems(size());
+ getTypeHandler()->onHold(buffer_id, &_stats.used_elems_ref(), &_stats.dead_elems_ref());
_free_list.disable();
}
@@ -146,18 +135,13 @@ BufferState::onFree(std::atomic<void*>& buffer)
assert(buffer.load(std::memory_order_relaxed) == _buffer.get());
assert(getState() == State::HOLD);
assert(_typeHandler != nullptr);
- assert(getDeadElems() <= size());
- assert(getHoldElems() == size() - getDeadElems());
+ assert(_stats.dead_elems() <= size());
+ assert(_stats.hold_elems() == (size() - _stats.dead_elems()));
getTypeHandler()->destroyElements(buffer, size());
Alloc::alloc().swap(_buffer);
getTypeHandler()->onFree(size());
buffer.store(nullptr, std::memory_order_release);
- _usedElems.store(0, std::memory_order_relaxed);
- _allocElems.store(0, std::memory_order_relaxed);
- _deadElems.store(0, std::memory_order_relaxed);
- _holdElems.store(0, std::memory_order_relaxed);
- _extraUsedBytes.store(0, std::memory_order_relaxed);
- _extraHoldBytes.store(0, std::memory_order_relaxed);
+ _stats.clear();
_state.store(State::FREE, std::memory_order_release);
_typeHandler = nullptr;
_arraySize = 0;
@@ -192,6 +176,36 @@ BufferState::disableElemHoldList()
_disableElemHoldList = true;
}
+bool
+BufferState::hold_elems(size_t num_elems, size_t extra_bytes)
+{
+ assert(isActive());
+ if (_disableElemHoldList) {
+ // The elements are directly marked as dead as they are not put on hold.
+ _stats.inc_dead_elems(num_elems);
+ return true;
+ }
+ _stats.inc_hold_elems(num_elems);
+ _stats.inc_extra_hold_bytes(extra_bytes);
+ return false;
+}
+
+void
+BufferState::free_elems(EntryRef ref, size_t num_elems, size_t ref_offset)
+{
+ if (isActive()) {
+ if (_free_list.enabled() && (num_elems == getArraySize())) {
+ _free_list.push_entry(ref);
+ }
+ } else {
+ assert(isOnHold());
+ }
+ _stats.inc_dead_elems(num_elems);
+ _stats.dec_hold_elems(num_elems);
+ getTypeHandler()->cleanHold(_buffer.get(), (ref_offset * _arraySize), num_elems,
+ BufferTypeBase::CleanContext(_stats.extra_used_bytes_ref(),
+ _stats.extra_hold_bytes_ref()));
+}
void
BufferState::fallbackResize(uint32_t bufferId,
@@ -211,13 +225,13 @@ BufferState::fallbackResize(uint32_t bufferId,
std::atomic_thread_fence(std::memory_order_release);
_buffer = std::move(newBuffer);
buffer.store(_buffer.get(), std::memory_order_release);
- _allocElems.store(alloc.elements, std::memory_order_relaxed);
+ _stats.set_alloc_elems(alloc.elements);
}
void
BufferState::resume_primary_buffer(uint32_t buffer_id)
{
- getTypeHandler()->resume_primary_buffer(buffer_id, &_usedElems, &_deadElems);
+ getTypeHandler()->resume_primary_buffer(buffer_id, &_stats.used_elems_ref(), &_stats.dead_elems_ref());
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/bufferstate.h b/vespalib/src/vespa/vespalib/datastore/bufferstate.h
index 8f32a93b487..3f023b41c51 100644
--- a/vespalib/src/vespa/vespalib/datastore/bufferstate.h
+++ b/vespalib/src/vespa/vespalib/datastore/bufferstate.h
@@ -3,6 +3,7 @@
#pragma once
#include "buffer_free_list.h"
+#include "buffer_stats.h"
#include "buffer_type.h"
#include "entryref.h"
#include <vespa/vespalib/util/generationhandler.h>
@@ -38,17 +39,7 @@ public:
};
private:
- std::atomic<ElemCount> _usedElems;
- std::atomic<ElemCount> _allocElems;
- std::atomic<ElemCount> _deadElems;
- std::atomic<ElemCount> _holdElems;
- // Number of bytes that are heap allocated by elements that are stored in this buffer.
- // For simple types this is 0.
- std::atomic<size_t> _extraUsedBytes;
- // Number of bytes that are heap allocated by elements that are stored in this buffer and is now on hold.
- // For simple types this is 0.
- std::atomic<size_t> _extraHoldBytes;
-
+ InternalBufferStats _stats;
BufferFreeList _free_list;
std::atomic<BufferTypeBase*> _typeHandler;
Alloc _buffer;
@@ -91,36 +82,38 @@ public:
*/
void onFree(std::atomic<void*>& buffer);
-
/**
- * Disable hold of elements, just mark then as dead without cleanup.
+ * Disable hold of elements, just mark elements as dead without cleanup.
* Typically used when tearing down data structure in a controlled manner.
*/
void disableElemHoldList();
- BufferFreeList& free_list() { return _free_list; }
- const BufferFreeList& free_list() const { return _free_list; }
+ /**
+ * Update stats to reflect that the given elements are put on hold.
+ * Returns true if element hold list is disabled for this buffer.
+ */
+ bool hold_elems(size_t num_elems, size_t extra_bytes);
- size_t size() const { return _usedElems.load(std::memory_order_relaxed); }
- size_t capacity() const { return _allocElems.load(std::memory_order_relaxed); }
- size_t remaining() const { return capacity() - size(); }
- void pushed_back(size_t numElems) {
- pushed_back(numElems, 0);
- }
- void pushed_back(size_t numElems, size_t extraBytes) {
- _usedElems.store(size() + numElems, std::memory_order_relaxed);
- _extraUsedBytes.store(getExtraUsedBytes() + extraBytes, std::memory_order_relaxed);
- }
- void cleanHold(void *buffer, size_t offset, ElemCount numElems) {
- getTypeHandler()->cleanHold(buffer, offset, numElems, BufferTypeBase::CleanContext(_extraUsedBytes, _extraHoldBytes));
- }
+ /**
+ * Free the given elements and update stats accordingly.
+ *
+ * The given entry ref is put on the free list (if enabled).
+ * Hold cleaning of elements is executed on the buffer type.
+ */
+ void free_elems(EntryRef ref, size_t num_elems, size_t ref_offset);
+
+ BufferStats& stats() { return _stats; }
+ const BufferStats& stats() const { return _stats; }
+
+ void enable_free_list(FreeList& type_free_list) { _free_list.enable(type_free_list); }
+ void disable_free_list() { _free_list.disable(); }
+
+ size_t size() const { return _stats.size(); }
+ size_t capacity() const { return _stats.capacity(); }
+ size_t remaining() const { return _stats.remaining(); }
void dropBuffer(uint32_t buffer_id, std::atomic<void*>& buffer);
uint32_t getTypeId() const { return _typeId; }
uint32_t getArraySize() const { return _arraySize; }
- size_t getDeadElems() const { return _deadElems.load(std::memory_order_relaxed); }
- size_t getHoldElems() const { return _holdElems.load(std::memory_order_relaxed); }
- size_t getExtraUsedBytes() const { return _extraUsedBytes.load(std::memory_order_relaxed); }
- size_t getExtraHoldBytes() const { return _extraHoldBytes.load(std::memory_order_relaxed); }
bool getCompacting() const { return _compacting; }
void setCompacting() { _compacting = true; }
uint32_t get_used_arrays() const noexcept { return size() / _arraySize; }
@@ -136,15 +129,6 @@ public:
const BufferTypeBase *getTypeHandler() const { return _typeHandler.load(std::memory_order_relaxed); }
BufferTypeBase *getTypeHandler() { return _typeHandler.load(std::memory_order_relaxed); }
- void incDeadElems(size_t value) { _deadElems.store(getDeadElems() + value, std::memory_order_relaxed); }
- void incHoldElems(size_t value) { _holdElems.store(getHoldElems() + value, std::memory_order_relaxed); }
- void decHoldElems(size_t value);
- void incExtraUsedBytes(size_t value) { _extraUsedBytes.store(getExtraUsedBytes() + value, std::memory_order_relaxed); }
- void incExtraHoldBytes(size_t value) {
- _extraHoldBytes.store(getExtraHoldBytes() + value, std::memory_order_relaxed);
- }
-
- bool hasDisabledElemHoldList() const { return _disableElemHoldList; }
void resume_primary_buffer(uint32_t buffer_id);
};
diff --git a/vespalib/src/vespa/vespalib/datastore/compact_buffer_candidates.cpp b/vespalib/src/vespa/vespalib/datastore/compact_buffer_candidates.cpp
index 41c216a9684..dd47a159e9b 100644
--- a/vespalib/src/vespa/vespalib/datastore/compact_buffer_candidates.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/compact_buffer_candidates.cpp
@@ -13,7 +13,7 @@ CompactBufferCandidates::CompactBufferCandidates(uint32_t num_buffers, uint32_t
_max_buffers(std::max(max_buffers, 1u)),
_active_buffers_ratio(std::min(1.0, std::max(0.0001, active_buffers_ratio))),
_ratio(ratio),
- _slack(slack),
+ _slack(_ratio == 0.0 ? 0u : slack),
_free_buffers(0)
{
_candidates.reserve(num_buffers);
diff --git a/vespalib/src/vespa/vespalib/datastore/compaction_context.cpp b/vespalib/src/vespa/vespalib/datastore/compaction_context.cpp
index 65e028119a2..1ce6401605e 100644
--- a/vespalib/src/vespa/vespalib/datastore/compaction_context.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/compaction_context.cpp
@@ -25,7 +25,7 @@ CompactionContext::compact(vespalib::ArrayRef<AtomicEntryRef> refs)
for (auto &atomic_entry_ref : refs) {
auto ref = atomic_entry_ref.load_relaxed();
if (ref.valid() && _filter.has(ref)) {
- EntryRef newRef = _store.move(ref);
+ EntryRef newRef = _store.move_on_compact(ref);
atomic_entry_ref.store_release(newRef);
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/compaction_strategy.cpp b/vespalib/src/vespa/vespalib/datastore/compaction_strategy.cpp
index 2dbd501f78e..4eb4ff16864 100644
--- a/vespalib/src/vespa/vespalib/datastore/compaction_strategy.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/compaction_strategy.cpp
@@ -5,6 +5,7 @@
#include <vespa/vespalib/util/memoryusage.h>
#include <vespa/vespalib/util/address_space.h>
#include <iostream>
+#include <limits>
namespace vespalib::datastore {
@@ -34,4 +35,10 @@ std::ostream& operator<<(std::ostream& os, const CompactionStrategy& compaction_
return os;
}
+CompactionStrategy
+CompactionStrategy::make_compact_all_active_buffers_strategy()
+{
+ return CompactionStrategy(0.0, 0.0, std::numeric_limits<uint32_t>::max(), 1.0);
+}
+
}
diff --git a/vespalib/src/vespa/vespalib/datastore/compaction_strategy.h b/vespalib/src/vespa/vespalib/datastore/compaction_strategy.h
index 2bcf30fc6fc..f78e123e5de 100644
--- a/vespalib/src/vespa/vespalib/datastore/compaction_strategy.h
+++ b/vespalib/src/vespa/vespalib/datastore/compaction_strategy.h
@@ -74,6 +74,7 @@ public:
bool should_compact_memory(const MemoryUsage& memory_usage) const;
bool should_compact_address_space(const AddressSpace& address_space) const;
CompactionSpec should_compact(const MemoryUsage& memory_usage, const AddressSpace& address_space) const;
+ static CompactionStrategy make_compact_all_active_buffers_strategy();
};
std::ostream& operator<<(std::ostream& os, const CompactionStrategy& compaction_strategy);
diff --git a/vespalib/src/vespa/vespalib/datastore/datastore.cpp b/vespalib/src/vespa/vespalib/datastore/datastore.cpp
index 15686a9f79d..76d622f3704 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastore.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/datastore.cpp
@@ -1,7 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "datastore.hpp"
-#include <vespa/vespalib/util/array.hpp>
#include <vespa/vespalib/util/rcuvector.hpp>
namespace vespalib::datastore {
@@ -10,7 +9,6 @@ template class DataStoreT<EntryRefT<22> >;
}
-template void vespalib::Array<vespalib::datastore::DataStoreBase::ElemHold1ListElem>::increase(size_t);
template class vespalib::RcuVector<vespalib::datastore::EntryRef>;
template class vespalib::RcuVectorBase<vespalib::datastore::EntryRef>;
template class vespalib::RcuVector<vespalib::datastore::AtomicEntryRef>;
diff --git a/vespalib/src/vespa/vespalib/datastore/datastore.h b/vespalib/src/vespa/vespalib/datastore/datastore.h
index 86c39b547d3..95f47e98ef5 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastore.h
+++ b/vespalib/src/vespa/vespalib/datastore/datastore.h
@@ -27,7 +27,7 @@ template <typename RefT = EntryRefT<22> >
class DataStoreT : public DataStoreBase
{
private:
- void free_elem_internal(EntryRef ref, size_t numElems, bool was_held);
+ void free_elem_internal(EntryRef ref, size_t numElems);
public:
typedef RefT RefType;
@@ -38,22 +38,6 @@ public:
~DataStoreT() override;
/**
- * Increase number of dead elements in buffer.
- *
- * @param ref Reference to dead stored features
- * @param dead Number of newly dead elements
- */
- void incDead(EntryRef ref, size_t deadElems) {
- RefType intRef(ref);
- DataStoreBase::incDead(intRef.bufferId(), deadElems);
- }
-
- /**
- * Free element(s).
- */
- void freeElem(EntryRef ref, size_t numElems) { free_elem_internal(ref, numElems, false); }
-
- /**
* Hold element(s).
*/
void holdElem(EntryRef ref, size_t numElems) {
@@ -61,14 +45,9 @@ public:
}
void holdElem(EntryRef ref, size_t numElems, size_t extraBytes);
- /**
- * Trim elem hold list, freeing elements that no longer needs to be held.
- *
- * @param usedGen lowest generation that is still used.
- */
- void trimElemHoldList(generation_t usedGen) override;
+ void reclaim_entry_refs(generation_t oldest_used_gen) override;
- void clearElemHoldList() override;
+ void reclaim_all_entry_refs() override;
bool getCompacting(EntryRef ref) const {
return getBufferState(RefType(ref).bufferId()).getCompacting();
@@ -97,7 +76,6 @@ class DataStore : public DataStoreT<RefT>
protected:
typedef DataStoreT<RefT> ParentType;
using ParentType::ensureBufferCapacity;
- using ParentType::_primary_buffer_ids;
using ParentType::getEntry;
using ParentType::dropBuffers;
using ParentType::init_primary_buffers;
diff --git a/vespalib/src/vespa/vespalib/datastore/datastore.hpp b/vespalib/src/vespa/vespalib/datastore/datastore.hpp
index 5b8df719915..bfb63954875 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastore.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/datastore.hpp
@@ -7,7 +7,7 @@
#include "free_list_allocator.hpp"
#include "free_list_raw_allocator.hpp"
#include "raw_allocator.hpp"
-#include <vespa/vespalib/util/array.hpp>
+#include <vespa/vespalib/util/generation_hold_list.hpp>
namespace vespalib::datastore {
@@ -22,23 +22,11 @@ DataStoreT<RefT>::~DataStoreT() = default;
template <typename RefT>
void
-DataStoreT<RefT>::free_elem_internal(EntryRef ref, size_t numElems, bool was_held)
+DataStoreT<RefT>::free_elem_internal(EntryRef ref, size_t numElems)
{
RefType intRef(ref);
BufferState &state = getBufferState(intRef.bufferId());
- if (state.isActive()) {
- if (state.free_list().enabled() && (numElems == state.getArraySize())) {
- state.free_list().push_entry(ref);
- }
- } else {
- assert(state.isOnHold() && was_held);
- }
- state.incDeadElems(numElems);
- if (was_held) {
- state.decHoldElems(numElems);
- }
- state.cleanHold(getBuffer(intRef.bufferId()),
- intRef.offset() * state.getArraySize(), numElems);
+ state.free_elems(ref, numElems, intRef.offset());
}
template <typename RefT>
@@ -47,48 +35,27 @@ DataStoreT<RefT>::holdElem(EntryRef ref, size_t numElems, size_t extraBytes)
{
RefType intRef(ref);
BufferState &state = getBufferState(intRef.bufferId());
- assert(state.isActive());
- if (state.hasDisabledElemHoldList()) {
- state.incDeadElems(numElems);
- return;
+ if (!state.hold_elems(numElems, extraBytes)) {
+ _entry_ref_hold_list.insert({ref, numElems});
}
- _elemHold1List.push_back(ElemHold1ListElem(ref, numElems));
- state.incHoldElems(numElems);
- state.incExtraHoldBytes(extraBytes);
}
template <typename RefT>
void
-DataStoreT<RefT>::trimElemHoldList(generation_t usedGen)
+DataStoreT<RefT>::reclaim_entry_refs(generation_t oldest_used_gen)
{
- ElemHold2List &elemHold2List = _elemHold2List;
-
- ElemHold2List::iterator it(elemHold2List.begin());
- ElemHold2List::iterator ite(elemHold2List.end());
- uint32_t freed = 0;
- for (; it != ite; ++it) {
- if (static_cast<sgeneration_t>(it->_generation - usedGen) >= 0)
- break;
- free_elem_internal(it->_ref, it->_len, true);
- ++freed;
- }
- if (freed != 0) {
- elemHold2List.erase(elemHold2List.begin(), it);
- }
+ _entry_ref_hold_list.reclaim(oldest_used_gen, [this](const auto& elem) {
+ free_elem_internal(elem.ref, elem.num_elems);
+ });
}
template <typename RefT>
void
-DataStoreT<RefT>::clearElemHoldList()
+DataStoreT<RefT>::reclaim_all_entry_refs()
{
- ElemHold2List &elemHold2List = _elemHold2List;
-
- ElemHold2List::iterator it(elemHold2List.begin());
- ElemHold2List::iterator ite(elemHold2List.end());
- for (; it != ite; ++it) {
- free_elem_internal(it->_ref, it->_len, true);
- }
- elemHold2List.clear();
+ _entry_ref_hold_list.reclaim_all([this](const auto& elem) {
+ free_elem_internal(elem.ref, elem.num_elems);
+ });
}
template <typename RefT>
diff --git a/vespalib/src/vespa/vespalib/datastore/datastorebase.cpp b/vespalib/src/vespa/vespalib/datastore/datastorebase.cpp
index 67749ee913d..a14082e2d5c 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastorebase.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/datastorebase.cpp
@@ -5,7 +5,7 @@
#include "compacting_buffers.h"
#include "compaction_spec.h"
#include "compaction_strategy.h"
-#include <vespa/vespalib/util/array.hpp>
+#include <vespa/vespalib/util/generation_hold_list.hpp>
#include <vespa/vespalib/util/stringfmt.h>
#include <algorithm>
#include <limits>
@@ -16,6 +16,10 @@ LOG_SETUP(".vespalib.datastore.datastorebase");
using vespalib::GenerationHeldBase;
+namespace vespalib {
+template class GenerationHoldList<datastore::DataStoreBase::EntryRefHoldElem, false, true>;
+}
+
namespace vespalib::datastore {
namespace {
@@ -36,7 +40,7 @@ constexpr size_t TOO_DEAD_SLACK = 0x4000u;
bool
primary_buffer_too_dead(const BufferState &state)
{
- size_t deadElems = state.getDeadElems();
+ size_t deadElems = state.stats().dead_elems();
size_t deadBytes = deadElems * state.getArraySize();
return ((deadBytes >= TOO_DEAD_SLACK) && (deadElems * 2 >= state.size()));
}
@@ -88,8 +92,7 @@ DataStoreBase::DataStoreBase(uint32_t numBuffers, uint32_t offset_bits, size_t m
_free_lists(),
_freeListsEnabled(false),
_initializing(false),
- _elemHold1List(),
- _elemHold2List(),
+ _entry_ref_hold_list(),
_numBuffers(numBuffers),
_offset_bits(offset_bits),
_hold_buffer_count(0u),
@@ -102,9 +105,6 @@ DataStoreBase::DataStoreBase(uint32_t numBuffers, uint32_t offset_bits, size_t m
DataStoreBase::~DataStoreBase()
{
disableFreeLists();
-
- assert(_elemHold1List.empty());
- assert(_elemHold2List.empty());
}
void
@@ -221,22 +221,10 @@ DataStoreBase::addType(BufferTypeBase *typeHandler)
}
void
-DataStoreBase::transferElemHoldList(generation_t generation)
-{
- ElemHold2List &elemHold2List = _elemHold2List;
- for (const ElemHold1ListElem & elemHold1 : _elemHold1List) {
- elemHold2List.push_back(ElemHold2ListElem(elemHold1, generation));
- }
- _elemHold1List.clear();
-}
-
-void
-DataStoreBase::transferHoldLists(generation_t generation)
+DataStoreBase::assign_generation(generation_t current_gen)
{
- _genHolder.transferHoldLists(generation);
- if (hasElemHold1()) {
- transferElemHoldList(generation);
- }
+ _genHolder.assign_generation(current_gen);
+ _entry_ref_hold_list.assign_generation(current_gen);
}
void
@@ -248,18 +236,18 @@ DataStoreBase::doneHoldBuffer(uint32_t bufferId)
}
void
-DataStoreBase::trimHoldLists(generation_t usedGen)
+DataStoreBase::reclaim_memory(generation_t oldest_used_gen)
{
- trimElemHoldList(usedGen); // Trim entries before trimming buffers
- _genHolder.trimHoldLists(usedGen);
+ reclaim_entry_refs(oldest_used_gen); // Trim entries before trimming buffers
+ _genHolder.reclaim(oldest_used_gen);
}
void
-DataStoreBase::clearHoldLists()
+DataStoreBase::reclaim_all_memory()
{
- transferElemHoldList(0);
- clearElemHoldList();
- _genHolder.clearHoldLists();
+ _entry_ref_hold_list.assign_generation(0);
+ reclaim_all_entry_refs();
+ _genHolder.reclaim_all();
}
void
@@ -269,13 +257,13 @@ DataStoreBase::dropBuffers()
for (uint32_t bufferId = 0; bufferId < numBuffers; ++bufferId) {
_states[bufferId].dropBuffer(bufferId, _buffers[bufferId].get_atomic_buffer());
}
- _genHolder.clearHoldLists();
+ _genHolder.reclaim_all();
}
vespalib::MemoryUsage
DataStoreBase::getMemoryUsage() const
{
- MemStats stats = getMemStats();
+ auto stats = getMemStats();
vespalib::MemoryUsage usage;
usage.setAllocatedBytes(stats._allocBytes);
usage.setUsedBytes(stats._usedBytes);
@@ -289,18 +277,18 @@ DataStoreBase::holdBuffer(uint32_t bufferId)
{
_states[bufferId].onHold(bufferId);
size_t holdBytes = 0u; // getMemStats() still accounts held buffers
- GenerationHeldBase::UP hold(new BufferHold(holdBytes, *this, bufferId));
- _genHolder.hold(std::move(hold));
+ auto hold = std::make_unique<BufferHold>(holdBytes, *this, bufferId);
+ _genHolder.insert(std::move(hold));
}
void
DataStoreBase::enableFreeLists()
{
- for (BufferState & bState : _states) {
+ for (auto& bState : _states) {
if (!bState.isActive() || bState.getCompacting()) {
continue;
}
- bState.free_list().enable(_free_lists[bState.getTypeId()]);
+ bState.enable_free_list(_free_lists[bState.getTypeId()]);
}
_freeListsEnabled = true;
}
@@ -308,8 +296,8 @@ DataStoreBase::enableFreeLists()
void
DataStoreBase::disableFreeLists()
{
- for (BufferState & bState : _states) {
- bState.free_list().disable();
+ for (auto& bState : _states) {
+ bState.disable_free_list();
}
_freeListsEnabled = false;
}
@@ -321,17 +309,11 @@ DataStoreBase::enableFreeList(uint32_t bufferId)
if (_freeListsEnabled &&
state.isActive() &&
!state.getCompacting()) {
- state.free_list().enable(_free_lists[state.getTypeId()]);
+ state.enable_free_list(_free_lists[state.getTypeId()]);
}
}
void
-DataStoreBase::disableFreeList(uint32_t bufferId)
-{
- _states[bufferId].free_list().disable();
-}
-
-void
DataStoreBase::disableElemHoldList()
{
for (auto &state : _states) {
@@ -341,47 +323,29 @@ DataStoreBase::disableElemHoldList()
}
}
-namespace {
-
-void
-add_buffer_state_to_mem_stats(const BufferState& state, size_t elementSize, DataStoreBase::MemStats& stats)
-{
- size_t extra_used_bytes = state.getExtraUsedBytes();
- stats._allocElems += state.capacity();
- stats._usedElems += state.size();
- stats._deadElems += state.getDeadElems();
- stats._holdElems += state.getHoldElems();
- stats._allocBytes += (state.capacity() * elementSize) + extra_used_bytes;
- stats._usedBytes += (state.size() * elementSize) + extra_used_bytes;
- stats._deadBytes += state.getDeadElems() * elementSize;
- stats._holdBytes += (state.getHoldElems() * elementSize) + state.getExtraHoldBytes();
-}
-
-}
-
-DataStoreBase::MemStats
+MemoryStats
DataStoreBase::getMemStats() const
{
- MemStats stats;
+ MemoryStats stats;
- for (const BufferState & bState: _states) {
+ for (const auto& bState: _states) {
auto typeHandler = bState.getTypeHandler();
- BufferState::State state = bState.getState();
+ auto state = bState.getState();
if ((state == BufferState::State::FREE) || (typeHandler == nullptr)) {
++stats._freeBuffers;
} else if (state == BufferState::State::ACTIVE) {
size_t elementSize = typeHandler->elementSize();
++stats._activeBuffers;
- add_buffer_state_to_mem_stats(bState, elementSize, stats);
+ bState.stats().add_to_mem_stats(elementSize, stats);
} else if (state == BufferState::State::HOLD) {
size_t elementSize = typeHandler->elementSize();
++stats._holdBuffers;
- add_buffer_state_to_mem_stats(bState, elementSize, stats);
+ bState.stats().add_to_mem_stats(elementSize, stats);
} else {
LOG_ABORT("should not be reached");
}
}
- size_t genHolderHeldBytes = _genHolder.getHeldBytes();
+ size_t genHolderHeldBytes = _genHolder.get_held_bytes();
stats._holdBytes += genHolderHeldBytes;
stats._allocBytes += genHolderHeldBytes;
stats._usedBytes += genHolderHeldBytes;
@@ -394,11 +358,11 @@ DataStoreBase::getAddressSpaceUsage() const
size_t usedArrays = 0;
size_t deadArrays = 0;
size_t limitArrays = 0;
- for (const BufferState & bState: _states) {
+ for (const auto& bState: _states) {
if (bState.isActive()) {
uint32_t arraySize = bState.getArraySize();
usedArrays += bState.size() / arraySize;
- deadArrays += bState.getDeadElems() / arraySize;
+ deadArrays += bState.stats().dead_elems() / arraySize;
limitArrays += bState.capacity() / arraySize;
} else if (bState.isOnHold()) {
uint32_t arraySize = bState.getArraySize();
@@ -410,7 +374,7 @@ DataStoreBase::getAddressSpaceUsage() const
LOG_ABORT("should not be reached");
}
}
- return vespalib::AddressSpace(usedArrays, deadArrays, limitArrays);
+ return {usedArrays, deadArrays, limitArrays};
}
void
@@ -427,26 +391,6 @@ DataStoreBase::onActive(uint32_t bufferId, uint32_t typeId, size_t elemsNeeded)
enableFreeList(bufferId);
}
-std::vector<uint32_t>
-DataStoreBase::startCompact(uint32_t typeId)
-{
- std::vector<uint32_t> toHold;
-
- for (uint32_t bufferId = 0; bufferId < _numBuffers; ++bufferId) {
- BufferState &state = getBufferState(bufferId);
- if (state.isActive() &&
- state.getTypeId() == typeId &&
- !state.getCompacting()) {
- state.setCompacting();
- toHold.push_back(bufferId);
- disableFreeList(bufferId);
- }
- }
- switch_primary_buffer(typeId, 0u);
- inc_compaction_count();
- return toHold;
-}
-
void
DataStoreBase::finishCompact(const std::vector<uint32_t> &toHold)
{
@@ -467,52 +411,14 @@ DataStoreBase::fallbackResize(uint32_t bufferId, size_t elemsNeeded)
state.fallbackResize(bufferId, elemsNeeded,
_buffers[bufferId].get_atomic_buffer(),
toHoldBuffer);
- GenerationHeldBase::UP
- hold(new FallbackHold(oldAllocElems * elementSize,
- std::move(toHoldBuffer),
- oldUsedElems,
- state.getTypeHandler(),
- state.getTypeId()));
+ auto hold = std::make_unique<FallbackHold>(oldAllocElems * elementSize,
+ std::move(toHoldBuffer),
+ oldUsedElems,
+ state.getTypeHandler(),
+ state.getTypeId());
if (!_initializing) {
- _genHolder.hold(std::move(hold));
- }
-}
-
-uint32_t
-DataStoreBase::startCompactWorstBuffer(uint32_t typeId)
-{
- uint32_t buffer_id = get_primary_buffer_id(typeId);
- const BufferTypeBase *typeHandler = _typeHandlers[typeId];
- assert(typeHandler->get_active_buffers_count() >= 1u);
- if (typeHandler->get_active_buffers_count() == 1u) {
- // Single active buffer for type, no need for scan
- markCompacting(buffer_id);
- return buffer_id;
- }
- // Multiple active buffers for type, must perform full scan
- return startCompactWorstBuffer(buffer_id,
- [=](const BufferState &state) { return state.isActive(typeId); });
-}
-
-template <typename BufferStateActiveFilter>
-uint32_t
-DataStoreBase::startCompactWorstBuffer(uint32_t initWorstBufferId, BufferStateActiveFilter &&filterFunc)
-{
- uint32_t worstBufferId = initWorstBufferId;
- size_t worstDeadElems = 0;
- for (uint32_t bufferId = 0; bufferId < _numBuffers; ++bufferId) {
- const auto &state = getBufferState(bufferId);
- if (filterFunc(state)) {
- assert(!state.getCompacting());
- size_t deadElems = state.getDeadElems() - state.getTypeHandler()->getReservedElements(bufferId);
- if (deadElems > worstDeadElems) {
- worstBufferId = bufferId;
- worstDeadElems = deadElems;
- }
- }
+ _genHolder.insert(std::move(hold));
}
- markCompacting(worstBufferId);
- return worstBufferId;
}
void
@@ -527,7 +433,7 @@ DataStoreBase::markCompacting(uint32_t bufferId)
assert(!state.getCompacting());
state.setCompacting();
state.disableElemHoldList();
- state.free_list().disable();
+ state.disable_free_list();
inc_compaction_count();
}
@@ -535,9 +441,15 @@ std::unique_ptr<CompactingBuffers>
DataStoreBase::start_compact_worst_buffers(CompactionSpec compaction_spec, const CompactionStrategy& compaction_strategy)
{
// compact memory usage
- CompactBufferCandidates elem_buffers(_numBuffers, compaction_strategy.get_max_buffers(), compaction_strategy.get_active_buffers_ratio(), compaction_strategy.getMaxDeadBytesRatio() / 2, CompactionStrategy::DEAD_BYTES_SLACK);
+ CompactBufferCandidates elem_buffers(_numBuffers, compaction_strategy.get_max_buffers(),
+ compaction_strategy.get_active_buffers_ratio(),
+ compaction_strategy.getMaxDeadBytesRatio() / 2,
+ CompactionStrategy::DEAD_BYTES_SLACK);
// compact address space
- CompactBufferCandidates array_buffers(_numBuffers, compaction_strategy.get_max_buffers(), compaction_strategy.get_active_buffers_ratio(), compaction_strategy.getMaxDeadAddressSpaceRatio() / 2, CompactionStrategy::DEAD_ADDRESS_SPACE_SLACK);
+ CompactBufferCandidates array_buffers(_numBuffers, compaction_strategy.get_max_buffers(),
+ compaction_strategy.get_active_buffers_ratio(),
+ compaction_strategy.getMaxDeadAddressSpaceRatio() / 2,
+ CompactionStrategy::DEAD_ADDRESS_SPACE_SLACK);
uint32_t free_buffers = 0;
for (uint32_t bufferId = 0; bufferId < _numBuffers; ++bufferId) {
const auto &state = getBufferState(bufferId);
@@ -546,7 +458,7 @@ DataStoreBase::start_compact_worst_buffers(CompactionSpec compaction_spec, const
uint32_t arraySize = typeHandler->getArraySize();
uint32_t reservedElements = typeHandler->getReservedElements(bufferId);
size_t used_elems = state.size();
- size_t deadElems = state.getDeadElems() - reservedElements;
+ size_t deadElems = state.stats().dead_elems() - reservedElements;
if (compaction_spec.compact_memory()) {
elem_buffers.add(bufferId, used_elems, deadElems);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/datastorebase.h b/vespalib/src/vespa/vespalib/datastore/datastorebase.h
index d83b3a84847..598f0872253 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastorebase.h
+++ b/vespalib/src/vespa/vespalib/datastore/datastorebase.h
@@ -4,12 +4,14 @@
#include "bufferstate.h"
#include "free_list.h"
+#include "memory_stats.h"
#include <vespa/vespalib/util/address_space.h>
#include <vespa/vespalib/util/generationholder.h>
+#include <vespa/vespalib/util/generation_hold_list.h>
#include <vespa/vespalib/util/memoryusage.h>
-#include <vector>
-#include <deque>
#include <atomic>
+#include <deque>
+#include <vector>
namespace vespalib::datastore {
@@ -24,25 +26,19 @@ class CompactionStrategy;
*/
class DataStoreBase
{
-public:
- /**
- * Hold list before freeze, before knowing how long elements must be held.
- */
- class ElemHold1ListElem
- {
- public:
- EntryRef _ref;
- size_t _len; // Aligned length
-
- ElemHold1ListElem(EntryRef ref, size_t len)
- : _ref(ref),
- _len(len)
- { }
+protected:
+ struct EntryRefHoldElem {
+ EntryRef ref;
+ size_t num_elems;
+
+ EntryRefHoldElem(EntryRef ref_in, size_t num_elems_in)
+ : ref(ref_in),
+ num_elems(num_elems_in)
+ {}
};
-protected:
+ using EntryRefHoldList = GenerationHoldList<EntryRefHoldElem, false, true>;
using generation_t = vespalib::GenerationHandler::generation_t;
- using sgeneration_t = vespalib::GenerationHandler::sgeneration_t;
private:
class BufferAndTypeId {
@@ -59,32 +55,16 @@ private:
uint32_t _typeId;
};
std::vector<BufferAndTypeId> _buffers; // For fast mapping with known types
-protected:
+
// Provides a mapping from typeId -> primary buffer for that type.
// The primary buffer is used for allocations of new element(s) if no available slots are found in free lists.
std::vector<uint32_t> _primary_buffer_ids;
+protected:
void* getBuffer(uint32_t bufferId) { return _buffers[bufferId].get_buffer_relaxed(); }
/**
- * Hold list at freeze, when knowing how long elements must be held
- */
- class ElemHold2ListElem : public ElemHold1ListElem
- {
- public:
- generation_t _generation;
-
- ElemHold2ListElem(const ElemHold1ListElem &hold1, generation_t generation)
- : ElemHold1ListElem(hold1),
- _generation(generation)
- { }
- };
-
- using ElemHold1List = vespalib::Array<ElemHold1ListElem>;
- using ElemHold2List = std::deque<ElemHold2ListElem>;
-
- /**
- * Class used to hold the old buffer as part of fallbackResize().
+ * Class used to hold the entire old buffer as part of fallbackResize().
*/
class FallbackHold : public vespalib::GenerationHeldBase
{
@@ -102,52 +82,6 @@ protected:
class BufferHold;
-public:
- class MemStats
- {
- public:
- size_t _allocElems;
- size_t _usedElems;
- size_t _deadElems;
- size_t _holdElems;
- size_t _allocBytes;
- size_t _usedBytes;
- size_t _deadBytes;
- size_t _holdBytes;
- uint32_t _freeBuffers;
- uint32_t _activeBuffers;
- uint32_t _holdBuffers;
-
- MemStats()
- : _allocElems(0),
- _usedElems(0),
- _deadElems(0),
- _holdElems(0),
- _allocBytes(0),
- _usedBytes(0),
- _deadBytes(0),
- _holdBytes(0),
- _freeBuffers(0),
- _activeBuffers(0),
- _holdBuffers(0)
- { }
-
- MemStats& operator+=(const MemStats &rhs) {
- _allocElems += rhs._allocElems;
- _usedElems += rhs._usedElems;
- _deadElems += rhs._deadElems;
- _holdElems += rhs._holdElems;
- _allocBytes += rhs._allocBytes;
- _usedBytes += rhs._usedBytes;
- _deadBytes += rhs._deadBytes;
- _holdBytes += rhs._holdBytes;
- _freeBuffers += rhs._freeBuffers;
- _activeBuffers += rhs._activeBuffers;
- _holdBuffers += rhs._holdBuffers;
- return *this;
- }
- };
-
private:
std::vector<BufferState> _states;
protected:
@@ -156,10 +90,7 @@ protected:
std::vector<FreeList> _free_lists;
bool _freeListsEnabled;
bool _initializing;
-
- ElemHold1List _elemHold1List;
- ElemHold2List _elemHold2List;
-
+ EntryRefHoldList _entry_ref_hold_list;
const uint32_t _numBuffers;
const uint32_t _offset_bits;
uint32_t _hold_buffer_count;
@@ -174,6 +105,7 @@ protected:
virtual ~DataStoreBase();
+private:
/**
* Get the next buffer id after the given buffer id.
*/
@@ -183,6 +115,7 @@ protected:
ret = 0;
return ret;
}
+protected:
/**
* Get the primary buffer for the given type id.
@@ -194,15 +127,14 @@ protected:
/**
* Trim elem hold list, freeing elements that no longer needs to be held.
*
- * @param usedGen lowest generation that is still used.
+ * @param oldest_used_gen the oldest generation that is still used.
*/
- virtual void trimElemHoldList(generation_t usedGen) = 0;
+ virtual void reclaim_entry_refs(generation_t oldest_used_gen) = 0;
- virtual void clearElemHoldList() = 0;
+ virtual void reclaim_all_entry_refs() = 0;
- template <typename BufferStateActiveFilter>
- uint32_t startCompactWorstBuffer(uint32_t initWorstBufferId, BufferStateActiveFilter &&filterFunc);
void markCompacting(uint32_t bufferId);
+
public:
uint32_t addType(BufferTypeBase *typeHandler);
void init_primary_buffers();
@@ -238,9 +170,11 @@ public:
*/
void switch_primary_buffer(uint32_t typeId, size_t elemsNeeded);
+private:
bool consider_grow_active_buffer(uint32_t type_id, size_t elems_needed);
void switch_or_grow_primary_buffer(uint32_t typeId, size_t elemsNeeded);
+public:
vespalib::MemoryUsage getMemoryUsage() const;
vespalib::AddressSpace getAddressSpaceUsage() const;
@@ -252,31 +186,28 @@ public:
const BufferState &getBufferState(uint32_t bufferId) const { return _states[bufferId]; }
BufferState &getBufferState(uint32_t bufferId) { return _states[bufferId]; }
uint32_t getNumBuffers() const { return _numBuffers; }
- bool hasElemHold1() const { return !_elemHold1List.empty(); }
-
- /**
- * Transfer element holds from hold1 list to hold2 list.
- */
- void transferElemHoldList(generation_t generation);
+public:
/**
- * Transfer holds from hold1 to hold2 lists, assigning generation.
+ * Assign generation on data elements on hold lists added since the last time this function was called.
*/
- void transferHoldLists(generation_t generation);
+ void assign_generation(generation_t current_gen);
+private:
/**
* Hold of buffer has ended.
*/
void doneHoldBuffer(uint32_t bufferId);
+public:
/**
- * Trim hold lists, freeing buffers that no longer needs to be held.
+ * Reclaim memory from hold lists, freeing buffers and entry refs that no longer needs to be held.
*
- * @param usedGen lowest generation that is still used.
+ * @param oldest_used_gen oldest generation that is still used.
*/
- void trimHoldLists(generation_t usedGen);
+ void reclaim_memory(generation_t oldest_used_gen);
- void clearHoldLists();
+ void reclaim_all_memory();
template <typename EntryType, typename RefType>
EntryType *getEntry(RefType ref) {
@@ -300,12 +231,6 @@ public:
void dropBuffers();
-
- void incDead(uint32_t bufferId, size_t deadElems) {
- BufferState &state = _states[bufferId];
- state.incDeadElems(deadElems);
- }
-
/**
* Enable free list management.
* This only works for fixed size elements.
@@ -317,16 +242,14 @@ public:
*/
void disableFreeLists();
+private:
/**
* Enable free list management.
* This only works for fixed size elements.
*/
void enableFreeList(uint32_t bufferId);
- /**
- * Disable free list management.
- */
- void disableFreeList(uint32_t bufferId);
+public:
void disableElemHoldList();
bool has_free_lists_enabled() const { return _freeListsEnabled; }
@@ -341,7 +264,7 @@ public:
/**
* Returns aggregated memory statistics for all buffers in this data store.
*/
- MemStats getMemStats() const;
+ MemoryStats getMemStats() const;
/**
* Assume that no readers are present while data structure is being initialized.
@@ -359,16 +282,18 @@ private:
void onActive(uint32_t bufferId, uint32_t typeId, size_t elemsNeeded);
void inc_hold_buffer_count();
+
public:
uint32_t getTypeId(uint32_t bufferId) const {
return _buffers[bufferId].getTypeId();
}
- std::vector<uint32_t> startCompact(uint32_t typeId);
-
void finishCompact(const std::vector<uint32_t> &toHold);
+
+private:
void fallbackResize(uint32_t bufferId, size_t elementsNeeded);
+public:
vespalib::GenerationHolder &getGenerationHolder() {
return _genHolder;
}
@@ -378,7 +303,6 @@ public:
return self._genHolder;
}
- uint32_t startCompactWorstBuffer(uint32_t typeId);
std::unique_ptr<CompactingBuffers> start_compact_worst_buffers(CompactionSpec compaction_spec, const CompactionStrategy &compaction_strategy);
uint64_t get_compaction_count() const { return _compaction_count.load(std::memory_order_relaxed); }
void inc_compaction_count() const { ++_compaction_count; }
@@ -386,3 +310,7 @@ public:
};
}
+
+namespace vespalib {
+extern template class GenerationHoldList<datastore::DataStoreBase::EntryRefHoldElem, false, true>;
+}
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 6f001ce3c94..47c64722785 100644
--- a/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.cpp
@@ -5,10 +5,15 @@
#include "entry_ref_filter.h"
#include "i_compactable.h"
#include <vespa/vespalib/util/array.hpp>
+#include <vespa/vespalib/util/generation_hold_list.hpp>
#include <vespa/vespalib/util/memoryusage.h>
#include <cassert>
#include <stdexcept>
+namespace vespalib {
+template class GenerationHoldList<uint32_t, false, true>;
+}
+
namespace vespalib::datastore {
FixedSizeHashMap::Node::Node(Node&&)
@@ -30,8 +35,7 @@ FixedSizeHashMap::FixedSizeHashMap(uint32_t modulo, uint32_t capacity, uint32_t
_free_head(no_node_idx),
_free_count(0u),
_hold_count(0u),
- _hold_1_list(),
- _hold_2_list(),
+ _hold_list(),
_num_shards(num_shards)
{
_nodes.reserve(capacity);
@@ -49,7 +53,10 @@ FixedSizeHashMap::FixedSizeHashMap(uint32_t modulo, uint32_t capacity, uint32_t
}
}
-FixedSizeHashMap::~FixedSizeHashMap() = default;
+FixedSizeHashMap::~FixedSizeHashMap()
+{
+ _hold_list.reclaim_all();
+}
void
FixedSizeHashMap::force_add(const EntryComparator& comp, const KvType& kv)
@@ -96,37 +103,6 @@ FixedSizeHashMap::add(const ShardedHashComparator & comp, std::function<EntryRef
return _nodes[node_idx].get_kv();
}
-void
-FixedSizeHashMap::transfer_hold_lists_slow(generation_t generation)
-{
- auto &hold_2_list = _hold_2_list;
- for (uint32_t node_idx : _hold_1_list) {
- hold_2_list.push_back(std::make_pair(generation, node_idx));
- }
- _hold_1_list.clear();
-
-}
-
-
-void
-FixedSizeHashMap::trim_hold_lists_slow(generation_t first_used)
-{
- while (!_hold_2_list.empty()) {
- auto& first = _hold_2_list.front();
- if (static_cast<sgeneration_t>(first.first - first_used) >= 0) {
- break;
- }
- uint32_t node_idx = first.second;
- auto& node = _nodes[node_idx];
- node.get_next_node_idx().store(_free_head, std::memory_order_relaxed);
- _free_head = node_idx;
- ++_free_count;
- --_hold_count;
- node.on_free();
- _hold_2_list.erase(_hold_2_list.begin());
- }
-}
-
FixedSizeHashMap::KvType*
FixedSizeHashMap::remove(const ShardedHashComparator & comp)
{
@@ -145,7 +121,7 @@ FixedSizeHashMap::remove(const ShardedHashComparator & comp)
}
--_count;
++_hold_count;
- _hold_1_list.push_back(node_idx);
+ _hold_list.insert(node_idx);
return &_nodes[node_idx].get_kv();
}
prev_node_idx = node_idx;
@@ -154,6 +130,19 @@ FixedSizeHashMap::remove(const ShardedHashComparator & comp)
return nullptr;
}
+void
+FixedSizeHashMap::reclaim_memory(generation_t oldest_used_gen)
+{
+ _hold_list.reclaim(oldest_used_gen, [this](uint32_t node_idx) {
+ auto& node = _nodes[node_idx];
+ node.get_next_node_idx().store(_free_head, std::memory_order_relaxed);
+ _free_head = node_idx;
+ ++_free_count;
+ --_hold_count;
+ node.on_free();
+ });
+}
+
MemoryUsage
FixedSizeHashMap::get_memory_usage() const
{
@@ -183,7 +172,7 @@ FixedSizeHashMap::foreach_key(const std::function<void(EntryRef)>& callback) con
}
void
-FixedSizeHashMap::move_keys(ICompactable& compactable, const EntryRefFilter &compacting_buffers)
+FixedSizeHashMap::move_keys_on_compact(ICompactable& compactable, const EntryRefFilter &compacting_buffers)
{
for (auto& chain_head : _chain_heads) {
uint32_t node_idx = chain_head.load_relaxed();
@@ -192,7 +181,7 @@ FixedSizeHashMap::move_keys(ICompactable& compactable, const EntryRefFilter &com
EntryRef old_ref = node.get_kv().first.load_relaxed();
assert(old_ref.valid());
if (compacting_buffers.has(old_ref)) {
- EntryRef new_ref = compactable.move(old_ref);
+ EntryRef new_ref = compactable.move_on_compact(old_ref);
node.get_kv().first.store_release(new_ref);
}
node_idx = node.get_next_node_idx().load(std::memory_order_relaxed);
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 c522bcc3c33..1e13f206adb 100644
--- a/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.h
+++ b/vespalib/src/vespa/vespalib/datastore/fixed_size_hash_map.h
@@ -6,11 +6,12 @@
#include "entry_comparator.h"
#include <vespa/vespalib/util/array.h>
#include <vespa/vespalib/util/arrayref.h>
+#include <vespa/vespalib/util/generation_hold_list.h>
#include <vespa/vespalib/util/generationhandler.h>
-#include <limits>
#include <atomic>
#include <deque>
#include <functional>
+#include <limits>
namespace vespalib {
class GenerationHolder;
@@ -56,8 +57,8 @@ private:
* A reader must own an appropriate GenerationHandler::Guard to ensure
* that memory is held while it can be accessed by reader.
*
- * The writer must update generation and call transfer_hold_lists and
- * trim_hold_lists as needed to free up memory no longer needed by any
+ * The writer must update generation and call assign_generation and
+ * reclaim_memory as needed to free up memory no longer needed by any
* readers.
*/
class FixedSizeHashMap {
@@ -65,7 +66,6 @@ public:
static constexpr uint32_t no_node_idx = std::numeric_limits<uint32_t>::max();
using KvType = std::pair<AtomicEntryRef, AtomicEntryRef>;
using generation_t = GenerationHandler::generation_t;
- using sgeneration_t = GenerationHandler::sgeneration_t;
private:
class ChainHead {
std::atomic<uint32_t> _node_idx;
@@ -103,6 +103,8 @@ private:
const KvType& get_kv() const noexcept { return _kv; }
};
+ using NodeIdxHoldList = GenerationHoldList<uint32_t, false, true>;
+
Array<ChainHead> _chain_heads;
Array<Node> _nodes;
uint32_t _modulo;
@@ -110,12 +112,9 @@ private:
uint32_t _free_head;
uint32_t _free_count;
uint32_t _hold_count;
- Array<uint32_t> _hold_1_list;
- std::deque<std::pair<generation_t, uint32_t>> _hold_2_list;
+ NodeIdxHoldList _hold_list;
uint32_t _num_shards;
- void transfer_hold_lists_slow(generation_t generation);
- void trim_hold_lists_slow(generation_t first_used);
void force_add(const EntryComparator& comp, const KvType& kv);
public:
FixedSizeHashMap(uint32_t module, uint32_t capacity, uint32_t num_shards);
@@ -143,23 +142,17 @@ public:
return nullptr;
}
- void transfer_hold_lists(generation_t generation) {
- if (!_hold_1_list.empty()) {
- transfer_hold_lists_slow(generation);
- }
+ void assign_generation(generation_t current_gen) {
+ _hold_list.assign_generation(current_gen);
}
- void trim_hold_lists(generation_t first_used) {
- if (!_hold_2_list.empty() && static_cast<sgeneration_t>(_hold_2_list.front().first - first_used) < 0) {
- trim_hold_lists_slow(first_used);
- }
- }
+ void reclaim_memory(generation_t oldest_used_gen);
bool full() const noexcept { return _nodes.size() == _nodes.capacity() && _free_count == 0u; }
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 EntryRefFilter &compacting_buffers);
+ void move_keys_on_compact(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
@@ -182,3 +175,7 @@ public:
};
}
+
+namespace vespalib {
+extern template class GenerationHoldList<uint32_t, false, true>;
+}
diff --git a/vespalib/src/vespa/vespalib/datastore/free_list.cpp b/vespalib/src/vespa/vespalib/datastore/free_list.cpp
index 6c96e51241c..44e49b68df9 100644
--- a/vespalib/src/vespa/vespalib/datastore/free_list.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/free_list.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "free_list.h"
+#include <algorithm>
#include <cassert>
namespace vespalib::datastore {
diff --git a/vespalib/src/vespa/vespalib/datastore/i_compactable.h b/vespalib/src/vespa/vespalib/datastore/i_compactable.h
index 069d32bb481..31c082e4371 100644
--- a/vespalib/src/vespa/vespalib/datastore/i_compactable.h
+++ b/vespalib/src/vespa/vespalib/datastore/i_compactable.h
@@ -8,12 +8,13 @@ namespace vespalib::datastore {
* Interface for moving an entry as part of compaction of data in old
* buffers into new buffers.
*
- * Old entry is unchanged and not placed on any hold lists since we
- * expect the old buffers to be freed soon anyway.
+ * A copy of the old entry is created and a reference to the new copy is
+ * returned. The old entry is unchanged and not placed on any hold
+ * lists since we expect the old buffers to be freed soon anyway.
*/
struct ICompactable {
virtual ~ICompactable() = default;
- virtual EntryRef move(EntryRef ref) = 0;
+ virtual EntryRef move_on_compact(EntryRef ref) = 0;
};
}
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 bb105d41519..5a75a30d182 100644
--- a/vespalib/src/vespa/vespalib/datastore/i_unique_store_dictionary.h
+++ b/vespalib/src/vespa/vespalib/datastore/i_unique_store_dictionary.h
@@ -25,12 +25,12 @@ public:
using generation_t = vespalib::GenerationHandler::generation_t;
virtual ~IUniqueStoreDictionary() = default;
virtual void freeze() = 0;
- virtual void transfer_hold_lists(generation_t generation) = 0;
- virtual void trim_hold_lists(generation_t firstUsed) = 0;
+ virtual void assign_generation(generation_t current_gen) = 0;
+ virtual void reclaim_memory(generation_t oldest_used_gen) = 0;
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 EntryRefFilter& compacting_buffers) = 0;
+ virtual void move_keys_on_compact(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/memory_stats.cpp b/vespalib/src/vespa/vespalib/datastore/memory_stats.cpp
new file mode 100644
index 00000000000..8e060b4cfb4
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/datastore/memory_stats.cpp
@@ -0,0 +1,40 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "memory_stats.h"
+
+namespace vespalib::datastore {
+
+MemoryStats::MemoryStats()
+ : _allocElems(0),
+ _usedElems(0),
+ _deadElems(0),
+ _holdElems(0),
+ _allocBytes(0),
+ _usedBytes(0),
+ _deadBytes(0),
+ _holdBytes(0),
+ _freeBuffers(0),
+ _activeBuffers(0),
+ _holdBuffers(0)
+{
+}
+
+MemoryStats&
+MemoryStats::operator+=(const MemoryStats& rhs)
+{
+ _allocElems += rhs._allocElems;
+ _usedElems += rhs._usedElems;
+ _deadElems += rhs._deadElems;
+ _holdElems += rhs._holdElems;
+ _allocBytes += rhs._allocBytes;
+ _usedBytes += rhs._usedBytes;
+ _deadBytes += rhs._deadBytes;
+ _holdBytes += rhs._holdBytes;
+ _freeBuffers += rhs._freeBuffers;
+ _activeBuffers += rhs._activeBuffers;
+ _holdBuffers += rhs._holdBuffers;
+ return *this;
+}
+
+}
+
diff --git a/vespalib/src/vespa/vespalib/datastore/memory_stats.h b/vespalib/src/vespa/vespalib/datastore/memory_stats.h
new file mode 100644
index 00000000000..18d7dd77559
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/datastore/memory_stats.h
@@ -0,0 +1,32 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+namespace vespalib::datastore {
+
+/**
+ * Represents aggregated memory statistics for all buffers in a data store.
+ */
+class MemoryStats
+{
+public:
+ size_t _allocElems;
+ size_t _usedElems;
+ size_t _deadElems;
+ size_t _holdElems;
+ size_t _allocBytes;
+ size_t _usedBytes;
+ size_t _deadBytes;
+ size_t _holdBytes;
+ uint32_t _freeBuffers;
+ uint32_t _activeBuffers;
+ uint32_t _holdBuffers;
+
+ MemoryStats();
+ MemoryStats& operator+=(const MemoryStats& rhs);
+};
+
+}
diff --git a/vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp b/vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp
index 0d67bf71c20..7395ef68a73 100644
--- a/vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp
@@ -28,7 +28,7 @@ RawAllocator<EntryT, RefT>::alloc(size_t numElems, size_t extraElems)
assert((numElems % arraySize) == 0u);
RefT ref((oldBufferSize / arraySize), buffer_id);
EntryT *buffer = _store.getEntryArray<EntryT>(ref, arraySize);
- state.pushed_back(numElems);
+ state.stats().pushed_back(numElems);
return HandleType(ref, buffer);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp b/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp
index 2ae22084472..a28c3071646 100644
--- a/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.cpp
@@ -32,7 +32,7 @@ ShardedHashMap::ShardedHashMap(std::unique_ptr<const EntryComparator> comp)
ShardedHashMap::~ShardedHashMap()
{
- _gen_holder.clearHoldLists();
+ _gen_holder.reclaim_all();
for (size_t i = 0; i < num_shards; ++i) {
auto map = _maps[i].load(std::memory_order_relaxed);
delete map;
@@ -58,7 +58,7 @@ ShardedHashMap::hold_shard(std::unique_ptr<const FixedSizeHashMap> map)
{
auto usage = map->get_memory_usage();
auto hold = std::make_unique<ShardedHashMapShardHeld>(usage.allocatedBytes(), std::move(map));
- _gen_holder.hold(std::move(hold));
+ _gen_holder.insert(std::move(hold));
}
ShardedHashMap::KvType&
@@ -107,27 +107,27 @@ ShardedHashMap::find(const EntryComparator& comp, EntryRef key_ref) const
}
void
-ShardedHashMap::transfer_hold_lists(generation_t generation)
+ShardedHashMap::assign_generation(generation_t current_gen)
{
for (size_t i = 0; i < num_shards; ++i) {
auto map = _maps[i].load(std::memory_order_relaxed);
if (map != nullptr) {
- map->transfer_hold_lists(generation);
+ map->assign_generation(current_gen);
}
}
- _gen_holder.transferHoldLists(generation);
+ _gen_holder.assign_generation(current_gen);
}
void
-ShardedHashMap::trim_hold_lists(generation_t first_used)
+ShardedHashMap::reclaim_memory(generation_t oldest_used_gen)
{
for (size_t i = 0; i < num_shards; ++i) {
auto map = _maps[i].load(std::memory_order_relaxed);
if (map != nullptr) {
- map->trim_hold_lists(first_used);
+ map->reclaim_memory(oldest_used_gen);
}
}
- _gen_holder.trimHoldLists(first_used);
+ _gen_holder.reclaim(oldest_used_gen);
}
size_t
@@ -153,7 +153,7 @@ ShardedHashMap::get_memory_usage() const
memory_usage.merge(map->get_memory_usage());
}
}
- size_t gen_holder_held_bytes = _gen_holder.getHeldBytes();
+ size_t gen_holder_held_bytes = _gen_holder.get_held_bytes();
memory_usage.incAllocatedBytes(gen_holder_held_bytes);
memory_usage.incAllocatedBytesOnHold(gen_holder_held_bytes);
return memory_usage;
@@ -171,12 +171,12 @@ ShardedHashMap::foreach_key(std::function<void(EntryRef)> callback) const
}
void
-ShardedHashMap::move_keys(ICompactable& compactable, const EntryRefFilter& compacting_buffers)
+ShardedHashMap::move_keys_on_compact(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);
+ map->move_keys_on_compact(compactable, compacting_buffers);
}
}
}
@@ -222,7 +222,7 @@ ShardedHashMap::foreach_value(std::function<void(const std::vector<EntryRef>&)>
bool
ShardedHashMap::has_held_buffers() const
{
- return _gen_holder.getHeldBytes() != 0;
+ return _gen_holder.get_held_bytes() != 0;
}
void
diff --git a/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h b/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h
index e0ba9488351..572a8790828 100644
--- a/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h
+++ b/vespalib/src/vespa/vespalib/datastore/sharded_hash_map.h
@@ -28,8 +28,8 @@ struct ICompactable;
* A reader must own an appropriate GenerationHandler::Guard to ensure
* that memory is held while it can be accessed by reader.
*
- * The writer must update generation and call transfer_hold_lists and
- * trim_hold_lists as needed to free up memory no longer needed by any
+ * The writer must update generation and call assign_generation and
+ * reclaim_memory as needed to free up memory no longer needed by any
* readers.
*/
class ShardedHashMap {
@@ -52,13 +52,13 @@ public:
KvType* remove(const EntryComparator& comp, EntryRef key_ref);
KvType* find(const EntryComparator& comp, EntryRef key_ref);
const KvType* find(const EntryComparator& comp, EntryRef key_ref) const;
- void transfer_hold_lists(generation_t generation);
- void trim_hold_lists(generation_t first_used);
+ void assign_generation(generation_t current_gen);
+ void reclaim_memory(generation_t oldest_used_gen);
size_t size() const noexcept;
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 EntryRefFilter& compacting_buffers);
+ void move_keys_on_compact(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);
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store.h b/vespalib/src/vespa/vespalib/datastore/unique_store.h
index e7c374985a7..1313d57fbab 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store.h
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store.h
@@ -70,8 +70,8 @@ public:
inline const DataStoreType& get_data_store() const noexcept { return _allocator.get_data_store(); }
// Pass on hold list management to underlying store
- void transferHoldLists(generation_t generation);
- void trimHoldLists(generation_t firstUsed);
+ void assign_generation(generation_t current_gen);
+ void reclaim_memory(generation_t oldest_used_gen);
vespalib::GenerationHolder &getGenerationHolder() { return _store.getGenerationHolder(); }
void setInitializing(bool initializing) { _store.setInitializing(initializing); }
void freeze();
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
index 37a56bf2561..b8493017020 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
@@ -109,20 +109,20 @@ private:
}
}
- EntryRef move(EntryRef oldRef) override {
+ EntryRef move_on_compact(EntryRef oldRef) override {
RefT iRef(oldRef);
uint32_t buffer_id = iRef.bufferId();
auto &inner_mapping = _mapping[buffer_id];
assert(iRef.offset() < inner_mapping.size());
EntryRef &mappedRef = inner_mapping[iRef.offset()];
assert(!mappedRef.valid());
- EntryRef newRef = _store.move(oldRef);
+ EntryRef newRef = _store.move_on_compact(oldRef);
mappedRef = newRef;
return newRef;
}
void fillMapping() {
- _dict.move_keys(*this, _filter);
+ _dict.move_keys_on_compact(*this, _filter);
}
public:
@@ -190,18 +190,18 @@ UniqueStore<EntryT, RefT, Compare, Allocator>::bufferState(EntryRef ref) const
template <typename EntryT, typename RefT, typename Compare, typename Allocator>
void
-UniqueStore<EntryT, RefT, Compare, Allocator>::transferHoldLists(generation_t generation)
+UniqueStore<EntryT, RefT, Compare, Allocator>::assign_generation(generation_t current_gen)
{
- _dict->transfer_hold_lists(generation);
- _store.transferHoldLists(generation);
+ _dict->assign_generation(current_gen);
+ _store.assign_generation(current_gen);
}
template <typename EntryT, typename RefT, typename Compare, typename Allocator>
void
-UniqueStore<EntryT, RefT, Compare, Allocator>::trimHoldLists(generation_t firstUsed)
+UniqueStore<EntryT, RefT, Compare, Allocator>::reclaim_memory(generation_t oldest_used_gen)
{
- _dict->trim_hold_lists(firstUsed);
- _store.trimHoldLists(firstUsed);
+ _dict->reclaim_memory(oldest_used_gen);
+ _store.reclaim_memory(oldest_used_gen);
}
template <typename EntryT, typename RefT, typename Compare, typename Allocator>
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.h b/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.h
index 04df88ab4b9..0f6d9ddfc9b 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.h
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.h
@@ -35,7 +35,7 @@ public:
~UniqueStoreAllocator() override;
EntryRef allocate(const EntryType& value);
void hold(EntryRef ref);
- EntryRef move(EntryRef ref) override;
+ EntryRef move_on_compact(EntryRef ref) override;
const WrappedEntryType& get_wrapped(EntryRef ref) const {
RefType iRef(ref);
return *_store.template getEntry<WrappedEntryType>(iRef);
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp
index 04a229d4ffa..8ad11b18218 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp
@@ -28,7 +28,7 @@ UniqueStoreAllocator<EntryT, RefT>::UniqueStoreAllocator(std::shared_ptr<alloc::
template <typename EntryT, typename RefT>
UniqueStoreAllocator<EntryT, RefT>::~UniqueStoreAllocator()
{
- _store.clearHoldLists();
+ _store.reclaim_all_memory();
_store.dropBuffers();
}
@@ -48,7 +48,7 @@ UniqueStoreAllocator<EntryT, RefT>::hold(EntryRef ref)
template <typename EntryT, typename RefT>
EntryRef
-UniqueStoreAllocator<EntryT, RefT>::move(EntryRef ref)
+UniqueStoreAllocator<EntryT, RefT>::move_on_compact(EntryRef ref)
{
return _store.template allocator<WrappedEntryType>(0).alloc(get_wrapped(ref)).ref;
}
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h b/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h
index 702bae38e7c..8c5f284bb14 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.h
@@ -74,12 +74,12 @@ public:
UniqueStoreDictionary(std::unique_ptr<EntryComparator> compare);
~UniqueStoreDictionary() override;
void freeze() override;
- void transfer_hold_lists(generation_t generation) override;
- void trim_hold_lists(generation_t firstUsed) override;
+ void assign_generation(generation_t current_gen) override;
+ void reclaim_memory(generation_t oldest_used_gen) override;
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 EntryRefFilter& compacting_buffers) override;
+ void move_keys_on_compact(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 8029b66309d..6708b4c1448 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_dictionary.hpp
@@ -41,25 +41,25 @@ UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::freeze()
template <typename BTreeDictionaryT, typename ParentT, typename HashDictionaryT>
void
-UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::transfer_hold_lists(generation_t generation)
+UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::assign_generation(generation_t current_gen)
{
if constexpr (has_btree_dictionary) {
- this->_btree_dict.getAllocator().transferHoldLists(generation);
+ this->_btree_dict.getAllocator().assign_generation(current_gen);
}
if constexpr (has_hash_dictionary) {
- this->_hash_dict.transfer_hold_lists(generation);
+ this->_hash_dict.assign_generation(current_gen);
}
}
template <typename BTreeDictionaryT, typename ParentT, typename HashDictionaryT>
void
-UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::trim_hold_lists(generation_t firstUsed)
+UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::reclaim_memory(generation_t oldest_used_gen)
{
if constexpr (has_btree_dictionary) {
- this->_btree_dict.getAllocator().trimHoldLists(firstUsed);
+ this->_btree_dict.getAllocator().reclaim_memory(oldest_used_gen);
}
if constexpr (has_hash_dictionary) {
- this->_hash_dict.trim_hold_lists(firstUsed);
+ this->_hash_dict.reclaim_memory(oldest_used_gen);
}
}
@@ -140,7 +140,7 @@ UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::remove(const
template <typename BTreeDictionaryT, typename ParentT, typename HashDictionaryT>
void
-UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::move_keys(ICompactable &compactable, const EntryRefFilter& compacting_buffers)
+UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::move_keys_on_compact(ICompactable &compactable, const EntryRefFilter& compacting_buffers)
{
if constexpr (has_btree_dictionary) {
auto itr = this->_btree_dict.begin();
@@ -148,7 +148,7 @@ UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::move_keys(ICo
EntryRef oldRef(itr.getKey().load_relaxed());
assert(oldRef.valid());
if (compacting_buffers.has(oldRef)) {
- EntryRef newRef(compactable.move(oldRef));
+ EntryRef newRef(compactable.move_on_compact(oldRef));
this->_btree_dict.thaw(itr);
itr.writeKey(AtomicEntryRef(newRef));
if constexpr (has_hash_dictionary) {
@@ -160,7 +160,7 @@ UniqueStoreDictionary<BTreeDictionaryT, ParentT, HashDictionaryT>::move_keys(ICo
++itr;
}
} else {
- this->_hash_dict.move_keys(compactable, compacting_buffers);
+ this->_hash_dict.move_keys_on_compact(compactable, compacting_buffers);
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h
index be5fa8f6c1e..8977fd1cce8 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h
@@ -111,7 +111,7 @@ public:
~UniqueStoreStringAllocator() override;
EntryRef allocate(const char *value);
void hold(EntryRef ref);
- EntryRef move(EntryRef ref) override;
+ EntryRef move_on_compact(EntryRef ref) override;
const UniqueStoreEntryBase& get_wrapped(EntryRef ref) const {
RefType iRef(ref);
auto &state = _store.getBufferState(iRef.bufferId());
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp
index 71ea16bcde2..65cab4850ba 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp
@@ -30,7 +30,7 @@ UniqueStoreStringAllocator<RefT>::UniqueStoreStringAllocator(std::shared_ptr<all
template <typename RefT>
UniqueStoreStringAllocator<RefT>::~UniqueStoreStringAllocator()
{
- _store.clearHoldLists();
+ _store.reclaim_all_memory();
_store.dropBuffers();
}
@@ -49,7 +49,7 @@ UniqueStoreStringAllocator<RefT>::allocate(const char *value)
auto handle = _store.template freeListAllocator<WrappedExternalEntryType, UniqueStoreEntryReclaimer<WrappedExternalEntryType>>(0).alloc(std::string(value));
RefT iRef(handle.ref);
auto &state = _store.getBufferState(iRef.bufferId());
- state.incExtraUsedBytes(value_len + 1);
+ state.stats().inc_extra_used_bytes(value_len + 1);
return handle.ref;
}
}
@@ -71,7 +71,7 @@ UniqueStoreStringAllocator<RefT>::hold(EntryRef ref)
template <typename RefT>
EntryRef
-UniqueStoreStringAllocator<RefT>::move(EntryRef ref)
+UniqueStoreStringAllocator<RefT>::move_on_compact(EntryRef ref)
{
RefT iRef(ref);
uint32_t type_id = _store.getTypeId(iRef.bufferId());
@@ -87,7 +87,7 @@ UniqueStoreStringAllocator<RefT>::move(EntryRef ref)
auto handle = _store.template allocator<WrappedExternalEntryType>(0).alloc(*_store.template getEntry<WrappedExternalEntryType>(iRef));
auto &state = _store.getBufferState(RefT(handle.ref).bufferId());
auto &value = static_cast<const WrappedExternalEntryType *>(handle.data)->value();
- state.incExtraUsedBytes(value.size() + 1);
+ state.stats().inc_extra_used_bytes(value.size() + 1);
return handle.ref;
}
}
diff --git a/vespalib/src/vespa/vespalib/util/generation_hold_list.h b/vespalib/src/vespa/vespalib/util/generation_hold_list.h
new file mode 100644
index 00000000000..bdb58afb504
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/util/generation_hold_list.h
@@ -0,0 +1,106 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "generationhandler.h"
+#include <atomic>
+#include <deque>
+#include <vector>
+
+namespace vespalib {
+
+/**
+ * Class used to hold data elements until they can be safely reclaimed when they are no longer accessed by readers.
+ *
+ * This class must be used in accordance with a GenerationHandler.
+ */
+template <typename T, bool track_bytes_held, bool use_deque>
+class GenerationHoldList {
+private:
+ using generation_t = vespalib::GenerationHandler::generation_t;
+
+ struct ElemWithGen {
+ T elem;
+ generation_t gen;
+ ElemWithGen(T elem_in, generation_t gen_in)
+ : elem(std::move(elem_in)),
+ gen(gen_in)
+ {}
+ size_t byte_size() const {
+ if constexpr (track_bytes_held) {
+ return elem->byte_size();
+ }
+ return 0;
+ }
+ };
+
+ struct NoopFunc { void operator()(const T&){} };
+
+ using ElemList = std::vector<T>;
+ using ElemWithGenList = std::conditional_t<use_deque,
+ std::deque<ElemWithGen>,
+ std::vector<ElemWithGen>>;
+
+ ElemList _phase_1_list;
+ ElemWithGenList _phase_2_list;
+ std::atomic<size_t> _held_bytes;
+
+ /**
+ * Transfer elements from phase 1 to phase 2 list, assigning the current generation.
+ */
+ void assign_generation_internal(generation_t current_gen);
+
+ template<typename Func>
+ void reclaim_internal(generation_t oldest_used_gen, Func callback);
+
+public:
+ GenerationHoldList();
+ ~GenerationHoldList();
+
+ /**
+ * Insert the given data element on this hold list.
+ */
+ void insert(T data);
+
+ /**
+ * Assign the current generation to all data elements inserted on the hold list
+ * since the last time this function was called.
+ */
+ void assign_generation(generation_t current_gen) {
+ if (!_phase_1_list.empty()) {
+ assign_generation_internal(current_gen);
+ }
+ }
+
+ /**
+ * Reclaim all data elements where the assigned generation < oldest used generation.
+ * The callback function is called for each data element reclaimed.
+ **/
+ template<typename Func>
+ void reclaim(generation_t oldest_used_gen, Func callback) {
+ if (!_phase_2_list.empty() && (_phase_2_list.front().gen < oldest_used_gen)) {
+ reclaim_internal(oldest_used_gen, callback);
+ }
+ }
+
+ void reclaim(generation_t oldest_used_gen) {
+ reclaim(oldest_used_gen, NoopFunc());
+ }
+
+ /**
+ * Reclaim all data elements from this hold list.
+ */
+ void reclaim_all();
+
+ /**
+ * Reclaim all data elements from this hold list.
+ * The callback function is called for all data elements reclaimed.
+ */
+ template<typename Func>
+ void reclaim_all(Func callback);
+
+ size_t get_held_bytes() const { return _held_bytes.load(std::memory_order_relaxed); }
+
+};
+
+}
diff --git a/vespalib/src/vespa/vespalib/util/generation_hold_list.hpp b/vespalib/src/vespa/vespalib/util/generation_hold_list.hpp
new file mode 100644
index 00000000000..4855d1c651d
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/util/generation_hold_list.hpp
@@ -0,0 +1,88 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "generation_hold_list.h"
+#include <cassert>
+
+namespace vespalib {
+
+template <typename T, bool track_bytes_held, bool use_deque>
+void
+GenerationHoldList<T, track_bytes_held, use_deque>::assign_generation_internal(generation_t current_gen)
+{
+ for (auto& elem : _phase_1_list) {
+ _phase_2_list.push_back(ElemWithGen(std::move(elem), current_gen));
+ }
+ _phase_1_list.clear();
+}
+
+template <typename T, bool track_bytes_held, bool use_deque>
+template <typename Func>
+void
+GenerationHoldList<T, track_bytes_held, use_deque>::reclaim_internal(generation_t oldest_used_gen, Func func)
+{
+ auto itr = _phase_2_list.begin();
+ auto ite = _phase_2_list.end();
+ for (; itr != ite; ++itr) {
+ if (itr->gen >= oldest_used_gen) {
+ break;
+ }
+ const auto& elem = itr->elem;
+ func(elem);
+ if constexpr (track_bytes_held) {
+ _held_bytes.store(get_held_bytes() - itr->byte_size(), std::memory_order_relaxed);
+ }
+ }
+ if (itr != _phase_2_list.begin()) {
+ _phase_2_list.erase(_phase_2_list.begin(), itr);
+ }
+}
+
+template <typename T, bool track_bytes_held, bool use_deque>
+GenerationHoldList<T, track_bytes_held, use_deque>::GenerationHoldList()
+ : _phase_1_list(),
+ _phase_2_list(),
+ _held_bytes()
+{
+}
+
+template <typename T, bool track_bytes_held, bool use_deque>
+GenerationHoldList<T, track_bytes_held, use_deque>::~GenerationHoldList()
+{
+ assert(_phase_1_list.empty());
+ assert(_phase_2_list.empty());
+ assert(get_held_bytes() == 0);
+}
+
+template <typename T, bool track_bytes_held, bool use_deque>
+void
+GenerationHoldList<T, track_bytes_held, use_deque>::insert(T data)
+{
+ _phase_1_list.push_back(std::move(data));
+ if constexpr (track_bytes_held) {
+ _held_bytes.store(get_held_bytes() + _phase_1_list.back()->byte_size(), std::memory_order_relaxed);
+ }
+}
+
+template <typename T, bool track_bytes_held, bool use_deque>
+void
+GenerationHoldList<T, track_bytes_held, use_deque>::reclaim_all()
+{
+ _phase_1_list.clear();
+ _phase_2_list.clear();
+ _held_bytes = 0;
+}
+
+template <typename T, bool track_bytes_held, bool use_deque>
+template <typename Func>
+void
+GenerationHoldList<T, track_bytes_held, use_deque>::reclaim_all(Func func)
+{
+ for (const auto& elem_with_gen : _phase_2_list) {
+ func(elem_with_gen.elem);
+ }
+ reclaim_all();
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/util/generationhandler.cpp b/vespalib/src/vespa/vespalib/util/generationhandler.cpp
index d1cc0271068..3562926d88d 100644
--- a/vespalib/src/vespa/vespalib/util/generationhandler.cpp
+++ b/vespalib/src/vespa/vespalib/util/generationhandler.cpp
@@ -111,7 +111,7 @@ GenerationHandler::Guard::operator=(Guard &&rhs)
}
void
-GenerationHandler::updateFirstUsedGeneration()
+GenerationHandler::update_oldest_used_generation()
{
for (;;) {
if (_first == _last.load(std::memory_order_relaxed))
@@ -125,12 +125,12 @@ GenerationHandler::updateFirstUsedGeneration()
toFree->_next = _free;
_free = toFree;
}
- _firstUsedGeneration.store(_first->_generation, std::memory_order_relaxed);
+ _oldest_used_generation.store(_first->_generation, std::memory_order_relaxed);
}
GenerationHandler::GenerationHandler()
: _generation(0),
- _firstUsedGeneration(0),
+ _oldest_used_generation(0),
_last(nullptr),
_first(nullptr),
_free(nullptr),
@@ -144,7 +144,7 @@ GenerationHandler::GenerationHandler()
GenerationHandler::~GenerationHandler(void)
{
- updateFirstUsedGeneration();
+ update_oldest_used_generation();
assert(_first == _last.load(std::memory_order_relaxed));
while (_free != nullptr) {
GenerationHold *toFree = _free;
@@ -190,7 +190,7 @@ GenerationHandler::incGeneration()
// reader
set_generation(ngen);
last->_generation.store(ngen, std::memory_order_relaxed);
- updateFirstUsedGeneration();
+ update_oldest_used_generation();
return;
}
GenerationHold *nhold = nullptr;
@@ -207,7 +207,7 @@ GenerationHandler::incGeneration()
last->_next = nhold;
set_generation(ngen);
_last.store(nhold, std::memory_order_release);
- updateFirstUsedGeneration();
+ update_oldest_used_generation();
}
uint32_t
@@ -215,7 +215,7 @@ GenerationHandler::getGenerationRefCount(generation_t gen) const
{
if (static_cast<sgeneration_t>(gen - getCurrentGeneration()) > 0)
return 0u;
- if (static_cast<sgeneration_t>(getFirstUsedGeneration() - gen) > 0)
+ if (static_cast<sgeneration_t>(get_oldest_used_generation() - gen) > 0)
return 0u;
for (GenerationHold *hold = _first; hold != nullptr; hold = hold->_next) {
if (hold->_generation.load(std::memory_order_relaxed) == gen)
diff --git a/vespalib/src/vespa/vespalib/util/generationhandler.h b/vespalib/src/vespa/vespalib/util/generationhandler.h
index 9637ad0e414..6ba71b7f5fb 100644
--- a/vespalib/src/vespa/vespalib/util/generationhandler.h
+++ b/vespalib/src/vespa/vespalib/util/generationhandler.h
@@ -73,7 +73,7 @@ public:
private:
std::atomic<generation_t> _generation;
- std::atomic<generation_t> _firstUsedGeneration;
+ std::atomic<generation_t> _oldest_used_generation;
std::atomic<GenerationHold *> _last; // Points to "current generation" entry
GenerationHold *_first; // Points to "firstUsedGeneration" entry
GenerationHold *_free; // List of free entries
@@ -101,17 +101,17 @@ public:
void incGeneration();
/**
- * Update first used generation.
+ * Update the oldest used generation.
* Should be called by the writer thread.
*/
- void updateFirstUsedGeneration();
+ void update_oldest_used_generation();
/**
- * Returns the first generation guarded by a reader. It might be too low
- * if writer hasn't updated first used generation after last reader left.
+ * Returns the oldest generation guarded by a reader.
+ * It might be too low if writer hasn't updated oldest used generation after last reader left.
*/
- generation_t getFirstUsedGeneration() const noexcept {
- return _firstUsedGeneration.load(std::memory_order_relaxed);
+ generation_t get_oldest_used_generation() const noexcept {
+ return _oldest_used_generation.load(std::memory_order_relaxed);
}
/**
diff --git a/vespalib/src/vespa/vespalib/util/generationholder.cpp b/vespalib/src/vespa/vespalib/util/generationholder.cpp
index 5bfa7d152ce..07bde82f007 100644
--- a/vespalib/src/vespa/vespalib/util/generationholder.cpp
+++ b/vespalib/src/vespa/vespalib/util/generationholder.cpp
@@ -1,66 +1,20 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "generationholder.h"
-#include <cassert>
+#include "generation_hold_list.hpp"
namespace vespalib {
-GenerationHeldBase::~GenerationHeldBase() = default;
-
-GenerationHolder::GenerationHolder()
- : _hold1List(),
- _hold2List(),
- _heldBytes(0)
-{ }
-
-GenerationHolder::~GenerationHolder()
-{
- assert(_hold1List.empty());
- assert(_hold2List.empty());
- assert(getHeldBytes() == 0);
-}
+template class GenerationHoldList<GenerationHeldBase::UP, true, false>;
-void
-GenerationHolder::hold(GenerationHeldBase::UP data)
-{
- _hold1List.push_back(GenerationHeldBase::SP(data.release()));
- _heldBytes.store(getHeldBytes() + _hold1List.back()->getSize(), std::memory_order_relaxed);
-}
+template void GenerationHolderParent::reclaim_internal
+ <GenerationHolderParent::NoopFunc>(generation_t oldest_used_gen, NoopFunc func);
-void
-GenerationHolder::transferHoldListsSlow(generation_t generation)
-{
- HoldList::iterator it(_hold1List.begin());
- HoldList::iterator ite(_hold1List.end());
- HoldList &hold2List = _hold2List;
- for (; it != ite; ++it) {
- assert((*it)->_generation == 0u);
- (*it)->_generation = generation;
- hold2List.push_back(*it);
- }
- _hold1List.clear();
-}
-
-void
-GenerationHolder::trimHoldListsSlow(generation_t usedGen)
-{
- for (;;) {
- if (_hold2List.empty())
- break;
- GenerationHeldBase &first = *_hold2List.front();
- if (static_cast<sgeneration_t>(first._generation - usedGen) >= 0)
- break;
- _heldBytes.store(getHeldBytes() - first.getSize(), std::memory_order_relaxed);
- _hold2List.erase(_hold2List.begin());
- }
-}
+GenerationHeldBase::~GenerationHeldBase() = default;
-void
-GenerationHolder::clearHoldLists()
+GenerationHolder::GenerationHolder()
+ : GenerationHolderParent()
{
- _hold1List.clear();
- _hold2List.clear();
- _heldBytes = 0;
}
}
diff --git a/vespalib/src/vespa/vespalib/util/generationholder.h b/vespalib/src/vespa/vespalib/util/generationholder.h
index ed68a80a308..86d402f9b3b 100644
--- a/vespalib/src/vespa/vespalib/util/generationholder.h
+++ b/vespalib/src/vespa/vespalib/util/generationholder.h
@@ -2,8 +2,8 @@
#pragma once
+#include "generation_hold_list.h"
#include "generationhandler.h"
-#include <vector>
#include <memory>
namespace vespalib {
@@ -11,79 +11,31 @@ namespace vespalib {
class GenerationHeldBase
{
public:
- typedef GenerationHandler::generation_t generation_t;
- typedef std::unique_ptr<GenerationHeldBase> UP;
- typedef std::shared_ptr<GenerationHeldBase> SP;
+ using generation_t = GenerationHandler::generation_t;
+ using UP = std::unique_ptr<GenerationHeldBase>;
+ using SP = std::shared_ptr<GenerationHeldBase>;
- generation_t _generation;
private:
- size_t _size;
+ size_t _byte_size;
public:
- GenerationHeldBase(size_t size)
- : _generation(0u),
- _size(size)
+ GenerationHeldBase(size_t byte_size_in)
+ : _byte_size(byte_size_in)
{ }
virtual ~GenerationHeldBase();
- size_t getSize() const { return _size; }
+ size_t byte_size() const { return _byte_size; }
};
+using GenerationHolderParent = GenerationHoldList<GenerationHeldBase::UP, true, false>;
+
/*
* GenerationHolder is meant to hold large elements until readers can
* no longer access them.
*/
-class GenerationHolder
-{
-private:
- typedef GenerationHandler::generation_t generation_t;
- typedef GenerationHandler::sgeneration_t sgeneration_t;
-
- typedef std::vector<GenerationHeldBase::SP> HoldList;
-
- HoldList _hold1List;
- HoldList _hold2List;
- std::atomic<size_t> _heldBytes;
-
- /**
- * Transfer holds from hold1 to hold2 lists, assigning generation.
- */
- void transferHoldListsSlow(generation_t generation);
-
- /**
- * Remove all data elements from this holder where generation < usedGen.
- **/
- void trimHoldListsSlow(generation_t usedGen);
-
+class GenerationHolder : public GenerationHolderParent {
public:
GenerationHolder();
- ~GenerationHolder();
-
- /**
- * Add the given data pointer to this holder.
- **/
- void hold(GenerationHeldBase::UP data);
-
- /**
- * Transfer holds from hold1 to hold2 lists, assigning generation.
- */
- void transferHoldLists(generation_t generation) {
- if (!_hold1List.empty()) {
- transferHoldListsSlow(generation);
- }
- }
-
- /**
- * Remove all data elements from this holder where generation < usedGen.
- **/
- void trimHoldLists(generation_t usedGen) {
- if (!_hold2List.empty() && static_cast<sgeneration_t>(_hold2List.front()->_generation - usedGen) < 0) {
- trimHoldListsSlow(usedGen);
- }
- }
-
- void clearHoldLists();
- size_t getHeldBytes() const { return _heldBytes.load(std::memory_order_relaxed); }
};
}
diff --git a/vespalib/src/vespa/vespalib/util/rcuvector.h b/vespalib/src/vespa/vespalib/util/rcuvector.h
index 5d084fe3815..b0929303692 100644
--- a/vespalib/src/vespa/vespalib/util/rcuvector.h
+++ b/vespalib/src/vespa/vespalib/util/rcuvector.h
@@ -182,7 +182,7 @@ public:
/**
* Remove all old data vectors where generation < firstUsed.
**/
- void removeOldGenerations(generation_t firstUsed);
+ void reclaim_memory(generation_t oldest_used_gen);
MemoryUsage getMemoryUsage() const override;
};
diff --git a/vespalib/src/vespa/vespalib/util/rcuvector.hpp b/vespalib/src/vespa/vespalib/util/rcuvector.hpp
index 97a73a73cc9..eadda8ac1e9 100644
--- a/vespalib/src/vespa/vespalib/util/rcuvector.hpp
+++ b/vespalib/src/vespa/vespalib/util/rcuvector.hpp
@@ -80,7 +80,7 @@ RcuVectorBase<T>::replaceVector(ArrayType replacement) {
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));
+ _genHolder.insert(std::move(hold));
onReallocation();
}
@@ -116,7 +116,7 @@ RcuVectorBase<T>::shrink(size_t newSize)
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));
+ _genHolder.insert(std::move(hold));
onReallocation();
}
}
@@ -162,7 +162,7 @@ template <typename T>
void
RcuVector<T>::onReallocation() {
RcuVectorBase<T>::onReallocation();
- _genHolderStore.transferHoldLists(_generation);
+ _genHolderStore.assign_generation(_generation);
}
template <typename T>
@@ -182,14 +182,14 @@ RcuVector<T>::RcuVector(GrowStrategy growStrategy)
template <typename T>
RcuVector<T>::~RcuVector()
{
- _genHolderStore.clearHoldLists();
+ _genHolderStore.reclaim_all();
}
template <typename T>
void
-RcuVector<T>::removeOldGenerations(generation_t firstUsed)
+RcuVector<T>::reclaim_memory(generation_t oldest_used_gen)
{
- _genHolderStore.trimHoldLists(firstUsed);
+ _genHolderStore.reclaim(oldest_used_gen);
}
template <typename T>
@@ -197,7 +197,7 @@ MemoryUsage
RcuVector<T>::getMemoryUsage() const
{
MemoryUsage retval(RcuVectorBase<T>::getMemoryUsage());
- retval.mergeGenerationHeldBytes(_genHolderStore.getHeldBytes());
+ retval.mergeGenerationHeldBytes(_genHolderStore.get_held_bytes());
return retval;
}
diff --git a/zkfacade/abi-spec.json b/zkfacade/abi-spec.json
index d227f8490dc..41a1854c276 100644
--- a/zkfacade/abi-spec.json
+++ b/zkfacade/abi-spec.json
@@ -1,4 +1,54 @@
{
+ "com.yahoo.vespa.curator.api.VespaCurator$Data": {
+ "superClass": "java.lang.Record",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final",
+ "record"
+ ],
+ "methods": [
+ "public void <init>(com.yahoo.vespa.curator.api.VespaCurator$Meta, byte[])",
+ "public final java.lang.String toString()",
+ "public final int hashCode()",
+ "public final boolean equals(java.lang.Object)",
+ "public com.yahoo.vespa.curator.api.VespaCurator$Meta meta()",
+ "public byte[] data()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.vespa.curator.api.VespaCurator$Meta": {
+ "superClass": "java.lang.Record",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final",
+ "record"
+ ],
+ "methods": [
+ "public void <init>(int)",
+ "public final java.lang.String toString()",
+ "public final int hashCode()",
+ "public final boolean equals(java.lang.Object)",
+ "public int version()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.vespa.curator.api.VespaCurator$SingletonWorker": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract void activate()",
+ "public abstract void deactivate()",
+ "public java.lang.String id()"
+ ],
+ "fields": []
+ },
"com.yahoo.vespa.curator.api.VespaCurator": {
"superClass": "java.lang.Object",
"interfaces": [],
@@ -8,7 +58,18 @@
"abstract"
],
"methods": [
- "public abstract java.lang.AutoCloseable lock(com.yahoo.path.Path, java.time.Duration)"
+ "public abstract java.util.Optional stat(com.yahoo.path.Path)",
+ "public abstract java.util.Optional read(com.yahoo.path.Path)",
+ "public abstract com.yahoo.vespa.curator.api.VespaCurator$Meta write(com.yahoo.path.Path, byte[])",
+ "public abstract java.util.Optional write(com.yahoo.path.Path, byte[], int)",
+ "public abstract void deleteAll(com.yahoo.path.Path)",
+ "public abstract void delete(com.yahoo.path.Path)",
+ "public abstract boolean delete(com.yahoo.path.Path, int)",
+ "public abstract java.util.List list(com.yahoo.path.Path)",
+ "public abstract java.lang.AutoCloseable lock(com.yahoo.path.Path, java.time.Duration)",
+ "public abstract void register(com.yahoo.vespa.curator.api.VespaCurator$SingletonWorker, java.time.Duration)",
+ "public abstract void unregister(com.yahoo.vespa.curator.api.VespaCurator$SingletonWorker, java.time.Duration)",
+ "public abstract boolean isActive(java.lang.String)"
],
"fields": []
}
diff --git a/zkfacade/pom.xml b/zkfacade/pom.xml
index 17be17d8302..41053585185 100644
--- a/zkfacade/pom.xml
+++ b/zkfacade/pom.xml
@@ -13,6 +13,12 @@
<version>8-SNAPSHOT</version>
<dependencies>
<dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>testutil</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java
index f159d471f55..ab0c77746fc 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/Curator.java
@@ -6,7 +6,6 @@ import com.yahoo.component.AbstractComponent;
import com.yahoo.component.annotation.Inject;
import com.yahoo.concurrent.DaemonThreadFactory;
import com.yahoo.path.Path;
-import com.yahoo.vespa.curator.api.VespaCurator;
import com.yahoo.vespa.curator.recipes.CuratorCounter;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.zookeeper.VespaZooKeeperServer;
@@ -59,13 +58,13 @@ import java.util.logging.Logger;
* @author vegardh
* @author bratseth
*/
-public class Curator extends AbstractComponent implements VespaCurator, AutoCloseable {
+public class Curator extends AbstractComponent implements AutoCloseable {
private static final Logger LOG = Logger.getLogger(Curator.class.getName());
private static final File ZK_CLIENT_CONFIG_FILE = new File(Defaults.getDefaults().underVespaHome("conf/zookeeper/zookeeper-client.cfg"));
- // Note that session timeout has min and max values are related to tickTime defined by server, see configserver.def
- private static final Duration ZK_SESSION_TIMEOUT = Duration.ofSeconds(120);
+ // Note that session timeout has min and max values are related to tickTime defined by server, see zookeeper-server.def
+ static final Duration DEFAULT_ZK_SESSION_TIMEOUT = Duration.ofSeconds(120);
private static final Duration ZK_CONNECTION_TIMEOUT = Duration.ofSeconds(30);
private static final Duration BASE_SLEEP_TIME = Duration.ofSeconds(1);
@@ -77,18 +76,21 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
private final CuratorFramework curatorFramework;
private final ConnectionSpec connectionSpec;
private final long juteMaxBuffer;
+ private final Duration sessionTimeout;
// All lock keys, to allow re-entrancy. This will grow forever, but this should be too slow to be a problem
private final ConcurrentHashMap<Path, Lock> locks = new ConcurrentHashMap<>();
/** Creates a curator instance from a comma-separated string of ZooKeeper host:port strings */
public static Curator create(String connectionSpec) {
- return new Curator(ConnectionSpec.create(connectionSpec), Optional.of(ZK_CLIENT_CONFIG_FILE), defaultJuteMaxBuffer);
+ return new Curator(ConnectionSpec.create(connectionSpec), Optional.of(ZK_CLIENT_CONFIG_FILE),
+ defaultJuteMaxBuffer, DEFAULT_ZK_SESSION_TIMEOUT);
}
// For testing only, use Optional.empty for clientConfigFile parameter to create default zookeeper client config
public static Curator create(String connectionSpec, Optional<File> clientConfigFile) {
- return new Curator(ConnectionSpec.create(connectionSpec), clientConfigFile, defaultJuteMaxBuffer);
+ return new Curator(ConnectionSpec.create(connectionSpec), clientConfigFile,
+ defaultJuteMaxBuffer, DEFAULT_ZK_SESSION_TIMEOUT);
}
@Inject
@@ -99,31 +101,35 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
CuratorConfig.Server::port,
curatorConfig.zookeeperLocalhostAffinity()),
Optional.of(ZK_CLIENT_CONFIG_FILE),
- defaultJuteMaxBuffer);
+ defaultJuteMaxBuffer,
+ Duration.ofSeconds(curatorConfig.zookeeperSessionTimeoutSeconds()));
}
protected Curator(String connectionSpec, String zooKeeperEnsembleConnectionSpec, Function<RetryPolicy, CuratorFramework> curatorFactory) {
- this(ConnectionSpec.create(connectionSpec, zooKeeperEnsembleConnectionSpec), curatorFactory.apply(DEFAULT_RETRY_POLICY), defaultJuteMaxBuffer);
+ this(ConnectionSpec.create(connectionSpec, zooKeeperEnsembleConnectionSpec), curatorFactory.apply(DEFAULT_RETRY_POLICY),
+ defaultJuteMaxBuffer, DEFAULT_ZK_SESSION_TIMEOUT);
}
- Curator(ConnectionSpec connectionSpec, Optional<File> clientConfigFile, long juteMaxBuffer) {
+ Curator(ConnectionSpec connectionSpec, Optional<File> clientConfigFile, long juteMaxBuffer, Duration sessionTimeout) {
this(connectionSpec,
CuratorFrameworkFactory
.builder()
.retryPolicy(DEFAULT_RETRY_POLICY)
- .sessionTimeoutMs((int) ZK_SESSION_TIMEOUT.toMillis())
+ .sessionTimeoutMs((int) sessionTimeout.toMillis())
.connectionTimeoutMs((int) ZK_CONNECTION_TIMEOUT.toMillis())
.connectString(connectionSpec.local())
.zookeeperFactory(new VespaZooKeeperFactory(createClientConfig(clientConfigFile)))
.dontUseContainerParents() // TODO: Consider changing this in Vespa 9
.build(),
- juteMaxBuffer);
+ juteMaxBuffer,
+ sessionTimeout);
}
- private Curator(ConnectionSpec connectionSpec, CuratorFramework curatorFramework, long juteMaxBuffer) {
+ private Curator(ConnectionSpec connectionSpec, CuratorFramework curatorFramework, long juteMaxBuffer, Duration sessionTimeout) {
this.connectionSpec = Objects.requireNonNull(connectionSpec);
this.curatorFramework = Objects.requireNonNull(curatorFramework);
this.juteMaxBuffer = juteMaxBuffer;
+ this.sessionTimeout = sessionTimeout;
addLoggingListener();
curatorFramework.start();
}
@@ -142,6 +148,10 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
}
}
+ public Duration sessionTimeout() {
+ return sessionTimeout;
+ }
+
/** For internal use; prefer creating a {@link CuratorCounter} */
public DistributedAtomicLong createAtomicCounter(String path) {
return new DistributedAtomicLong(curatorFramework, path, new ExponentialBackoffRetry((int) BASE_SLEEP_TIME.toMillis(), MAX_RETRIES));
@@ -195,7 +205,11 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
* If the path and any of its parents does not exists they are created.
*/
// TODO: Use create().orSetData() in Curator 4 and later
- public void set(Path path, byte[] data) {
+ public Stat set(Path path, byte[] data) {
+ return set(path, data, -1);
+ }
+
+ public Stat set(Path path, byte[] data, int expectedVersion) {
if (data.length > juteMaxBuffer)
throw new IllegalArgumentException("Cannot not set data at " + path.getAbsolute() + ", " +
data.length + " bytes is too much, max number of bytes allowed per node is " + juteMaxBuffer);
@@ -205,12 +219,13 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
String absolutePath = path.getAbsolute();
try {
- framework().setData().forPath(absolutePath, data);
+ return framework().setData().withVersion(expectedVersion).forPath(absolutePath, data);
} catch (Exception e) {
throw new RuntimeException("Could not set data at " + absolutePath, e);
}
}
+
/** @see #create(Path, Duration) */
public boolean create(Path path) { return create(path, null); }
@@ -220,6 +235,9 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
* Returns whether a change was attempted.
*/
public boolean create(Path path, Duration ttl) {
+ return create(path, ttl, null);
+ }
+ private boolean create(Path path, Duration ttl, Stat stat) {
if (exists(path)) return false;
String absolutePath = path.getAbsolute();
@@ -231,7 +249,8 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
throw new IllegalArgumentException(ttl.toString());
b.withTtl(millis).withMode(CreateMode.PERSISTENT_WITH_TTL);
}
- b.creatingParentsIfNeeded().forPath(absolutePath, new byte[0]);
+ if (stat == null) b.creatingParentsIfNeeded() .forPath(absolutePath, new byte[0]);
+ else b.creatingParentsIfNeeded().storingStatIn(stat).forPath(absolutePath, new byte[0]);
} catch (org.apache.zookeeper.KeeperException.NodeExistsException e) {
// Path created between exists() and create() call, do nothing
} catch (Exception e) {
@@ -258,12 +277,25 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
}
/**
- * Deletes the given path and any children it may have.
- * If the path does not exists nothing is done.
+ * Deletes the path and any children it may have.
+ * If the path does not exist, nothing is done.
*/
public void delete(Path path) {
+ delete(path, true);
+ }
+
+ /**
+ * Deletes the path and any children it may have.
+ * If the path does not exist, nothing is done.
+ */
+ public void delete(Path path, boolean recursive) {
+ delete(path, -1, recursive);
+ }
+
+ public void delete(Path path, int expectedVersion, boolean recursive) {
try {
- framework().delete().guaranteed().deletingChildrenIfNeeded().forPath(path.getAbsolute());
+ if (recursive) framework().delete().guaranteed().deletingChildrenIfNeeded().withVersion(expectedVersion).forPath(path.getAbsolute());
+ else framework().delete().guaranteed() .withVersion(expectedVersion).forPath(path.getAbsolute());
} catch (KeeperException.NoNodeException e) {
// Do nothing
} catch (Exception e) {
@@ -290,8 +322,13 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
* Empty is returned if the path does not exist.
*/
public Optional<byte[]> getData(Path path) {
+ return getData(path, null);
+ }
+
+ Optional<byte[]> getData(Path path, Stat stat) {
try {
- return Optional.of(framework().getData().forPath(path.getAbsolute()));
+ return stat == null ? Optional.of(framework().getData() .forPath(path.getAbsolute()))
+ : Optional.of(framework().getData().storingStatIn(stat).forPath(path.getAbsolute()));
}
catch (KeeperException.NoNodeException e) {
return Optional.empty();
@@ -317,14 +354,17 @@ public class Curator extends AbstractComponent implements VespaCurator, AutoClos
}
}
- /** Create and acquire a re-entrant lock in given path */
- public Lock lock(Path path, Duration timeout) {
- create(path);
+ /** Create and acquire a re-entrant lock in given path with a TTL */
+ public Lock lock(Path path, Duration timeout, Duration ttl) {
+ create(path, ttl);
Lock lock = locks.computeIfAbsent(path, (pathArg) -> new Lock(pathArg.getAbsolute(), this));
lock.acquire(timeout);
return lock;
}
+ /** Create and acquire a re-entrant lock in given path */
+ public Lock lock(Path path, Duration timeout) { return lock(path, timeout, null); }
+
/** Returns the curator framework API */
public CuratorFramework framework() {
return curatorFramework;
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorWrapper.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorWrapper.java
new file mode 100644
index 00000000000..27d969c0c09
--- /dev/null
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/CuratorWrapper.java
@@ -0,0 +1,166 @@
+package com.yahoo.vespa.curator;
+
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.annotation.Inject;
+import com.yahoo.concurrent.UncheckedTimeoutException;
+import com.yahoo.jdisc.Metric;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.curator.api.VespaCurator;
+import com.yahoo.yolean.UncheckedInterruptedException;
+import org.apache.curator.framework.state.ConnectionState;
+import org.apache.zookeeper.KeeperException.BadVersionException;
+import org.apache.zookeeper.data.Stat;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Implementation of {@link VespaCurator} which delegates to a {@link Curator}.
+ * This prefixes all paths with {@code "/user"}, to ensure separation with system ZK usage.
+ *
+ * @author jonmv
+ */
+public class CuratorWrapper extends AbstractComponent implements VespaCurator {
+
+ static final Path userRoot = Path.fromString("user");
+
+ private final Curator curator;
+ private final SingletonManager singletons;
+
+ @Inject
+ public CuratorWrapper(Curator curator, Metric metric) {
+ this(curator, Clock.systemUTC(), Duration.ofSeconds(1), metric);
+ }
+
+ CuratorWrapper(Curator curator, Clock clock, Duration tickTimeout, Metric metric) {
+ this.curator = curator;
+ this.singletons = new SingletonManager(curator, clock, tickTimeout, metric);
+
+ curator.framework().getConnectionStateListenable().addListener((curatorFramework, connectionState) -> {
+ if (connectionState == ConnectionState.LOST) singletons.invalidate();
+ });
+ }
+
+ @Override
+ public Optional<Meta> stat(Path path) {
+ return curator.getStat(userRoot.append(path)).map(stat -> new Meta(stat.getVersion()));
+ }
+
+ @Override
+ public Optional<Data> read(Path path) {
+ Stat stat = new Stat();
+ return curator.getData(userRoot.append(path), stat).map(data -> new Data(new Meta(stat.getVersion()), data));
+ }
+
+ @Override
+ public Meta write(Path path, byte[] data) {
+ return new Meta(curator.set(userRoot.append(path), data).getVersion());
+ }
+
+ @Override
+ public Optional<Meta> write(Path path, byte[] data, int expectedVersion) {
+ try {
+ return Optional.of(new Meta(curator.set(userRoot.append(path), data, expectedVersion).getVersion()));
+ }
+ catch (RuntimeException e) {
+ if (e.getCause() instanceof BadVersionException) return Optional.empty();
+ throw e;
+ }
+ }
+
+ @Override
+ public void deleteAll(Path path) {
+ curator.delete(userRoot.append(path));
+ }
+
+ @Override
+ public void delete(Path path) {
+ curator.delete(userRoot.append(path), false);
+ }
+
+ @Override
+ public boolean delete(Path path, int expectedVersion) {
+ try {
+ curator.delete(userRoot.append(path), expectedVersion, false);
+ return true;
+ }
+ catch (RuntimeException e) {
+ if (e.getCause() instanceof BadVersionException) return false;
+ throw e;
+ }
+ }
+
+ @Override
+ public List<String> list(Path path) {
+ return curator.getChildren(userRoot.append(path));
+ }
+
+ @Override
+ public AutoCloseable lock(Path path, Duration timeout) {
+ return curator.lock(userRoot.append(path), timeout);
+ }
+
+ @Override
+ public void register(SingletonWorker singleton, Duration timeout) {
+ try {
+ await(singletons.register(singleton.id(), singleton), timeout, "register " + singleton);
+ }
+ catch (RuntimeException e) {
+ try {
+ unregister(singleton, timeout);
+ }
+ catch (Exception f) {
+ e.addSuppressed(f);
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public void unregister(SingletonWorker singleton, Duration timeout) {
+ await(singletons.unregister(singleton), timeout, "unregister " + singleton);
+ }
+
+ private void await(Future<?> future, Duration timeout, String action) {
+ try {
+ future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException e) {
+ future.cancel(true);
+ throw new UncheckedInterruptedException("interrupted while " + action, e, true);
+ }
+ catch (TimeoutException e) {
+ future.cancel(true);
+ throw new UncheckedTimeoutException("timed out while " + action, e);
+ }
+ catch (ExecutionException e) {
+ throw new RuntimeException("failed to " + action, e.getCause());
+ }
+ }
+
+ @Override
+ public boolean isActive(String singletonId) {
+ return singletons.isActive(singletonId);
+ }
+
+ @Override
+ public void deconstruct() {
+ try {
+ singletons.shutdown().get();
+ }
+ catch (InterruptedException e) {
+ throw new UncheckedInterruptedException(e, true);
+ }
+ catch (ExecutionException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ }
+
+}
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/SingletonManager.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/SingletonManager.java
new file mode 100644
index 00000000000..04f1f6bbc09
--- /dev/null
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/SingletonManager.java
@@ -0,0 +1,427 @@
+package com.yahoo.vespa.curator;
+
+import com.yahoo.jdisc.Metric;
+import com.yahoo.path.Path;
+import com.yahoo.protect.Process;
+import com.yahoo.vespa.curator.api.VespaCurator.SingletonWorker;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.FINE;
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+
+/**
+ * Manages {@link com.yahoo.vespa.curator.api.VespaCurator.SingletonWorker}.
+ *
+ * @author jonmv
+ */
+class SingletonManager {
+
+ private static final Logger logger = Logger.getLogger(SingletonManager.class.getName());
+
+ private final Curator curator;
+ private final Clock clock;
+ private final Duration tickTimeout;
+ private final Map<String, Janitor> janitors = new HashMap<>();
+ private final Map<String, Integer> count = new HashMap<>();
+ private final Map<SingletonWorker, String> registrations = new IdentityHashMap<>();
+ private final Metric metric;
+
+ SingletonManager(Curator curator, Clock clock, Duration tickTimeout, Metric metric) {
+ this.curator = curator;
+ this.clock = clock;
+ this.tickTimeout = tickTimeout;
+ this.metric = metric;
+ }
+
+ synchronized CompletableFuture<?> register(String singletonId, SingletonWorker singleton) {
+ if (singletonId.isEmpty() || singletonId.contains("/") || singletonId.contains("..")) {
+ throw new IllegalArgumentException("singleton ID must be non-empty, and may not contain '/' or '..', but got " + singletonId);
+ }
+ String old = registrations.putIfAbsent(singleton, singletonId);
+ if (old != null) throw new IllegalArgumentException(singleton + " already registered with ID " + old);
+ count.merge(singletonId, 1, Integer::sum);
+ return janitors.computeIfAbsent(singletonId, Janitor::new).register(singleton);
+ }
+
+ synchronized CompletableFuture<?> unregister(SingletonWorker singleton) {
+ String id = registrations.remove(singleton);
+ if (id == null) throw new IllegalArgumentException(singleton + " is not registered");
+ return janitors.get(id).unregister(singleton)
+ .whenComplete((__, ___) -> unregistered(id, singleton));
+ }
+
+ synchronized void unregistered(String singletonId, SingletonWorker singleton) {
+ registrations.remove(singleton);
+ if (count.merge(singletonId, -1, Integer::sum) > 0) return;
+ count.remove(singletonId);
+ janitors.remove(singletonId).shutdown();
+ }
+
+ /**
+ * The instant until which this container holds the exclusive lease for activation of singletons with this ID.
+ * The container may abandon the lease early, if deactivation is triggered and completes before the deadline.
+ * Unless connection to the underlying ZK cluster is lost, the returned value will regularly move forwards in time.
+ */
+ synchronized Optional<Instant> activeUntil(String singletonId) {
+ return Optional.ofNullable(janitors.get(singletonId)).map(janitor -> janitor.doom.get());
+ }
+
+ /** Whether this container currently holds the activation lease for the given singleton ID. */
+ boolean isActive(String singletonId) {
+ return activeUntil(singletonId).map(clock.instant()::isBefore).orElse(false);
+ }
+
+ /** Invalidate all leases, due to connection loss. */
+ synchronized void invalidate() {
+ for (Janitor janitor : janitors.values()) janitor.invalidate();
+ }
+
+ public synchronized CompletableFuture<?> shutdown() {
+ CompletableFuture<?>[] futures = new CompletableFuture[registrations.size()];
+ int i = 0;
+ for (SingletonWorker singleton : List.copyOf(registrations.keySet())) {
+ String id = registrations.get(singleton);
+ logger.log(WARNING, singleton + " still registered with id '" + id + "' at shutdown");
+ futures[i++] = unregister(singleton);
+ }
+ return CompletableFuture.allOf(futures)
+ .orTimeout(10, TimeUnit.SECONDS);
+ }
+
+
+ private class Janitor {
+
+ static class Task {
+
+ enum Type { register, unregister }
+
+ final Type type;
+ final SingletonWorker singleton;
+ final CompletableFuture<?> future = new CompletableFuture<>();
+
+ private Task(Type type, SingletonWorker singleton) {
+ this.type = type;
+ this.singleton = singleton;
+ }
+
+ static Task register(SingletonWorker singleton) { return new Task(Type.register, singleton); }
+ static Task unregister(SingletonWorker singleton) { return new Task(Type.unregister, singleton); }
+
+ }
+
+ private static final Instant INVALID = Instant.ofEpochMilli(-1);
+
+ final BlockingDeque<Task> tasks = new LinkedBlockingDeque<>();
+ final Deque<SingletonWorker> singletons = new ArrayDeque<>(2);
+ final AtomicReference<Instant> doom = new AtomicReference<>();
+ final AtomicBoolean shutdown = new AtomicBoolean();
+ final Thread worker;
+ final String id;
+ final Path path;
+ final MetricHelper metrics;
+ Lock lock = null;
+ boolean active;
+
+ Janitor(String id) {
+ this.id = id;
+ this.path = Path.fromString("/vespa/singleton/v1/" + id);
+ this.worker = new Thread(this::run, "singleton-janitor-" + id);
+ this.metrics = new MetricHelper();
+
+ worker.setDaemon(true);
+ worker.start();
+ }
+
+ public void unlock() {
+ doom.set(null);
+ if (lock != null) try {
+ logger.log(INFO, "Relinquishing lease for " + id);
+ lock.close();
+ lock = null;
+ }
+ catch (Exception e) {
+ logger.log(WARNING, "Failed closing " + lock, e);
+ }
+ }
+
+ private void run() {
+ try {
+ while ( ! shutdown.get()) {
+ try {
+ // Attempt to acquire the lock, and extend our doom.
+ renewLease();
+ // Ensure activation status is set accordingly to doom, or clear state if this fails.
+ updateStatus();
+ // Process the next pending, externally triggered task, if any.
+ doTask();
+ }
+ catch (InterruptedException e) {
+ if ( ! shutdown.get()) {
+ logger.log(WARNING, worker + " interrupted, restarting event loop");
+ }
+ }
+ }
+ unlock();
+ }
+ catch (Throwable t) {
+ Process.logAndDie(worker + " can't continue, shutting down", t);
+ }
+ }
+
+ protected void doTask() throws InterruptedException {
+ Task task = tasks.poll(tickTimeout.toMillis(), TimeUnit.MILLISECONDS);
+ try {
+ if (task != null) switch (task.type) {
+ case register -> {
+ doRegister(task.singleton);
+ task.future.complete(null);
+ }
+ case unregister -> {
+ doUnregister(task.singleton);
+ task.future.complete(null);
+ }
+ }
+ }
+ catch (RuntimeException e) {
+ logger.log(WARNING, "Exception attempting to " + task.type + " " + task.singleton + " in " + worker, e);
+ task.future.completeExceptionally(e);
+ }
+ }
+
+ private void doRegister(SingletonWorker singleton) {
+ logger.log(INFO, "Registering " + singleton + " with ID: " + id);
+ SingletonWorker current = singletons.peek();
+ singletons.push(singleton);
+ if (active) {
+ RuntimeException e = null;
+ if (current != null) try {
+ logger.log(INFO, "Deactivating " + current);
+ metrics.deactivation(current::deactivate);
+ }
+ catch (RuntimeException f) {
+ e = f;
+ }
+ try {
+ logger.log(INFO, "Activating " + singleton);
+ metrics.activation(singleton::activate);
+ }
+ catch (RuntimeException f) {
+ if (e == null) e = f;
+ else e.addSuppressed(f);
+ }
+ if (singletons.isEmpty()) {
+ logger.log(INFO, "No registered singletons, invalidating lease");
+ invalidate();
+ }
+ if (e != null) throw e;
+ }
+ }
+
+ private void doUnregister(SingletonWorker singleton) {
+ logger.log(INFO, "Unregistering " + singleton + " with ID: " + id);
+ RuntimeException e = null;
+ SingletonWorker current = singletons.peek();
+ if ( ! singletons.remove(singleton)) return;
+ if (active && current == singleton) {
+ try {
+ logger.log(INFO, "Deactivating " + singleton);
+ metrics.deactivation(singleton::deactivate);
+ }
+ catch (RuntimeException f) {
+ e = f;
+ }
+ if ( ! singletons.isEmpty()) try {
+ logger.log(INFO, "Activating " + singletons.peek());
+ metrics.activation(singletons.peek()::activate);
+ }
+ catch (RuntimeException f) {
+ if (e == null) e = f;
+ else e.addSuppressed(f);
+ }
+ if (singletons.isEmpty()) {
+ logger.log(INFO, "No registered singletons, invalidating lease");
+ invalidate();
+ }
+ }
+ if (e != null) throw e;
+ }
+
+ /**
+ * Attempt to acquire the lock, if not held.
+ * If lock is held, or acquired, ping the ZK cluster to extend our deadline.
+ */
+ private void renewLease() {
+ if (doom.get() == INVALID) {
+ logger.log(INFO, "Lease invalidated");
+ doom.set(null);
+ return; // Skip to updateStatus, deactivation, and release the lock.
+ }
+ // Witness value to detect if invalidation occurs between here and successful ping.
+ Instant ourDoom = doom.get();
+ Instant start = clock.instant();
+ if (lock == null) try {
+ lock = curator.lock(path.append("lock"), tickTimeout);
+ logger.log(INFO, "Acquired lock for ID: " + id);
+ }
+ catch (RuntimeException e) {
+ logger.log(FINE, e, () -> "Failed acquiring lock for '" + path + "' within " + tickTimeout);
+ return;
+ }
+ try {
+ curator.set(path.append("ping"), new byte[0]);
+ }
+ catch (RuntimeException e) {
+ logger.log(FINE, "Failed pinging ZK cluster", e);
+ return;
+ }
+ if ( ! doom.compareAndSet(ourDoom, start.plus(curator.sessionTimeout().multipliedBy(9).dividedBy(10)))) {
+ logger.log(FINE, "Deadline changed, current lease renewal is void");
+ }
+ }
+
+ /**
+ * Attempt to activate or deactivate if status has changed.
+ * If activation fails, we release the lock, to a different container may acquire it.
+ */
+ private void updateStatus() {
+ Instant ourDoom = doom.get();
+ boolean shouldBeActive = ourDoom != null && ourDoom != INVALID && ! clock.instant().isAfter(ourDoom);
+ if ( ! active && shouldBeActive) {
+ logger.log(INFO, "Activating singleton for ID: " + id);
+ try {
+ active = true;
+ if ( ! singletons.isEmpty()) metrics.activation(singletons.peek()::activate);
+ }
+ catch (RuntimeException e) {
+ logger.log(WARNING, "Failed to activate " + singletons.peek() + ", deactivating again", e);
+ shouldBeActive = false;
+ }
+ }
+ if (active && ! shouldBeActive) {
+ logger.log(INFO, "Deactivating singleton for ID: " + id);
+ logger.log(FINE, () -> "Doom value is " + doom);
+ try {
+ if ( ! singletons.isEmpty()) metrics.deactivation(singletons.peek()::deactivate);
+ active = false;
+ }
+ catch (RuntimeException e) {
+ logger.log(WARNING, "Failed to deactivate " + singletons.peek(), e);
+ }
+ unlock();
+ }
+ }
+
+ CompletableFuture<?> register(SingletonWorker singleton) {
+ Task task = Task.register(singleton);
+ tasks.offer(task);
+ return task.future;
+ }
+
+ CompletableFuture<?> unregister(SingletonWorker singleton) {
+ Task task = Task.unregister(singleton);
+ tasks.offer(task);
+ return task.future;
+ }
+
+ void invalidate() {
+ doom.set(INVALID);
+ }
+
+ void shutdown() {
+ logger.log(INFO, "Shutting down " + this);
+ if ( ! shutdown.compareAndSet(false, true)) {
+ logger.log(WARNING, "Shutdown called more than once on " + this);
+ }
+ if (Thread.currentThread() != worker) {
+ try {
+ worker.join();
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ else {
+ unlock();
+ }
+ if ( ! tasks.isEmpty()) {
+ logger.log(WARNING, "Non-empty task list after shutdown: " + tasks.size() + " remaining");
+ for (Task task : tasks) task.future.cancel(true); // Shouldn't happen.
+ }
+ }
+
+ private class MetricHelper {
+
+ static final String PREFIX = "jdisc.singleton.";
+ static final String IS_ACTIVE = PREFIX + "is_active";
+ static final String ACTIVATION = PREFIX + "activation.count";
+ static final String ACTIVATION_MILLIS = PREFIX + "activation.millis";
+ static final String ACTIVATION_FAILURES = PREFIX + "activation.failure.count";
+ static final String DEACTIVATION = PREFIX + "deactivation.count";
+ static final String DEACTIVATION_MILLIS = PREFIX + "deactivation.millis";
+ static final String DEACTIVATION_FAILURES = PREFIX + "deactivation.failure.count";
+
+ final Metric.Context context;
+
+ MetricHelper() {
+ this.context = metric.createContext(Map.of("singletonId", id));
+ }
+
+ void activation(Runnable activation) {
+ Instant start = clock.instant();
+ boolean failed = false;
+ metric.add(ACTIVATION, 1, context);
+ try {
+ activation.run();
+ }
+ catch (RuntimeException e) {
+ failed = true;
+ throw e;
+ }
+ finally {
+ metric.set(ACTIVATION_MILLIS, Duration.between(start, clock.instant()).toMillis(), context);
+ if (failed) metric.add(ACTIVATION_FAILURES, 1, context);
+ else metric.set(IS_ACTIVE, 1, context);
+ }
+ }
+
+ void deactivation(Runnable deactivation) {
+ Instant start = clock.instant();
+ boolean failed = false;
+ metric.add(DEACTIVATION, 1, context);
+ try {
+ deactivation.run();
+ }
+ catch (RuntimeException e) {
+ failed = true;
+ throw e;
+ }
+ finally {
+ metric.set(DEACTIVATION_MILLIS, Duration.between(start, clock.instant()).toMillis(), context);
+ if (failed) metric.add(DEACTIVATION_FAILURES, 1, context);
+ metric.set(IS_ACTIVE, 0, context);
+ }
+ }
+
+ }
+
+ }
+
+}
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/api/VespaCurator.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/api/VespaCurator.java
index 32aef2da118..f2bc38a4644 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/api/VespaCurator.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/api/VespaCurator.java
@@ -5,16 +5,197 @@ import com.yahoo.concurrent.UncheckedTimeoutException;
import com.yahoo.path.Path;
import java.time.Duration;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/**
* A client for a ZooKeeper cluster running inside Vespa. Applications that want to use ZooKeeper can inject this in
* their code.
*
* @author mpolden
+ * @author jonmv
*/
public interface VespaCurator {
- /** Create and acquire a re-entrant lock in given path. This blocks until the lock is acquired or timeout elapses. */
+ /** Returns the stat for the node at the given path, or empty if no node exists at that path. */
+ Optional<Meta> stat(Path path);
+
+ /** Returns the content and stat for the node at the given path, or empty if no node exists at that path. */
+ Optional<Data> read(Path path);
+
+ /**
+ * Writes the given data to a node at the given path, creating it and its parents as needed, and returns the
+ * stat of the modified node. Failure to write, due to connection loss, is retried a limited number of times.
+ */
+ Meta write(Path path, byte[] data);
+
+ /**
+ * Atomically compares the version in the stat of the node at the given path, with the expected version, and then:
+ * if they are equal, attempts the write operation (see {@link #write(Path, byte[])});
+ * otherwise, return empty.
+ */
+ Optional<Meta> write(Path path, byte[] data, int expectedVersion);
+
+ /** Recursively deletes any node at the given path, and any children it may have. */
+ void deleteAll(Path path);
+
+ /** Deletes the node at the given path. Failres due to connection loss are retried a limited number of times. */
+ void delete(Path path);
+
+ /**
+ * Atomically compares the version in the stat of the node at the given path, with the expected version, and then:
+ * if they are equal, attempts the delete operation (see {@link #delete(Path)}), and returns {@code} true;
+ * otherwise, returns {@code false}.
+ */
+ boolean delete(Path path, int expectedVersion);
+
+ /** Lists the children of the node at the given path, or throws if there is no node at that path. */
+ List<String> list(Path path);
+
+ /** Creates and acquires a re-entrant lock with the given path. This blocks until the lock is acquired or timeout elapses. */
AutoCloseable lock(Path path, Duration timeout) throws UncheckedTimeoutException;
+ /** Data of a ZK node, including content (possibly empty, never {@code null}) and metadata. */
+ record Data(Meta meta, byte[] data) { }
+
+ /** Metadata for a ZK node. */
+ record Meta(int version) { }
+
+ /**
+ * Register the singleton with the framework, so it may become active.
+ * <p>
+ * <strong>Call this after construction of the singleton, typically during component construction!</strong>
+ * <ul>
+ * <li>If this activates the singleton, this happens synchronously, and any errors are propagated here.</li>
+ * <li>If this replaces an already active singleton, its deactivation is also called, prior to activation of this.</li>
+ * <li>If (de)activation is not complete within the given timeout, a timeout exception is thrown.</li>
+ * <li>If an error occurs (due to failed activation), unregistration is automatically attempted, with the same timeout.</li>
+ * </ul>
+ */
+ void register(SingletonWorker singleton, Duration timeout);
+
+ /**
+ * Unregister with the framework, so this singleton may no longer be active, and returns
+ * <p>
+ * <strong>Call this before deconstruction of the singleton, typically during component deconstruction!</strong>
+ * <ul>
+ * <li>If this singleton is active, deactivation will be called synchronously, and errors propagated here.</li>
+ * <li>If this also triggers activation of a new singleton, its activation is called after deactivation of this.</li>
+ * <li>If (de)activation is not complete within the given timeout, a timeout exception is thrown.</li>
+ * </ul>
+ */
+ void unregister(SingletonWorker singleton, Duration timeout);
+
+ /**
+ * Whether this container currently holds the exclusive lease for activation of singletons with this ID.
+ */
+ boolean isActive(String singletonId);
+
+ /**
+ * Callback interface for processes of which only a single instance should be active at any time, across all
+ * containers in the cluster, and across all component generations.
+ * <p>
+ * <br>
+ * Sample usage:
+ * <pre>
+ * public class SingletonHolder extends AbstractComponent {
+ *
+ * private static final Duration timeout = Duration.ofSeconds(10);
+ * private final VespaCurator curator;
+ * private final SingletonWorker singleton;
+ *
+ * public SingletonHolder(VespaCurator curator) {
+ * this.curator = curator;
+ * this.singleton = new Singleton();
+ * curator.register(singleton, timeout);
+ * }
+ *
+ * &#064;Override
+ * public void deconstruct() {
+ * curator.unregister(singleton, timeout);
+ * singleton.shutdown();
+ * }
+ *
+ * }
+ *
+ * public class Singleton implements SingletonWorker {
+ *
+ * private final SharedResource resource = ...; // Shared resource that requires exclusive access.
+ * private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ * private final AtomicBoolean running = new AtomicBoolean();
+ * private Future&lt;?&gt; future = null;
+ *
+ * &#064;Override
+ * public void activate() {
+ * resource.open(); // Verify resource works here, and propagate any errors out.
+ * running.set(true);
+ * future = executor.submit(this::doWork);
+ * }
+ *
+ * &#064;Override
+ * public void deactivate() {
+ * running.set(false);
+ * try { future.get(10, TimeUnit.SECONDS); }
+ * catch (Exception e) { ... }
+ * finally { resource.close(); }
+ * }
+ *
+ * private void doWork() {
+ * while (running.get()) { ... } // Regularly check whether we should keep running.
+ * }
+ *
+ * public void shutdown() {
+ * executor.shutdownNow(); // Executor should have no running tasks at this point.
+ * }
+ *
+ * }
+ * </pre>
+ * <p>
+ * <br>
+ * Notes to implementors:
+ * <ul>
+ * <li>{@link #activate()} is called by the system on a singleton whenever it is the newest registered
+ * singleton in this container, and this container has the lease for the ID with which the singleton
+ * was registered. See {@link #id}, {@link #register} and {@link #isActive}.</li>
+ * <li>{@link #deactivate()} is called by the system on a singleton which is currently active whenever
+ * the above no longer holds. See {@link #unregister}.</li>
+ * <li>Callbacks for the same ID are always invoked by the same thread, in serial; the callbacks must
+ * return in a timely manner, but are encouraged to throw exceptions when something's wrong</li>
+ * <li>Activation and deactivation may be triggered by:
+ * <ol>
+ * <li>the container acquiring or losing the activation lease; or</li>
+ * <li>registration of unregistration of a new or obsolete singleton.</li>
+ * </ol>
+ * Events triggered by the latter happen synchronously, and errors are propagated to the caller for cleanup.
+ * Events triggered by the former may happen in the background, and because the system tries to always have
+ * one activated singleton, exceptions during activation will cause the container to abandon its lease, so
+ * another container may obtain it instead; exceptions during deactivation are only logged.
+ * </li>
+ * <li>A container without any registered singletons will not attempt to hold the activation lease.</li>
+ * </ul>
+ */
+ interface SingletonWorker {
+
+ /**
+ * Called by the system whenever this singleton becomes the single active worker.
+ * If this is triggered because the container obtains the activation lease, and activation fails,
+ * then the container immediately releases the lease, so another container may acquire it instead.
+ */
+ void activate();
+
+ /** Called by the system whenever this singleton is no longer the single active worker. */
+ void deactivate();
+
+ /**
+ * The singleton ID to use when registering this with a {@link VespaCurator}.
+ * At most one singleton worker with the given ID will be active, in the cluster, at any time.
+ * {@link VespaCurator#isActive(String)} may be polled to see whether this container is currently
+ * allowed to have an active singleton with the given ID.
+ */
+ default String id() { return getClass().getName(); }
+
+ }
+
}
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCuratorFramework.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCuratorFramework.java
index 81fb24bd7e5..c8566015ea1 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCuratorFramework.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/mock/MockCuratorFramework.java
@@ -8,6 +8,7 @@ import com.yahoo.concurrent.UncheckedTimeoutException;
import com.yahoo.path.Path;
import com.yahoo.vespa.curator.CompletionTimeoutException;
import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.mock.MemoryFileSystem.Node;
import com.yahoo.vespa.curator.recipes.CuratorLockException;
import org.apache.curator.CuratorZookeeperClient;
import org.apache.curator.framework.CuratorFramework;
@@ -83,6 +84,8 @@ import org.apache.curator.retry.RetryForever;
import org.apache.curator.utils.EnsurePath;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.KeeperException.BadVersionException;
+import org.apache.zookeeper.KeeperException.NoNodeException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
@@ -124,6 +127,8 @@ public class MockCuratorFramework implements CuratorFramework {
/** Listeners to changes to a particular path */
private final ListenerMap listeners = new ListenerMap();
+ public final MockListenable<ConnectionStateListener> connectionStateListeners = new MockListenable<>();
+
private CuratorFrameworkState curatorState = CuratorFrameworkState.LATENT;
private int monotonicallyIncreasingNumber = 0;
@@ -267,7 +272,7 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public Listenable<ConnectionStateListener> getConnectionStateListenable() {
- return new MockListenable<>();
+ return connectionStateListeners;
}
@Override
@@ -351,7 +356,7 @@ public class MockCuratorFramework implements CuratorFramework {
// ----- Start of adaptor methods from Curator to the mock file system -----
/** Creates a node below the given directory root */
- private String createNode(String pathString, byte[] content, boolean createParents, CreateMode createMode, MemoryFileSystem.Node root, Listeners listeners, Long ttl)
+ private String createNode(String pathString, byte[] content, boolean createParents, Stat stat, CreateMode createMode, MemoryFileSystem.Node root, Listeners listeners, Long ttl)
throws KeeperException.NodeExistsException, KeeperException.NoNodeException {
validatePath(pathString);
Path path = Path.fromString(pathString);
@@ -371,18 +376,21 @@ public class MockCuratorFramework implements CuratorFramework {
node.setTtl(ttl);
String nodePath = "/" + path.getParentPath().toString() + "/" + name;
listeners.notify(Path.fromString(nodePath), content, PathChildrenCacheEvent.Type.CHILD_ADDED);
+ if (stat != null) stat.setVersion(node.version());
return nodePath;
}
/** Deletes a node below the given directory root */
- private void deleteNode(String pathString, boolean deleteChildren, MemoryFileSystem.Node root, Listeners listeners)
- throws KeeperException.NoNodeException, KeeperException.NotEmptyException {
+ private void deleteNode(String pathString, boolean deleteChildren, int version, MemoryFileSystem.Node root, Listeners listeners)
+ throws KeeperException {
validatePath(pathString);
Path path = Path.fromString(pathString);
MemoryFileSystem.Node parent = root.getNode(Paths.get(path.getParentPath().toString()), false);
if (parent == null) throw new KeeperException.NoNodeException(path.toString());
MemoryFileSystem.Node node = parent.children().get(path.getName());
if (node == null) throw new KeeperException.NoNodeException(path.getName() + " under " + parent);
+ if (version != -1 && version != node.version())
+ throw new KeeperException.BadVersionException("expected version " + version + ", but was " + node.version());
if ( ! node.children().isEmpty() && ! deleteChildren)
throw new KeeperException.NotEmptyException(path.toString());
parent.remove(path.getName());
@@ -390,16 +398,20 @@ public class MockCuratorFramework implements CuratorFramework {
}
/** Returns the data of a node */
- private byte[] getData(String pathString, MemoryFileSystem.Node root) throws KeeperException.NoNodeException {
+ private byte[] getData(String pathString, Stat stat, MemoryFileSystem.Node root) throws KeeperException.NoNodeException {
validatePath(pathString);
- return getNode(pathString, root).getContent();
+ return getNode(pathString, stat, root).getContent();
}
/** sets the data of an existing node */
- private void setData(String pathString, byte[] content, MemoryFileSystem.Node root, Listeners listeners)
- throws KeeperException.NoNodeException {
+ private void setData(String pathString, byte[] content, int version, Stat stat, MemoryFileSystem.Node root, Listeners listeners)
+ throws KeeperException {
validatePath(pathString);
- getNode(pathString, root).setContent(content);
+ Node node = getNode(pathString, null, root);
+ if (version != -1 && version != node.version())
+ throw new KeeperException.BadVersionException("expected version " + version + ", but was " + node.version());
+ node.setContent(content);
+ if (stat != null) stat.setVersion(node.version());
listeners.notify(Path.fromString(pathString), content, PathChildrenCacheEvent.Type.CHILD_UPDATED);
}
@@ -414,13 +426,14 @@ public class MockCuratorFramework implements CuratorFramework {
}
/** Returns a node or throws the appropriate exception if it doesn't exist */
- private MemoryFileSystem.Node getNode(String pathString, MemoryFileSystem.Node root) throws KeeperException.NoNodeException {
+ private MemoryFileSystem.Node getNode(String pathString, Stat stat, MemoryFileSystem.Node root) throws KeeperException.NoNodeException {
validatePath(pathString);
Path path = Path.fromString(pathString);
MemoryFileSystem.Node parent = root.getNode(Paths.get(path.getParentPath().toString()), false);
if (parent == null) throw new KeeperException.NoNodeException(path.toString());
MemoryFileSystem.Node node = parent.children().get(path.getName());
if (node == null) throw new KeeperException.NoNodeException(path.toString());
+ if (stat != null) stat.setVersion(node.version());
return node;
}
@@ -783,6 +796,7 @@ public class MockCuratorFramework implements CuratorFramework {
private abstract static class MockProtectACLCreateModeStatPathAndBytesable<String>
implements ProtectACLCreateModeStatPathAndBytesable<String> {
+ Stat stat = null;
public BackgroundPathAndBytesable<String> withACL(List<ACL> list) {
throw new UnsupportedOperationException("Not implemented in MockCurator");
}
@@ -832,6 +846,7 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public ACLBackgroundPathAndBytesable<String> storingStatIn(Stat stat) {
+ this.stat = stat;
return this;
}
@@ -850,12 +865,12 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public String forPath(String s, byte[] bytes) throws Exception {
- return createNode(s, bytes, createParents, createMode, fileSystem.root(), listeners, ttl);
+ return createNode(s, bytes, createParents, stat, createMode, fileSystem.root(), listeners, ttl);
}
@Override
public String forPath(String s) throws Exception {
- return createNode(s, new byte[0], createParents, createMode, fileSystem.root(), listeners, ttl);
+ return createNode(s, new byte[0], createParents, stat, createMode, fileSystem.root(), listeners, ttl);
}
};
@@ -867,12 +882,12 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public String forPath(String s, byte[] bytes) throws Exception {
- return createNode(s, bytes, createParents, createMode, fileSystem.root(), listeners, ttl);
+ return createNode(s, bytes, createParents, stat, createMode, fileSystem.root(), listeners, ttl);
}
@Override
public String forPath(String s) throws Exception {
- return createNode(s, new byte[0], createParents, createMode, fileSystem.root(), listeners, ttl);
+ return createNode(s, new byte[0], createParents, stat, createMode, fileSystem.root(), listeners, ttl);
}
};
@@ -890,11 +905,11 @@ public class MockCuratorFramework implements CuratorFramework {
}
public String forPath(String s) throws Exception {
- return createNode(s, new byte[0], createParents, createMode, fileSystem.root(), listeners, ttl);
+ return createNode(s, new byte[0], createParents, null, createMode, fileSystem.root(), listeners, ttl);
}
public String forPath(String s, byte[] bytes) throws Exception {
- return createNode(s, bytes, createParents, createMode, fileSystem.root(), listeners, ttl);
+ return createNode(s, bytes, createParents, null, createMode, fileSystem.root(), listeners, ttl);
}
@Override
@@ -1043,9 +1058,8 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public Stat forPath(String path) throws Exception {
try {
- MemoryFileSystem.Node node = getNode(path, fileSystem.root());
Stat stat = new Stat();
- stat.setVersion(node.version());
+ getNode(path, stat, fileSystem.root());
return stat;
}
catch (KeeperException.NoNodeException e) {
@@ -1067,6 +1081,7 @@ public class MockCuratorFramework implements CuratorFramework {
private class MockDeleteBuilder extends MockBackgroundPathableBuilder<Void> implements DeleteBuilder {
private boolean deleteChildren = false;
+ private int version = -1;
@Override
public BackgroundVersionable deletingChildrenIfNeeded() {
@@ -1081,11 +1096,12 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public BackgroundPathable<Void> withVersion(int i) {
- throw new UnsupportedOperationException("Not implemented in MockCurator");
+ version = i;
+ return this;
}
public Void forPath(String pathString) throws Exception {
- deleteNode(pathString, deleteChildren, fileSystem.root(), listeners);
+ deleteNode(pathString, deleteChildren, version, fileSystem.root(), listeners);
return null;
}
@@ -1107,7 +1123,7 @@ public class MockCuratorFramework implements CuratorFramework {
}
public byte[] forPath(String path) throws Exception {
- return getData(path, fileSystem.root());
+ return getData(path, null, fileSystem.root());
}
@Override
@@ -1142,13 +1158,34 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public WatchPathable<byte[]> storingStatIn(Stat stat) {
- throw new UnsupportedOperationException("Not implemented in MockCurator");
+ return new WatchPathable<byte[]>() {
+ @Override
+ public byte[] forPath(String path) throws Exception {
+ return getData(path, stat, fileSystem.root());
+ }
+
+ @Override
+ public Pathable<byte[]> watched() {
+ return null;
+ }
+
+ @Override
+ public Pathable<byte[]> usingWatcher(Watcher watcher) {
+ return null;
+ }
+
+ @Override
+ public Pathable<byte[]> usingWatcher(CuratorWatcher watcher) {
+ return null;
+ }
+ };
}
}
// extends MockBackgroundACLPathAndBytesableBuilder<Stat>
private class MockSetDataBuilder implements SetDataBuilder {
+ int version = -1;
@Override
public SetDataBackgroundVersionable compressed() {
throw new UnsupportedOperationException("Not implemented in MockCurator");
@@ -1156,18 +1193,22 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public BackgroundPathAndBytesable<Stat> withVersion(int i) {
- throw new UnsupportedOperationException("Not implemented in MockCurator");
+ version = i;
+ return this;
}
@Override
public Stat forPath(String path, byte[] bytes) throws Exception {
- setData(path, bytes, fileSystem.root(), listeners);
- return null;
+ Stat stat = new Stat();
+ setData(path, bytes, version, stat, fileSystem.root(), listeners);
+ return stat;
}
@Override
- public Stat forPath(String s) throws Exception {
- return null;
+ public Stat forPath(String path) throws Exception {
+ Stat stat = new Stat();
+ setData(path, new byte[0], version, stat, fileSystem.root(), listeners);
+ return stat;
}
@Override
@@ -1206,18 +1247,21 @@ public class MockCuratorFramework implements CuratorFramework {
}
/** Allows addition of directoryListeners which are never called */
- private static class MockListenable<T> implements Listenable<T> {
+ public static class MockListenable<T> implements Listenable<T> {
+
+ public final List<T> listeners = new ArrayList<>();
@Override
public void addListener(T t) {
+ listeners.add(t);
}
@Override
- public void addListener(T t, Executor executor) {
- }
+ public void addListener(T t, Executor executor) { throw new UnsupportedOperationException("not supported in mock curator"); }
@Override
public void removeListener(T t) {
+ listeners.remove(t);
}
}
@@ -1288,13 +1332,13 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public CuratorTransactionBridge forPath(String s, byte[] bytes) throws Exception {
- createNode(s, bytes, false, createMode, newRoot, delayedListener, null);
+ createNode(s, bytes, false, null, createMode, newRoot, delayedListener, null);
return new MockCuratorTransactionBridge();
}
@Override
public CuratorTransactionBridge forPath(String s) throws Exception {
- createNode(s, new byte[0], false, createMode, newRoot, delayedListener, null);
+ createNode(s, new byte[0], false, null, createMode, newRoot, delayedListener, null);
return new MockCuratorTransactionBridge();
}
@@ -1316,14 +1360,16 @@ public class MockCuratorFramework implements CuratorFramework {
private class MockTransactionDeleteBuilder implements TransactionDeleteBuilder<CuratorTransactionBridge> {
+ int version = -1;
@Override
public Pathable<CuratorTransactionBridge> withVersion(int i) {
- throw new UnsupportedOperationException("Not implemented in MockCurator");
+ version = i;
+ return this;
}
@Override
public CuratorTransactionBridge forPath(String path) throws Exception {
- deleteNode(path, false, newRoot, delayedListener);
+ deleteNode(path, false, version, newRoot, delayedListener);
return new MockCuratorTransactionBridge();
}
@@ -1331,6 +1377,7 @@ public class MockCuratorFramework implements CuratorFramework {
private class MockTransactionSetDataBuilder implements TransactionSetDataBuilder<CuratorTransactionBridge> {
+ int version = -1;
@Override
public VersionPathAndBytesable<CuratorTransactionBridge> compressed() {
throw new UnsupportedOperationException("Not implemented in MockCurator");
@@ -1338,18 +1385,19 @@ public class MockCuratorFramework implements CuratorFramework {
@Override
public PathAndBytesable<CuratorTransactionBridge> withVersion(int i) {
- throw new UnsupportedOperationException("Not implemented in MockCurator");
+ version = i;
+ return this;
}
@Override
public CuratorTransactionBridge forPath(String s, byte[] bytes) throws Exception {
- MockCuratorFramework.this.setData(s, bytes, newRoot, delayedListener);
+ MockCuratorFramework.this.setData(s, bytes, version, null, newRoot, delayedListener);
return new MockCuratorTransactionBridge();
}
@Override
public CuratorTransactionBridge forPath(String s) throws Exception {
- MockCuratorFramework.this.setData(s, new byte[0], newRoot, delayedListener);
+ MockCuratorFramework.this.setData(s, new byte[0], version, null, newRoot, delayedListener);
return new MockCuratorTransactionBridge();
}
diff --git a/zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorTest.java b/zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorTest.java
index 71eae121313..3ace79ecc67 100644
--- a/zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorTest.java
+++ b/zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorTest.java
@@ -82,7 +82,8 @@ public class CuratorTest {
CuratorConfig.Server::port,
curatorConfig.zookeeperLocalhostAffinity()),
Optional.empty(),
- juteMaxBuffer);
+ juteMaxBuffer,
+ Curator.DEFAULT_ZK_SESSION_TIMEOUT);
}
}
diff --git a/zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorWrapperTest.java b/zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorWrapperTest.java
new file mode 100644
index 00000000000..5eb38c559a9
--- /dev/null
+++ b/zkfacade/src/test/java/com/yahoo/vespa/curator/CuratorWrapperTest.java
@@ -0,0 +1,333 @@
+package com.yahoo.vespa.curator;
+
+import com.yahoo.jdisc.test.MockMetric;
+import com.yahoo.path.Path;
+import com.yahoo.test.ManualClock;
+import com.yahoo.vespa.curator.api.VespaCurator;
+import com.yahoo.vespa.curator.api.VespaCurator.Meta;
+import com.yahoo.vespa.curator.api.VespaCurator.SingletonWorker;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.curator.mock.MockCuratorFramework;
+import org.apache.curator.framework.state.ConnectionState;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author jonmv
+ */
+public class CuratorWrapperTest {
+
+ static final Path lockPath = Path.fromString("/vespa/singleton/v1/singleton/lock");
+
+ @Test
+ public void testUserApi() throws Exception {
+ try (Curator wrapped = new MockCurator()) {
+ CuratorWrapper curator = new CuratorWrapper(wrapped, new MockMetric());
+
+ Path path = Path.fromString("path");
+ assertEquals(Optional.empty(), curator.stat(path));
+
+ Meta meta = curator.write(path, "data".getBytes(UTF_8));
+ assertEquals(Optional.of(meta), curator.stat(path));
+
+ assertEquals("data", new String(curator.read(path).get().data(), UTF_8));
+ assertEquals(meta, curator.read(path).get().meta());
+
+ assertEquals(Optional.empty(), curator.write(path, new byte[0], 0));
+
+ meta = curator.write(path, new byte[0], meta.version()).get();
+ assertEquals(3, meta.version());
+
+ assertEquals(List.of("path"), curator.list(Path.createRoot()));
+
+ assertFalse(curator.delete(path, 0));
+
+ curator.delete(path, 3);
+
+ assertEquals(List.of(), curator.list(Path.createRoot()));
+
+ try (AutoCloseable lock = curator.lock(path, Duration.ofSeconds(1))) {
+ assertEquals(List.of("path"), wrapped.getChildren(CuratorWrapper.userRoot));
+ }
+ }
+ }
+
+ @Test
+ public void testSingleSingleton() {
+ try (Curator wrapped = new MockCurator()) {
+ Phaser stunning = new Phaser(1);
+ ManualClock clock = new ManualClock() {
+ @Override public Instant instant() {
+ stunning.arriveAndAwaitAdvance();
+ // Let test thread advance time when desired.
+ stunning.arriveAndAwaitAdvance();
+ return super.instant();
+ };
+ };
+ MockMetric metric = new MockMetric();
+ CuratorWrapper curator = new CuratorWrapper(wrapped, clock, Duration.ofMillis(100), metric);
+
+ // First singleton to register becomes active during construction.
+ Singleton singleton = new Singleton(curator);
+ assertTrue(singleton.isActive);
+ assertTrue(wrapped.exists(lockPath));
+ stunning.register();
+ assertTrue(curator.isActive(singleton.id()));
+ stunning.arriveAndDeregister();
+ singleton.shutdown();
+ assertFalse(singleton.isActive);
+ // ... and deactivated as a result of unregistering again.
+
+ // Singleton can be set up again, but this time, time runs away.
+ Phaser mark2 = new Phaser(2); // Janitor and helper.
+ new Thread(() -> {
+ mark2.arriveAndAwaitAdvance(); // Wait for janitor to call activate.
+ stunning.arriveAndAwaitAdvance(); // Let janitor measure time spent on activation, while test thread waits for it.
+ stunning.arriveAndAwaitAdvance(); // Let janitor measure time spent on activation, while test thread waits for it.
+ }).start();
+ singleton = new Singleton(curator) {
+ @Override public void activate() {
+ // Set up sync in clock on next renewLease.
+ super.activate();
+ stunning.register();
+ mark2.arrive();
+ }
+ };
+ assertTrue(singleton.isActive);
+
+ stunning.arriveAndAwaitAdvance(); // Wait for next renewLease.
+ stunning.arriveAndAwaitAdvance(); // Let next renewLease complete.
+ stunning.arriveAndAwaitAdvance(); // Wait for next updateStatus.
+ clock.advance(wrapped.sessionTimeout());
+ singleton.phaser.register(); // Set up so we can synchronise with deactivation.
+ stunning.forceTermination(); // Let lease expire, and ensure further ticks complete if we lose the race to unregister.
+
+ singleton.phaser.arriveAndAwaitAdvance();
+ assertFalse(singleton.isActive);
+ verifyMetrics(Map.of("activation.count", 2.0,
+ "activation.millis", 0.0,
+ "deactivation.count", 2.0,
+ "deactivation.millis", 0.0),
+ metric);
+
+ // Singleton is reactivated next tick.
+ singleton.phaser.awaitAdvance(singleton.phaser.arriveAndDeregister());
+ assertTrue(singleton.isActive);
+ verifyMetrics(Map.of("activation.count", 3.0,
+ "activation.millis", 0.0,
+ "deactivation.count", 2.0,
+ "deactivation.millis", 0.0),
+ metric);
+
+ // Manager unregisters remaining singletons on shutdown.
+ curator.deconstruct();
+ assertFalse(singleton.isActive);
+ verifyMetrics(Map.of("activation.count", 3.0,
+ "activation.millis", 0.0,
+ "deactivation.count", 3.0,
+ "deactivation.millis", 0.0,
+ "is_active", 0.0),
+ metric);
+ }
+ }
+
+ @Test
+ public void testSingletonsInSameContainer() {
+ try (Curator wrapped = new MockCurator()) {
+ MockMetric metric = new MockMetric();
+ CuratorWrapper curator = new CuratorWrapper(wrapped, new ManualClock(), Duration.ofMillis(100), metric);
+
+ // First singleton to register becomes active during construction.
+ Singleton singleton = new Singleton(curator);
+ assertTrue(singleton.isActive);
+ assertTrue(wrapped.exists(lockPath));
+ assertTrue(curator.isActive(singleton.id()));
+
+ Singleton newSingleton = new Singleton(curator);
+ assertTrue(newSingleton.isActive);
+ assertFalse(singleton.isActive);
+
+ Singleton newerSingleton = new Singleton(curator);
+ assertTrue(newerSingleton.isActive);
+ assertFalse(newSingleton.isActive);
+ assertFalse(singleton.isActive);
+
+ singleton.shutdown();
+ assertTrue(newerSingleton.isActive);
+ assertFalse(newSingleton.isActive);
+ assertFalse(singleton.isActive);
+
+ newerSingleton.shutdown();
+ assertFalse(newerSingleton.isActive);
+ assertTrue(newSingleton.isActive);
+ assertFalse(singleton.isActive);
+ verifyMetrics(Map.of("activation.count", 4.0,
+ "activation.millis", 0.0,
+ "deactivation.count", 3.0,
+ "deactivation.millis", 0.0,
+ "is_active", 1.0),
+ metric);
+
+ // Add a singleton which fails activation.
+ Phaser stunning = new Phaser(2);
+ AtomicReference<String> thrownMessage = new AtomicReference<>();
+ new Thread(() -> {
+ RuntimeException e = assertThrows(RuntimeException.class,
+ () -> new Singleton(curator) {
+ @Override public void activate() {
+ throw new RuntimeException("expected test exception");
+ }
+ @Override public void deactivate() {
+ stunning.arriveAndAwaitAdvance();
+ stunning.arriveAndAwaitAdvance();
+ throw new RuntimeException("expected test exception");
+ }
+ @Override public String toString() {
+ return "failing singleton";
+ }
+ });
+ thrownMessage.set(e.getMessage());
+ stunning.arriveAndAwaitAdvance();
+ }).start();
+
+ stunning.arriveAndAwaitAdvance(); // Failing component is about to be deactivated.
+ assertFalse(newSingleton.isActive);
+ assertTrue(curator.isActive(newSingleton.id())); // No actual active components, but container has the lease.
+ verifyMetrics(Map.of("activation.count", 5.0,
+ "activation.millis", 0.0,
+ "activation.failure.count", 1.0,
+ "deactivation.count", 5.0,
+ "deactivation.millis", 0.0,
+ "is_active", 0.0),
+ metric);
+ stunning.arriveAndAwaitAdvance(); // Failing component is done being deactivated.
+ stunning.arriveAndAwaitAdvance(); // Failing component is done cleaning up after itself.
+ assertTrue(newSingleton.isActive);
+ assertEquals("failed to register failing singleton", thrownMessage.get());
+ verifyMetrics(Map.of("activation.count", 6.0,
+ "activation.millis", 0.0,
+ "activation.failure.count", 1.0,
+ "deactivation.count", 5.0,
+ "deactivation.millis", 0.0,
+ "is_active", 1.0),
+ metric);
+
+ newSingleton.shutdown();
+ curator.deconstruct();
+ verifyMetrics(Map.of("activation.count", 6.0,
+ "activation.millis", 0.0,
+ "activation.failure.count", 1.0,
+ "deactivation.count", 6.0,
+ "deactivation.millis", 0.0,
+ "is_active", 0.0),
+ metric);
+ }
+ }
+
+ @Test
+ public void testSingletonsInDifferentContainers() {
+ try (MockCurator wrapped = new MockCurator()) {
+ MockMetric metric = new MockMetric();
+ CuratorWrapper curator = new CuratorWrapper(wrapped, new ManualClock(), Duration.ofMillis(100), metric);
+
+ // Simulate a different container holding the lock.
+ Singleton singleton;
+ try (Lock lock = wrapped.lock(lockPath, Duration.ofSeconds(1))) {
+ singleton = new Singleton(curator);
+ assertFalse(singleton.isActive);
+ assertFalse(curator.isActive(singleton.id()));
+ assertEquals(Map.of(), metric.metrics());
+ singleton.phaser.register();
+ }
+
+ singleton.phaser.arriveAndAwaitAdvance();
+ assertTrue(curator.isActive(singleton.id()));
+ assertTrue(singleton.isActive);
+ verifyMetrics(Map.of("activation.count", 1.0),
+ metric);
+
+ // Simulate a different container wanting the lock.
+ Phaser stunning = new Phaser(2);
+ new Thread(() -> {
+ try (Lock lock = wrapped.lock(lockPath, Duration.ofSeconds(2))) {
+ stunning.arriveAndAwaitAdvance();
+ stunning.arriveAndAwaitAdvance();
+ }
+ }).start();
+
+ // Simulate connection loss for our singleton's ZK session.
+ ((MockCuratorFramework) wrapped.framework()).connectionStateListeners.listeners.forEach(listener -> listener.stateChanged(null, ConnectionState.LOST));
+ singleton.phaser.arriveAndAwaitAdvance();
+ stunning.arriveAndAwaitAdvance();
+ assertFalse(singleton.isActive);
+ verifyMetrics(Map.of("activation.count", 1.0,
+ "activation.millis", 0.0,
+ "deactivation.count", 1.0,
+ "deactivation.millis", 0.0,
+ "is_active", 0.0),
+ metric);
+
+ // Connection is restored, and the other container releases the lock again.
+ stunning.arriveAndAwaitAdvance();
+ singleton.phaser.arriveAndAwaitAdvance();
+ assertTrue(singleton.isActive);
+ verifyMetrics(Map.of("activation.count", 2.0,
+ "activation.millis", 0.0,
+ "deactivation.count", 1.0,
+ "deactivation.millis", 0.0),
+ metric);
+
+ singleton.phaser.arriveAndDeregister();
+ singleton.shutdown();
+ curator.deconstruct();
+ assertFalse(singleton.isActive);
+ verifyMetrics(Map.of("activation.count", 2.0,
+ "activation.millis", 0.0,
+ "deactivation.count", 2.0,
+ "deactivation.millis", 0.0,
+ "is_active", 0.0),
+ metric);
+ }
+ }
+
+ static class Singleton implements SingletonWorker {
+ final VespaCurator curator;
+ Singleton(VespaCurator curator) {
+ this.curator = curator;
+
+ curator.register(this, Duration.ofSeconds(2));
+ }
+ boolean isActive;
+ Phaser phaser = new Phaser(1);
+ @Override public String id() { return "singleton"; } // ... lest anonymous subclasses get different IDs ... ƪ(`▿▿▿▿´ƪ)
+ @Override public void activate() {
+ if (isActive) throw new IllegalStateException("already active");
+ isActive = true;
+ phaser.arriveAndAwaitAdvance();
+ }
+ @Override public void deactivate() {
+ if ( ! isActive) throw new IllegalStateException("already inactive");
+ isActive = false;
+ phaser.arriveAndAwaitAdvance();
+ }
+ public void shutdown() { curator.unregister(this, Duration.ofSeconds(2)); }
+ }
+
+ static void verifyMetrics(Map<String, Double> expected, MockMetric metrics) {
+ expected.forEach((metric, value) -> assertEquals(metric, value, metrics.metrics().get("jdisc.singleton." + metric).get(Map.of("singletonId", "singleton"))));
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java b/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java
new file mode 100644
index 00000000000..e03e0b07944
--- /dev/null
+++ b/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java
@@ -0,0 +1,353 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server;
+
+import java.io.Flushable;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.apache.zookeeper.common.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This RequestProcessor logs requests to disk. It batches the requests to do
+ * the io efficiently. The request is not passed to the next RequestProcessor
+ * until its log has been synced to disk.
+ *
+ * SyncRequestProcessor is used in 3 different cases
+ * 1. Leader - Sync request to disk and forward it to AckRequestProcessor which
+ * send ack back to itself.
+ * 2. Follower - Sync request to disk and forward request to
+ * SendAckRequestProcessor which send the packets to leader.
+ * SendAckRequestProcessor is flushable which allow us to force
+ * push packets to leader.
+ * 3. Observer - Sync committed request to disk (received as INFORM packet).
+ * It never send ack back to the leader, so the nextProcessor will
+ * be null. This change the semantic of txnlog on the observer
+ * since it only contains committed txns.
+ */
+public class SyncRequestProcessor extends ZooKeeperCriticalThread implements RequestProcessor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SyncRequestProcessor.class);
+
+ private static final Request REQUEST_OF_DEATH = Request.requestOfDeath;
+
+ private static class FlushRequest extends Request {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ public FlushRequest() {
+ super(null, 0, 0, 0, null, null);
+ }
+ }
+
+ private static final Request turnForwardingDelayOn = new Request(null, 0, 0, 0, null, null);
+ private static final Request turnForwardingDelayOff = new Request(null, 0, 0, 0, null, null);
+
+ private static class DelayingProcessor implements RequestProcessor, Flushable {
+ private final RequestProcessor next;
+ private Queue<Request> delayed = null;
+ private DelayingProcessor(RequestProcessor next) {
+ this.next = next;
+ }
+ @Override
+ public void flush() throws IOException {
+ if (delayed == null && next instanceof Flushable) {
+ ((Flushable) next).flush();
+ }
+ }
+ @Override
+ public void processRequest(Request request) throws RequestProcessorException {
+ if (delayed == null) {
+ next.processRequest(request);
+ } else {
+ delayed.add(request);
+ }
+ }
+ @Override
+ public void shutdown() {
+ next.shutdown();
+ }
+ private void close() {
+ if (delayed == null) {
+ delayed = new ArrayDeque<>();
+ }
+ }
+ private void open() throws RequestProcessorException {
+ if (delayed != null) {
+ for (Request request : delayed) {
+ next.processRequest(request);
+ }
+ delayed = null;
+ }
+ }
+ }
+
+ /** The number of log entries to log before starting a snapshot */
+ private static int snapCount = ZooKeeperServer.getSnapCount();
+
+ /**
+ * The total size of log entries before starting a snapshot
+ */
+ private static long snapSizeInBytes = ZooKeeperServer.getSnapSizeInBytes();
+
+ /**
+ * Random numbers used to vary snapshot timing
+ */
+ private int randRoll;
+ private long randSize;
+
+ private final BlockingQueue<Request> queuedRequests = new LinkedBlockingQueue<Request>();
+
+ private final Semaphore snapThreadMutex = new Semaphore(1);
+
+ private final ZooKeeperServer zks;
+
+ private final DelayingProcessor nextProcessor;
+
+ /**
+ * Transactions that have been written and are waiting to be flushed to
+ * disk. Basically this is the list of SyncItems whose callbacks will be
+ * invoked after flush returns successfully.
+ */
+ private final Queue<Request> toFlush;
+ private long lastFlushTime;
+
+ public SyncRequestProcessor(ZooKeeperServer zks, RequestProcessor nextProcessor) {
+ super("SyncThread:" + zks.getServerId(), zks.getZooKeeperServerListener());
+ this.zks = zks;
+ this.nextProcessor = nextProcessor == null ? null : new DelayingProcessor(nextProcessor);
+ this.toFlush = new ArrayDeque<>(zks.getMaxBatchSize());
+ }
+
+ /**
+ * used by tests to check for changing
+ * snapcounts
+ * @param count
+ */
+ public static void setSnapCount(int count) {
+ snapCount = count;
+ }
+
+ /**
+ * used by tests to get the snapcount
+ * @return the snapcount
+ */
+ public static int getSnapCount() {
+ return snapCount;
+ }
+
+ private long getRemainingDelay() {
+ long flushDelay = zks.getFlushDelay();
+ long duration = Time.currentElapsedTime() - lastFlushTime;
+ if (duration < flushDelay) {
+ return flushDelay - duration;
+ }
+ return 0;
+ }
+
+ /** If both flushDelay and maxMaxBatchSize are set (bigger than 0), flush
+ * whenever either condition is hit. If only one or the other is
+ * set, flush only when the relevant condition is hit.
+ */
+ private boolean shouldFlush() {
+ long flushDelay = zks.getFlushDelay();
+ long maxBatchSize = zks.getMaxBatchSize();
+ if ((flushDelay > 0) && (getRemainingDelay() == 0)) {
+ return true;
+ }
+ return (maxBatchSize > 0) && (toFlush.size() >= maxBatchSize);
+ }
+
+ /**
+ * used by tests to check for changing
+ * snapcounts
+ * @param size
+ */
+ public static void setSnapSizeInBytes(long size) {
+ snapSizeInBytes = size;
+ }
+
+ private boolean shouldSnapshot() {
+ int logCount = zks.getZKDatabase().getTxnCount();
+ long logSize = zks.getZKDatabase().getTxnSize();
+ return (logCount > (snapCount / 2 + randRoll))
+ || (snapSizeInBytes > 0 && logSize > (snapSizeInBytes / 2 + randSize));
+ }
+
+ private void resetSnapshotStats() {
+ randRoll = ThreadLocalRandom.current().nextInt(snapCount / 2);
+ randSize = Math.abs(ThreadLocalRandom.current().nextLong() % (snapSizeInBytes / 2));
+ }
+
+ @Override
+ public void run() {
+ try {
+ // we do this in an attempt to ensure that not all of the servers
+ // in the ensemble take a snapshot at the same time
+ resetSnapshotStats();
+ lastFlushTime = Time.currentElapsedTime();
+ while (true) {
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_SIZE.add(queuedRequests.size());
+
+ long pollTime = Math.min(zks.getMaxWriteQueuePollTime(), getRemainingDelay());
+ Request si = queuedRequests.poll(pollTime, TimeUnit.MILLISECONDS);
+ if (si == null) {
+ /* We timed out looking for more writes to batch, go ahead and flush immediately */
+ flush();
+ si = queuedRequests.take();
+ }
+
+ if (si == REQUEST_OF_DEATH) {
+ break;
+ }
+
+ if (si == turnForwardingDelayOn) {
+ nextProcessor.close();
+ continue;
+ }
+ if (si == turnForwardingDelayOff) {
+ nextProcessor.open();
+ continue;
+ }
+
+ if (si instanceof FlushRequest) {
+ flush();
+ ((FlushRequest) si).latch.countDown();
+ continue;
+ }
+
+ long startProcessTime = Time.currentElapsedTime();
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_TIME.add(startProcessTime - si.syncQueueStartTime);
+
+ // track the number of records written to the log
+ if (!si.isThrottled() && zks.getZKDatabase().append(si)) {
+ if (shouldSnapshot()) {
+ resetSnapshotStats();
+ // roll the log
+ zks.getZKDatabase().rollLog();
+ // take a snapshot
+ if (!snapThreadMutex.tryAcquire()) {
+ LOG.warn("Too busy to snap, skipping");
+ } else {
+ new ZooKeeperThread("Snapshot Thread") {
+ public void run() {
+ try {
+ zks.takeSnapshot();
+ } catch (Exception e) {
+ LOG.warn("Unexpected exception", e);
+ } finally {
+ snapThreadMutex.release();
+ }
+ }
+ }.start();
+ }
+ }
+ } else if (toFlush.isEmpty()) {
+ // optimization for read heavy workloads
+ // iff this is a read or a throttled request(which doesn't need to be written to the disk),
+ // and there are no pending flushes (writes), then just pass this to the next processor
+ if (nextProcessor != null) {
+ nextProcessor.processRequest(si);
+ nextProcessor.flush();
+ }
+ continue;
+ }
+ toFlush.add(si);
+ if (shouldFlush()) {
+ flush();
+ }
+ ServerMetrics.getMetrics().SYNC_PROCESS_TIME.add(Time.currentElapsedTime() - startProcessTime);
+ }
+ } catch (Throwable t) {
+ handleException(this.getName(), t);
+ }
+ LOG.info("SyncRequestProcessor exited!");
+ }
+
+ /** Flushes all pending writes, and waits for this to complete. */
+ public void syncFlush() throws InterruptedException {
+ FlushRequest marker = new FlushRequest();
+ queuedRequests.add(marker);
+ marker.latch.await();
+ }
+
+ public void setDelayForwarding(boolean delayForwarding) {
+ queuedRequests.add(delayForwarding ? turnForwardingDelayOn : turnForwardingDelayOff);
+ }
+
+ private void flush() throws IOException, RequestProcessorException {
+ if (this.toFlush.isEmpty()) {
+ return;
+ }
+
+ ServerMetrics.getMetrics().BATCH_SIZE.add(toFlush.size());
+
+ long flushStartTime = Time.currentElapsedTime();
+ zks.getZKDatabase().commit();
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_FLUSH_TIME.add(Time.currentElapsedTime() - flushStartTime);
+
+ if (this.nextProcessor == null) {
+ this.toFlush.clear();
+ } else {
+ while (!this.toFlush.isEmpty()) {
+ final Request i = this.toFlush.remove();
+ long latency = Time.currentElapsedTime() - i.syncQueueStartTime;
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_AND_FLUSH_TIME.add(latency);
+ this.nextProcessor.processRequest(i);
+ }
+ nextProcessor.flush();
+ }
+ lastFlushTime = Time.currentElapsedTime();
+ }
+
+ public void shutdown() {
+ LOG.info("Shutting down");
+ queuedRequests.add(REQUEST_OF_DEATH);
+ try {
+ this.join();
+ this.flush();
+ } catch (InterruptedException e) {
+ LOG.warn("Interrupted while wating for {} to finish", this);
+ Thread.currentThread().interrupt();
+ } catch (IOException e) {
+ LOG.warn("Got IO exception during shutdown");
+ } catch (RequestProcessorException e) {
+ LOG.warn("Got request processor exception during shutdown");
+ }
+ if (nextProcessor != null) {
+ nextProcessor.shutdown();
+ }
+ }
+
+ public void processRequest(final Request request) {
+ Objects.requireNonNull(request, "Request cannot be null");
+
+ request.syncQueueStartTime = Time.currentElapsedTime();
+ queuedRequests.add(request);
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUED.add(1);
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/Learner.java b/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/Learner.java
new file mode 100644
index 00000000000..8e80fae57dc
--- /dev/null
+++ b/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/Learner.java
@@ -0,0 +1,920 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.net.ssl.SSLSocket;
+import org.apache.jute.BinaryInputArchive;
+import org.apache.jute.BinaryOutputArchive;
+import org.apache.jute.InputArchive;
+import org.apache.jute.OutputArchive;
+import org.apache.jute.Record;
+import org.apache.zookeeper.ZooDefs.OpCode;
+import org.apache.zookeeper.common.Time;
+import org.apache.zookeeper.common.X509Exception;
+import org.apache.zookeeper.server.ExitCode;
+import org.apache.zookeeper.server.Request;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.apache.zookeeper.server.ServerMetrics;
+import org.apache.zookeeper.server.TxnLogEntry;
+import org.apache.zookeeper.server.ZooTrace;
+import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer;
+import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
+import org.apache.zookeeper.server.util.ConfigUtils;
+import org.apache.zookeeper.server.util.MessageTracker;
+import org.apache.zookeeper.server.util.SerializeUtils;
+import org.apache.zookeeper.server.util.ZxidUtils;
+import org.apache.zookeeper.txn.SetDataTxn;
+import org.apache.zookeeper.txn.TxnDigest;
+import org.apache.zookeeper.txn.TxnHeader;
+import org.apache.zookeeper.util.ServiceUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class is the superclass of two of the three main actors in a ZK
+ * ensemble: Followers and Observers. Both Followers and Observers share
+ * a good deal of code which is moved into Peer to avoid duplication.
+ */
+public class Learner {
+
+ static class PacketInFlight {
+
+ TxnHeader hdr;
+ Record rec;
+ TxnDigest digest;
+
+ }
+
+ QuorumPeer self;
+ LearnerZooKeeperServer zk;
+
+ protected BufferedOutputStream bufferedOutput;
+
+ protected Socket sock;
+ protected MultipleAddresses leaderAddr;
+ protected AtomicBoolean sockBeingClosed = new AtomicBoolean(false);
+
+ /**
+ * Socket getter
+ */
+ public Socket getSocket() {
+ return sock;
+ }
+
+ LearnerSender sender = null;
+ protected InputArchive leaderIs;
+ protected OutputArchive leaderOs;
+ /** the protocol version of the leader */
+ protected int leaderProtocolVersion = 0x01;
+
+ private static final int BUFFERED_MESSAGE_SIZE = 10;
+ protected final MessageTracker messageTracker = new MessageTracker(BUFFERED_MESSAGE_SIZE);
+
+ protected static final Logger LOG = LoggerFactory.getLogger(Learner.class);
+
+ /**
+ * Time to wait after connection attempt with the Leader or LearnerMaster before this
+ * Learner tries to connect again.
+ */
+ private static final int leaderConnectDelayDuringRetryMs = Integer.getInteger("zookeeper.leaderConnectDelayDuringRetryMs", 100);
+
+ private static final boolean nodelay = System.getProperty("follower.nodelay", "true").equals("true");
+
+ public static final String LEARNER_ASYNC_SENDING = "zookeeper.learner.asyncSending";
+ private static boolean asyncSending =
+ Boolean.parseBoolean(ConfigUtils.getPropertyBackwardCompatibleWay(LEARNER_ASYNC_SENDING));
+ public static final String LEARNER_CLOSE_SOCKET_ASYNC = "zookeeper.learner.closeSocketAsync";
+ public static final boolean closeSocketAsync = Boolean
+ .parseBoolean(ConfigUtils.getPropertyBackwardCompatibleWay(LEARNER_CLOSE_SOCKET_ASYNC));
+
+ static {
+ LOG.info("leaderConnectDelayDuringRetryMs: {}", leaderConnectDelayDuringRetryMs);
+ LOG.info("TCP NoDelay set to: {}", nodelay);
+ LOG.info("{} = {}", LEARNER_ASYNC_SENDING, asyncSending);
+ LOG.info("{} = {}", LEARNER_CLOSE_SOCKET_ASYNC, closeSocketAsync);
+ }
+
+ final ConcurrentHashMap<Long, ServerCnxn> pendingRevalidations = new ConcurrentHashMap<Long, ServerCnxn>();
+
+ public int getPendingRevalidationsCount() {
+ return pendingRevalidations.size();
+ }
+
+ // for testing
+ protected static void setAsyncSending(boolean newMode) {
+ asyncSending = newMode;
+ LOG.info("{} = {}", LEARNER_ASYNC_SENDING, asyncSending);
+
+ }
+ protected static boolean getAsyncSending() {
+ return asyncSending;
+ }
+ /**
+ * validate a session for a client
+ *
+ * @param clientId
+ * the client to be revalidated
+ * @param timeout
+ * the timeout for which the session is valid
+ * @throws IOException
+ */
+ void validateSession(ServerCnxn cnxn, long clientId, int timeout) throws IOException {
+ LOG.info("Revalidating client: 0x{}", Long.toHexString(clientId));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeLong(clientId);
+ dos.writeInt(timeout);
+ dos.close();
+ QuorumPacket qp = new QuorumPacket(Leader.REVALIDATE, -1, baos.toByteArray(), null);
+ pendingRevalidations.put(clientId, cnxn);
+ if (LOG.isTraceEnabled()) {
+ ZooTrace.logTraceMessage(
+ LOG,
+ ZooTrace.SESSION_TRACE_MASK,
+ "To validate session 0x" + Long.toHexString(clientId));
+ }
+ writePacket(qp, true);
+ }
+
+ /**
+ * write a packet to the leader.
+ *
+ * This method is called by multiple threads. We need to make sure that only one thread is writing to leaderOs at a time.
+ * When packets are sent synchronously, writing is done within a synchronization block.
+ * When packets are sent asynchronously, sender.queuePacket() is called, which writes to a BlockingQueue, which is thread-safe.
+ * Reading from this BlockingQueue and writing to leaderOs is the learner sender thread only.
+ * So we have only one thread writing to leaderOs at a time in either case.
+ *
+ * @param pp
+ * the proposal packet to be sent to the leader
+ * @throws IOException
+ */
+ void writePacket(QuorumPacket pp, boolean flush) throws IOException {
+ if (asyncSending) {
+ sender.queuePacket(pp);
+ } else {
+ writePacketNow(pp, flush);
+ }
+ }
+
+ void writePacketNow(QuorumPacket pp, boolean flush) throws IOException {
+ synchronized (leaderOs) {
+ if (pp != null) {
+ messageTracker.trackSent(pp.getType());
+ leaderOs.writeRecord(pp, "packet");
+ }
+ if (flush) {
+ bufferedOutput.flush();
+ }
+ }
+ }
+
+ /**
+ * Start thread that will forward any packet in the queue to the leader
+ */
+ protected void startSendingThread() {
+ sender = new LearnerSender(this);
+ sender.start();
+ }
+
+ /**
+ * read a packet from the leader
+ *
+ * @param pp
+ * the packet to be instantiated
+ * @throws IOException
+ */
+ void readPacket(QuorumPacket pp) throws IOException {
+ synchronized (leaderIs) {
+ leaderIs.readRecord(pp, "packet");
+ messageTracker.trackReceived(pp.getType());
+ }
+ if (LOG.isTraceEnabled()) {
+ final long traceMask =
+ (pp.getType() == Leader.PING) ? ZooTrace.SERVER_PING_TRACE_MASK
+ : ZooTrace.SERVER_PACKET_TRACE_MASK;
+
+ ZooTrace.logQuorumPacket(LOG, traceMask, 'i', pp);
+ }
+ }
+
+ /**
+ * send a request packet to the leader
+ *
+ * @param request
+ * the request from the client
+ * @throws IOException
+ */
+ void request(Request request) throws IOException {
+ if (request.isThrottled()) {
+ LOG.error("Throttled request sent to leader: {}. Exiting", request);
+ ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream oa = new DataOutputStream(baos);
+ oa.writeLong(request.sessionId);
+ oa.writeInt(request.cxid);
+ oa.writeInt(request.type);
+ if (request.request != null) {
+ request.request.rewind();
+ int len = request.request.remaining();
+ byte[] b = new byte[len];
+ request.request.get(b);
+ request.request.rewind();
+ oa.write(b);
+ }
+ oa.close();
+ QuorumPacket qp = new QuorumPacket(Leader.REQUEST, -1, baos.toByteArray(), request.authInfo);
+ writePacket(qp, true);
+ }
+
+ /**
+ * Returns the address of the node we think is the leader.
+ */
+ protected QuorumServer findLeader() {
+ QuorumServer leaderServer = null;
+ // Find the leader by id
+ Vote current = self.getCurrentVote();
+ for (QuorumServer s : self.getView().values()) {
+ if (s.id == current.getId()) {
+ // Ensure we have the leader's correct IP address before
+ // attempting to connect.
+ s.recreateSocketAddresses();
+ leaderServer = s;
+ break;
+ }
+ }
+ if (leaderServer == null) {
+ LOG.warn("Couldn't find the leader with id = {}", current.getId());
+ }
+ return leaderServer;
+ }
+
+ /**
+ * Overridable helper method to return the System.nanoTime().
+ * This method behaves identical to System.nanoTime().
+ */
+ protected long nanoTime() {
+ return System.nanoTime();
+ }
+
+ /**
+ * Overridable helper method to simply call sock.connect(). This can be
+ * overriden in tests to fake connection success/failure for connectToLeader.
+ */
+ protected void sockConnect(Socket sock, InetSocketAddress addr, int timeout) throws IOException {
+ sock.connect(addr, timeout);
+ }
+
+ /**
+ * Establish a connection with the LearnerMaster found by findLearnerMaster.
+ * Followers only connect to Leaders, Observers can connect to any active LearnerMaster.
+ * Retries until either initLimit time has elapsed or 5 tries have happened.
+ * @param multiAddr - the address of the Peer to connect to.
+ * @throws IOException - if the socket connection fails on the 5th attempt
+ * if there is an authentication failure while connecting to leader
+ */
+ protected void connectToLeader(MultipleAddresses multiAddr, String hostname) throws IOException {
+
+ this.leaderAddr = multiAddr;
+ Set<InetSocketAddress> addresses;
+ if (self.isMultiAddressReachabilityCheckEnabled()) {
+ // even if none of the addresses are reachable, we want to try to establish connection
+ // see ZOOKEEPER-3758
+ addresses = multiAddr.getAllReachableAddressesOrAll();
+ } else {
+ addresses = multiAddr.getAllAddresses();
+ }
+ ExecutorService executor = Executors.newFixedThreadPool(addresses.size());
+ CountDownLatch latch = new CountDownLatch(addresses.size());
+ AtomicReference<Socket> socket = new AtomicReference<>(null);
+ addresses.stream().map(address -> new LeaderConnector(address, socket, latch)).forEach(executor::submit);
+
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ LOG.warn("Interrupted while trying to connect to Leader", e);
+ } finally {
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
+ LOG.error("not all the LeaderConnector terminated properly");
+ }
+ } catch (InterruptedException ie) {
+ LOG.error("Interrupted while terminating LeaderConnector executor.", ie);
+ }
+ }
+
+ if (socket.get() == null) {
+ throw new IOException("Failed connect to " + multiAddr);
+ } else {
+ sock = socket.get();
+ sockBeingClosed.set(false);
+ }
+
+ self.authLearner.authenticate(sock, hostname);
+
+ leaderIs = BinaryInputArchive.getArchive(new BufferedInputStream(sock.getInputStream()));
+ bufferedOutput = new BufferedOutputStream(sock.getOutputStream());
+ leaderOs = BinaryOutputArchive.getArchive(bufferedOutput);
+ if (asyncSending) {
+ startSendingThread();
+ }
+ }
+
+ class LeaderConnector implements Runnable {
+
+ private AtomicReference<Socket> socket;
+ private InetSocketAddress address;
+ private CountDownLatch latch;
+
+ LeaderConnector(InetSocketAddress address, AtomicReference<Socket> socket, CountDownLatch latch) {
+ this.address = address;
+ this.socket = socket;
+ this.latch = latch;
+ }
+
+ @Override
+ public void run() {
+ try {
+ Thread.currentThread().setName("LeaderConnector-" + address);
+ Socket sock = connectToLeader();
+
+ if (sock != null && sock.isConnected()) {
+ if (socket.compareAndSet(null, sock)) {
+ LOG.info("Successfully connected to leader, using address: {}", address);
+ } else {
+ LOG.info("Connection to the leader is already established, close the redundant connection");
+ sock.close();
+ }
+ }
+
+ } catch (Exception e) {
+ LOG.error("Failed connect to {}", address, e);
+ } finally {
+ latch.countDown();
+ }
+ }
+
+ private Socket connectToLeader() throws IOException, X509Exception, InterruptedException {
+ Socket sock = createSocket();
+
+ // leader connection timeout defaults to tickTime * initLimit
+ int connectTimeout = self.tickTime * self.initLimit;
+
+ // but if connectToLearnerMasterLimit is specified, use that value to calculate
+ // timeout instead of using the initLimit value
+ if (self.connectToLearnerMasterLimit > 0) {
+ connectTimeout = self.tickTime * self.connectToLearnerMasterLimit;
+ }
+
+ int remainingTimeout;
+ long startNanoTime = nanoTime();
+
+ for (int tries = 0; tries < 5 && socket.get() == null; tries++) {
+ try {
+ // recalculate the init limit time because retries sleep for 1000 milliseconds
+ remainingTimeout = connectTimeout - (int) ((nanoTime() - startNanoTime) / 1_000_000);
+ if (remainingTimeout <= 0) {
+ LOG.error("connectToLeader exceeded on retries.");
+ throw new IOException("connectToLeader exceeded on retries.");
+ }
+
+ sockConnect(sock, address, Math.min(connectTimeout, remainingTimeout));
+ if (self.isSslQuorum()) {
+ ((SSLSocket) sock).startHandshake();
+ }
+ sock.setTcpNoDelay(nodelay);
+ break;
+ } catch (IOException e) {
+ remainingTimeout = connectTimeout - (int) ((nanoTime() - startNanoTime) / 1_000_000);
+
+ if (remainingTimeout <= leaderConnectDelayDuringRetryMs) {
+ LOG.error(
+ "Unexpected exception, connectToLeader exceeded. tries={}, remaining init limit={}, connecting to {}",
+ tries,
+ remainingTimeout,
+ address,
+ e);
+ throw e;
+ } else if (tries >= 4) {
+ LOG.error(
+ "Unexpected exception, retries exceeded. tries={}, remaining init limit={}, connecting to {}",
+ tries,
+ remainingTimeout,
+ address,
+ e);
+ throw e;
+ } else {
+ LOG.warn(
+ "Unexpected exception, tries={}, remaining init limit={}, connecting to {}",
+ tries,
+ remainingTimeout,
+ address,
+ e);
+ sock = createSocket();
+ }
+ }
+ Thread.sleep(leaderConnectDelayDuringRetryMs);
+ }
+
+ return sock;
+ }
+ }
+
+ /**
+ * Creating a simple or and SSL socket.
+ * This can be overridden in tests to fake already connected sockets for connectToLeader.
+ */
+ protected Socket createSocket() throws X509Exception, IOException {
+ Socket sock;
+ if (self.isSslQuorum()) {
+ sock = self.getX509Util().createSSLSocket();
+ } else {
+ sock = new Socket();
+ }
+ sock.setSoTimeout(self.tickTime * self.initLimit);
+ return sock;
+ }
+
+ /**
+ * Once connected to the leader or learner master, perform the handshake
+ * protocol to establish a following / observing connection.
+ * @param pktType
+ * @return the zxid the Leader sends for synchronization purposes.
+ * @throws IOException
+ */
+ protected long registerWithLeader(int pktType) throws IOException {
+ /*
+ * Send follower info, including last zxid and sid
+ */
+ long lastLoggedZxid = self.getLastLoggedZxid();
+ QuorumPacket qp = new QuorumPacket();
+ qp.setType(pktType);
+ qp.setZxid(ZxidUtils.makeZxid(self.getAcceptedEpoch(), 0));
+
+ /*
+ * Add sid to payload
+ */
+ LearnerInfo li = new LearnerInfo(self.getId(), 0x10000, self.getQuorumVerifier().getVersion());
+ ByteArrayOutputStream bsid = new ByteArrayOutputStream();
+ BinaryOutputArchive boa = BinaryOutputArchive.getArchive(bsid);
+ boa.writeRecord(li, "LearnerInfo");
+ qp.setData(bsid.toByteArray());
+
+ writePacket(qp, true);
+ readPacket(qp);
+ final long newEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
+ if (qp.getType() == Leader.LEADERINFO) {
+ // we are connected to a 1.0 server so accept the new epoch and read the next packet
+ leaderProtocolVersion = ByteBuffer.wrap(qp.getData()).getInt();
+ byte[] epochBytes = new byte[4];
+ final ByteBuffer wrappedEpochBytes = ByteBuffer.wrap(epochBytes);
+ if (newEpoch > self.getAcceptedEpoch()) {
+ wrappedEpochBytes.putInt((int) self.getCurrentEpoch());
+ self.setAcceptedEpoch(newEpoch);
+ } else if (newEpoch == self.getAcceptedEpoch()) {
+ // since we have already acked an epoch equal to the leaders, we cannot ack
+ // again, but we still need to send our lastZxid to the leader so that we can
+ // sync with it if it does assume leadership of the epoch.
+ // the -1 indicates that this reply should not count as an ack for the new epoch
+ wrappedEpochBytes.putInt(-1);
+ } else {
+ throw new IOException("Leaders epoch, "
+ + newEpoch
+ + " is less than accepted epoch, "
+ + self.getAcceptedEpoch());
+ }
+ QuorumPacket ackNewEpoch = new QuorumPacket(Leader.ACKEPOCH, lastLoggedZxid, epochBytes, null);
+ writePacket(ackNewEpoch, true);
+ return ZxidUtils.makeZxid(newEpoch, 0);
+ } else {
+ if (newEpoch > self.getAcceptedEpoch()) {
+ self.setAcceptedEpoch(newEpoch);
+ }
+ if (qp.getType() != Leader.NEWLEADER) {
+ LOG.error("First packet should have been NEWLEADER");
+ throw new IOException("First packet should have been NEWLEADER");
+ }
+ return qp.getZxid();
+ }
+ }
+
+ /**
+ * Finally, synchronize our history with the Leader (if Follower)
+ * or the LearnerMaster (if Observer).
+ * @param newLeaderZxid
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ protected void syncWithLeader(long newLeaderZxid) throws Exception {
+ QuorumPacket ack = new QuorumPacket(Leader.ACK, 0, null, null);
+ QuorumPacket qp = new QuorumPacket();
+ long newEpoch = ZxidUtils.getEpochFromZxid(newLeaderZxid);
+
+ QuorumVerifier newLeaderQV = null;
+
+ // In the DIFF case we don't need to do a snapshot because the transactions will sync on top of any existing snapshot
+ // For SNAP and TRUNC the snapshot is needed to save that history
+ boolean snapshotNeeded = true;
+ boolean syncSnapshot = false;
+ readPacket(qp);
+ Deque<Long> packetsCommitted = new ArrayDeque<>();
+ Deque<PacketInFlight> packetsNotLogged = new ArrayDeque<>();
+ Deque<PacketInFlight> packetsNotCommitted = new ArrayDeque<>();
+ synchronized (zk) {
+ if (qp.getType() == Leader.DIFF) {
+ LOG.info("Getting a diff from the leader 0x{}", Long.toHexString(qp.getZxid()));
+ self.setSyncMode(QuorumPeer.SyncMode.DIFF);
+ if (zk.shouldForceWriteInitialSnapshotAfterLeaderElection()) {
+ LOG.info("Forcing a snapshot write as part of upgrading from an older Zookeeper. This should only happen while upgrading.");
+ snapshotNeeded = true;
+ syncSnapshot = true;
+ } else {
+ snapshotNeeded = false;
+ }
+ } else if (qp.getType() == Leader.SNAP) {
+ self.setSyncMode(QuorumPeer.SyncMode.SNAP);
+ LOG.info("Getting a snapshot from leader 0x{}", Long.toHexString(qp.getZxid()));
+ // The leader is going to dump the database
+ // db is clear as part of deserializeSnapshot()
+ zk.getZKDatabase().deserializeSnapshot(leaderIs);
+ // ZOOKEEPER-2819: overwrite config node content extracted
+ // from leader snapshot with local config, to avoid potential
+ // inconsistency of config node content during rolling restart.
+ if (!self.isReconfigEnabled()) {
+ LOG.debug("Reset config node content from local config after deserialization of snapshot.");
+ zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier());
+ }
+ String signature = leaderIs.readString("signature");
+ if (!signature.equals("BenWasHere")) {
+ LOG.error("Missing signature. Got {}", signature);
+ throw new IOException("Missing signature");
+ }
+ zk.getZKDatabase().setlastProcessedZxid(qp.getZxid());
+
+ // immediately persist the latest snapshot when there is txn log gap
+ syncSnapshot = true;
+ } else if (qp.getType() == Leader.TRUNC) {
+ //we need to truncate the log to the lastzxid of the leader
+ self.setSyncMode(QuorumPeer.SyncMode.TRUNC);
+ LOG.warn("Truncating log to get in sync with the leader 0x{}", Long.toHexString(qp.getZxid()));
+ boolean truncated = zk.getZKDatabase().truncateLog(qp.getZxid());
+ if (!truncated) {
+ // not able to truncate the log
+ LOG.error("Not able to truncate the log 0x{}", Long.toHexString(qp.getZxid()));
+ ServiceUtils.requestSystemExit(ExitCode.QUORUM_PACKET_ERROR.getValue());
+ }
+ zk.getZKDatabase().setlastProcessedZxid(qp.getZxid());
+
+ } else {
+ LOG.error("Got unexpected packet from leader: {}, exiting ... ", LearnerHandler.packetToString(qp));
+ ServiceUtils.requestSystemExit(ExitCode.QUORUM_PACKET_ERROR.getValue());
+ }
+ zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier());
+ zk.createSessionTracker();
+
+ long lastQueued = 0;
+
+ // in Zab V1.0 (ZK 3.4+) we might take a snapshot when we get the NEWLEADER message, but in pre V1.0
+ // we take the snapshot on the UPDATE message, since Zab V1.0 also gets the UPDATE (after the NEWLEADER)
+ // we need to make sure that we don't take the snapshot twice.
+ boolean isPreZAB1_0 = true;
+ //If we are not going to take the snapshot be sure the transactions are not applied in memory
+ // but written out to the transaction log
+ boolean writeToTxnLog = !snapshotNeeded;
+ TxnLogEntry logEntry;
+ // we are now going to start getting transactions to apply followed by an UPTODATE
+ outerLoop:
+ while (self.isRunning()) {
+ readPacket(qp);
+ switch (qp.getType()) {
+ case Leader.PROPOSAL:
+ PacketInFlight pif = new PacketInFlight();
+ logEntry = SerializeUtils.deserializeTxn(qp.getData());
+ pif.hdr = logEntry.getHeader();
+ pif.rec = logEntry.getTxn();
+ pif.digest = logEntry.getDigest();
+ if (pif.hdr.getZxid() != lastQueued + 1) {
+ LOG.warn(
+ "Got zxid 0x{} expected 0x{}",
+ Long.toHexString(pif.hdr.getZxid()),
+ Long.toHexString(lastQueued + 1));
+ }
+ lastQueued = pif.hdr.getZxid();
+
+ if (pif.hdr.getType() == OpCode.reconfig) {
+ SetDataTxn setDataTxn = (SetDataTxn) pif.rec;
+ QuorumVerifier qv = self.configFromString(new String(setDataTxn.getData(), UTF_8));
+ self.setLastSeenQuorumVerifier(qv, true);
+ }
+
+ packetsNotLogged.add(pif);
+ packetsNotCommitted.add(pif);
+ break;
+ case Leader.COMMIT:
+ case Leader.COMMITANDACTIVATE:
+ pif = packetsNotCommitted.peekFirst();
+ if (pif.hdr.getZxid() != qp.getZxid()) {
+ LOG.warn(
+ "Committing 0x{}, but next proposal is 0x{}",
+ Long.toHexString(qp.getZxid()),
+ Long.toHexString(pif.hdr.getZxid()));
+ } else {
+ if (qp.getType() == Leader.COMMITANDACTIVATE) {
+ QuorumVerifier qv = self.configFromString(new String(((SetDataTxn) pif.rec).getData(), UTF_8));
+ boolean majorChange = self.processReconfig(
+ qv,
+ ByteBuffer.wrap(qp.getData()).getLong(), qp.getZxid(),
+ true);
+ if (majorChange) {
+ throw new Exception("changes proposed in reconfig");
+ }
+ }
+ if (!writeToTxnLog) {
+ zk.processTxn(pif.hdr, pif.rec);
+ packetsNotLogged.remove();
+ packetsNotCommitted.remove();
+ } else {
+ packetsNotCommitted.remove();
+ packetsCommitted.add(qp.getZxid());
+ }
+ }
+ break;
+ case Leader.INFORM:
+ case Leader.INFORMANDACTIVATE:
+ PacketInFlight packet = new PacketInFlight();
+
+ if (qp.getType() == Leader.INFORMANDACTIVATE) {
+ ByteBuffer buffer = ByteBuffer.wrap(qp.getData());
+ long suggestedLeaderId = buffer.getLong();
+ byte[] remainingdata = new byte[buffer.remaining()];
+ buffer.get(remainingdata);
+ logEntry = SerializeUtils.deserializeTxn(remainingdata);
+ packet.hdr = logEntry.getHeader();
+ packet.rec = logEntry.getTxn();
+ packet.digest = logEntry.getDigest();
+ QuorumVerifier qv = self.configFromString(new String(((SetDataTxn) packet.rec).getData(), UTF_8));
+ boolean majorChange = self.processReconfig(qv, suggestedLeaderId, qp.getZxid(), true);
+ if (majorChange) {
+ throw new Exception("changes proposed in reconfig");
+ }
+ } else {
+ logEntry = SerializeUtils.deserializeTxn(qp.getData());
+ packet.rec = logEntry.getTxn();
+ packet.hdr = logEntry.getHeader();
+ packet.digest = logEntry.getDigest();
+ // Log warning message if txn comes out-of-order
+ if (packet.hdr.getZxid() != lastQueued + 1) {
+ LOG.warn(
+ "Got zxid 0x{} expected 0x{}",
+ Long.toHexString(packet.hdr.getZxid()),
+ Long.toHexString(lastQueued + 1));
+ }
+ lastQueued = packet.hdr.getZxid();
+ }
+ if (!writeToTxnLog) {
+ // Apply to db directly if we haven't taken the snapshot
+ zk.processTxn(packet.hdr, packet.rec);
+ } else {
+ packetsNotLogged.add(packet);
+ packetsCommitted.add(qp.getZxid());
+ }
+
+ break;
+ case Leader.UPTODATE:
+ LOG.info("Learner received UPTODATE message");
+ if (newLeaderQV != null) {
+ boolean majorChange = self.processReconfig(newLeaderQV, null, null, true);
+ if (majorChange) {
+ throw new Exception("changes proposed in reconfig");
+ }
+ }
+ if (isPreZAB1_0) {
+ zk.takeSnapshot(syncSnapshot);
+ self.setCurrentEpoch(newEpoch);
+ }
+ self.setZooKeeperServer(zk);
+ self.adminServer.setZooKeeperServer(zk);
+ break outerLoop;
+ case Leader.NEWLEADER: // Getting NEWLEADER here instead of in discovery
+ // means this is Zab 1.0
+ LOG.info("Learner received NEWLEADER message");
+ if (qp.getData() != null && qp.getData().length > 1) {
+ try {
+ QuorumVerifier qv = self.configFromString(new String(qp.getData(), UTF_8));
+ self.setLastSeenQuorumVerifier(qv, true);
+ newLeaderQV = qv;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (snapshotNeeded) {
+ zk.takeSnapshot(syncSnapshot);
+ }
+
+ self.setCurrentEpoch(newEpoch);
+ writeToTxnLog = true;
+ //Anything after this needs to go to the transaction log, not applied directly in memory
+ isPreZAB1_0 = false;
+
+ // ZOOKEEPER-3911: make sure sync the uncommitted logs before commit them (ACK NEWLEADER).
+ sock.setSoTimeout(self.tickTime * self.syncLimit);
+ self.setSyncMode(QuorumPeer.SyncMode.NONE);
+ zk.startupWithoutServing();
+ if (zk instanceof FollowerZooKeeperServer) {
+ FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
+ fzk.syncProcessor.setDelayForwarding(true);
+ for (PacketInFlight p : packetsNotLogged) {
+ fzk.logRequest(p.hdr, p.rec, p.digest);
+ }
+ packetsNotLogged.clear();
+ fzk.syncProcessor.syncFlush();
+ }
+
+ writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true);
+
+ if (zk instanceof FollowerZooKeeperServer) {
+ FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
+ fzk.syncProcessor.setDelayForwarding(false);
+ fzk.syncProcessor.syncFlush();
+ }
+ break;
+ }
+ }
+ }
+ ack.setZxid(ZxidUtils.makeZxid(newEpoch, 0));
+ writePacket(ack, true);
+ zk.startServing();
+ /*
+ * Update the election vote here to ensure that all members of the
+ * ensemble report the same vote to new servers that start up and
+ * send leader election notifications to the ensemble.
+ *
+ * @see https://issues.apache.org/jira/browse/ZOOKEEPER-1732
+ */
+ self.updateElectionVote(newEpoch);
+
+ // We need to log the stuff that came in between the snapshot and the uptodate
+ if (zk instanceof FollowerZooKeeperServer) {
+ FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
+ for (PacketInFlight p : packetsNotLogged) {
+ fzk.logRequest(p.hdr, p.rec, p.digest);
+ }
+ for (Long zxid : packetsCommitted) {
+ fzk.commit(zxid);
+ }
+ } else if (zk instanceof ObserverZooKeeperServer) {
+ // Similar to follower, we need to log requests between the snapshot
+ // and UPTODATE
+ ObserverZooKeeperServer ozk = (ObserverZooKeeperServer) zk;
+ for (PacketInFlight p : packetsNotLogged) {
+ Long zxid = packetsCommitted.peekFirst();
+ if (p.hdr.getZxid() != zxid) {
+ // log warning message if there is no matching commit
+ // old leader send outstanding proposal to observer
+ LOG.warn(
+ "Committing 0x{}, but next proposal is 0x{}",
+ Long.toHexString(zxid),
+ Long.toHexString(p.hdr.getZxid()));
+ continue;
+ }
+ packetsCommitted.remove();
+ Request request = new Request(null, p.hdr.getClientId(), p.hdr.getCxid(), p.hdr.getType(), null, null);
+ request.setTxn(p.rec);
+ request.setHdr(p.hdr);
+ request.setTxnDigest(p.digest);
+ ozk.commitRequest(request);
+ }
+ } else {
+ // New server type need to handle in-flight packets
+ throw new UnsupportedOperationException("Unknown server type");
+ }
+ }
+
+ protected void revalidate(QuorumPacket qp) throws IOException {
+ ByteArrayInputStream bis = new ByteArrayInputStream(qp.getData());
+ DataInputStream dis = new DataInputStream(bis);
+ long sessionId = dis.readLong();
+ boolean valid = dis.readBoolean();
+ ServerCnxn cnxn = pendingRevalidations.remove(sessionId);
+ if (cnxn == null) {
+ LOG.warn("Missing session 0x{} for validation", Long.toHexString(sessionId));
+ } else {
+ zk.finishSessionInit(cnxn, valid);
+ }
+ if (LOG.isTraceEnabled()) {
+ ZooTrace.logTraceMessage(
+ LOG,
+ ZooTrace.SESSION_TRACE_MASK,
+ "Session 0x" + Long.toHexString(sessionId) + " is valid: " + valid);
+ }
+ }
+
+ protected void ping(QuorumPacket qp) throws IOException {
+ // Send back the ping with our session data
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+ Map<Long, Integer> touchTable = zk.getTouchSnapshot();
+ for (Entry<Long, Integer> entry : touchTable.entrySet()) {
+ dos.writeLong(entry.getKey());
+ dos.writeInt(entry.getValue());
+ }
+
+ QuorumPacket pingReply = new QuorumPacket(qp.getType(), qp.getZxid(), bos.toByteArray(), qp.getAuthinfo());
+ writePacket(pingReply, true);
+ }
+
+ /**
+ * Shutdown the Peer
+ */
+ public void shutdown() {
+ self.setZooKeeperServer(null);
+ self.closeAllConnections();
+ self.adminServer.setZooKeeperServer(null);
+
+ if (sender != null) {
+ sender.shutdown();
+ }
+
+ closeSocket();
+ // shutdown previous zookeeper
+ if (zk != null) {
+ // If we haven't finished SNAP sync, force fully shutdown
+ // to avoid potential inconsistency
+ zk.shutdown(self.getSyncMode().equals(QuorumPeer.SyncMode.SNAP));
+ }
+ }
+
+ boolean isRunning() {
+ return self.isRunning() && zk.isRunning();
+ }
+
+ void closeSocket() {
+ if (sock != null) {
+ if (sockBeingClosed.compareAndSet(false, true)) {
+ if (closeSocketAsync) {
+ final Thread closingThread = new Thread(() -> closeSockSync(), "CloseSocketThread(sid:" + zk.getServerId());
+ closingThread.setDaemon(true);
+ closingThread.start();
+ } else {
+ closeSockSync();
+ }
+ }
+ }
+ }
+
+ void closeSockSync() {
+ try {
+ long startTime = Time.currentElapsedTime();
+ if (sock != null) {
+ sock.close();
+ sock = null;
+ }
+ ServerMetrics.getMetrics().SOCKET_CLOSING_TIME.add(Time.currentElapsedTime() - startTime);
+ } catch (IOException e) {
+ LOG.warn("Ignoring error closing connection to leader", e);
+ }
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java b/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java
index c1399e53083..3b7a9dfc331 100644
--- a/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java
+++ b/zookeeper-server/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java
@@ -67,7 +67,7 @@ public class SendAckRequestProcessor implements RequestProcessor, Flushable {
LOG.warn("Closing connection to leader, exception during packet send", e);
try {
Socket socket = learner.sock;
- if ( socket != null && ! learner.sock.isClosed()) {
+ if (socket != null && !socket.isClosed()) {
learner.sock.close();
}
} catch (IOException e1) {
diff --git a/zookeeper-server/zookeeper-server/src/test/java/com/yahoo/vespa/zookeper/VespaZooKeeperTest.java b/zookeeper-server/zookeeper-server/src/test/java/com/yahoo/vespa/zookeper/VespaZooKeeperTest.java
index db643d76e0d..3a125d72a89 100644
--- a/zookeeper-server/zookeeper-server/src/test/java/com/yahoo/vespa/zookeper/VespaZooKeeperTest.java
+++ b/zookeeper-server/zookeeper-server/src/test/java/com/yahoo/vespa/zookeper/VespaZooKeeperTest.java
@@ -38,6 +38,9 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
+/**
+ * @author jonmv
+ */
public class VespaZooKeeperTest {
static final Path tempDirRoot = getTmpDir();
@@ -45,18 +48,17 @@ public class VespaZooKeeperTest {
/**
* Performs dynamic reconfiguration of ZooKeeper servers.
- *
+ * <p>
* First, a cluster of 3 servers is set up, and some data is written to it.
* Then, 3 new servers are added, and the first 3 marked for retirement;
* this should force the quorum to move the 3 new servers, but not disconnect the old ones.
* Next, the old servers are removed.
* Then, the cluster is reduced to size 1.
* Finally, the cluster grows to size 3 again.
- *
+ * <p>
* Throughout all of this, quorum should remain, and the data should remain the same.
*/
@Test(timeout = 120_000)
- @Ignore // Unstable, some ZK server keeps resetting connections sometimes.
public void testReconfiguration() throws ExecutionException, InterruptedException, IOException, KeeperException, TimeoutException {
List<ZooKeeper> keepers = new ArrayList<>();
for (int i = 0; i < 8; i++) keepers.add(new ZooKeeper());
@@ -126,7 +128,7 @@ public class VespaZooKeeperTest {
static String writeData(ZookeeperServerConfig config) throws IOException, InterruptedException, KeeperException {
try (ZooKeeperAdmin admin = createAdmin(config)) {
List<ACL> acl = ZooDefs.Ids.OPEN_ACL_UNSAFE;
- String node = admin.create("/test-node", "hi".getBytes(UTF_8), acl, CreateMode.EPHEMERAL_SEQUENTIAL);
+ String node = admin.create("/test-node", "hi".getBytes(UTF_8), acl, CreateMode.PERSISTENT_SEQUENTIAL);
String read = new String(admin.getData(node, false, new Stat()), UTF_8);
assertEquals("hi", read);
return node;