diff options
15 files changed, 122 insertions, 90 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java index 89fa560e035..516a1aa265f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java @@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.dns; import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; import com.yahoo.yolean.Exceptions; -import java.util.Collections; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -20,22 +20,15 @@ import java.util.logging.Logger; * * @author mpolden */ -public class NameServiceQueue { +public record NameServiceQueue(List<NameServiceRequest> requests) { public static final NameServiceQueue EMPTY = new NameServiceQueue(List.of()); private static final Logger log = Logger.getLogger(NameServiceQueue.class.getName()); - private final LinkedList<NameServiceRequest> requests; - /** DO NOT USE. Public for serialization purposes */ public NameServiceQueue(List<NameServiceRequest> requests) { - this.requests = new LinkedList<>(Objects.requireNonNull(requests, "requests must be non-null")); - } - - /** Returns a view of requests in this queue */ - public List<NameServiceRequest> requests() { - return Collections.unmodifiableList(requests); + this.requests = List.copyOf(Objects.requireNonNull(requests, "requests must be non-null")); } /** Returns a copy of this containing the last n requests */ @@ -50,13 +43,18 @@ public class NameServiceQueue { /** Returns a copy of this with given request queued according to priority */ public NameServiceQueue with(NameServiceRequest request, Priority priority) { - var queue = new NameServiceQueue(this.requests); - if (priority == Priority.high) { - queue.requests.addFirst(request); - } else { - queue.requests.add(request); + List<NameServiceRequest> copy = new ArrayList<>(this.requests.size() + 1); + switch (priority) { + case normal -> { + copy.addAll(this.requests); + copy.add(request); + } + case high -> { + copy.add(request); + copy.addAll(this.requests); + } } - return queue; + return new NameServiceQueue(copy); } /** Returns a copy of this with given request added */ @@ -73,19 +71,18 @@ public class NameServiceQueue { requireNonNegative(n); if (requests.isEmpty()) return this; - var queue = new NameServiceQueue(requests); - for (int i = 0; i < n && !queue.requests.isEmpty(); i++) { - var request = queue.requests.peek(); + LinkedList<NameServiceRequest> copy = new LinkedList<>(requests); + for (int i = 0; i < n && !copy.isEmpty(); i++) { + var request = copy.peek(); try { request.dispatchTo(nameService); - queue.requests.poll(); + copy.poll(); } catch (Exception e) { log.log(Level.WARNING, "Failed to execute " + request + ": " + Exceptions.toMessageString(e) + ", request will be retried"); } } - - return queue; + return new NameServiceQueue(copy); } @Override diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java index ab2e0312b15..9c6ab32a338 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java @@ -150,7 +150,7 @@ public class ControllerMaintenance extends AbstractComponent { this.jobRunner = duration(system.isCd() ? 45 : 90, SECONDS); this.osVersionStatusUpdater = duration(2, MINUTES); this.osUpgrader = duration(1, MINUTES); - this.osUpgradeScheduler = duration(3, HOURS); + this.osUpgradeScheduler = duration(15, MINUTES); this.contactInformationMaintainer = duration(12, HOURS); this.nameServiceDispatcher = duration(10, SECONDS); this.costReportMaintainer = duration(2, HOURS); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java index 19616f862ab..964d4228f9a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java @@ -38,7 +38,7 @@ public class NameServiceDispatcher extends ControllerMaintainer { var queue = db.readNameServiceQueue(); var instant = clock.instant(); var remaining = queue.dispatchTo(nameService, requestCount); - if (queue == remaining) return 1.0; // Queue unchanged + if (queue.equals(remaining)) return 1.0; // Queue unchanged var dispatched = queue.first(requestCount); if (!dispatched.requests().isEmpty()) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java index e37f5e895c0..3fd2a262e2e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java @@ -63,16 +63,10 @@ public class NameServiceQueueSerializer { root.field(requestsField).traverse((ArrayTraverser) (i, object) -> { var request = Request.valueOf(object.field(requestType).asString()); switch (request) { - case createRecords: - items.add(createRecordsFromSlime(object)); - break; - case createRecord: - items.add(createRecordFromSlime(object)); - break; - case removeRecords: - items.add(removeRecordsFromSlime(object)); - break; - default: throw new IllegalArgumentException("No serialization defined for request " + request); + case createRecords -> items.add(createRecordsFromSlime(object)); + case createRecord -> items.add(createRecordFromSlime(object)); + case removeRecords -> items.add(removeRecordsFromSlime(object)); + default -> throw new IllegalArgumentException("No serialization defined for request " + request); } }); return new NameServiceQueue(items); 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 341cba60519..10bf0d2aa4f 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 @@ -2061,7 +2061,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { VersionStatus versionStatus = controller.readVersionStatus(); if (version.equals(Version.emptyVersion)) version = controller.systemVersion(versionStatus); - if (!versionStatus.isActive(version)) + if ( ! versionStatus.isActive(version) && ! isOperator(request)) throw new IllegalArgumentException("Cannot trigger deployment of version '" + version + "': " + "Version is not active in this system. " + "Active versions: " + versionStatus.versions() @@ -3043,9 +3043,23 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private void ensureApplicationExists(TenantAndApplicationId id, HttpRequest request) { if (controller.applications().getApplication(id).isEmpty()) { - log.fine("Application does not exist in public, creating: " + id); - var credentials = accessControlRequests.credentials(id.tenant(), null /* not used on public */ , request.getJDiscRequest()); - controller.applications().createApplication(id, credentials); + if (controller.system().isPublic() || hasOktaContext(request)) { + log.fine("Application does not exist in public, creating: " + id); + var credentials = accessControlRequests.credentials(id.tenant(), null /* not used on public */ , request.getJDiscRequest()); + controller.applications().createApplication(id, credentials); + } else { + log.fine("Application does not exist in hosted, failing: " + id); + throw new IllegalArgumentException("Application does not exist. Create application in Console first."); + } + } + } + + private boolean hasOktaContext(HttpRequest request) { + try { + OAuthCredentials.fromOktaRequestContext(request.getJDiscRequest().context()); + return true; + } catch (IllegalArgumentException e) { + return false; } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java index 2ecd63546e6..05dd8257ad0 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java @@ -60,12 +60,12 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler { public HttpResponse auditAndHandle(HttpRequest request) { try { var path = new Path(request.getUri()); - switch (request.getMethod()) { - case GET: return get(path, request); - case POST: return post(path, request); - case DELETE: return delete(path, request); - default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); - } + return switch (request.getMethod()) { + case GET -> get(path, request); + case POST -> post(path, request); + case DELETE -> delete(path, request); + default -> ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); + }; } catch (IllegalArgumentException e) { return ErrorResponse.badRequest(Exceptions.toMessageString(e)); } catch (RuntimeException e) { @@ -106,7 +106,7 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler { List<DeploymentId> deployments = endpoints.stream() .flatMap(e -> e.deployments().stream()) .distinct() - .collect(Collectors.toList()); + .toList(); Map<DeploymentId, RoutingStatus> deploymentsStatus = deployments.stream() .collect(Collectors.toMap( @@ -256,8 +256,7 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler { EndpointList declaredEndpoints = controller.routing().declaredEndpointsOf(application); for (var instance : instances) { var zones = zoneId == null - ? instance.deployments().keySet().stream().sorted(Comparator.comparing(ZoneId::value)) - .collect(Collectors.toList()) + ? instance.deployments().keySet().stream().sorted(Comparator.comparing(ZoneId::value)).toList() : List.of(zoneId); for (var zone : zones) { DeploymentId deploymentId = requireDeployment(new DeploymentId(instance.id(), zone), instance); @@ -350,28 +349,26 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler { } private static String asString(RoutingStatus.Value value) { - switch (value) { - case in: return "in"; - case out: return "out"; - default: return "unknown"; - } + return switch (value) { + case in -> "in"; + case out -> "out"; + }; } private static String asString(RoutingStatus.Agent agent) { - switch (agent) { - case operator: return "operator"; - case system: return "system"; - case tenant: return "tenant"; - default: return "unknown"; - } + return switch (agent) { + case operator -> "operator"; + case system -> "system"; + case tenant -> "tenant"; + case unknown -> "unknown"; + }; } private static String asString(RoutingMethod method) { - switch (method) { - case exclusive: return "exclusive"; - case sharedLayer4: return "sharedLayer4"; - default: return "unknown"; - } + return switch (method) { + case exclusive -> "exclusive"; + case sharedLayer4 -> "sharedLayer4"; + }; } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index ed4f0597fad..0f03333146f 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -102,6 +102,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -1647,7 +1648,7 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - void create_application_on_deploy() { + void create_application_on_deploy_with_okta() { // Setup createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); @@ -1669,13 +1670,42 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploy/dev-us-east-1/", POST) .data(entity) .oAuthCredentials(OKTA_CREDENTIALS) - .userIdentity(USER_ID), - "{\"message\":\"Deployment started in run 1 of dev-us-east-1 for tenant1.application1.instance1. This may take about 15 minutes the first time.\",\"run\":1}"); + """ + {"message":"Deployment started in run 1 of dev-us-east-1 for tenant1.application1.instance1. This may take about 15 minutes the first time.","run":1}"""); assertTrue(tester.controller().applications().getApplication(appId).isPresent()); } + @Test + void create_application_on_deploy_with_athenz() { + // Setup + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); + + // Create tenant + tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") + .oAuthCredentials(OKTA_CREDENTIALS), + new File("tenant-without-applications.json")); + + // Deploy application + var id = ApplicationId.from("tenant1", "application1", "instance1"); + var appId = TenantAndApplicationId.from(id); + var entity = createApplicationDeployData(applicationPackageInstance1); + + assertTrue(tester.controller().applications().getApplication(appId).isEmpty()); + + // POST (deploy) an application to start a manual deployment to dev + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploy/dev-us-east-1/", POST) + .data(entity) + .userIdentity(USER_ID), + """ + {"error-code":"BAD_REQUEST","message":"Application does not exist. Create application in Console first."}""", 400); + + assertFalse(tester.controller().applications().getApplication(appId).isPresent()); + } + private static String serializeInstant(Instant i) { return DateTimeFormatter.ISO_INSTANT.format(i.truncatedTo(ChronoUnit.SECONDS)); } diff --git a/searchlib/src/vespa/searchcommon/common/schema.cpp b/searchlib/src/vespa/searchcommon/common/schema.cpp index 0cc9d3cb2d8..1f2f924a4cd 100644 --- a/searchlib/src/vespa/searchcommon/common/schema.cpp +++ b/searchlib/src/vespa/searchcommon/common/schema.cpp @@ -6,6 +6,7 @@ #include <vespa/vespalib/stllike/asciistream.h> #include <vespa/vespalib/stllike/hashtable.hpp> #include <vespa/fastos/file.h> +#include <limits> #include <vespa/log/log.h> LOG_SETUP(".index.schema"); @@ -48,7 +49,7 @@ writeFieldSets(vespalib::asciistream &os, struct FieldName { vespalib::string name; - FieldName(const config::StringVector & lines) + explicit FieldName(const config::StringVector & lines) : name(ConfigParser::parse<vespalib::string>("name", lines)) { } @@ -184,9 +185,9 @@ Schema::FieldSet::FieldSet(const config::StringVector & lines) : _name(ConfigParser::parse<vespalib::string>("name", lines)), _fields() { - std::vector<FieldName> fn = ConfigParser::parseArray<std::vector<FieldName>>("field", lines); - for (size_t i = 0; i < fn.size(); ++i) { - _fields.push_back(fn[i].name); + auto fn = ConfigParser::parseArray<std::vector<FieldName>>("field", lines); + for (const auto & fname : fn) { + _fields.push_back(fname.name); } } @@ -224,8 +225,8 @@ Schema::Schema() = default; Schema::Schema(const Schema & rhs) = default; Schema & Schema::operator=(const Schema & rhs) = default; -Schema::Schema(Schema && rhs) = default; -Schema & Schema::operator=(Schema && rhs) = default; +Schema::Schema(Schema && rhs) noexcept = default; +Schema & Schema::operator=(Schema && rhs) noexcept = default; Schema::~Schema() = default; bool diff --git a/searchlib/src/vespa/searchcommon/common/schema.h b/searchlib/src/vespa/searchcommon/common/schema.h index 42291e04634..2e9edaa702e 100644 --- a/searchlib/src/vespa/searchcommon/common/schema.h +++ b/searchlib/src/vespa/searchcommon/common/schema.h @@ -5,7 +5,6 @@ #include "datatype.h" #include <vespa/config/common/types.h> #include <vespa/vespalib/stllike/hash_map.h> -#include <vespa/vespalib/util/ptrholder.h> namespace vespalib { class asciistream; } namespace search::index { @@ -90,7 +89,7 @@ public: /** * Create this index field based on the given config lines. **/ - IndexField(const config::StringVector &lines); + explicit IndexField(const config::StringVector &lines); IndexField &setAvgElemLen(uint32_t avgElemLen) { _avgElemLen = avgElemLen; return *this; } IndexField &set_interleaved_features(bool value) { @@ -121,7 +120,7 @@ public: std::vector<vespalib::string> _fields; public: - FieldSet(vespalib::stringref n) : _name(n), _fields() {} + explicit FieldSet(vespalib::stringref n) : _name(n), _fields() {} FieldSet(const FieldSet &); FieldSet & operator =(const FieldSet &); FieldSet(FieldSet &&) noexcept = default; @@ -130,12 +129,12 @@ public: /** * Create this field collection based on the given config lines. **/ - FieldSet(const config::StringVector & lines); + explicit FieldSet(const config::StringVector & lines); ~FieldSet(); FieldSet &addField(vespalib::stringref fieldName) { - _fields.push_back(fieldName); + _fields.emplace_back(fieldName); return *this; } @@ -170,8 +169,8 @@ public: Schema(); Schema(const Schema & rhs); Schema & operator=(const Schema & rhs); - Schema(Schema && rhs); - Schema & operator=(Schema && rhs); + Schema(Schema && rhs) noexcept; + Schema & operator=(Schema && rhs) noexcept; ~Schema(); /** diff --git a/searchlib/src/vespa/searchlib/docstore/filechunk.h b/searchlib/src/vespa/searchlib/docstore/filechunk.h index 2a2cbb45c53..af5690e8569 100644 --- a/searchlib/src/vespa/searchlib/docstore/filechunk.h +++ b/searchlib/src/vespa/searchlib/docstore/filechunk.h @@ -12,7 +12,6 @@ #include <vespa/vespalib/util/cpu_usage.h> #include <vespa/vespalib/util/generationhandler.h> #include <vespa/vespalib/util/memoryusage.h> -#include <vespa/vespalib/util/ptrholder.h> #include <vespa/vespalib/util/time.h> class FastOS_FileInterface; diff --git a/slobrok/src/vespa/slobrok/cfg.h b/slobrok/src/vespa/slobrok/cfg.h index bd7bffe1d15..9514081ca38 100644 --- a/slobrok/src/vespa/slobrok/cfg.h +++ b/slobrok/src/vespa/slobrok/cfg.h @@ -1,7 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once -#include <vespa/vespalib/util/ptrholder.h> #include <vespa/config-slobroks.h> #include <vespa/config/subscription/configuri.h> #include <vespa/config/subscription/confighandle.h> @@ -14,7 +13,7 @@ namespace slobrok { class Configurable { public: virtual void setup(const std::vector<std::string> &slobrokSpecs) = 0; - virtual ~Configurable() { } + virtual ~Configurable() = default; }; diff --git a/storage/src/vespa/storage/distributor/operations/external/getoperation.cpp b/storage/src/vespa/storage/distributor/operations/external/getoperation.cpp index 06872cadde6..df7bfe52904 100644 --- a/storage/src/vespa/storage/distributor/operations/external/getoperation.cpp +++ b/storage/src/vespa/storage/distributor/operations/external/getoperation.cpp @@ -6,6 +6,7 @@ #include <vespa/vdslib/state/nodestate.h> #include <vespa/document/fieldvalue/document.h> #include <vespa/storage/distributor/distributor_bucket_space.h> +#include <vespa/document/bucket/bucketidfactory.h> #include <cassert> #include <vespa/log/log.h> @@ -47,7 +48,7 @@ GetOperation::GroupId::operator==(const GroupId& other) const GetOperation::GetOperation(const DistributorNodeContext& node_ctx, const DistributorBucketSpace &bucketSpace, - std::shared_ptr<BucketDatabase::ReadGuard> read_guard, + const std::shared_ptr<BucketDatabase::ReadGuard> & read_guard, std::shared_ptr<api::GetCommand> msg, PersistenceOperationMetricSet& metric, api::InternalReadConsistency desired_read_consistency) @@ -135,7 +136,7 @@ GetOperation::onStart(DistributorStripeMessageSender& sender) LOG(debug, "No useful bucket copies for get on document %s. Returning without document", _msg->getDocumentId().toString().c_str()); sendReply(sender); } -}; +} void GetOperation::onReceive(DistributorStripeMessageSender& sender, const std::shared_ptr<api::StorageReply>& msg) @@ -251,9 +252,7 @@ GetOperation::assignTargetNodeGroups(const BucketDatabase::ReadGuard& read_guard auto entries = read_guard.find_parents_and_self(bid); - for (uint32_t j = 0; j < entries.size(); ++j) { - const BucketDatabase::Entry& e = entries[j]; - + for (const auto & e : entries) { LOG(spam, "Entry for %s: %s", e.getBucketId().toString().c_str(), e->toString().c_str()); diff --git a/storage/src/vespa/storage/distributor/operations/external/getoperation.h b/storage/src/vespa/storage/distributor/operations/external/getoperation.h index 943a92f4b36..61443100695 100644 --- a/storage/src/vespa/storage/distributor/operations/external/getoperation.h +++ b/storage/src/vespa/storage/distributor/operations/external/getoperation.h @@ -28,7 +28,7 @@ class GetOperation : public Operation public: GetOperation(const DistributorNodeContext& node_ctx, const DistributorBucketSpace &bucketSpace, - std::shared_ptr<BucketDatabase::ReadGuard> read_guard, + const std::shared_ptr<BucketDatabase::ReadGuard> & read_guard, std::shared_ptr<api::GetCommand> msg, PersistenceOperationMetricSet& metric, api::InternalReadConsistency desired_read_consistency = api::InternalReadConsistency::Strong); diff --git a/vdslib/src/vespa/vdslib/state/nodestate.h b/vdslib/src/vespa/vdslib/state/nodestate.h index f65a0275d4b..913d4d93be8 100644 --- a/vdslib/src/vespa/vdslib/state/nodestate.h +++ b/vdslib/src/vespa/vdslib/state/nodestate.h @@ -11,7 +11,7 @@ #pragma once #include "state.h" -#include <vespa/document/bucket/bucketidfactory.h> +#include <vespa/document/util/printable.h> #include <vespa/vespalib/objects/floatingpointtype.h> #include <memory> diff --git a/vespalib/src/vespa/vespalib/util/ptrholder.h b/vespalib/src/vespa/vespalib/util/ptrholder.h index de2c4b6941e..eed0a4d2052 100644 --- a/vespalib/src/vespa/vespalib/util/ptrholder.h +++ b/vespalib/src/vespa/vespalib/util/ptrholder.h @@ -46,7 +46,7 @@ public: * std::shared_ptr instances are used internally to track shared * resources **/ - virtual ~PtrHolder() {} + virtual ~PtrHolder(); /** * @brief Check if the current value is set (not 0) @@ -124,4 +124,7 @@ public: } }; +template<typename T> +PtrHolder<T>::~PtrHolder() = default; + } // namespace vespalib |