summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java2
-rw-r--r--config-model/src/test/derived/neuralnet_noqueryprofile/neuralnet.sd6
-rw-r--r--config-model/src/test/derived/neuralnet_noqueryprofile/rank-profiles.cfg12
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mail.java12
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java33
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java6
-rw-r--r--integration/intellij/build.gradle2
-rw-r--r--integration/intellij/pom.xml2
-rw-r--r--integration/intellij/src/main/bnf/ai/vespa/intellij/schema/parser/sd.bnf8
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java28
-rw-r--r--searchsummary/src/tests/docsummary/attributedfw/attributedfw_test.cpp3
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp246
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h2
-rw-r--r--vespalib/CMakeLists.txt3
-rw-r--r--vespalib/src/apps/vespa-tsan-digest/.gitignore1
-rw-r--r--vespalib/src/apps/vespa-tsan-digest/CMakeLists.txt9
-rw-r--r--vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp278
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/Lock.java43
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockAttempt.java2
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockStats.java23
-rw-r--r--zkfacade/src/main/java/com/yahoo/vespa/curator/stats/ThreadLockStats.java15
23 files changed, 598 insertions, 153 deletions
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
index 893595befd3..029c0efb55f 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
@@ -773,7 +773,7 @@ public class RankProfile implements Cloneable {
input.getValue() + ", with type " + input.getValue() + "" +
" but this input is already defined with type " + existingType +
" in another profile this inherits");
- inputs.put(input.getKey(), input.getValue());
+ allInputs.put(input.getKey(), input.getValue());
}
}
allInputs.putAll(inputs);
diff --git a/config-model/src/test/derived/neuralnet_noqueryprofile/neuralnet.sd b/config-model/src/test/derived/neuralnet_noqueryprofile/neuralnet.sd
index 4241e9dab85..9069f59bbe3 100644
--- a/config-model/src/test/derived/neuralnet_noqueryprofile/neuralnet.sd
+++ b/config-model/src/test/derived/neuralnet_noqueryprofile/neuralnet.sd
@@ -69,7 +69,7 @@ search neuralnet {
}
- rank-profile defaultRankProfile inherits default {
+ rank-profile default {
inputs {
query(W_0) tensor(x[9],hidden[9])
@@ -80,6 +80,10 @@ search neuralnet {
query(b_out) tensor(out[1])
}
+ }
+
+ rank-profile defaultRankProfile inherits default {
+
constants {
maxSignedSixtyFourBitInteger: 9223372036854775807
}
diff --git a/config-model/src/test/derived/neuralnet_noqueryprofile/rank-profiles.cfg b/config-model/src/test/derived/neuralnet_noqueryprofile/rank-profiles.cfg
index 9c3cfd28b9a..f5134dd15f9 100644
--- a/config-model/src/test/derived/neuralnet_noqueryprofile/rank-profiles.cfg
+++ b/config-model/src/test/derived/neuralnet_noqueryprofile/rank-profiles.cfg
@@ -1,4 +1,16 @@
rankprofile[].name "default"
+rankprofile[].fef.property[].name "vespa.type.query.W_0"
+rankprofile[].fef.property[].value "tensor(hidden[9],x[9])"
+rankprofile[].fef.property[].name "vespa.type.query.b_0"
+rankprofile[].fef.property[].value "tensor(hidden[9])"
+rankprofile[].fef.property[].name "vespa.type.query.W_1"
+rankprofile[].fef.property[].value "tensor(hidden[9],out[9])"
+rankprofile[].fef.property[].name "vespa.type.query.b_1"
+rankprofile[].fef.property[].value "tensor(out[9])"
+rankprofile[].fef.property[].name "vespa.type.query.W_out"
+rankprofile[].fef.property[].value "tensor(out[9])"
+rankprofile[].fef.property[].name "vespa.type.query.b_out"
+rankprofile[].fef.property[].value "tensor(out[1])"
rankprofile[].name "unranked"
rankprofile[].fef.property[].name "vespa.rank.firstphase"
rankprofile[].fef.property[].value "value(0)"
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mail.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mail.java
index b586b97ddf0..36b6e251fbc 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mail.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mail.java
@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Objects;
+import java.util.Optional;
/**
* A message with a subject and a nonempty set of recipients.
@@ -16,18 +17,29 @@ public class Mail {
private final Collection<String> recipients;
private final String subject;
private final String message;
+ private final Optional<String> htmlMessage;
public Mail(Collection<String> recipients, String subject, String message) {
+ this(recipients, subject, message, Optional.empty());
+ }
+
+ public Mail(Collection<String> recipients, String subject, String message, String htmlMessage) {
+ this(recipients, subject, message, Optional.of(htmlMessage));
+ }
+
+ Mail(Collection<String> recipients, String subject, String message, Optional<String> htmlMessage) {
if (recipients.isEmpty())
throw new IllegalArgumentException("Empty recipient list is not allowed.");
recipients.forEach(Objects::requireNonNull);
this.recipients = ImmutableList.copyOf(recipients);
this.subject = Objects.requireNonNull(subject);
this.message = Objects.requireNonNull(message);
+ this.htmlMessage = Objects.requireNonNull(htmlMessage);
}
public Collection<String> recipients() { return recipients; }
public String subject() { return subject; }
public String message() { return message; }
+ public Optional<String> htmlMessage() { return htmlMessage; }
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
index 935ba17eed6..7ac7a36d742 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.api.integration.zone;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
@@ -85,6 +86,9 @@ public interface ZoneRegistry {
URI dashboardUrl(TenantName id);
/** Returns a URL which displays information about the given application. */
+ URI dashboardUrl(TenantName tenantName, ApplicationName applicationName);
+
+ /** Returns a URL which displays information about the given application instance. */
URI dashboardUrl(ApplicationId id);
/** Returns a URL which displays information about the given job run. */
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 91d127976ce..b0966f7db21 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
@@ -438,8 +438,15 @@ public class JobController {
});
}
finally {
- for (Mutex lock : locks)
- lock.close();
+ for (Mutex lock : locks) {
+ try {
+ lock.close();
+ } catch (Throwable t) {
+ log.log(WARNING, "Failed to close the lock " + lock + ": the lock may or may not " +
+ "have been released in ZooKeeper, and if not this controller " +
+ "must be restarted to release the lock", t);
+ }
+ }
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java
index 7692752f3ca..6b14872b07d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notify/Notifier.java
@@ -95,27 +95,38 @@ public class Notifier {
private Mail mailOf(Notification n, Collection<String> recipients) {
var source = n.source();
- var subject = Text.format("[%s] %s Vespa Notification for %s - %s", n.level().toString().toUpperCase(), n.type().name(), source.tenant(), source.application());
+ var subject = Text.format("[%s] %s Vespa Notification for %s", n.level().toString().toUpperCase(), n.type().name(), applicationIdSource(source));
var body = new StringBuilder();
body.append("Source: ").append(n.source().toString()).append("\n")
.append("\n")
.append(String.join("\n", n.messages()))
.append("\n")
.append(url(source).toString());
- return new Mail(recipients, subject.toString(), body.toString());
+ return new Mail(recipients, subject, body.toString());
+ }
+
+ private String applicationIdSource(NotificationSource source) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(source.tenant().value());
+ source.application().ifPresent(applicationName -> sb.append(".").append(applicationName.value()));
+ source.instance().ifPresent(instanceName -> sb.append(".").append(instanceName.value()));
+ return sb.toString();
}
private URI url(NotificationSource source) {
- if (source.application().isPresent() && source.instance().isPresent()) {
- if (source.jobType().isPresent() && source.runNumber().isPresent()) {
- return zoneRegistry.dashboardUrl(
- new RunId(ApplicationId.from(source.tenant(),
- source.application().get(),
- source.instance().get()),
- source.jobType().get(),
- source.runNumber().getAsLong()));
+ if (source.application().isPresent()) {
+ if (source.instance().isPresent()) {
+ if (source.jobType().isPresent() && source.runNumber().isPresent()) {
+ return zoneRegistry.dashboardUrl(
+ new RunId(ApplicationId.from(source.tenant(),
+ source.application().get(),
+ source.instance().get()),
+ source.jobType().get(),
+ source.runNumber().getAsLong()));
+ }
+ return zoneRegistry.dashboardUrl(ApplicationId.from(source.tenant(), source.application().get(), source.instance().get()));
}
- return zoneRegistry.dashboardUrl(ApplicationId.from(source.tenant(), source.application().get(), source.instance().get()));
+ return zoneRegistry.dashboardUrl(source.tenant(), source.application().get());
}
return zoneRegistry.dashboardUrl(source.tenant());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
index a4b17239626..5b8e25cbfe8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.integration;
import com.yahoo.component.AbstractComponent;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
@@ -200,6 +201,11 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
}
@Override
+ public URI dashboardUrl(TenantName tenantName, ApplicationName applicationName) {
+ return URI.create("https://dashboard.tld/" + tenantName + "/" + applicationName);
+ }
+
+ @Override
public URI dashboardUrl(TenantName tenantName) {
return URI.create("https://dashboard.tld/" + tenantName);
}
diff --git a/integration/intellij/build.gradle b/integration/intellij/build.gradle
index 6bc385a983c..1c2dae46d87 100644
--- a/integration/intellij/build.gradle
+++ b/integration/intellij/build.gradle
@@ -36,7 +36,7 @@ compileJava {
}
group 'ai.vespa'
-version '1.1.4' // Also update pom.xml version if this is changed
+version '1.1.5' // Also update pom.xml version if this is changed
sourceCompatibility = 11
diff --git a/integration/intellij/pom.xml b/integration/intellij/pom.xml
index 07488ed3d93..1c973b84d37 100644
--- a/integration/intellij/pom.xml
+++ b/integration/intellij/pom.xml
@@ -9,7 +9,7 @@
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>vespa-intellij</artifactId> <!-- Not used - plugin is build by gradle -->
- <version>1.1.4</version> <!-- See copy-zip below, which depends on this being the same as the v. in build.gradle -->
+ <version>1.1.5</version> <!-- See copy-zip below, which depends on this being the same as the v. in build.gradle -->
<description>
Maven wrapper for the gradle build of this IntelliJ plugin.
</description>
diff --git a/integration/intellij/src/main/bnf/ai/vespa/intellij/schema/parser/sd.bnf b/integration/intellij/src/main/bnf/ai/vespa/intellij/schema/parser/sd.bnf
index cb4bc024c86..b0e0e5e61dc 100644
--- a/integration/intellij/src/main/bnf/ai/vespa/intellij/schema/parser/sd.bnf
+++ b/integration/intellij/src/main/bnf/ai/vespa/intellij/schema/parser/sd.bnf
@@ -111,7 +111,7 @@ AnnotationFieldDefinition ::= field IdentifierVal type FieldTypeName '{' '}'
// The *Expr alternatives are consumed greedily so order matters.
//-------------------------
RankingExpression ::= LiteralTensorExpr | FilePathExpr | ParenthesisedExpr | BooleanExpr | ArithmeticExpr | IfFunctionExpr |
- QueryDefinitionExpr | FunctionCallExpr | InListRankingExpr | PrimitiveExpr | SliceExpr | LambdaExpr
+ QueryDefinitionExpr | FunctionCallOrLambdaExpr | InListRankingExpr | PrimitiveExpr | SliceExpr
FilePathExpr ::= file ':' (FilePath | WordWrapper)
@@ -129,7 +129,9 @@ ArithmeticOperator ::= '+' | '-' | '*' | '/' | '%' | '^' | "||" | "&&"
QueryDefinitionExpr ::= QueryDefinition | ItemRawScoreDefinition
-FunctionCallExpr ::= IdentifierWithDashVal '(' RankingExpression (COMMA RankingExpression)* ')' ('.' IdentifierWithDashVal)?
+// Rough parsing but hard to do better due to greediness: If this is a lambda arg expressions must be identifiers
+FunctionCallOrLambdaExpr ::= IdentifierWithDashVal '(' RankingExpression (COMMA RankingExpression)* ')' ('.' IdentifierWithDashVal)?
+ ParenthesisedExpr? // This turns the function call into a lambda
ParenthesisedExpr ::= '(' RankingExpression ')'
@@ -152,7 +154,7 @@ TensorValue ::= CellAddress ':' RankingExpression
CellAddress ::= Label | FullTensorAddress
-LambdaExp ::= FunctionCallExpr ParenthesisedExpr
+LambdaExpr ::= IdentifierWithDashVal '(' IdentifierVal (COMMA IdentifierVal)* ')' ParenthesisedExpr
//-------------------------
//-- Rank Profile rules ---
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 75977da369c..61e777a9576 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
@@ -27,9 +27,6 @@ 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;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.ConvergenceException;
-import com.yahoo.vespa.hosted.node.admin.task.util.file.FileFinder;
-import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
-import com.yahoo.vespa.hosted.node.admin.task.util.fs.ContainerPath;
import java.time.Clock;
import java.time.Duration;
@@ -140,31 +137,6 @@ public class NodeAgentImpl implements NodeAgent {
if (loopThread != null)
throw new IllegalStateException("Can not re-start a node agent.");
- // TODO: Remove after this has rolled out everywhere
- int[] stats = new int[]{0, 0, 0};
- ContainerPath vespaHome = initialContext.paths().underVespaHome("");
- FileFinder.files(initialContext.paths().of("/")).forEachPath(path -> {
- UnixPath unixPath = new UnixPath(path);
-
- String permissions = unixPath.getPermissions();
- if (!permissions.endsWith("---")) {
- unixPath.setPermissions(permissions.substring(0, 6) + "---");
- stats[0]++;
- }
-
- if (path.startsWith(vespaHome) && unixPath.getOwnerId() != initialContext.users().vespa().uid()) {
- unixPath.setOwnerId(initialContext.users().vespa().uid());
- stats[1]++;
- }
-
- if (path.startsWith(vespaHome) && unixPath.getGroupId() != initialContext.users().vespa().gid()) {
- unixPath.setGroupId(initialContext.users().vespa().gid());
- stats[2]++;
- }
- });
- if (stats[0] + stats[1] + stats[2] > 0)
- initialContext.log(logger, "chmod %d, chown UID %d, chown GID %d files", stats[0], stats[1], stats[2]);
-
loopThread = new Thread(() -> {
while (!terminated.get()) {
try {
diff --git a/searchsummary/src/tests/docsummary/attributedfw/attributedfw_test.cpp b/searchsummary/src/tests/docsummary/attributedfw/attributedfw_test.cpp
index 9f6ca8f6b57..67d505582d8 100644
--- a/searchsummary/src/tests/docsummary/attributedfw/attributedfw_test.cpp
+++ b/searchsummary/src/tests/docsummary/attributedfw/attributedfw_test.cpp
@@ -59,6 +59,8 @@ public:
}
_writer = AttributeDFWFactory::create(_attrs.mgr(), field_name, filter_elements, _matching_elems_fields);
_writer->setIndex(0);
+ EXPECT_TRUE(_writer->setFieldWriterStateIndex(0));
+ _state._fieldWriterStates.resize(1);
_field_name = field_name;
_state._attributes.resize(1);
_state._attributes[0] = _state._attrCtx->getAttribute(field_name);
@@ -77,6 +79,7 @@ public:
_callback.clear();
_callback.add_matching_elements(docid, _field_name, matching_elems);
_state._matching_elements = std::unique_ptr<MatchingElements>();
+ _state._fieldWriterStates[0] = nullptr; // Force new state to pick up changed matching elements
expect_field(exp_slime_as_json, docid);
}
};
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp
index 448feedac80..eb4f1a19e06 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/attributedfw.cpp
@@ -3,12 +3,12 @@
#include "attributedfw.h"
#include "docsumstate.h"
#include "docsumwriter.h"
+#include "docsum_field_writer_state.h"
#include <vespa/eval/eval/value.h>
#include <vespa/eval/eval/value_codec.h>
-#include <vespa/searchcommon/attribute/iattributecontext.h>
+#include <vespa/searchcommon/attribute/i_multi_value_attribute.h>
+#include <vespa/searchcommon/attribute/multi_value_traits.h>
#include <vespa/searchlib/attribute/iattributemanager.h>
-#include <vespa/searchlib/attribute/integerbase.h>
-#include <vespa/searchlib/attribute/stringbase.h>
#include <vespa/searchlib/common/matching_elements.h>
#include <vespa/searchlib/common/matching_elements_fields.h>
#include <vespa/searchlib/tensor/i_tensor_attribute.h>
@@ -23,6 +23,8 @@ using namespace search;
using search::attribute::BasicType;
using search::attribute::IAttributeContext;
using search::attribute::IAttributeVector;
+using search::attribute::IMultiValueAttribute;
+using search::attribute::IMultiValueReadView;
using vespalib::Memory;
using vespalib::slime::Cursor;
using vespalib::slime::Inserter;
@@ -140,145 +142,219 @@ SingleAttrDFW::insertField(uint32_t docid, GetDocsumsState * state, ResType type
//-----------------------------------------------------------------------------
-template <typename DataType>
-class MultiAttrDFW : public AttrDFW {
-private:
- bool _is_weighted_set;
- bool _filter_elements;
- std::shared_ptr<MatchingElementsFields> _matching_elems_fields;
+template <typename MultiValueType>
+const IMultiValueReadView<MultiValueType>*
+make_read_view(const IAttributeVector& attribute, vespalib::Stash& stash)
+{
+ auto multi_value_attribute = attribute.as_multi_value_attribute();
+ if (multi_value_attribute != nullptr) {
+ return multi_value_attribute->make_read_view(IMultiValueAttribute::MultiValueTag<MultiValueType>(), stash);
+ }
+ return nullptr;
+}
+class EmptyWriterState : public DocsumFieldWriterState
+{
public:
- explicit MultiAttrDFW(const vespalib::string& attr_name, bool is_weighted_set,
- bool filter_elements, std::shared_ptr<MatchingElementsFields> matching_elems_fields)
- : AttrDFW(attr_name),
- _is_weighted_set(is_weighted_set),
- _filter_elements(filter_elements),
- _matching_elems_fields(std::move(matching_elems_fields))
- {
- if (filter_elements && _matching_elems_fields) {
- _matching_elems_fields->add_field(attr_name);
- }
- }
- void insertField(uint32_t docid, GetDocsumsState* state, ResType type, Inserter& target) override;
+ EmptyWriterState() = default;
+ ~EmptyWriterState() = default;
+ void insertField(uint32_t, Inserter&) override { }
};
-void
-set(const vespalib::string & value, Symbol itemSymbol, Cursor & cursor)
+template <typename MultiValueType>
+class MultiAttrDFWState : public DocsumFieldWriterState
{
- cursor.setString(itemSymbol, value);
-}
+ const vespalib::string& _field_name;
+ const IMultiValueReadView<MultiValueType>* _read_view;
+ const MatchingElements* _matching_elements;
+public:
+ MultiAttrDFWState(const vespalib::string& field_name, const IAttributeVector& attr, vespalib::Stash& stash, const MatchingElements* matching_elements);
+ ~MultiAttrDFWState() override;
+ void insertField(uint32_t docid, Inserter& target) override;
+};
-void
-append(const IAttributeVector::WeightedString & element, Cursor& arr)
-{
- arr.addString(element.getValue());
-}
-void
-set(int64_t value, Symbol itemSymbol, Cursor & cursor)
+template <typename MultiValueType>
+MultiAttrDFWState<MultiValueType>::MultiAttrDFWState(const vespalib::string& field_name, const IAttributeVector& attr, vespalib::Stash& stash, const MatchingElements* matching_elements)
+ : _field_name(field_name),
+ _read_view(make_read_view<MultiValueType>(attr, stash)),
+ _matching_elements(matching_elements)
{
- cursor.setLong(itemSymbol, value);
}
-void
-append(const IAttributeVector::WeightedInt & element, Cursor& arr)
-{
- arr.addLong(element.getValue());
-}
+template <typename MultiValueType>
+MultiAttrDFWState<MultiValueType>::~MultiAttrDFWState() = default;
+template <typename V>
void
-set(double value, Symbol itemSymbol, Cursor & cursor)
+set_value(V value, Symbol item_symbol, Cursor& cursor)
{
- cursor.setDouble(itemSymbol, value);
+ if constexpr (std::is_same_v<V, const char*>) {
+ cursor.setString(item_symbol, value);
+ } else if constexpr(std::is_floating_point_v<V>) {
+ cursor.setDouble(item_symbol, value);
+ } else {
+ cursor.setLong(item_symbol, value);
+ }
}
+template <typename V>
void
-append(const IAttributeVector::WeightedFloat & element, Cursor& arr)
+append_value(V value, Cursor& arr)
{
- arr.addDouble(element.getValue());
+ if constexpr (std::is_same_v<V, const char*>) {
+ arr.addString(value);
+ } else if constexpr(std::is_floating_point_v<V>) {
+ arr.addDouble(value);
+ } else {
+ arr.addLong(value);
+ }
}
Memory ITEM("item");
Memory WEIGHT("weight");
-template <typename DataType>
+template <typename MultiValueType>
void
-MultiAttrDFW<DataType>::insertField(uint32_t docid, GetDocsumsState* state, ResType, Inserter& target)
+MultiAttrDFWState<MultiValueType>::insertField(uint32_t docid, Inserter& target)
{
- const auto& attr = get_attribute(*state);
- uint32_t entries = attr.getValueCount(docid);
- if (entries == 0) {
- return; // Don't insert empty fields
+ using ValueType = multivalue::ValueType_t<MultiValueType>;
+ if (!_read_view) {
+ return;
}
+ auto elements = _read_view->get_values(docid);
+ if (elements.empty()) {
+ return;
+ }
+ Cursor &arr = target.insertArray(elements.size());
- std::vector<DataType> elements(entries);
- entries = std::min(entries, attr.get(docid, elements.data(), entries));
- Cursor &arr = target.insertArray(entries);
-
- if (_filter_elements) {
- const auto& matching_elems = state->get_matching_elements(*_matching_elems_fields)
- .get_matching_elements(docid, getAttributeName());
- if (!matching_elems.empty() && matching_elems.back() < entries) {
- if (_is_weighted_set) {
+ if (_matching_elements) {
+ const auto& matching_elems = _matching_elements->get_matching_elements(docid, _field_name);
+ if (!matching_elems.empty() && matching_elems.back() < elements.size()) {
+ if constexpr (multivalue::is_WeightedValue_v<MultiValueType>) {
Symbol itemSymbol = arr.resolve(ITEM);
Symbol weightSymbol = arr.resolve(WEIGHT);
for (uint32_t id_to_keep : matching_elems) {
- const DataType & element = elements[id_to_keep];
+ auto& element = elements[id_to_keep];
Cursor& elemC = arr.addObject();
- set(element.getValue(), itemSymbol, elemC);
- elemC.setLong(weightSymbol, element.getWeight());
+ set_value<ValueType>(element.value(), itemSymbol, elemC);
+ elemC.setLong(weightSymbol, element.weight());
}
} else {
for (uint32_t id_to_keep : matching_elems) {
- append(elements[id_to_keep], arr);
+ append_value<ValueType>(elements[id_to_keep], arr);
}
}
}
} else {
- if (_is_weighted_set) {
+ if constexpr (multivalue::is_WeightedValue_v<MultiValueType>) {
Symbol itemSymbol = arr.resolve(ITEM);
Symbol weightSymbol = arr.resolve(WEIGHT);
for (const auto & element : elements) {
Cursor& elemC = arr.addObject();
- set(element.getValue(), itemSymbol, elemC);
- elemC.setLong(weightSymbol, element.getWeight());
+ set_value<ValueType>(element.value(), itemSymbol, elemC);
+ elemC.setLong(weightSymbol, element.weight());
}
} else {
for (const auto & element : elements) {
- append(element, arr);
+ append_value<ValueType>(element, arr);
}
}
}
}
-std::unique_ptr<IDocsumFieldWriter>
-create_multi_writer(const IAttributeVector& attr,
- bool filter_elements,
- std::shared_ptr<MatchingElementsFields> matching_elems_fields)
+class MultiAttrDFW : public AttrDFW {
+private:
+ bool _filter_elements;
+ uint32_t _state_index; // index into _fieldWriterStates in GetDocsumsState
+ std::shared_ptr<MatchingElementsFields> _matching_elems_fields;
+
+public:
+ explicit MultiAttrDFW(const vespalib::string& attr_name, bool filter_elements, std::shared_ptr<MatchingElementsFields> matching_elems_fields)
+ : AttrDFW(attr_name),
+ _filter_elements(filter_elements),
+ _matching_elems_fields(std::move(matching_elems_fields))
+ {
+ if (filter_elements && _matching_elems_fields) {
+ _matching_elems_fields->add_field(attr_name);
+ }
+ }
+ bool setFieldWriterStateIndex(uint32_t fieldWriterStateIndex) override;
+ void insertField(uint32_t docid, GetDocsumsState* state, ResType type, Inserter& target) override;
+};
+
+bool
+MultiAttrDFW::setFieldWriterStateIndex(uint32_t fieldWriterStateIndex)
+{
+ _state_index = fieldWriterStateIndex;
+ return true;
+}
+
+template <typename DataType>
+DocsumFieldWriterState*
+make_field_writer_state_helper(const vespalib::string& field_name, const IAttributeVector& attr, vespalib::Stash& stash, const MatchingElements* matching_elements)
{
- auto type = attr.getBasicType();
bool is_weighted_set = attr.hasWeightedSetType();
+ if (is_weighted_set) {
+ return &stash.create<MultiAttrDFWState<multivalue::WeightedValue<DataType>>>(field_name, attr, stash, matching_elements);
+ } else {
+ return &stash.create<MultiAttrDFWState<DataType>>(field_name, attr, stash, matching_elements);
+ }
+}
+
+DocsumFieldWriterState*
+make_field_writer_state(const vespalib::string& field_name, const IAttributeVector& attr, vespalib::Stash& stash, const MatchingElements* matching_elements)
+{
+ auto type = attr.getBasicType();
switch (type) {
- case BasicType::NONE:
- case BasicType::STRING: {
- return std::make_unique<MultiAttrDFW<IAttributeVector::WeightedString>>(attr.getName(), is_weighted_set,
- filter_elements, std::move(matching_elems_fields));
+ case BasicType::Type::STRING:
+ return make_field_writer_state_helper<const char*>(field_name, attr, stash, matching_elements);
+ case BasicType::Type::INT8:
+ return make_field_writer_state_helper<int8_t>(field_name, attr, stash, matching_elements);
+ case BasicType::Type::INT16:
+ return make_field_writer_state_helper<int16_t>(field_name, attr, stash, matching_elements);
+ case BasicType::Type::INT32:
+ return make_field_writer_state_helper<int32_t>(field_name, attr, stash, matching_elements);
+ case BasicType::Type::INT64:
+ return make_field_writer_state_helper<int64_t>(field_name, attr, stash, matching_elements);
+ case BasicType::Type::FLOAT:
+ return make_field_writer_state_helper<float>(field_name, attr, stash, matching_elements);
+ case BasicType::Type::DOUBLE:
+ return make_field_writer_state_helper<double>(field_name, attr, stash, matching_elements);
+ default:
+ ;
+ }
+ return &stash.create<EmptyWriterState>();
+}
+
+void
+MultiAttrDFW::insertField(uint32_t docid, GetDocsumsState *state, ResType, vespalib::slime::Inserter &target)
+{
+ auto& field_writer_state = state->_fieldWriterStates[_state_index];
+ if (!field_writer_state) {
+ const MatchingElements *matching_elements = nullptr;
+ if (_filter_elements) {
+ matching_elements = &state->get_matching_elements(*_matching_elems_fields);
+ }
+ const auto& attr = get_attribute(*state);
+ field_writer_state = make_field_writer_state(getAttributeName(), attr, state->get_stash(), matching_elements);
}
- case BasicType::BOOL:
- case BasicType::UINT2:
- case BasicType::UINT4:
+ field_writer_state->insertField(docid, target);
+}
+
+std::unique_ptr<IDocsumFieldWriter>
+create_multi_writer(const IAttributeVector& attr, bool filter_elements, std::shared_ptr<MatchingElementsFields> matching_elems_fields)
+{
+ auto type = attr.getBasicType();
+ switch (type) {
+ case BasicType::STRING:
case BasicType::INT8:
case BasicType::INT16:
case BasicType::INT32:
- case BasicType::INT64: {
- return std::make_unique<MultiAttrDFW<IAttributeVector::WeightedInt>>(attr.getName(), is_weighted_set,
- filter_elements, std::move(matching_elems_fields));
- }
+ case BasicType::INT64:
case BasicType::FLOAT:
- case BasicType::DOUBLE: {
- return std::make_unique<MultiAttrDFW<IAttributeVector::WeightedFloat>>(attr.getName(), is_weighted_set,
- filter_elements, std::move(matching_elems_fields));
- }
+ case BasicType::DOUBLE:
+ return std::make_unique<MultiAttrDFW>(attr.getName(), filter_elements, std::move(matching_elems_fields));
default:
// should not happen
LOG(error, "Bad value for attribute type: %u", type);
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
index 1127af6d6bd..c25aca15200 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
@@ -92,7 +92,7 @@ public:
// used by RankFeaturesDFW
FeatureSet::SP _rankFeatures;
- // Used by AttributeCombinerDFW when filtering is enabled
+ // Used by AttributeCombinerDFW and MultiAttrDFW when filtering is enabled
std::unique_ptr<search::MatchingElements> _matching_elements;
GetDocsumsState(const GetDocsumsState &) = delete;
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt
index c3c7fee2cef..7796ce7df28 100644
--- a/vespalib/CMakeLists.txt
+++ b/vespalib/CMakeLists.txt
@@ -12,8 +12,9 @@ vespa_define_module(
APPS
src/apps/make_fixture_macros
src/apps/vespa-detect-hostname
- src/apps/vespa-validate-hostname
src/apps/vespa-drop-file-from-cache
+ src/apps/vespa-tsan-digest
+ src/apps/vespa-validate-hostname
TESTS
src/tests/alloc
diff --git a/vespalib/src/apps/vespa-tsan-digest/.gitignore b/vespalib/src/apps/vespa-tsan-digest/.gitignore
new file mode 100644
index 00000000000..ca770e0e9c3
--- /dev/null
+++ b/vespalib/src/apps/vespa-tsan-digest/.gitignore
@@ -0,0 +1 @@
+/vespa-tsan-digest
diff --git a/vespalib/src/apps/vespa-tsan-digest/CMakeLists.txt b/vespalib/src/apps/vespa-tsan-digest/CMakeLists.txt
new file mode 100644
index 00000000000..3214d833783
--- /dev/null
+++ b/vespalib/src/apps/vespa-tsan-digest/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_vespa-tsan-digest_app
+ SOURCES
+ tsan_digest.cpp
+ OUTPUT_NAME vespa-tsan-digest
+ INSTALL bin
+ DEPENDS
+ vespalib
+)
diff --git a/vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp b/vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp
new file mode 100644
index 00000000000..bebb32ac1ec
--- /dev/null
+++ b/vespalib/src/apps/vespa-tsan-digest/tsan_digest.cpp
@@ -0,0 +1,278 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/stllike/string.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/util/size_literals.h>
+#include <xxhash.h>
+#include <cassert>
+#include <vector>
+#include <map>
+#include <memory>
+#include <algorithm>
+#include <unistd.h>
+#include <string.h>
+
+using vespalib::make_string_short::fmt;
+
+constexpr auto npos = vespalib::string::npos;
+
+//-----------------------------------------------------------------------------
+
+size_t trace_limit = 7;
+
+//-----------------------------------------------------------------------------
+
+class Hasher {
+private:
+ XXH3_state_t *_state;
+public:
+ Hasher() : _state(XXH3_createState()) { assert(_state != nullptr && "Out of memory!"); }
+ ~Hasher() { XXH3_freeState(_state); }
+ void reset() { XXH3_64bits_reset(_state); }
+ void update(const char *buf, size_t len) { XXH3_64bits_update(_state, buf, len); }
+ uint64_t get() const { return XXH3_64bits_digest(_state); }
+};
+
+uint64_t get_hash(const std::vector<vespalib::string> &list) {
+ static Hasher hasher;
+ hasher.reset();
+ for (const auto &item: list) {
+ hasher.update(item.data(), item.size());
+ }
+ return hasher.get();
+}
+
+//-----------------------------------------------------------------------------
+
+class StackTrace {
+private:
+ vespalib::string _heading;
+ std::vector<vespalib::string> _frames;
+ uint64_t _hash;
+public:
+ StackTrace(const vespalib::string &heading) noexcept
+ : _heading(heading), _frames(), _hash() {}
+ void add_frame(const vespalib::string &frame) {
+ _frames.push_back(frame);
+ }
+ void done() { _hash = get_hash(_frames); }
+ uint64_t hash() const { return _hash; }
+ void dump(FILE *dst) const {
+ fprintf(dst, "%s\n", _heading.c_str());
+ for (const auto &frame: _frames) {
+ fprintf(dst, "%s\n", frame.c_str());
+ }
+ fprintf(dst, "\n");
+ }
+};
+
+vespalib::string make_trace_heading(const vespalib::string &line) {
+ auto pos = line.find(" at 0x");
+ if ((pos != npos) && (line.find("Location") == npos)) {
+ return line.substr(0, pos);
+ }
+ return line;
+}
+
+std::vector<StackTrace> extract_traces(const std::vector<vespalib::string> &lines, size_t cutoff) {
+ std::vector<StackTrace> result;
+ for (size_t i = 1; (i < lines.size()) && (result.size() < cutoff); ++i) {
+ auto pos = lines[i].find("#0 ");
+ if (pos != npos) {
+ size_t start = i;
+ result.emplace_back(make_trace_heading(lines[i - 1]));
+ result.back().add_frame(lines[i]);
+ for (i = i + 1; i < lines.size(); ++i) {
+ if (((i - start) > trace_limit) ||
+ (lines[i].find("#") == npos))
+ {
+ break;
+ }
+ result.back().add_frame(lines[i]);
+ }
+ result.back().done();
+ }
+ }
+ return result;
+};
+
+//-----------------------------------------------------------------------------
+
+enum class ReportType { UNKNOWN, RACE };
+
+ReportType detect_report_type(const std::vector<vespalib::string> &lines) {
+ for (const auto &line: lines) {
+ if (starts_with(line, "WARNING: ThreadSanitizer: data race")) {
+ return ReportType::RACE;
+ }
+ }
+ return ReportType::UNKNOWN;
+}
+
+//-----------------------------------------------------------------------------
+
+bool is_delimiter(const vespalib::string &line) {
+ // TSAN delimiter is 18=, look for at least 16=
+ return (line.find("================") < line.size());
+}
+
+void dump_delimiter(FILE *dst) {
+ fprintf(dst, "==================\n");
+}
+
+//-----------------------------------------------------------------------------
+
+struct Report {
+ using UP = std::unique_ptr<Report>;
+ virtual vespalib::string make_key() const = 0;
+ virtual void add(Report::UP report) = 0;
+ virtual size_t count() const = 0;
+ virtual void dump(FILE *dst) const = 0;
+ virtual ~Report() {}
+};
+
+class RawReport : public Report {
+private:
+ std::vector<vespalib::string> _lines;
+public:
+ RawReport(const std::vector<vespalib::string> &lines)
+ : _lines(lines) {}
+ vespalib::string make_key() const override {
+ return fmt("raw:%zu", get_hash(_lines));
+ }
+ void add(Report::UP) override {
+ fprintf(stderr, "WARNING: hash collision for raw report\n");
+ }
+ size_t count() const override { return 1; }
+ void dump(FILE *dst) const override {
+ for (const auto &line: _lines) {
+ fprintf(dst, "%s\n", line.c_str());
+ }
+ }
+};
+
+class RaceReport : public Report {
+private:
+ StackTrace _trace1;
+ StackTrace _trace2;
+ size_t _total;
+ size_t _inverted;
+
+public:
+ RaceReport(const StackTrace &trace1, const StackTrace &trace2)
+ : _trace1(trace1), _trace2(trace2), _total(1), _inverted(0) {}
+
+ vespalib::string make_key() const override {
+ if (_trace2.hash() < _trace1.hash()) {
+ return fmt("race:%zu,%zu", _trace2.hash(), _trace1.hash());
+ }
+ return fmt("race:%zu,%zu", _trace1.hash(), _trace2.hash());
+ }
+ void add(Report::UP report) override {
+ // should have correct type due to key prefix
+ const RaceReport &rhs = dynamic_cast<RaceReport&>(*report);
+ ++_total;
+ if (_trace1.hash() != rhs._trace1.hash()) {
+ ++_inverted;
+ }
+ }
+ size_t count() const override { return _total; }
+ void dump(FILE *dst) const override {
+ fprintf(dst, "WARNING: ThreadSanitizer: data race\n");
+ _trace1.dump(dst);
+ _trace2.dump(dst);
+ fprintf(dst, "INFO: total: %zu (inverted: %zu)\n", _total, _inverted);
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+size_t total_reports = 0;
+std::map<vespalib::string,Report::UP> reports;
+
+void handle_report(std::unique_ptr<Report> report) {
+ ++total_reports;
+ auto [pos, first] = reports.try_emplace(report->make_key(), std::move(report));
+ if (!first) {
+ assert(report && "should still be valid");
+ pos->second->add(std::move(report));
+ }
+}
+
+void make_report(const std::vector<vespalib::string> &lines) {
+ auto type = detect_report_type(lines);
+ if (type == ReportType::RACE) {
+ auto traces = extract_traces(lines, 2);
+ if (traces.size() == 2) {
+ return handle_report(std::make_unique<RaceReport>(traces[0], traces[1]));
+ }
+ }
+ return handle_report(std::make_unique<RawReport>(lines));
+}
+
+void handle_line(const vespalib::string &line) {
+ static bool inside = false;
+ static std::vector<vespalib::string> lines;
+ if (is_delimiter(line)) {
+ inside = !inside;
+ if (!inside && !lines.empty()) {
+ make_report(lines);
+ lines.clear();
+ }
+ } else if (inside) {
+ lines.push_back(line);
+ }
+}
+
+void read_input() {
+ char buf[64_Ki];
+ bool eof = false;
+ vespalib::string line;
+ while (!eof) {
+ ssize_t res = read(STDIN_FILENO, buf, sizeof(buf));
+ if (res < 0) {
+ throw fmt("error reading stdin: %s", strerror(errno));
+ }
+ eof = (res == 0);
+ for (int i = 0; i < res; ++i) {
+ if (buf[i] == '\n') {
+ handle_line(line);
+ line.clear();
+ } else {
+ line.push_back(buf[i]);
+ }
+ }
+ }
+ if (!line.empty()) {
+ handle_line(line);
+ }
+}
+
+void write_output() {
+ std::vector<Report*> list;
+ list.reserve(reports.size());
+ for (const auto &[key, value]: reports) {
+ list.push_back(value.get());
+ }
+ std::sort(list.begin(), list.end(),
+ [](const auto &a, const auto &b) {
+ return (a->count() > b->count());
+ });
+ for (const auto *report: list) {
+ dump_delimiter(stdout);
+ report->dump(stdout);
+ dump_delimiter(stdout);
+ }
+ fprintf(stderr, "%zu reports in, %zu reports out\n", total_reports, reports.size());
+}
+
+int main(int, char **) {
+ try {
+ read_input();
+ write_output();
+ } catch (vespalib::string &err) {
+ fprintf(stderr, "%s\n", err.c_str());
+ return 1;
+ }
+ return 0;
+}
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/Lock.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/Lock.java
index 6f914a8e9a3..05294a5435b 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/Lock.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/Lock.java
@@ -9,6 +9,8 @@ import com.yahoo.vespa.curator.stats.ThreadLockStats;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -21,6 +23,10 @@ import java.util.concurrent.TimeUnit;
*/
public class Lock implements Mutex {
+ private final Object monitor = new Object();
+ private long nextSequenceNumber = 0;
+ private final Map<Long, Long> reentriesByThreadId = new HashMap<>();
+
private final InterProcessLock mutex;
private final String lockPath;
@@ -52,14 +58,43 @@ public class Lock implements Mutex {
throw new UncheckedTimeoutException("Timed out after waiting " + timeout +
" to acquire lock '" + lockPath + "'");
}
- threadLockStats.lockAcquired();
+
+ invoke(+1L, threadLockStats::lockAcquired);
+ }
+
+ @FunctionalInterface
+ private interface TriConsumer {
+ void accept(String lockId, long reentryCountDiff, Map<Long, Long> reentriesByThreadId);
+ }
+
+ // TODO(hakon): Remove once debugging is unnecessary
+ private void invoke(long reentryCountDiff, TriConsumer consumer) {
+ long threadId = Thread.currentThread().getId();
+ final long sequenceNumber;
+ final Map<Long, Long> reentriesByThreadIdCopy;
+ synchronized (monitor) {
+ sequenceNumber = nextSequenceNumber++;
+ reentriesByThreadId.merge(threadId, reentryCountDiff, (oldValue, argumentValue) -> {
+ long sum = oldValue + argumentValue /* == reentryCountDiff */;
+ if (sum == 0) {
+ // Remove from map
+ return null;
+ } else {
+ return sum;
+ }
+ });
+ reentriesByThreadIdCopy = Map.copyOf(reentriesByThreadId);
+ }
+
+ String lockId = Integer.toHexString(System.identityHashCode(this));
+ consumer.accept(lockId, sequenceNumber, reentriesByThreadIdCopy);
}
@Override
public void close() {
ThreadLockStats threadLockStats = LockStats.getForCurrentThread();
// Update metrics now before release() to avoid double-counting time in locked state.
- threadLockStats.preRelease();
+ invoke(-1L, threadLockStats::preRelease);
try {
mutex.release();
threadLockStats.postRelease();
@@ -72,6 +107,10 @@ public class Lock implements Mutex {
protected String lockPath() { return lockPath; }
+ @Override
+ public String toString() {
+ return "Lock{" + lockPath + "}";
+ }
}
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockAttempt.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockAttempt.java
index 887e2cd2700..1bbd3c7c734 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockAttempt.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockAttempt.java
@@ -66,7 +66,7 @@ public class LockAttempt {
public String getLockPath() { return lockPath; }
public Instant getTimeAcquiredWasInvoked() { return callAcquireInstant; }
public Duration getAcquireTimeout() { return timeout; }
- public boolean getReentry() { return reentry; }
+ public boolean isReentry() { return reentry; }
public LockState getLockState() { return lockState; }
public Optional<Instant> getTimeLockWasAcquired() { return lockAcquiredInstant; }
public boolean isAcquiring() { return lockAcquiredInstant.isEmpty(); }
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockStats.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockStats.java
index e4f78a4f9e9..ecb344dedb9 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockStats.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/LockStats.java
@@ -64,28 +64,33 @@ public class LockStats {
}
/** Must be invoked only after the first and non-reentry acquisition of the lock. */
- void notifyOfThreadHoldingLock(Thread currentThread, String lockPath) {
+ void notifyOfThreadHoldingLock(Thread currentThread, String lockPath, String lockId,
+ long sequenceNumber, Map<Long, Long> reentriesByThreadId) {
Thread oldThread = lockPathsHeld.put(lockPath, currentThread);
if (oldThread != null) {
getLockMetrics(lockPath).incrementAcquireWithoutReleaseCount();
- logger.warning("Thread " + currentThread.getName() +
- " reports it has the lock on " + lockPath + ", but thread " + oldThread.getName() +
- " has not reported it released the lock");
+ logger.warning("Thread " + currentThread.getName() + " reports it has the lock on " +
+ lockPath + ", but thread " + oldThread.getName() +
+ " has not reported it released the lock. " + lockId + "#" + sequenceNumber +
+ ", reentries by thread ID = " + reentriesByThreadId);
}
}
/** Must be invoked only before the last and non-reentry release of the lock. */
- void notifyOfThreadReleasingLock(Thread currentThread, String lockPath) {
+ void notifyOfThreadReleasingLock(Thread currentThread, String lockPath, String lockId,
+ long sequenceNumber, Map<Long, Long> reentriesByThreadId) {
Thread oldThread = lockPathsHeld.remove(lockPath);
if (oldThread == null) {
getLockMetrics(lockPath).incrementNakedReleaseCount();
- logger.warning("Thread " + currentThread.getName() +
- " is releasing the lock " + lockPath + ", but nobody owns that lock");
+ logger.warning("Thread " + currentThread.getName() + " is releasing the lock " + lockPath +
+ ", but nobody owns that lock. " + lockId + "#" + sequenceNumber +
+ ", reentries by thread ID = " + reentriesByThreadId);
} else if (oldThread != currentThread) {
getLockMetrics(lockPath).incrementForeignReleaseCount();
logger.warning("Thread " + currentThread.getName() +
- " is releasing the lock " + lockPath + ", but it was owned by thread "
- + oldThread.getName());
+ " is releasing the lock " + lockPath + ", but it was owned by thread " +
+ oldThread.getName() + ". " + lockId + "#" + sequenceNumber +
+ ", reentries by thread ID = " + reentriesByThreadId);
}
}
diff --git a/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/ThreadLockStats.java b/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/ThreadLockStats.java
index d4511bd04fb..7f8eafdcf7f 100644
--- a/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/ThreadLockStats.java
+++ b/zkfacade/src/main/java/com/yahoo/vespa/curator/stats/ThreadLockStats.java
@@ -6,6 +6,7 @@ import com.yahoo.vespa.curator.Lock;
import java.time.Duration;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
@@ -97,7 +98,7 @@ public class ThreadLockStats {
}
/** Mutable method (see class doc) */
- public void lockAcquired() {
+ public void lockAcquired(String lockId, long sequenceNumber, Map<Long, Long> reentriesByThreadId) {
withLastLockAttempt(lockAttempt -> {
// Note on the order of lockAcquired() vs notifyOfThreadHoldingLock(): When the latter is
// invoked, other threads may query e.g. isAcquired() on the lockAttempt, which would
@@ -105,19 +106,21 @@ public class ThreadLockStats {
// but seems better to ensure LockAttempt is updated first.
lockAttempt.lockAcquired();
- if (!lockAttempt.getReentry()) {
- LockStats.getGlobal().notifyOfThreadHoldingLock(thread, lockAttempt.getLockPath());
+ if (!lockAttempt.isReentry()) {
+ LockStats.getGlobal().notifyOfThreadHoldingLock(thread, lockAttempt.getLockPath(),
+ lockId, sequenceNumber, reentriesByThreadId);
}
});
}
/** Mutable method (see class doc) */
- public void preRelease() {
+ public void preRelease(String lockId, long sequenceNumber, Map<Long, Long> reentriesByThreadId) {
withLastLockAttempt(lockAttempt -> {
// Note on the order of these two statement: Same concerns apply here as in lockAcquired().
- if (!lockAttempt.getReentry()) {
- LockStats.getGlobal().notifyOfThreadReleasingLock(thread, lockAttempt.getLockPath());
+ if (!lockAttempt.isReentry()) {
+ LockStats.getGlobal().notifyOfThreadReleasingLock(thread, lockAttempt.getLockPath(),
+ lockId, sequenceNumber, reentriesByThreadId);
}
lockAttempt.preRelease();