diff options
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(); |