aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/go/go.mod7
-rw-r--r--client/go/go.sum75
-rw-r--r--client/go/internal/cli/cmd/deploy.go4
-rw-r--r--client/go/internal/cli/cmd/fetch.go46
-rw-r--r--client/go/internal/cli/cmd/root.go1
-rw-r--r--client/go/internal/cli/cmd/visit.go4
-rw-r--r--client/go/internal/util/spinner.go3
-rw-r--r--client/go/internal/util/tuning.go26
-rw-r--r--client/go/internal/vespa/deploy.go123
-rw-r--r--client/go/internal/vespa/deploy_test.go66
-rw-r--r--client/js/app/yarn.lock458
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java12
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java6
-rw-r--r--container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java2
-rw-r--r--dependency-versions/pom.xml6
-rw-r--r--dist/vespa-engine.repo2
-rw-r--r--dist/vespa.spec6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java6
-rw-r--r--parent/pom.xml11
-rwxr-xr-xscrewdriver/release-rpms.sh12
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/query.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp20
-rwxr-xr-xsearchlib/src/main/javacc/RankingExpressionParser.jj1
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java4
-rw-r--r--searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp16
-rw-r--r--searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp40
-rw-r--r--searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp464
-rw-r--r--searchlib/src/vespa/searchlib/attribute/CMakeLists.txt40
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp126
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.cpp38
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.h16
-rw-r--r--searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.cpp14
-rw-r--r--searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.h72
-rw-r--r--searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.hpp67
-rw-r--r--searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/iterator_pack.h11
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp22
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp22
-rw-r--r--searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h15
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp32
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistattribute.h16
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h27
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp92
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglisttraits.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp8
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp8
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt2
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/integer_term_vector.cpp65
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/integer_term_vector.h29
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/string_term_vector.cpp66
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/string_term_vector.h28
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/term_vector.h27
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/termnodes.cpp20
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/termnodes.h16
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/blueprint.cpp18
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/blueprint.h32
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp28
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h10
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/iterator_pack.h11
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp48
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h4
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/test/attribute_builder.cpp28
-rw-r--r--searchlib/src/vespa/searchlib/test/attribute_builder.h2
-rw-r--r--vespa-dependencies-enforcer/allowed-maven-dependencies.txt9
-rw-r--r--vespajlib/src/main/java/com/yahoo/collections/Comparables.java2
-rw-r--r--vespalib/src/tests/btree/btree_test.cpp48
-rw-r--r--vespalib/src/tests/util/bfloat16/CMakeLists.txt3
-rw-r--r--vespalib/src/tests/util/bfloat16/bfloat16_test.cpp59
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreeiterator.h22
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreeiterator.hpp108
-rw-r--r--zookeeper-server/CMakeLists.txt1
-rw-r--r--zookeeper-server/pom.xml1
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/CMakeLists.txt2
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/pom.xml104
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ConfigServerZooKeeperServer.java43
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ReconfigurableVespaZooKeeperServer.java47
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaMtlsAuthenticationProvider.java41
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java60
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java93
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperServerImpl.java54
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/common/NetUtils.java94
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java353
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/VespaNettyServerCnxnFactory.java37
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java2410
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java309
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/Learner.java927
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java181
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java136
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java2711
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java236
-rw-r--r--zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java83
104 files changed, 9556 insertions, 1150 deletions
diff --git a/client/go/go.mod b/client/go/go.mod
index a122508b1b9..e5c205e51f4 100644
--- a/client/go/go.mod
+++ b/client/go/go.mod
@@ -1,13 +1,13 @@
module github.com/vespa-engine/vespa/client/go
-go 1.19
+go 1.20
require (
github.com/alessio/shellescape v1.4.2
github.com/briandowns/spinner v1.23.0
github.com/fatih/color v1.16.0
- // This is the most recent version compatible with Go 1.19. Upgrade when we upgrade our Go version
- github.com/go-json-experiment/json v0.0.0-20230216065249-540f01442424
+ // This is the most recent version compatible with Go 1.20. Upgrade when we upgrade our Go version
+ github.com/go-json-experiment/json v0.0.0-20230324203220-04923b7a9528
github.com/klauspost/compress v1.17.3
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20
@@ -30,6 +30,7 @@ require (
github.com/kr/pretty v0.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/term v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
diff --git a/client/go/go.sum b/client/go/go.sum
index 92e96c4e813..e94515d46c1 100644
--- a/client/go/go.sum
+++ b/client/go/go.sum
@@ -1,40 +1,21 @@
-github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
-github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A=
github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE=
-github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
-github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
-github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
-github.com/go-json-experiment/json v0.0.0-20230216065249-540f01442424 h1:I1EK0t+BDH+kvlozNqrvzKqsWeM2QUKxXH0iW2fjDDw=
-github.com/go-json-experiment/json v0.0.0-20230216065249-540f01442424/go.mod h1:I+I5/LT2lLP0eZsBNaVDrOrYASx9h7o7mRHmy+535/A=
+github.com/go-json-experiment/json v0.0.0-20230324203220-04923b7a9528 h1:hmpF6G+rHcypt8J6jhBH/rDUx+04Th/L61Y8uCKFb7Q=
+github.com/go-json-experiment/json v0.0.0-20230324203220-04923b7a9528/go.mod h1:AHV+bpNGVGD0DCHMBhhTYtT7yeBYD9Yk92XAjB7vOgo=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
-github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
-github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
-github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
-github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
-github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
-github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g=
-github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
-github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
-github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -45,10 +26,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
-github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
@@ -57,74 +34,30 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/zalando/go-keyring v0.2.2 h1:f0xmpYiSrHtSNAVgwip93Cg8tuF45HJM6rHq/A5RI/4=
-github.com/zalando/go-keyring v0.2.2/go.mod h1:sI3evg9Wvpw3+n4SqplGSJUMwtDeROfD4nsFz4z9PG0=
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
-golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
-golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
-golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
-golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
-golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
-golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
-golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
-golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
-golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
-golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
-golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
-golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go
index 8806a21c9fc..dd605237b5f 100644
--- a/client/go/internal/cli/cmd/deploy.go
+++ b/client/go/internal/cli/cmd/deploy.go
@@ -60,7 +60,7 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`,
return err
}
timeout := time.Duration(waitSecs) * time.Second
- opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target, Timeout: timeout}
+ opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target}
if versionArg != "" {
version, err := version.Parse(versionArg)
if err != nil {
@@ -162,7 +162,7 @@ func newActivateCmd(cli *CLI) *cobra.Command {
if _, err := waiter.DeployService(target); err != nil {
return err
}
- opts := vespa.DeploymentOptions{Target: target, Timeout: timeout}
+ opts := vespa.DeploymentOptions{Target: target}
err = vespa.Activate(sessionID, opts)
if err != nil {
return err
diff --git a/client/go/internal/cli/cmd/fetch.go b/client/go/internal/cli/cmd/fetch.go
new file mode 100644
index 00000000000..b2e7d11ba7b
--- /dev/null
+++ b/client/go/internal/cli/cmd/fetch.go
@@ -0,0 +1,46 @@
+package cmd
+
+import (
+ "github.com/spf13/cobra"
+ "github.com/vespa-engine/vespa/client/go/internal/vespa"
+)
+
+func newFetchCmd(cli *CLI) *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "fetch [path]",
+ Short: "Download a deployed application package",
+ Long: `Download a deployed application package.
+
+This command can be used to download an already deployed Vespa application
+package. The package is written as a ZIP file to the given path, or current
+directory if no path is given.
+`,
+ Example: `$ vespa fetch
+$ vespa fetch mydir/
+$ vespa fetch -t cloud mycloudapp.zip
+`,
+ Args: cobra.MaximumNArgs(1),
+ DisableAutoGenTag: true,
+ SilenceUsage: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ target, err := cli.target(targetOptions{})
+ if err != nil {
+ return err
+ }
+ path := "."
+ if len(args) > 0 {
+ path = args[0]
+ }
+ dstPath := ""
+ if err := cli.spinner(cli.Stderr, "Downloading application package...", func() error {
+ dstPath, err = vespa.Fetch(vespa.DeploymentOptions{Target: target}, path)
+ return err
+ }); err != nil {
+ return err
+ }
+ cli.printSuccess("Application package written to ", dstPath)
+ return nil
+ },
+ }
+ return cmd
+}
diff --git a/client/go/internal/cli/cmd/root.go b/client/go/internal/cli/cmd/root.go
index 4d1a7cf6f89..004bdc038fe 100644
--- a/client/go/internal/cli/cmd/root.go
+++ b/client/go/internal/cli/cmd/root.go
@@ -282,6 +282,7 @@ func (c *CLI) configureCommands() {
rootCmd.AddCommand(newVersionCmd(c)) // version
rootCmd.AddCommand(newVisitCmd(c)) // visit
rootCmd.AddCommand(newFeedCmd(c)) // feed
+ rootCmd.AddCommand(newFetchCmd(c)) // fetch
}
func (c *CLI) bindWaitFlag(cmd *cobra.Command, defaultSecs int, value *int) {
diff --git a/client/go/internal/cli/cmd/visit.go b/client/go/internal/cli/cmd/visit.go
index bb226701e0a..2ca01764deb 100644
--- a/client/go/internal/cli/cmd/visit.go
+++ b/client/go/internal/cli/cmd/visit.go
@@ -90,8 +90,8 @@ func newVisitCmd(cli *CLI) *cobra.Command {
)
cmd := &cobra.Command{
Use: "visit",
- Short: "Fetch and print all documents from Vespa",
- Long: `Fetch and print all documents from Vespa.
+ Short: "Retrieve and print all documents from Vespa",
+ Long: `Retrieve and print all documents from Vespa.
By default prints each document received on its own line (JSONL format).
`,
diff --git a/client/go/internal/util/spinner.go b/client/go/internal/util/spinner.go
index 880375f961b..323a5fffe12 100644
--- a/client/go/internal/util/spinner.go
+++ b/client/go/internal/util/spinner.go
@@ -14,6 +14,9 @@ import (
// displayed after message.
func Spinner(w io.Writer, message string, fn func() error) error {
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(w))
+ // Cursor is hidden by default. Hiding cursor requires Stop() to be called to restore cursor (i.e. if the process is
+ // interrupted), however we don't want to bother with a signal handler just for this
+ s.HideCursor = false
if err := s.Color("blue", "bold"); err != nil {
return err
}
diff --git a/client/go/internal/util/tuning.go b/client/go/internal/util/tuning.go
index b36c0a431da..cca314247ab 100644
--- a/client/go/internal/util/tuning.go
+++ b/client/go/internal/util/tuning.go
@@ -15,19 +15,29 @@ import (
func OptionallyReduceTimerFrequency() {
if os.Getenv(envvars.VESPA_TIMER_HZ) == "" {
backticks := BackTicksIgnoreStderr
- out, _ := backticks.Run("uname", "-r")
- if strings.Contains(out, "linuxkit") {
- if os.Getenv(envvars.VESPA_TIMER_HZ) != "100" {
- trace.Trace(
- "Running docker on macos.",
- "Reducing base frequency from 1000hz to 100hz due to high cost of sampling time.",
- "This will reduce timeout accuracy.")
+ uname, _ := backticks.Run("uname", "-r")
+ if strings.Contains(uname, "linuxkit") {
+ setTimerHZ("Docker on macOS detected.", "100")
+ } else {
+ virt, _ := backticks.Run("systemd-detect-virt", "--vm")
+ if strings.TrimSpace(virt) == "qemu" {
+ setTimerHZ("QEMU virtualization detected.", "100")
}
- os.Setenv(envvars.VESPA_TIMER_HZ, "100")
}
}
}
+func setTimerHZ(description, timerHZ string) {
+ if os.Getenv(envvars.VESPA_TIMER_HZ) == timerHZ {
+ return
+ }
+ trace.Trace(
+ description,
+ "Reducing base frequency from 1000hz to "+timerHZ+"hz due to high cost of sampling time.",
+ "This will reduce timeout accuracy.")
+ os.Setenv(envvars.VESPA_TIMER_HZ, timerHZ)
+}
+
func TuneResourceLimits() {
var numfiles uint64 = 262144
var numprocs uint64 = 409600
diff --git a/client/go/internal/vespa/deploy.go b/client/go/internal/vespa/deploy.go
index b39c51916e7..4684e313291 100644
--- a/client/go/internal/vespa/deploy.go
+++ b/client/go/internal/vespa/deploy.go
@@ -12,6 +12,7 @@ import (
"mime/multipart"
"net/http"
"net/url"
+ "os"
"path/filepath"
"strconv"
"strings"
@@ -47,7 +48,6 @@ type Deployment struct {
type DeploymentOptions struct {
Target Target
ApplicationPackage ApplicationPackage
- Timeout time.Duration
Version version.Version
}
@@ -119,6 +119,127 @@ func ZoneFromString(s string) (ZoneID, error) {
return ZoneID{Environment: parts[0], Region: parts[1]}, nil
}
+func Fetch(deployment DeploymentOptions, path string) (string, error) {
+ if util.IsDirectory(path) {
+ path = filepath.Join(path, "application.zip")
+ }
+ if util.PathExists(path) {
+ return "", fmt.Errorf("%s already exists", path)
+ }
+ if deployment.Target.IsCloud() {
+ return path, fetchFromController(deployment, path)
+ }
+ return path, fetchFromConfigServer(deployment, path)
+}
+
+func deployServiceGet(url string, deployment DeploymentOptions, w io.Writer) error {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return err
+ }
+ response, err := deployServiceDo(req, 0, deployment)
+ if err != nil {
+ return err
+ }
+ defer response.Body.Close()
+ _, err = io.Copy(w, response.Body)
+ return err
+}
+
+func fetchFromController(deployment DeploymentOptions, path string) error {
+ var (
+ pkgURL *url.URL
+ err error
+ )
+ switch deployment.Target.Deployment().Zone.Environment {
+ case "dev", "perf":
+ pkgURL, err = deployment.url(fmt.Sprintf("/application/v4/tenant/%s/application/%s/instance/%s/job/%s/package",
+ deployment.Target.Deployment().Application.Tenant,
+ deployment.Target.Deployment().Application.Application,
+ deployment.Target.Deployment().Application.Instance,
+ deployment.Target.Deployment().Zone.Environment+"-"+deployment.Target.Deployment().Zone.Region,
+ ))
+ default:
+ pkgURL, err = deployment.url(fmt.Sprintf("/application/v4/tenant/%s/application/%s/package",
+ deployment.Target.Deployment().Application.Tenant,
+ deployment.Target.Deployment().Application.Application),
+ )
+ }
+ if err != nil {
+ return err
+ }
+ tmpFile, err := os.CreateTemp("", "vespa")
+ if err != nil {
+ return err
+ }
+ defer os.Remove(tmpFile.Name())
+ if err := deployServiceGet(pkgURL.String(), deployment, tmpFile); err != nil {
+ return err
+ }
+ if err := tmpFile.Close(); err != nil {
+ return err
+ }
+ return os.Rename(tmpFile.Name(), path)
+}
+
+func fetchFromConfigServer(deployment DeploymentOptions, path string) error {
+ tmpDir, err := os.MkdirTemp("", "vespa")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(tmpDir)
+ u, err := deployment.url("/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content")
+ if err != nil {
+ return err
+ }
+ dir := filepath.Join(tmpDir, "application")
+ if err := fetchFilesFromConfigServer(deployment, u, dir); err != nil {
+ return err
+ }
+ zipFile := filepath.Join(tmpDir, "application.zip")
+ if err := zipDir(dir, zipFile); err != nil {
+ return err
+ }
+ return os.Rename(zipFile, path)
+}
+
+func fetchFilesFromConfigServer(deployment DeploymentOptions, contentURL *url.URL, path string) error {
+ var data bytes.Buffer
+ if err := deployServiceGet(contentURL.String(), deployment, &data); err != nil {
+ return err
+ }
+ var fileURLs []string
+ if err := json.Unmarshal(data.Bytes(), &fileURLs); err != nil {
+ return err
+ }
+ for _, fu := range fileURLs {
+ u, err := url.Parse(fu)
+ if err != nil {
+ return err
+ }
+ entryName := filepath.Join(path, filepath.Base(u.Path))
+ if strings.HasSuffix(u.Path, "/") {
+ if err := fetchFilesFromConfigServer(deployment, u, entryName); err != nil {
+ return err
+ }
+ } else {
+ if err := os.MkdirAll(filepath.Dir(entryName), 0755); err != nil {
+ return err
+ }
+ f, err := os.Create(entryName)
+ if err != nil {
+ return err
+ }
+ if err := deployServiceGet(fu, deployment, f); err != nil {
+ f.Close()
+ return err
+ }
+ f.Close()
+ }
+ }
+ return nil
+}
+
// Prepare deployment and return the session ID
func Prepare(deployment DeploymentOptions) (PrepareResult, error) {
if deployment.Target.IsCloud() {
diff --git a/client/go/internal/vespa/deploy_test.go b/client/go/internal/vespa/deploy_test.go
index 9dfdc47d8e6..09129d3027a 100644
--- a/client/go/internal/vespa/deploy_test.go
+++ b/client/go/internal/vespa/deploy_test.go
@@ -2,6 +2,7 @@
package vespa
import (
+ "archive/zip"
"io"
"mime"
"mime/multipart"
@@ -14,6 +15,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vespa-engine/vespa/client/go/internal/mock"
+ "github.com/vespa-engine/vespa/client/go/internal/util"
"github.com/vespa-engine/vespa/client/go/internal/version"
)
@@ -196,6 +198,70 @@ func TestDeactivateCloud(t *testing.T) {
assert.Equal(t, "https://api-ctl.vespa-cloud.com:4443/application/v4/tenant/t1/application/a1/instance/i1/environment/dev/region/us-north-1", req.URL.String())
}
+func TestFetch(t *testing.T) {
+ httpClient := mock.HTTPClient{}
+ target := LocalTarget(&httpClient, TLSOptions{}, 0)
+ opts := DeploymentOptions{Target: target}
+ httpClient.NextResponse(mock.HTTPResponse{
+ URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content",
+ Status: 200,
+ Body: []byte(`[
+"/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/schemas/",
+"/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/services.xml"
+]`),
+ })
+ httpClient.NextResponse(mock.HTTPResponse{
+ URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/schemas/",
+ Status: 200,
+ Body: []byte(`[
+"/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/schemas/music.sd"
+]`),
+ })
+ httpClient.NextResponse(mock.HTTPResponse{
+ URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/schemas/music.sd",
+ Status: 200,
+ Body: []byte(`music.sd contents`),
+ })
+ httpClient.NextResponse(mock.HTTPResponse{
+ URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/content/services.xml",
+ Status: 200,
+ Body: []byte(`services.xml contents`),
+ })
+ dir := t.TempDir()
+ dst, err := Fetch(opts, dir)
+ require.Nil(t, err)
+ assert.True(t, util.PathExists(dst))
+
+ f, err := os.Open(dst)
+ require.Nil(t, err)
+ defer f.Close()
+ zr, err := zip.NewReader(f, 1000)
+ require.Nil(t, err)
+ schema, err := zr.Open("schemas/music.sd")
+ require.Nil(t, err)
+ data, err := io.ReadAll(schema)
+ require.Nil(t, err)
+ assert.Equal(t, `music.sd contents`, string(data))
+}
+
+func TestFetchCloud(t *testing.T) {
+ httpClient := mock.HTTPClient{}
+ target, _ := createCloudTarget(t, io.Discard)
+ cloudTarget, ok := target.(*cloudTarget)
+ require.True(t, ok)
+ cloudTarget.httpClient = &httpClient
+ opts := DeploymentOptions{Target: target}
+ httpClient.NextResponse(mock.HTTPResponse{
+ URI: "/application/v4/tenant/t1/application/a1/instance/i1/job/dev-us-north-1/package",
+ Status: 200,
+ Body: []byte(`application zip`),
+ })
+ dir := t.TempDir()
+ dst, err := Fetch(opts, dir)
+ require.Nil(t, err)
+ assert.True(t, util.PathExists(dst))
+}
+
type pkgFixture struct {
expectedPath string
expectedTestPath string
diff --git a/client/js/app/yarn.lock b/client/js/app/yarn.lock
index 3f4d43813d9..e3d408e242e 100644
--- a/client/js/app/yarn.lock
+++ b/client/js/app/yarn.lock
@@ -599,115 +599,115 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
-"@esbuild/android-arm64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz#276c5f99604054d3dbb733577e09adae944baa90"
- integrity sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==
-
-"@esbuild/android-arm@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.5.tgz#4a3cbf14758166abaae8ba9c01a80e68342a4eec"
- integrity sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==
-
-"@esbuild/android-x64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.5.tgz#21a3d11cd4613d2d3c5ccb9e746c254eb9265b0a"
- integrity sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==
-
-"@esbuild/darwin-arm64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz#714cb839f467d6a67b151ee8255886498e2b9bf6"
- integrity sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==
-
-"@esbuild/darwin-x64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz#2c553e97a6d2b4ae76a884e35e6cbab85a990bbf"
- integrity sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==
-
-"@esbuild/freebsd-arm64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz#d554f556718adb31917a0da24277bf84b6ee87f3"
- integrity sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==
-
-"@esbuild/freebsd-x64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz#288f7358a3bb15d99e73c65c9adaa3dabb497432"
- integrity sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==
-
-"@esbuild/linux-arm64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz#95933ae86325c93cb6b5e8333d22120ecfdc901b"
- integrity sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==
-
-"@esbuild/linux-arm@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz#0acef93aa3e0579e46d33b666627bddb06636664"
- integrity sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==
-
-"@esbuild/linux-ia32@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz#b6e5c9e80b42131cbd6b1ddaa48c92835f1ed67f"
- integrity sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==
-
-"@esbuild/linux-loong64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz#e5f0cf95a180158b01ff5f417da796a1c09dfbea"
- integrity sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==
-
-"@esbuild/linux-mips64el@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz#ae36fb86c7d5f641f3a0c8472e83dcb6ea36a408"
- integrity sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==
-
-"@esbuild/linux-ppc64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz#7960cb1666f0340ddd9eef7b26dcea3835d472d0"
- integrity sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==
-
-"@esbuild/linux-riscv64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz#32207df26af60a3a9feea1783fc21b9817bade19"
- integrity sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==
-
-"@esbuild/linux-s390x@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz#b38d5681db89a3723862dfa792812397b1510a7d"
- integrity sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==
-
-"@esbuild/linux-x64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz#46feba2ad041a241379d150f415b472fe3885075"
- integrity sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==
-
-"@esbuild/netbsd-x64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz#3b5c1fb068f26bfc681d31f682adf1bea4ef0702"
- integrity sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==
-
-"@esbuild/openbsd-x64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz#ca6830316ca68056c5c88a875f103ad3235e00db"
- integrity sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==
-
-"@esbuild/sunos-x64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz#9efc4eb9539a7be7d5a05ada52ee43cda0d8e2dd"
- integrity sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==
-
-"@esbuild/win32-arm64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz#29f8184afa7a02a956ebda4ed638099f4b8ff198"
- integrity sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==
-
-"@esbuild/win32-ia32@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz#f3de07afb292ecad651ae4bb8727789de2d95b05"
- integrity sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==
-
-"@esbuild/win32-x64@0.19.5":
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz#faad84c41ba12e3a0acb52571df9bff37bee75f6"
- integrity sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==
+"@esbuild/android-arm64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.7.tgz#646156aea43e8e6723de6e94a4ac07c5aed41be1"
+ integrity sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w==
+
+"@esbuild/android-arm@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.7.tgz#0827b49aed813c33ea18ee257c1728cdc4a01030"
+ integrity sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww==
+
+"@esbuild/android-x64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.7.tgz#fa294ed5214d88219d519e0ab1bbb0253a89b864"
+ integrity sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw==
+
+"@esbuild/darwin-arm64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.7.tgz#e24d2ed545749ff251eabe8bce11fefa688892d3"
+ integrity sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw==
+
+"@esbuild/darwin-x64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.7.tgz#02d1f8a572874c90d8f55dde8a859e5145bd06f6"
+ integrity sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ==
+
+"@esbuild/freebsd-arm64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.7.tgz#bc6a69b9a7915da278f0a5ebaec069c813982c22"
+ integrity sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ==
+
+"@esbuild/freebsd-x64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.7.tgz#ec3708488625d70e565968ceea1355e7c8613865"
+ integrity sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA==
+
+"@esbuild/linux-arm64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.7.tgz#8e04b66c306858f92d4f90f8222775270755e88a"
+ integrity sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g==
+
+"@esbuild/linux-arm@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.7.tgz#12d5b65e089029ee1fe4c591b60969c9b1a85355"
+ integrity sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww==
+
+"@esbuild/linux-ia32@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.7.tgz#01eabc2a3ad9039e115db650268e4f48f910dbe2"
+ integrity sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g==
+
+"@esbuild/linux-loong64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.7.tgz#70681113632970e6a5766607bbdb98aa18cf4d5f"
+ integrity sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw==
+
+"@esbuild/linux-mips64el@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.7.tgz#f63c022a71a3d70c482d1943a27cb8997021e230"
+ integrity sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w==
+
+"@esbuild/linux-ppc64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.7.tgz#614eafd08b0c50212f287b948b3c08d6e60f221f"
+ integrity sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ==
+
+"@esbuild/linux-riscv64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.7.tgz#31d3b63f92f65968268a8e61ba59872538e80e88"
+ integrity sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw==
+
+"@esbuild/linux-s390x@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.7.tgz#be94974e0caa0783ae05f9477fd7170b9ac29cb0"
+ integrity sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg==
+
+"@esbuild/linux-x64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.7.tgz#84e8018a913dd4ecee954623e395984aef3d0007"
+ integrity sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ==
+
+"@esbuild/netbsd-x64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.7.tgz#98898ba8800374c9df9bb182ca4f69fcecaf4411"
+ integrity sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ==
+
+"@esbuild/openbsd-x64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.7.tgz#46dc4eda2adb51f16361b1ad10e9b3f4938c4573"
+ integrity sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ==
+
+"@esbuild/sunos-x64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.7.tgz#1650d40dd88412ecc11490119cd23cbaf661a591"
+ integrity sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw==
+
+"@esbuild/win32-arm64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.7.tgz#e61de6c4eb204d83fd912f3ae6812cc8c7d32d25"
+ integrity sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw==
+
+"@esbuild/win32-ia32@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.7.tgz#3d9c159d42c67e37a433e44ef8217c661cb6f6d0"
+ integrity sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A==
+
+"@esbuild/win32-x64@0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.7.tgz#02c4446f802706098d8e6ee70cf2b7aba96ded0b"
+ integrity sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ==
"@eslint-community/eslint-utils@^4.2.0":
version "4.4.0"
@@ -1269,70 +1269,70 @@
dependencies:
"@babel/runtime" "^7.13.10"
-"@remix-run/router@1.12.0":
- version "1.12.0"
- resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.12.0.tgz#e89b64b6fa97a8a5b740a4c38c2904b80f1f229a"
- integrity sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg==
-
-"@rollup/rollup-android-arm-eabi@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.4.1.tgz#f276b0fa322270aa42d1f56c982db6ef8d6a4393"
- integrity sha512-Ss4suS/sd+6xLRu+MLCkED2mUrAyqHmmvZB+zpzZ9Znn9S8wCkTQCJaQ8P8aHofnvG5L16u9MVnJjCqioPErwQ==
-
-"@rollup/rollup-android-arm64@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.4.1.tgz#f0492f00d18e1067785f8e820e137c00528c5e62"
- integrity sha512-sRSkGTvGsARwWd7TzC8LKRf8FiPn7257vd/edzmvG4RIr9x68KBN0/Ek48CkuUJ5Pj/Dp9vKWv6PEupjKWjTYA==
-
-"@rollup/rollup-darwin-arm64@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.4.1.tgz#40443db7f4559171d797581e0618ec1a4c8dcee9"
- integrity sha512-nz0AiGrrXyaWpsmBXUGOBiRDU0wyfSXbFuF98pPvIO8O6auQsPG6riWsfQqmCCC5FNd8zKQ4JhgugRNAkBJ8mQ==
-
-"@rollup/rollup-darwin-x64@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.4.1.tgz#2868f37a9f9c2c22c091b6209f6ce7454437edf9"
- integrity sha512-Ogqvf4/Ve/faMaiPRvzsJEqajbqs00LO+8vtrPBVvLgdw4wBg6ZDXdkDAZO+4MLnrc8mhGV6VJAzYScZdPLtJg==
-
-"@rollup/rollup-linux-arm-gnueabihf@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.4.1.tgz#d78d7ad358d24058166ab5599de3dcb5ab951add"
- integrity sha512-9zc2tqlr6HfO+hx9+wktUlWTRdje7Ub15iJqKcqg5uJZ+iKqmd2CMxlgPpXi7+bU7bjfDIuvCvnGk7wewFEhCg==
-
-"@rollup/rollup-linux-arm64-gnu@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.4.1.tgz#5d07588b40a04f5b6fbd9e0169c8dc32c1c2ed21"
- integrity sha512-phLb1fN3rq2o1j1v+nKxXUTSJnAhzhU0hLrl7Qzb0fLpwkGMHDem+o6d+ZI8+/BlTXfMU4kVWGvy6g9k/B8L6Q==
-
-"@rollup/rollup-linux-arm64-musl@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.4.1.tgz#d452e88a02755f449f6e98d4ce424d655ef42cfe"
- integrity sha512-M2sDtw4tf57VPSjbTAN/lz1doWUqO2CbQuX3L9K6GWIR5uw9j+ROKCvvUNBY8WUbMxwaoc8mH9HmmBKsLht7+w==
-
-"@rollup/rollup-linux-x64-gnu@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.4.1.tgz#e8e8e87ab098784383a5ced4aa4bbfa7b2c92a4e"
- integrity sha512-mHIlRLX+hx+30cD6c4BaBOsSqdnCE4ok7/KDvjHYAHoSuveoMMxIisZFvcLhUnyZcPBXDGZTuBoalcuh43UfQQ==
-
-"@rollup/rollup-linux-x64-musl@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.4.1.tgz#3e5da42626672e2d620ed12746158b0cf6143b23"
- integrity sha512-tB+RZuDi3zxFx7vDrjTNGVLu2KNyzYv+UY8jz7e4TMEoAj7iEt8Qk6xVu6mo3pgjnsHj6jnq3uuRsHp97DLwOA==
-
-"@rollup/rollup-win32-arm64-msvc@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.4.1.tgz#0f0d0c6b75c53643fab8238c76889a95bca3b9cc"
- integrity sha512-Hdn39PzOQowK/HZzYpCuZdJC91PE6EaGbTe2VCA9oq2u18evkisQfws0Smh9QQGNNRa/T7MOuGNQoLeXhhE3PQ==
-
-"@rollup/rollup-win32-ia32-msvc@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.4.1.tgz#8bb9e8fbf0fdf96fe3bebcee23f5cfdbbd9a4a0a"
- integrity sha512-tLpKb1Elm9fM8c5w3nl4N1eLTP4bCqTYw9tqUBxX8/hsxqHO3dxc2qPbZ9PNkdK4tg4iLEYn0pOUnVByRd2CbA==
-
-"@rollup/rollup-win32-x64-msvc@4.4.1":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.4.1.tgz#8311b77e6cce322865ba12ada8c3779369610d18"
- integrity sha512-eAhItDX9yQtZVM3yvXS/VR3qPqcnXvnLyx1pLXl4JzyNMBNO3KC986t/iAg2zcMzpAp9JSvxB5VZGnBiNoA98w==
+"@remix-run/router@1.13.0":
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.0.tgz#7e29c4ee85176d9c08cb0f4456bff74d092c5065"
+ integrity sha512-5dMOnVnefRsl4uRnAdoWjtVTdh8e6aZqgM4puy9nmEADH72ck+uXwzpJLEKE9Q6F8ZljNewLgmTfkxUrBdv4WA==
+
+"@rollup/rollup-android-arm-eabi@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.5.0.tgz#5984f98288150a2c34928de023bbd122d61ce754"
+ integrity sha512-OINaBGY+Wc++U0rdr7BLuFClxcoWaVW3vQYqmQq6B3bqQ/2olkaoz+K8+af/Mmka/C2yN5j+L9scBkv4BtKsDA==
+
+"@rollup/rollup-android-arm64@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.5.0.tgz#8456a8c623cca4042ae4bf2ce03d875a02433191"
+ integrity sha512-UdMf1pOQc4ZmUA/NTmKhgJTBimbSKnhPS2zJqucqFyBRFPnPDtwA8MzrGNTjDeQbIAWfpJVAlxejw+/lQyBK/w==
+
+"@rollup/rollup-darwin-arm64@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.5.0.tgz#76be6832eee21dabc28f84f9f54fbfcc66615992"
+ integrity sha512-L0/CA5p/idVKI+c9PcAPGorH6CwXn6+J0Ys7Gg1axCbTPgI8MeMlhA6fLM9fK+ssFhqogMHFC8HDvZuetOii7w==
+
+"@rollup/rollup-darwin-x64@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.5.0.tgz#66bd162a3fea48cb1cef50cedccfbeee5685b444"
+ integrity sha512-QZCbVqU26mNlLn8zi/XDDquNmvcr4ON5FYAHQQsyhrHx8q+sQi/6xduoznYXwk/KmKIXG5dLfR0CvY+NAWpFYQ==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.5.0.tgz#a0e6b2a1d67a4ba0c2a61985175f65c05abc5f73"
+ integrity sha512-VpSQ+xm93AeV33QbYslgf44wc5eJGYfYitlQzAi3OObu9iwrGXEnmu5S3ilkqE3Pr/FkgOiJKV/2p0ewf4Hrtg==
+
+"@rollup/rollup-linux-arm64-gnu@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.5.0.tgz#5434b844a47ba4e35602ee312de9f39b38b1777b"
+ integrity sha512-OrEyIfpxSsMal44JpEVx9AEcGpdBQG1ZuWISAanaQTSMeStBW+oHWwOkoqR54bw3x8heP8gBOyoJiGg+fLY8qQ==
+
+"@rollup/rollup-linux-arm64-musl@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.5.0.tgz#149cab95107821fe4ae46d5f2c0658c5b0e56b9c"
+ integrity sha512-1H7wBbQuE6igQdxMSTjtFfD+DGAudcYWhp106z/9zBA8OQhsJRnemO4XGavdzHpGhRtRxbgmUGdO3YQgrWf2RA==
+
+"@rollup/rollup-linux-x64-gnu@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.5.0.tgz#6929bf3013e9d599605953ea1bc51f35376bfff7"
+ integrity sha512-FVyFI13tXw5aE65sZdBpNjPVIi4Q5mARnL/39UIkxvSgRAIqCo5sCpCELk0JtXHGee2owZz5aNLbWNfBHzr71Q==
+
+"@rollup/rollup-linux-x64-musl@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.5.0.tgz#a17f5decabf05b74aad684de56cf43a72a289a0b"
+ integrity sha512-eBPYl2sLpH/o8qbSz6vPwWlDyThnQjJfcDOGFbNjmjb44XKC1F5dQfakOsADRVrXCNzM6ZsSIPDG5dc6HHLNFg==
+
+"@rollup/rollup-win32-arm64-msvc@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.5.0.tgz#f145f10c33aa187a11fd60933465be46667e6e42"
+ integrity sha512-xaOHIfLOZypoQ5U2I6rEaugS4IYtTgP030xzvrBf5js7p9WI9wik07iHmsKaej8Z83ZDxN5GyypfoyKV5O5TJA==
+
+"@rollup/rollup-win32-ia32-msvc@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.5.0.tgz#798614b191f9ce1dc58079d1dfbc234c71df9e0e"
+ integrity sha512-Al6quztQUrHwcOoU2TuFblUQ5L+/AmPBXFR6dUvyo4nRj2yQRK0WIUaGMF/uwKulvRcXkpHe3k9A8Vf93VDktA==
+
+"@rollup/rollup-win32-x64-msvc@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.5.0.tgz#748970e066839e33ed8c935061e370c4ab050517"
+ integrity sha512-8kdW+brNhI/NzJ4fxDufuJUjepzINqJKLGHuxyAtpPG9bMbn8P5mtaCcbOm0EzLJ+atg+kF9dwg8jpclkVqx5w==
"@sinclair/typebox@^0.27.8":
version "0.27.8"
@@ -2435,32 +2435,32 @@ esbuild-jest@^0:
babel-jest "^26.6.3"
esbuild@^0.19.3:
- version "0.19.5"
- resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.5.tgz#53a0e19dfbf61ba6c827d51a80813cf071239a8c"
- integrity sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.7.tgz#b9a7235097b81278dcf090e2532ed13c95a2ee84"
+ integrity sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ==
optionalDependencies:
- "@esbuild/android-arm" "0.19.5"
- "@esbuild/android-arm64" "0.19.5"
- "@esbuild/android-x64" "0.19.5"
- "@esbuild/darwin-arm64" "0.19.5"
- "@esbuild/darwin-x64" "0.19.5"
- "@esbuild/freebsd-arm64" "0.19.5"
- "@esbuild/freebsd-x64" "0.19.5"
- "@esbuild/linux-arm" "0.19.5"
- "@esbuild/linux-arm64" "0.19.5"
- "@esbuild/linux-ia32" "0.19.5"
- "@esbuild/linux-loong64" "0.19.5"
- "@esbuild/linux-mips64el" "0.19.5"
- "@esbuild/linux-ppc64" "0.19.5"
- "@esbuild/linux-riscv64" "0.19.5"
- "@esbuild/linux-s390x" "0.19.5"
- "@esbuild/linux-x64" "0.19.5"
- "@esbuild/netbsd-x64" "0.19.5"
- "@esbuild/openbsd-x64" "0.19.5"
- "@esbuild/sunos-x64" "0.19.5"
- "@esbuild/win32-arm64" "0.19.5"
- "@esbuild/win32-ia32" "0.19.5"
- "@esbuild/win32-x64" "0.19.5"
+ "@esbuild/android-arm" "0.19.7"
+ "@esbuild/android-arm64" "0.19.7"
+ "@esbuild/android-x64" "0.19.7"
+ "@esbuild/darwin-arm64" "0.19.7"
+ "@esbuild/darwin-x64" "0.19.7"
+ "@esbuild/freebsd-arm64" "0.19.7"
+ "@esbuild/freebsd-x64" "0.19.7"
+ "@esbuild/linux-arm" "0.19.7"
+ "@esbuild/linux-arm64" "0.19.7"
+ "@esbuild/linux-ia32" "0.19.7"
+ "@esbuild/linux-loong64" "0.19.7"
+ "@esbuild/linux-mips64el" "0.19.7"
+ "@esbuild/linux-ppc64" "0.19.7"
+ "@esbuild/linux-riscv64" "0.19.7"
+ "@esbuild/linux-s390x" "0.19.7"
+ "@esbuild/linux-x64" "0.19.7"
+ "@esbuild/netbsd-x64" "0.19.7"
+ "@esbuild/openbsd-x64" "0.19.7"
+ "@esbuild/sunos-x64" "0.19.7"
+ "@esbuild/win32-arm64" "0.19.7"
+ "@esbuild/win32-ia32" "0.19.7"
+ "@esbuild/win32-x64" "0.19.7"
escalade@^3.1.1:
version "3.1.1"
@@ -4356,9 +4356,9 @@ multimatch@^4.0.0:
minimatch "^3.0.4"
nanoid@^3.3.6:
- version "3.3.6"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
- integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
nanomatch@^1.2.9:
version "1.2.13"
@@ -4808,19 +4808,19 @@ react-refresh@^0.14.0:
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
react-router-dom@^6:
- version "6.19.0"
- resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.19.0.tgz#ee807e36ae7dd954db7a3f770e38b7cc026c66a8"
- integrity sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ==
+ version "6.20.0"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.20.0.tgz#7b9527a1e29c7fb90736a5f89d54ca01f40e264b"
+ integrity sha512-CbcKjEyiSVpA6UtCHOIYLUYn/UJfwzp55va4yEfpk7JBN3GPqWfHrdLkAvNCcpXr8QoihcDMuk0dzWZxtlB/mQ==
dependencies:
- "@remix-run/router" "1.12.0"
- react-router "6.19.0"
+ "@remix-run/router" "1.13.0"
+ react-router "6.20.0"
-react-router@6.19.0:
- version "6.19.0"
- resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.19.0.tgz#6d5062fa33495859daca98d86292ab03b037a9ca"
- integrity sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw==
+react-router@6.20.0:
+ version "6.20.0"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.20.0.tgz#4275a3567ecc55f7703073158048db10096bb539"
+ integrity sha512-pVvzsSsgUxxtuNfTHC4IxjATs10UaAtvLGVSA1tbUE4GDaOSU1Esu2xF5nWLz7KPiMuW8BJWuPFdlGYJ7/rW0w==
dependencies:
- "@remix-run/router" "1.12.0"
+ "@remix-run/router" "1.13.0"
react-textarea-autosize@8.3.4:
version "8.3.4"
@@ -4997,22 +4997,22 @@ rimraf@^3.0.2:
glob "^7.1.3"
rollup@^4.2.0:
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.4.1.tgz#2f85169f23d13dabb3d9b846d753965757353820"
- integrity sha512-idZzrUpWSblPJX66i+GzrpjKE3vbYrlWirUHteoAbjKReZwa0cohAErOYA5efoMmNCdvG9yrJS+w9Kl6csaH4w==
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.5.0.tgz#358ee6947fe0e4c8bacdae6896539cade3107655"
+ integrity sha512-41xsWhzxqjMDASCxH5ibw1mXk+3c4TNI2UjKbLxe6iEzrSQnqOzmmK8/3mufCPbzHNJ2e04Fc1ddI35hHy+8zg==
optionalDependencies:
- "@rollup/rollup-android-arm-eabi" "4.4.1"
- "@rollup/rollup-android-arm64" "4.4.1"
- "@rollup/rollup-darwin-arm64" "4.4.1"
- "@rollup/rollup-darwin-x64" "4.4.1"
- "@rollup/rollup-linux-arm-gnueabihf" "4.4.1"
- "@rollup/rollup-linux-arm64-gnu" "4.4.1"
- "@rollup/rollup-linux-arm64-musl" "4.4.1"
- "@rollup/rollup-linux-x64-gnu" "4.4.1"
- "@rollup/rollup-linux-x64-musl" "4.4.1"
- "@rollup/rollup-win32-arm64-msvc" "4.4.1"
- "@rollup/rollup-win32-ia32-msvc" "4.4.1"
- "@rollup/rollup-win32-x64-msvc" "4.4.1"
+ "@rollup/rollup-android-arm-eabi" "4.5.0"
+ "@rollup/rollup-android-arm64" "4.5.0"
+ "@rollup/rollup-darwin-arm64" "4.5.0"
+ "@rollup/rollup-darwin-x64" "4.5.0"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.5.0"
+ "@rollup/rollup-linux-arm64-gnu" "4.5.0"
+ "@rollup/rollup-linux-arm64-musl" "4.5.0"
+ "@rollup/rollup-linux-x64-gnu" "4.5.0"
+ "@rollup/rollup-linux-x64-musl" "4.5.0"
+ "@rollup/rollup-win32-arm64-msvc" "4.5.0"
+ "@rollup/rollup-win32-ia32-msvc" "4.5.0"
+ "@rollup/rollup-win32-x64-msvc" "4.5.0"
fsevents "~2.3.2"
rsvp@^4.8.4:
@@ -5645,9 +5645,9 @@ v8-to-istanbul@^9.0.1:
convert-source-map "^1.6.0"
vite@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.0.tgz#3bfb65acda2a97127e4fa240156664a1f234ce08"
- integrity sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw==
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.2.tgz#3c94627dace83b9bf04b64eaf618038e30fb95c0"
+ integrity sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==
dependencies:
esbuild "^0.19.3"
postcss "^8.4.31"
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java
index 6e4c14bdeac..36b6d0fe07a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java
@@ -25,6 +25,7 @@ import com.yahoo.vespa.model.search.SearchCluster;
import com.yahoo.vespa.model.search.StreamingSearchCluster;
import java.util.Collection;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -72,8 +73,15 @@ public class ContainerSearch extends ContainerSubsystem<SearchChains>
}
private static Collection<String> getSchemasWithGlobalPhase(DeployState state) {
- return state.rankProfileRegistry().all().stream()
- .filter(rp -> rp.getGlobalPhase() != null).map(rp -> rp.schema().getName()).collect(Collectors.toSet());
+ var res = new HashSet<String>();
+ for (var schema : state.getSchemas()) {
+ for (var rp : state.rankProfileRegistry().rankProfilesOf(schema)) {
+ if (rp.getGlobalPhase() != null) {
+ res.add(schema.getName());
+ }
+ }
+ }
+ return res;
}
public void connectSearchClusters(Map<String, SearchCluster> searchClusters) {
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java
index 3fb577a5ff3..f7f3f97f3ac 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/StreamingSearcher.java
@@ -136,10 +136,8 @@ public class StreamingSearcher extends VespaBackEndSearcher {
initializeMissingQueryFields(query);
if (documentSelectionQueryParameterCount(query) != 1) {
- return new Result(query, ErrorMessage.createIllegalQuery("Streaming search needs one and " +
- "only one of these query parameters to be set: " +
- "streaming.userid, streaming.groupname, or " +
- "streaming.selection"));
+ return new Result(query, ErrorMessage.createIllegalQuery("Streaming search requires either " +
+ "streaming.groupname or streaming.selection"));
}
if (query.getTrace().isTraceable(4))
query.trace("Routing to search cluster " + getSearchClusterName() + " and document type " + documentType, 4);
diff --git a/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java b/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java
index dd5e1c71b16..2a246739100 100644
--- a/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java
+++ b/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/StreamingSearcherTestCase.java
@@ -244,7 +244,7 @@ public class StreamingSearcherTestCase {
// Magic query values are used to trigger specific behaviors from mock visitor.
checkError(searcher, "/?query=noselection",
- "Illegal query", "Streaming search needs one and only one");
+ "Illegal query", "Streaming search requires either");
checkError(searcher, "/?streaming.userid=1&query=parseexception",
"Invalid query parameter", "Failed to parse document selection string");
checkError(searcher, "/?streaming.userid=1&query=tokenizeexception",
diff --git a/dependency-versions/pom.xml b/dependency-versions/pom.xml
index c4edf840c7c..ca5d5cabee6 100644
--- a/dependency-versions/pom.xml
+++ b/dependency-versions/pom.xml
@@ -86,12 +86,12 @@
<commons-digester.vespa.version>3.2</commons-digester.vespa.version>
<commons-exec.vespa.version>1.3</commons-exec.vespa.version>
<commons-io.vespa.version>2.15.0</commons-io.vespa.version>
- <commons-lang3.vespa.version>3.13.0</commons-lang3.vespa.version>
+ <commons-lang3.vespa.version>3.14.0</commons-lang3.vespa.version>
<commons.math3.vespa.version>3.6.1</commons.math3.vespa.version>
<commons-compress.vespa.version>1.25.0</commons-compress.vespa.version>
<commons-cli.vespa.version>1.6.0</commons-cli.vespa.version>
<curator.vespa.version>5.5.0</curator.vespa.version>
- <dropwizard.metrics.vespa.version>4.2.22</dropwizard.metrics.vespa.version>
+ <dropwizard.metrics.vespa.version>4.1.12.1</dropwizard.metrics.vespa.version> <!-- ZK 3.9.1 requires this -->
<eclipse-collections.vespa.version>11.1.0</eclipse-collections.vespa.version>
<felix.vespa.version>7.0.5</felix.vespa.version>
<felix.log.vespa.version>1.3.0</felix.log.vespa.version>
@@ -120,7 +120,7 @@
<mojo-executor.vespa.version>2.4.0</mojo-executor.vespa.version>
<netty.vespa.version>4.1.101.Final</netty.vespa.version>
<netty-tcnative.vespa.version>2.0.62.Final</netty-tcnative.vespa.version>
- <onnxruntime.vespa.version>1.16.2</onnxruntime.vespa.version>
+ <onnxruntime.vespa.version>1.16.3</onnxruntime.vespa.version>
<opennlp.vespa.version>2.3.0</opennlp.vespa.version>
<opentest4j.vespa.version>1.3.0</opentest4j.vespa.version>
<org.json.vespa.version>20231013</org.json.vespa.version>
diff --git a/dist/vespa-engine.repo b/dist/vespa-engine.repo
index d884b404533..29c408ca66f 100644
--- a/dist/vespa-engine.repo
+++ b/dist/vespa-engine.repo
@@ -12,7 +12,7 @@ enabled=1
[copr:copr.fedorainfracloud.org:group_vespa:vespa]
name=Copr repo for vespa owned by @vespa
-baseurl=https://download.copr.fedorainfracloud.org/results/@vespa/vespa/centos-stream-8-$basearch/
+baseurl=https://download.copr.fedorainfracloud.org/results/@vespa/vespa/epel-8-$basearch/
type=rpm-md
skip_if_unavailable=True
gpgcheck=1
diff --git a/dist/vespa.spec b/dist/vespa.spec
index 6cc694004e9..99e020a0bf9 100644
--- a/dist/vespa.spec
+++ b/dist/vespa.spec
@@ -42,7 +42,7 @@ License: Commercial
URL: http://vespa.ai
Source0: vespa-%{version}.tar.gz
-BuildRequires: vespa-build-dependencies = 1.2.2
+BuildRequires: vespa-build-dependencies >= 1.2.3
Requires: %{name}-base = %{version}-%{release}
Requires: %{name}-base-libs = %{version}-%{release}
@@ -156,7 +156,7 @@ Requires: openssl-libs
Requires: vespa-lz4 >= 1.9.4-1
Requires: vespa-libzstd >= 1.5.4-1
%if 0%{?el8}
-Requires: vespa-openblas >= 0.3.21
+Requires: vespa-openblas >= 0.3.25
%else
Requires: openblas-serial
%endif
@@ -196,7 +196,7 @@ Requires: vespa-protobuf = 3.21.12
Requires: protobuf
Requires: llvm-libs
%endif
-Requires: vespa-onnxruntime = 1.16.2
+Requires: vespa-onnxruntime = 1.16.3
%description libs
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
index 31d79d34c94..d511570881b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDb.java
@@ -445,7 +445,7 @@ public class CuratorDb {
}
public Optional<LoadBalancer> readLoadBalancer(LoadBalancerId id) {
- return read(loadBalancerPath(id), bytes -> LoadBalancerSerializer.fromJson(id, bytes));
+ return read(loadBalancerPath(id), LoadBalancerSerializer::fromJson);
}
public void writeLoadBalancer(LoadBalancer loadBalancer, LoadBalancer.State fromState) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java
index 99cc0d1e601..c4a2e4cb549 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java
@@ -111,7 +111,7 @@ public class LoadBalancerSerializer {
}
}
- public static LoadBalancer fromJson(LoadBalancerId id, byte[] data) {
+ public static LoadBalancer fromJson(byte[] data) {
Cursor object = SlimeUtils.jsonToSlime(data).get();
Set<Real> reals = new LinkedHashSet<>();
@@ -128,8 +128,7 @@ public class LoadBalancerSerializer {
Set<String> networks = new LinkedHashSet<>();
object.field(networksField).traverse((ArrayTraverser) (i, network) -> networks.add(network.asString()));
- // TODO jonmv: remove fallback after data is re-written.
- String idSeed = SlimeUtils.optionalString(object.field(idSeedField)).orElse(id.application().tenant().value() + id.application().application().value() + id.application().instance().value() + id.cluster().value());
+ String idSeed = object.field(idSeedField).asString();
Optional<DomainName> hostname = SlimeUtils.optionalString(object.field(hostnameField)).map(DomainName::of);
Optional<String> ip4Address = SlimeUtils.optionalString(object.field(lbIpAddressField));
Optional<String> ip6Address = SlimeUtils.optionalString(object.field(lbIp6AddressField));
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java
index 9ec63933921..60341661fc0 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializerTest.java
@@ -58,7 +58,7 @@ public class LoadBalancerSerializerTest {
LoadBalancer.State.active,
now);
- var serialized = LoadBalancerSerializer.fromJson(loadBalancer.id(), LoadBalancerSerializer.toJson(loadBalancer));
+ var serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer));
assertEquals(loadBalancer.id(), serialized.id());
assertEquals(loadBalancer.idSeed(), serialized.idSeed());
assertEquals(loadBalancer.instance().get().hostname(), serialized.instance().get().hostname());
@@ -89,7 +89,7 @@ public class LoadBalancerSerializerTest {
LoadBalancer.State.active,
now);
- var serialized = LoadBalancerSerializer.fromJson(loadBalancer.id(), LoadBalancerSerializer.toJson(loadBalancer));
+ var serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer));
assertEquals(loadBalancer.id(), serialized.id());
assertEquals(loadBalancer.idSeed(), serialized.idSeed());
assertEquals(loadBalancer.instance().get().hostname(), serialized.instance().get().hostname());
@@ -112,7 +112,7 @@ public class LoadBalancerSerializerTest {
var now = Instant.now();
var loadBalancer = new LoadBalancer(loadBalancerId, "seed", Optional.empty(), LoadBalancer.State.reserved, now);
- var serialized = LoadBalancerSerializer.fromJson(loadBalancerId, LoadBalancerSerializer.toJson(loadBalancer));
+ var serialized = LoadBalancerSerializer.fromJson(LoadBalancerSerializer.toJson(loadBalancer));
assertEquals(loadBalancer.id(), serialized.id());
assertEquals(loadBalancer.idSeed(), serialized.idSeed());
assertEquals(loadBalancer.instance(), serialized.instance());
diff --git a/parent/pom.xml b/parent/pom.xml
index c6ae9cdc7e3..545db6ba1f1 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -697,6 +697,12 @@
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty.vespa.version}</version>
+ <classifier>linux-x86_64</classifier>
+ </dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-transport-native-epoll</artifactId>
+ <version>${netty.vespa.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
@@ -704,6 +710,11 @@
<version>${netty-tcnative.vespa.version}</version>
</dependency>
<dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-tcnative-boringssl-static</artifactId>
+ <version>${netty-tcnative.vespa.version}</version>
+ </dependency>
+ <dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
<version>${prometheus.client.vespa.version}</version>
diff --git a/screwdriver/release-rpms.sh b/screwdriver/release-rpms.sh
index b3834572a4a..76ab74e9a5c 100755
--- a/screwdriver/release-rpms.sh
+++ b/screwdriver/release-rpms.sh
@@ -12,10 +12,10 @@ fi
readonly VESPA_RELEASE="$1"
readonly VESPA_REF="$2"
-VESPA_RPM_X86_64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-x86_64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1)
+VESPA_RPM_X86_64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-x86_64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1)
echo "Latest x86_64 RPM on Copr: $VESPA_RPM_X86_64"
-VESPA_RPM_AARCH64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-aarch64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1)
+VESPA_RPM_AARCH64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-aarch64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1)
echo "Latest aarch64 RPM on Copr: $VESPA_RPM_AARCH64"
if [[ "$VESPA_RELEASE" == "$VESPA_RPM_X86_64" ]] && [[ "$VESPA_RELEASE" == "$VESPA_RPM_AARCH64" ]]; then
@@ -35,11 +35,11 @@ cd vespa
dist/release-vespa-rpm.sh $VESPA_RELEASE $VESPA_REF
while [[ "$VESPA_RELEASE" != "$VESPA_RPM_X86_64" ]] || [[ "$VESPA_RELEASE" != "$VESPA_RPM_AARCH64" ]] ; do
- dnf clean --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-x86_64 --repoid=vespa metadata
- VESPA_RPM_X86_64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-x86_64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1)
+ dnf clean --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-x86_64 --repoid=vespa metadata
+ VESPA_RPM_X86_64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-x86_64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1)
echo "RPM x86_64: $VESPA_RPM_X86_64"
- dnf clean --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-aarch64 --repoid=vespa metadata
- VESPA_RPM_AARCH64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/centos-stream-8-aarch64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1)
+ dnf clean --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-aarch64 --repoid=vespa metadata
+ VESPA_RPM_AARCH64=$(dnf repoquery --repofrompath=vespa,https://copr-be.cloud.fedoraproject.org/results/@vespa/vespa/epel-8-aarch64 --repoid=vespa -q vespa | cut -d: -f2 | cut -d- -f1 | sort -V | tail -1)
echo "RPM aarch64: $VESPA_RPM_AARCH64"
sleep 150
done
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp
index 0393bdc2ee8..95e4eac437c 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/lid_allocator.cpp
@@ -222,7 +222,7 @@ public:
setEstimate(HitEstimate(_activeLids.size(), false));
}
- bool isWhiteList() const override { return true; }
+ bool isWhiteList() const noexcept final { return true; }
SearchIterator::UP createFilterSearch(bool strict, FilterConstraint) const override {
if (_all_lids_active) {
@@ -231,7 +231,7 @@ public:
return create_search_helper(strict);
}
- ~WhiteListBlueprint() {
+ ~WhiteListBlueprint() override {
for (auto matchData : _matchDataVector) {
delete matchData;
}
diff --git a/searchcore/src/vespa/searchcore/proton/matching/query.cpp b/searchcore/src/vespa/searchcore/proton/matching/query.cpp
index 071e914b405..f55ba77cec8 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/query.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/query.cpp
@@ -143,7 +143,7 @@ void exchange_location_nodes(const string &location_str,
IntermediateBlueprint *
asRankOrAndNot(Blueprint * blueprint) {
return ((blueprint->isAndNot() || blueprint->isRank()))
- ? static_cast<IntermediateBlueprint *>(blueprint)
+ ? blueprint->asIntermediate()
: nullptr;
}
diff --git a/searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp b/searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp
index fcae5794e9e..af26a47fba3 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/rangequerylocator.cpp
@@ -25,15 +25,13 @@ namespace {
RangeLimitMetaInfo
locateFirst(uint32_t field_id, const Blueprint & blueprint) {
- if (blueprint.isIntermediate()) {
- const auto & intermediate = static_cast<const IntermediateBlueprint &>(blueprint);
- if (intermediate.isAndNot()) {
- return locateFirst(field_id, intermediate.getChild(0));
- } else if (intermediate.isRank()) {
- return locateFirst(field_id, intermediate.getChild(0));
- } else if (intermediate.isAnd()) {
- for (size_t i(0); i < intermediate.childCnt(); i++) {
- RangeLimitMetaInfo childMeta = locateFirst(field_id, intermediate.getChild(i));
+ const auto * intermediate = blueprint.asIntermediate();
+ if (intermediate) {
+ if (intermediate->isAndNot() || intermediate->isRank()) {
+ return locateFirst(field_id, intermediate->getChild(0));
+ } else if (intermediate->isAnd()) {
+ for (size_t i(0); i < intermediate->childCnt(); i++) {
+ RangeLimitMetaInfo childMeta = locateFirst(field_id, intermediate->getChild(i));
if (childMeta.valid()) {
return childMeta;
}
@@ -42,9 +40,9 @@ locateFirst(uint32_t field_id, const Blueprint & blueprint) {
} else {
const Blueprint::State & state = blueprint.getState();
if (state.isTermLike() && (state.numFields() == 1) && (state.field(0).getFieldId() == field_id)) {
- const LeafBlueprint &leaf = static_cast<const LeafBlueprint &>(blueprint);
+ const LeafBlueprint * leaf = blueprint.asLeaf();
vespalib::string from, too;
- if (leaf.getRange(from, too)) {
+ if (leaf->getRange(from, too)) {
return {from, too, state.estimate().estHits};
}
}
diff --git a/searchlib/src/main/javacc/RankingExpressionParser.jj b/searchlib/src/main/javacc/RankingExpressionParser.jj
index 42f8f846199..591f0eb8b37 100755
--- a/searchlib/src/main/javacc/RankingExpressionParser.jj
+++ b/searchlib/src/main/javacc/RankingExpressionParser.jj
@@ -5,6 +5,7 @@
* @author bratseth
*/
options {
+ UNICODE_INPUT = true;
CACHE_TOKENS = true;
DEBUG_PARSER = false;
USER_TOKEN_MANAGER = false;
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java
index 9626059a42e..0088e3eb9de 100644
--- a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java
@@ -220,6 +220,7 @@ public class EvaluationTestCase {
@Test
public void testTensorEvaluation() {
EvaluationTester tester = new EvaluationTester();
+
tester.assertEvaluates("{}", "tensor0", "{}");
// tensor map
@@ -227,6 +228,9 @@ public class EvaluationTestCase {
"map(tensor0, f(x) (log10(x)))", "{ {d1:0}:10, {d1:1}:100, {d1:2}:1000 }");
tester.assertEvaluates("{ {d1:0}:4, {d1:1}:9, {d1:2 }:16 }",
"map(tensor0, f(x) (x * x))", "{ {d1:0}:2, {d1:1}:3, {d1:2}:4 }");
+ // Unicode key
+ tester.assertEvaluates("tensor<int8>(drink{}):{Martini\uD83C\uDF78:30.0}",
+ "tensor<int8>(drink{}): {\"Martini\uD83C\uDF78\": 30 }");
// -- tensor map shorthands
tester.assertEvaluates("{ {d1:0}:0, {d1:1}:1, {d1:2 }:0 }",
"tensor0 == 3", "{ {d1:0}:2, {d1:1}:3, {d1:2}:4 }");
diff --git a/searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp b/searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp
index 62628d61d49..3d3ff469546 100644
--- a/searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp
+++ b/searchlib/src/tests/attribute/postinglistattribute/postinglistattribute_test.cpp
@@ -449,7 +449,7 @@ PostingListAttributeTest::checkPostingList(const VectorType & vec, const std::ve
{
const typename VectorType::EnumStore & enumStore = vec.getEnumStore();
auto& dict = enumStore.get_dictionary();
- const typename VectorType::PostingList & postingList = vec.getPostingList();
+ const auto& posting_store = vec.get_posting_store();
for (size_t i = 0; i < values.size(); ++i) {
const uint32_t docBegin = range.getBegin(i);
@@ -457,10 +457,9 @@ PostingListAttributeTest::checkPostingList(const VectorType & vec, const std::ve
auto find_result = dict.find_posting_list(enumStore.make_comparator(values[i]), dict.get_frozen_root());
ASSERT_TRUE(find_result.first.valid());
- bool has_bitvector = VectorType::PostingList::isBitVector(postingList.getTypeId(find_result.second));
+ bool has_bitvector = VectorType::PostingStore::isBitVector(posting_store.getTypeId(find_result.second));
- typename VectorType::PostingList::Iterator postings;
- postings = postingList.begin(find_result.second);
+ auto postings = posting_store.begin(find_result.second);
uint32_t numHits(0);
bool has_btree = postings.valid();
if (postings.valid()) {
@@ -472,12 +471,12 @@ PostingListAttributeTest::checkPostingList(const VectorType & vec, const std::ve
EXPECT_EQ(doc, docEnd);
} else {
EXPECT_TRUE(has_bitvector && vec.getIsFilter());
- numHits = postingList.getBitVectorEntry(find_result.second)->_bv->reader().countTrueBits();
+ numHits = posting_store.getBitVectorEntry(find_result.second)->_bv->reader().countTrueBits();
}
if (has_bitvector) {
uint32_t doc = docBegin;
uint32_t bv_num_hits = 0;
- auto& bv = postingList.getBitVectorEntry(find_result.second)->_bv->reader();
+ auto& bv = posting_store.getBitVectorEntry(find_result.second)->_bv->reader();
for (auto lid = bv.getFirstTrueBit(); lid < bv.size(); lid = bv.getNextTrueBit(lid + 1)) {
EXPECT_EQ(doc++, lid);
++bv_num_hits;
@@ -701,12 +700,11 @@ PostingListAttributeTest::checkPostingList(AttributeType & vec, ValueType value,
{
const typename AttributeType::EnumStore & enumStore = vec.getEnumStore();
auto& dict = enumStore.get_dictionary();
- const typename AttributeType::PostingList & postingList = vec.getPostingList();
+ const auto& posting_store = vec.get_posting_store();
auto find_result = dict.find_posting_list(vec.getEnumStore().make_comparator(value), dict.get_frozen_root());
ASSERT_TRUE(find_result.first.valid());
- typename AttributeType::PostingList::Iterator postings;
- postings = postingList.begin(find_result.second);
+ auto postings = posting_store.begin(find_result.second);
DocSet::iterator docBegin = expected.begin();
DocSet::iterator docEnd = expected.end();
diff --git a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
index 343a5c8e38b..c701d5ac19f 100644
--- a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
+++ b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp
@@ -233,6 +233,7 @@ private:
void testRangeSearch(const AttributePtr & ptr, uint32_t numDocs, std::vector<ValueType> values);
void testRangeSearch();
void testRangeSearchLimited();
+ void testRangeSearchLimitedHugeDictionary();
// test case insensitive search
@@ -504,7 +505,7 @@ SearchContextTest::checkResultSet(const ResultSet & rs, const DocSet & expected,
ASSERT_TRUE(array != nullptr);
uint32_t i = 0;
for (auto iter = expected.begin(); iter != expected.end(); ++iter, ++i) {
- EXPECT_TRUE(array[i].getDocId() == *iter);
+ EXPECT_EQUAL(array[i].getDocId(), *iter);
}
}
}
@@ -1176,6 +1177,42 @@ SearchContextTest::testRangeSearch(const AttributePtr & ptr, uint32_t numDocs, s
}
}
+DocSet
+createDocs(uint32_t from, int32_t count) {
+ DocSet docs;
+ if (count >= 0) {
+ for (int32_t i(0); i < count; i++) {
+ docs.put(from + i);
+ }
+ } else {
+ for (int32_t i(0); i > count; i--) {
+ docs.put(from + i);
+ }
+ }
+ return docs;
+}
+
+void
+SearchContextTest::testRangeSearchLimitedHugeDictionary() {
+ Config cfg(BasicType::INT32, CollectionType::SINGLE);
+ cfg.setFastSearch(true);
+ std::vector<int32_t> v;
+ v.reserve(2000);
+ for (size_t i(0); i < v.capacity(); i++) {
+ v.push_back(i);
+ }
+ auto ptr = AttributeBuilder("limited-int32", cfg).fill(v).get();
+ auto& vec = dynamic_cast<IntegerAttribute &>(*ptr);
+
+ performRangeSearch(vec, "[1;9;1200]", createDocs(2, 9));
+ performRangeSearch(vec, "[1;1109;1200]", createDocs(2, 1109));
+ performRangeSearch(vec, "[1;3009;1200]", createDocs(2, 1200));
+
+ performRangeSearch(vec, "[1;9;-1200]", createDocs(2, 9));
+ performRangeSearch(vec, "[1;1109;-1200]", createDocs(2, 1109));
+ performRangeSearch(vec, "[1;3009;-1200]", createDocs(2000, -1200));
+}
+
void
SearchContextTest::testRangeSearchLimited()
{
@@ -1980,6 +2017,7 @@ SearchContextTest::Main()
testSearchIterator();
testRangeSearch();
testRangeSearchLimited();
+ testRangeSearchLimitedHugeDictionary();
testCaseInsensitiveSearch();
testRegexSearch();
testPrefixSearch();
diff --git a/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp b/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp
index 51e22dbcf2c..9cbfa49cf26 100644
--- a/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp
+++ b/searchlib/src/tests/queryeval/blueprint/intermediate_blueprints_test.cpp
@@ -473,395 +473,169 @@ TEST("test SourceBlender Blueprint") {
// createSearch tested by iterator unit test
}
-TEST("test SourceBlender below AND optimization") {
- auto selector_1 = std::make_unique<InvalidSelector>(); // the one
- auto selector_2 = std::make_unique<InvalidSelector>(); // not the one
- //-------------------------------------------------------------------------
- auto *top = new AndBlueprint();
- Blueprint::UP top_bp(top);
- top->addChild(ap(MyLeafSpec(2).create()));
- top->addChild(ap(MyLeafSpec(1).create()));
- top->addChild(ap(MyLeafSpec(3).create()));
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(200).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(100).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(300).create()->setSourceId(3)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(30).create()->setSourceId(3)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_2);
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1)));
- top->addChild(ap(blender));
+std::unique_ptr<IntermediateBlueprint>
+addLeafs(std::unique_ptr<IntermediateBlueprint> parent, std::initializer_list<std::pair<uint32_t, uint32_t>> list) {
+ for (const auto & leaf : list) {
+ parent->addChild(ap(MyLeafSpec(leaf.first).create()->setSourceId(leaf.second)));
}
- //-------------------------------------------------------------------------
- auto *expect = new AndBlueprint();
- Blueprint::UP expect_bp(expect);
+ return parent;
+}
+
+std::unique_ptr<IntermediateBlueprint>
+addLeafs(uint32_t sourceId, std::unique_ptr<IntermediateBlueprint> parent, std::initializer_list<std::pair<uint32_t, uint32_t>> list) {
+ parent->setSourceId(sourceId);
+ return addLeafs(std::move(parent), list);
+}
+
+struct SourceBlenderTestFixture {
+ InvalidSelector selector_1; // the one
+ InvalidSelector selector_2; // not the one
+ void addChildrenForSBTest(IntermediateBlueprint & parent);
+};
+
+void SourceBlenderTestFixture::addChildrenForSBTest(IntermediateBlueprint & parent) {
+ parent.addChild(ap(MyLeafSpec(2).create()));
+ parent.addChild(ap(MyLeafSpec(1).create()));
+ parent.addChild(ap(MyLeafSpec(3).create()));
+ parent.addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(selector_1), {{200, 2}, {100, 1}, {300, 3}}));
+ parent.addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(selector_1), {{20, 2}, {10, 1}, {30, 3}}));
+ parent.addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(selector_2), {{10, 1}, {20, 2}}));
+ parent.addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(selector_1), {{2000, 2}, {1000, 1}}));
+}
+
+TEST_F("test SourceBlender below AND optimization", SourceBlenderTestFixture) {
+ auto top = std::make_unique<AndBlueprint>();
+ f.addChildrenForSBTest(*top);
+
+ auto expect = std::make_unique<AndBlueprint>();
expect->addChild(ap(MyLeafSpec(1).create()));
expect->addChild(ap(MyLeafSpec(2).create()));
expect->addChild(ap(MyLeafSpec(3).create()));
- {
- auto *blender = new SourceBlenderBlueprint(*selector_2);
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- expect->addChild(ap(blender));
- }
- {
- auto *blender(new SourceBlenderBlueprint(*selector_1));
- {
- auto *sub_and = new AndBlueprint();
- sub_and->setSourceId(3);
- sub_and->addChild(ap(MyLeafSpec(30).create()->setSourceId(3)));
- sub_and->addChild(ap(MyLeafSpec(300).create()->setSourceId(3)));
- blender->addChild(ap(sub_and));
- }
- {
- auto *sub_and = new AndBlueprint();
- sub_and->setSourceId(2);
- sub_and->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- sub_and->addChild(ap(MyLeafSpec(200).create()->setSourceId(2)));
- sub_and->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2)));
- blender->addChild(ap(sub_and));
- }
- {
- auto *sub_and = new AndBlueprint();
- sub_and->setSourceId(1);
- sub_and->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- sub_and->addChild(ap(MyLeafSpec(100).create()->setSourceId(1)));
- sub_and->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1)));
- blender->addChild(ap(sub_and));
- }
- expect->addChild(ap(blender));
- }
+ expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_2), {{10, 1}, {20, 2}}));
+
+ auto blender = std::make_unique<SourceBlenderBlueprint>(f.selector_1);
+ blender->addChild(addLeafs(3, std::make_unique<AndBlueprint>(), {{30, 3}, {300, 3}}));
+ blender->addChild(addLeafs(2, std::make_unique<AndBlueprint>(), {{20, 2}, {200, 2}, {2000, 2}}));
+ blender->addChild(addLeafs(1, std::make_unique<AndBlueprint>(), {{10, 1}, {100, 1}, {1000, 1}}));
+ expect->addChild(std::move(blender));
+
//-------------------------------------------------------------------------
- EXPECT_NOT_EQUAL(expect_bp->asString(), top_bp->asString());
- top_bp = Blueprint::optimize(std::move(top_bp));
- EXPECT_EQUAL(expect_bp->asString(), top_bp->asString());
- expect_bp = Blueprint::optimize(std::move(expect_bp));
+ EXPECT_NOT_EQUAL(expect->asString(), top->asString());
+ auto top_bp = Blueprint::optimize(std::move(top));
+ EXPECT_EQUAL(expect->asString(), top_bp->asString());
+ auto expect_bp = Blueprint::optimize(std::move(expect));
EXPECT_EQUAL(expect_bp->asString(), top_bp->asString());
}
-TEST("test SourceBlender below OR optimization") {
- auto selector_1 = std::make_unique<InvalidSelector>(); // the one
- auto selector_2 = std::make_unique<InvalidSelector>(); // not the one
+TEST_F("test SourceBlender below OR optimization", SourceBlenderTestFixture) {
+ auto top = std::make_unique<OrBlueprint>();
+ f.addChildrenForSBTest(*top);
//-------------------------------------------------------------------------
- auto *top = new OrBlueprint();
- Blueprint::UP top_up(top);
- top->addChild(ap(MyLeafSpec(2).create()));
- top->addChild(ap(MyLeafSpec(1).create()));
- top->addChild(ap(MyLeafSpec(3).create()));
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(200).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(100).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(300).create()->setSourceId(3)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(30).create()->setSourceId(3)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_2);
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1)));
- top->addChild(ap(blender));
- }
- //-------------------------------------------------------------------------
- auto *expect = new OrBlueprint();
- Blueprint::UP expect_up(expect);
- {
- auto *blender(new SourceBlenderBlueprint(*selector_1));
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(3);
- sub_and->addChild(ap(MyLeafSpec(300).create()->setSourceId(3)));
- sub_and->addChild(ap(MyLeafSpec(30).create()->setSourceId(3)));
- blender->addChild(ap(sub_and));
- }
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(2);
- sub_and->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2)));
- sub_and->addChild(ap(MyLeafSpec(200).create()->setSourceId(2)));
- sub_and->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- blender->addChild(ap(sub_and));
- }
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(1);
- sub_and->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1)));
- sub_and->addChild(ap(MyLeafSpec(100).create()->setSourceId(1)));
- sub_and->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(sub_and));
- }
- expect->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_2);
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- expect->addChild(ap(blender));
- }
+ auto expect = std::make_unique<OrBlueprint>();
+ auto blender = std::make_unique<SourceBlenderBlueprint>(f.selector_1);
+ blender->addChild(addLeafs(3, std::make_unique<OrBlueprint>(), {{300, 3}, {30, 3}}));
+ blender->addChild(addLeafs(2, std::make_unique<OrBlueprint>(), {{2000, 2}, {200, 2}, {20, 2}}));
+ blender->addChild(addLeafs(1, std::make_unique<OrBlueprint>(), {{1000, 1}, {100, 1}, {10, 1}}));
+ expect->addChild(std::move(blender));
+ expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_2), {{10, 1}, {20, 2}}));
expect->addChild(ap(MyLeafSpec(3).create()));
expect->addChild(ap(MyLeafSpec(2).create()));
expect->addChild(ap(MyLeafSpec(1).create()));
//-------------------------------------------------------------------------
- EXPECT_NOT_EQUAL(expect_up->asString(), top_up->asString());
- top_up = Blueprint::optimize(std::move(top_up));
- EXPECT_EQUAL(expect_up->asString(), top_up->asString());
- expect_up = Blueprint::optimize(std::move(expect_up));
- EXPECT_EQUAL(expect_up->asString(), top_up->asString());
+ EXPECT_NOT_EQUAL(expect->asString(), top->asString());
+ auto top_bp = Blueprint::optimize(std::move(top));
+ EXPECT_EQUAL(expect->asString(), top_bp->asString());
+ auto expect_bp = Blueprint::optimize(std::move(expect));
+ EXPECT_EQUAL(expect_bp->asString(), top_bp->asString());
}
-TEST("test SourceBlender below AND_NOT optimization") {
- auto selector_1 = std::make_unique<InvalidSelector>(); // the one
- auto selector_2 = std::make_unique<InvalidSelector>(); // not the one
- //-------------------------------------------------------------------------
- auto *top = new AndNotBlueprint();
- Blueprint::UP top_up(top);
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(42).create()->setSourceId(1)));
- top->addChild(ap(blender));
- }
- top->addChild(ap(MyLeafSpec(2).create()));
- top->addChild(ap(MyLeafSpec(1).create()));
- top->addChild(ap(MyLeafSpec(3).create()));
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(200).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(100).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(300).create()->setSourceId(3)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(30).create()->setSourceId(3)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_2);
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1)));
- top->addChild(ap(blender));
- }
+TEST_F("test SourceBlender below AND_NOT optimization", SourceBlenderTestFixture) {
+ auto top = std::make_unique<AndNotBlueprint>();
+ top->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_1), {{42, 1}}));
+ f.addChildrenForSBTest(*top);
+
//-------------------------------------------------------------------------
- auto *expect = new AndNotBlueprint();
- Blueprint::UP expect_up(expect);
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(42).create()->setSourceId(1)));
- expect->addChild(ap(blender));
- }
- {
- auto *blender(new SourceBlenderBlueprint(*selector_1));
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(3);
- sub_and->addChild(ap(MyLeafSpec(300).create()->setSourceId(3)));
- sub_and->addChild(ap(MyLeafSpec(30).create()->setSourceId(3)));
- blender->addChild(ap(sub_and));
- }
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(2);
- sub_and->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2)));
- sub_and->addChild(ap(MyLeafSpec(200).create()->setSourceId(2)));
- sub_and->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- blender->addChild(ap(sub_and));
- }
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(1);
- sub_and->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1)));
- sub_and->addChild(ap(MyLeafSpec(100).create()->setSourceId(1)));
- sub_and->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(sub_and));
- }
- expect->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_2);
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- expect->addChild(ap(blender));
- }
+ auto expect = std::make_unique<AndNotBlueprint>();
+ expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_1), {{42, 1}}));
+ auto blender = std::make_unique<SourceBlenderBlueprint>(f.selector_1);
+ blender->addChild(addLeafs(3, std::make_unique<OrBlueprint>(), {{300, 3}, {30, 3}}));
+ blender->addChild(addLeafs(2, std::make_unique<OrBlueprint>(), {{2000, 2}, {200, 2}, {20, 2}}));
+ blender->addChild(addLeafs(1, std::make_unique<OrBlueprint>(), {{1000, 1}, {100, 1}, {10, 1}}));
+ expect->addChild(std::move(blender));
+ expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_2), {{10, 1}, {20, 2}}));
expect->addChild(ap(MyLeafSpec(3).create()));
expect->addChild(ap(MyLeafSpec(2).create()));
expect->addChild(ap(MyLeafSpec(1).create()));
+
//-------------------------------------------------------------------------
- EXPECT_NOT_EQUAL(expect_up->asString(), top_up->asString());
- top_up = Blueprint::optimize(std::move(top_up));
- EXPECT_EQUAL(expect_up->asString(), top_up->asString());
- expect_up = Blueprint::optimize(std::move(expect_up));
+ EXPECT_NOT_EQUAL(expect->asString(), top->asString());
+ auto top_up = Blueprint::optimize(std::move(top));
+ EXPECT_EQUAL(expect->asString(), top_up->asString());
+ auto expect_up = Blueprint::optimize(std::move(expect));
EXPECT_EQUAL(expect_up->asString(), top_up->asString());
}
-TEST("test SourceBlender below RANK optimization") {
- auto selector_1 = std::make_unique<InvalidSelector>(); // the one
- auto selector_2 = std::make_unique<InvalidSelector>(); // not the one
- //-------------------------------------------------------------------------
- auto *top = new RankBlueprint();
- Blueprint::UP top_up(top);
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(42).create()->setSourceId(1)));
- top->addChild(ap(blender));
- }
- top->addChild(ap(MyLeafSpec(2).create()));
- top->addChild(ap(MyLeafSpec(1).create()));
- top->addChild(ap(MyLeafSpec(3).create()));
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(200).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(100).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(300).create()->setSourceId(3)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(30).create()->setSourceId(3)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_2);
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- top->addChild(ap(blender));
- }
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2)));
- blender->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1)));
- top->addChild(ap(blender));
- }
+TEST_F("test SourceBlender below RANK optimization", SourceBlenderTestFixture) {
+ auto top = std::make_unique<RankBlueprint>();
+ top->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_1), {{42, 1}}));
+ f.addChildrenForSBTest(*top);
+
//-------------------------------------------------------------------------
- auto *expect = new RankBlueprint();
- Blueprint::UP expect_up(expect);
- {
- auto *blender = new SourceBlenderBlueprint(*selector_1);
- blender->addChild(ap(MyLeafSpec(42).create()->setSourceId(1)));
- expect->addChild(ap(blender));
- }
+ auto expect = std::make_unique<RankBlueprint>();
+ expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_1), {{42, 1}}));
expect->addChild(ap(MyLeafSpec(2).create()));
expect->addChild(ap(MyLeafSpec(1).create()));
expect->addChild(ap(MyLeafSpec(3).create()));
- {
- auto *blender = new SourceBlenderBlueprint(*selector_2);
- blender->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- expect->addChild(ap(blender));
- }
- {
- auto *blender(new SourceBlenderBlueprint(*selector_1));
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(3);
- sub_and->addChild(ap(MyLeafSpec(300).create()->setSourceId(3)));
- sub_and->addChild(ap(MyLeafSpec(30).create()->setSourceId(3)));
- blender->addChild(ap(sub_and));
- }
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(2);
- sub_and->addChild(ap(MyLeafSpec(2000).create()->setSourceId(2)));
- sub_and->addChild(ap(MyLeafSpec(200).create()->setSourceId(2)));
- sub_and->addChild(ap(MyLeafSpec(20).create()->setSourceId(2)));
- blender->addChild(ap(sub_and));
- }
- {
- auto *sub_and = new OrBlueprint();
- sub_and->setSourceId(1);
- sub_and->addChild(ap(MyLeafSpec(1000).create()->setSourceId(1)));
- sub_and->addChild(ap(MyLeafSpec(100).create()->setSourceId(1)));
- sub_and->addChild(ap(MyLeafSpec(10).create()->setSourceId(1)));
- blender->addChild(ap(sub_and));
- }
- expect->addChild(ap(blender));
- }
+ expect->addChild(addLeafs(std::make_unique<SourceBlenderBlueprint>(f.selector_2), {{10, 1}, {20, 2}}));
+ auto blender = std::make_unique<SourceBlenderBlueprint>(f.selector_1);
+ blender->addChild(addLeafs(3, std::make_unique<OrBlueprint>(), {{300, 3}, {30, 3}}));
+ blender->addChild(addLeafs(2, std::make_unique<OrBlueprint>(), {{2000, 2}, {200, 2}, {20, 2}}));
+ blender->addChild(addLeafs(1, std::make_unique<OrBlueprint>(), {{1000, 1}, {100, 1}, {10, 1}}));
+ expect->addChild(std::move(blender));
+
//-------------------------------------------------------------------------
- EXPECT_NOT_EQUAL(expect_up->asString(), top_up->asString());
- top_up = Blueprint::optimize(std::move(top_up));
- EXPECT_EQUAL(expect_up->asString(), top_up->asString());
- expect_up = Blueprint::optimize(std::move(expect_up));
+ EXPECT_NOT_EQUAL(expect->asString(), top->asString());
+ auto top_up = Blueprint::optimize(std::move(top));
+ EXPECT_EQUAL(expect->asString(), top_up->asString());
+ auto expect_up = Blueprint::optimize(std::move(expect));
EXPECT_EQUAL(expect_up->asString(), top_up->asString());
}
TEST("test empty root node optimization and safeness") {
//-------------------------------------------------------------------------
// tests leaf node elimination
- Blueprint::UP top1_up(ap(MyLeafSpec(0, true).create()));
+ Blueprint::UP top1(ap(MyLeafSpec(0, true).create()));
//-------------------------------------------------------------------------
// tests intermediate node elimination
- Blueprint::UP top2_up(ap((new AndBlueprint())->
- addChild(ap(MyLeafSpec(0, true).create())).
- addChild(ap(MyLeafSpec(10).create())).
- addChild(ap(MyLeafSpec(20).create()))));
+ Blueprint::UP top2(ap((new AndBlueprint())->
+ addChild(ap(MyLeafSpec(0, true).create())).
+ addChild(ap(MyLeafSpec(10).create())).
+ addChild(ap(MyLeafSpec(20).create()))));
//-------------------------------------------------------------------------
// tests safety of empty AND_NOT child removal
- Blueprint::UP top3_up(ap((new AndNotBlueprint())->
- addChild(ap(MyLeafSpec(0, true).create())).
- addChild(ap(MyLeafSpec(10).create())).
- addChild(ap(MyLeafSpec(20).create()))));
+ Blueprint::UP top3(ap((new AndNotBlueprint())->
+ addChild(ap(MyLeafSpec(0, true).create())).
+ addChild(ap(MyLeafSpec(10).create())).
+ addChild(ap(MyLeafSpec(20).create()))));
//-------------------------------------------------------------------------
// tests safety of empty RANK child removal
- Blueprint::UP top4_up(ap((new RankBlueprint())->
- addChild(ap(MyLeafSpec(0, true).create())).
- addChild(ap(MyLeafSpec(10).create())).
- addChild(ap(MyLeafSpec(20).create()))));
+ Blueprint::UP top4(ap((new RankBlueprint())->
+ addChild(ap(MyLeafSpec(0, true).create())).
+ addChild(ap(MyLeafSpec(10).create())).
+ addChild(ap(MyLeafSpec(20).create()))));
//-------------------------------------------------------------------------
// tests safety of empty OR child removal
- Blueprint::UP top5_up(ap((new OrBlueprint())->
- addChild(ap(MyLeafSpec(0, true).create())).
- addChild(ap(MyLeafSpec(0, true).create())).
- addChild(ap(MyLeafSpec(0, true).create()))));
+ Blueprint::UP top5(ap((new OrBlueprint())->
+ addChild(ap(MyLeafSpec(0, true).create())).
+ addChild(ap(MyLeafSpec(0, true).create())).
+ addChild(ap(MyLeafSpec(0, true).create()))));
//-------------------------------------------------------------------------
auto expect_up = std::make_unique<EmptyBlueprint>();
- //-------------------------------------------------------------------------
- top1_up = Blueprint::optimize(std::move(top1_up));
- top2_up = Blueprint::optimize(std::move(top2_up));
- top3_up = Blueprint::optimize(std::move(top3_up));
- top4_up = Blueprint::optimize(std::move(top4_up));
- top5_up = Blueprint::optimize(std::move(top5_up));
- EXPECT_EQUAL(expect_up->asString(), top1_up->asString());
- EXPECT_EQUAL(expect_up->asString(), top2_up->asString());
- EXPECT_EQUAL(expect_up->asString(), top3_up->asString());
- EXPECT_EQUAL(expect_up->asString(), top4_up->asString());
- EXPECT_EQUAL(expect_up->asString(), top5_up->asString());
+ EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top1))->asString());
+ EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top2))->asString());
+ EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top3))->asString());
+ EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top4))->asString());
+ EXPECT_EQUAL(expect_up->asString(), Blueprint::optimize(std::move(top5))->asString());
}
TEST("and with one empty child is optimized away") {
diff --git a/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt b/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
index 1d47b4d02ff..6ec78daecd1 100644
--- a/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/attribute/CMakeLists.txt
@@ -6,6 +6,7 @@ vespa_add_library(searchlib_attribute OBJECT
attribute.cpp
attribute_blueprint_factory.cpp
attribute_header.cpp
+ attribute_object_visitor.cpp
attribute_operation.cpp
attribute_read_guard.cpp
attribute_weighted_set_blueprint.cpp
@@ -38,27 +39,27 @@ vespa_add_library(searchlib_attribute OBJECT
defines.cpp
dfa_fuzzy_matcher.cpp
dfa_string_comparator.cpp
+ direct_weighted_set_blueprint.cpp
distance_metric_utils.cpp
diversity.cpp
dociditerator.cpp
document_weight_or_filter_search.cpp
- searchcontextelementiterator.cpp
empty_search_context.cpp
+ enum_store_compaction_spec.cpp
+ enum_store_dictionary.cpp
+ enum_store_loaders.cpp
enumattribute.cpp
enumattributesaver.cpp
enumcomparator.cpp
+ enumerated_multi_value_read_view.cpp
enumhintsearchcontext.cpp
enummodifier.cpp
- enum_store_compaction_spec.cpp
- enum_store_dictionary.cpp
- enum_store_loaders.cpp
enumstore.cpp
- enumerated_multi_value_read_view.cpp
- extendableattributes.cpp
extendable_numeric_array_multi_value_read_view.cpp
extendable_numeric_weighted_set_multi_value_read_view.cpp
extendable_string_array_multi_value_read_view.cpp
extendable_string_weighted_set_multi_value_read_view.cpp
+ extendableattributes.cpp
fixedsourceselector.cpp
flagattribute.cpp
floatbase.cpp
@@ -82,8 +83,8 @@ vespa_add_library(searchlib_attribute OBJECT
multi_numeric_enum_search_context.cpp
multi_numeric_flag_search_context.cpp
multi_numeric_search_context.cpp
- multi_string_enum_search_context.cpp
multi_string_enum_hint_search_context.cpp
+ multi_string_enum_search_context.cpp
multi_value_mapping.cpp
multi_value_mapping_base.cpp
multienumattribute.cpp
@@ -98,11 +99,11 @@ vespa_add_library(searchlib_attribute OBJECT
multivalueattributesaver.cpp
multivalueattributesaverutils.cpp
not_implemented_attribute.cpp
- numericbase.cpp
numeric_matcher.cpp
numeric_range_matcher.cpp
numeric_search_context.cpp
numeric_sort_blob_writer.cpp
+ numericbase.cpp
posting_list_merger.cpp
postingchange.cpp
postinglistattribute.cpp
@@ -121,6 +122,17 @@ vespa_add_library(searchlib_attribute OBJECT
reference_mappings.cpp
save_utils.cpp
search_context.cpp
+ searchcontextelementiterator.cpp
+ single_enum_search_context.cpp
+ single_numeric_enum_search_context.cpp
+ single_numeric_search_context.cpp
+ single_raw_attribute.cpp
+ single_raw_attribute_loader.cpp
+ single_raw_attribute_saver.cpp
+ single_raw_ext_attribute.cpp
+ single_small_numeric_search_context.cpp
+ single_string_enum_hint_search_context.cpp
+ single_string_enum_search_context.cpp
singleboolattribute.cpp
singleenumattribute.cpp
singleenumattributesaver.cpp
@@ -131,22 +143,12 @@ vespa_add_library(searchlib_attribute OBJECT
singlesmallnumericattribute.cpp
singlestringattribute.cpp
singlestringpostattribute.cpp
- single_enum_search_context.cpp
- single_numeric_enum_search_context.cpp
- single_numeric_search_context.cpp
- single_raw_attribute.cpp
- single_raw_attribute_loader.cpp
- single_raw_attribute_saver.cpp
- single_raw_ext_attribute.cpp
- single_small_numeric_search_context.cpp
- single_string_enum_search_context.cpp
- single_string_enum_hint_search_context.cpp
sourceselector.cpp
- stringbase.cpp
string_matcher.cpp
string_search_context.cpp
string_search_helper.cpp
string_sort_blob_writer.cpp
+ stringbase.cpp
valuemodifier.cpp
DEPENDS
)
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
index cf8cbe3177f..50c79ce4108 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_blueprint_factory.cpp
@@ -1,12 +1,14 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "attribute_blueprint_factory.h"
+#include "attribute_blueprint_params.h"
+#include "attribute_object_visitor.h"
#include "attribute_weighted_set_blueprint.h"
+#include "direct_weighted_set_blueprint.h"
+#include "document_weight_or_filter_search.h"
#include "i_document_weight_attribute.h"
#include "iterator_pack.h"
#include "predicate_attribute.h"
-#include "attribute_blueprint_params.h"
-#include "document_weight_or_filter_search.h"
#include <vespa/eval/eval/value.h>
#include <vespa/searchlib/common/location.h>
#include <vespa/searchlib/common/locationiterators.h>
@@ -160,7 +162,7 @@ public:
void visitMembers(vespalib::ObjectVisitor &visitor) const override;
- const attribute::ISearchContext *get_attribute_search_context() const override {
+ const attribute::ISearchContext *get_attribute_search_context() const noexcept final {
return _search_context.get();
}
bool getRange(vespalib::string &from, vespalib::string &to) const override;
@@ -191,29 +193,7 @@ AttributeFieldBlueprint::AttributeFieldBlueprint(FieldSpecBase field, const IAtt
}
}
-vespalib::string
-get_type(const IAttributeVector& attr)
-{
- auto coll_type = CollectionType(attr.getCollectionType());
- auto basic_type = BasicType(attr.getBasicType());
- if (coll_type.type() == CollectionType::SINGLE) {
- return basic_type.asString();
- }
- std::ostringstream oss;
- oss << coll_type.asString() << "<" << basic_type.asString() << ">";
- return oss.str();
-}
-void
-visit_attribute(vespalib::ObjectVisitor& visitor, const IAttributeVector& attr)
-{
- visitor.openStruct("attribute", "IAttributeVector");
- visitor.visitString("name", attr.getName());
- visitor.visitString("type", get_type(attr));
- visitor.visitBool("fast_search", attr.getIsFastSearch());
- visitor.visitBool("filter", attr.getIsFilter());
- visitor.closeStruct();
-}
void
AttributeFieldBlueprint::visitMembers(vespalib::ObjectVisitor &visitor) const
@@ -408,101 +388,9 @@ private:
//-----------------------------------------------------------------------------
-template <typename SearchType>
-class DirectWeightedSetBlueprint : public ComplexLeafBlueprint
-{
-private:
- std::vector<int32_t> _weights;
- std::vector<IDocumentWeightAttribute::LookupResult> _terms;
- const IAttributeVector &_iattr;
- const IDocumentWeightAttribute &_attr;
- vespalib::datastore::EntryRef _dictionary_snapshot;
-public:
- DirectWeightedSetBlueprint(const FieldSpec &field, const IAttributeVector &iattr, const IDocumentWeightAttribute &attr, size_t size_hint)
- : ComplexLeafBlueprint(field),
- _weights(),
- _terms(),
- _iattr(iattr),
- _attr(attr),
- _dictionary_snapshot(_attr.get_dictionary_snapshot())
- {
- set_allow_termwise_eval(true);
- _weights.reserve(size_hint);
- _terms.reserve(size_hint);
- }
- ~DirectWeightedSetBlueprint() override;
- void addTerm(const IDocumentWeightAttribute::LookupKey & key, int32_t weight, HitEstimate & estimate) {
- IDocumentWeightAttribute::LookupResult result = _attr.lookup(key, _dictionary_snapshot);
- HitEstimate childEst(result.posting_size, (result.posting_size == 0));
- if (!childEst.empty) {
- if (estimate.empty) {
- estimate = childEst;
- } else {
- estimate.estHits += childEst.estHits;
- }
- _weights.push_back(weight);
- _terms.push_back(result);
- }
- }
- void complete(HitEstimate estimate) {
- setEstimate(estimate);
- }
- SearchIterator::UP createLeafSearch(const TermFieldMatchDataArray &tfmda, bool) const override;
-
- std::unique_ptr<SearchIterator> createFilterSearch(bool strict, FilterConstraint constraint) const override;
- std::unique_ptr<queryeval::MatchingElementsSearch> create_matching_elements_search(const MatchingElementsFields &fields) const override {
- if (fields.has_field(_iattr.getName())) {
- return queryeval::MatchingElementsSearch::create(_iattr, _dictionary_snapshot, vespalib::ConstArrayRef<IDocumentWeightAttribute::LookupResult>(_terms));
- } else {
- return {};
- }
- }
- void visitMembers(vespalib::ObjectVisitor& visitor) const override {
- LeafBlueprint::visitMembers(visitor);
- visit_attribute(visitor, _iattr);
- }
-};
-
-template <typename SearchType>
-SearchIterator::UP
-DirectWeightedSetBlueprint<SearchType>::createLeafSearch(const TermFieldMatchDataArray &tfmda, bool) const
-{
- assert(tfmda.size() == 1);
- assert(getState().numFields() == 1);
- if (_terms.empty()) {
- return std::make_unique<queryeval::EmptySearch>();
- }
- std::vector<DocumentWeightIterator> iterators;
- const size_t numChildren = _terms.size();
- iterators.reserve(numChildren);
- for (const IDocumentWeightAttribute::LookupResult &r : _terms) {
- _attr.create(r.posting_idx, iterators);
- }
- bool field_is_filter = getState().fields()[0].isFilter();
- if (field_is_filter && tfmda[0]->isNotNeeded()) {
- return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators));
- }
- return SearchType::create(*tfmda[0], field_is_filter, _weights, std::move(iterators));
-}
-
-
-template <typename SearchType>
-DirectWeightedSetBlueprint<SearchType>::~DirectWeightedSetBlueprint() = default;
-
-template <typename SearchType>
-std::unique_ptr<SearchIterator>
-DirectWeightedSetBlueprint<SearchType>::createFilterSearch(bool, FilterConstraint) const
-{
- std::vector<DocumentWeightIterator> iterators;
- iterators.reserve(_terms.size());
- for (const IDocumentWeightAttribute::LookupResult &r : _terms) {
- _attr.create(r.posting_idx, iterators);
- }
- return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators));
-}
//-----------------------------------------------------------------------------
@@ -798,7 +686,7 @@ public:
setResult(std::move(ws));
} else {
if (_dwa != nullptr) {
- auto *bp = new DirectWeightedSetBlueprint<queryeval::WeightedSetTermSearch>(_field, _attr, *_dwa, n.getNumTerms());
+ auto *bp = new attribute::DirectWeightedSetBlueprint<queryeval::WeightedSetTermSearch>(_field, _attr, *_dwa, n.getNumTerms());
createDirectWeightedSet(bp, n);
} else {
auto *bp = new WeightedSetTermBlueprint(_field);
@@ -809,7 +697,7 @@ public:
void visit(query::DotProduct &n) override {
if (_dwa != nullptr) {
- auto *bp = new DirectWeightedSetBlueprint<queryeval::DotProductSearch>(_field, _attr, *_dwa, n.getNumTerms());
+ auto *bp = new attribute::DirectWeightedSetBlueprint<queryeval::DotProductSearch>(_field, _attr, *_dwa, n.getNumTerms());
createDirectWeightedSet(bp, n);
} else {
auto *bp = new DotProductBlueprint(_field);
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.cpp
new file mode 100644
index 00000000000..39f39212d5c
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.cpp
@@ -0,0 +1,38 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "attribute_object_visitor.h"
+#include <vespa/searchcommon/attribute/iattributevector.h>
+#include <vespa/vespalib/objects/objectvisitor.h>
+#include <sstream>
+
+namespace search::attribute {
+
+namespace {
+
+vespalib::string
+get_type(const IAttributeVector& attr)
+{
+ auto coll_type = CollectionType(attr.getCollectionType());
+ auto basic_type = BasicType(attr.getBasicType());
+ if (coll_type.type() == CollectionType::SINGLE) {
+ return basic_type.asString();
+ }
+ std::ostringstream oss;
+ oss << coll_type.asString() << "<" << basic_type.asString() << ">";
+ return oss.str();
+}
+
+}
+
+void
+visit_attribute(vespalib::ObjectVisitor& visitor, const IAttributeVector& attr)
+{
+ visitor.openStruct("attribute", "IAttributeVector");
+ visitor.visitString("name", attr.getName());
+ visitor.visitString("type", get_type(attr));
+ visitor.visitBool("fast_search", attr.getIsFastSearch());
+ visitor.visitBool("filter", attr.getIsFilter());
+ visitor.closeStruct();
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.h b/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.h
new file mode 100644
index 00000000000..29c7e1556b6
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_object_visitor.h
@@ -0,0 +1,16 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+namespace vespalib { class ObjectVisitor; }
+
+namespace search::attribute {
+
+class IAttributeVector;
+
+/**
+ * Function used to visit the basic properties of an IAttributeVector.
+ */
+void visit_attribute(vespalib::ObjectVisitor& visitor, const IAttributeVector& attr);
+
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.cpp b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.cpp
new file mode 100644
index 00000000000..01b683f3b6d
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.cpp
@@ -0,0 +1,14 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "direct_weighted_set_blueprint.h"
+#include "direct_weighted_set_blueprint.hpp"
+#include <vespa/searchlib/queryeval/dot_product_search.h>
+#include <vespa/searchlib/queryeval/weighted_set_term_search.h>
+
+namespace search::attribute {
+
+template class DirectWeightedSetBlueprint<queryeval::WeightedSetTermSearch>;
+template class DirectWeightedSetBlueprint<queryeval::DotProductSearch>;
+
+}
+
diff --git a/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.h b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.h
new file mode 100644
index 00000000000..e50c7688ac7
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.h
@@ -0,0 +1,72 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "attribute_object_visitor.h"
+#include "i_document_weight_attribute.h"
+#include <vespa/searchcommon/attribute/iattributevector.h>
+#include <vespa/searchlib/common/matching_elements_fields.h>
+#include <vespa/searchlib/fef/termfieldmatchdataarray.h>
+#include <vespa/searchlib/queryeval/blueprint.h>
+#include <vespa/searchlib/queryeval/field_spec.h>
+#include <vespa/searchlib/queryeval/matching_elements_search.h>
+
+namespace search::queryeval { class SearchIterator; }
+
+namespace search::attribute {
+
+/**
+ * Blueprint used for WeightedSetTerm or DotProduct over a multi-value attribute
+ * which supports the IDocumentWeightAttribute interface.
+ *
+ * This allows access to low-level posting lists, which speeds up query execution.
+ */
+template <typename SearchType>
+class DirectWeightedSetBlueprint : public queryeval::ComplexLeafBlueprint
+{
+private:
+ std::vector<int32_t> _weights;
+ std::vector<IDocumentWeightAttribute::LookupResult> _terms;
+ const IAttributeVector &_iattr;
+ const IDocumentWeightAttribute &_attr;
+ vespalib::datastore::EntryRef _dictionary_snapshot;
+
+public:
+ DirectWeightedSetBlueprint(const queryeval::FieldSpec &field, const IAttributeVector &iattr, const IDocumentWeightAttribute &attr, size_t size_hint);
+ ~DirectWeightedSetBlueprint() override;
+
+ void addTerm(const IDocumentWeightAttribute::LookupKey & key, int32_t weight, HitEstimate & estimate) {
+ IDocumentWeightAttribute::LookupResult result = _attr.lookup(key, _dictionary_snapshot);
+ HitEstimate childEst(result.posting_size, (result.posting_size == 0));
+ if (!childEst.empty) {
+ if (estimate.empty) {
+ estimate = childEst;
+ } else {
+ estimate.estHits += childEst.estHits;
+ }
+ _weights.push_back(weight);
+ _terms.push_back(result);
+ }
+ }
+ void complete(HitEstimate estimate) {
+ setEstimate(estimate);
+ }
+
+ std::unique_ptr<queryeval::SearchIterator> createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool) const override;
+
+ std::unique_ptr<queryeval::SearchIterator> createFilterSearch(bool strict, FilterConstraint constraint) const override;
+ std::unique_ptr<queryeval::MatchingElementsSearch> create_matching_elements_search(const MatchingElementsFields &fields) const override {
+ if (fields.has_field(_iattr.getName())) {
+ return queryeval::MatchingElementsSearch::create(_iattr, _dictionary_snapshot, vespalib::ConstArrayRef<IDocumentWeightAttribute::LookupResult>(_terms));
+ } else {
+ return {};
+ }
+ }
+ void visitMembers(vespalib::ObjectVisitor& visitor) const override {
+ LeafBlueprint::visitMembers(visitor);
+ visit_attribute(visitor, _iattr);
+ }
+};
+
+}
+
diff --git a/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.hpp b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.hpp
new file mode 100644
index 00000000000..bf6410c347c
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/attribute/direct_weighted_set_blueprint.hpp
@@ -0,0 +1,67 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "direct_weighted_set_blueprint.h"
+#include "document_weight_or_filter_search.h"
+#include <vespa/searchlib/fef/termfieldmatchdata.h>
+#include <vespa/searchlib/queryeval/emptysearch.h>
+#include <memory>
+
+namespace search::attribute {
+
+template <typename SearchType>
+DirectWeightedSetBlueprint<SearchType>::DirectWeightedSetBlueprint(const queryeval::FieldSpec &field,
+ const IAttributeVector &iattr,
+ const IDocumentWeightAttribute &attr,
+ size_t size_hint)
+ : ComplexLeafBlueprint(field),
+ _weights(),
+ _terms(),
+ _iattr(iattr),
+ _attr(attr),
+ _dictionary_snapshot(_attr.get_dictionary_snapshot())
+{
+ set_allow_termwise_eval(true);
+ _weights.reserve(size_hint);
+ _terms.reserve(size_hint);
+}
+
+template <typename SearchType>
+DirectWeightedSetBlueprint<SearchType>::~DirectWeightedSetBlueprint() = default;
+
+template <typename SearchType>
+std::unique_ptr<queryeval::SearchIterator>
+DirectWeightedSetBlueprint<SearchType>::createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool) const
+{
+ assert(tfmda.size() == 1);
+ assert(getState().numFields() == 1);
+ if (_terms.empty()) {
+ return std::make_unique<queryeval::EmptySearch>();
+ }
+ std::vector<DocumentWeightIterator> iterators;
+ const size_t numChildren = _terms.size();
+ iterators.reserve(numChildren);
+ for (const IDocumentWeightAttribute::LookupResult &r : _terms) {
+ _attr.create(r.posting_idx, iterators);
+ }
+ bool field_is_filter = getState().fields()[0].isFilter();
+ if (field_is_filter && tfmda[0]->isNotNeeded()) {
+ return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators));
+ }
+ return SearchType::create(*tfmda[0], field_is_filter, _weights, std::move(iterators));
+}
+
+template <typename SearchType>
+std::unique_ptr<queryeval::SearchIterator>
+DirectWeightedSetBlueprint<SearchType>::createFilterSearch(bool, FilterConstraint) const
+{
+ std::vector<DocumentWeightIterator> iterators;
+ iterators.reserve(_terms.size());
+ for (const IDocumentWeightAttribute::LookupResult &r : _terms) {
+ _attr.create(r.posting_idx, iterators);
+ }
+ return attribute::DocumentWeightOrFilterSearch::create(std::move(iterators));
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp b/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp
index 44413b42921..3dda6017e0a 100644
--- a/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/iterator_pack.cpp
@@ -2,6 +2,7 @@
#include "iterator_pack.h"
#include <vespa/searchlib/common/bitvector.h>
+#include <limits>
namespace search {
@@ -10,7 +11,7 @@ AttributeIteratorPack::~AttributeIteratorPack() = default;
AttributeIteratorPack::AttributeIteratorPack(std::vector<DocumentWeightIterator> &&children)
: _children(std::move(children))
{
- assert(_children.size() < 0x10000);
+ assert(_children.size() <= std::numeric_limits<ref_t>::max());
}
std::unique_ptr<BitVector>
diff --git a/searchlib/src/vespa/searchlib/attribute/iterator_pack.h b/searchlib/src/vespa/searchlib/attribute/iterator_pack.h
index 388dce3aeb7..04df7c9b772 100644
--- a/searchlib/src/vespa/searchlib/attribute/iterator_pack.h
+++ b/searchlib/src/vespa/searchlib/attribute/iterator_pack.h
@@ -15,6 +15,7 @@ private:
std::vector<DocumentWeightIterator> _children;
public:
+ using ref_t = uint16_t;
AttributeIteratorPack() noexcept : _children() {}
AttributeIteratorPack(AttributeIteratorPack &&rhs) noexcept = default;
AttributeIteratorPack &operator=(AttributeIteratorPack &&rhs) noexcept = default;
@@ -22,11 +23,11 @@ public:
explicit AttributeIteratorPack(std::vector<DocumentWeightIterator> &&children);
~AttributeIteratorPack();
- uint32_t get_docid(uint16_t ref) const {
+ uint32_t get_docid(ref_t ref) const {
return _children[ref].valid() ? _children[ref].getKey() : endDocId;
}
- uint32_t seek(uint16_t ref, uint32_t docid) {
+ uint32_t seek(ref_t ref, uint32_t docid) {
_children[ref].linearSeek(docid);
if (__builtin_expect(_children[ref].valid(), true)) {
return _children[ref].getKey();
@@ -34,14 +35,14 @@ public:
return endDocId;
}
- int32_t get_weight(uint16_t ref, uint32_t) {
+ int32_t get_weight(ref_t ref, uint32_t) {
return _children[ref].getData();
}
std::unique_ptr<BitVector> get_hits(uint32_t begin_id, uint32_t end_id);
void or_hits_into(BitVector &result, uint32_t begin_id);
- uint16_t size() const noexcept { return _children.size(); }
+ ref_t size() const noexcept { return _children.size(); }
void initRange(uint32_t begin, uint32_t end) {
(void) end;
for (auto &child: _children) {
@@ -49,7 +50,7 @@ public:
}
}
private:
- uint32_t next(uint16_t ref) {
+ uint32_t next(ref_t ref) {
++_children[ref];
return get_docid(ref);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h
index 09388d6d44c..2d0b8cbd733 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.h
@@ -61,13 +61,13 @@ private:
using DocIndices = typename MultiValueNumericEnumAttribute<B, M>::DocIndices;
using FrozenDictionary = typename Dictionary::FrozenView;
using Posting = typename PostingParent::Posting;
- using PostingList = typename PostingParent::PostingList;
+ using PostingStore = typename PostingParent::PostingStore;
using PostingMap = typename PostingParent::PostingMap;
using QueryTermSimpleUP = AttributeVector::QueryTermSimpleUP;
using WeightedIndex = typename MultiValueNumericEnumAttribute<B, M>::WeightedIndex;
using generation_t = typename MultiValueNumericEnumAttribute<B, M>::generation_t;
- using PostingParent::_postingList;
+ using PostingParent::_posting_store;
using PostingParent::clearAllPostings;
using PostingParent::handle_load_posting_lists;
using PostingParent::handle_load_posting_lists_and_update_enum_store;
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
index 38e464f207a..53c183a6987 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
@@ -21,7 +21,7 @@ void
MultiValueNumericPostingAttribute<B, M>::mergeMemoryStats(vespalib::MemoryUsage & total)
{
auto& compaction_strategy = this->getConfig().getCompactionStrategy();
- total.merge(this->getPostingList().update_stat(compaction_strategy));
+ total.merge(this->get_posting_store().update_stat(compaction_strategy));
}
template <typename B, typename M>
@@ -60,16 +60,16 @@ void
MultiValueNumericPostingAttribute<B, M>::reclaim_memory(generation_t oldest_used_gen)
{
MultiValueNumericEnumAttribute<B, M>::reclaim_memory(oldest_used_gen);
- _postingList.reclaim_memory(oldest_used_gen);
+ _posting_store.reclaim_memory(oldest_used_gen);
}
template <typename B, typename M>
void
MultiValueNumericPostingAttribute<B, M>::before_inc_generation(generation_t current_gen)
{
- _postingList.freeze();
+ _posting_store.freeze();
MultiValueNumericEnumAttribute<B, M>::before_inc_generation(current_gen);
- _postingList.assign_generation(current_gen);
+ _posting_store.assign_generation(current_gen);
}
template <typename B, typename M>
@@ -106,9 +106,9 @@ MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::lookup(
if (find_result.first.valid()) {
auto pidx = find_result.second;
if (pidx.valid()) {
- const PostingList &plist = self.getPostingList();
- auto minmax = plist.getAggregated(pidx);
- return LookupResult(pidx, plist.frozenSize(pidx), minmax.getMin(), minmax.getMax(), find_result.first);
+ const auto& store = self.get_posting_store();
+ auto minmax = store.getAggregated(pidx);
+ return LookupResult(pidx, store.frozenSize(pidx), minmax.getMin(), minmax.getMax(), find_result.first);
}
}
return LookupResult();
@@ -127,7 +127,7 @@ void
MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::create(vespalib::datastore::EntryRef idx, std::vector<DocumentWeightIterator> &dst) const
{
assert(idx.valid());
- self.getPostingList().beginFrozen(idx, dst);
+ self.get_posting_store().beginFrozen(idx, dst);
}
template <typename B, typename M>
@@ -135,21 +135,21 @@ DocumentWeightIterator
MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::create(vespalib::datastore::EntryRef idx) const
{
assert(idx.valid());
- return self.getPostingList().beginFrozen(idx);
+ return self.get_posting_store().beginFrozen(idx);
}
template <typename B, typename M>
std::unique_ptr<queryeval::SearchIterator>
MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::make_bitvector_iterator(vespalib::datastore::EntryRef idx, uint32_t doc_id_limit, fef::TermFieldMatchData &match_data, bool strict) const
{
- return self.getPostingList().make_bitvector_iterator(idx, doc_id_limit, match_data, strict);
+ return self.get_posting_store().make_bitvector_iterator(idx, doc_id_limit, match_data, strict);
}
template <typename B, typename M>
bool
MultiValueNumericPostingAttribute<B, M>::DocumentWeightAttributeAdapter::has_weight_iterator(vespalib::datastore::EntryRef idx) const noexcept
{
- return self.getPostingList().has_btree(idx);
+ return self.get_posting_store().has_btree(idx);
}
template <typename B, typename M>
diff --git a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h
index 9ecfa93e5ec..67f4f25ac5b 100644
--- a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.h
@@ -59,7 +59,7 @@ private:
using WeightedIndex = typename MultiValueStringAttributeT<B, T>::WeightedIndex;
using generation_t = typename MultiValueStringAttributeT<B, T>::generation_t;
- using PostingParent::_postingList;
+ using PostingParent::_posting_store;
using PostingParent::clearAllPostings;
using PostingParent::handle_load_posting_lists;
using PostingParent::handle_load_posting_lists_and_update_enum_store;
@@ -70,9 +70,9 @@ private:
void applyValueChanges(const DocIndices& docIndices, EnumStoreBatchUpdater& updater) override ;
public:
- using PostingParent::getPostingList;
+ using PostingParent::get_posting_store;
using Dictionary = EnumPostingTree;
- using PostingList = typename PostingParent::PostingList;
+ using PostingStore = typename PostingParent::PostingStore;
MultiValueStringPostingAttributeT(const vespalib::string & name, const AttributeVector::Config & c);
MultiValueStringPostingAttributeT(const vespalib::string & name);
diff --git a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
index 7c162d32c1f..5d4f140b96c 100644
--- a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
@@ -69,7 +69,7 @@ void
MultiValueStringPostingAttributeT<B, T>::mergeMemoryStats(vespalib::MemoryUsage &total)
{
auto& compaction_strategy = this->getConfig().getCompactionStrategy();
- total.merge(this->_postingList.update_stat(compaction_strategy));
+ total.merge(this->_posting_store.update_stat(compaction_strategy));
}
template <typename B, typename T>
@@ -77,16 +77,16 @@ void
MultiValueStringPostingAttributeT<B, T>::reclaim_memory(generation_t oldest_used_gen)
{
MultiValueStringAttributeT<B, T>::reclaim_memory(oldest_used_gen);
- _postingList.reclaim_memory(oldest_used_gen);
+ _posting_store.reclaim_memory(oldest_used_gen);
}
template <typename B, typename T>
void
MultiValueStringPostingAttributeT<B, T>::before_inc_generation(generation_t current_gen)
{
- _postingList.freeze();
+ _posting_store.freeze();
MultiValueStringAttributeT<B, T>::before_inc_generation(current_gen);
- _postingList.assign_generation(current_gen);
+ _posting_store.assign_generation(current_gen);
}
@@ -126,9 +126,9 @@ MultiValueStringPostingAttributeT<B, T>::DocumentWeightAttributeAdapter::lookup(
if (find_result.first.valid()) {
auto pidx = find_result.second;
if (pidx.valid()) {
- const PostingList &plist = self.getPostingList();
- auto minmax = plist.getAggregated(pidx);
- return LookupResult(pidx, plist.frozenSize(pidx), minmax.getMin(), minmax.getMax(), find_result.first);
+ const auto& store = self.get_posting_store();
+ auto minmax = store.getAggregated(pidx);
+ return LookupResult(pidx, store.frozenSize(pidx), minmax.getMin(), minmax.getMax(), find_result.first);
}
}
return LookupResult();
@@ -147,7 +147,7 @@ void
MultiValueStringPostingAttributeT<B, T>::DocumentWeightAttributeAdapter::create(vespalib::datastore::EntryRef idx, std::vector<DocumentWeightIterator> &dst) const
{
assert(idx.valid());
- self.getPostingList().beginFrozen(idx, dst);
+ self.get_posting_store().beginFrozen(idx, dst);
}
template <typename B, typename M>
@@ -155,21 +155,21 @@ DocumentWeightIterator
MultiValueStringPostingAttributeT<B, M>::DocumentWeightAttributeAdapter::create(vespalib::datastore::EntryRef idx) const
{
assert(idx.valid());
- return self.getPostingList().beginFrozen(idx);
+ return self.get_posting_store().beginFrozen(idx);
}
template <typename B, typename M>
bool
MultiValueStringPostingAttributeT<B, M>::DocumentWeightAttributeAdapter::has_weight_iterator(vespalib::datastore::EntryRef idx) const noexcept
{
- return self.getPostingList().has_btree(idx);
+ return self.get_posting_store().has_btree(idx);
}
template <typename B, typename M>
std::unique_ptr<queryeval::SearchIterator>
MultiValueStringPostingAttributeT<B, M>::DocumentWeightAttributeAdapter::make_bitvector_iterator(vespalib::datastore::EntryRef idx, uint32_t doc_id_limit, fef::TermFieldMatchData &match_data, bool strict) const
{
- return self.getPostingList().make_bitvector_iterator(idx, doc_id_limit, match_data, strict);
+ return self.get_posting_store().make_bitvector_iterator(idx, doc_id_limit, match_data, strict);
}
template <typename B, typename T>
diff --git a/searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h b/searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h
index 8f350f34c35..bc792b68a88 100644
--- a/searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h
+++ b/searchlib/src/vespa/searchlib/attribute/posting_list_traverser.h
@@ -5,18 +5,17 @@
namespace search::attribute {
/*
- * Class used to traverse a posting list and call the functor for each
- * lid.
+ * Class used to traverse a posting list and call the functor for each lid.
*/
-template <typename PostingList>
+template <typename PostingStore>
class PostingListTraverser
{
using EntryRef = vespalib::datastore::EntryRef;
- const PostingList &_postingList;
+ const PostingStore &_posting_store;
EntryRef _pidx;
public:
- PostingListTraverser(const PostingList &postingList, EntryRef pidx)
- : _postingList(postingList),
+ PostingListTraverser(const PostingStore &posting_store, EntryRef pidx)
+ : _posting_store(posting_store),
_pidx(pidx)
{
}
@@ -25,13 +24,13 @@ public:
template <typename Func>
void
foreach(Func func) const {
- _postingList.foreach_frozen(_pidx, func);
+ _posting_store.foreach_frozen(_pidx, func);
}
template <typename Func>
void
foreach_key(Func func) const {
- _postingList.foreach_frozen_key(_pidx, func);
+ _posting_store.foreach_frozen_key(_pidx, func);
}
};
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp
index 4e88fb96c7e..d3f9c3f5d82 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp
@@ -15,8 +15,8 @@ PostingListAttributeBase<P>::
PostingListAttributeBase(AttributeVector &attr,
IEnumStore &enumStore)
: attribute::IPostingListAttributeBase(),
- _postingList(enumStore.get_dictionary(), attr.getStatus(),
- attr.getConfig()),
+ _posting_store(enumStore.get_dictionary(), attr.getStatus(),
+ attr.getConfig()),
_attr(attr),
_dictionary(enumStore.get_dictionary())
{ }
@@ -28,11 +28,11 @@ template <typename P>
void
PostingListAttributeBase<P>::clearAllPostings()
{
- _postingList.clearBuilder();
+ _posting_store.clearBuilder();
_attr.incGeneration(); // Force freeze
auto clearer = [this](EntryRef posting_idx)
{
- _postingList.clear(posting_idx);
+ _posting_store.clear(posting_idx);
};
_dictionary.clear_all_posting_lists(clearer);
_attr.incGeneration(); // Force freeze
@@ -69,7 +69,7 @@ PostingListAttributeBase<P>::handle_load_posting_lists_and_update_enum_store(enu
if (loader.is_folded_change(enum_indexes[posting_enum], enum_indexes[preve])) {
postings.removeDups();
newIndex = EntryRef();
- _postingList.apply(newIndex,
+ _posting_store.apply(newIndex,
postings._additions.data(),
postings._additions.data() +
postings._additions.size(),
@@ -91,7 +91,7 @@ PostingListAttributeBase<P>::handle_load_posting_lists_and_update_enum_store(enu
loader.set_ref_count(enum_indexes[preve], refCount);
postings.removeDups();
newIndex = EntryRef();
- _postingList.apply(newIndex,
+ _posting_store.apply(newIndex,
postings._additions.data(),
postings._additions.data() + postings._additions.size(),
postings._removals.data(),
@@ -112,7 +112,7 @@ PostingListAttributeBase<P>::updatePostings(PostingMap &changePost,
change.removeDups();
auto updater= [this, &change](EntryRef posting_idx) -> EntryRef
{
- _postingList.apply(posting_idx,
+ _posting_store.apply(posting_idx,
change._additions.data(),
change._additions.data() + change._additions.size(),
change._removals.data(),
@@ -135,7 +135,7 @@ PostingListAttributeBase<P>::forwardedOnAddDoc(DocId doc,
if (doc >= wantCapacity) {
wantCapacity = doc + 1;
}
- return _postingList.resizeBitVectors(wantSize, wantCapacity);
+ return _posting_store.resizeBitVectors(wantSize, wantCapacity);
}
template <typename P>
@@ -155,7 +155,7 @@ clearPostings(attribute::IAttributeVector::EnumHandle eidx,
EntryRef er(eidx);
auto updater = [this, &postings](EntryRef posting_idx) -> EntryRef
{
- _postingList.apply(posting_idx,
+ _posting_store.apply(posting_idx,
postings._additions.data(),
postings._additions.data() + postings._additions.size(),
postings._removals.data(),
@@ -169,28 +169,28 @@ template <typename P>
void
PostingListAttributeBase<P>::forwardedShrinkLidSpace(uint32_t newSize)
{
- (void) _postingList.resizeBitVectors(newSize, newSize);
+ (void) _posting_store.resizeBitVectors(newSize, newSize);
}
template <typename P>
attribute::PostingStoreMemoryUsage
PostingListAttributeBase<P>::getMemoryUsage() const
{
- return _postingList.getMemoryUsage();
+ return _posting_store.getMemoryUsage();
}
template <typename P>
bool
PostingListAttributeBase<P>::consider_compact_worst_btree_nodes(const CompactionStrategy& compaction_strategy)
{
- return _postingList.consider_compact_worst_btree_nodes(compaction_strategy);
+ return _posting_store.consider_compact_worst_btree_nodes(compaction_strategy);
}
template <typename P>
bool
PostingListAttributeBase<P>::consider_compact_worst_buffers(const CompactionStrategy& compaction_strategy)
{
- return _postingList.consider_compact_worst_buffers(compaction_strategy);
+ return _posting_store.consider_compact_worst_buffers(compaction_strategy);
}
template <typename P, typename LoadedVector, typename LoadedValueType,
@@ -219,7 +219,7 @@ handle_load_posting_lists(LoadedVector& loaded)
EntryRef newIndex;
PostingChange<P> postings;
uint32_t docIdLimit = _attr.getNumDocs();
- _postingList.resizeBitVectors(docIdLimit, docIdLimit);
+ _posting_store.resizeBitVectors(docIdLimit, docIdLimit);
if ( ! loaded.empty() ) {
vespalib::Array<typename LoadedVector::Type> similarValues;
auto value = loaded.read();
@@ -237,7 +237,7 @@ handle_load_posting_lists(LoadedVector& loaded)
} else {
postings.removeDups();
newIndex = EntryRef();
- _postingList.apply(newIndex,
+ _posting_store.apply(newIndex,
postings._additions.data(),
postings._additions.data() +
postings._additions.size(),
@@ -259,7 +259,7 @@ handle_load_posting_lists(LoadedVector& loaded)
}
postings.removeDups();
newIndex = EntryRef();
- _postingList.apply(newIndex,
+ _posting_store.apply(newIndex,
postings._additions.data(),
postings._additions.data() +
postings._additions.size(),
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h
index 3987d661d26..e2be0d69434 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h
@@ -43,10 +43,10 @@ protected:
using DocId = AttributeVector::DocId;
using EntryRef = vespalib::datastore::EntryRef;
using EnumIndex = IEnumStore::Index;
- using PostingList = typename AggregationTraits::PostingList;
+ using PostingStore = typename AggregationTraits::PostingStoreType;
using PostingMap = std::map<EnumPostingPair, PostingChange<P> >;
- PostingList _postingList;
+ PostingStore _posting_store;
AttributeVector &_attr;
IEnumStoreDictionary& _dictionary;
@@ -57,8 +57,8 @@ protected:
void updatePostings(PostingMap &changePost, const vespalib::datastore::EntryComparator &cmp);
void clearAllPostings();
- void disableFreeLists() { _postingList.disableFreeLists(); }
- void disable_entry_hold_list() { _postingList.disable_entry_hold_list(); }
+ void disableFreeLists() { _posting_store.disableFreeLists(); }
+ void disable_entry_hold_list() { _posting_store.disable_entry_hold_list(); }
void handle_load_posting_lists_and_update_enum_store(enumstore::EnumeratedPostingsLoader& loader);
bool forwardedOnAddDoc(DocId doc, size_t wantSize, size_t wantCapacity);
@@ -71,8 +71,8 @@ protected:
bool consider_compact_worst_buffers(const CompactionStrategy& compaction_strategy) override;
public:
- const PostingList & getPostingList() const { return _postingList; }
- PostingList & getPostingList() { return _postingList; }
+ const PostingStore & get_posting_store() const { return _posting_store; }
+ PostingStore & get_posting_store() { return _posting_store; }
};
template <typename P, typename LoadedVector, typename LoadedValueType,
@@ -86,14 +86,14 @@ public:
using EnumIndex = IEnumStore::Index;
using EnumStore = EnumStoreType;
using ComparatorType = typename EnumStore::ComparatorType;
- using PostingList = typename Parent::PostingList;
+ using PostingStore = typename Parent::PostingStore;
using PostingMap = typename Parent::PostingMap;
using Parent::clearAllPostings;
using Parent::updatePostings;
using Parent::handle_load_posting_lists_and_update_enum_store;
using Parent::clearPostings;
- using Parent::_postingList;
+ using Parent::_posting_store;
using Parent::_attr;
using Parent::_dictionary;
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h
index f45f8f2245e..f5683546eea 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.h
@@ -34,6 +34,7 @@ protected:
using FrozenDictionary = Dictionary::FrozenView;
using EntryRef = vespalib::datastore::EntryRef;
using EnumIndex = IEnumStore::Index;
+ static constexpr uint32_t max_posting_lists_to_count = 1000;
const IEnumStoreDictionary& _dictionary;
const ISearchContext& _baseSearchCtx;
@@ -82,20 +83,20 @@ class PostingListSearchContextT : public PostingListSearchContext
protected:
using DataType = DataT;
using Traits = PostingListTraits<DataType>;
- using PostingList = typename Traits::PostingList;
+ using PostingStore = typename Traits::PostingStoreType;
using Posting = typename Traits::Posting;
using AtomicEntryRef = vespalib::datastore::AtomicEntryRef;
using EntryRef = vespalib::datastore::EntryRef;
- using FrozenView = typename PostingList::BTreeType::FrozenView;
+ using FrozenView = typename PostingStore::BTreeType::FrozenView;
- const PostingList &_postingList;
+ const PostingStore& _posting_store;
/*
* Synthetic posting lists for range search, in array or bitvector form
*/
PostingListMerger<DataT> _merger;
PostingListSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues,
- bool hasWeight, const PostingList &postingList,
+ bool hasWeight, const PostingStore& posting_store,
bool useBitVector, const ISearchContext &baseSearchCtx);
~PostingListSearchContextT() override;
@@ -113,7 +114,7 @@ protected:
unsigned int singleHits() const;
unsigned int approximateHits() const override;
- void applyRangeLimit(int rangeLimit);
+ void applyRangeLimit(long rangeLimit);
};
@@ -128,11 +129,11 @@ protected:
using Dictionary = typename Parent::Dictionary;
using DictionaryConstIterator = Dictionary::ConstIterator;
using EntryRef = vespalib::datastore::EntryRef;
- using PostingList = typename Parent::PostingList;
+ using PostingStore = typename Parent::PostingStore;
using Parent::_docIdLimit;
using Parent::_lowerDictItr;
using Parent::_merger;
- using Parent::_postingList;
+ using Parent::_posting_store;
using Parent::_uniqueValues;
using Parent::_upperDictItr;
using Parent::singleHits;
@@ -142,7 +143,7 @@ protected:
mutable std::vector<EntryRef> _posting_indexes;
PostingListFoldedSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues,
- bool hasWeight, const PostingList &postingList,
+ bool hasWeight, const PostingStore& posting_store,
bool useBitVector, const ISearchContext &baseSearchCtx);
~PostingListFoldedSearchContextT() override;
@@ -242,7 +243,7 @@ PostingSearchContext(BaseSC&& base_sc, bool useBitVector, const AttrT &toBeSearc
toBeSearched.getCommittedDocIdLimit(),
toBeSearched.getStatus().getNumValues(),
toBeSearched.hasWeightedSetType(),
- toBeSearched.getPostingList(),
+ toBeSearched.get_posting_store(),
useBitVector,
*this),
_toBeSearched(toBeSearched),
@@ -451,14 +452,14 @@ NumericPostingSearchContext<BaseSC, AttrT, DataT>::calc_estimated_hits_in_range(
{
size_t exact_sum = 0;
size_t estimated_sum = 0;
- constexpr uint32_t max_posting_lists_to_count = 1000;
+
auto it = this->_lowerDictItr;
- for (uint32_t count = 0; (it != this->_upperDictItr) && (count < max_posting_lists_to_count); ++it, ++count) {
- exact_sum += this->_postingList.frozenSize(it.getData().load_acquire());
+ for (uint32_t count = 0; (it != this->_upperDictItr) && (count < this->max_posting_lists_to_count); ++it, ++count) {
+ exact_sum += this->_posting_store.frozenSize(it.getData().load_acquire());
}
if (it != this->_upperDictItr) {
uint32_t remaining_posting_lists = this->_upperDictItr - it;
- float hits_per_posting_list = static_cast<float>(exact_sum) / static_cast<float>(max_posting_lists_to_count);
+ float hits_per_posting_list = static_cast<float>(exact_sum) / static_cast<float>(this->max_posting_lists_to_count);
estimated_sum = remaining_posting_lists * hits_per_posting_list;
}
return exact_sum + estimated_sum;
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp
index cc12b1f7825..27ef06565a6 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistsearchcontext.hpp
@@ -21,9 +21,9 @@ namespace search::attribute {
template <typename DataT>
PostingListSearchContextT<DataT>::
PostingListSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues, bool hasWeight,
- const PostingList &postingList, bool useBitVector, const ISearchContext &searchContext)
+ const PostingStore& posting_store, bool useBitVector, const ISearchContext &searchContext)
: PostingListSearchContext(dictionary, dictionary.get_has_btree_dictionary(), docIdLimit, numValues, hasWeight, useBitVector, searchContext),
- _postingList(postingList),
+ _posting_store(posting_store),
_merger(docIdLimit)
{
}
@@ -39,16 +39,16 @@ PostingListSearchContextT<DataT>::lookupSingle()
PostingListSearchContext::lookupSingle();
if (!_pidx.valid())
return;
- uint32_t typeId = _postingList.getTypeId(_pidx);
- if (!_postingList.isSmallArray(typeId)) {
- if (_postingList.isBitVector(typeId)) {
- const BitVectorEntry *bve = _postingList.getBitVectorEntry(_pidx);
+ uint32_t typeId = _posting_store.getTypeId(_pidx);
+ if (!_posting_store.isSmallArray(typeId)) {
+ if (_posting_store.isBitVector(typeId)) {
+ const BitVectorEntry *bve = _posting_store.getBitVectorEntry(_pidx);
const GrowableBitVector *bv = bve->_bv.get();
_bv = &bv->reader();
_pidx = bve->_tree;
}
if (_pidx.valid()) {
- auto frozenView = _postingList.getTreeEntry(_pidx)->getFrozenView(_postingList.getAllocator());
+ auto frozenView = _posting_store.getTreeEntry(_pidx)->getFrozenView(_posting_store.getAllocator());
_frozenRoot = frozenView.getRoot();
if (!_frozenRoot.valid()) {
_pidx = vespalib::datastore::EntryRef();
@@ -62,7 +62,7 @@ void
PostingListSearchContextT<DataT>::fillArray()
{
for (auto it(_lowerDictItr); it != _upperDictItr; ++it) {
- _merger.addToArray(PostingListTraverser<PostingList>(_postingList,
+ _merger.addToArray(PostingListTraverser<PostingStore>(_posting_store,
it.getData().load_acquire()));
}
_merger.merge();
@@ -73,8 +73,8 @@ void
PostingListSearchContextT<DataT>::fillBitVector()
{
for (auto it(_lowerDictItr); it != _upperDictItr; ++it) {
- _merger.addToBitVector(PostingListTraverser<PostingList>(_postingList,
- it.getData().load_acquire()));
+ _merger.addToBitVector(PostingListTraverser<PostingStore>(_posting_store,
+ it.getData().load_acquire()));
}
}
@@ -134,10 +134,10 @@ PostingListSearchContextT<DataT>::diversify(bool forward, size_t wanted_hits, co
if (!_merger.merge_done()) {
_merger.reserveArray(128, wanted_hits);
if (_uniqueValues == 1u && !_lowerDictItr.valid() && _pidx.valid()) {
- diversity::diversify_single(_pidx, _postingList, wanted_hits, diversity_attr,
+ diversity::diversify_single(_pidx, _posting_store, wanted_hits, diversity_attr,
max_per_group, cutoff_groups, cutoff_strict, _merger.getWritableArray(), _merger.getWritableStartPos());
} else {
- diversity::diversify(forward, _lowerDictItr, _upperDictItr, _postingList, wanted_hits, diversity_attr,
+ diversity::diversify(forward, _lowerDictItr, _upperDictItr, _posting_store, wanted_hits, diversity_attr,
max_per_group, cutoff_groups, cutoff_strict, _merger.getWritableArray(), _merger.getWritableStartPos());
}
_merger.merge();
@@ -160,7 +160,7 @@ createPostingIterator(fef::TermFieldMatchData *matchData, bool strict)
DocIt postings;
vespalib::ConstArrayRef<Posting> array = _merger.getArray();
postings.set(&array[0], &array[array.size()]);
- if (_postingList.isFilter()) {
+ if (_posting_store.isFilter()) {
return std::make_unique<FilterAttributePostingListIteratorT<DocIt>>(_baseSearchCtx, matchData, postings);
} else {
return std::make_unique<AttributePostingListIteratorT<DocIt>>(_baseSearchCtx, _hasWeight, matchData, postings);
@@ -180,24 +180,23 @@ createPostingIterator(fef::TermFieldMatchData *matchData, bool strict)
if (!_pidx.valid()) {
return std::make_unique<EmptySearch>();
}
- const PostingList &postingList = _postingList;
if (!_frozenRoot.valid()) {
- uint32_t clusterSize = _postingList.getClusterSize(_pidx);
+ uint32_t clusterSize = _posting_store.getClusterSize(_pidx);
assert(clusterSize != 0);
using DocIt = DocIdMinMaxIterator<Posting>;
DocIt postings;
- const Posting *array = postingList.getKeyDataEntry(_pidx, clusterSize);
+ const Posting *array = _posting_store.getKeyDataEntry(_pidx, clusterSize);
postings.set(array, array + clusterSize);
- if (postingList.isFilter()) {
+ if (_posting_store.isFilter()) {
return std::make_unique<FilterAttributePostingListIteratorT<DocIt>>(_baseSearchCtx, matchData, postings);
} else {
return std::make_unique<AttributePostingListIteratorT<DocIt>>(_baseSearchCtx, _hasWeight, matchData, postings);
}
}
- typename PostingList::BTreeType::FrozenView frozen(_frozenRoot, postingList.getAllocator());
+ typename PostingStore::BTreeType::FrozenView frozen(_frozenRoot, _posting_store.getAllocator());
- using DocIt = typename PostingList::ConstIterator;
- if (_postingList.isFilter()) {
+ using DocIt = typename PostingStore::ConstIterator;
+ if (_posting_store.isFilter()) {
return std::make_unique<FilterAttributePostingListIteratorT<DocIt>>(_baseSearchCtx, matchData, frozen.getRoot(), frozen.getAllocator());
} else {
return std::make_unique<AttributePostingListIteratorT<DocIt>> (_baseSearchCtx, _hasWeight, matchData, frozen.getRoot(), frozen.getAllocator());
@@ -220,9 +219,9 @@ PostingListSearchContextT<DataT>::singleHits() const
return 0u;
}
if (!_frozenRoot.valid()) {
- return _postingList.getClusterSize(_pidx);
+ return _posting_store.getClusterSize(_pidx);
}
- typename PostingList::BTreeType::FrozenView frozenView(_frozenRoot, _postingList.getAllocator());
+ typename PostingStore::BTreeType::FrozenView frozenView(_frozenRoot, _posting_store.getAllocator());
return frozenView.size();
}
@@ -244,34 +243,55 @@ PostingListSearchContextT<DataT>::approximateHits() const
template <typename DataT>
void
-PostingListSearchContextT<DataT>::applyRangeLimit(int rangeLimit)
+PostingListSearchContextT<DataT>::applyRangeLimit(long rangeLimit)
{
+ long n = 0;
+ size_t count = 0;
if (rangeLimit > 0) {
DictionaryConstIterator middle = _lowerDictItr;
- for (int n(0); (n < rangeLimit) && (middle != _upperDictItr); ++middle) {
- n += _postingList.frozenSize(middle.getData().load_acquire());
+ for (; (n < rangeLimit) && (count < max_posting_lists_to_count) && (middle != _upperDictItr); ++middle, count++) {
+ n += _posting_store.frozenSize(middle.getData().load_acquire());
+ }
+ if (middle == _upperDictItr) {
+ // All there is
+ } else if (n >= rangeLimit) {
+ _upperDictItr = middle;
+ } else {
+ size_t offset = ((rangeLimit - n) * count)/n;
+ middle += offset;
+ if (middle.valid() && ((_upperDictItr - middle) > 0)) {
+ _upperDictItr = middle;
+ }
}
- _upperDictItr = middle;
- _uniqueValues = _upperDictItr - _lowerDictItr;
} else if ((rangeLimit < 0) && (_lowerDictItr != _upperDictItr)) {
rangeLimit = -rangeLimit;
DictionaryConstIterator middle = _upperDictItr;
- for (int n(0); (n < rangeLimit) && (middle != _lowerDictItr); ) {
+ for (; (n < rangeLimit) && (count < max_posting_lists_to_count) && (middle != _lowerDictItr); count++) {
--middle;
- n += _postingList.frozenSize(middle.getData().load_acquire());
+ n += _posting_store.frozenSize(middle.getData().load_acquire());
+ }
+ if (middle == _lowerDictItr) {
+ // All there is
+ } else if (n >= rangeLimit) {
+ _lowerDictItr = middle;
+ } else {
+ size_t offset = ((rangeLimit - n) * count)/n;
+ middle -= offset;
+ if (middle.valid() && ((middle - _lowerDictItr) > 0)) {
+ _lowerDictItr = middle;
+ }
}
- _lowerDictItr = middle;
- _uniqueValues = _upperDictItr - _lowerDictItr;
}
+ _uniqueValues = std::abs(_upperDictItr - _lowerDictItr);
}
template <typename DataT>
PostingListFoldedSearchContextT<DataT>::
PostingListFoldedSearchContextT(const IEnumStoreDictionary& dictionary, uint32_t docIdLimit, uint64_t numValues,
- bool hasWeight, const PostingList &postingList,
+ bool hasWeight, const PostingStore& posting_store,
bool useBitVector, const ISearchContext &searchContext)
- : Parent(dictionary, docIdLimit, numValues, hasWeight, postingList, useBitVector, searchContext),
+ : Parent(dictionary, docIdLimit, numValues, hasWeight, posting_store, useBitVector, searchContext),
_resume_scan_itr(),
_posting_indexes()
{
@@ -290,7 +310,7 @@ PostingListFoldedSearchContextT<DataT>::calc_estimated_hits_in_range() const
if (use_dictionary_entry(it)) {
auto pidx = it.getData().load_acquire();
if (pidx.valid()) {
- sum += _postingList.frozenSize(pidx);
+ sum += _posting_store.frozenSize(pidx);
if (!overflow) {
if (_posting_indexes.size() < MAX_POSTING_INDEXES_SIZE) {
_posting_indexes.emplace_back(pidx);
@@ -312,9 +332,9 @@ void
PostingListFoldedSearchContextT<DataT>::fill_array_or_bitvector_helper(EntryRef pidx)
{
if constexpr (fill_array) {
- _merger.addToArray(PostingListTraverser<PostingList>(_postingList, pidx));
+ _merger.addToArray(PostingListTraverser<PostingStore>(_posting_store, pidx));
} else {
- _merger.addToBitVector(PostingListTraverser<PostingList>(_postingList, pidx));
+ _merger.addToBitVector(PostingListTraverser<PostingStore>(_posting_store, pidx));
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglisttraits.h b/searchlib/src/vespa/searchlib/attribute/postinglisttraits.h
index 928ecc7aaf1..9a1432a6d77 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglisttraits.h
+++ b/searchlib/src/vespa/searchlib/attribute/postinglisttraits.h
@@ -19,7 +19,7 @@ public:
using AggrCalcType = vespalib::btree::NoAggrCalc;
using const_iterator = vespalib::btree::BTreeConstIterator<uint32_t, vespalib::btree::BTreeNoLeafData, AggregatedType, std::less<uint32_t>, BTreeTraits >;
using PostingStoreBase = vespalib::btree::BTreeStore<uint32_t, vespalib::btree::BTreeNoLeafData, AggregatedType, std::less<uint32_t>, BTreeTraits, AggrCalcType> ;
- using PostingList = PostingStore<vespalib::btree::BTreeNoLeafData>;
+ using PostingStoreType = PostingStore<vespalib::btree::BTreeNoLeafData>;
using Posting = PostingStoreBase::KeyDataType;
};
@@ -33,7 +33,7 @@ public:
using AggrCalcType = vespalib::btree::MinMaxAggrCalc;
using const_iterator = vespalib::btree::BTreeConstIterator<uint32_t, int32_t, AggregatedType, std::less<uint32_t>, BTreeTraits >;
using PostingStoreBase = vespalib::btree::BTreeStore<uint32_t, int32_t, AggregatedType, std::less<uint32_t>, BTreeTraits, AggrCalcType>;
- using PostingList = PostingStore<int32_t>;
+ using PostingStoreType = PostingStore<int32_t>;
using Posting = PostingStoreBase::KeyDataType;
};
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h
index 8a187014add..fd055206a86 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.h
@@ -49,7 +49,7 @@ private:
using ValueModifier = typename B::BaseClass::ValueModifier;
using generation_t = typename SingleValueNumericEnumAttribute<B>::generation_t;
- using PostingParent::_postingList;
+ using PostingParent::_posting_store;
using PostingParent::clearAllPostings;
using PostingParent::handle_load_posting_lists;
using PostingParent::handle_load_posting_lists_and_update_enum_store;
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
index 1aee447760d..6e9c6a73337 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
@@ -37,7 +37,7 @@ void
SingleValueNumericPostingAttribute<B>::mergeMemoryStats(vespalib::MemoryUsage & total)
{
auto& compaction_strategy = this->getConfig().getCompactionStrategy();
- total.merge(this->_postingList.update_stat(compaction_strategy));
+ total.merge(this->_posting_store.update_stat(compaction_strategy));
}
template <typename B>
@@ -124,16 +124,16 @@ void
SingleValueNumericPostingAttribute<B>::reclaim_memory(generation_t oldest_used_gen)
{
SingleValueNumericEnumAttribute<B>::reclaim_memory(oldest_used_gen);
- _postingList.reclaim_memory(oldest_used_gen);
+ _posting_store.reclaim_memory(oldest_used_gen);
}
template <typename B>
void
SingleValueNumericPostingAttribute<B>::before_inc_generation(generation_t current_gen)
{
- _postingList.freeze();
+ _posting_store.freeze();
SingleValueNumericEnumAttribute<B>::before_inc_generation(current_gen);
- _postingList.assign_generation(current_gen);
+ _posting_store.assign_generation(current_gen);
}
template <typename B>
diff --git a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h
index 8a204a2d46b..543cfdd90ec 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.h
@@ -43,15 +43,15 @@ private:
using ValueModifier = typename SingleValueStringAttributeT<B>::ValueModifier;
using generation_t = typename SingleValueStringAttributeT<B>::generation_t;
- using PostingParent::_postingList;
+ using PostingParent::_posting_store;
using PostingParent::clearAllPostings;
using PostingParent::handle_load_posting_lists;
using PostingParent::handle_load_posting_lists_and_update_enum_store;
using PostingParent::forwardedOnAddDoc;
public:
- using PostingList = typename PostingParent::PostingList;
+ using PostingStore = typename PostingParent::PostingStore;
using Dictionary = EnumPostingTree;
- using PostingParent::getPostingList;
+ using PostingParent::get_posting_store;
private:
void freezeEnumDictionary() override;
diff --git a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
index 72eae570efc..85b0c095d76 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
@@ -43,7 +43,7 @@ void
SingleValueStringPostingAttributeT<B>::mergeMemoryStats(vespalib::MemoryUsage & total)
{
auto& compaction_strategy = this->getConfig().getCompactionStrategy();
- total.merge(this->_postingList.update_stat(compaction_strategy));
+ total.merge(this->_posting_store.update_stat(compaction_strategy));
}
template <typename B>
@@ -125,16 +125,16 @@ void
SingleValueStringPostingAttributeT<B>::reclaim_memory(generation_t oldest_used_gen)
{
SingleValueStringAttributeT<B>::reclaim_memory(oldest_used_gen);
- _postingList.reclaim_memory(oldest_used_gen);
+ _posting_store.reclaim_memory(oldest_used_gen);
}
template <typename B>
void
SingleValueStringPostingAttributeT<B>::before_inc_generation(generation_t current_gen)
{
- _postingList.freeze();
+ _posting_store.freeze();
SingleValueStringAttributeT<B>::before_inc_generation(current_gen);
- _postingList.assign_generation(current_gen);
+ _posting_store.assign_generation(current_gen);
}
template <typename B>
diff --git a/searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt b/searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt
index a4ec4666f47..baec790101d 100644
--- a/searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/query/tree/CMakeLists.txt
@@ -2,6 +2,7 @@
vespa_add_library(searchlib_query_tree OBJECT
SOURCES
const_bool_nodes.cpp
+ integer_term_vector.cpp
intermediate.cpp
intermediatenodes.cpp
location.cpp
@@ -10,6 +11,7 @@ vespa_add_library(searchlib_query_tree OBJECT
simplequery.cpp
stackdumpcreator.cpp
stackdumpquerycreator.cpp
+ string_term_vector.cpp
term.cpp
termnodes.cpp
DEPENDS
diff --git a/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.cpp b/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.cpp
new file mode 100644
index 00000000000..904f1945849
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.cpp
@@ -0,0 +1,65 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "integer_term_vector.h"
+#include <cassert>
+#include <charconv>
+
+namespace search::query {
+
+IntegerTermVector::IntegerTermVector(uint32_t sz)
+ : _terms()
+{
+ _terms.reserve(sz);
+}
+
+IntegerTermVector::~IntegerTermVector() = default;
+
+void
+IntegerTermVector::addTerm(vespalib::stringref, Weight)
+{
+ // Will/should never happen
+ assert(false);
+}
+
+void
+IntegerTermVector::addTerm(int64_t, Weight)
+{
+ // Will/should never happen
+ assert(false);
+}
+
+void
+IntegerTermVector::addTerm(int64_t term)
+{
+ _terms.emplace_back(term);
+}
+
+TermVector::StringAndWeight
+IntegerTermVector::getAsString(uint32_t index) const
+{
+ const auto & v = _terms[index];
+ auto res = std::to_chars(_scratchPad, _scratchPad + sizeof(_scratchPad) - 1, v, 10);
+ res.ptr[0] = '\0';
+ return {vespalib::stringref(_scratchPad, res.ptr - _scratchPad), Weight(1)};
+}
+
+TermVector::IntegerAndWeight
+IntegerTermVector::getAsInteger(uint32_t index) const
+{
+ return {_terms[index], Weight(1)};
+}
+
+Weight
+IntegerTermVector::getWeight(uint32_t) const
+{
+ return Weight(1);
+
+}
+
+uint32_t
+IntegerTermVector::size() const
+{
+ return _terms.size();
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.h b/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.h
new file mode 100644
index 00000000000..65742c308f0
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/query/tree/integer_term_vector.h
@@ -0,0 +1,29 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "term_vector.h"
+#include <vector>
+
+namespace search::query {
+
+/*
+ * Class for integer terms owned by a MultiTerm term node.
+ * Weights are not stored, all terms have weight 1.
+ */
+class IntegerTermVector : public TermVector {
+ std::vector<int64_t> _terms;
+ mutable char _scratchPad[24];
+public:
+ explicit IntegerTermVector(uint32_t sz);
+ ~IntegerTermVector() override;
+ void addTerm(vespalib::stringref, Weight) override;
+ void addTerm(int64_t term, Weight weight) override;
+ void addTerm(int64_t term);
+ [[nodiscard]] StringAndWeight getAsString(uint32_t index) const override;
+ [[nodiscard]] IntegerAndWeight getAsInteger(uint32_t index) const override;
+ [[nodiscard]] Weight getWeight(uint32_t index) const override;
+ [[nodiscard]] uint32_t size() const override;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/query/tree/string_term_vector.cpp b/searchlib/src/vespa/searchlib/query/tree/string_term_vector.cpp
new file mode 100644
index 00000000000..560a0088c78
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/query/tree/string_term_vector.cpp
@@ -0,0 +1,66 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "string_term_vector.h"
+#include <cassert>
+#include <charconv>
+
+namespace search::query {
+
+StringTermVector::StringTermVector(uint32_t sz)
+ : _terms()
+{
+ _terms.reserve(sz);
+}
+
+StringTermVector::~StringTermVector() = default;
+
+void
+StringTermVector::addTerm(vespalib::stringref, Weight)
+{
+ // Will/should never happen
+ assert(false);
+}
+
+void
+StringTermVector::addTerm(int64_t, Weight)
+{
+ // Will/should never happen
+ assert(false);
+}
+
+void
+StringTermVector::addTerm(vespalib::stringref term)
+{
+ _terms.emplace_back(term);
+}
+
+TermVector::StringAndWeight
+StringTermVector::getAsString(uint32_t index) const
+{
+ const auto & v = _terms[index];
+ return {v, Weight(1)};
+}
+
+
+TermVector::IntegerAndWeight
+StringTermVector::getAsInteger(uint32_t index) const
+{
+ const auto & v = _terms[index];
+ int64_t value(0);
+ std::from_chars(v.c_str(), v.c_str() + v.size(), value);
+ return {value, Weight(1)};
+}
+
+Weight
+StringTermVector::getWeight(uint32_t) const
+{
+ return Weight(1);
+}
+
+uint32_t
+StringTermVector::size() const
+{
+ return _terms.size();
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/query/tree/string_term_vector.h b/searchlib/src/vespa/searchlib/query/tree/string_term_vector.h
new file mode 100644
index 00000000000..e4202cd950a
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/query/tree/string_term_vector.h
@@ -0,0 +1,28 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "term_vector.h"
+#include <vector>
+
+namespace search::query {
+
+/*
+ * Class for string terms owned by a MultiTerm term node.
+ * Weights are not stored, all terms have weight 1.
+ */
+class StringTermVector : public TermVector {
+ std::vector<vespalib::string> _terms;
+public:
+ explicit StringTermVector(uint32_t sz);
+ ~StringTermVector() override;
+ void addTerm(vespalib::stringref term, Weight weight) override;
+ void addTerm(int64_t term, Weight weight) override;
+ void addTerm(vespalib::stringref term);
+ [[nodiscard]] StringAndWeight getAsString(uint32_t index) const override;
+ [[nodiscard]] IntegerAndWeight getAsInteger(uint32_t index) const override;
+ [[nodiscard]] Weight getWeight(uint32_t index) const override;
+ [[nodiscard]] uint32_t size() const override;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/query/tree/term_vector.h b/searchlib/src/vespa/searchlib/query/tree/term_vector.h
new file mode 100644
index 00000000000..5bb1d9fbb0b
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/query/tree/term_vector.h
@@ -0,0 +1,27 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/searchlib/query/weight.h>
+#include <vespa/vespalib/stllike/string.h>
+#include <utility>
+
+namespace search::query {
+
+/*
+ * Interface class for terms owned by a MultiTerm query node.
+ */
+class TermVector {
+public:
+ using StringAndWeight = std::pair<vespalib::stringref, Weight>;
+ using IntegerAndWeight = std::pair<int64_t, Weight>;
+ virtual ~TermVector() = default;
+ virtual void addTerm(vespalib::stringref term, Weight weight) = 0;
+ virtual void addTerm(int64_t term, Weight weight) = 0;
+ [[nodiscard]] virtual StringAndWeight getAsString(uint32_t index) const = 0;
+ [[nodiscard]] virtual IntegerAndWeight getAsInteger(uint32_t index) const = 0;
+ [[nodiscard]] virtual Weight getWeight(uint32_t index) const = 0;
+ [[nodiscard]] virtual uint32_t size() const = 0;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/query/tree/termnodes.cpp b/searchlib/src/vespa/searchlib/query/tree/termnodes.cpp
index 38b635d453a..a8fbe81a222 100644
--- a/searchlib/src/vespa/searchlib/query/tree/termnodes.cpp
+++ b/searchlib/src/vespa/searchlib/query/tree/termnodes.cpp
@@ -29,10 +29,10 @@ FuzzyTerm::~FuzzyTerm() = default;
namespace {
-class StringTermVector final : public MultiTerm::TermVector {
+class WeightedStringTermVector final : public TermVector {
public:
- explicit StringTermVector(uint32_t sz) : _terms() { _terms.reserve(sz); }
- ~StringTermVector() override;
+ explicit WeightedStringTermVector(uint32_t sz) : _terms() { _terms.reserve(sz); }
+ ~WeightedStringTermVector() override;
void addTerm(stringref term, Weight weight) override {
_terms.emplace_back(term, weight);
}
@@ -59,9 +59,9 @@ private:
std::vector<std::pair<vespalib::string, Weight>> _terms;
};
-class IntegerTermVector final : public MultiTerm::TermVector {
+class WeightedIntegerTermVector final : public TermVector {
public:
- explicit IntegerTermVector(uint32_t sz) : _terms() { _terms.reserve(sz); }
+ explicit WeightedIntegerTermVector(uint32_t sz) : _terms() { _terms.reserve(sz); }
void addTerm(stringref, Weight) override {
// Will/should never happen
assert(false);
@@ -87,7 +87,7 @@ private:
mutable char _scratchPad[24];
};
-StringTermVector::~StringTermVector() = default;
+WeightedStringTermVector::~WeightedStringTermVector() = default;
}
@@ -99,10 +99,10 @@ MultiTerm::MultiTerm(uint32_t num_terms)
MultiTerm::~MultiTerm() = default;
-std::unique_ptr<MultiTerm::TermVector>
+std::unique_ptr<TermVector>
MultiTerm::downgrade() {
// Downgrade all number to string. This should really not happen
- auto new_terms = std::make_unique<StringTermVector>(_num_terms);
+ auto new_terms = std::make_unique<WeightedStringTermVector>(_num_terms);
for (uint32_t i(0), m(_terms->size()); i < m; i++) {
auto v = _terms->getAsString(i);
new_terms->addTerm(v.first, v.second);
@@ -113,7 +113,7 @@ MultiTerm::downgrade() {
void
MultiTerm::addTerm(vespalib::stringref term, Weight weight) {
if ( ! _terms) {
- _terms = std::make_unique<StringTermVector>(_num_terms);
+ _terms = std::make_unique<WeightedStringTermVector>(_num_terms);
_type = Type::STRING;
}
if (_type == Type::INTEGER) {
@@ -126,7 +126,7 @@ MultiTerm::addTerm(vespalib::stringref term, Weight weight) {
void
MultiTerm::addTerm(int64_t term, Weight weight) {
if ( ! _terms) {
- _terms = std::make_unique<IntegerTermVector>(_num_terms);
+ _terms = std::make_unique<WeightedIntegerTermVector>(_num_terms);
_type = Type::INTEGER;
}
_terms->addTerm(term, weight);
diff --git a/searchlib/src/vespa/searchlib/query/tree/termnodes.h b/searchlib/src/vespa/searchlib/query/tree/termnodes.h
index 2da184e8c0a..5db11bf6ba9 100644
--- a/searchlib/src/vespa/searchlib/query/tree/termnodes.h
+++ b/searchlib/src/vespa/searchlib/query/tree/termnodes.h
@@ -7,6 +7,7 @@
#include "querynodemixin.h"
#include "range.h"
#include "term.h"
+#include "term_vector.h"
#include "const_bool_nodes.h"
namespace search::query {
@@ -177,19 +178,8 @@ public:
class MultiTerm : public Node {
public:
enum class Type {STRING, INTEGER, UNKNOWN};
- using StringAndWeight = std::pair<vespalib::stringref, Weight>;
- using IntegerAndWeight = std::pair<int64_t, Weight>;
- struct TermVector {
- using StringAndWeight = MultiTerm::StringAndWeight;
- using IntegerAndWeight = MultiTerm::IntegerAndWeight;
- virtual ~TermVector() = default;
- virtual void addTerm(vespalib::stringref term, Weight weight) = 0;
- virtual void addTerm(int64_t term, Weight weight) = 0;
- virtual StringAndWeight getAsString(uint32_t index) const = 0;
- virtual IntegerAndWeight getAsInteger(uint32_t index) const = 0;
- virtual Weight getWeight(uint32_t index) const = 0;
- virtual uint32_t size() const = 0;
- };
+ using StringAndWeight = TermVector::StringAndWeight;
+ using IntegerAndWeight = TermVector::IntegerAndWeight;
~MultiTerm() override;
void addTerm(vespalib::stringref term, Weight weight);
void addTerm(int64_t term, Weight weight);
diff --git a/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp
index 5eaa2dc40ab..30aee5e0e83 100644
--- a/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp
@@ -644,21 +644,23 @@ IntermediateBlueprint::freeze()
namespace {
bool
-areAnyParentsEquiv(const Blueprint * node)
-{
+areAnyParentsEquiv(const Blueprint * node) {
return (node != nullptr) && (node->isEquiv() || areAnyParentsEquiv(node->getParent()));
}
bool
-canBlueprintSkipUnpack(const Blueprint & bp, const fef::MatchData & md)
-{
+emptyUnpackInfo(const IntermediateBlueprint * intermediate, const fef::MatchData & md) {
+ return intermediate != nullptr && intermediate->calculateUnpackInfo(md).empty();
+}
+
+bool
+canBlueprintSkipUnpack(const Blueprint & bp, const fef::MatchData & md) {
if (bp.always_needs_unpack()) {
return false;
}
- return (bp.isWhiteList() ||
- (bp.getState().numFields() != 0) ||
- (bp.isIntermediate() &&
- static_cast<const IntermediateBlueprint &>(bp).calculateUnpackInfo(md).empty()));
+ return bp.isWhiteList() ||
+ (bp.getState().numFields() != 0) ||
+ emptyUnpackInfo(bp.asIntermediate(), md);
}
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/blueprint.h b/searchlib/src/vespa/searchlib/queryeval/blueprint.h
index cd0e8f2af40..81d225661d0 100644
--- a/searchlib/src/vespa/searchlib/queryeval/blueprint.h
+++ b/searchlib/src/vespa/searchlib/queryeval/blueprint.h
@@ -27,6 +27,11 @@ class SearchIterator;
class ExecuteInfo;
class MatchingElementsSearch;
class LeafBlueprint;
+class IntermediateBlueprint;
+class SourceBlenderBlueprint;
+class AndBlueprint;
+class AndNotBlueprint;
+class OrBlueprint;
/**
* A Blueprint is an intermediate representation of a search. More
@@ -251,16 +256,19 @@ public:
vespalib::slime::Cursor & asSlime(const vespalib::slime::Inserter & cursor) const;
virtual vespalib::string getClassName() const;
virtual void visitMembers(vespalib::ObjectVisitor &visitor) const;
- virtual bool isEquiv() const { return false; }
- virtual bool isWhiteList() const { return false; }
- virtual bool isIntermediate() const { return false; }
- virtual LeafBlueprint * asLeaf() noexcept { return nullptr; }
- virtual bool isAnd() const { return false; }
- virtual bool isAndNot() const { return false; }
- virtual bool isOr() const { return false; }
- virtual bool isSourceBlender() const { return false; }
- virtual bool isRank() const { return false; }
- virtual const attribute::ISearchContext *get_attribute_search_context() const { return nullptr; }
+ virtual bool isEquiv() const noexcept { return false; }
+ virtual bool isWhiteList() const noexcept { return false; }
+ virtual IntermediateBlueprint * asIntermediate() noexcept { return nullptr; }
+ const IntermediateBlueprint * asIntermediate() const noexcept { return const_cast<Blueprint *>(this)->asIntermediate(); }
+ virtual const LeafBlueprint * asLeaf() const noexcept { return nullptr; }
+ virtual AndBlueprint * asAnd() noexcept { return nullptr; }
+ bool isAnd() const noexcept { return const_cast<Blueprint *>(this)->asAnd() != nullptr; }
+ virtual AndNotBlueprint * asAndNot() noexcept { return nullptr; }
+ bool isAndNot() const noexcept { return const_cast<Blueprint *>(this)->asAndNot() != nullptr; }
+ virtual OrBlueprint * asOr() noexcept { return nullptr; }
+ virtual SourceBlenderBlueprint * asSourceBlender() noexcept { return nullptr; }
+ virtual bool isRank() const noexcept { return false; }
+ virtual const attribute::ISearchContext *get_attribute_search_context() const noexcept { return nullptr; }
// For document summaries with matched-elements-only set.
virtual std::unique_ptr<MatchingElementsSearch> create_matching_elements_search(const MatchingElementsFields &fields) const;
@@ -354,7 +362,7 @@ public:
void freeze() final;
UnpackInfo calculateUnpackInfo(const fef::MatchData & md) const;
- bool isIntermediate() const override { return true; }
+ IntermediateBlueprint * asIntermediate() noexcept final { return this; }
};
@@ -400,7 +408,7 @@ public:
void fetchPostings(const ExecuteInfo &execInfo) override;
void freeze() final;
SearchIteratorUP createSearch(fef::MatchData &md, bool strict) const override;
- LeafBlueprint * asLeaf() noexcept final { return this; }
+ const LeafBlueprint * asLeaf() const noexcept final { return this; }
virtual bool getRange(vespalib::string & from, vespalib::string & to) const;
virtual SearchIteratorUP createLeafSearch(const fef::TermFieldMatchDataArray &tfmda, bool strict) const = 0;
diff --git a/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h
index bc4c68a9e24..df1ea13105a 100644
--- a/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h
+++ b/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h
@@ -27,7 +27,7 @@ public:
void visitMembers(vespalib::ObjectVisitor &visitor) const override;
void fetchPostings(const ExecuteInfo &execInfo) override;
- bool isEquiv() const override { return true; }
+ bool isEquiv() const noexcept final { return true; }
};
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp
index c0439df1c1b..b315965b5f4 100644
--- a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.cpp
@@ -34,13 +34,13 @@ size_t lookup_create_source(std::vector<std::unique_ptr<CombineType> > &sources,
template <typename CombineType>
void optimize_source_blenders(IntermediateBlueprint &self, size_t begin_idx) {
std::vector<size_t> source_blenders;
- SourceBlenderBlueprint *reference = nullptr;
+ const SourceBlenderBlueprint * reference = nullptr;
for (size_t i = begin_idx; i < self.childCnt(); ++i) {
- if (self.getChild(i).isSourceBlender()) {
- auto *child = static_cast<SourceBlenderBlueprint *>(&self.getChild(i));
- if (reference == nullptr || reference->isCompatibleWith(*child)) {
+ const SourceBlenderBlueprint * sbChild = self.getChild(i).asSourceBlender();
+ if (sbChild) {
+ if (reference == nullptr || reference->isCompatibleWith(*sbChild)) {
source_blenders.push_back(i);
- reference = child;
+ reference = sbChild;
}
}
}
@@ -50,16 +50,14 @@ void optimize_source_blenders(IntermediateBlueprint &self, size_t begin_idx) {
while (!source_blenders.empty()) {
blender_up = self.removeChild(source_blenders.back());
source_blenders.pop_back();
- assert(blender_up->isSourceBlender());
- auto *blender = static_cast<SourceBlenderBlueprint *>(blender_up.get());
+ SourceBlenderBlueprint * blender = blender_up->asSourceBlender();
while (blender->childCnt() > 0) {
Blueprint::UP child_up = blender->removeChild(blender->childCnt() - 1);
size_t source_idx = lookup_create_source(sources, child_up->getSourceId(), self.get_docid_limit());
sources[source_idx]->addChild(std::move(child_up));
}
}
- assert(blender_up->isSourceBlender());
- auto *top = static_cast<SourceBlenderBlueprint *>(blender_up.get());
+ SourceBlenderBlueprint * top = blender_up->asSourceBlender();
while (!sources.empty()) {
top->addChild(std::move(sources.back()));
sources.pop_back();
@@ -109,8 +107,8 @@ AndNotBlueprint::optimize_self(OptimizePass pass)
return;
}
if (pass == OptimizePass::FIRST) {
- if (getChild(0).isAndNot()) {
- auto *child = static_cast<AndNotBlueprint *>(&getChild(0));
+ AndNotBlueprint * child = getChild(0).asAndNot();
+ if (child != nullptr) {
while (child->childCnt() > 1) {
addChild(child->removeChild(1));
}
@@ -197,8 +195,8 @@ AndBlueprint::optimize_self(OptimizePass pass)
{
if (pass == OptimizePass::FIRST) {
for (size_t i = 0; i < childCnt(); ++i) {
- if (getChild(i).isAnd()) {
- auto *child = static_cast<AndBlueprint *>(&getChild(i));
+ AndBlueprint * child = getChild(i).asAnd();
+ if (child != nullptr) {
while (child->childCnt() > 0) {
addChild(child->removeChild(0));
}
@@ -299,8 +297,8 @@ OrBlueprint::optimize_self(OptimizePass pass)
{
if (pass == OptimizePass::FIRST) {
for (size_t i = 0; (childCnt() > 1) && (i < childCnt()); ++i) {
- if (getChild(i).isOr()) {
- auto *child = static_cast<OrBlueprint *>(&getChild(i));
+ OrBlueprint * child = getChild(i).asOr();
+ if (child != nullptr) {
while (child->childCnt() > 0) {
addChild(child->removeChild(0));
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h
index 75dc47272af..6d8082b60f6 100644
--- a/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h
+++ b/searchlib/src/vespa/searchlib/queryeval/intermediate_blueprints.h
@@ -18,7 +18,7 @@ public:
HitEstimate combine(const std::vector<HitEstimate> &data) const override;
FieldSpecBaseList exposeFields() const override;
void optimize_self(OptimizePass pass) override;
- bool isAndNot() const override { return true; }
+ AndNotBlueprint * asAndNot() noexcept final { return this; }
Blueprint::UP get_replacement() override;
void sort(Children &children) const override;
bool inheritStrict(size_t i) const override;
@@ -44,7 +44,7 @@ public:
HitEstimate combine(const std::vector<HitEstimate> &data) const override;
FieldSpecBaseList exposeFields() const override;
void optimize_self(OptimizePass pass) override;
- bool isAnd() const override { return true; }
+ AndBlueprint * asAnd() noexcept final { return this; }
Blueprint::UP get_replacement() override;
void sort(Children &children) const override;
bool inheritStrict(size_t i) const override;
@@ -68,7 +68,7 @@ public:
HitEstimate combine(const std::vector<HitEstimate> &data) const override;
FieldSpecBaseList exposeFields() const override;
void optimize_self(OptimizePass pass) override;
- bool isOr() const override { return true; }
+ OrBlueprint * asOr() noexcept final { return this; }
Blueprint::UP get_replacement() override;
void sort(Children &children) const override;
bool inheritStrict(size_t i) const override;
@@ -166,7 +166,7 @@ public:
Blueprint::UP get_replacement() override;
void sort(Children &children) const override;
bool inheritStrict(size_t i) const override;
- bool isRank() const override { return true; }
+ bool isRank() const noexcept final { return true; }
SearchIterator::UP
createIntermediateSearch(MultiSearch::Children subSearches,
bool strict, fef::MatchData &md) const override;
@@ -199,7 +199,7 @@ public:
/** check if this blueprint has the same source selector as the other */
bool isCompatibleWith(const SourceBlenderBlueprint &other) const;
- bool isSourceBlender() const override { return true; }
+ SourceBlenderBlueprint * asSourceBlender() noexcept final { return this; }
uint8_t calculate_cost_tier() const override;
};
diff --git a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp
index 8c56a6694dc..fdea54424de 100644
--- a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.cpp
@@ -4,6 +4,7 @@
#include "termwise_helper.h"
#include <vespa/searchlib/fef/matchdata.h>
#include <cassert>
+#include <limits>
namespace search::queryeval {
@@ -28,7 +29,7 @@ SearchIteratorPack::SearchIteratorPack(const std::vector<SearchIterator*> &child
_children.emplace_back(child);
}
assert((_children.size() == _childMatch.size()) || _childMatch.empty());
- assert(_children.size() < 0x10000);
+ assert(_children.size() <= std::numeric_limits<ref_t>::max());
}
SearchIteratorPack::SearchIteratorPack(const std::vector<SearchIterator*> &children, MatchDataUP md)
diff --git a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h
index ce0c47f0882..0a1b140f28a 100644
--- a/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h
+++ b/searchlib/src/vespa/searchlib/queryeval/iterator_pack.h
@@ -18,6 +18,7 @@ private:
MatchDataUP _md;
public:
+ using ref_t = uint16_t;
SearchIteratorPack();
~SearchIteratorPack();
SearchIteratorPack(SearchIteratorPack &&rhs) noexcept;
@@ -31,25 +32,25 @@ public:
// TODO: use MultiSearch::Children to pass ownership
SearchIteratorPack(const std::vector<SearchIterator*> &children, MatchDataUP md);
- uint32_t get_docid(uint16_t ref) const {
+ uint32_t get_docid(ref_t ref) const {
return _children[ref]->getDocId();
}
- uint32_t seek(uint16_t ref, uint32_t docid) {
+ uint32_t seek(ref_t ref, uint32_t docid) {
_children[ref]->seek(docid);
return _children[ref]->getDocId();
}
- int32_t get_weight(uint16_t ref, uint32_t docid) {
+ int32_t get_weight(ref_t ref, uint32_t docid) {
_children[ref]->doUnpack(docid);
return _childMatch[ref]->getWeight();
}
- void unpack(uint16_t ref, uint32_t docid) {
+ void unpack(ref_t ref, uint32_t docid) {
_children[ref]->doUnpack(docid);
}
- uint16_t size() const { return _children.size(); }
+ ref_t size() const { return _children.size(); }
void initRange(uint32_t begin, uint32_t end) {
for (auto & child: _children) {
child->initRange(begin, end);
diff --git a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h
index 5710d2c2106..a95ca0efc72 100644
--- a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h
+++ b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.h
@@ -83,7 +83,7 @@ public:
return *this;
}
- const attribute::ISearchContext *get_attribute_search_context() const override {
+ const attribute::ISearchContext *get_attribute_search_context() const noexcept final {
return _ctx.get();
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h
index cc6331375cf..6a988e67149 100644
--- a/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h
+++ b/searchlib/src/vespa/searchlib/queryeval/same_element_blueprint.h
@@ -26,7 +26,7 @@ public:
~SameElementBlueprint() override;
// no match data
- bool isWhiteList() const override { return true; }
+ bool isWhiteList() const noexcept final { return true; }
// used by create visitor
FieldSpec getNextChildField(const vespalib::string &field_name, uint32_t field_id);
diff --git a/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp b/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp
index 431f386fa86..0bbdf89bab7 100644
--- a/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.cpp
@@ -138,8 +138,8 @@ public:
bool
allTermsHaveMatch(const SimplePhraseSearch::Children &terms, const vector<uint32_t> &eval_order, uint32_t doc_id) {
- for (uint32_t i = 0; i < terms.size(); ++i) {
- if (!terms[eval_order[i]]->seek(doc_id)) {
+ for (unsigned int order : eval_order) {
+ if (!terms[order]->seek(doc_id)) {
return false;
}
}
@@ -147,13 +147,18 @@ allTermsHaveMatch(const SimplePhraseSearch::Children &terms, const vector<uint32
}
} // namespace
-void
+inline void
SimplePhraseSearch::phraseSeek(uint32_t doc_id) {
if (allTermsHaveMatch(getChildren(), _eval_order, doc_id)) {
- AndSearch::doUnpack(doc_id);
- if (PhraseMatcher(_childMatch, _eval_order, _iterators).hasMatch()) {
- setDocId(doc_id);
- }
+ matchPhrase(doc_id);
+ }
+}
+
+void
+SimplePhraseSearch::matchPhrase(uint32_t doc_id) {
+ AndSearch::doUnpack(doc_id);
+ if (PhraseMatcher(_childMatch, _eval_order, _iterators).hasMatch()) {
+ setDocId(doc_id);
}
}
@@ -180,25 +185,30 @@ void
SimplePhraseSearch::doSeek(uint32_t doc_id) {
phraseSeek(doc_id);
if (_strict) {
- uint32_t next_candidate = doc_id;
- while (getDocId() < doc_id || getDocId() == beginId()) {
- getChildren()[0]->seek(next_candidate + 1);
- next_candidate = getChildren()[0]->getDocId();
- if (isAtEnd(next_candidate)) {
- setAtEnd();
- return;
- }
- // child must behave as strict.
- assert(next_candidate > doc_id && next_candidate != beginId());
+ doStrictSeek(doc_id);
+ }
+}
- phraseSeek(next_candidate);
+void
+SimplePhraseSearch::doStrictSeek(uint32_t doc_id) {
+ uint32_t next_candidate = doc_id;
+ while (getDocId() < doc_id || getDocId() == beginId()) {
+ getChildren()[0]->seek(next_candidate + 1);
+ next_candidate = getChildren()[0]->getDocId();
+ if (isAtEnd(next_candidate)) {
+ setAtEnd();
+ return;
}
+ // child must behave as strict.
+ assert(next_candidate > doc_id && next_candidate != beginId());
+
+ phraseSeek(next_candidate);
}
}
void
SimplePhraseSearch::doUnpack(uint32_t doc_id) {
- // All children has already been unpacked before this call is made.
+ // All children have already been unpacked before this call is made.
_tmd.reset(doc_id);
PhraseMatcher(_childMatch, _eval_order, _iterators).fillPositions(_tmd);
diff --git a/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h b/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h
index 00e75f44844..8ee49183f51 100644
--- a/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h
+++ b/searchlib/src/vespa/searchlib/queryeval/simple_phrase_search.h
@@ -27,7 +27,9 @@ class SimplePhraseSearch : public AndSearch
// Reuse this vector instead of allocating a new one when needed.
std::vector<It> _iterators;
- void phraseSeek(uint32_t doc_id);
+ VESPA_DLL_LOCAL void phraseSeek(uint32_t doc_id);
+ VESPA_DLL_LOCAL void matchPhrase(uint32_t doc_id) __attribute__((noinline));
+ VESPA_DLL_LOCAL void doStrictSeek(uint32_t doc_id) __attribute__((noinline));
public:
/**
* Takes ownership of the contents of children.
diff --git a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp
index 2b1ae90e452..e2e9516badf 100644
--- a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_blueprint.cpp
@@ -97,7 +97,7 @@ WeightedSetTermBlueprint::createLeafSearch(const fef::TermFieldMatchDataArray &t
{
assert(tfmda.size() == 1);
if ((_terms.size() == 1) && tfmda[0]->isNotNeeded()) {
- if (LeafBlueprint * leaf = _terms[0]->asLeaf(); leaf != nullptr) {
+ if (const LeafBlueprint * leaf = _terms[0]->asLeaf(); leaf != nullptr) {
// Always returnin a strict iterator independently of what was required,
// as that is what we do with all the children when there are more.
return leaf->createLeafSearch(tfmda, true);
diff --git a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp
index cc37433a696..32ae321e031 100644
--- a/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/weighted_set_term_search.cpp
@@ -18,7 +18,7 @@ template <typename HEAP, typename IteratorPack>
class WeightedSetTermSearchImpl : public WeightedSetTermSearch
{
private:
- using ref_t = uint32_t;
+ using ref_t = IteratorPack::ref_t;
struct CmpDocId {
const uint32_t *termPos;
diff --git a/searchlib/src/vespa/searchlib/test/attribute_builder.cpp b/searchlib/src/vespa/searchlib/test/attribute_builder.cpp
index 9a88d44c72e..d6a8b0f4fdf 100644
--- a/searchlib/src/vespa/searchlib/test/attribute_builder.cpp
+++ b/searchlib/src/vespa/searchlib/test/attribute_builder.cpp
@@ -32,14 +32,27 @@ add_docs(AttributeVector& attr, size_t num_docs)
attr.addDocs(num_docs);
}
+
template <typename AttrType, typename ValueType>
void
-fill_helper(AttributeVector& attr, std::initializer_list<ValueType> values)
+fill_helper(AttributeVector& attr, std::span<ValueType> values)
{
add_docs(attr, values.size());
auto& real = dynamic_cast<AttrType&>(attr);
uint32_t docid = 1;
- for (auto value : values) {
+ for (const auto& value : values) {
+ real.update(docid++, value);
+ }
+ attr.commit(true);
+}
+
+template <typename AttrType, typename ValueType>
+void
+fill_helper(AttributeVector& attr, std::initializer_list<ValueType> values) {
+ add_docs(attr, values.size());
+ auto& real = dynamic_cast<AttrType&>(attr);
+ uint32_t docid = 1;
+ for (const auto& value : values) {
real.update(docid++, value);
}
attr.commit(true);
@@ -54,7 +67,7 @@ fill_array_helper(AttributeVector& attr, std::initializer_list<std::initializer_
auto& real = dynamic_cast<AttrType&>(attr);
uint32_t docid = 1;
for (auto value : values) {
- for (auto elem : value) {
+ for (const auto& elem : value) {
real.append(docid, elem, 1);
}
++docid;
@@ -71,7 +84,7 @@ fill_wset_helper(AttributeVector& attr, std::initializer_list<std::initializer_l
auto& real = dynamic_cast<AttrType&>(attr);
uint32_t docid = 1;
for (auto value : values) {
- for (auto elem : value) {
+ for (const auto& elem : value) {
real.append(docid, elem.first, elem.second);
}
++docid;
@@ -89,6 +102,13 @@ AttributeBuilder::docs(size_t num_docs)
}
AttributeBuilder&
+AttributeBuilder::fill(std::span<int32_t> values)
+{
+ fill_helper<IntegerAttribute, int32_t>(_attr, values);
+ return *this;
+}
+
+AttributeBuilder&
AttributeBuilder::fill(std::initializer_list<int32_t> values)
{
fill_helper<IntegerAttribute, int32_t>(_attr, values);
diff --git a/searchlib/src/vespa/searchlib/test/attribute_builder.h b/searchlib/src/vespa/searchlib/test/attribute_builder.h
index 473003bafc9..cdbed838327 100644
--- a/searchlib/src/vespa/searchlib/test/attribute_builder.h
+++ b/searchlib/src/vespa/searchlib/test/attribute_builder.h
@@ -8,6 +8,7 @@
#include <memory>
#include <utility>
#include <vector>
+#include <span>
namespace search { class AttributeVector; }
namespace search::attribute { class Config; }
@@ -38,6 +39,7 @@ public:
AttributeBuilder& docs(size_t num_docs);
// Fill functions for integer attributes
+ AttributeBuilder& fill(std::span<int32_t> values);
AttributeBuilder& fill(std::initializer_list<int32_t> values);
AttributeBuilder& fill(std::initializer_list<int64_t> values);
AttributeBuilder& fill_array(std::initializer_list<IntList> values);
diff --git a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
index 1ca890eb2a8..525c77f7df6 100644
--- a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
+++ b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
@@ -58,10 +58,17 @@ io.netty:netty-codec:${netty.vespa.version}
io.netty:netty-common:${netty.vespa.version}
io.netty:netty-handler:${netty.vespa.version}
io.netty:netty-resolver:${netty.vespa.version}
+io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}
+io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:linux-aarch_64
+io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:linux-x86_64
+io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:osx-aarch_64
+io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:osx-x86_64
+io.netty:netty-tcnative-boringssl-static:${netty-tcnative.vespa.version}:windows-x86_64
io.netty:netty-tcnative-classes:${netty-tcnative.vespa.version}
io.netty:netty-tcnative:${netty-tcnative.vespa.version}
io.netty:netty-transport-classes-epoll:${netty.vespa.version}
io.netty:netty-transport-native-epoll:${netty.vespa.version}
+io.netty:netty-transport-native-epoll:${netty.vespa.version}:linux-x86_64
io.netty:netty-transport-native-unix-common:${netty.vespa.version}
io.netty:netty-transport:${netty.vespa.version}
io.prometheus:simpleclient:${prometheus.client.vespa.version}
@@ -122,8 +129,10 @@ org.apache.velocity:velocity-engine-core:${velocity.vespa.version}
org.apache.yetus:audience-annotations:0.12.0
org.apache.zookeeper:zookeeper-jute:${zookeeper.client.vespa.version}
org.apache.zookeeper:zookeeper-jute:3.8.1
+org.apache.zookeeper:zookeeper-jute:3.9.1
org.apache.zookeeper:zookeeper:${zookeeper.client.vespa.version}
org.apache.zookeeper:zookeeper:3.8.1
+org.apache.zookeeper:zookeeper:3.9.1
org.apiguardian:apiguardian-api:${apiguardian.vespa.version}
org.assertj:assertj-core:${assertj.vespa.version}
org.bouncycastle:bcpkix-jdk18on:${bouncycastle.vespa.version}
diff --git a/vespajlib/src/main/java/com/yahoo/collections/Comparables.java b/vespajlib/src/main/java/com/yahoo/collections/Comparables.java
index 2e6c8fc82b3..f76509cca2c 100644
--- a/vespajlib/src/main/java/com/yahoo/collections/Comparables.java
+++ b/vespajlib/src/main/java/com/yahoo/collections/Comparables.java
@@ -18,7 +18,7 @@ public class Comparables {
}
/**
- * Returns the least element, or {@code second} if they are equal according to
+ * Returns the greatest element, or {@code second} if they are equal according to
* {@link Comparable#compareTo(Object) compareTo}.
*/
public static <T extends Comparable<? super T>> T max(T first, T second) {
diff --git a/vespalib/src/tests/btree/btree_test.cpp b/vespalib/src/tests/btree/btree_test.cpp
index 21838676906..30e3df7dd14 100644
--- a/vespalib/src/tests/btree/btree_test.cpp
+++ b/vespalib/src/tests/btree/btree_test.cpp
@@ -232,6 +232,7 @@ protected:
void requireThatUpperBoundWorksT();
void requireThatIteratorDistanceWorks(int numEntries);
void test_step_forward(int num_entries);
+ void test_step_backward(int num_entries);
};
template <typename LeafNodeType>
@@ -1476,8 +1477,12 @@ BTreeTest::requireThatIteratorDistanceWorks(int numEntries)
iitbs.binarySeek(i);
++it;
}
- iitlsp.linearSeekPast(i);
- iitbsp.binarySeekPast(i);
+ if (iitlsp.valid()) {
+ iitlsp.linearSeekPast(i);
+ }
+ if (iitbsp.valid()) {
+ iitbsp.binarySeekPast(i);
+ }
Iterator iitlsp2 = iitls;
Iterator iitbsp2 = iitbs;
Iterator iitnr = i < numEntries ? iitn : tree.begin();
@@ -1548,8 +1553,35 @@ BTreeTest::test_step_forward(int num_entries)
}
}
+void
+BTreeTest::test_step_backward(int num_entries)
+{
+ GenerationHandler g;
+ MyTree tree;
+ for (int i = 0; i < num_entries; ++i) {
+ tree.insert(i, toStr(i));
+ }
+ auto it = tree.begin();
+ for (int i = 0; i <= num_entries; ++i) {
+ auto iit = tree.lowerBound(i);
+ auto iit2 = iit;
+ iit2 -= i;
+ EXPECT_TRUE(iit2.identical(it));
+ iit2 = iit;
+ iit2 -= (1000000 + i);
+ EXPECT_TRUE(iit2.identical(it));
+ for (int j = 0; j <= i; ++j) {
+ auto jit = tree.lowerBound(j);
+ auto iit3 = iit;
+ iit3 -= (i - j);
+ EXPECT_TRUE(iit3.identical(jit));
+ }
+ }
+}
+
TEST_F(BTreeTest, require_that_iterator_distance_works)
{
+ requireThatIteratorDistanceWorks(0);
requireThatIteratorDistanceWorks(1);
requireThatIteratorDistanceWorks(3);
requireThatIteratorDistanceWorks(8);
@@ -1560,6 +1592,7 @@ TEST_F(BTreeTest, require_that_iterator_distance_works)
TEST_F(BTreeTest, require_that_step_forward_works)
{
+ test_step_forward(0);
test_step_forward(1);
test_step_forward(3);
test_step_forward(8);
@@ -1568,6 +1601,17 @@ TEST_F(BTreeTest, require_that_step_forward_works)
test_step_forward(400);
}
+TEST_F(BTreeTest, require_that_step_backward_works)
+{
+ test_step_backward(0);
+ test_step_backward(1);
+ test_step_backward(3);
+ test_step_backward(8);
+ test_step_backward(20);
+ test_step_backward(100);
+ test_step_backward(400);
+}
+
TEST_F(BTreeTest, require_that_foreach_key_works)
{
using Tree = BTree<int, int, btree::NoAggregated, MyComp, MyTraits>;
diff --git a/vespalib/src/tests/util/bfloat16/CMakeLists.txt b/vespalib/src/tests/util/bfloat16/CMakeLists.txt
index 6a6ed9b1997..72d6c8720d6 100644
--- a/vespalib/src/tests/util/bfloat16/CMakeLists.txt
+++ b/vespalib/src/tests/util/bfloat16/CMakeLists.txt
@@ -1,10 +1,11 @@
# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-if(EXISTS /opt/vespa-deps/include/onnxruntime/core/framework/endian.h)
+if(EXISTS /opt/vespa-deps/include/onnxruntime/onnxruntime_cxx_api.h)
vespa_add_executable(vespalib_bfloat16_test_app TEST
SOURCES
bfloat16_test.cpp
DEPENDS
vespalib
+ onnxruntime
GTest::GTest
)
vespa_add_test(NAME vespalib_bfloat16_test_app COMMAND vespalib_bfloat16_test_app)
diff --git a/vespalib/src/tests/util/bfloat16/bfloat16_test.cpp b/vespalib/src/tests/util/bfloat16/bfloat16_test.cpp
index b0d93dde7d3..196c5f98c9e 100644
--- a/vespalib/src/tests/util/bfloat16/bfloat16_test.cpp
+++ b/vespalib/src/tests/util/bfloat16/bfloat16_test.cpp
@@ -83,13 +83,15 @@ TEST(BFloat16Test, constants_check) {
EXPECT_EQ(try_half_epsilon.to_float(), 1.0f);
EXPECT_LT(big, std::numeric_limits<float>::max());
+ EXPECT_GT(big, 0.5 * std::numeric_limits<float>::max());
EXPECT_GT(low, std::numeric_limits<float>::lowest());
+ EXPECT_EQ(low, -big);
- printf("bfloat16 epsilon: %.10g (float has %.20g)\n", eps, std::numeric_limits<float>::epsilon());
- printf("bfloat16 norm_min: %.20g (float has %.20g)\n", n_min, std::numeric_limits<float>::min());
- printf("bfloat16 denorm_min: %.20g (float has %.20g)\n", d_min, std::numeric_limits<float>::denorm_min());
- printf("bfloat16 max: %.20g (float has %.20g)\n", big, std::numeric_limits<float>::max());
- printf("bfloat16 lowest: %.20g (float has %.20g)\n", low, std::numeric_limits<float>::lowest());
+ printf("bfloat16 epsilon: %a (float has %a)\n", eps, std::numeric_limits<float>::epsilon());
+ printf("bfloat16 norm_min: %a (float has %a)\n", n_min, std::numeric_limits<float>::min());
+ printf("bfloat16 denorm_min: %a (float has %a)\n", d_min, std::numeric_limits<float>::denorm_min());
+ printf("bfloat16 max: %a (float has %a)\n", big, std::numeric_limits<float>::max());
+ printf("bfloat16 lowest: %a (float has %a)\n", low, std::numeric_limits<float>::lowest());
}
TEST(BFloat16Test, traits_check) {
@@ -165,52 +167,19 @@ TEST(BFloat16Test, check_special_values) {
EXPECT_EQ(memcmp(&f_snan, &f_from_b_snan, sizeof(float)), 0);
}
-#include <onnxruntime/core/framework/endian.h>
-
-// extract from onnx-internal header file:
-namespace onnxruntime {
-
-//BFloat16
-struct BFloat16 {
- uint16_t val{0};
- explicit BFloat16() = default;
- explicit BFloat16(uint16_t v) : val(v) {}
- explicit BFloat16(float v) {
- if (endian::native == endian::little) {
- std::memcpy(&val, reinterpret_cast<char*>(&v) + sizeof(uint16_t), sizeof(uint16_t));
- } else {
- std::memcpy(&val, &v, sizeof(uint16_t));
- }
- }
-
- float ToFloat() const {
- float result;
- char* const first = reinterpret_cast<char*>(&result);
- char* const second = first + sizeof(uint16_t);
- if (endian::native == endian::little) {
- std::memset(first, 0, sizeof(uint16_t));
- std::memcpy(second, &val, sizeof(uint16_t));
- } else {
- std::memcpy(first, &val, sizeof(uint16_t));
- std::memset(second, 0, sizeof(uint16_t));
- }
- return result;
- }
-};
-
-} // namespace onnxruntime
+#include <onnxruntime/onnxruntime_cxx_api.h>
TEST(OnnxBFloat16Test, has_same_encoding) {
- EXPECT_EQ(sizeof(vespalib::BFloat16), sizeof(onnxruntime::BFloat16));
+ EXPECT_EQ(sizeof(vespalib::BFloat16), sizeof(Ort::BFloat16_t));
EXPECT_EQ(sizeof(vespalib::BFloat16), sizeof(uint16_t));
- EXPECT_EQ(sizeof(onnxruntime::BFloat16), sizeof(uint16_t));
+ EXPECT_EQ(sizeof(Ort::BFloat16_t), sizeof(uint16_t));
vespalib::BFloat16 our_value;
uint32_t ok_count = 0;
uint32_t nan_count = 0;
for (uint32_t i = 0; i < (1u << 16u); ++i) {
uint16_t bits = i;
our_value.assign_bits(bits);
- onnxruntime::BFloat16 their_value(bits);
+ Ort::BFloat16_t their_value = Ort::BFloat16_t::FromBits(bits);
if (our_value.get_bits() != bits) {
printf("bad bits %04x -> %04x (vespalib)\n", bits, our_value.get_bits());
printf("onnx converts -> %04x\n", their_value.val);
@@ -226,7 +195,7 @@ TEST(OnnxBFloat16Test, has_same_encoding) {
EXPECT_EQ(our_value.get_bits(), their_value.val);
if (our_value.get_bits() != their_value.val) {
printf("vespalib bits %04x != %04x onnx bits\n", our_value.get_bits(), their_value.val);
- printf("corresponds to floats %g and %g\n", our_value.to_float(), their_value.ToFloat());
+ printf("corresponds to floats %a and %a\n", our_value.to_float(), their_value.ToFloat());
continue;
}
float our_float = our_value.to_float();
@@ -237,13 +206,13 @@ TEST(OnnxBFloat16Test, has_same_encoding) {
continue;
}
if (our_float != their_float) {
- printf("bits %04x as float differs: vespalib %g != %g onnx\n", bits, our_value.to_float(), their_value.ToFloat());
+ printf("bits %04x as float differs: vespalib %a != %a onnx\n", bits, our_value.to_float(), their_value.ToFloat());
} else {
++ok_count;
}
EXPECT_EQ(our_float, their_float);
vespalib::BFloat16 our_back(our_float);
- onnxruntime::BFloat16 their_back(their_float);
+ Ort::BFloat16_t their_back(their_float);
EXPECT_EQ(our_back.get_bits(), their_back.val);
}
printf("normal floats behave equally OK in both vespalib and onnx: %d (0x%04x)\n", ok_count, ok_count);
diff --git a/vespalib/src/vespa/vespalib/btree/btreeiterator.h b/vespalib/src/vespa/vespalib/btree/btreeiterator.h
index 9519951f9e2..cd63499a5ed 100644
--- a/vespalib/src/vespa/vespalib/btree/btreeiterator.h
+++ b/vespalib/src/vespa/vespalib/btree/btreeiterator.h
@@ -232,11 +232,18 @@ protected:
return *this;
}
+ void set_subtree_position(const InternalNodeType* node, uint32_t level, uint32_t idx, size_t position);
+
/*
* Step iterator forwards the given number of steps.
*/
void step_forward(size_t steps);
+ /*
+ * Step iterator backwards the given number of steps.
+ */
+ void step_backward(size_t steps);
+
~BTreeIteratorBase();
BTreeIteratorBase(const BTreeIteratorBase &other);
BTreeIteratorBase &operator=(const BTreeIteratorBase &other);
@@ -507,6 +514,7 @@ protected:
using ParentType::_compatLeafNode;
using ParentType::clearPath;
using ParentType::setupEmpty;
+ using ParentType::step_backward;
using ParentType::step_forward;
public:
using ParentType::end;
@@ -570,6 +578,14 @@ public:
step_forward(steps);
return *this;
}
+
+ /*
+ * Step iterator backward the given number of steps.
+ */
+ BTreeConstIterator & operator-=(size_t steps) {
+ step_backward(steps);
+ return *this;
+ }
/**
* Position iterator at first position with a key that is greater
* than or equal to the key argument. The iterator must be set up
@@ -712,6 +728,7 @@ public:
using ParentType::_leafRoot;
using ParentType::_compatLeafNode;
using ParentType::end;
+ using ParentType::step_backward;
using ParentType::step_forward;
using EntryRef = datastore::EntryRef;
@@ -746,6 +763,11 @@ public:
return *this;
}
+ BTreeIterator & operator-=(size_t steps) {
+ step_backward(steps);
+ return *this;
+ }
+
NodeAllocatorType & getAllocator() const {
return const_cast<NodeAllocatorType &>(*_allocator);
}
diff --git a/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp b/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp
index 3119d05cfd9..b9afce54f6b 100644
--- a/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btreeiterator.hpp
@@ -530,6 +530,38 @@ template <typename KeyT, typename DataT, typename AggrT,
uint32_t INTERNAL_SLOTS, uint32_t LEAF_SLOTS, uint32_t PATH_SIZE>
void
BTreeIteratorBase<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS, PATH_SIZE>::
+set_subtree_position(const InternalNodeType* node, uint32_t level, uint32_t idx, size_t position)
+{
+ /*
+ * Walk down subtree adjusting iterator for new partial position.
+ */
+ _path[level].setIdx(idx);
+ size_t remaining_steps = position;
+ while (level > 0) {
+ --level;
+ node = _allocator->mapInternalRef(node->getChild(idx));
+ assert(remaining_steps < node->validLeaves());
+ idx = 0;
+ while (idx < node->validSlots()) {
+ auto valid_leaves = _allocator->validLeaves(node->getChild(idx));
+ if (remaining_steps < valid_leaves) {
+ break;
+ }
+ remaining_steps -= valid_leaves;
+ ++idx;
+ }
+ assert(idx < node->validSlots());
+ _path[level].setNodeAndIdx(node, idx);
+ }
+ auto lnode = _allocator->mapLeafRef(node->getChild(idx));
+ assert(remaining_steps < lnode->validSlots());
+ _leaf.setNodeAndIdx(lnode, remaining_steps);
+}
+
+template <typename KeyT, typename DataT, typename AggrT,
+ uint32_t INTERNAL_SLOTS, uint32_t LEAF_SLOTS, uint32_t PATH_SIZE>
+void
+BTreeIteratorBase<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS, PATH_SIZE>::
step_forward(size_t steps)
{
auto lnode = _leaf.getNode();
@@ -557,10 +589,7 @@ step_forward(size_t steps)
node = _path[level].getNode();
idx = _path[level].getIdx() + 1;
while (idx < node->validSlots()) {
- auto ref = node->getChild(idx);
- auto valid_leaves = (level != 0) ?
- _allocator->mapInternalRef(ref)->validLeaves() :
- _allocator->mapLeafRef(ref)->validLeaves();
+ auto valid_leaves = _allocator->validLeaves(node->getChild(idx));
if (remaining_steps < valid_leaves) {
break;
}
@@ -577,32 +606,61 @@ step_forward(size_t steps)
}
}
}
+ set_subtree_position(node, level, idx, remaining_steps);
+}
+
+template <typename KeyT, typename DataT, typename AggrT,
+ uint32_t INTERNAL_SLOTS, uint32_t LEAF_SLOTS, uint32_t PATH_SIZE>
+void
+BTreeIteratorBase<KeyT, DataT, AggrT, INTERNAL_SLOTS, LEAF_SLOTS, PATH_SIZE>::
+step_backward(size_t steps)
+{
+ int64_t remaining_steps = steps;
+ if (remaining_steps == 0) {
+ return;
+ }
+ if (_leaf.getNode() == nullptr) {
+ rbegin();
+ if (_leaf.getNode() == nullptr) {
+ return;
+ }
+ --remaining_steps;
+ }
+ auto idx = _leaf.getIdx();
+ if (idx >= remaining_steps) {
+ _leaf.setIdx(idx - remaining_steps);
+ return;
+ }
+ if (_pathSize == 0) {
+ _leaf.setIdx(0);
+ return;
+ }
+ remaining_steps -= idx;
+ uint32_t level = 0;
+ uint32_t levels = _pathSize;
+ const InternalNodeType* node;
/*
- * Walk down subtree adjusting iterator for new position.
+ * Find intermediate node representing subtree containing old and new
+ * position.
*/
- _path[level].setIdx(idx);
- while (level > 0) {
- --level;
- node = _allocator->mapInternalRef(node->getChild(idx));
- assert(remaining_steps < node->validLeaves());
- idx = 0;
- while (idx < node->validSlots()) {
- auto ref = node->getChild(idx);
- auto valid_leaves = (level != 0) ?
- _allocator->mapInternalRef(ref)->validLeaves() :
- _allocator->mapLeafRef(ref)->validLeaves();
- if (remaining_steps < valid_leaves) {
- break;
+ for (;;) {
+ node = _path[level].getNode();
+ idx = _path[level].getIdx();
+ while (idx > 0 && remaining_steps > 0) {
+ --idx;
+ remaining_steps -= _allocator->validLeaves(node->getChild(idx));
+ }
+ if (remaining_steps <= 0) {
+ break;
+ } else {
+ ++level;
+ if (level == levels) {
+ begin();
+ return;
}
- remaining_steps -= valid_leaves;
- ++idx;
}
- assert(idx < node->validSlots());
- _path[level].setNodeAndIdx(node, idx);
}
- lnode = _allocator->mapLeafRef(node->getChild(idx));
- assert(remaining_steps < lnode->validSlots());
- _leaf.setNodeAndIdx(lnode, remaining_steps);
+ set_subtree_position(node, level, idx, -remaining_steps);
}
template <typename KeyT, typename DataT, typename AggrT, typename CompareT,
diff --git a/zookeeper-server/CMakeLists.txt b/zookeeper-server/CMakeLists.txt
index d410469e225..d598747e75f 100644
--- a/zookeeper-server/CMakeLists.txt
+++ b/zookeeper-server/CMakeLists.txt
@@ -2,3 +2,4 @@
add_subdirectory(zookeeper-server-common)
add_subdirectory(zookeeper-server)
add_subdirectory(zookeeper-server-3.8.1)
+add_subdirectory(zookeeper-server-3.9.1)
diff --git a/zookeeper-server/pom.xml b/zookeeper-server/pom.xml
index f0ed16e67d4..f78e997f246 100644
--- a/zookeeper-server/pom.xml
+++ b/zookeeper-server/pom.xml
@@ -15,6 +15,7 @@
<module>zookeeper-server-common</module>
<module>zookeeper-server</module>
<module>zookeeper-server-3.8.1</module>
+ <module>zookeeper-server-3.9.1</module>
</modules>
<dependencies>
<dependency>
diff --git a/zookeeper-server/zookeeper-server-3.9.1/CMakeLists.txt b/zookeeper-server/zookeeper-server-3.9.1/CMakeLists.txt
new file mode 100644
index 00000000000..295693f22d7
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/CMakeLists.txt
@@ -0,0 +1,2 @@
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+install_jar(zookeeper-server-3.9.1-jar-with-dependencies.jar)
diff --git a/zookeeper-server/zookeeper-server-3.9.1/pom.xml b/zookeeper-server/zookeeper-server-3.9.1/pom.xml
new file mode 100644
index 00000000000..77aec63a781
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>zookeeper-server-parent</artifactId>
+ <version>8-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <artifactId>zookeeper-server-3.9.1</artifactId>
+ <packaging>container-plugin</packaging>
+ <version>8-SNAPSHOT</version>
+ <properties>
+ <zookeeper.version>3.9.1</zookeeper.version>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>zookeeper-server-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>zookeeper-client-common</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <!-- Don't use ZK version from zookeeper-client-common -->
+ <groupId>org.apache.zookeeper</groupId>
+ <artifactId>zookeeper</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.zookeeper</groupId>
+ <artifactId>zookeeper</artifactId>
+ <version>${zookeeper.version}</version>
+ <exclusions>
+ <!--
+ Container provides wiring for all common log libraries
+ Duplicate embedding results in various warnings being printed to stderr
+ -->
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!-- snappy-java and metrics-core are included here
+ to be able to work with ZooKeeper 3.7.0 due to
+ class loading issues -->
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <scope>compile</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.xerial.snappy</groupId>
+ <artifactId>snappy-java</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <importPackage>com.sun.management</importPackage>
+ <bundleSymbolicName>zookeeper-server</bundleSymbolicName>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ConfigServerZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ConfigServerZooKeeperServer.java
new file mode 100644
index 00000000000..d986f02d89a
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ConfigServerZooKeeperServer.java
@@ -0,0 +1,43 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.zookeeper;
+
+import com.yahoo.cloud.config.ZookeeperServerConfig;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.annotation.Inject;
+import java.nio.file.Path;
+
+/**
+ *
+ * Server used for starting config server, needed to be able to have different behavior for hosted and
+ * self-hosted Vespa (controlled by zookeeperServerConfig.dynamicReconfiguration).
+ *
+ * @author Harald Musum
+ */
+public class ConfigServerZooKeeperServer extends AbstractComponent implements VespaZooKeeperServer {
+
+ private final VespaZooKeeperServer zooKeeperServer;
+
+ @Inject
+ public ConfigServerZooKeeperServer(ZookeeperServerConfig zookeeperServerConfig) {
+ this.zooKeeperServer = zookeeperServerConfig.dynamicReconfiguration()
+ ? new ReconfigurableVespaZooKeeperServer(new Reconfigurer(new VespaZooKeeperAdminImpl()), zookeeperServerConfig)
+ : new VespaZooKeeperServerImpl(zookeeperServerConfig);
+ }
+
+ @Override
+ public void deconstruct() { zooKeeperServer.shutdown(); }
+
+ @Override
+ public void shutdown() {
+ zooKeeperServer.shutdown();
+ }
+
+ @Override
+ public void start(Path configFilePath) {
+ zooKeeperServer.start(configFilePath);
+ }
+
+ @Override
+ public boolean reconfigurable() { return zooKeeperServer.reconfigurable(); }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ReconfigurableVespaZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ReconfigurableVespaZooKeeperServer.java
new file mode 100644
index 00000000000..1b469beb1b8
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/ReconfigurableVespaZooKeeperServer.java
@@ -0,0 +1,47 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.zookeeper;
+
+import ai.vespa.validation.Validation;
+import com.yahoo.cloud.config.ZookeeperServerConfig;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.annotation.Inject;
+import java.nio.file.Path;
+import java.time.Duration;
+
+/**
+ * Starts or reconfigures zookeeper cluster.
+ * The QuorumPeer conditionally created here is owned by the Reconfigurer;
+ * when it already has a peer, that peer is used here in case start or shutdown is required.
+ * Guarantees that server is up by writing a node to ZooKeeper successfully before
+ * returning from constructor.
+ *
+ * @author hmusum
+ */
+public class ReconfigurableVespaZooKeeperServer extends AbstractComponent implements VespaZooKeeperServer {
+
+ private QuorumPeer peer;
+
+ @Inject
+ public ReconfigurableVespaZooKeeperServer(Reconfigurer reconfigurer, ZookeeperServerConfig zookeeperServerConfig) {
+ Validation.require(zookeeperServerConfig.dynamicReconfiguration(),
+ zookeeperServerConfig.dynamicReconfiguration(),
+ "dynamicReconfiguration must be true");
+ peer = reconfigurer.startOrReconfigure(zookeeperServerConfig, this, () -> peer = new VespaQuorumPeer());
+ }
+
+ @Override
+ public void shutdown() {
+ peer.shutdown(Duration.ofMinutes(1));
+ }
+
+ @Override
+ public void start(Path configFilePath) {
+ peer.start(configFilePath);
+ }
+
+ @Override
+ public boolean reconfigurable() {
+ return true;
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaMtlsAuthenticationProvider.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaMtlsAuthenticationProvider.java
new file mode 100644
index 00000000000..3c8a373f121
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaMtlsAuthenticationProvider.java
@@ -0,0 +1,41 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.zookeeper;
+
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.common.X509Exception;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.apache.zookeeper.server.auth.AuthenticationProvider;
+import org.apache.zookeeper.server.auth.X509AuthenticationProvider;
+
+import java.security.cert.X509Certificate;
+import java.util.logging.Logger;
+
+/**
+ * A {@link AuthenticationProvider} to be used in combination with Vespa mTLS
+ *
+ * @author bjorncs
+ */
+public class VespaMtlsAuthenticationProvider extends X509AuthenticationProvider {
+
+ private static final Logger log = Logger.getLogger(VespaMtlsAuthenticationProvider.class.getName());
+
+ public VespaMtlsAuthenticationProvider() throws X509Exception { super(null, null);}
+
+ @Override
+ public KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte[] authData) {
+ // Vespa's mTLS peer authorization rules are performed by the underlying trust manager implementation.
+ // The client is authorized once the SSL handshake has completed.
+ X509Certificate[] certificateChain = (X509Certificate[]) cnxn.getClientCertificateChain();
+ if (certificateChain == null || certificateChain.length == 0) {
+ log.warning("Client not authenticated - should not be possible with clientAuth=NEED");
+ return KeeperException.Code.AUTHFAILED;
+ }
+ X509Certificate certificate = certificateChain[0];
+ cnxn.addAuthInfo(new Id(getScheme(), certificate.getSubjectX500Principal().getName()));
+ return KeeperException.Code.OK;
+ }
+
+ @Override public String getScheme() { return "x509"; }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java
new file mode 100644
index 00000000000..dd5ac4e252b
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaQuorumPeer.java
@@ -0,0 +1,60 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.zookeeper;
+
+import com.yahoo.protect.Process;
+import org.apache.zookeeper.server.admin.AdminServer;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
+import org.apache.zookeeper.server.quorum.QuorumPeerMain;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Starts or stops a ZooKeeper server. Extends QuorumPeerMain to be able to call initializeAndRun() and wraps
+ * exceptions so that it can be used by code that does not depend on ZooKeeper.
+ *
+ * @author hmusum
+ */
+class VespaQuorumPeer extends QuorumPeerMain implements QuorumPeer {
+
+ private static final Logger log = java.util.logging.Logger.getLogger(VespaQuorumPeer.class.getName());
+
+ @Override
+ public void start(Path path) {
+ initializeAndRun(new String[]{ path.toFile().getAbsolutePath()});
+ }
+
+ @Override
+ public void shutdown(Duration timeout) {
+ if (quorumPeer != null) {
+ log.log(Level.FINE, "Shutting down ZooKeeper server");
+ try {
+ quorumPeer.shutdown();
+ quorumPeer.join(timeout.toMillis()); // Wait for shutdown to complete
+ if (quorumPeer.isAlive())
+ throw new IllegalStateException("Peer still alive after " + timeout);
+ } catch (RuntimeException | InterruptedException e) {
+ // If shutdown fails, we have no other option than forcing the JVM to stop and letting it be restarted.
+ //
+ // When a VespaZooKeeperServer component receives a new config, the container will try to start a new
+ // server with the new config, this will fail until the old server is deconstructed. If the old server
+ // fails to deconstruct/shutdown, the new one will never start and if that happens forcing a restart is
+ // the better option.
+ Process.logAndDie("Failed to shut down ZooKeeper server properly, forcing shutdown", e);
+ }
+ }
+ }
+
+ @Override
+ protected void initializeAndRun(String[] args) {
+ try {
+ super.initializeAndRun(args);
+ } catch (QuorumPeerConfig.ConfigException | IOException | AdminServer.AdminServerException e) {
+ throw new RuntimeException("Exception when initializing or running ZooKeeper server", e);
+ }
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java
new file mode 100644
index 00000000000..1f15c758583
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperAdminImpl.java
@@ -0,0 +1,93 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.zookeeper;
+
+import com.yahoo.cloud.config.ZookeeperServerConfig;
+import com.yahoo.net.HostName;
+import com.yahoo.vespa.zookeeper.client.ZkClientConfigBuilder;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.admin.ZooKeeperAdmin;
+import org.apache.zookeeper.data.ACL;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+
+/**
+ * @author hmusum
+ */
+@SuppressWarnings("unused") // Created by injection
+public class VespaZooKeeperAdminImpl implements VespaZooKeeperAdmin {
+
+ private static final Logger log = java.util.logging.Logger.getLogger(VespaZooKeeperAdminImpl.class.getName());
+
+ @Override
+ public void reconfigure(String connectionSpec, String servers) throws ReconfigException {
+ try (ZooKeeperAdmin zooKeeperAdmin = createAdmin(connectionSpec)) {
+ long fromConfig = -1;
+ // Using string parameters because the List variant of reconfigure fails to join empty lists (observed on 3.5.6, fixed in 3.7.0).
+ log.log(Level.INFO, "Applying ZooKeeper config: " + servers);
+ byte[] appliedConfig = zooKeeperAdmin.reconfigure(null, null, servers, fromConfig, null);
+ log.log(Level.INFO, "Applied ZooKeeper config: " + new String(appliedConfig, StandardCharsets.UTF_8));
+
+ // Verify by issuing a write operation; this is only accepted once new quorum is obtained.
+ List<ACL> acl = ZooDefs.Ids.OPEN_ACL_UNSAFE;
+ String node = zooKeeperAdmin.create("/reconfigure-dummy-node", new byte[0], acl, CreateMode.EPHEMERAL_SEQUENTIAL);
+ zooKeeperAdmin.delete(node, -1);
+
+ log.log(Level.INFO, "Verified ZooKeeper config: " + new String(appliedConfig, StandardCharsets.UTF_8));
+ }
+ catch ( KeeperException.ReconfigInProgress
+ | KeeperException.ConnectionLossException
+ | KeeperException.NewConfigNoQuorum e) {
+ throw new ReconfigException(e);
+ }
+ catch (KeeperException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private ZooKeeperAdmin createAdmin(String connectionSpec) {
+ return uncheck(() -> new ZooKeeperAdmin(connectionSpec, (int) sessionTimeout().toMillis(),
+ (event) -> log.log(Level.FINE, event.toString()), new ZkClientConfigBuilder().toConfig()));
+ }
+
+ /** Creates a node in zookeeper, with hostname as part of node name, this ensures that server is up and working before returning */
+ void createDummyNode(ZookeeperServerConfig zookeeperServerConfig) {
+ int sleepTime = 2_000;
+ try (ZooKeeperAdmin zooKeeperAdmin = createAdmin(localConnectionSpec(zookeeperServerConfig))) {
+ Instant end = Instant.now().plus(Duration.ofMinutes(5));
+ Exception exception = null;
+ do {
+ try {
+ zooKeeperAdmin.create("/dummy-node-" + HostName.getLocalhost(), new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+ return;
+ } catch (KeeperException e) {
+ if (e instanceof KeeperException.NodeExistsException) {
+ try {
+ zooKeeperAdmin.setData("/dummy-node-" + HostName.getLocalhost(), new byte[0], -1);
+ return;
+ } catch (KeeperException ex) {
+ log.log(Level.FINE, e.getMessage());
+ Thread.sleep(sleepTime);
+ continue;
+ }
+ }
+ log.log(Level.FINE, e.getMessage());
+ exception = e;
+ Thread.sleep(sleepTime);
+ }
+ } while (Instant.now().isBefore(end));
+ throw new RuntimeException("Unable to create dummy node: ", exception);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
+
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperServerImpl.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperServerImpl.java
new file mode 100644
index 00000000000..4a7f85d6985
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/com/yahoo/vespa/zookeeper/VespaZooKeeperServerImpl.java
@@ -0,0 +1,54 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.zookeeper;
+
+import ai.vespa.validation.Validation;
+import com.yahoo.cloud.config.ZookeeperServerConfig;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.annotation.Inject;
+import java.nio.file.Path;
+import java.time.Duration;
+
+/**
+ * ZooKeeper server. Guarantees that the server is up by writing a node to ZooKeeper successfully before
+ * returning from constructor.
+ *
+ * @author Ulf Lilleengen
+ * @author Harald Musum
+ */
+public class VespaZooKeeperServerImpl extends AbstractComponent implements VespaZooKeeperServer {
+
+ private final VespaQuorumPeer peer;
+ private final ZooKeeperRunner runner;
+
+ @Inject
+ public VespaZooKeeperServerImpl(ZookeeperServerConfig zookeeperServerConfig) {
+ Validation.require(! zookeeperServerConfig.dynamicReconfiguration(),
+ ! zookeeperServerConfig.dynamicReconfiguration(),
+ "dynamicReconfiguration must be false");
+ this.peer = new VespaQuorumPeer();
+ this.runner = new ZooKeeperRunner(zookeeperServerConfig, this);
+ new VespaZooKeeperAdminImpl().createDummyNode(zookeeperServerConfig);
+ }
+
+ @Override
+ public void deconstruct() {
+ runner.shutdown();
+ super.deconstruct();
+ }
+
+ @Override
+ public void shutdown() {
+ peer.shutdown(Duration.ofMinutes(1));
+ }
+
+ @Override
+ public void start(Path configFilePath) {
+ peer.start(configFilePath);
+ }
+
+ @Override
+ public boolean reconfigurable() {
+ return false;
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/common/NetUtils.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/common/NetUtils.java
new file mode 100644
index 00000000000..baa69f12968
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/common/NetUtils.java
@@ -0,0 +1,94 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.common;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+/**
+ * This class contains common utilities for netstuff. Like printing IPv6 literals correctly
+ */
+public class NetUtils {
+
+ // Note: Changed from original to use hostname from InetSocketAddress if there exists one
+ public static String formatInetAddr(InetSocketAddress addr) {
+ String hostName = addr.getHostName();
+ if (hostName != null) {
+ return String.format("%s:%s", hostName, addr.getPort());
+ }
+
+ InetAddress ia = addr.getAddress();
+
+ if (ia == null) {
+ return String.format("%s:%s", addr.getHostString(), addr.getPort());
+ }
+ if (ia instanceof Inet6Address) {
+ return String.format("[%s]:%s", ia.getHostAddress(), addr.getPort());
+ } else {
+ return String.format("%s:%s", ia.getHostAddress(), addr.getPort());
+ }
+ }
+
+ /**
+ * Separates host and port from given host port string if host port string is enclosed
+ * within square bracket.
+ *
+ * @param hostPort host port string
+ * @return String[]{host, port} if host port string is host:port
+ * or String[] {host, port:port} if host port string is host:port:port
+ * or String[] {host} if host port string is host
+ * or String[]{} if not a ipv6 host port string.
+ */
+ public static String[] getIPV6HostAndPort(String hostPort) {
+ if (hostPort.startsWith("[")) {
+ int i = hostPort.lastIndexOf(']');
+ if (i < 0) {
+ throw new IllegalArgumentException(
+ hostPort + " starts with '[' but has no matching ']'");
+ }
+ String host = hostPort.substring(1, i);
+ if (host.isEmpty()) {
+ throw new IllegalArgumentException(host + " is empty.");
+ }
+ if (hostPort.length() > i + 1) {
+ return getHostPort(hostPort, i, host);
+ }
+ return new String[] { host };
+ } else {
+ //Not an IPV6 host port string
+ return new String[] {};
+ }
+ }
+
+ private static String[] getHostPort(String hostPort, int indexOfClosingBracket, String host) {
+ // [127::1]:2181 , check separator : exits
+ if (hostPort.charAt(indexOfClosingBracket + 1) != ':') {
+ throw new IllegalArgumentException(hostPort + " does not have : after ]");
+ }
+ // [127::1]: scenario
+ if (indexOfClosingBracket + 2 == hostPort.length()) {
+ throw new IllegalArgumentException(hostPort + " doesn't have a port after colon.");
+ }
+ //do not include
+ String port = hostPort.substring(indexOfClosingBracket + 2);
+ return new String[] { host, port };
+ }
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java
new file mode 100644
index 00000000000..cf7f4c44015
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/SyncRequestProcessor.java
@@ -0,0 +1,353 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server;
+
+import java.io.Flushable;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.apache.zookeeper.common.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This RequestProcessor logs requests to disk. It batches the requests to do
+ * the io efficiently. The request is not passed to the next RequestProcessor
+ * until its log has been synced to disk.
+ *
+ * SyncRequestProcessor is used in 3 different cases
+ * 1. Leader - Sync request to disk and forward it to AckRequestProcessor which
+ * send ack back to itself.
+ * 2. Follower - Sync request to disk and forward request to
+ * SendAckRequestProcessor which send the packets to leader.
+ * SendAckRequestProcessor is flushable which allow us to force
+ * push packets to leader.
+ * 3. Observer - Sync committed request to disk (received as INFORM packet).
+ * It never send ack back to the leader, so the nextProcessor will
+ * be null. This change the semantic of txnlog on the observer
+ * since it only contains committed txns.
+ */
+public class SyncRequestProcessor extends ZooKeeperCriticalThread implements RequestProcessor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SyncRequestProcessor.class);
+
+ private static final Request REQUEST_OF_DEATH = Request.requestOfDeath;
+
+ private static class FlushRequest extends Request {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ public FlushRequest() {
+ super(null, 0, 0, 0, null, null);
+ }
+ }
+
+ private static final Request TURN_FORWARDING_DELAY_ON_REQUEST = new Request(null, 0, 0, 0, null, null);
+ private static final Request TURN_FORWARDING_DELAY_OFF_REQUEST = new Request(null, 0, 0, 0, null, null);
+
+ private static class DelayingProcessor implements RequestProcessor, Flushable {
+ private final RequestProcessor next;
+ private Queue<Request> delayed = null;
+ private DelayingProcessor(RequestProcessor next) {
+ this.next = next;
+ }
+ @Override
+ public void flush() throws IOException {
+ if (delayed == null && next instanceof Flushable) {
+ ((Flushable) next).flush();
+ }
+ }
+ @Override
+ public void processRequest(Request request) throws RequestProcessorException {
+ if (delayed == null) {
+ next.processRequest(request);
+ } else {
+ delayed.add(request);
+ }
+ }
+ @Override
+ public void shutdown() {
+ next.shutdown();
+ }
+ private void startDelaying() {
+ if (delayed == null) {
+ delayed = new ArrayDeque<>();
+ }
+ }
+ private void flushAndStopDelaying() throws RequestProcessorException {
+ if (delayed != null) {
+ for (Request request : delayed) {
+ next.processRequest(request);
+ }
+ delayed = null;
+ }
+ }
+ }
+
+ /** The number of log entries to log before starting a snapshot */
+ private static int snapCount = ZooKeeperServer.getSnapCount();
+
+ /**
+ * The total size of log entries before starting a snapshot
+ */
+ private static long snapSizeInBytes = ZooKeeperServer.getSnapSizeInBytes();
+
+ /**
+ * Random numbers used to vary snapshot timing
+ */
+ private int randRoll;
+ private long randSize;
+
+ private final BlockingQueue<Request> queuedRequests = new LinkedBlockingQueue<>();
+
+ private final Semaphore snapThreadMutex = new Semaphore(1);
+
+ private final ZooKeeperServer zks;
+
+ private final DelayingProcessor nextProcessor;
+
+ /**
+ * Transactions that have been written and are waiting to be flushed to
+ * disk. Basically this is the list of SyncItems whose callbacks will be
+ * invoked after flush returns successfully.
+ */
+ private final Queue<Request> toFlush;
+ private long lastFlushTime;
+
+ public SyncRequestProcessor(ZooKeeperServer zks, RequestProcessor nextProcessor) {
+ super("SyncThread:" + zks.getServerId(), zks.getZooKeeperServerListener());
+ this.zks = zks;
+ this.nextProcessor = nextProcessor == null ? null : new DelayingProcessor(nextProcessor);
+ this.toFlush = new ArrayDeque<>(zks.getMaxBatchSize());
+ }
+
+ /**
+ * used by tests to check for changing
+ * snapcounts
+ * @param count
+ */
+ public static void setSnapCount(int count) {
+ snapCount = count;
+ }
+
+ /**
+ * used by tests to get the snapcount
+ * @return the snapcount
+ */
+ public static int getSnapCount() {
+ return snapCount;
+ }
+
+ private long getRemainingDelay() {
+ long flushDelay = zks.getFlushDelay();
+ long duration = Time.currentElapsedTime() - lastFlushTime;
+ if (duration < flushDelay) {
+ return flushDelay - duration;
+ }
+ return 0;
+ }
+
+ /** If both flushDelay and maxMaxBatchSize are set (bigger than 0), flush
+ * whenever either condition is hit. If only one or the other is
+ * set, flush only when the relevant condition is hit.
+ */
+ private boolean shouldFlush() {
+ long flushDelay = zks.getFlushDelay();
+ long maxBatchSize = zks.getMaxBatchSize();
+ if ((flushDelay > 0) && (getRemainingDelay() == 0)) {
+ return true;
+ }
+ return (maxBatchSize > 0) && (toFlush.size() >= maxBatchSize);
+ }
+
+ /**
+ * used by tests to check for changing
+ * snapcounts
+ * @param size
+ */
+ public static void setSnapSizeInBytes(long size) {
+ snapSizeInBytes = size;
+ }
+
+ private boolean shouldSnapshot() {
+ int logCount = zks.getZKDatabase().getTxnCount();
+ long logSize = zks.getZKDatabase().getTxnSize();
+ return (logCount > (snapCount / 2 + randRoll))
+ || (snapSizeInBytes > 0 && logSize > (snapSizeInBytes / 2 + randSize));
+ }
+
+ private void resetSnapshotStats() {
+ randRoll = ThreadLocalRandom.current().nextInt(snapCount / 2);
+ randSize = Math.abs(ThreadLocalRandom.current().nextLong() % (snapSizeInBytes / 2));
+ }
+
+ @Override
+ public void run() {
+ try {
+ // we do this in an attempt to ensure that not all of the servers
+ // in the ensemble take a snapshot at the same time
+ resetSnapshotStats();
+ lastFlushTime = Time.currentElapsedTime();
+ while (true) {
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_SIZE.add(queuedRequests.size());
+
+ long pollTime = Math.min(zks.getMaxWriteQueuePollTime(), getRemainingDelay());
+ Request si = queuedRequests.poll(pollTime, TimeUnit.MILLISECONDS);
+ if (si == null) {
+ /* We timed out looking for more writes to batch, go ahead and flush immediately */
+ flush();
+ si = queuedRequests.take();
+ }
+
+ if (si == REQUEST_OF_DEATH) {
+ break;
+ }
+
+ if (si == TURN_FORWARDING_DELAY_ON_REQUEST) {
+ nextProcessor.startDelaying();
+ continue;
+ }
+ if (si == TURN_FORWARDING_DELAY_OFF_REQUEST) {
+ nextProcessor.flushAndStopDelaying();
+ continue;
+ }
+
+ if (si instanceof FlushRequest) {
+ flush();
+ ((FlushRequest) si).latch.countDown();
+ continue;
+ }
+
+ long startProcessTime = Time.currentElapsedTime();
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_TIME.add(startProcessTime - si.syncQueueStartTime);
+
+ // track the number of records written to the log
+ if (!si.isThrottled() && zks.getZKDatabase().append(si)) {
+ if (shouldSnapshot()) {
+ resetSnapshotStats();
+ // roll the log
+ zks.getZKDatabase().rollLog();
+ // take a snapshot
+ if (!snapThreadMutex.tryAcquire()) {
+ LOG.warn("Too busy to snap, skipping");
+ } else {
+ new ZooKeeperThread("Snapshot Thread") {
+ public void run() {
+ try {
+ zks.takeSnapshot();
+ } catch (Exception e) {
+ LOG.warn("Unexpected exception", e);
+ } finally {
+ snapThreadMutex.release();
+ }
+ }
+ }.start();
+ }
+ }
+ } else if (toFlush.isEmpty()) {
+ // optimization for read heavy workloads
+ // iff this is a read or a throttled request(which doesn't need to be written to the disk),
+ // and there are no pending flushes (writes), then just pass this to the next processor
+ if (nextProcessor != null) {
+ nextProcessor.processRequest(si);
+ nextProcessor.flush();
+ }
+ continue;
+ }
+ toFlush.add(si);
+ if (shouldFlush()) {
+ flush();
+ }
+ ServerMetrics.getMetrics().SYNC_PROCESS_TIME.add(Time.currentElapsedTime() - startProcessTime);
+ }
+ } catch (Throwable t) {
+ handleException(this.getName(), t);
+ }
+ LOG.info("SyncRequestProcessor exited!");
+ }
+
+ /** Flushes all pending writes, and waits for this to complete. */
+ public void syncFlush() throws InterruptedException {
+ FlushRequest marker = new FlushRequest();
+ queuedRequests.add(marker);
+ marker.latch.await();
+ }
+
+ public void setDelayForwarding(boolean delayForwarding) {
+ queuedRequests.add(delayForwarding ? TURN_FORWARDING_DELAY_ON_REQUEST : TURN_FORWARDING_DELAY_OFF_REQUEST);
+ }
+
+ private void flush() throws IOException, RequestProcessorException {
+ if (this.toFlush.isEmpty()) {
+ return;
+ }
+
+ ServerMetrics.getMetrics().BATCH_SIZE.add(toFlush.size());
+
+ long flushStartTime = Time.currentElapsedTime();
+ zks.getZKDatabase().commit();
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_FLUSH_TIME.add(Time.currentElapsedTime() - flushStartTime);
+
+ if (this.nextProcessor == null) {
+ this.toFlush.clear();
+ } else {
+ while (!this.toFlush.isEmpty()) {
+ final Request i = this.toFlush.remove();
+ long latency = Time.currentElapsedTime() - i.syncQueueStartTime;
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_AND_FLUSH_TIME.add(latency);
+ this.nextProcessor.processRequest(i);
+ }
+ nextProcessor.flush();
+ }
+ lastFlushTime = Time.currentElapsedTime();
+ }
+
+ public void shutdown() {
+ LOG.info("Shutting down");
+ queuedRequests.add(REQUEST_OF_DEATH);
+ try {
+ this.join();
+ this.flush();
+ } catch (InterruptedException e) {
+ LOG.warn("Interrupted while wating for {} to finish", this);
+ Thread.currentThread().interrupt();
+ } catch (IOException e) {
+ LOG.warn("Got IO exception during shutdown");
+ } catch (RequestProcessorException e) {
+ LOG.warn("Got request processor exception during shutdown");
+ }
+ if (nextProcessor != null) {
+ nextProcessor.shutdown();
+ }
+ }
+
+ public void processRequest(final Request request) {
+ Objects.requireNonNull(request, "Request cannot be null");
+
+ request.syncQueueStartTime = Time.currentElapsedTime();
+ queuedRequests.add(request);
+ ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUED.add(1);
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/VespaNettyServerCnxnFactory.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/VespaNettyServerCnxnFactory.java
new file mode 100644
index 00000000000..114d2987fe2
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/VespaNettyServerCnxnFactory.java
@@ -0,0 +1,37 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package org.apache.zookeeper.server;
+
+import com.yahoo.vespa.zookeeper.Configurator;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.logging.Logger;
+
+/**
+ * Overrides secure setting with value from {@link Configurator}.
+ * Workaround for incorrect handling of clientSecurePort in combination with ZooKeeper Dynamic Reconfiguration in 3.6.2
+ * See https://issues.apache.org/jira/browse/ZOOKEEPER-3577.
+ *
+ * Using package {@link org.apache.zookeeper.server} as {@link NettyServerCnxnFactory#NettyServerCnxnFactory()} is package-private.
+ *
+ * @author bjorncs
+ */
+public class VespaNettyServerCnxnFactory extends NettyServerCnxnFactory {
+
+ private static final Logger log = Logger.getLogger(VespaNettyServerCnxnFactory.class.getName());
+
+ private final boolean isSecure;
+
+ public VespaNettyServerCnxnFactory() {
+ super();
+ this.isSecure = Configurator.VespaNettyServerCnxnFactory_isSecure;
+ boolean portUnificationEnabled = Boolean.getBoolean(NettyServerCnxnFactory.PORT_UNIFICATION_KEY);
+ log.info(String.format("For %h: isSecure=%b, portUnification=%b", this, isSecure, portUnificationEnabled));
+ }
+
+ @Override
+ public void configure(InetSocketAddress addr, int maxClientCnxns, int backlog, boolean secure) throws IOException {
+ log.info(String.format("For %h: configured() invoked with parameter 'secure'=%b, overridden to %b", this, secure, isSecure));
+ super.configure(addr, maxClientCnxns, backlog, isSecure);
+ }
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java
new file mode 100644
index 00000000000..895bbeffa5f
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java
@@ -0,0 +1,2410 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
+import java.util.zip.Adler32;
+import java.util.zip.CheckedInputStream;
+import javax.security.sasl.SaslException;
+import org.apache.jute.BinaryInputArchive;
+import org.apache.jute.BinaryOutputArchive;
+import org.apache.jute.InputArchive;
+import org.apache.jute.Record;
+import org.apache.zookeeper.Environment;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.KeeperException.Code;
+import org.apache.zookeeper.KeeperException.SessionExpiredException;
+import org.apache.zookeeper.Quotas;
+import org.apache.zookeeper.StatsTrack;
+import org.apache.zookeeper.Version;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.ZooDefs.OpCode;
+import org.apache.zookeeper.ZookeeperBanner;
+import org.apache.zookeeper.common.PathUtils;
+import org.apache.zookeeper.common.StringUtils;
+import org.apache.zookeeper.common.Time;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.data.StatPersisted;
+import org.apache.zookeeper.jmx.MBeanRegistry;
+import org.apache.zookeeper.metrics.MetricsContext;
+import org.apache.zookeeper.proto.AuthPacket;
+import org.apache.zookeeper.proto.ConnectRequest;
+import org.apache.zookeeper.proto.ConnectResponse;
+import org.apache.zookeeper.proto.CreateRequest;
+import org.apache.zookeeper.proto.DeleteRequest;
+import org.apache.zookeeper.proto.GetSASLRequest;
+import org.apache.zookeeper.proto.ReplyHeader;
+import org.apache.zookeeper.proto.RequestHeader;
+import org.apache.zookeeper.proto.SetACLRequest;
+import org.apache.zookeeper.proto.SetDataRequest;
+import org.apache.zookeeper.proto.SetSASLResponse;
+import org.apache.zookeeper.server.DataTree.ProcessTxnResult;
+import org.apache.zookeeper.server.RequestProcessor.RequestProcessorException;
+import org.apache.zookeeper.server.ServerCnxn.CloseRequestException;
+import org.apache.zookeeper.server.SessionTracker.Session;
+import org.apache.zookeeper.server.SessionTracker.SessionExpirer;
+import org.apache.zookeeper.server.auth.ProviderRegistry;
+import org.apache.zookeeper.server.auth.ServerAuthenticationProvider;
+import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
+import org.apache.zookeeper.server.quorum.ReadOnlyZooKeeperServer;
+import org.apache.zookeeper.server.util.JvmPauseMonitor;
+import org.apache.zookeeper.server.util.OSMXBean;
+import org.apache.zookeeper.server.util.QuotaMetricsUtils;
+import org.apache.zookeeper.server.util.RequestPathMetricsCollector;
+import org.apache.zookeeper.txn.CreateSessionTxn;
+import org.apache.zookeeper.txn.TxnDigest;
+import org.apache.zookeeper.txn.TxnHeader;
+import org.apache.zookeeper.util.ServiceUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class implements a simple standalone ZooKeeperServer. It sets up the
+ * following chain of RequestProcessors to process requests:
+ * PrepRequestProcessor -&gt; SyncRequestProcessor -&gt; FinalRequestProcessor
+ */
+public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider {
+
+ protected static final Logger LOG;
+ private static final RateLogger RATE_LOGGER;
+
+ public static final String GLOBAL_OUTSTANDING_LIMIT = "zookeeper.globalOutstandingLimit";
+
+ public static final String ENABLE_EAGER_ACL_CHECK = "zookeeper.enableEagerACLCheck";
+ public static final String SKIP_ACL = "zookeeper.skipACL";
+ public static final String ENFORCE_QUOTA = "zookeeper.enforceQuota";
+
+ // When enabled, will check ACL constraints appertained to the requests first,
+ // before sending the requests to the quorum.
+ static boolean enableEagerACLCheck;
+
+ static final boolean skipACL;
+
+ public static final boolean enforceQuota;
+
+ public static final String SASL_SUPER_USER = "zookeeper.superUser";
+
+ public static final String ALLOW_SASL_FAILED_CLIENTS = "zookeeper.allowSaslFailedClients";
+ public static final String ZOOKEEPER_DIGEST_ENABLED = "zookeeper.digest.enabled";
+ private static boolean digestEnabled;
+
+ public static final String ZOOKEEPER_SERIALIZE_LAST_PROCESSED_ZXID_ENABLED = "zookeeper.serializeLastProcessedZxid.enabled";
+ private static boolean serializeLastProcessedZxidEnabled;
+
+ // Add a enable/disable option for now, we should remove this one when
+ // this feature is confirmed to be stable
+ public static final String CLOSE_SESSION_TXN_ENABLED = "zookeeper.closeSessionTxn.enabled";
+ private static boolean closeSessionTxnEnabled = true;
+ private volatile CountDownLatch restoreLatch;
+
+ static {
+ LOG = LoggerFactory.getLogger(ZooKeeperServer.class);
+
+ RATE_LOGGER = new RateLogger(LOG);
+
+ ZookeeperBanner.printBanner(LOG);
+
+ Environment.logEnv("Server environment:", LOG);
+
+ enableEagerACLCheck = Boolean.getBoolean(ENABLE_EAGER_ACL_CHECK);
+ LOG.info("{} = {}", ENABLE_EAGER_ACL_CHECK, enableEagerACLCheck);
+
+ skipACL = System.getProperty(SKIP_ACL, "no").equals("yes");
+ if (skipACL) {
+ LOG.info("{}==\"yes\", ACL checks will be skipped", SKIP_ACL);
+ }
+
+ enforceQuota = Boolean.parseBoolean(System.getProperty(ENFORCE_QUOTA, "false"));
+ if (enforceQuota) {
+ LOG.info("{} = {}, Quota Enforce enables", ENFORCE_QUOTA, enforceQuota);
+ }
+
+ digestEnabled = Boolean.parseBoolean(System.getProperty(ZOOKEEPER_DIGEST_ENABLED, "true"));
+ LOG.info("{} = {}", ZOOKEEPER_DIGEST_ENABLED, digestEnabled);
+
+ closeSessionTxnEnabled = Boolean.parseBoolean(
+ System.getProperty(CLOSE_SESSION_TXN_ENABLED, "true"));
+ LOG.info("{} = {}", CLOSE_SESSION_TXN_ENABLED, closeSessionTxnEnabled);
+
+ setSerializeLastProcessedZxidEnabled(Boolean.parseBoolean(
+ System.getProperty(ZOOKEEPER_SERIALIZE_LAST_PROCESSED_ZXID_ENABLED, "true")));
+ }
+
+ // @VisibleForTesting
+ public static boolean isEnableEagerACLCheck() {
+ return enableEagerACLCheck;
+ }
+
+ // @VisibleForTesting
+ public static void setEnableEagerACLCheck(boolean enabled) {
+ ZooKeeperServer.enableEagerACLCheck = enabled;
+ LOG.info("Update {} to {}", ENABLE_EAGER_ACL_CHECK, enabled);
+ }
+
+ public static boolean isCloseSessionTxnEnabled() {
+ return closeSessionTxnEnabled;
+ }
+
+ public static void setCloseSessionTxnEnabled(boolean enabled) {
+ ZooKeeperServer.closeSessionTxnEnabled = enabled;
+ LOG.info("Update {} to {}", CLOSE_SESSION_TXN_ENABLED,
+ ZooKeeperServer.closeSessionTxnEnabled);
+ }
+
+ protected ZooKeeperServerBean jmxServerBean;
+ protected DataTreeBean jmxDataTreeBean;
+
+ public static final int DEFAULT_TICK_TIME = 3000;
+ protected int tickTime = DEFAULT_TICK_TIME;
+ public static final int DEFAULT_THROTTLED_OP_WAIT_TIME = 0; // disabled
+ protected static volatile int throttledOpWaitTime =
+ Integer.getInteger("zookeeper.throttled_op_wait_time", DEFAULT_THROTTLED_OP_WAIT_TIME);
+ /** value of -1 indicates unset, use default */
+ protected int minSessionTimeout = -1;
+ /** value of -1 indicates unset, use default */
+ protected int maxSessionTimeout = -1;
+ /** Socket listen backlog. Value of -1 indicates unset */
+ protected int listenBacklog = -1;
+ protected SessionTracker sessionTracker;
+ private FileTxnSnapLog txnLogFactory = null;
+ private ZKDatabase zkDb;
+ private ResponseCache readResponseCache;
+ private ResponseCache getChildrenResponseCache;
+ private final AtomicLong hzxid = new AtomicLong(0);
+ public static final Exception ok = new Exception("No prob");
+ protected RequestProcessor firstProcessor;
+ protected JvmPauseMonitor jvmPauseMonitor;
+ protected volatile State state = State.INITIAL;
+ private boolean isResponseCachingEnabled = true;
+ /* contains the configuration file content read at startup */
+ protected String initialConfig;
+ protected boolean reconfigEnabled;
+ private final RequestPathMetricsCollector requestPathMetricsCollector;
+ private static final int DEFAULT_SNAP_COUNT = 100000;
+ private static final int DEFAULT_GLOBAL_OUTSTANDING_LIMIT = 1000;
+
+ private boolean localSessionEnabled = false;
+ protected enum State {
+ INITIAL,
+ RUNNING,
+ SHUTDOWN,
+ ERROR
+ }
+
+ /**
+ * This is the secret that we use to generate passwords. For the moment,
+ * it's more of a checksum that's used in reconnection, which carries no
+ * security weight, and is treated internally as if it carries no
+ * security weight.
+ */
+ private static final long superSecret = 0XB3415C00L;
+
+ private final AtomicInteger requestsInProcess = new AtomicInteger(0);
+ final Deque<ChangeRecord> outstandingChanges = new ArrayDeque<>();
+ // this data structure must be accessed under the outstandingChanges lock
+ final Map<String, ChangeRecord> outstandingChangesForPath = new HashMap<>();
+
+ protected ServerCnxnFactory serverCnxnFactory;
+ protected ServerCnxnFactory secureServerCnxnFactory;
+
+ private final ServerStats serverStats;
+ private final ZooKeeperServerListener listener;
+ private ZooKeeperServerShutdownHandler zkShutdownHandler;
+ private volatile int createSessionTrackerServerId = 1;
+
+ private static final String FLUSH_DELAY = "zookeeper.flushDelay";
+ private static volatile long flushDelay;
+ private static final String MAX_WRITE_QUEUE_POLL_SIZE = "zookeeper.maxWriteQueuePollTime";
+ private static volatile long maxWriteQueuePollTime;
+ private static final String MAX_BATCH_SIZE = "zookeeper.maxBatchSize";
+ private static volatile int maxBatchSize;
+
+ /**
+ * Starting size of read and write ByteArroyOuputBuffers. Default is 32 bytes.
+ * Flag not used for small transfers like connectResponses.
+ */
+ public static final String INT_BUFFER_STARTING_SIZE_BYTES = "zookeeper.intBufferStartingSizeBytes";
+ public static final int DEFAULT_STARTING_BUFFER_SIZE = 1024;
+ public static final int intBufferStartingSizeBytes;
+
+ public static final String GET_DATA_RESPONSE_CACHE_SIZE = "zookeeper.maxResponseCacheSize";
+ public static final String GET_CHILDREN_RESPONSE_CACHE_SIZE = "zookeeper.maxGetChildrenResponseCacheSize";
+
+ static {
+ long configuredFlushDelay = Long.getLong(FLUSH_DELAY, 0);
+ setFlushDelay(configuredFlushDelay);
+ setMaxWriteQueuePollTime(Long.getLong(MAX_WRITE_QUEUE_POLL_SIZE, configuredFlushDelay / 3));
+ setMaxBatchSize(Integer.getInteger(MAX_BATCH_SIZE, 1000));
+
+ intBufferStartingSizeBytes = Integer.getInteger(INT_BUFFER_STARTING_SIZE_BYTES, DEFAULT_STARTING_BUFFER_SIZE);
+
+ if (intBufferStartingSizeBytes < 32) {
+ String msg = "Buffer starting size (" + intBufferStartingSizeBytes + ") must be greater than or equal to 32. "
+ + "Configure with \"-Dzookeeper.intBufferStartingSizeBytes=<size>\" ";
+ LOG.error(msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ LOG.info("{} = {}", INT_BUFFER_STARTING_SIZE_BYTES, intBufferStartingSizeBytes);
+ }
+
+ // Connection throttling
+ private final BlueThrottle connThrottle = new BlueThrottle();
+
+ private RequestThrottler requestThrottler;
+ public static final String SNAP_COUNT = "zookeeper.snapCount";
+
+ /**
+ * This setting sets a limit on the total number of large requests that
+ * can be inflight and is designed to prevent ZooKeeper from accepting
+ * too many large requests such that the JVM runs out of usable heap and
+ * ultimately crashes.
+ *
+ * The limit is enforced by the {@link #checkRequestSizeWhenReceivingMessage(int)}
+ * method which is called by the connection layer ({@link NIOServerCnxn},
+ * {@link NettyServerCnxn}) before allocating a byte buffer and pulling
+ * data off the TCP socket. The limit is then checked again by the
+ * ZooKeeper server in {@link #processPacket(ServerCnxn, RequestHeader, RequestRecord)} which
+ * also atomically updates {@link #currentLargeRequestBytes}. The request is
+ * then marked as a large request, with the request size stored in the Request
+ * object so that it can later be decremented from {@link #currentLargeRequestBytes}.
+ *
+ * When a request is completed or dropped, the relevant code path calls the
+ * {@link #requestFinished(Request)} method which performs the decrement if
+ * needed.
+ */
+ private volatile int largeRequestMaxBytes = 100 * 1024 * 1024;
+
+ /**
+ * The size threshold after which a request is considered a large request
+ * and is checked against the large request byte limit.
+ */
+ private volatile int largeRequestThreshold = -1;
+
+ private final AtomicInteger currentLargeRequestBytes = new AtomicInteger(0);
+
+ private final AuthenticationHelper authHelper = new AuthenticationHelper();
+
+ void removeCnxn(ServerCnxn cnxn) {
+ zkDb.removeCnxn(cnxn);
+ }
+
+ /**
+ * Creates a ZooKeeperServer instance. Nothing is setup, use the setX
+ * methods to prepare the instance (eg datadir, datalogdir, ticktime,
+ * builder, etc...)
+ *
+ */
+ public ZooKeeperServer() {
+ listener = new ZooKeeperServerListenerImpl(this);
+ serverStats = new ServerStats(this);
+ this.requestPathMetricsCollector = new RequestPathMetricsCollector();
+ }
+
+ /**
+ * Keeping this constructor for backward compatibility
+ */
+ public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, int clientPortListenBacklog, ZKDatabase zkDb, String initialConfig) {
+ this(txnLogFactory, tickTime, minSessionTimeout, maxSessionTimeout, clientPortListenBacklog, zkDb, initialConfig, QuorumPeerConfig.isReconfigEnabled());
+ }
+
+ /**
+ * * Creates a ZooKeeperServer instance. It sets everything up, but doesn't
+ * actually start listening for clients until run() is invoked.
+ *
+ */
+ public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, int clientPortListenBacklog, ZKDatabase zkDb, String initialConfig, boolean reconfigEnabled) {
+ serverStats = new ServerStats(this);
+ this.txnLogFactory = txnLogFactory;
+ this.txnLogFactory.setServerStats(this.serverStats);
+ this.zkDb = zkDb;
+ this.tickTime = tickTime;
+ setMinSessionTimeout(minSessionTimeout);
+ setMaxSessionTimeout(maxSessionTimeout);
+ this.listenBacklog = clientPortListenBacklog;
+ this.reconfigEnabled = reconfigEnabled;
+
+ listener = new ZooKeeperServerListenerImpl(this);
+
+ readResponseCache = new ResponseCache(Integer.getInteger(
+ GET_DATA_RESPONSE_CACHE_SIZE,
+ ResponseCache.DEFAULT_RESPONSE_CACHE_SIZE), "getData");
+
+ getChildrenResponseCache = new ResponseCache(Integer.getInteger(
+ GET_CHILDREN_RESPONSE_CACHE_SIZE,
+ ResponseCache.DEFAULT_RESPONSE_CACHE_SIZE), "getChildren");
+
+ this.initialConfig = initialConfig;
+
+ this.requestPathMetricsCollector = new RequestPathMetricsCollector();
+
+ this.initLargeRequestThrottlingSettings();
+
+ LOG.info(
+ "Created server with"
+ + " tickTime {} ms"
+ + " minSessionTimeout {} ms"
+ + " maxSessionTimeout {} ms"
+ + " clientPortListenBacklog {}"
+ + " datadir {}"
+ + " snapdir {}",
+ tickTime,
+ getMinSessionTimeout(),
+ getMaxSessionTimeout(),
+ getClientPortListenBacklog(),
+ txnLogFactory.getDataDir(),
+ txnLogFactory.getSnapDir());
+ }
+
+ public String getInitialConfig() {
+ return initialConfig;
+ }
+
+ /**
+ * Adds JvmPauseMonitor and calls
+ * {@link #ZooKeeperServer(FileTxnSnapLog, int, int, int, int, ZKDatabase, String)}
+ *
+ */
+ public ZooKeeperServer(JvmPauseMonitor jvmPauseMonitor, FileTxnSnapLog txnLogFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, int clientPortListenBacklog, ZKDatabase zkDb, String initialConfig) {
+ this(txnLogFactory, tickTime, minSessionTimeout, maxSessionTimeout, clientPortListenBacklog, zkDb, initialConfig, QuorumPeerConfig.isReconfigEnabled());
+ this.jvmPauseMonitor = jvmPauseMonitor;
+ if (jvmPauseMonitor != null) {
+ LOG.info("Added JvmPauseMonitor to server");
+ }
+ }
+
+ /**
+ * creates a zookeeperserver instance.
+ * @param txnLogFactory the file transaction snapshot logging class
+ * @param tickTime the ticktime for the server
+ */
+ public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, String initialConfig) {
+ this(txnLogFactory, tickTime, -1, -1, -1, new ZKDatabase(txnLogFactory), initialConfig, QuorumPeerConfig.isReconfigEnabled());
+ }
+
+ public ServerStats serverStats() {
+ return serverStats;
+ }
+
+ public RequestPathMetricsCollector getRequestPathMetricsCollector() {
+ return requestPathMetricsCollector;
+ }
+
+ public BlueThrottle connThrottle() {
+ return connThrottle;
+ }
+
+ public void dumpConf(PrintWriter pwriter) {
+ pwriter.print("clientPort=");
+ pwriter.println(getClientPort());
+ pwriter.print("secureClientPort=");
+ pwriter.println(getSecureClientPort());
+ pwriter.print("dataDir=");
+ pwriter.println(zkDb.snapLog.getSnapDir().getAbsolutePath());
+ pwriter.print("dataDirSize=");
+ pwriter.println(getDataDirSize());
+ pwriter.print("dataLogDir=");
+ pwriter.println(zkDb.snapLog.getDataDir().getAbsolutePath());
+ pwriter.print("dataLogSize=");
+ pwriter.println(getLogDirSize());
+ pwriter.print("tickTime=");
+ pwriter.println(getTickTime());
+ pwriter.print("maxClientCnxns=");
+ pwriter.println(getMaxClientCnxnsPerHost());
+ pwriter.print("minSessionTimeout=");
+ pwriter.println(getMinSessionTimeout());
+ pwriter.print("maxSessionTimeout=");
+ pwriter.println(getMaxSessionTimeout());
+ pwriter.print("clientPortListenBacklog=");
+ pwriter.println(getClientPortListenBacklog());
+
+ pwriter.print("serverId=");
+ pwriter.println(getServerId());
+ }
+
+ public ZooKeeperServerConf getConf() {
+ return new ZooKeeperServerConf(
+ getClientPort(),
+ zkDb.snapLog.getSnapDir().getAbsolutePath(),
+ zkDb.snapLog.getDataDir().getAbsolutePath(),
+ getTickTime(),
+ getMaxClientCnxnsPerHost(),
+ getMinSessionTimeout(),
+ getMaxSessionTimeout(),
+ getServerId(),
+ getClientPortListenBacklog());
+ }
+
+ /**
+ * This constructor is for backward compatibility with the existing unit
+ * test code.
+ * It defaults to FileLogProvider persistence provider.
+ */
+ public ZooKeeperServer(File snapDir, File logDir, int tickTime) throws IOException {
+ this(new FileTxnSnapLog(snapDir, logDir), tickTime, "");
+ }
+
+ /**
+ * Default constructor, relies on the config for its argument values
+ *
+ * @throws IOException
+ */
+ public ZooKeeperServer(FileTxnSnapLog txnLogFactory) throws IOException {
+ this(txnLogFactory, DEFAULT_TICK_TIME, -1, -1, -1, new ZKDatabase(txnLogFactory), "", QuorumPeerConfig.isReconfigEnabled());
+ }
+
+ /**
+ * get the zookeeper database for this server
+ * @return the zookeeper database for this server
+ */
+ public ZKDatabase getZKDatabase() {
+ return this.zkDb;
+ }
+
+ /**
+ * set the zkdatabase for this zookeeper server
+ * @param zkDb
+ */
+ public void setZKDatabase(ZKDatabase zkDb) {
+ this.zkDb = zkDb;
+ }
+
+ /**
+ * Restore sessions and data
+ */
+ public void loadData() throws IOException, InterruptedException {
+ /*
+ * When a new leader starts executing Leader#lead, it
+ * invokes this method. The database, however, has been
+ * initialized before running leader election so that
+ * the server could pick its zxid for its initial vote.
+ * It does it by invoking QuorumPeer#getLastLoggedZxid.
+ * Consequently, we don't need to initialize it once more
+ * and avoid the penalty of loading it a second time. Not
+ * reloading it is particularly important for applications
+ * that host a large database.
+ *
+ * The following if block checks whether the database has
+ * been initialized or not. Note that this method is
+ * invoked by at least one other method:
+ * ZooKeeperServer#startdata.
+ *
+ * See ZOOKEEPER-1642 for more detail.
+ */
+ if (zkDb.isInitialized()) {
+ setZxid(zkDb.getDataTreeLastProcessedZxid());
+ } else {
+ setZxid(zkDb.loadDataBase());
+ }
+
+ // Clean up dead sessions
+ zkDb.getSessions().stream()
+ .filter(session -> zkDb.getSessionWithTimeOuts().get(session) == null)
+ .forEach(session -> killSession(session, zkDb.getDataTreeLastProcessedZxid()));
+
+ // Make a clean snapshot
+ takeSnapshot();
+ }
+
+ public File takeSnapshot() throws IOException {
+ return takeSnapshot(false);
+ }
+
+ public File takeSnapshot(boolean syncSnap) throws IOException {
+ return takeSnapshot(syncSnap, true, false);
+ }
+
+ /**
+ * Takes a snapshot on the server.
+ *
+ * @param syncSnap syncSnap sync the snapshot immediately after write
+ * @param isSevere if true system exist, otherwise throw IOException
+ * @param fastForwardFromEdits whether fast forward database to the latest recorded transactions
+ *
+ * @return file snapshot file object
+ * @throws IOException
+ */
+ public synchronized File takeSnapshot(boolean syncSnap, boolean isSevere, boolean fastForwardFromEdits) throws IOException {
+ long start = Time.currentElapsedTime();
+ File snapFile = null;
+ try {
+ if (fastForwardFromEdits) {
+ zkDb.fastForwardDataBase();
+ }
+ snapFile = txnLogFactory.save(zkDb.getDataTree(), zkDb.getSessionWithTimeOuts(), syncSnap);
+ } catch (IOException e) {
+ if (isSevere) {
+ LOG.error("Severe unrecoverable error, exiting", e);
+ // This is a severe error that we cannot recover from,
+ // so we need to exit
+ ServiceUtils.requestSystemExit(ExitCode.TXNLOG_ERROR_TAKING_SNAPSHOT.getValue());
+ } else {
+ throw e;
+ }
+ }
+ long elapsed = Time.currentElapsedTime() - start;
+ LOG.info("Snapshot taken in {} ms", elapsed);
+ ServerMetrics.getMetrics().SNAPSHOT_TIME.add(elapsed);
+ return snapFile;
+ }
+
+ /**
+ * Restores database from a snapshot. It is used by the restore admin server command.
+ *
+ * @param inputStream input stream of snapshot
+ * @return last processed zxid
+ */
+ public synchronized long restoreFromSnapshot(final InputStream inputStream) throws IOException {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("InputStream can not be null when restoring from snapshot");
+ }
+
+ long start = Time.currentElapsedTime();
+ LOG.info("Before restore database. lastProcessedZxid={}, nodeCount={},sessionCount={}",
+ getZKDatabase().getDataTreeLastProcessedZxid(),
+ getZKDatabase().dataTree.getNodeCount(),
+ getZKDatabase().getSessionCount());
+
+ // restore to a new zkDatabase
+ final ZKDatabase newZKDatabase = new ZKDatabase(this.txnLogFactory);
+ final CheckedInputStream cis = new CheckedInputStream(new BufferedInputStream(inputStream), new Adler32());
+ final InputArchive ia = BinaryInputArchive.getArchive(cis);
+ newZKDatabase.deserializeSnapshot(ia, cis);
+ LOG.info("Restored to a new database. lastProcessedZxid={}, nodeCount={}, sessionCount={}",
+ newZKDatabase.getDataTreeLastProcessedZxid(),
+ newZKDatabase.dataTree.getNodeCount(),
+ newZKDatabase.getSessionCount());
+
+ // create a CountDownLatch
+ restoreLatch = new CountDownLatch(1);
+
+ try {
+ // set to the new zkDatabase
+ setZKDatabase(newZKDatabase);
+
+ // re-create SessionTrack
+ createSessionTracker();
+ } finally {
+ // unblock request submission
+ restoreLatch.countDown();
+ restoreLatch = null;
+ }
+
+ LOG.info("After restore database. lastProcessedZxid={}, nodeCount={}, sessionCount={}",
+ getZKDatabase().getDataTreeLastProcessedZxid(),
+ getZKDatabase().dataTree.getNodeCount(),
+ getZKDatabase().getSessionCount());
+
+ long elapsed = Time.currentElapsedTime() - start;
+ LOG.info("Restore taken in {} ms", elapsed);
+ ServerMetrics.getMetrics().RESTORE_TIME.add(elapsed);
+
+ return getLastProcessedZxid();
+ }
+
+ public boolean shouldForceWriteInitialSnapshotAfterLeaderElection() {
+ return txnLogFactory.shouldForceWriteInitialSnapshotAfterLeaderElection();
+ }
+
+ @Override
+ public long getDataDirSize() {
+ if (zkDb == null) {
+ return 0L;
+ }
+ File path = zkDb.snapLog.getDataDir();
+ return getDirSize(path);
+ }
+
+ @Override
+ public long getLogDirSize() {
+ if (zkDb == null) {
+ return 0L;
+ }
+ File path = zkDb.snapLog.getSnapDir();
+ return getDirSize(path);
+ }
+
+ private long getDirSize(File file) {
+ long size = 0L;
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ size += getDirSize(f);
+ }
+ }
+ } else {
+ size = file.length();
+ }
+ return size;
+ }
+
+ public long getZxid() {
+ return hzxid.get();
+ }
+
+ public SessionTracker getSessionTracker() {
+ return sessionTracker;
+ }
+
+ long getNextZxid() {
+ return hzxid.incrementAndGet();
+ }
+
+ public void setZxid(long zxid) {
+ hzxid.set(zxid);
+ }
+
+ private void close(long sessionId) {
+ Request si = new Request(null, sessionId, 0, OpCode.closeSession, null, null);
+ submitRequest(si);
+ }
+
+ public void closeSession(long sessionId) {
+ LOG.info("Closing session 0x{}", Long.toHexString(sessionId));
+
+ // we do not want to wait for a session close. send it as soon as we
+ // detect it!
+ close(sessionId);
+ }
+
+ protected void killSession(long sessionId, long zxid) {
+ zkDb.killSession(sessionId, zxid);
+ if (LOG.isTraceEnabled()) {
+ ZooTrace.logTraceMessage(
+ LOG,
+ ZooTrace.SESSION_TRACE_MASK,
+ "ZooKeeperServer --- killSession: 0x" + Long.toHexString(sessionId));
+ }
+ if (sessionTracker != null) {
+ sessionTracker.removeSession(sessionId);
+ }
+ }
+
+ public void expire(Session session) {
+ long sessionId = session.getSessionId();
+ LOG.info(
+ "Expiring session 0x{}, timeout of {}ms exceeded",
+ Long.toHexString(sessionId),
+ session.getTimeout());
+ close(sessionId);
+ }
+
+ public void expire(long sessionId) {
+ LOG.info("forcibly expiring session 0x{}", Long.toHexString(sessionId));
+
+ close(sessionId);
+ }
+
+ public static class MissingSessionException extends IOException {
+
+ private static final long serialVersionUID = 7467414635467261007L;
+
+ public MissingSessionException(String msg) {
+ super(msg);
+ }
+
+ }
+
+ void touch(ServerCnxn cnxn) throws MissingSessionException {
+ if (cnxn == null) {
+ return;
+ }
+ long id = cnxn.getSessionId();
+ int to = cnxn.getSessionTimeout();
+ if (!sessionTracker.touchSession(id, to)) {
+ throw new MissingSessionException("No session with sessionid 0x"
+ + Long.toHexString(id)
+ + " exists, probably expired and removed");
+ }
+ }
+
+ protected void registerJMX() {
+ // register with JMX
+ try {
+ jmxServerBean = new ZooKeeperServerBean(this);
+ MBeanRegistry.getInstance().register(jmxServerBean, null);
+
+ try {
+ jmxDataTreeBean = new DataTreeBean(zkDb.getDataTree());
+ MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxDataTreeBean = null;
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxServerBean = null;
+ }
+ }
+
+ public void startdata() throws IOException, InterruptedException {
+ //check to see if zkDb is not null
+ if (zkDb == null) {
+ zkDb = new ZKDatabase(this.txnLogFactory);
+ }
+ if (!zkDb.isInitialized()) {
+ loadData();
+ }
+ }
+
+ public synchronized void startup() {
+ startupWithServerState(State.RUNNING);
+ }
+
+ public synchronized void startupWithoutServing() {
+ startupWithServerState(State.INITIAL);
+ }
+
+ public synchronized void startServing() {
+ setState(State.RUNNING);
+ notifyAll();
+ }
+
+ private void startupWithServerState(State state) {
+ if (sessionTracker == null) {
+ createSessionTracker();
+ }
+ startSessionTracker();
+ setupRequestProcessors();
+
+ startRequestThrottler();
+
+ registerJMX();
+
+ startJvmPauseMonitor();
+
+ registerMetrics();
+
+ setState(state);
+
+ requestPathMetricsCollector.start();
+
+ localSessionEnabled = sessionTracker.isLocalSessionsEnabled();
+
+ notifyAll();
+ }
+
+ protected void startJvmPauseMonitor() {
+ if (this.jvmPauseMonitor != null) {
+ this.jvmPauseMonitor.serviceStart();
+ }
+ }
+
+ protected void startRequestThrottler() {
+ requestThrottler = createRequestThrottler();
+ requestThrottler.start();
+ }
+
+ protected RequestThrottler createRequestThrottler() {
+ return new RequestThrottler(this);
+ }
+
+ protected void setupRequestProcessors() {
+ RequestProcessor finalProcessor = new FinalRequestProcessor(this);
+ RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);
+ ((SyncRequestProcessor) syncProcessor).start();
+ firstProcessor = new PrepRequestProcessor(this, syncProcessor);
+ ((PrepRequestProcessor) firstProcessor).start();
+ }
+
+ public ZooKeeperServerListener getZooKeeperServerListener() {
+ return listener;
+ }
+
+ /**
+ * Change the server ID used by {@link #createSessionTracker()}. Must be called prior to
+ * {@link #startup()} being called
+ *
+ * @param newId ID to use
+ */
+ public void setCreateSessionTrackerServerId(int newId) {
+ createSessionTrackerServerId = newId;
+ }
+
+ protected void createSessionTracker() {
+ sessionTracker = new SessionTrackerImpl(this, zkDb.getSessionWithTimeOuts(), tickTime, createSessionTrackerServerId, getZooKeeperServerListener());
+ }
+
+ protected void startSessionTracker() {
+ ((SessionTrackerImpl) sessionTracker).start();
+ }
+
+ /**
+ * Sets the state of ZooKeeper server. After changing the state, it notifies
+ * the server state change to a registered shutdown handler, if any.
+ * <p>
+ * The following are the server state transitions:
+ * <ul><li>During startup the server will be in the INITIAL state.</li>
+ * <li>After successfully starting, the server sets the state to RUNNING.
+ * </li>
+ * <li>The server transitions to the ERROR state if it hits an internal
+ * error. {@link ZooKeeperServerListenerImpl} notifies any critical resource
+ * error events, e.g., SyncRequestProcessor not being able to write a txn to
+ * disk.</li>
+ * <li>During shutdown the server sets the state to SHUTDOWN, which
+ * corresponds to the server not running.</li>
+ *
+ * <li>During maintenance (e.g. restore) the server sets the state to MAINTENANCE
+ * </li></ul>
+ *
+ * @param state new server state.
+ */
+ protected void setState(State state) {
+ this.state = state;
+ // Notify server state changes to the registered shutdown handler, if any.
+ if (zkShutdownHandler != null) {
+ zkShutdownHandler.handle(state);
+ } else {
+ LOG.debug(
+ "ZKShutdownHandler is not registered, so ZooKeeper server"
+ + " won't take any action on ERROR or SHUTDOWN server state changes");
+ }
+ }
+
+ /**
+ * This can be used while shutting down the server to see whether the server
+ * is already shutdown or not.
+ *
+ * @return true if the server is running or server hits an error, false
+ * otherwise.
+ */
+ protected boolean canShutdown() {
+ return state == State.RUNNING || state == State.ERROR;
+ }
+
+ /**
+ * @return true if the server is running, false otherwise.
+ */
+ public boolean isRunning() {
+ return state == State.RUNNING;
+ }
+
+ public void shutdown() {
+ shutdown(false);
+ }
+
+ /**
+ * Shut down the server instance
+ * @param fullyShutDown true if another server using the same database will not replace this one in the same process
+ */
+ public synchronized void shutdown(boolean fullyShutDown) {
+ if (!canShutdown()) {
+ if (fullyShutDown && zkDb != null) {
+ zkDb.clear();
+ }
+ LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!");
+ return;
+ }
+ LOG.info("shutting down");
+
+ // new RuntimeException("Calling shutdown").printStackTrace();
+ setState(State.SHUTDOWN);
+
+ // unregister all metrics that are keeping a strong reference to this object
+ // subclasses will do their specific clean up
+ unregisterMetrics();
+
+ if (requestThrottler != null) {
+ requestThrottler.shutdown();
+ }
+
+ // Since sessionTracker and syncThreads poll we just have to
+ // set running to false and they will detect it during the poll
+ // interval.
+ if (sessionTracker != null) {
+ sessionTracker.shutdown();
+ }
+ if (firstProcessor != null) {
+ firstProcessor.shutdown();
+ }
+ if (jvmPauseMonitor != null) {
+ jvmPauseMonitor.serviceStop();
+ }
+
+ if (zkDb != null) {
+ if (fullyShutDown) {
+ zkDb.clear();
+ } else {
+ // else there is no need to clear the database
+ // * When a new quorum is established we can still apply the diff
+ // on top of the same zkDb data
+ // * If we fetch a new snapshot from leader, the zkDb will be
+ // cleared anyway before loading the snapshot
+ try {
+ // This will fast-forward the database to the latest recorded transactions
+ zkDb.fastForwardDataBase();
+ } catch (IOException e) {
+ LOG.error("Error updating DB", e);
+ zkDb.clear();
+ }
+ }
+ }
+
+ requestPathMetricsCollector.shutdown();
+ unregisterJMX();
+ }
+
+ protected void unregisterJMX() {
+ // unregister from JMX
+ try {
+ if (jmxDataTreeBean != null) {
+ MBeanRegistry.getInstance().unregister(jmxDataTreeBean);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ try {
+ if (jmxServerBean != null) {
+ MBeanRegistry.getInstance().unregister(jmxServerBean);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ jmxServerBean = null;
+ jmxDataTreeBean = null;
+ }
+
+ public void incInProcess() {
+ requestsInProcess.incrementAndGet();
+ }
+
+ public void decInProcess() {
+ requestsInProcess.decrementAndGet();
+ if (requestThrottler != null) {
+ requestThrottler.throttleWake();
+ }
+ }
+
+ public int getInProcess() {
+ return requestsInProcess.get();
+ }
+
+ public int getInflight() {
+ return requestThrottleInflight();
+ }
+
+ private int requestThrottleInflight() {
+ if (requestThrottler != null) {
+ return requestThrottler.getInflight();
+ }
+ return 0;
+ }
+
+ static class PrecalculatedDigest {
+ final long nodeDigest;
+ final long treeDigest;
+
+ PrecalculatedDigest(long nodeDigest, long treeDigest) {
+ this.nodeDigest = nodeDigest;
+ this.treeDigest = treeDigest;
+ }
+ }
+
+
+ /**
+ * This structure is used to facilitate information sharing between PrepRP
+ * and FinalRP.
+ */
+ static class ChangeRecord {
+ PrecalculatedDigest precalculatedDigest;
+ byte[] data;
+
+ ChangeRecord(long zxid, String path, StatPersisted stat, int childCount, List<ACL> acl) {
+ this.zxid = zxid;
+ this.path = path;
+ this.stat = stat;
+ this.childCount = childCount;
+ this.acl = acl;
+ }
+
+ long zxid;
+
+ String path;
+
+ StatPersisted stat; /* Make sure to create a new object when changing */
+
+ int childCount;
+
+ List<ACL> acl; /* Make sure to create a new object when changing */
+
+ ChangeRecord duplicate(long zxid) {
+ StatPersisted stat = new StatPersisted();
+ if (this.stat != null) {
+ DataTree.copyStatPersisted(this.stat, stat);
+ }
+ ChangeRecord changeRecord = new ChangeRecord(zxid, path, stat, childCount,
+ acl == null ? new ArrayList<>() : new ArrayList<>(acl));
+ changeRecord.precalculatedDigest = precalculatedDigest;
+ changeRecord.data = data;
+ return changeRecord;
+ }
+
+ }
+
+ byte[] generatePasswd(long id) {
+ Random r = new Random(id ^ superSecret);
+ byte[] p = new byte[16];
+ r.nextBytes(p);
+ return p;
+ }
+
+ protected boolean checkPasswd(long sessionId, byte[] passwd) {
+ return sessionId != 0 && Arrays.equals(passwd, generatePasswd(sessionId));
+ }
+
+ long createSession(ServerCnxn cnxn, byte[] passwd, int timeout) {
+ if (passwd == null) {
+ // Possible since it's just deserialized from a packet on the wire.
+ passwd = new byte[0];
+ }
+ long sessionId = sessionTracker.createSession(timeout);
+ Random r = new Random(sessionId ^ superSecret);
+ r.nextBytes(passwd);
+ CreateSessionTxn txn = new CreateSessionTxn(timeout);
+ cnxn.setSessionId(sessionId);
+ Request si = new Request(cnxn, sessionId, 0, OpCode.createSession, RequestRecord.fromRecord(txn), null);
+ submitRequest(si);
+ return sessionId;
+ }
+
+ /**
+ * set the owner of this session as owner
+ * @param id the session id
+ * @param owner the owner of the session
+ * @throws SessionExpiredException
+ */
+ public void setOwner(long id, Object owner) throws SessionExpiredException {
+ sessionTracker.setOwner(id, owner);
+ }
+
+ protected void revalidateSession(ServerCnxn cnxn, long sessionId, int sessionTimeout) throws IOException {
+ boolean rc = sessionTracker.touchSession(sessionId, sessionTimeout);
+ if (LOG.isTraceEnabled()) {
+ ZooTrace.logTraceMessage(
+ LOG,
+ ZooTrace.SESSION_TRACE_MASK,
+ "Session 0x" + Long.toHexString(sessionId) + " is valid: " + rc);
+ }
+ finishSessionInit(cnxn, rc);
+ }
+
+ public void reopenSession(ServerCnxn cnxn, long sessionId, byte[] passwd, int sessionTimeout) throws IOException {
+ if (checkPasswd(sessionId, passwd)) {
+ revalidateSession(cnxn, sessionId, sessionTimeout);
+ } else {
+ LOG.warn(
+ "Incorrect password from {} for session 0x{}",
+ cnxn.getRemoteSocketAddress(),
+ Long.toHexString(sessionId));
+ finishSessionInit(cnxn, false);
+ }
+ }
+
+ public void finishSessionInit(ServerCnxn cnxn, boolean valid) {
+ // register with JMX
+ try {
+ if (valid) {
+ if (serverCnxnFactory != null && serverCnxnFactory.cnxns.contains(cnxn)) {
+ serverCnxnFactory.registerConnection(cnxn);
+ } else if (secureServerCnxnFactory != null && secureServerCnxnFactory.cnxns.contains(cnxn)) {
+ secureServerCnxnFactory.registerConnection(cnxn);
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ }
+
+ try {
+ ConnectResponse rsp = new ConnectResponse(
+ 0,
+ valid ? cnxn.getSessionTimeout() : 0,
+ valid ? cnxn.getSessionId() : 0, // send 0 if session is no
+ // longer valid
+ valid ? generatePasswd(cnxn.getSessionId()) : new byte[16],
+ this instanceof ReadOnlyZooKeeperServer);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ BinaryOutputArchive bos = BinaryOutputArchive.getArchive(baos);
+ bos.writeInt(-1, "len");
+ rsp.serialize(bos, "connect");
+ baos.close();
+ ByteBuffer bb = ByteBuffer.wrap(baos.toByteArray());
+ bb.putInt(bb.remaining() - 4).rewind();
+ cnxn.sendBuffer(bb);
+
+ if (valid) {
+ LOG.debug(
+ "Established session 0x{} with negotiated timeout {} for client {}",
+ Long.toHexString(cnxn.getSessionId()),
+ cnxn.getSessionTimeout(),
+ cnxn.getRemoteSocketAddress());
+ cnxn.enableRecv();
+ } else {
+
+ LOG.info(
+ "Invalid session 0x{} for client {}, probably expired",
+ Long.toHexString(cnxn.getSessionId()),
+ cnxn.getRemoteSocketAddress());
+ cnxn.sendBuffer(ServerCnxnFactory.closeConn);
+ }
+
+ } catch (Exception e) {
+ LOG.warn("Exception while establishing session, closing", e);
+ cnxn.close(ServerCnxn.DisconnectReason.IO_EXCEPTION_IN_SESSION_INIT);
+ }
+ }
+
+ public void closeSession(ServerCnxn cnxn, RequestHeader requestHeader) {
+ closeSession(cnxn.getSessionId());
+ }
+
+ public long getServerId() {
+ return 0;
+ }
+
+ /**
+ * If the underlying Zookeeper server support local session, this method
+ * will set a isLocalSession to true if a request is associated with
+ * a local session.
+ *
+ * @param si
+ */
+ protected void setLocalSessionFlag(Request si) {
+ }
+
+ public void submitRequest(Request si) {
+ if (restoreLatch != null) {
+ try {
+ LOG.info("Blocking request submission while restore is in progress");
+ restoreLatch.await();
+ } catch (final InterruptedException e) {
+ LOG.warn("Unexpected interruption", e);
+ }
+ }
+ enqueueRequest(si);
+ }
+
+ public void enqueueRequest(Request si) {
+ if (requestThrottler == null) {
+ synchronized (this) {
+ try {
+ // Since all requests are passed to the request
+ // processor it should wait for setting up the request
+ // processor chain. The state will be updated to RUNNING
+ // after the setup.
+ while (state == State.INITIAL) {
+ wait(1000);
+ }
+ } catch (InterruptedException e) {
+ LOG.warn("Unexpected interruption", e);
+ }
+ if (requestThrottler == null) {
+ throw new RuntimeException("Not started");
+ }
+ }
+ }
+ requestThrottler.submitRequest(si);
+ }
+
+ public void submitRequestNow(Request si) {
+ if (firstProcessor == null) {
+ synchronized (this) {
+ try {
+ // Since all requests are passed to the request
+ // processor it should wait for setting up the request
+ // processor chain. The state will be updated to RUNNING
+ // after the setup.
+ while (state == State.INITIAL) {
+ wait(1000);
+ }
+ } catch (InterruptedException e) {
+ LOG.warn("Unexpected interruption", e);
+ }
+ if (firstProcessor == null || state != State.RUNNING) {
+ throw new RuntimeException("Not started");
+ }
+ }
+ }
+ try {
+ touch(si.cnxn);
+ boolean validpacket = Request.isValid(si.type);
+ if (validpacket) {
+ setLocalSessionFlag(si);
+ firstProcessor.processRequest(si);
+ if (si.cnxn != null) {
+ incInProcess();
+ }
+ } else {
+ LOG.warn("Received packet at server of unknown type {}", si.type);
+ // Update request accounting/throttling limits
+ requestFinished(si);
+ new UnimplementedRequestProcessor().processRequest(si);
+ }
+ } catch (MissingSessionException e) {
+ LOG.debug("Dropping request.", e);
+ // Update request accounting/throttling limits
+ requestFinished(si);
+ } catch (RequestProcessorException e) {
+ LOG.error("Unable to process request", e);
+ // Update request accounting/throttling limits
+ requestFinished(si);
+ }
+ }
+
+ public static int getSnapCount() {
+ int snapCount = Integer.getInteger(SNAP_COUNT, DEFAULT_SNAP_COUNT);
+ // snapCount must be 2 or more. See org.apache.zookeeper.server.SyncRequestProcessor
+ if (snapCount < 2) {
+ LOG.warn("SnapCount should be 2 or more. Now, snapCount is reset to 2");
+ snapCount = 2;
+ }
+ return snapCount;
+ }
+
+ public int getGlobalOutstandingLimit() {
+ return Integer.getInteger(GLOBAL_OUTSTANDING_LIMIT, DEFAULT_GLOBAL_OUTSTANDING_LIMIT);
+ }
+
+ public static long getSnapSizeInBytes() {
+ long size = Long.getLong("zookeeper.snapSizeLimitInKb", 4194304L); // 4GB by default
+ if (size <= 0) {
+ LOG.info("zookeeper.snapSizeLimitInKb set to a non-positive value {}; disabling feature", size);
+ }
+ return size * 1024; // Convert to bytes
+ }
+
+ public void setServerCnxnFactory(ServerCnxnFactory factory) {
+ serverCnxnFactory = factory;
+ }
+
+ public ServerCnxnFactory getServerCnxnFactory() {
+ return serverCnxnFactory;
+ }
+
+ public ServerCnxnFactory getSecureServerCnxnFactory() {
+ return secureServerCnxnFactory;
+ }
+
+ public void setSecureServerCnxnFactory(ServerCnxnFactory factory) {
+ secureServerCnxnFactory = factory;
+ }
+
+ /**
+ * return the last processed id from the
+ * datatree
+ */
+ public long getLastProcessedZxid() {
+ return zkDb.getDataTreeLastProcessedZxid();
+ }
+
+ /**
+ * return the outstanding requests
+ * in the queue, which haven't been
+ * processed yet
+ */
+ public long getOutstandingRequests() {
+ return getInProcess();
+ }
+
+ /**
+ * return the total number of client connections that are alive
+ * to this server
+ */
+ public int getNumAliveConnections() {
+ int numAliveConnections = 0;
+
+ if (serverCnxnFactory != null) {
+ numAliveConnections += serverCnxnFactory.getNumAliveConnections();
+ }
+
+ if (secureServerCnxnFactory != null) {
+ numAliveConnections += secureServerCnxnFactory.getNumAliveConnections();
+ }
+
+ return numAliveConnections;
+ }
+
+ /**
+ * truncate the log to get in sync with others
+ * if in a quorum
+ * @param zxid the zxid that it needs to get in sync
+ * with others
+ * @throws IOException
+ */
+ public void truncateLog(long zxid) throws IOException {
+ this.zkDb.truncateLog(zxid);
+ }
+
+ public int getTickTime() {
+ return tickTime;
+ }
+
+ public void setTickTime(int tickTime) {
+ LOG.info("tickTime set to {} ms", tickTime);
+ this.tickTime = tickTime;
+ }
+
+ public static int getThrottledOpWaitTime() {
+ return throttledOpWaitTime;
+ }
+
+ public static void setThrottledOpWaitTime(int time) {
+ LOG.info("throttledOpWaitTime set to {} ms", time);
+ throttledOpWaitTime = time;
+ }
+
+ public int getMinSessionTimeout() {
+ return minSessionTimeout;
+ }
+
+ public void setMinSessionTimeout(int min) {
+ this.minSessionTimeout = min == -1 ? tickTime * 2 : min;
+ LOG.info("minSessionTimeout set to {} ms", this.minSessionTimeout);
+ }
+
+ public int getMaxSessionTimeout() {
+ return maxSessionTimeout;
+ }
+
+ public void setMaxSessionTimeout(int max) {
+ this.maxSessionTimeout = max == -1 ? tickTime * 20 : max;
+ LOG.info("maxSessionTimeout set to {} ms", this.maxSessionTimeout);
+ }
+
+ public int getClientPortListenBacklog() {
+ return listenBacklog;
+ }
+
+ public void setClientPortListenBacklog(int backlog) {
+ this.listenBacklog = backlog;
+ LOG.info("clientPortListenBacklog set to {}", backlog);
+ }
+
+ public int getClientPort() {
+ return serverCnxnFactory != null ? serverCnxnFactory.getLocalPort() : -1;
+ }
+
+ public int getSecureClientPort() {
+ return secureServerCnxnFactory != null ? secureServerCnxnFactory.getLocalPort() : -1;
+ }
+
+ /** Maximum number of connections allowed from particular host (ip) */
+ public int getMaxClientCnxnsPerHost() {
+ if (serverCnxnFactory != null) {
+ return serverCnxnFactory.getMaxClientCnxnsPerHost();
+ }
+ if (secureServerCnxnFactory != null) {
+ return secureServerCnxnFactory.getMaxClientCnxnsPerHost();
+ }
+ return -1;
+ }
+
+ public void setTxnLogFactory(FileTxnSnapLog txnLog) {
+ this.txnLogFactory = txnLog;
+ }
+
+ public FileTxnSnapLog getTxnLogFactory() {
+ return this.txnLogFactory;
+ }
+
+ /**
+ * Returns the elapsed sync of time of transaction log in milliseconds.
+ */
+ public long getTxnLogElapsedSyncTime() {
+ return txnLogFactory.getTxnLogElapsedSyncTime();
+ }
+
+ public String getState() {
+ return "standalone";
+ }
+
+ public void dumpEphemerals(PrintWriter pwriter) {
+ zkDb.dumpEphemerals(pwriter);
+ }
+
+ public Map<Long, Set<String>> getEphemerals() {
+ return zkDb.getEphemerals();
+ }
+
+ public double getConnectionDropChance() {
+ return connThrottle.getDropChance();
+ }
+
+ public void processConnectRequest(ServerCnxn cnxn, ConnectRequest request) throws IOException, ClientCnxnLimitException {
+ LOG.debug(
+ "Session establishment request from client {} client's lastZxid is 0x{}",
+ cnxn.getRemoteSocketAddress(),
+ Long.toHexString(request.getLastZxidSeen()));
+
+ long sessionId = request.getSessionId();
+ int tokensNeeded = 1;
+ if (connThrottle.isConnectionWeightEnabled()) {
+ if (sessionId == 0) {
+ if (localSessionEnabled) {
+ tokensNeeded = connThrottle.getRequiredTokensForLocal();
+ } else {
+ tokensNeeded = connThrottle.getRequiredTokensForGlobal();
+ }
+ } else {
+ tokensNeeded = connThrottle.getRequiredTokensForRenew();
+ }
+ }
+
+ if (!connThrottle.checkLimit(tokensNeeded)) {
+ throw new ClientCnxnLimitException();
+ }
+ ServerMetrics.getMetrics().CONNECTION_TOKEN_DEFICIT.add(connThrottle.getDeficit());
+ ServerMetrics.getMetrics().CONNECTION_REQUEST_COUNT.add(1);
+
+ if (!cnxn.protocolManager.isReadonlyAvailable()) {
+ LOG.warn(
+ "Connection request from old client {}; will be dropped if server is in r-o mode",
+ cnxn.getRemoteSocketAddress());
+ }
+
+ if (!request.getReadOnly() && this instanceof ReadOnlyZooKeeperServer) {
+ String msg = "Refusing session request for not-read-only client " + cnxn.getRemoteSocketAddress();
+ LOG.info(msg);
+ throw new CloseRequestException(msg, ServerCnxn.DisconnectReason.NOT_READ_ONLY_CLIENT);
+ }
+ if (request.getLastZxidSeen() > zkDb.dataTree.lastProcessedZxid) {
+ String msg = "Refusing session request for client "
+ + cnxn.getRemoteSocketAddress()
+ + " as it has seen zxid 0x"
+ + Long.toHexString(request.getLastZxidSeen())
+ + " our last zxid is 0x"
+ + Long.toHexString(getZKDatabase().getDataTreeLastProcessedZxid())
+ + " client must try another server";
+
+ LOG.info(msg);
+ throw new CloseRequestException(msg, ServerCnxn.DisconnectReason.CLIENT_ZXID_AHEAD);
+ }
+ int sessionTimeout = request.getTimeOut();
+ byte[] passwd = request.getPasswd();
+ int minSessionTimeout = getMinSessionTimeout();
+ if (sessionTimeout < minSessionTimeout) {
+ sessionTimeout = minSessionTimeout;
+ }
+ int maxSessionTimeout = getMaxSessionTimeout();
+ if (sessionTimeout > maxSessionTimeout) {
+ sessionTimeout = maxSessionTimeout;
+ }
+ cnxn.setSessionTimeout(sessionTimeout);
+ // We don't want to receive any packets until we are sure that the
+ // session is setup
+ cnxn.disableRecv();
+ if (sessionId == 0) {
+ long id = createSession(cnxn, passwd, sessionTimeout);
+ LOG.debug(
+ "Client attempting to establish new session: session = 0x{}, zxid = 0x{}, timeout = {}, address = {}",
+ Long.toHexString(id),
+ Long.toHexString(request.getLastZxidSeen()),
+ request.getTimeOut(),
+ cnxn.getRemoteSocketAddress());
+ } else {
+ validateSession(cnxn, sessionId);
+ LOG.debug(
+ "Client attempting to renew session: session = 0x{}, zxid = 0x{}, timeout = {}, address = {}",
+ Long.toHexString(sessionId),
+ Long.toHexString(request.getLastZxidSeen()),
+ request.getTimeOut(),
+ cnxn.getRemoteSocketAddress());
+ if (serverCnxnFactory != null) {
+ serverCnxnFactory.closeSession(sessionId, ServerCnxn.DisconnectReason.CLIENT_RECONNECT);
+ }
+ if (secureServerCnxnFactory != null) {
+ secureServerCnxnFactory.closeSession(sessionId, ServerCnxn.DisconnectReason.CLIENT_RECONNECT);
+ }
+ cnxn.setSessionId(sessionId);
+ reopenSession(cnxn, sessionId, passwd, sessionTimeout);
+ ServerMetrics.getMetrics().CONNECTION_REVALIDATE_COUNT.add(1);
+
+ }
+ }
+
+ /**
+ * Validate if a particular session can be reestablished.
+ *
+ * @param cnxn
+ * @param sessionId
+ */
+ protected void validateSession(ServerCnxn cnxn, long sessionId)
+ throws IOException {
+ // do nothing
+ }
+
+ public boolean shouldThrottle(long outStandingCount) {
+ int globalOutstandingLimit = getGlobalOutstandingLimit();
+ if (globalOutstandingLimit < getInflight() || globalOutstandingLimit < getInProcess()) {
+ return outStandingCount > 0;
+ }
+ return false;
+ }
+
+ long getFlushDelay() {
+ return flushDelay;
+ }
+
+ static void setFlushDelay(long delay) {
+ LOG.info("{} = {} ms", FLUSH_DELAY, delay);
+ flushDelay = delay;
+ }
+
+ long getMaxWriteQueuePollTime() {
+ return maxWriteQueuePollTime;
+ }
+
+ static void setMaxWriteQueuePollTime(long maxTime) {
+ LOG.info("{} = {} ms", MAX_WRITE_QUEUE_POLL_SIZE, maxTime);
+ maxWriteQueuePollTime = maxTime;
+ }
+
+ int getMaxBatchSize() {
+ return maxBatchSize;
+ }
+
+ static void setMaxBatchSize(int size) {
+ LOG.info("{}={}", MAX_BATCH_SIZE, size);
+ maxBatchSize = size;
+ }
+
+ private void initLargeRequestThrottlingSettings() {
+ setLargeRequestMaxBytes(Integer.getInteger("zookeeper.largeRequestMaxBytes", largeRequestMaxBytes));
+ setLargeRequestThreshold(Integer.getInteger("zookeeper.largeRequestThreshold", -1));
+ }
+
+ public int getLargeRequestMaxBytes() {
+ return largeRequestMaxBytes;
+ }
+
+ public void setLargeRequestMaxBytes(int bytes) {
+ if (bytes <= 0) {
+ LOG.warn("Invalid max bytes for all large requests {}. It should be a positive number.", bytes);
+ LOG.warn("Will not change the setting. The max bytes stay at {}", largeRequestMaxBytes);
+ } else {
+ largeRequestMaxBytes = bytes;
+ LOG.info("The max bytes for all large requests are set to {}", largeRequestMaxBytes);
+ }
+ }
+
+ public int getLargeRequestThreshold() {
+ return largeRequestThreshold;
+ }
+
+ public void setLargeRequestThreshold(int threshold) {
+ if (threshold == 0 || threshold < -1) {
+ LOG.warn("Invalid large request threshold {}. It should be -1 or positive. Setting to -1 ", threshold);
+ largeRequestThreshold = -1;
+ } else {
+ largeRequestThreshold = threshold;
+ LOG.info("The large request threshold is set to {}", largeRequestThreshold);
+ }
+ }
+
+ public int getLargeRequestBytes() {
+ return currentLargeRequestBytes.get();
+ }
+
+ private boolean isLargeRequest(int length) {
+ // The large request limit is disabled when threshold is -1
+ if (largeRequestThreshold == -1) {
+ return false;
+ }
+ return length > largeRequestThreshold;
+ }
+
+ public boolean checkRequestSizeWhenReceivingMessage(int length) throws IOException {
+ if (!isLargeRequest(length)) {
+ return true;
+ }
+ if (currentLargeRequestBytes.get() + length <= largeRequestMaxBytes) {
+ return true;
+ } else {
+ ServerMetrics.getMetrics().LARGE_REQUESTS_REJECTED.add(1);
+ throw new IOException("Rejecting large request");
+ }
+
+ }
+
+ private boolean checkRequestSizeWhenMessageReceived(int length) throws IOException {
+ if (!isLargeRequest(length)) {
+ return true;
+ }
+
+ int bytes = currentLargeRequestBytes.addAndGet(length);
+ if (bytes > largeRequestMaxBytes) {
+ currentLargeRequestBytes.addAndGet(-length);
+ ServerMetrics.getMetrics().LARGE_REQUESTS_REJECTED.add(1);
+ throw new IOException("Rejecting large request");
+ }
+ return true;
+ }
+
+ public void requestFinished(Request request) {
+ int largeRequestLength = request.getLargeRequestSize();
+ if (largeRequestLength != -1) {
+ currentLargeRequestBytes.addAndGet(-largeRequestLength);
+ }
+ }
+
+ public void processPacket(ServerCnxn cnxn, RequestHeader h, RequestRecord request) throws IOException {
+ // Need to increase the outstanding request count first, otherwise
+ // there might be a race condition that it enabled recv after
+ // processing request and then disabled when check throttling.
+ //
+ // Be aware that we're actually checking the global outstanding
+ // request before this request.
+ //
+ // It's fine if the IOException thrown before we decrease the count
+ // in cnxn, since it will close the cnxn anyway.
+ cnxn.incrOutstandingAndCheckThrottle(h);
+
+ if (h.getType() == OpCode.auth) {
+ LOG.info("got auth packet {}", cnxn.getRemoteSocketAddress());
+ AuthPacket authPacket = request.readRecord(AuthPacket::new);
+ String scheme = authPacket.getScheme();
+ ServerAuthenticationProvider ap = ProviderRegistry.getServerProvider(scheme);
+ Code authReturn = KeeperException.Code.AUTHFAILED;
+ if (ap != null) {
+ try {
+ // handleAuthentication may close the connection, to allow the client to choose
+ // a different server to connect to.
+ authReturn = ap.handleAuthentication(
+ new ServerAuthenticationProvider.ServerObjs(this, cnxn),
+ authPacket.getAuth());
+ } catch (RuntimeException e) {
+ LOG.warn("Caught runtime exception from AuthenticationProvider: {}", scheme, e);
+ authReturn = KeeperException.Code.AUTHFAILED;
+ }
+ }
+ if (authReturn == KeeperException.Code.OK) {
+ LOG.info("Session 0x{}: auth success for scheme {} and address {}",
+ Long.toHexString(cnxn.getSessionId()), scheme,
+ cnxn.getRemoteSocketAddress());
+ ReplyHeader rh = new ReplyHeader(h.getXid(), 0, KeeperException.Code.OK.intValue());
+ cnxn.sendResponse(rh, null, null);
+ } else {
+ if (ap == null) {
+ LOG.warn(
+ "No authentication provider for scheme: {} has {}",
+ scheme,
+ ProviderRegistry.listProviders());
+ } else {
+ LOG.warn("Authentication failed for scheme: {}", scheme);
+ }
+ // send a response...
+ ReplyHeader rh = new ReplyHeader(h.getXid(), 0, KeeperException.Code.AUTHFAILED.intValue());
+ cnxn.sendResponse(rh, null, null);
+ // ... and close connection
+ cnxn.sendBuffer(ServerCnxnFactory.closeConn);
+ cnxn.disableRecv();
+ }
+ return;
+ } else if (h.getType() == OpCode.sasl) {
+ processSasl(request, cnxn, h);
+ } else {
+ if (!authHelper.enforceAuthentication(cnxn, h.getXid())) {
+ // Authentication enforcement is failed
+ // Already sent response to user about failure and closed the session, lets return
+ return;
+ } else {
+ Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), h.getType(), request, cnxn.getAuthInfo());
+ int length = request.limit();
+ if (isLargeRequest(length)) {
+ // checkRequestSize will throw IOException if request is rejected
+ checkRequestSizeWhenMessageReceived(length);
+ si.setLargeRequestSize(length);
+ }
+ si.setOwner(ServerCnxn.me);
+ submitRequest(si);
+ }
+ }
+ }
+
+ private static boolean isSaslSuperUser(String id) {
+ if (id == null || id.isEmpty()) {
+ return false;
+ }
+
+ Properties properties = System.getProperties();
+ int prefixLen = SASL_SUPER_USER.length();
+
+ for (String k : properties.stringPropertyNames()) {
+ if (k.startsWith(SASL_SUPER_USER)
+ && (k.length() == prefixLen || k.charAt(prefixLen) == '.')) {
+ String value = properties.getProperty(k);
+
+ if (value != null && value.equals(id)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean shouldAllowSaslFailedClientsConnect() {
+ return Boolean.getBoolean(ALLOW_SASL_FAILED_CLIENTS);
+ }
+
+ private void processSasl(RequestRecord request, ServerCnxn cnxn, RequestHeader requestHeader) throws IOException {
+ LOG.debug("Responding to client SASL token.");
+ GetSASLRequest clientTokenRecord = request.readRecord(GetSASLRequest::new);
+ byte[] clientToken = clientTokenRecord.getToken();
+ LOG.debug("Size of client SASL token: {}", clientToken.length);
+ byte[] responseToken = null;
+ try {
+ ZooKeeperSaslServer saslServer = cnxn.zooKeeperSaslServer;
+ try {
+ // note that clientToken might be empty (clientToken.length == 0):
+ // if using the DIGEST-MD5 mechanism, clientToken will be empty at the beginning of the
+ // SASL negotiation process.
+ responseToken = saslServer.evaluateResponse(clientToken);
+ if (saslServer.isComplete()) {
+ String authorizationID = saslServer.getAuthorizationID();
+ LOG.info("Session 0x{}: adding SASL authorization for authorizationID: {}",
+ Long.toHexString(cnxn.getSessionId()), authorizationID);
+ cnxn.addAuthInfo(new Id("sasl", authorizationID));
+
+ if (isSaslSuperUser(authorizationID)) {
+ cnxn.addAuthInfo(new Id("super", ""));
+ LOG.info(
+ "Session 0x{}: Authenticated Id '{}' as super user",
+ Long.toHexString(cnxn.getSessionId()),
+ authorizationID);
+ }
+ }
+ } catch (SaslException e) {
+ LOG.warn("Client {} failed to SASL authenticate: {}", cnxn.getRemoteSocketAddress(), e);
+ if (shouldAllowSaslFailedClientsConnect() && !authHelper.isSaslAuthRequired()) {
+ LOG.warn("Maintaining client connection despite SASL authentication failure.");
+ } else {
+ int error;
+ if (authHelper.isSaslAuthRequired()) {
+ LOG.warn(
+ "Closing client connection due to server requires client SASL authenticaiton,"
+ + "but client SASL authentication has failed, or client is not configured with SASL "
+ + "authentication.");
+ error = Code.SESSIONCLOSEDREQUIRESASLAUTH.intValue();
+ } else {
+ LOG.warn("Closing client connection due to SASL authentication failure.");
+ error = Code.AUTHFAILED.intValue();
+ }
+
+ ReplyHeader replyHeader = new ReplyHeader(requestHeader.getXid(), 0, error);
+ cnxn.sendResponse(replyHeader, new SetSASLResponse(null), "response");
+ cnxn.sendCloseSession();
+ cnxn.disableRecv();
+ return;
+ }
+ }
+ } catch (NullPointerException e) {
+ LOG.error("cnxn.saslServer is null: cnxn object did not initialize its saslServer properly.");
+ }
+ if (responseToken != null) {
+ LOG.debug("Size of server SASL response: {}", responseToken.length);
+ }
+
+ ReplyHeader replyHeader = new ReplyHeader(requestHeader.getXid(), 0, Code.OK.intValue());
+ Record record = new SetSASLResponse(responseToken);
+ cnxn.sendResponse(replyHeader, record, "response");
+ }
+
+ // entry point for quorum/Learner.java
+ public ProcessTxnResult processTxn(TxnHeader hdr, Record txn) {
+ processTxnForSessionEvents(null, hdr, txn);
+ return processTxnInDB(hdr, txn, null);
+ }
+
+ // entry point for FinalRequestProcessor.java
+ public ProcessTxnResult processTxn(Request request) {
+ TxnHeader hdr = request.getHdr();
+ processTxnForSessionEvents(request, hdr, request.getTxn());
+
+ final boolean writeRequest = (hdr != null);
+ final boolean quorumRequest = request.isQuorum();
+
+ // return fast w/o synchronization when we get a read
+ if (!writeRequest && !quorumRequest) {
+ return new ProcessTxnResult();
+ }
+ synchronized (outstandingChanges) {
+ ProcessTxnResult rc = processTxnInDB(hdr, request.getTxn(), request.getTxnDigest());
+
+ // request.hdr is set for write requests, which are the only ones
+ // that add to outstandingChanges.
+ if (writeRequest) {
+ long zxid = hdr.getZxid();
+ while (!outstandingChanges.isEmpty()
+ && outstandingChanges.peek().zxid <= zxid) {
+ ChangeRecord cr = outstandingChanges.remove();
+ ServerMetrics.getMetrics().OUTSTANDING_CHANGES_REMOVED.add(1);
+ if (cr.zxid < zxid) {
+ LOG.warn(
+ "Zxid outstanding 0x{} is less than current 0x{}",
+ Long.toHexString(cr.zxid),
+ Long.toHexString(zxid));
+ }
+ if (outstandingChangesForPath.get(cr.path) == cr) {
+ outstandingChangesForPath.remove(cr.path);
+ }
+ }
+ }
+
+ // do not add non quorum packets to the queue.
+ if (quorumRequest) {
+ getZKDatabase().addCommittedProposal(request);
+ }
+ return rc;
+ }
+ }
+
+ private void processTxnForSessionEvents(Request request, TxnHeader hdr, Record txn) {
+ int opCode = (request == null) ? hdr.getType() : request.type;
+ long sessionId = (request == null) ? hdr.getClientId() : request.sessionId;
+
+ if (opCode == OpCode.createSession) {
+ if (hdr != null && txn instanceof CreateSessionTxn) {
+ CreateSessionTxn cst = (CreateSessionTxn) txn;
+ sessionTracker.commitSession(sessionId, cst.getTimeOut());
+ } else if (request == null || !request.isLocalSession()) {
+ LOG.warn("*****>>>>> Got {} {}", txn.getClass(), txn.toString());
+ }
+ } else if (opCode == OpCode.closeSession) {
+ sessionTracker.removeSession(sessionId);
+ }
+ }
+
+ private ProcessTxnResult processTxnInDB(TxnHeader hdr, Record txn, TxnDigest digest) {
+ if (hdr == null) {
+ return new ProcessTxnResult();
+ } else {
+ return getZKDatabase().processTxn(hdr, txn, digest);
+ }
+ }
+
+ public Map<Long, Set<Long>> getSessionExpiryMap() {
+ return sessionTracker.getSessionExpiryMap();
+ }
+
+ /**
+ * This method is used to register the ZooKeeperServerShutdownHandler to get
+ * server's error or shutdown state change notifications.
+ * {@link ZooKeeperServerShutdownHandler#handle(State)} will be called for
+ * every server state changes {@link #setState(State)}.
+ *
+ * @param zkShutdownHandler shutdown handler
+ */
+ void registerServerShutdownHandler(ZooKeeperServerShutdownHandler zkShutdownHandler) {
+ this.zkShutdownHandler = zkShutdownHandler;
+ }
+
+ public boolean isResponseCachingEnabled() {
+ return isResponseCachingEnabled;
+ }
+
+ public void setResponseCachingEnabled(boolean isEnabled) {
+ isResponseCachingEnabled = isEnabled;
+ }
+
+ public ResponseCache getReadResponseCache() {
+ return isResponseCachingEnabled ? readResponseCache : null;
+ }
+
+ public ResponseCache getGetChildrenResponseCache() {
+ return isResponseCachingEnabled ? getChildrenResponseCache : null;
+ }
+
+ protected void registerMetrics() {
+ MetricsContext rootContext = ServerMetrics.getMetrics().getMetricsProvider().getRootContext();
+
+ final ZKDatabase zkdb = this.getZKDatabase();
+ final ServerStats stats = this.serverStats();
+
+ rootContext.registerGauge("avg_latency", stats::getAvgLatency);
+
+ rootContext.registerGauge("max_latency", stats::getMaxLatency);
+ rootContext.registerGauge("min_latency", stats::getMinLatency);
+
+ rootContext.registerGauge("packets_received", stats::getPacketsReceived);
+ rootContext.registerGauge("packets_sent", stats::getPacketsSent);
+ rootContext.registerGauge("num_alive_connections", stats::getNumAliveClientConnections);
+
+ rootContext.registerGauge("outstanding_requests", stats::getOutstandingRequests);
+ rootContext.registerGauge("uptime", stats::getUptime);
+
+ rootContext.registerGauge("znode_count", zkdb::getNodeCount);
+
+ rootContext.registerGauge("watch_count", zkdb.getDataTree()::getWatchCount);
+ rootContext.registerGauge("ephemerals_count", zkdb.getDataTree()::getEphemeralsCount);
+
+ rootContext.registerGauge("approximate_data_size", zkdb.getDataTree()::cachedApproximateDataSize);
+
+ rootContext.registerGauge("global_sessions", zkdb::getSessionCount);
+ rootContext.registerGauge("local_sessions", this.getSessionTracker()::getLocalSessionCount);
+
+ OSMXBean osMbean = new OSMXBean();
+ rootContext.registerGauge("open_file_descriptor_count", osMbean::getOpenFileDescriptorCount);
+ rootContext.registerGauge("max_file_descriptor_count", osMbean::getMaxFileDescriptorCount);
+ rootContext.registerGauge("connection_drop_probability", this::getConnectionDropChance);
+
+ rootContext.registerGauge("last_client_response_size", stats.getClientResponseStats()::getLastBufferSize);
+ rootContext.registerGauge("max_client_response_size", stats.getClientResponseStats()::getMaxBufferSize);
+ rootContext.registerGauge("min_client_response_size", stats.getClientResponseStats()::getMinBufferSize);
+
+ rootContext.registerGauge("outstanding_tls_handshake", this::getOutstandingHandshakeNum);
+ rootContext.registerGauge("auth_failed_count", stats::getAuthFailedCount);
+ rootContext.registerGauge("non_mtls_remote_conn_count", stats::getNonMTLSRemoteConnCount);
+ rootContext.registerGauge("non_mtls_local_conn_count", stats::getNonMTLSLocalConnCount);
+
+ rootContext.registerGaugeSet(QuotaMetricsUtils.QUOTA_COUNT_LIMIT_PER_NAMESPACE,
+ () -> QuotaMetricsUtils.getQuotaCountLimit(zkDb.getDataTree()));
+ rootContext.registerGaugeSet(QuotaMetricsUtils.QUOTA_BYTES_LIMIT_PER_NAMESPACE,
+ () -> QuotaMetricsUtils.getQuotaBytesLimit(zkDb.getDataTree()));
+ rootContext.registerGaugeSet(QuotaMetricsUtils.QUOTA_COUNT_USAGE_PER_NAMESPACE,
+ () -> QuotaMetricsUtils.getQuotaCountUsage(zkDb.getDataTree()));
+ rootContext.registerGaugeSet(QuotaMetricsUtils.QUOTA_BYTES_USAGE_PER_NAMESPACE,
+ () -> QuotaMetricsUtils.getQuotaBytesUsage(zkDb.getDataTree()));
+ }
+
+ protected void unregisterMetrics() {
+
+ MetricsContext rootContext = ServerMetrics.getMetrics().getMetricsProvider().getRootContext();
+
+ rootContext.unregisterGauge("avg_latency");
+
+ rootContext.unregisterGauge("max_latency");
+ rootContext.unregisterGauge("min_latency");
+
+ rootContext.unregisterGauge("packets_received");
+ rootContext.unregisterGauge("packets_sent");
+ rootContext.unregisterGauge("num_alive_connections");
+
+ rootContext.unregisterGauge("outstanding_requests");
+ rootContext.unregisterGauge("uptime");
+
+ rootContext.unregisterGauge("znode_count");
+
+ rootContext.unregisterGauge("watch_count");
+ rootContext.unregisterGauge("ephemerals_count");
+ rootContext.unregisterGauge("approximate_data_size");
+
+ rootContext.unregisterGauge("global_sessions");
+ rootContext.unregisterGauge("local_sessions");
+
+ rootContext.unregisterGauge("open_file_descriptor_count");
+ rootContext.unregisterGauge("max_file_descriptor_count");
+ rootContext.unregisterGauge("connection_drop_probability");
+
+ rootContext.unregisterGauge("last_client_response_size");
+ rootContext.unregisterGauge("max_client_response_size");
+ rootContext.unregisterGauge("min_client_response_size");
+
+ rootContext.unregisterGauge("auth_failed_count");
+ rootContext.unregisterGauge("non_mtls_remote_conn_count");
+ rootContext.unregisterGauge("non_mtls_local_conn_count");
+
+ rootContext.unregisterGaugeSet(QuotaMetricsUtils.QUOTA_COUNT_LIMIT_PER_NAMESPACE);
+ rootContext.unregisterGaugeSet(QuotaMetricsUtils.QUOTA_BYTES_LIMIT_PER_NAMESPACE);
+ rootContext.unregisterGaugeSet(QuotaMetricsUtils.QUOTA_COUNT_USAGE_PER_NAMESPACE);
+ rootContext.unregisterGaugeSet(QuotaMetricsUtils.QUOTA_BYTES_USAGE_PER_NAMESPACE);
+ }
+
+ /**
+ * Hook into admin server, useful to expose additional data
+ * that do not represent metrics.
+ *
+ * @param response a sink which collects the data.
+ */
+ public void dumpMonitorValues(BiConsumer<String, Object> response) {
+ ServerStats stats = serverStats();
+ response.accept("version", Version.getFullVersion());
+ response.accept("server_state", stats.getServerState());
+ }
+
+ /**
+ * Grant or deny authorization to an operation on a node as a function of:
+ * @param cnxn : the server connection or null for admin server commands
+ * @param acl : set of ACLs for the node
+ * @param perm : the permission that the client is requesting
+ * @param ids : the credentials supplied by the client
+ * @param path : the ZNode path
+ * @param setAcls : for set ACL operations, the list of ACLs being set. Otherwise null.
+ */
+ public void checkACL(ServerCnxn cnxn, List<ACL> acl, int perm, List<Id> ids, String path, List<ACL> setAcls) throws KeeperException.NoAuthException {
+ if (skipACL) {
+ return;
+ }
+
+ LOG.debug("Permission requested: {} ", perm);
+ LOG.debug("ACLs for node: {}", acl);
+ LOG.debug("Client credentials: {}", ids);
+
+ if (acl == null || acl.size() == 0) {
+ return;
+ }
+ for (Id authId : ids) {
+ if (authId.getScheme().equals("super")) {
+ return;
+ }
+ }
+ for (ACL a : acl) {
+ Id id = a.getId();
+ if ((a.getPerms() & perm) != 0) {
+ if (id.getScheme().equals("world") && id.getId().equals("anyone")) {
+ return;
+ }
+ ServerAuthenticationProvider ap = ProviderRegistry.getServerProvider(id.getScheme());
+ if (ap != null) {
+ for (Id authId : ids) {
+ if (authId.getScheme().equals(id.getScheme())
+ && ap.matches(
+ new ServerAuthenticationProvider.ServerObjs(this, cnxn),
+ new ServerAuthenticationProvider.MatchValues(path, authId.getId(), id.getId(), perm, setAcls))) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ throw new KeeperException.NoAuthException();
+ }
+
+ /**
+ * check a path whether exceeded the quota.
+ *
+ * @param path
+ * the path of the node, used for the quota prefix check
+ * @param lastData
+ * the current node data, {@code null} for none
+ * @param data
+ * the data to be set, or {@code null} for none
+ * @param type
+ * currently, create and setData need to check quota
+ */
+ public void checkQuota(String path, byte[] lastData, byte[] data, int type) throws KeeperException.QuotaExceededException {
+ if (!enforceQuota) {
+ return;
+ }
+ long dataBytes = (data == null) ? 0 : data.length;
+ ZKDatabase zkDatabase = getZKDatabase();
+ String lastPrefix = zkDatabase.getDataTree().getMaxPrefixWithQuota(path);
+ if (StringUtils.isEmpty(lastPrefix)) {
+ return;
+ }
+
+ final String namespace = PathUtils.getTopNamespace(path);
+ switch (type) {
+ case OpCode.create:
+ checkQuota(lastPrefix, dataBytes, 1, namespace);
+ break;
+ case OpCode.setData:
+ checkQuota(lastPrefix, dataBytes - (lastData == null ? 0 : lastData.length), 0, namespace);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported OpCode for checkQuota: " + type);
+ }
+ }
+
+ /**
+ * check a path whether exceeded the quota.
+ *
+ * @param lastPrefix
+ the path of the node which has a quota.
+ * @param bytesDiff
+ * the diff to be added to number of bytes
+ * @param countDiff
+ * the diff to be added to the count
+ * @param namespace
+ * the namespace for collecting quota exceeded errors
+ */
+ private void checkQuota(String lastPrefix, long bytesDiff, long countDiff, String namespace)
+ throws KeeperException.QuotaExceededException {
+ LOG.debug("checkQuota: lastPrefix={}, bytesDiff={}, countDiff={}", lastPrefix, bytesDiff, countDiff);
+
+ // now check the quota we set
+ String limitNode = Quotas.limitPath(lastPrefix);
+ DataNode node = getZKDatabase().getNode(limitNode);
+ StatsTrack limitStats;
+ if (node == null) {
+ // should not happen
+ LOG.error("Missing limit node for quota {}", limitNode);
+ return;
+ }
+ synchronized (node) {
+ limitStats = new StatsTrack(node.data);
+ }
+ //check the quota
+ boolean checkCountQuota = countDiff != 0 && (limitStats.getCount() > -1 || limitStats.getCountHardLimit() > -1);
+ boolean checkByteQuota = bytesDiff != 0 && (limitStats.getBytes() > -1 || limitStats.getByteHardLimit() > -1);
+
+ if (!checkCountQuota && !checkByteQuota) {
+ return;
+ }
+
+ //check the statPath quota
+ String statNode = Quotas.statPath(lastPrefix);
+ node = getZKDatabase().getNode(statNode);
+
+ StatsTrack currentStats;
+ if (node == null) {
+ // should not happen
+ LOG.error("Missing node for stat {}", statNode);
+ return;
+ }
+ synchronized (node) {
+ currentStats = new StatsTrack(node.data);
+ }
+
+ //check the Count Quota
+ if (checkCountQuota) {
+ long newCount = currentStats.getCount() + countDiff;
+ boolean isCountHardLimit = limitStats.getCountHardLimit() > -1;
+ long countLimit = isCountHardLimit ? limitStats.getCountHardLimit() : limitStats.getCount();
+
+ if (newCount > countLimit) {
+ String msg = "Quota exceeded: " + lastPrefix + " [current count=" + newCount + ", " + (isCountHardLimit ? "hard" : "soft") + "CountLimit=" + countLimit + "]";
+ RATE_LOGGER.rateLimitLog(msg);
+ if (isCountHardLimit) {
+ updateQuotaExceededMetrics(namespace);
+ throw new KeeperException.QuotaExceededException(lastPrefix);
+ }
+ }
+ }
+
+ //check the Byte Quota
+ if (checkByteQuota) {
+ long newBytes = currentStats.getBytes() + bytesDiff;
+ boolean isByteHardLimit = limitStats.getByteHardLimit() > -1;
+ long byteLimit = isByteHardLimit ? limitStats.getByteHardLimit() : limitStats.getBytes();
+ if (newBytes > byteLimit) {
+ String msg = "Quota exceeded: " + lastPrefix + " [current bytes=" + newBytes + ", " + (isByteHardLimit ? "hard" : "soft") + "ByteLimit=" + byteLimit + "]";
+ RATE_LOGGER.rateLimitLog(msg);
+ if (isByteHardLimit) {
+ updateQuotaExceededMetrics(namespace);
+ throw new KeeperException.QuotaExceededException(lastPrefix);
+ }
+ }
+ }
+ }
+
+ public static boolean isDigestEnabled() {
+ return digestEnabled;
+ }
+
+ public static void setDigestEnabled(boolean digestEnabled) {
+ LOG.info("{} = {}", ZOOKEEPER_DIGEST_ENABLED, digestEnabled);
+ ZooKeeperServer.digestEnabled = digestEnabled;
+ }
+
+ public static boolean isSerializeLastProcessedZxidEnabled() {
+ return serializeLastProcessedZxidEnabled;
+ }
+
+ public static void setSerializeLastProcessedZxidEnabled(boolean serializeLastZxidEnabled) {
+ serializeLastProcessedZxidEnabled = serializeLastZxidEnabled;
+ LOG.info("{} = {}", ZOOKEEPER_SERIALIZE_LAST_PROCESSED_ZXID_ENABLED, serializeLastZxidEnabled);
+ }
+
+ /**
+ * Trim a path to get the immediate predecessor.
+ *
+ * @param path
+ * @return
+ * @throws KeeperException.BadArgumentsException
+ */
+ private String parentPath(String path) throws KeeperException.BadArgumentsException {
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash == -1 || path.indexOf('\0') != -1 || getZKDatabase().isSpecialPath(path)) {
+ throw new KeeperException.BadArgumentsException(path);
+ }
+ return lastSlash == 0 ? "/" : path.substring(0, lastSlash);
+ }
+
+ private String effectiveACLPath(Request request) throws KeeperException.BadArgumentsException, KeeperException.InvalidACLException {
+ boolean mustCheckACL = false;
+ String path = null;
+ List<ACL> acl = null;
+
+ switch (request.type) {
+ case OpCode.create:
+ case OpCode.create2: {
+ CreateRequest req = request.readRequestRecordNoException(CreateRequest::new);
+ if (req != null) {
+ mustCheckACL = true;
+ acl = req.getAcl();
+ path = parentPath(req.getPath());
+ }
+ break;
+ }
+ case OpCode.delete: {
+ DeleteRequest req = request.readRequestRecordNoException(DeleteRequest::new);
+ if (req != null) {
+ path = parentPath(req.getPath());
+ }
+ break;
+ }
+ case OpCode.setData: {
+ SetDataRequest req = request.readRequestRecordNoException(SetDataRequest::new);
+ if (req != null) {
+ path = req.getPath();
+ }
+ break;
+ }
+ case OpCode.setACL: {
+ SetACLRequest req = request.readRequestRecordNoException(SetACLRequest::new);
+ if (req != null) {
+ mustCheckACL = true;
+ acl = req.getAcl();
+ path = req.getPath();
+ }
+ break;
+ }
+ }
+
+ if (mustCheckACL) {
+ /* we ignore the extrapolated ACL returned by fixupACL because
+ * we only care about it being well-formed (and if it isn't, an
+ * exception will be raised).
+ */
+ PrepRequestProcessor.fixupACL(path, request.authInfo, acl);
+ }
+
+ return path;
+ }
+
+ private int effectiveACLPerms(Request request) {
+ switch (request.type) {
+ case OpCode.create:
+ case OpCode.create2:
+ return ZooDefs.Perms.CREATE;
+ case OpCode.delete:
+ return ZooDefs.Perms.DELETE;
+ case OpCode.setData:
+ return ZooDefs.Perms.WRITE;
+ case OpCode.setACL:
+ return ZooDefs.Perms.ADMIN;
+ default:
+ return ZooDefs.Perms.ALL;
+ }
+ }
+
+ /**
+ * Check Write Requests for Potential Access Restrictions
+ * <p>
+ * Before a request is being proposed to the quorum, lets check it
+ * against local ACLs. Non-write requests (read, session, etc.)
+ * are passed along. Invalid requests are sent a response.
+ * <p>
+ * While we are at it, if the request will set an ACL: make sure it's
+ * a valid one.
+ *
+ * @param request
+ * @return true if request is permitted, false if not.
+ */
+ public boolean authWriteRequest(Request request) {
+ int err;
+ String pathToCheck;
+
+ if (!enableEagerACLCheck) {
+ return true;
+ }
+
+ err = KeeperException.Code.OK.intValue();
+
+ try {
+ pathToCheck = effectiveACLPath(request);
+ if (pathToCheck != null) {
+ checkACL(request.cnxn, zkDb.getACL(pathToCheck, null), effectiveACLPerms(request), request.authInfo, pathToCheck, null);
+ }
+ } catch (KeeperException.NoAuthException e) {
+ LOG.debug("Request failed ACL check", e);
+ err = e.code().intValue();
+ } catch (KeeperException.InvalidACLException e) {
+ LOG.debug("Request has an invalid ACL check", e);
+ err = e.code().intValue();
+ } catch (KeeperException.NoNodeException e) {
+ LOG.debug("ACL check against non-existent node: {}", e.getMessage());
+ } catch (KeeperException.BadArgumentsException e) {
+ LOG.debug("ACL check against illegal node path: {}", e.getMessage());
+ } catch (Throwable t) {
+ LOG.error("Uncaught exception in authWriteRequest with: ", t);
+ throw t;
+ } finally {
+ if (err != KeeperException.Code.OK.intValue()) {
+ /* This request has a bad ACL, so we are dismissing it early. */
+ decInProcess();
+ ReplyHeader rh = new ReplyHeader(request.cxid, 0, err);
+ try {
+ request.cnxn.sendResponse(rh, null, null);
+ } catch (IOException e) {
+ LOG.error("IOException : {}", e);
+ }
+ }
+ }
+
+ return err == KeeperException.Code.OK.intValue();
+ }
+
+ public int getOutstandingHandshakeNum() {
+ if (serverCnxnFactory instanceof NettyServerCnxnFactory) {
+ return ((NettyServerCnxnFactory) serverCnxnFactory).getOutstandingHandshakeNum();
+ } else {
+ return 0;
+ }
+ }
+
+ public boolean isReconfigEnabled() {
+ return this.reconfigEnabled;
+ }
+
+ public ZooKeeperServerShutdownHandler getZkShutdownHandler() {
+ return zkShutdownHandler;
+ }
+
+ static void updateQuotaExceededMetrics(final String namespace) {
+ if (namespace == null) {
+ return;
+ }
+ ServerMetrics.getMetrics().QUOTA_EXCEEDED_ERROR_PER_NAMESPACE.add(namespace, 1);
+ }
+}
+
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java
new file mode 100644
index 00000000000..1f629bed73d
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import javax.management.JMException;
+import org.apache.zookeeper.KeeperException.SessionExpiredException;
+import org.apache.zookeeper.jmx.MBeanRegistry;
+import org.apache.zookeeper.metrics.MetricsContext;
+import org.apache.zookeeper.server.ContainerManager;
+import org.apache.zookeeper.server.DataTreeBean;
+import org.apache.zookeeper.server.FinalRequestProcessor;
+import org.apache.zookeeper.server.PrepRequestProcessor;
+import org.apache.zookeeper.server.Request;
+import org.apache.zookeeper.server.RequestProcessor;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.apache.zookeeper.server.ServerMetrics;
+import org.apache.zookeeper.server.ZKDatabase;
+import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
+
+/**
+ *
+ * Just like the standard ZooKeeperServer. We just replace the request
+ * processors: PrepRequestProcessor -&gt; ProposalRequestProcessor -&gt;
+ * CommitProcessor -&gt; Leader.ToBeAppliedRequestProcessor -&gt;
+ * FinalRequestProcessor
+ */
+public class LeaderZooKeeperServer extends QuorumZooKeeperServer {
+
+ private ContainerManager containerManager; // guarded by sync
+
+ CommitProcessor commitProcessor;
+
+ PrepRequestProcessor prepRequestProcessor;
+
+ /**
+ * @throws IOException
+ */
+ public LeaderZooKeeperServer(FileTxnSnapLog logFactory, QuorumPeer self, ZKDatabase zkDb) throws IOException {
+ super(logFactory, self.tickTime, self.minSessionTimeout, self.maxSessionTimeout, self.clientPortListenBacklog, zkDb, self);
+ }
+
+ public Leader getLeader() {
+ return self.leader;
+ }
+
+ @Override
+ protected void setupRequestProcessors() {
+ RequestProcessor finalProcessor = new FinalRequestProcessor(this);
+ RequestProcessor toBeAppliedProcessor = new Leader.ToBeAppliedRequestProcessor(finalProcessor, getLeader());
+ commitProcessor = new CommitProcessor(toBeAppliedProcessor, Long.toString(getServerId()), false, getZooKeeperServerListener());
+ commitProcessor.start();
+ ProposalRequestProcessor proposalProcessor = new ProposalRequestProcessor(this, commitProcessor);
+ proposalProcessor.initialize();
+ prepRequestProcessor = new PrepRequestProcessor(this, proposalProcessor);
+ prepRequestProcessor.start();
+ firstProcessor = new LeaderRequestProcessor(this, prepRequestProcessor);
+
+ setupContainerManager();
+ }
+
+ private synchronized void setupContainerManager() {
+ containerManager = new ContainerManager(
+ getZKDatabase(),
+ prepRequestProcessor,
+ Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)),
+ Integer.getInteger("znode.container.maxPerMinute", 10000),
+ Long.getLong("znode.container.maxNeverUsedIntervalMs", 0)
+ );
+ }
+
+ @Override
+ public synchronized void startup() {
+ super.startup();
+ if (containerManager != null) {
+ containerManager.start();
+ }
+ }
+
+ @Override
+ protected void registerMetrics() {
+ super.registerMetrics();
+
+ MetricsContext rootContext = ServerMetrics.getMetrics().getMetricsProvider().getRootContext();
+ rootContext.registerGauge("learners", gaugeWithLeader(
+ (leader) -> leader.getLearners().size())
+ );
+ rootContext.registerGauge("synced_followers", gaugeWithLeader(
+ (leader) -> leader.getForwardingFollowers().size()
+ ));
+ rootContext.registerGauge("synced_non_voting_followers", gaugeWithLeader(
+ (leader) -> leader.getNonVotingFollowers().size()
+ ));
+ rootContext.registerGauge("synced_observers", self::getSynced_observers_metric);
+ rootContext.registerGauge("pending_syncs", gaugeWithLeader(
+ (leader) -> leader.getNumPendingSyncs()
+ ));
+ rootContext.registerGauge("leader_uptime", gaugeWithLeader(
+ (leader) -> leader.getUptime()
+ ));
+ rootContext.registerGauge("last_proposal_size", gaugeWithLeader(
+ (leader) -> leader.getProposalStats().getLastBufferSize()
+ ));
+ rootContext.registerGauge("max_proposal_size", gaugeWithLeader(
+ (leader) -> leader.getProposalStats().getMaxBufferSize()
+ ));
+ rootContext.registerGauge("min_proposal_size", gaugeWithLeader(
+ (leader) -> leader.getProposalStats().getMinBufferSize()
+ ));
+ }
+
+ private org.apache.zookeeper.metrics.Gauge gaugeWithLeader(Function<Leader, Number> supplier) {
+ return () -> {
+ final Leader leader = getLeader();
+ if (leader == null) {
+ return null;
+ }
+ return supplier.apply(leader);
+ };
+ }
+
+ @Override
+ protected void unregisterMetrics() {
+ super.unregisterMetrics();
+
+ MetricsContext rootContext = ServerMetrics.getMetrics().getMetricsProvider().getRootContext();
+ rootContext.unregisterGauge("learners");
+ rootContext.unregisterGauge("synced_followers");
+ rootContext.unregisterGauge("synced_non_voting_followers");
+ rootContext.unregisterGauge("synced_observers");
+ rootContext.unregisterGauge("pending_syncs");
+ rootContext.unregisterGauge("leader_uptime");
+
+ rootContext.unregisterGauge("last_proposal_size");
+ rootContext.unregisterGauge("max_proposal_size");
+ rootContext.unregisterGauge("min_proposal_size");
+ }
+
+ @Override
+ public synchronized void shutdown(boolean fullyShutDown) {
+ if (containerManager != null) {
+ containerManager.stop();
+ }
+ super.shutdown(fullyShutDown);
+ }
+
+ @Override
+ public int getGlobalOutstandingLimit() {
+ int divisor = self.getQuorumSize() > 2 ? self.getQuorumSize() - 1 : 1;
+ int globalOutstandingLimit = super.getGlobalOutstandingLimit() / divisor;
+ return globalOutstandingLimit;
+ }
+
+ @Override
+ public void createSessionTracker() {
+ sessionTracker = new LeaderSessionTracker(
+ this,
+ getZKDatabase().getSessionWithTimeOuts(),
+ tickTime,
+ self.getMyId(),
+ self.areLocalSessionsEnabled(),
+ getZooKeeperServerListener());
+ }
+
+ public boolean touch(long sess, int to) {
+ return sessionTracker.touchSession(sess, to);
+ }
+
+ public boolean checkIfValidGlobalSession(long sess, int to) {
+ if (self.areLocalSessionsEnabled() && !upgradeableSessionTracker.isGlobalSession(sess)) {
+ return false;
+ }
+ return sessionTracker.touchSession(sess, to);
+ }
+
+ /**
+ * Requests coming from the learner should go directly to
+ * PrepRequestProcessor
+ *
+ * @param request
+ */
+ public void submitLearnerRequest(Request request) {
+ /*
+ * Requests coming from the learner should have gone through
+ * submitRequest() on each server which already perform some request
+ * validation, so we don't need to do it again.
+ *
+ * Additionally, LearnerHandler should start submitting requests into
+ * the leader's pipeline only when the leader's server is started, so we
+ * can submit the request directly into PrepRequestProcessor.
+ *
+ * This is done so that requests from learners won't go through
+ * LeaderRequestProcessor which perform local session upgrade.
+ */
+ prepRequestProcessor.processRequest(request);
+ }
+
+ @Override
+ protected void registerJMX() {
+ // register with JMX
+ try {
+ jmxDataTreeBean = new DataTreeBean(getZKDatabase().getDataTree());
+ MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxDataTreeBean = null;
+ }
+ }
+
+ public void registerJMX(LeaderBean leaderBean, LocalPeerBean localPeerBean) {
+ // register with JMX
+ if (self.jmxLeaderElectionBean != null) {
+ try {
+ MBeanRegistry.getInstance().unregister(self.jmxLeaderElectionBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ }
+ self.jmxLeaderElectionBean = null;
+ }
+
+ try {
+ jmxServerBean = leaderBean;
+ MBeanRegistry.getInstance().register(leaderBean, localPeerBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxServerBean = null;
+ }
+ }
+
+ boolean registerJMX(LearnerHandlerBean handlerBean) {
+ try {
+ MBeanRegistry.getInstance().register(handlerBean, jmxServerBean);
+ return true;
+ } catch (JMException e) {
+ LOG.warn("Could not register connection", e);
+ }
+ return false;
+ }
+
+ @Override
+ protected void unregisterJMX() {
+ // unregister from JMX
+ try {
+ if (jmxDataTreeBean != null) {
+ MBeanRegistry.getInstance().unregister(jmxDataTreeBean);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ jmxDataTreeBean = null;
+ }
+
+ protected void unregisterJMX(Leader leader) {
+ // unregister from JMX
+ try {
+ if (jmxServerBean != null) {
+ MBeanRegistry.getInstance().unregister(jmxServerBean);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ jmxServerBean = null;
+ }
+
+ @Override
+ public String getState() {
+ return "leader";
+ }
+
+ /**
+ * Returns the id of the associated QuorumPeer, which will do for a unique
+ * id of this server.
+ */
+ @Override
+ public long getServerId() {
+ return self.getMyId();
+ }
+
+ @Override
+ protected void revalidateSession(ServerCnxn cnxn, long sessionId, int sessionTimeout) throws IOException {
+ super.revalidateSession(cnxn, sessionId, sessionTimeout);
+ try {
+ // setowner as the leader itself, unless updated
+ // via the follower handlers
+ setOwner(sessionId, ServerCnxn.me);
+ } catch (SessionExpiredException e) {
+ // this is ok, it just means that the session revalidation failed.
+ }
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/Learner.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/Learner.java
new file mode 100644
index 00000000000..8d8b6dabce8
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/Learner.java
@@ -0,0 +1,927 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.net.ssl.SSLSocket;
+import org.apache.jute.BinaryInputArchive;
+import org.apache.jute.BinaryOutputArchive;
+import org.apache.jute.InputArchive;
+import org.apache.jute.OutputArchive;
+import org.apache.jute.Record;
+import org.apache.zookeeper.ZooDefs.OpCode;
+import org.apache.zookeeper.common.Time;
+import org.apache.zookeeper.common.X509Exception;
+import org.apache.zookeeper.server.ExitCode;
+import org.apache.zookeeper.server.Request;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.apache.zookeeper.server.ServerMetrics;
+import org.apache.zookeeper.server.TxnLogEntry;
+import org.apache.zookeeper.server.ZooTrace;
+import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer;
+import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
+import org.apache.zookeeper.server.util.ConfigUtils;
+import org.apache.zookeeper.server.util.MessageTracker;
+import org.apache.zookeeper.server.util.SerializeUtils;
+import org.apache.zookeeper.server.util.ZxidUtils;
+import org.apache.zookeeper.txn.SetDataTxn;
+import org.apache.zookeeper.txn.TxnDigest;
+import org.apache.zookeeper.txn.TxnHeader;
+import org.apache.zookeeper.util.ServiceUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class is the superclass of two of the three main actors in a ZK
+ * ensemble: Followers and Observers. Both Followers and Observers share
+ * a good deal of code which is moved into Peer to avoid duplication.
+ */
+public class Learner {
+
+ static class PacketInFlight {
+
+ TxnHeader hdr;
+ Record rec;
+ TxnDigest digest;
+
+ }
+
+ QuorumPeer self;
+ LearnerZooKeeperServer zk;
+
+ protected BufferedOutputStream bufferedOutput;
+
+ protected Socket sock;
+ protected MultipleAddresses leaderAddr;
+ protected AtomicBoolean sockBeingClosed = new AtomicBoolean(false);
+
+ /**
+ * Socket getter
+ */
+ public Socket getSocket() {
+ return sock;
+ }
+
+ LearnerSender sender = null;
+ protected InputArchive leaderIs;
+ protected OutputArchive leaderOs;
+ /** the protocol version of the leader */
+ protected int leaderProtocolVersion = 0x01;
+
+ private static final int BUFFERED_MESSAGE_SIZE = 10;
+ protected final MessageTracker messageTracker = new MessageTracker(BUFFERED_MESSAGE_SIZE);
+
+ protected static final Logger LOG = LoggerFactory.getLogger(Learner.class);
+
+ /**
+ * Time to wait after connection attempt with the Leader or LearnerMaster before this
+ * Learner tries to connect again.
+ */
+ private static final int leaderConnectDelayDuringRetryMs = Integer.getInteger("zookeeper.leaderConnectDelayDuringRetryMs", 100);
+
+ private static final boolean nodelay = System.getProperty("follower.nodelay", "true").equals("true");
+
+ public static final String LEARNER_ASYNC_SENDING = "zookeeper.learner.asyncSending";
+ private static boolean asyncSending =
+ Boolean.parseBoolean(ConfigUtils.getPropertyBackwardCompatibleWay(LEARNER_ASYNC_SENDING));
+ public static final String LEARNER_CLOSE_SOCKET_ASYNC = "zookeeper.learner.closeSocketAsync";
+ public static final boolean closeSocketAsync = Boolean
+ .parseBoolean(ConfigUtils.getPropertyBackwardCompatibleWay(LEARNER_CLOSE_SOCKET_ASYNC));
+
+ static {
+ LOG.info("leaderConnectDelayDuringRetryMs: {}", leaderConnectDelayDuringRetryMs);
+ LOG.info("TCP NoDelay set to: {}", nodelay);
+ LOG.info("{} = {}", LEARNER_ASYNC_SENDING, asyncSending);
+ LOG.info("{} = {}", LEARNER_CLOSE_SOCKET_ASYNC, closeSocketAsync);
+ }
+
+ final ConcurrentHashMap<Long, ServerCnxn> pendingRevalidations = new ConcurrentHashMap<>();
+
+ public int getPendingRevalidationsCount() {
+ return pendingRevalidations.size();
+ }
+
+ // for testing
+ protected static void setAsyncSending(boolean newMode) {
+ asyncSending = newMode;
+ LOG.info("{} = {}", LEARNER_ASYNC_SENDING, asyncSending);
+
+ }
+ protected static boolean getAsyncSending() {
+ return asyncSending;
+ }
+ /**
+ * validate a session for a client
+ *
+ * @param clientId
+ * the client to be revalidated
+ * @param timeout
+ * the timeout for which the session is valid
+ * @throws IOException
+ */
+ void validateSession(ServerCnxn cnxn, long clientId, int timeout) throws IOException {
+ LOG.info("Revalidating client: 0x{}", Long.toHexString(clientId));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeLong(clientId);
+ dos.writeInt(timeout);
+ dos.close();
+ QuorumPacket qp = new QuorumPacket(Leader.REVALIDATE, -1, baos.toByteArray(), null);
+ pendingRevalidations.put(clientId, cnxn);
+ if (LOG.isTraceEnabled()) {
+ ZooTrace.logTraceMessage(
+ LOG,
+ ZooTrace.SESSION_TRACE_MASK,
+ "To validate session 0x" + Long.toHexString(clientId));
+ }
+ writePacket(qp, true);
+ }
+
+ /**
+ * write a packet to the leader.
+ *
+ * This method is called by multiple threads. We need to make sure that only one thread is writing to leaderOs at a time.
+ * When packets are sent synchronously, writing is done within a synchronization block.
+ * When packets are sent asynchronously, sender.queuePacket() is called, which writes to a BlockingQueue, which is thread-safe.
+ * Reading from this BlockingQueue and writing to leaderOs is the learner sender thread only.
+ * So we have only one thread writing to leaderOs at a time in either case.
+ *
+ * @param pp
+ * the proposal packet to be sent to the leader
+ * @throws IOException
+ */
+ void writePacket(QuorumPacket pp, boolean flush) throws IOException {
+ if (asyncSending) {
+ sender.queuePacket(pp);
+ } else {
+ writePacketNow(pp, flush);
+ }
+ }
+
+ void writePacketNow(QuorumPacket pp, boolean flush) throws IOException {
+ synchronized (leaderOs) {
+ if (pp != null) {
+ messageTracker.trackSent(pp.getType());
+ leaderOs.writeRecord(pp, "packet");
+ }
+ if (flush) {
+ bufferedOutput.flush();
+ }
+ }
+ }
+
+ /**
+ * Start thread that will forward any packet in the queue to the leader
+ */
+ protected void startSendingThread() {
+ sender = new LearnerSender(this);
+ sender.start();
+ }
+
+ /**
+ * read a packet from the leader
+ *
+ * @param pp
+ * the packet to be instantiated
+ * @throws IOException
+ */
+ void readPacket(QuorumPacket pp) throws IOException {
+ synchronized (leaderIs) {
+ leaderIs.readRecord(pp, "packet");
+ messageTracker.trackReceived(pp.getType());
+ }
+ if (LOG.isTraceEnabled()) {
+ final long traceMask =
+ (pp.getType() == Leader.PING) ? ZooTrace.SERVER_PING_TRACE_MASK
+ : ZooTrace.SERVER_PACKET_TRACE_MASK;
+
+ ZooTrace.logQuorumPacket(LOG, traceMask, 'i', pp);
+ }
+ }
+
+ /**
+ * send a request packet to the leader
+ *
+ * @param request
+ * the request from the client
+ * @throws IOException
+ */
+ void request(Request request) throws IOException {
+ if (request.isThrottled()) {
+ LOG.error("Throttled request sent to leader: {}. Exiting", request);
+ ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream oa = new DataOutputStream(baos);
+ oa.writeLong(request.sessionId);
+ oa.writeInt(request.cxid);
+ oa.writeInt(request.type);
+ byte[] payload = request.readRequestBytes();
+ if (payload != null) {
+ oa.write(payload);
+ }
+ oa.close();
+ QuorumPacket qp = new QuorumPacket(Leader.REQUEST, -1, baos.toByteArray(), request.authInfo);
+ writePacket(qp, true);
+ }
+
+ /**
+ * Returns the address of the node we think is the leader.
+ */
+ protected QuorumServer findLeader() {
+ QuorumServer leaderServer = null;
+ // Find the leader by id
+ Vote current = self.getCurrentVote();
+ for (QuorumServer s : self.getView().values()) {
+ if (s.id == current.getId()) {
+ // Ensure we have the leader's correct IP address before
+ // attempting to connect.
+ s.recreateSocketAddresses();
+ leaderServer = s;
+ break;
+ }
+ }
+ if (leaderServer == null) {
+ LOG.warn("Couldn't find the leader with id = {}", current.getId());
+ }
+ return leaderServer;
+ }
+
+ /**
+ * Overridable helper method to return the System.nanoTime().
+ * This method behaves identical to System.nanoTime().
+ */
+ protected long nanoTime() {
+ return System.nanoTime();
+ }
+
+ /**
+ * Overridable helper method to simply call sock.connect(). This can be
+ * overriden in tests to fake connection success/failure for connectToLeader.
+ */
+ protected void sockConnect(Socket sock, InetSocketAddress addr, int timeout) throws IOException {
+ sock.connect(addr, timeout);
+ }
+
+ /**
+ * Establish a connection with the LearnerMaster found by findLearnerMaster.
+ * Followers only connect to Leaders, Observers can connect to any active LearnerMaster.
+ * Retries until either initLimit time has elapsed or 5 tries have happened.
+ * @param multiAddr - the address of the Peer to connect to.
+ * @throws IOException - if the socket connection fails on the 5th attempt
+ * if there is an authentication failure while connecting to leader
+ */
+ protected void connectToLeader(MultipleAddresses multiAddr, String hostname) throws IOException {
+
+ this.leaderAddr = multiAddr;
+ Set<InetSocketAddress> addresses;
+ if (self.isMultiAddressReachabilityCheckEnabled()) {
+ // even if none of the addresses are reachable, we want to try to establish connection
+ // see ZOOKEEPER-3758
+ addresses = multiAddr.getAllReachableAddressesOrAll();
+ } else {
+ addresses = multiAddr.getAllAddresses();
+ }
+ ExecutorService executor = Executors.newFixedThreadPool(addresses.size());
+ CountDownLatch latch = new CountDownLatch(addresses.size());
+ AtomicReference<Socket> socket = new AtomicReference<>(null);
+ addresses.stream().map(address -> new LeaderConnector(address, socket, latch)).forEach(executor::submit);
+
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ LOG.warn("Interrupted while trying to connect to Leader", e);
+ } finally {
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
+ LOG.error("not all the LeaderConnector terminated properly");
+ }
+ } catch (InterruptedException ie) {
+ LOG.error("Interrupted while terminating LeaderConnector executor.", ie);
+ }
+ }
+
+ if (socket.get() == null) {
+ throw new IOException("Failed connect to " + multiAddr);
+ } else {
+ sock = socket.get();
+ sockBeingClosed.set(false);
+ }
+
+ self.authLearner.authenticate(sock, hostname);
+
+ leaderIs = BinaryInputArchive.getArchive(new BufferedInputStream(sock.getInputStream()));
+ bufferedOutput = new BufferedOutputStream(sock.getOutputStream());
+ leaderOs = BinaryOutputArchive.getArchive(bufferedOutput);
+ if (asyncSending) {
+ startSendingThread();
+ }
+ }
+
+ class LeaderConnector implements Runnable {
+
+ private AtomicReference<Socket> socket;
+ private InetSocketAddress address;
+ private CountDownLatch latch;
+
+ LeaderConnector(InetSocketAddress address, AtomicReference<Socket> socket, CountDownLatch latch) {
+ this.address = address;
+ this.socket = socket;
+ this.latch = latch;
+ }
+
+ @Override
+ public void run() {
+ try {
+ Thread.currentThread().setName("LeaderConnector-" + address);
+ Socket sock = connectToLeader();
+
+ if (sock != null && sock.isConnected()) {
+ if (socket.compareAndSet(null, sock)) {
+ LOG.info("Successfully connected to leader, using address: {}", address);
+ } else {
+ LOG.info("Connection to the leader is already established, close the redundant connection");
+ sock.close();
+ }
+ }
+
+ } catch (Exception e) {
+ LOG.error("Failed connect to {}", address, e);
+ } finally {
+ latch.countDown();
+ }
+ }
+
+ private Socket connectToLeader() throws IOException, X509Exception, InterruptedException {
+ Socket sock = createSocket();
+
+ // leader connection timeout defaults to tickTime * initLimit
+ int connectTimeout = self.tickTime * self.initLimit;
+
+ // but if connectToLearnerMasterLimit is specified, use that value to calculate
+ // timeout instead of using the initLimit value
+ if (self.connectToLearnerMasterLimit > 0) {
+ connectTimeout = self.tickTime * self.connectToLearnerMasterLimit;
+ }
+
+ int remainingTimeout;
+ long startNanoTime = nanoTime();
+
+ for (int tries = 0; tries < 5 && socket.get() == null; tries++) {
+ try {
+ // recalculate the init limit time because retries sleep for 1000 milliseconds
+ remainingTimeout = connectTimeout - (int) ((nanoTime() - startNanoTime) / 1_000_000);
+ if (remainingTimeout <= 0) {
+ LOG.error("connectToLeader exceeded on retries.");
+ throw new IOException("connectToLeader exceeded on retries.");
+ }
+
+ sockConnect(sock, address, Math.min(connectTimeout, remainingTimeout));
+ if (self.isSslQuorum()) {
+ ((SSLSocket) sock).startHandshake();
+ }
+ sock.setTcpNoDelay(nodelay);
+ break;
+ } catch (IOException e) {
+ remainingTimeout = connectTimeout - (int) ((nanoTime() - startNanoTime) / 1_000_000);
+
+ if (remainingTimeout <= leaderConnectDelayDuringRetryMs) {
+ LOG.error(
+ "Unexpected exception, connectToLeader exceeded. tries={}, remaining init limit={}, connecting to {}",
+ tries,
+ remainingTimeout,
+ address,
+ e);
+ throw e;
+ } else if (tries >= 4) {
+ LOG.error(
+ "Unexpected exception, retries exceeded. tries={}, remaining init limit={}, connecting to {}",
+ tries,
+ remainingTimeout,
+ address,
+ e);
+ throw e;
+ } else {
+ LOG.warn(
+ "Unexpected exception, tries={}, remaining init limit={}, connecting to {}",
+ tries,
+ remainingTimeout,
+ address,
+ e);
+ sock = createSocket();
+ }
+ }
+ Thread.sleep(leaderConnectDelayDuringRetryMs);
+ }
+
+ return sock;
+ }
+ }
+
+ /**
+ * Creating a simple or and SSL socket.
+ * This can be overridden in tests to fake already connected sockets for connectToLeader.
+ */
+ protected Socket createSocket() throws X509Exception, IOException {
+ Socket sock;
+ if (self.isSslQuorum()) {
+ sock = self.getX509Util().createSSLSocket();
+ } else {
+ sock = new Socket();
+ }
+ sock.setSoTimeout(self.tickTime * self.initLimit);
+ return sock;
+ }
+
+ /**
+ * Once connected to the leader or learner master, perform the handshake
+ * protocol to establish a following / observing connection.
+ * @param pktType
+ * @return the zxid the Leader sends for synchronization purposes.
+ * @throws IOException
+ */
+ protected long registerWithLeader(int pktType) throws IOException {
+ /*
+ * Send follower info, including last zxid and sid
+ */
+ long lastLoggedZxid = self.getLastLoggedZxid();
+ QuorumPacket qp = new QuorumPacket();
+ qp.setType(pktType);
+ qp.setZxid(ZxidUtils.makeZxid(self.getAcceptedEpoch(), 0));
+
+ /*
+ * Add sid to payload
+ */
+ LearnerInfo li = new LearnerInfo(self.getMyId(), 0x10000, self.getQuorumVerifier().getVersion());
+ ByteArrayOutputStream bsid = new ByteArrayOutputStream();
+ BinaryOutputArchive boa = BinaryOutputArchive.getArchive(bsid);
+ boa.writeRecord(li, "LearnerInfo");
+ qp.setData(bsid.toByteArray());
+
+ writePacket(qp, true);
+ readPacket(qp);
+ final long newEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
+ if (qp.getType() == Leader.LEADERINFO) {
+ // we are connected to a 1.0 server so accept the new epoch and read the next packet
+ leaderProtocolVersion = ByteBuffer.wrap(qp.getData()).getInt();
+ byte[] epochBytes = new byte[4];
+ final ByteBuffer wrappedEpochBytes = ByteBuffer.wrap(epochBytes);
+ if (newEpoch > self.getAcceptedEpoch()) {
+ wrappedEpochBytes.putInt((int) self.getCurrentEpoch());
+ self.setAcceptedEpoch(newEpoch);
+ } else if (newEpoch == self.getAcceptedEpoch()) {
+ // since we have already acked an epoch equal to the leaders, we cannot ack
+ // again, but we still need to send our lastZxid to the leader so that we can
+ // sync with it if it does assume leadership of the epoch.
+ // the -1 indicates that this reply should not count as an ack for the new epoch
+ wrappedEpochBytes.putInt(-1);
+ } else {
+ throw new IOException("Leaders epoch, "
+ + newEpoch
+ + " is less than accepted epoch, "
+ + self.getAcceptedEpoch());
+ }
+ QuorumPacket ackNewEpoch = new QuorumPacket(Leader.ACKEPOCH, lastLoggedZxid, epochBytes, null);
+ writePacket(ackNewEpoch, true);
+ return ZxidUtils.makeZxid(newEpoch, 0);
+ } else {
+ if (newEpoch > self.getAcceptedEpoch()) {
+ self.setAcceptedEpoch(newEpoch);
+ }
+ if (qp.getType() != Leader.NEWLEADER) {
+ LOG.error("First packet should have been NEWLEADER");
+ throw new IOException("First packet should have been NEWLEADER");
+ }
+ return qp.getZxid();
+ }
+ }
+
+ /**
+ * Finally, synchronize our history with the Leader (if Follower)
+ * or the LearnerMaster (if Observer).
+ * @param newLeaderZxid
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ protected void syncWithLeader(long newLeaderZxid) throws Exception {
+ long newEpoch = ZxidUtils.getEpochFromZxid(newLeaderZxid);
+ QuorumVerifier newLeaderQV = null;
+
+ class SyncHelper {
+
+ // In the DIFF case we don't need to do a snapshot because the transactions will sync on top of any existing snapshot.
+ // For SNAP and TRUNC the snapshot is needed to save that history.
+ boolean willSnapshot = true;
+ boolean syncSnapshot = false;
+
+ // PROPOSALs received during sync, for matching up with COMMITs.
+ Deque<PacketInFlight> proposals = new ArrayDeque<>();
+
+ // PROPOSALs we delay forwarding to the ZK server until sync is done.
+ Deque<PacketInFlight> delayedProposals = new ArrayDeque<>();
+
+ // COMMITs we delay forwarding to the ZK server until sync is done.
+ Deque<Long> delayedCommits = new ArrayDeque<>();
+
+ void syncSnapshot() {
+ syncSnapshot = true;
+ }
+
+ void noSnapshot() {
+ willSnapshot = false;
+ }
+
+ void propose(PacketInFlight pif) {
+ proposals.add(pif);
+ delayedProposals.add(pif);
+ }
+
+ PacketInFlight nextProposal() {
+ return proposals.peekFirst();
+ }
+
+ void commit() {
+ PacketInFlight packet = proposals.remove();
+ if (willSnapshot) {
+ zk.processTxn(packet.hdr, packet.rec);
+ delayedProposals.remove();
+ } else {
+ delayedCommits.add(packet.hdr.getZxid());
+ }
+ }
+
+ void writeState() throws IOException, InterruptedException {
+ // Ensure all received transaction PROPOSALs are written before we ACK the NEWLEADER,
+ // since this allows the leader to apply those transactions to its served state:
+ if (willSnapshot) {
+ zk.takeSnapshot(syncSnapshot); // either, the snapshot contains the transactions,
+ willSnapshot = false; // but anything after this needs to go to the transaction log; or
+ }
+
+ self.setCurrentEpoch(newEpoch);
+ sock.setSoTimeout(self.tickTime * self.syncLimit);
+ self.setSyncMode(QuorumPeer.SyncMode.NONE);
+ zk.startupWithoutServing();
+
+ // if we're a follower, we need to ensure the transactions are safely logged before ACK'ing.
+ if (zk instanceof FollowerZooKeeperServer) {
+ FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
+ // The leader expects the NEWLEADER ACK to precede all the PROPOSAL ACKs, so we only write them first.
+ fzk.syncProcessor.setDelayForwarding(true);
+ for (PacketInFlight p : delayedProposals) {
+ fzk.logRequest(p.hdr, p.rec, p.digest);
+ }
+ delayedProposals.clear();
+ fzk.syncProcessor.syncFlush();
+ }
+ }
+
+ void flushAcks() throws InterruptedException {
+ if (zk instanceof FollowerZooKeeperServer) {
+ // The NEWLEADER is ACK'ed, and we can now ACK the PROPOSALs we wrote in writeState.
+ FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
+ fzk.syncProcessor.setDelayForwarding(false);
+ fzk.syncProcessor.syncFlush(); // Ensure these are all ACK'ed before the UPTODATE ACK.
+ }
+ }
+
+ void applyDelayedPackets() {
+ // Any delayed packets must now be applied: all PROPOSALs first, then any COMMITs.
+ if (zk instanceof FollowerZooKeeperServer) {
+ FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
+ for (PacketInFlight p : delayedProposals) {
+ fzk.logRequest(p.hdr, p.rec, p.digest);
+ }
+ for (Long zxid : delayedCommits) {
+ fzk.commit(zxid);
+ }
+ } else if (zk instanceof ObserverZooKeeperServer) {
+ ObserverZooKeeperServer ozk = (ObserverZooKeeperServer) zk;
+ for (PacketInFlight p : delayedProposals) {
+ Long zxid = delayedCommits.peekFirst();
+ if (p.hdr.getZxid() != zxid) {
+ // log warning message if there is no matching commit
+ // old leader send outstanding proposal to observer
+ LOG.warn(
+ "Committing 0x{}, but next proposal is 0x{}",
+ Long.toHexString(zxid),
+ Long.toHexString(p.hdr.getZxid()));
+ continue;
+ }
+ delayedCommits.remove();
+ Request request = new Request(p.hdr.getClientId(), p.hdr.getCxid(), p.hdr.getType(), p.hdr, p.rec, -1);
+ request.setTxnDigest(p.digest);
+ ozk.commitRequest(request);
+ }
+ } else {
+ // New server type need to handle in-flight packets
+ throw new UnsupportedOperationException("Unknown server type");
+ }
+ }
+
+ }
+
+ SyncHelper helper = new SyncHelper();
+ QuorumPacket qp = new QuorumPacket();
+ readPacket(qp);
+ synchronized (zk) {
+ if (qp.getType() == Leader.DIFF) {
+ LOG.info("Getting a diff from the leader 0x{}", Long.toHexString(qp.getZxid()));
+ self.setSyncMode(QuorumPeer.SyncMode.DIFF);
+ if (zk.shouldForceWriteInitialSnapshotAfterLeaderElection()) {
+ LOG.info("Forcing a snapshot write as part of upgrading from an older Zookeeper. This should only happen while upgrading.");
+ helper.syncSnapshot();
+ } else {
+ helper.noSnapshot();
+ }
+ } else if (qp.getType() == Leader.SNAP) {
+ self.setSyncMode(QuorumPeer.SyncMode.SNAP);
+ LOG.info("Getting a snapshot from leader 0x{}", Long.toHexString(qp.getZxid()));
+ // The leader is going to dump the database
+ zk.getZKDatabase().deserializeSnapshot(leaderIs);
+ // ZOOKEEPER-2819: overwrite config node content extracted
+ // from leader snapshot with local config, to avoid potential
+ // inconsistency of config node content during rolling restart.
+ if (!self.isReconfigEnabled()) {
+ LOG.debug("Reset config node content from local config after deserialization of snapshot.");
+ zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier());
+ }
+ String signature = leaderIs.readString("signature");
+ if (!signature.equals("BenWasHere")) {
+ LOG.error("Missing signature. Got {}", signature);
+ throw new IOException("Missing signature");
+ }
+ zk.getZKDatabase().setlastProcessedZxid(qp.getZxid());
+
+ // Immediately persist the latest snapshot when there is txn log gap
+ helper.syncSnapshot();
+ } else if (qp.getType() == Leader.TRUNC) {
+ // We need to truncate the log to the lastZxid of the leader
+ self.setSyncMode(QuorumPeer.SyncMode.TRUNC);
+ LOG.warn("Truncating log to get in sync with the leader 0x{}", Long.toHexString(qp.getZxid()));
+ boolean truncated = zk.getZKDatabase().truncateLog(qp.getZxid());
+ if (!truncated) {
+ LOG.error("Not able to truncate the log 0x{}", Long.toHexString(qp.getZxid()));
+ ServiceUtils.requestSystemExit(ExitCode.QUORUM_PACKET_ERROR.getValue());
+ }
+ zk.getZKDatabase().setlastProcessedZxid(qp.getZxid());
+ } else {
+ LOG.error("Got unexpected packet from leader: {}, exiting ... ", LearnerHandler.packetToString(qp));
+ ServiceUtils.requestSystemExit(ExitCode.QUORUM_PACKET_ERROR.getValue());
+ }
+ zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier());
+ zk.createSessionTracker();
+
+
+ // we are now going to start getting transactions to apply followed by an UPTODATE
+ long lastQueued = 0;
+ TxnLogEntry logEntry;
+ outerLoop:
+ while (self.isRunning()) {
+ readPacket(qp);
+ switch (qp.getType()) {
+ case Leader.PROPOSAL:
+ PacketInFlight pif = new PacketInFlight();
+ logEntry = SerializeUtils.deserializeTxn(qp.getData());
+ pif.hdr = logEntry.getHeader();
+ pif.rec = logEntry.getTxn();
+ pif.digest = logEntry.getDigest();
+ if (pif.hdr.getZxid() != lastQueued + 1) {
+ LOG.warn(
+ "Got zxid 0x{} expected 0x{}",
+ Long.toHexString(pif.hdr.getZxid()),
+ Long.toHexString(lastQueued + 1));
+ }
+ lastQueued = pif.hdr.getZxid();
+
+ if (pif.hdr.getType() == OpCode.reconfig) {
+ SetDataTxn setDataTxn = (SetDataTxn) pif.rec;
+ QuorumVerifier qv = self.configFromString(new String(setDataTxn.getData(), UTF_8));
+ self.setLastSeenQuorumVerifier(qv, true);
+ }
+ helper.propose(pif);
+ break;
+ case Leader.COMMIT:
+ case Leader.COMMITANDACTIVATE:
+ pif = helper.nextProposal();
+ if (pif.hdr.getZxid() != qp.getZxid()) {
+ LOG.warn(
+ "Committing 0x{}, but next proposal is 0x{}",
+ Long.toHexString(qp.getZxid()),
+ Long.toHexString(pif.hdr.getZxid()));
+ } else {
+ if (qp.getType() == Leader.COMMITANDACTIVATE) {
+ tryReconfig(pif, ByteBuffer.wrap(qp.getData()).getLong(), qp.getZxid());
+ }
+ helper.commit();
+ }
+ break;
+ case Leader.INFORM:
+ case Leader.INFORMANDACTIVATE:
+ PacketInFlight packet = new PacketInFlight();
+ if (qp.getType() == Leader.INFORMANDACTIVATE) {
+ ByteBuffer buffer = ByteBuffer.wrap(qp.getData());
+ long suggestedLeaderId = buffer.getLong();
+ byte[] remainingData = new byte[buffer.remaining()];
+ buffer.get(remainingData);
+ logEntry = SerializeUtils.deserializeTxn(remainingData);
+ packet.hdr = logEntry.getHeader();
+ packet.rec = logEntry.getTxn();
+ packet.digest = logEntry.getDigest();
+ tryReconfig(packet, suggestedLeaderId, qp.getZxid());
+ } else {
+ logEntry = SerializeUtils.deserializeTxn(qp.getData());
+ packet.rec = logEntry.getTxn();
+ packet.hdr = logEntry.getHeader();
+ packet.digest = logEntry.getDigest();
+ // Log warning message if txn comes out-of-order
+ if (packet.hdr.getZxid() != lastQueued + 1) {
+ LOG.warn(
+ "Got zxid 0x{} expected 0x{}",
+ Long.toHexString(packet.hdr.getZxid()),
+ Long.toHexString(lastQueued + 1));
+ }
+ lastQueued = packet.hdr.getZxid();
+ }
+ helper.propose(packet);
+ helper.commit();
+ break;
+ case Leader.UPTODATE:
+ LOG.info("Learner received UPTODATE message");
+ if (newLeaderQV != null) {
+ boolean majorChange = self.processReconfig(newLeaderQV, null, null, true);
+ if (majorChange) {
+ throw new Exception("changes proposed in reconfig");
+ }
+ }
+ helper.flushAcks();
+ self.setZooKeeperServer(zk);
+ self.adminServer.setZooKeeperServer(zk);
+ break outerLoop;
+ case Leader.NEWLEADER: // Getting NEWLEADER here instead of in discovery
+ LOG.info("Learner received NEWLEADER message");
+ if (qp.getData() != null && qp.getData().length > 1) {
+ try {
+ QuorumVerifier qv = self.configFromString(new String(qp.getData(), UTF_8));
+ self.setLastSeenQuorumVerifier(qv, true);
+ newLeaderQV = qv;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ helper.writeState();
+ writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true);
+ break;
+ }
+ }
+ }
+ QuorumPacket ack = new QuorumPacket(Leader.ACK, 0, null, null);
+ ack.setZxid(ZxidUtils.makeZxid(newEpoch, 0));
+ writePacket(ack, true);
+ zk.startServing();
+ /*
+ * Update the election vote here to ensure that all members of the
+ * ensemble report the same vote to new servers that start up and
+ * send leader election notifications to the ensemble.
+ *
+ * @see https://issues.apache.org/jira/browse/ZOOKEEPER-1732
+ */
+ self.updateElectionVote(newEpoch);
+
+ helper.applyDelayedPackets();
+ }
+
+ private void tryReconfig(PacketInFlight pif, long newLeader, long zxid) throws Exception {
+ QuorumVerifier qv = self.configFromString(new String(((SetDataTxn) pif.rec).getData(), UTF_8));
+ boolean majorChange = self.processReconfig(qv, newLeader, zxid, true);
+ if (majorChange) {
+ throw new Exception("changes proposed in reconfig");
+ }
+ }
+
+ protected void revalidate(QuorumPacket qp) throws IOException {
+ ByteArrayInputStream bis = new ByteArrayInputStream(qp.getData());
+ DataInputStream dis = new DataInputStream(bis);
+ long sessionId = dis.readLong();
+ boolean valid = dis.readBoolean();
+ ServerCnxn cnxn = pendingRevalidations.remove(sessionId);
+ if (cnxn == null) {
+ LOG.warn("Missing session 0x{} for validation", Long.toHexString(sessionId));
+ } else {
+ zk.finishSessionInit(cnxn, valid);
+ }
+ if (LOG.isTraceEnabled()) {
+ ZooTrace.logTraceMessage(
+ LOG,
+ ZooTrace.SESSION_TRACE_MASK,
+ "Session 0x" + Long.toHexString(sessionId) + " is valid: " + valid);
+ }
+ }
+
+ protected void ping(QuorumPacket qp) throws IOException {
+ // Send back the ping with our session data
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+ Map<Long, Integer> touchTable = zk.getTouchSnapshot();
+ for (Entry<Long, Integer> entry : touchTable.entrySet()) {
+ dos.writeLong(entry.getKey());
+ dos.writeInt(entry.getValue());
+ }
+
+ QuorumPacket pingReply = new QuorumPacket(qp.getType(), qp.getZxid(), bos.toByteArray(), qp.getAuthinfo());
+ writePacket(pingReply, true);
+ }
+
+ /**
+ * Shutdown the Peer
+ */
+ public void shutdown() {
+ self.setZooKeeperServer(null);
+ self.closeAllConnections();
+ self.adminServer.setZooKeeperServer(null);
+
+ if (sender != null) {
+ sender.shutdown();
+ }
+
+ closeSocket();
+ // shutdown previous zookeeper
+ if (zk != null) {
+ // If we haven't finished SNAP sync, force fully shutdown
+ // to avoid potential inconsistency
+ zk.shutdown(self.getSyncMode().equals(QuorumPeer.SyncMode.SNAP));
+ }
+ }
+
+ boolean isRunning() {
+ return self.isRunning() && zk.isRunning();
+ }
+
+ void closeSocket() {
+ if (sock != null) {
+ if (sockBeingClosed.compareAndSet(false, true)) {
+ if (closeSocketAsync) {
+ final Thread closingThread = new Thread(() -> closeSockSync(), "CloseSocketThread(sid:" + zk.getServerId());
+ closingThread.setDaemon(true);
+ closingThread.start();
+ } else {
+ closeSockSync();
+ }
+ }
+ }
+ }
+
+ void closeSockSync() {
+ try {
+ long startTime = Time.currentElapsedTime();
+ if (sock != null) {
+ sock.close();
+ sock = null;
+ }
+ ServerMetrics.getMetrics().SOCKET_CLOSING_TIME.add(Time.currentElapsedTime() - startTime);
+ } catch (IOException e) {
+ LOG.warn("Ignoring error closing connection to leader", e);
+ }
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java
new file mode 100644
index 00000000000..8ea94fd4daf
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.zookeeper.jmx.MBeanRegistry;
+import org.apache.zookeeper.server.DataTreeBean;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.apache.zookeeper.server.SyncRequestProcessor;
+import org.apache.zookeeper.server.ZKDatabase;
+import org.apache.zookeeper.server.ZooKeeperServerBean;
+import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
+
+/**
+ * Parent class for all ZooKeeperServers for Learners
+ */
+public abstract class LearnerZooKeeperServer extends QuorumZooKeeperServer {
+
+ /*
+ * Request processors
+ */
+ protected CommitProcessor commitProcessor;
+ protected SyncRequestProcessor syncProcessor;
+
+ public LearnerZooKeeperServer(FileTxnSnapLog logFactory, int tickTime, int minSessionTimeout, int maxSessionTimeout, int listenBacklog, ZKDatabase zkDb, QuorumPeer self) throws IOException {
+ super(logFactory, tickTime, minSessionTimeout, maxSessionTimeout, listenBacklog, zkDb, self);
+ }
+
+ /**
+ * Abstract method to return the learner associated with this server.
+ * Since the Learner may change under our feet (when QuorumPeer reassigns
+ * it) we can't simply take a reference here. Instead, we need the
+ * subclasses to implement this.
+ */
+ public abstract Learner getLearner();
+
+ /**
+ * Returns the current state of the session tracker. This is only currently
+ * used by a Learner to build a ping response packet.
+ *
+ */
+ protected Map<Long, Integer> getTouchSnapshot() {
+ if (sessionTracker != null) {
+ return ((LearnerSessionTracker) sessionTracker).snapshot();
+ }
+ Map<Long, Integer> map = Collections.emptyMap();
+ return map;
+ }
+
+ /**
+ * Returns the id of the associated QuorumPeer, which will do for a unique
+ * id of this server.
+ */
+ @Override
+ public long getServerId() {
+ return self.getMyId();
+ }
+
+ @Override
+ public void createSessionTracker() {
+ sessionTracker = new LearnerSessionTracker(
+ this,
+ getZKDatabase().getSessionWithTimeOuts(),
+ this.tickTime,
+ self.getMyId(),
+ self.areLocalSessionsEnabled(),
+ getZooKeeperServerListener());
+ }
+
+ @Override
+ protected void revalidateSession(ServerCnxn cnxn, long sessionId, int sessionTimeout) throws IOException {
+ if (upgradeableSessionTracker.isLocalSession(sessionId)) {
+ super.revalidateSession(cnxn, sessionId, sessionTimeout);
+ } else {
+ getLearner().validateSession(cnxn, sessionId, sessionTimeout);
+ }
+ }
+
+ @Override
+ protected void registerJMX() {
+ // register with JMX
+ try {
+ jmxDataTreeBean = new DataTreeBean(getZKDatabase().getDataTree());
+ MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxDataTreeBean = null;
+ }
+ }
+
+ public void registerJMX(ZooKeeperServerBean serverBean, LocalPeerBean localPeerBean) {
+ // register with JMX
+ if (self.jmxLeaderElectionBean != null) {
+ try {
+ MBeanRegistry.getInstance().unregister(self.jmxLeaderElectionBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ }
+ self.jmxLeaderElectionBean = null;
+ }
+
+ try {
+ jmxServerBean = serverBean;
+ MBeanRegistry.getInstance().register(serverBean, localPeerBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxServerBean = null;
+ }
+ }
+
+ @Override
+ protected void unregisterJMX() {
+ // unregister from JMX
+ try {
+ if (jmxDataTreeBean != null) {
+ MBeanRegistry.getInstance().unregister(jmxDataTreeBean);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ jmxDataTreeBean = null;
+ }
+
+ protected void unregisterJMX(Learner peer) {
+ // unregister from JMX
+ try {
+ if (jmxServerBean != null) {
+ MBeanRegistry.getInstance().unregister(jmxServerBean);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ jmxServerBean = null;
+ }
+
+ @Override
+ public synchronized void shutdown(boolean fullyShutDown) {
+ if (!canShutdown()) {
+ LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!");
+ } else {
+ LOG.info("Shutting down");
+ try {
+ if (syncProcessor != null) {
+ // Shutting down the syncProcessor here, first, ensures queued transactions here are written to
+ // permanent storage, which ensures that crash recovery data is consistent with what is used for a
+ // leader election immediately following shutdown, because of the old leader going down; and also
+ // that any state on its way to being written is also loaded in the potential call to
+ // fast-forward-from-edits, in super.shutdown(...), so we avoid getting a DIFF from the new leader
+ // that contains entries we have already written to our transaction log.
+ syncProcessor.shutdown();
+ }
+ } catch (Exception e) {
+ LOG.warn("Ignoring unexpected exception in syncprocessor shutdown", e);
+ }
+ }
+ try {
+ super.shutdown(fullyShutDown);
+ } catch (Exception e) {
+ LOG.warn("Ignoring unexpected exception during shutdown", e);
+ }
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java
new file mode 100644
index 00000000000..1a44a98e6e7
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.BiConsumer;
+import org.apache.zookeeper.server.FinalRequestProcessor;
+import org.apache.zookeeper.server.Request;
+import org.apache.zookeeper.server.RequestProcessor;
+import org.apache.zookeeper.server.SyncRequestProcessor;
+import org.apache.zookeeper.server.ZKDatabase;
+import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A ZooKeeperServer for the Observer node type. Not much is different, but
+ * we anticipate specializing the request processors in the future.
+ *
+ */
+public class ObserverZooKeeperServer extends LearnerZooKeeperServer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ObserverZooKeeperServer.class);
+
+ /**
+ * Enable since request processor for writing txnlog to disk and
+ * take periodic snapshot. Default is ON.
+ */
+
+ private boolean syncRequestProcessorEnabled = this.self.getSyncEnabled();
+
+ /*
+ * Pending sync requests
+ */ ConcurrentLinkedQueue<Request> pendingSyncs = new ConcurrentLinkedQueue<>();
+
+ ObserverZooKeeperServer(FileTxnSnapLog logFactory, QuorumPeer self, ZKDatabase zkDb) throws IOException {
+ super(logFactory, self.tickTime, self.minSessionTimeout, self.maxSessionTimeout, self.clientPortListenBacklog, zkDb, self);
+ LOG.info("syncEnabled ={}", syncRequestProcessorEnabled);
+ }
+
+ public Observer getObserver() {
+ return self.observer;
+ }
+
+ @Override
+ public Learner getLearner() {
+ return self.observer;
+ }
+
+ /**
+ * Unlike a Follower, which sees a full request only during the PROPOSAL
+ * phase, Observers get all the data required with the INFORM packet.
+ * This method commits a request that has been unpacked by from an INFORM
+ * received from the Leader.
+ *
+ * @param request
+ */
+ public void commitRequest(Request request) {
+ if (syncProcessor != null) {
+ // Write to txnlog and take periodic snapshot
+ syncProcessor.processRequest(request);
+ }
+ commitProcessor.commit(request);
+ }
+
+ /**
+ * Set up the request processors for an Observer:
+ * firstProcesor-&gt;commitProcessor-&gt;finalProcessor
+ */
+ @Override
+ protected void setupRequestProcessors() {
+ // We might consider changing the processor behaviour of
+ // Observers to, for example, remove the disk sync requirements.
+ // Currently, they behave almost exactly the same as followers.
+ RequestProcessor finalProcessor = new FinalRequestProcessor(this);
+ commitProcessor = new CommitProcessor(finalProcessor, Long.toString(getServerId()), true, getZooKeeperServerListener());
+ commitProcessor.start();
+ firstProcessor = new ObserverRequestProcessor(this, commitProcessor);
+ ((ObserverRequestProcessor) firstProcessor).start();
+
+ /*
+ * Observer should write to disk, so that the it won't request
+ * too old txn from the leader which may lead to getting an entire
+ * snapshot.
+ *
+ * However, this may degrade performance as it has to write to disk
+ * and do periodic snapshot which may double the memory requirements
+ */
+ if (syncRequestProcessorEnabled) {
+ syncProcessor = new SyncRequestProcessor(this, null);
+ syncProcessor.start();
+ }
+ }
+
+ /*
+ * Process a sync request
+ */
+ public synchronized void sync() {
+ if (pendingSyncs.size() == 0) {
+ LOG.warn("Not expecting a sync.");
+ return;
+ }
+
+ Request r = pendingSyncs.remove();
+ commitProcessor.commit(r);
+ }
+
+ @Override
+ public String getState() {
+ return "observer";
+ }
+
+ @Override
+ public void dumpMonitorValues(BiConsumer<String, Object> response) {
+ super.dumpMonitorValues(response);
+ response.accept("observer_master_id", getObserver().getLearnerMasterId());
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java
new file mode 100644
index 00000000000..f6fc87d7716
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeer.java
@@ -0,0 +1,2711 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import static org.apache.zookeeper.common.NetUtils.formatInetAddr;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import javax.security.sasl.SaslException;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.zookeeper.KeeperException.BadArgumentsException;
+import org.apache.zookeeper.common.AtomicFileOutputStream;
+import org.apache.zookeeper.common.AtomicFileWritingIdiom;
+import org.apache.zookeeper.common.AtomicFileWritingIdiom.WriterStatement;
+import org.apache.zookeeper.common.QuorumX509Util;
+import org.apache.zookeeper.common.Time;
+import org.apache.zookeeper.common.X509Exception;
+import org.apache.zookeeper.jmx.MBeanRegistry;
+import org.apache.zookeeper.jmx.ZKMBeanInfo;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.apache.zookeeper.server.ServerCnxnFactory;
+import org.apache.zookeeper.server.ServerMetrics;
+import org.apache.zookeeper.server.ZKDatabase;
+import org.apache.zookeeper.server.ZooKeeperServer;
+import org.apache.zookeeper.server.ZooKeeperThread;
+import org.apache.zookeeper.server.admin.AdminServer;
+import org.apache.zookeeper.server.admin.AdminServer.AdminServerException;
+import org.apache.zookeeper.server.admin.AdminServerFactory;
+import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
+import org.apache.zookeeper.server.quorum.auth.NullQuorumAuthLearner;
+import org.apache.zookeeper.server.quorum.auth.NullQuorumAuthServer;
+import org.apache.zookeeper.server.quorum.auth.QuorumAuth;
+import org.apache.zookeeper.server.quorum.auth.QuorumAuthLearner;
+import org.apache.zookeeper.server.quorum.auth.QuorumAuthServer;
+import org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthLearner;
+import org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthServer;
+import org.apache.zookeeper.server.quorum.flexible.QuorumMaj;
+import org.apache.zookeeper.server.quorum.flexible.QuorumOracleMaj;
+import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
+import org.apache.zookeeper.server.util.ConfigUtils;
+import org.apache.zookeeper.server.util.JvmPauseMonitor;
+import org.apache.zookeeper.server.util.ZxidUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class manages the quorum protocol. There are three states this server
+ * can be in:
+ * <ol>
+ * <li>Leader election - each server will elect a leader (proposing itself as a
+ * leader initially).</li>
+ * <li>Follower - the server will synchronize with the leader and replicate any
+ * transactions.</li>
+ * <li>Leader - the server will process requests and forward them to followers.
+ * A majority of followers must log the request before it can be accepted.
+ * </ol>
+ *
+ * This class will setup a datagram socket that will always respond with its
+ * view of the current leader. The response will take the form of:
+ *
+ * <pre>
+ * int xid;
+ *
+ * long myid;
+ *
+ * long leader_id;
+ *
+ * long leader_zxid;
+ * </pre>
+ *
+ * The request for the current leader will consist solely of an xid: int xid;
+ */
+public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider {
+
+ private static final Logger LOG = LoggerFactory.getLogger(QuorumPeer.class);
+
+ public static final String CONFIG_KEY_KERBEROS_CANONICALIZE_HOST_NAMES = "zookeeper.kerberos.canonicalizeHostNames";
+ public static final String CONFIG_DEFAULT_KERBEROS_CANONICALIZE_HOST_NAMES = "false";
+
+ private QuorumBean jmxQuorumBean;
+ LocalPeerBean jmxLocalPeerBean;
+ private Map<Long, RemotePeerBean> jmxRemotePeerBean;
+ LeaderElectionBean jmxLeaderElectionBean;
+
+ // The QuorumCnxManager is held through an AtomicReference to ensure cross-thread visibility
+ // of updates; see the implementation comment at setLastSeenQuorumVerifier().
+ private AtomicReference<QuorumCnxManager> qcmRef = new AtomicReference<>();
+
+ QuorumAuthServer authServer;
+ QuorumAuthLearner authLearner;
+
+ /**
+ * ZKDatabase is a top level member of quorumpeer
+ * which will be used in all the zookeeperservers
+ * instantiated later. Also, it is created once on
+ * bootup and only thrown away in case of a truncate
+ * message from the leader
+ */
+ private ZKDatabase zkDb;
+
+ private JvmPauseMonitor jvmPauseMonitor;
+
+ private final AtomicBoolean suspended = new AtomicBoolean(false);
+
+ public static final class AddressTuple {
+
+ public final MultipleAddresses quorumAddr;
+ public final MultipleAddresses electionAddr;
+ public final InetSocketAddress clientAddr;
+
+ public AddressTuple(MultipleAddresses quorumAddr, MultipleAddresses electionAddr, InetSocketAddress clientAddr) {
+ this.quorumAddr = quorumAddr;
+ this.electionAddr = electionAddr;
+ this.clientAddr = clientAddr;
+ }
+
+ }
+
+ private int observerMasterPort;
+
+ public int getObserverMasterPort() {
+ return observerMasterPort;
+ }
+
+ public void setObserverMasterPort(int observerMasterPort) {
+ this.observerMasterPort = observerMasterPort;
+ }
+
+ public static final String CONFIG_KEY_MULTI_ADDRESS_ENABLED = "zookeeper.multiAddress.enabled";
+ public static final String CONFIG_DEFAULT_MULTI_ADDRESS_ENABLED = "false";
+
+ private boolean multiAddressEnabled = true;
+ public boolean isMultiAddressEnabled() {
+ return multiAddressEnabled;
+ }
+
+ public void setMultiAddressEnabled(boolean multiAddressEnabled) {
+ this.multiAddressEnabled = multiAddressEnabled;
+ LOG.info("multiAddress.enabled set to {}", multiAddressEnabled);
+ }
+
+ public static final String CONFIG_KEY_MULTI_ADDRESS_REACHABILITY_CHECK_TIMEOUT_MS = "zookeeper.multiAddress.reachabilityCheckTimeoutMs";
+
+ private int multiAddressReachabilityCheckTimeoutMs = (int) MultipleAddresses.DEFAULT_TIMEOUT.toMillis();
+ public int getMultiAddressReachabilityCheckTimeoutMs() {
+ return multiAddressReachabilityCheckTimeoutMs;
+ }
+
+ public void setMultiAddressReachabilityCheckTimeoutMs(int multiAddressReachabilityCheckTimeoutMs) {
+ this.multiAddressReachabilityCheckTimeoutMs = multiAddressReachabilityCheckTimeoutMs;
+ LOG.info("multiAddress.reachabilityCheckTimeoutMs set to {}", multiAddressReachabilityCheckTimeoutMs);
+ }
+
+ public static final String CONFIG_KEY_MULTI_ADDRESS_REACHABILITY_CHECK_ENABLED = "zookeeper.multiAddress.reachabilityCheckEnabled";
+
+ private boolean multiAddressReachabilityCheckEnabled = true;
+
+ public boolean isMultiAddressReachabilityCheckEnabled() {
+ return multiAddressReachabilityCheckEnabled;
+ }
+
+ public void setMultiAddressReachabilityCheckEnabled(boolean multiAddressReachabilityCheckEnabled) {
+ this.multiAddressReachabilityCheckEnabled = multiAddressReachabilityCheckEnabled;
+ LOG.info("multiAddress.reachabilityCheckEnabled set to {}", multiAddressReachabilityCheckEnabled);
+ }
+
+ public static class QuorumServer {
+
+ public MultipleAddresses addr = new MultipleAddresses();
+
+ public MultipleAddresses electionAddr = new MultipleAddresses();
+
+ public InetSocketAddress clientAddr = null;
+
+ public long id;
+
+ public String hostname;
+
+ public LearnerType type = LearnerType.PARTICIPANT;
+
+ public boolean isClientAddrFromStatic = false;
+
+ private List<InetSocketAddress> myAddrs;
+
+ public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr, InetSocketAddress clientAddr) {
+ this(id, addr, electionAddr, clientAddr, LearnerType.PARTICIPANT);
+ }
+
+ public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr) {
+ this(id, addr, electionAddr, null, LearnerType.PARTICIPANT);
+ }
+
+ // VisibleForTesting
+ public QuorumServer(long id, InetSocketAddress addr) {
+ this(id, addr, null, null, LearnerType.PARTICIPANT);
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ /**
+ * Performs a DNS lookup for server address and election address.
+ *
+ * If the DNS lookup fails, this.addr and electionAddr remain
+ * unmodified.
+ */
+ public void recreateSocketAddresses() {
+ if (this.addr.isEmpty()) {
+ LOG.warn("Server address has not been initialized");
+ return;
+ }
+ if (this.electionAddr.isEmpty()) {
+ LOG.warn("Election address has not been initialized");
+ return;
+ }
+ this.addr.recreateSocketAddresses();
+ this.electionAddr.recreateSocketAddresses();
+ }
+
+ private LearnerType getType(String s) throws ConfigException {
+ switch (s.trim().toLowerCase()) {
+ case "observer":
+ return LearnerType.OBSERVER;
+ case "participant":
+ return LearnerType.PARTICIPANT;
+ default:
+ throw new ConfigException("Unrecognised peertype: " + s);
+ }
+ }
+
+ public QuorumServer(long sid, String addressStr) throws ConfigException {
+ this(sid, addressStr, QuorumServer::getInetAddress);
+ }
+
+ QuorumServer(long sid, String addressStr, Function<InetSocketAddress, InetAddress> getInetAddress) throws ConfigException {
+ this.id = sid;
+ initializeWithAddressString(addressStr, getInetAddress);
+ }
+
+ public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr, LearnerType type) {
+ this(id, addr, electionAddr, null, type);
+ }
+
+ public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr, InetSocketAddress clientAddr, LearnerType type) {
+ this.id = id;
+ if (addr != null) {
+ this.addr.addAddress(addr);
+ }
+ if (electionAddr != null) {
+ this.electionAddr.addAddress(electionAddr);
+ }
+ this.type = type;
+ this.clientAddr = clientAddr;
+
+ setMyAddrs();
+ }
+
+ private static final String wrongFormat =
+ " does not have the form server_config or server_config;client_config"
+ + " where server_config is the pipe separated list of host:port:port or host:port:port:type"
+ + " and client_config is port or host:port";
+
+ private void initializeWithAddressString(String addressStr, Function<InetSocketAddress, InetAddress> getInetAddress) throws ConfigException {
+ LearnerType newType = null;
+ String[] serverClientParts = addressStr.split(";");
+ String[] serverAddresses = serverClientParts[0].split("\\|");
+
+ if (serverClientParts.length == 2) {
+ String[] clientParts = ConfigUtils.getHostAndPort(serverClientParts[1]);
+ if (clientParts.length > 2) {
+ throw new ConfigException(addressStr + wrongFormat);
+ }
+
+ // is client_config a host:port or just a port
+ String clientHostName = (clientParts.length == 2) ? clientParts[0] : "0.0.0.0";
+ try {
+ clientAddr = new InetSocketAddress(clientHostName, Integer.parseInt(clientParts[clientParts.length - 1]));
+ } catch (NumberFormatException e) {
+ throw new ConfigException("Address unresolved: " + hostname + ":" + clientParts[clientParts.length - 1]);
+ }
+ }
+
+ boolean multiAddressEnabled = Boolean.parseBoolean(
+ System.getProperty(QuorumPeer.CONFIG_KEY_MULTI_ADDRESS_ENABLED, QuorumPeer.CONFIG_DEFAULT_MULTI_ADDRESS_ENABLED));
+ if (!multiAddressEnabled && serverAddresses.length > 1) {
+ throw new ConfigException("Multiple address feature is disabled, but multiple addresses were specified for sid " + this.id);
+ }
+
+ boolean canonicalize = Boolean.parseBoolean(
+ System.getProperty(
+ CONFIG_KEY_KERBEROS_CANONICALIZE_HOST_NAMES,
+ CONFIG_DEFAULT_KERBEROS_CANONICALIZE_HOST_NAMES));
+
+ for (String serverAddress : serverAddresses) {
+ String serverParts[] = ConfigUtils.getHostAndPort(serverAddress);
+ if ((serverClientParts.length > 2) || (serverParts.length < 3)
+ || (serverParts.length > 4)) {
+ throw new ConfigException(addressStr + wrongFormat);
+ }
+
+ String serverHostName = serverParts[0];
+
+ // server_config should be either host:port:port or host:port:port:type
+ InetSocketAddress tempAddress;
+ InetSocketAddress tempElectionAddress;
+ try {
+ tempAddress = new InetSocketAddress(serverHostName, Integer.parseInt(serverParts[1]));
+ addr.addAddress(tempAddress);
+ } catch (NumberFormatException e) {
+ throw new ConfigException("Address unresolved: " + serverHostName + ":" + serverParts[1]);
+ }
+ try {
+ tempElectionAddress = new InetSocketAddress(serverHostName, Integer.parseInt(serverParts[2]));
+ electionAddr.addAddress(tempElectionAddress);
+ } catch (NumberFormatException e) {
+ throw new ConfigException("Address unresolved: " + serverHostName + ":" + serverParts[2]);
+ }
+
+ if (tempAddress.getPort() == tempElectionAddress.getPort()) {
+ throw new ConfigException("Client and election port must be different! Please update the "
+ + "configuration file on server." + this.id);
+ }
+
+ if (canonicalize) {
+ InetAddress ia = getInetAddress.apply(tempAddress);
+ if (ia == null) {
+ throw new ConfigException("Unable to canonicalize address " + serverHostName + " because it's not resolvable");
+ }
+
+ String canonicalHostName = ia.getCanonicalHostName();
+
+ if (!canonicalHostName.equals(serverHostName)
+ // Avoid using literal IP address when
+ // security check fails
+ && !canonicalHostName.equals(ia.getHostAddress())) {
+ LOG.info("Host name for quorum server {} "
+ + "canonicalized from {} to {}",
+ this.id, serverHostName, canonicalHostName);
+ serverHostName = canonicalHostName;
+ }
+ }
+
+ if (serverParts.length == 4) {
+ LearnerType tempType = getType(serverParts[3]);
+ if (newType == null) {
+ newType = tempType;
+ }
+
+ if (newType != tempType) {
+ throw new ConfigException("Multiple addresses should have similar roles: " + type + " vs " + tempType);
+ }
+ }
+
+ this.hostname = serverHostName;
+ }
+
+ if (newType != null) {
+ type = newType;
+ }
+
+ setMyAddrs();
+ }
+
+ private static InetAddress getInetAddress(InetSocketAddress addr) {
+ return addr.getAddress();
+ }
+
+ private void setMyAddrs() {
+ this.myAddrs = new ArrayList<>();
+ this.myAddrs.addAll(this.addr.getAllAddresses());
+ this.myAddrs.add(this.clientAddr);
+ this.myAddrs.addAll(this.electionAddr.getAllAddresses());
+ this.myAddrs = excludedSpecialAddresses(this.myAddrs);
+ }
+
+ public static String delimitedHostString(InetSocketAddress addr) {
+ String host = addr.getHostString();
+ if (host.contains(":")) {
+ return "[" + host + "]";
+ } else {
+ return host;
+ }
+ }
+
+ public String toString() {
+ StringWriter sw = new StringWriter();
+
+ List<InetSocketAddress> addrList = new LinkedList<>(addr.getAllAddresses());
+ List<InetSocketAddress> electionAddrList = new LinkedList<>(electionAddr.getAllAddresses());
+
+ if (addrList.size() > 0 && electionAddrList.size() > 0) {
+ addrList.sort(Comparator.comparing(InetSocketAddress::getHostString));
+ electionAddrList.sort(Comparator.comparing(InetSocketAddress::getHostString));
+ sw.append(IntStream.range(0, addrList.size()).mapToObj(i -> String.format("%s:%d:%d",
+ delimitedHostString(addrList.get(i)), addrList.get(i).getPort(), electionAddrList.get(i).getPort()))
+ .collect(Collectors.joining("|")));
+ }
+
+ if (type == LearnerType.OBSERVER) {
+ sw.append(":observer");
+ } else if (type == LearnerType.PARTICIPANT) {
+ sw.append(":participant");
+ }
+
+ if (clientAddr != null && !isClientAddrFromStatic) {
+ sw.append(";");
+ sw.append(delimitedHostString(clientAddr));
+ sw.append(":");
+ sw.append(String.valueOf(clientAddr.getPort()));
+ }
+
+ return sw.toString();
+ }
+
+ public int hashCode() {
+ assert false : "hashCode not designed";
+ return 42; // any arbitrary constant will do
+ }
+
+ private boolean checkAddressesEqual(InetSocketAddress addr1, InetSocketAddress addr2) {
+ return (addr1 != null || addr2 == null)
+ && (addr1 == null || addr2 != null)
+ && (addr1 == null || addr2 == null || addr1.equals(addr2));
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof QuorumServer)) {
+ return false;
+ }
+ QuorumServer qs = (QuorumServer) o;
+ if ((qs.id != id) || (qs.type != type)) {
+ return false;
+ }
+ if (!addr.equals(qs.addr)) {
+ return false;
+ }
+ if (!electionAddr.equals(qs.electionAddr)) {
+ return false;
+ }
+ return checkAddressesEqual(clientAddr, qs.clientAddr);
+ }
+
+ public void checkAddressDuplicate(QuorumServer s) throws BadArgumentsException {
+ List<InetSocketAddress> otherAddrs = new ArrayList<>(s.addr.getAllAddresses());
+ otherAddrs.add(s.clientAddr);
+ otherAddrs.addAll(s.electionAddr.getAllAddresses());
+ otherAddrs = excludedSpecialAddresses(otherAddrs);
+
+ for (InetSocketAddress my : this.myAddrs) {
+
+ for (InetSocketAddress other : otherAddrs) {
+ if (my.equals(other)) {
+ String error = String.format("%s of server.%d conflicts %s of server.%d", my, this.id, other, s.id);
+ throw new BadArgumentsException(error);
+ }
+ }
+ }
+ }
+
+ private List<InetSocketAddress> excludedSpecialAddresses(List<InetSocketAddress> addrs) {
+ List<InetSocketAddress> included = new ArrayList<>();
+
+ for (InetSocketAddress addr : addrs) {
+ if (addr == null) {
+ continue;
+ }
+ InetAddress inetaddr = addr.getAddress();
+
+ if (inetaddr == null || inetaddr.isAnyLocalAddress() // wildCard addresses (0.0.0.0 or [::])
+ || inetaddr.isLoopbackAddress()) { // loopback address(localhost/127.0.0.1)
+ continue;
+ }
+ included.add(addr);
+ }
+ return included;
+ }
+
+ }
+
+ public enum ServerState {
+ LOOKING,
+ FOLLOWING,
+ LEADING,
+ OBSERVING
+ }
+
+ /**
+ * (Used for monitoring) shows the current phase of
+ * Zab protocol that peer is running.
+ */
+ public enum ZabState {
+ ELECTION,
+ DISCOVERY,
+ SYNCHRONIZATION,
+ BROADCAST
+ }
+
+ /**
+ * (Used for monitoring) When peer is in synchronization phase, this shows
+ * which synchronization mechanism is being used
+ */
+ public enum SyncMode {
+ NONE,
+ DIFF,
+ SNAP,
+ TRUNC
+ }
+
+ /*
+ * A peer can either be participating, which implies that it is willing to
+ * both vote in instances of consensus and to elect or become a Leader, or
+ * it may be observing in which case it isn't.
+ *
+ * We need this distinction to decide which ServerState to move to when
+ * conditions change (e.g. which state to become after LOOKING).
+ */
+ public enum LearnerType {
+ PARTICIPANT,
+ OBSERVER
+ }
+
+ /*
+ * To enable observers to have no identifier, we need a generic identifier
+ * at least for QuorumCnxManager. We use the following constant to as the
+ * value of such a generic identifier.
+ */
+
+ static final long OBSERVER_ID = Long.MAX_VALUE;
+
+ /*
+ * Record leader election time
+ */
+ public long start_fle, end_fle; // fle = fast leader election
+ public static final String FLE_TIME_UNIT = "MS";
+ private long unavailableStartTime;
+
+ /*
+ * Default value of peer is participant
+ */
+ private LearnerType learnerType = LearnerType.PARTICIPANT;
+
+ public LearnerType getLearnerType() {
+ return learnerType;
+ }
+
+ /**
+ * Sets the LearnerType
+ */
+ public void setLearnerType(LearnerType p) {
+ learnerType = p;
+ }
+
+ protected synchronized void setConfigFileName(String s) {
+ configFilename = s;
+ }
+
+ private String configFilename = null;
+
+ public int getQuorumSize() {
+ return getVotingView().size();
+ }
+
+ public void setJvmPauseMonitor(JvmPauseMonitor jvmPauseMonitor) {
+ this.jvmPauseMonitor = jvmPauseMonitor;
+ }
+
+ /**
+ * QuorumVerifier implementation; default (majority).
+ */
+
+ //last committed quorum verifier
+ private QuorumVerifier quorumVerifier;
+
+ //last proposed quorum verifier
+ private QuorumVerifier lastSeenQuorumVerifier = null;
+
+ // Lock object that guard access to quorumVerifier and lastSeenQuorumVerifier.
+ final Object QV_LOCK = new Object();
+
+ /**
+ * My id
+ */
+ private long myid;
+
+ /**
+ * get the id of this quorum peer.
+ */
+ public long getMyId() {
+ return myid;
+ }
+
+ // VisibleForTesting
+ void setId(long id) {
+ this.myid = id;
+ }
+
+ private boolean sslQuorum;
+ private boolean shouldUsePortUnification;
+
+ public boolean isSslQuorum() {
+ return sslQuorum;
+ }
+
+ public boolean shouldUsePortUnification() {
+ return shouldUsePortUnification;
+ }
+
+ private final QuorumX509Util x509Util;
+
+ QuorumX509Util getX509Util() {
+ return x509Util;
+ }
+
+ /**
+ * This is who I think the leader currently is.
+ */
+ private volatile Vote currentVote;
+
+ public synchronized Vote getCurrentVote() {
+ return currentVote;
+ }
+
+ public synchronized void setCurrentVote(Vote v) {
+ currentVote = v;
+ }
+
+ private volatile boolean running = true;
+
+ private String initialConfig;
+
+ /**
+ * The number of milliseconds of each tick
+ */
+ protected int tickTime;
+
+ /**
+ * Whether learners in this quorum should create new sessions as local.
+ * False by default to preserve existing behavior.
+ */
+ protected boolean localSessionsEnabled = false;
+
+ /**
+ * Whether learners in this quorum should upgrade local sessions to
+ * global. Only matters if local sessions are enabled.
+ */
+ protected boolean localSessionsUpgradingEnabled = true;
+
+ /**
+ * Minimum number of milliseconds to allow for session timeout.
+ * A value of -1 indicates unset, use default.
+ */
+ protected int minSessionTimeout = -1;
+
+ /**
+ * Maximum number of milliseconds to allow for session timeout.
+ * A value of -1 indicates unset, use default.
+ */
+ protected int maxSessionTimeout = -1;
+
+ /**
+ * The ZooKeeper server's socket backlog length. The number of connections
+ * that will be queued to be read before new connections are dropped. A
+ * value of one indicates the default backlog will be used.
+ */
+ protected int clientPortListenBacklog = -1;
+
+ /**
+ * The number of ticks that the initial synchronization phase can take
+ */
+ protected volatile int initLimit;
+
+ /**
+ * The number of ticks that can pass between sending a request and getting
+ * an acknowledgment
+ */
+ protected volatile int syncLimit;
+
+ /**
+ * The number of ticks that can pass before retrying to connect to learner master
+ */
+ protected volatile int connectToLearnerMasterLimit;
+
+ /**
+ * Enables/Disables sync request processor. This option is enabled
+ * by default and is to be used with observers.
+ */
+ protected boolean syncEnabled = true;
+
+ /**
+ * The current tick
+ */
+ protected AtomicInteger tick = new AtomicInteger();
+
+ /**
+ * Whether or not to listen on all IPs for the two quorum ports
+ * (broadcast and fast leader election).
+ */
+ protected boolean quorumListenOnAllIPs = false;
+
+ /**
+ * Keeps time taken for leader election in milliseconds. Sets the value to
+ * this variable only after the completion of leader election.
+ */
+ private long electionTimeTaken = -1;
+
+ /**
+ * Enable/Disables quorum authentication using sasl. Defaulting to false.
+ */
+ protected boolean quorumSaslEnableAuth;
+
+ /**
+ * If this is false, quorum peer server will accept another quorum peer client
+ * connection even if the authentication did not succeed. This can be used while
+ * upgrading ZooKeeper server. Defaulting to false (required).
+ */
+ protected boolean quorumServerSaslAuthRequired;
+
+ /**
+ * If this is false, quorum peer learner will talk to quorum peer server
+ * without authentication. This can be used while upgrading ZooKeeper
+ * server. Defaulting to false (required).
+ */
+ protected boolean quorumLearnerSaslAuthRequired;
+
+ /**
+ * Kerberos quorum service principal. Defaulting to 'zkquorum/localhost'.
+ */
+ protected String quorumServicePrincipal;
+
+ /**
+ * Quorum learner login context name in jaas-conf file to read the kerberos
+ * security details. Defaulting to 'QuorumLearner'.
+ */
+ protected String quorumLearnerLoginContext;
+
+ /**
+ * Quorum server login context name in jaas-conf file to read the kerberos
+ * security details. Defaulting to 'QuorumServer'.
+ */
+ protected String quorumServerLoginContext;
+
+ // TODO: need to tune the default value of thread size
+ private static final int QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE = 20;
+ /**
+ * The maximum number of threads to allow in the connectionExecutors thread
+ * pool which will be used to initiate quorum server connections.
+ */
+ protected int quorumCnxnThreadsSize = QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE;
+
+ public static final String QUORUM_CNXN_TIMEOUT_MS = "zookeeper.quorumCnxnTimeoutMs";
+ private static int quorumCnxnTimeoutMs;
+
+ static {
+ quorumCnxnTimeoutMs = Integer.getInteger(QUORUM_CNXN_TIMEOUT_MS, -1);
+ LOG.info("{}={}", QUORUM_CNXN_TIMEOUT_MS, quorumCnxnTimeoutMs);
+ }
+
+ /**
+ * @deprecated As of release 3.4.0, this class has been deprecated, since
+ * it is used with one of the udp-based versions of leader election, which
+ * we are also deprecating.
+ *
+ * This class simply responds to requests for the current leader of this
+ * node.
+ * <p>
+ * The request contains just an xid generated by the requestor.
+ * <p>
+ * The response has the xid, the id of this server, the id of the leader,
+ * and the zxid of the leader.
+ *
+ *
+ */
+ @Deprecated
+ class ResponderThread extends ZooKeeperThread {
+
+ ResponderThread() {
+ super("ResponderThread");
+ }
+
+ volatile boolean running = true;
+
+ @Override
+ public void run() {
+ try {
+ byte[] b = new byte[36];
+ ByteBuffer responseBuffer = ByteBuffer.wrap(b);
+ DatagramPacket packet = new DatagramPacket(b, b.length);
+ while (running) {
+ udpSocket.receive(packet);
+ if (packet.getLength() != 4) {
+ LOG.warn("Got more than just an xid! Len = {}", packet.getLength());
+ } else {
+ responseBuffer.clear();
+ responseBuffer.getInt(); // Skip the xid
+ responseBuffer.putLong(myid);
+ Vote current = getCurrentVote();
+ switch (getPeerState()) {
+ case LOOKING:
+ responseBuffer.putLong(current.getId());
+ responseBuffer.putLong(current.getZxid());
+ break;
+ case LEADING:
+ responseBuffer.putLong(myid);
+ try {
+ long proposed;
+ synchronized (leader) {
+ proposed = leader.lastProposed;
+ }
+ responseBuffer.putLong(proposed);
+ } catch (NullPointerException npe) {
+ // This can happen in state transitions,
+ // just ignore the request
+ }
+ break;
+ case FOLLOWING:
+ responseBuffer.putLong(current.getId());
+ try {
+ responseBuffer.putLong(follower.getZxid());
+ } catch (NullPointerException npe) {
+ // This can happen in state transitions,
+ // just ignore the request
+ }
+ break;
+ case OBSERVING:
+ // Do nothing, Observers keep themselves to
+ // themselves.
+ break;
+ }
+ packet.setData(b);
+ udpSocket.send(packet);
+ }
+ packet.setLength(b.length);
+ }
+ } catch (RuntimeException e) {
+ LOG.warn("Unexpected runtime exception in ResponderThread", e);
+ } catch (IOException e) {
+ LOG.warn("Unexpected IO exception in ResponderThread", e);
+ } finally {
+ LOG.warn("QuorumPeer responder thread exited");
+ }
+ }
+
+ }
+
+ private ServerState state = ServerState.LOOKING;
+
+ private AtomicReference<ZabState> zabState = new AtomicReference<>(ZabState.ELECTION);
+ private AtomicReference<SyncMode> syncMode = new AtomicReference<>(SyncMode.NONE);
+ private AtomicReference<String> leaderAddress = new AtomicReference<>("");
+ private AtomicLong leaderId = new AtomicLong(-1);
+
+ private boolean reconfigFlag = false; // indicates that a reconfig just committed
+
+ public synchronized void setPeerState(ServerState newState) {
+ state = newState;
+ if (newState == ServerState.LOOKING) {
+ setLeaderAddressAndId(null, -1);
+ setZabState(ZabState.ELECTION);
+ } else {
+ LOG.info("Peer state changed: {}", getDetailedPeerState());
+ }
+ }
+
+ public void setZabState(ZabState zabState) {
+ if ((zabState == ZabState.BROADCAST) && (unavailableStartTime != 0)) {
+ long unavailableTime = Time.currentElapsedTime() - unavailableStartTime;
+ ServerMetrics.getMetrics().UNAVAILABLE_TIME.add(unavailableTime);
+ if (getPeerState() == ServerState.LEADING) {
+ ServerMetrics.getMetrics().LEADER_UNAVAILABLE_TIME.add(unavailableTime);
+ }
+ unavailableStartTime = 0;
+ }
+ this.zabState.set(zabState);
+ LOG.info("Peer state changed: {}", getDetailedPeerState());
+ }
+
+ public void setSyncMode(SyncMode syncMode) {
+ this.syncMode.set(syncMode);
+ LOG.info("Peer state changed: {}", getDetailedPeerState());
+ }
+
+ public ZabState getZabState() {
+ return zabState.get();
+ }
+
+ public SyncMode getSyncMode() {
+ return syncMode.get();
+ }
+
+ public void setLeaderAddressAndId(MultipleAddresses addr, long newId) {
+ if (addr != null) {
+ leaderAddress.set(String.join("|", addr.getAllHostStrings()));
+ } else {
+ leaderAddress.set(null);
+ }
+ leaderId.set(newId);
+ }
+
+ public String getLeaderAddress() {
+ return leaderAddress.get();
+ }
+
+ public long getLeaderId() {
+ return leaderId.get();
+ }
+
+ public String getDetailedPeerState() {
+ final StringBuilder sb = new StringBuilder(getPeerState().toString().toLowerCase());
+ final ZabState zabState = getZabState();
+ if (!ZabState.ELECTION.equals(zabState)) {
+ sb.append(" - ").append(zabState.toString().toLowerCase());
+ }
+ final SyncMode syncMode = getSyncMode();
+ if (!SyncMode.NONE.equals(syncMode)) {
+ sb.append(" - ").append(syncMode.toString().toLowerCase());
+ }
+ return sb.toString();
+ }
+
+ public synchronized void reconfigFlagSet() {
+ reconfigFlag = true;
+ }
+ public synchronized void reconfigFlagClear() {
+ reconfigFlag = false;
+ }
+ public synchronized boolean isReconfigStateChange() {
+ return reconfigFlag;
+ }
+ public synchronized ServerState getPeerState() {
+ return state;
+ }
+
+ DatagramSocket udpSocket;
+
+ private final AtomicReference<AddressTuple> myAddrs = new AtomicReference<>();
+
+ /**
+ * Resolves hostname for a given server ID.
+ *
+ * This method resolves hostname for a given server ID in both quorumVerifer
+ * and lastSeenQuorumVerifier. If the server ID matches the local server ID,
+ * it also updates myAddrs.
+ */
+ public void recreateSocketAddresses(long id) {
+ QuorumVerifier qv = getQuorumVerifier();
+ if (qv != null) {
+ QuorumServer qs = qv.getAllMembers().get(id);
+ if (qs != null) {
+ qs.recreateSocketAddresses();
+ if (id == getMyId()) {
+ setAddrs(qs.addr, qs.electionAddr, qs.clientAddr);
+ }
+ }
+ }
+ qv = getLastSeenQuorumVerifier();
+ if (qv != null) {
+ QuorumServer qs = qv.getAllMembers().get(id);
+ if (qs != null) {
+ qs.recreateSocketAddresses();
+ }
+ }
+ }
+
+ private AddressTuple getAddrs() {
+ AddressTuple addrs = myAddrs.get();
+ if (addrs != null) {
+ return addrs;
+ }
+ try {
+ synchronized (QV_LOCK) {
+ addrs = myAddrs.get();
+ while (addrs == null) {
+ QV_LOCK.wait();
+ addrs = myAddrs.get();
+ }
+ return addrs;
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
+ }
+
+ public MultipleAddresses getQuorumAddress() {
+ return getAddrs().quorumAddr;
+ }
+
+ public MultipleAddresses getElectionAddress() {
+ return getAddrs().electionAddr;
+ }
+
+ public InetSocketAddress getClientAddress() {
+ final AddressTuple addrs = myAddrs.get();
+ return (addrs == null) ? null : addrs.clientAddr;
+ }
+
+ private void setAddrs(MultipleAddresses quorumAddr, MultipleAddresses electionAddr, InetSocketAddress clientAddr) {
+ synchronized (QV_LOCK) {
+ myAddrs.set(new AddressTuple(quorumAddr, electionAddr, clientAddr));
+ QV_LOCK.notifyAll();
+ }
+ }
+
+ private int electionType;
+
+ Election electionAlg;
+
+ ServerCnxnFactory cnxnFactory;
+ ServerCnxnFactory secureCnxnFactory;
+
+ private FileTxnSnapLog logFactory = null;
+
+ private final QuorumStats quorumStats;
+
+ AdminServer adminServer;
+
+ private final boolean reconfigEnabled;
+
+ public static QuorumPeer testingQuorumPeer() throws SaslException {
+ return new QuorumPeer();
+ }
+
+ public QuorumPeer() throws SaslException {
+ super("QuorumPeer");
+ quorumStats = new QuorumStats(this);
+ jmxRemotePeerBean = new HashMap<>();
+ adminServer = AdminServerFactory.createAdminServer();
+ x509Util = createX509Util();
+ initialize();
+ reconfigEnabled = QuorumPeerConfig.isReconfigEnabled();
+ }
+
+ // VisibleForTesting
+ QuorumX509Util createX509Util() {
+ return new QuorumX509Util();
+ }
+
+ /**
+ * For backward compatibility purposes, we instantiate QuorumMaj by default.
+ */
+
+ public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File dataDir, File dataLogDir, int electionType, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit, ServerCnxnFactory cnxnFactory) throws IOException {
+ this(quorumPeers, dataDir, dataLogDir, electionType, myid, tickTime, initLimit, syncLimit, connectToLearnerMasterLimit, false, cnxnFactory, new QuorumMaj(quorumPeers));
+ }
+
+ public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File dataDir, File dataLogDir, int electionType, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit, boolean quorumListenOnAllIPs, ServerCnxnFactory cnxnFactory, QuorumVerifier quorumConfig) throws IOException {
+ this();
+ this.cnxnFactory = cnxnFactory;
+ this.electionType = electionType;
+ this.myid = myid;
+ this.tickTime = tickTime;
+ this.initLimit = initLimit;
+ this.syncLimit = syncLimit;
+ this.connectToLearnerMasterLimit = connectToLearnerMasterLimit;
+ this.quorumListenOnAllIPs = quorumListenOnAllIPs;
+ this.logFactory = new FileTxnSnapLog(dataLogDir, dataDir);
+ this.zkDb = new ZKDatabase(this.logFactory);
+ if (quorumConfig == null) {
+ quorumConfig = new QuorumMaj(quorumPeers);
+ }
+ setQuorumVerifier(quorumConfig, false);
+ adminServer = AdminServerFactory.createAdminServer();
+ }
+
+ public void initialize() throws SaslException {
+ // init quorum auth server & learner
+ if (isQuorumSaslAuthEnabled()) {
+ Set<String> authzHosts = new HashSet<>();
+ for (QuorumServer qs : getView().values()) {
+ authzHosts.add(qs.hostname);
+ }
+ authServer = new SaslQuorumAuthServer(isQuorumServerSaslAuthRequired(), quorumServerLoginContext, authzHosts);
+ authLearner = new SaslQuorumAuthLearner(isQuorumLearnerSaslAuthRequired(), quorumServicePrincipal, quorumLearnerLoginContext);
+ } else {
+ authServer = new NullQuorumAuthServer();
+ authLearner = new NullQuorumAuthLearner();
+ }
+ }
+
+ QuorumStats quorumStats() {
+ return quorumStats;
+ }
+
+ @Override
+ public synchronized void start() {
+ if (!getView().containsKey(myid)) {
+ throw new RuntimeException("My id " + myid + " not in the peer list");
+ }
+ loadDataBase();
+ startServerCnxnFactory();
+ try {
+ adminServer.start();
+ } catch (AdminServerException e) {
+ LOG.warn("Problem starting AdminServer", e);
+ }
+ startLeaderElection();
+ startJvmPauseMonitor();
+ super.start();
+ }
+
+ private void loadDataBase() {
+ try {
+ zkDb.loadDataBase();
+
+ // load the epochs
+ long lastProcessedZxid = zkDb.getDataTree().lastProcessedZxid;
+ long epochOfZxid = ZxidUtils.getEpochFromZxid(lastProcessedZxid);
+ try {
+ currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME);
+ } catch (FileNotFoundException e) {
+ // pick a reasonable epoch number
+ // this should only happen once when moving to a
+ // new code version
+ currentEpoch = epochOfZxid;
+ LOG.info(
+ "{} not found! Creating with a reasonable default of {}. "
+ + "This should only happen when you are upgrading your installation",
+ CURRENT_EPOCH_FILENAME,
+ currentEpoch);
+ writeLongToFile(CURRENT_EPOCH_FILENAME, currentEpoch);
+ }
+ if (epochOfZxid > currentEpoch) {
+ // acceptedEpoch.tmp file in snapshot directory
+ File currentTmp = new File(getTxnFactory().getSnapDir(),
+ CURRENT_EPOCH_FILENAME + AtomicFileOutputStream.TMP_EXTENSION);
+ if (currentTmp.exists()) {
+ long epochOfTmp = readLongFromFile(currentTmp.getName());
+ LOG.info("{} found. Setting current epoch to {}.", currentTmp, epochOfTmp);
+ setCurrentEpoch(epochOfTmp);
+ } else {
+ throw new IOException(
+ "The current epoch, " + ZxidUtils.zxidToString(currentEpoch)
+ + ", is older than the last zxid, " + lastProcessedZxid);
+ }
+ }
+ try {
+ acceptedEpoch = readLongFromFile(ACCEPTED_EPOCH_FILENAME);
+ } catch (FileNotFoundException e) {
+ // pick a reasonable epoch number
+ // this should only happen once when moving to a
+ // new code version
+ acceptedEpoch = epochOfZxid;
+ LOG.info(
+ "{} not found! Creating with a reasonable default of {}. "
+ + "This should only happen when you are upgrading your installation",
+ ACCEPTED_EPOCH_FILENAME,
+ acceptedEpoch);
+ writeLongToFile(ACCEPTED_EPOCH_FILENAME, acceptedEpoch);
+ }
+ if (acceptedEpoch < currentEpoch) {
+ throw new IOException("The accepted epoch, "
+ + ZxidUtils.zxidToString(acceptedEpoch)
+ + " is less than the current epoch, "
+ + ZxidUtils.zxidToString(currentEpoch));
+ }
+ } catch (IOException ie) {
+ LOG.error("Unable to load database on disk", ie);
+ throw new RuntimeException("Unable to run quorum server ", ie);
+ }
+ }
+
+ ResponderThread responder;
+
+ public synchronized void stopLeaderElection() {
+ responder.running = false;
+ responder.interrupt();
+ }
+ public synchronized void startLeaderElection() {
+ try {
+ if (getPeerState() == ServerState.LOOKING) {
+ currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
+ }
+ } catch (IOException e) {
+ RuntimeException re = new RuntimeException(e.getMessage());
+ re.setStackTrace(e.getStackTrace());
+ throw re;
+ }
+
+ this.electionAlg = createElectionAlgorithm(electionType);
+ }
+
+ private void startJvmPauseMonitor() {
+ if (this.jvmPauseMonitor != null) {
+ this.jvmPauseMonitor.serviceStart();
+ }
+ }
+
+ /**
+ * Count the number of nodes in the map that could be followers.
+ * @param peers
+ * @return The number of followers in the map
+ */
+ protected static int countParticipants(Map<Long, QuorumServer> peers) {
+ int count = 0;
+ for (QuorumServer q : peers.values()) {
+ if (q.type == LearnerType.PARTICIPANT) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * This constructor is only used by the existing unit test code.
+ * It defaults to FileLogProvider persistence provider.
+ */
+ public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File snapDir, File logDir, int clientPort, int electionAlg, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit) throws IOException {
+ this(
+ quorumPeers,
+ snapDir,
+ logDir,
+ electionAlg,
+ myid,
+ tickTime,
+ initLimit,
+ syncLimit,
+ connectToLearnerMasterLimit,
+ false,
+ ServerCnxnFactory.createFactory(getClientAddress(quorumPeers, myid, clientPort), -1),
+ new QuorumMaj(quorumPeers));
+ }
+
+ public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File snapDir, File logDir, int clientPort, int electionAlg, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit, String oraclePath) throws IOException {
+ this(
+ quorumPeers,
+ snapDir,
+ logDir,
+ electionAlg,
+ myid,
+ tickTime,
+ initLimit,
+ syncLimit,
+ connectToLearnerMasterLimit,
+ false,
+ ServerCnxnFactory.createFactory(getClientAddress(quorumPeers, myid, clientPort), -1),
+ new QuorumOracleMaj(quorumPeers, oraclePath));
+ }
+
+ /**
+ * This constructor is only used by the existing unit test code.
+ * It defaults to FileLogProvider persistence provider.
+ */
+ public QuorumPeer(Map<Long, QuorumServer> quorumPeers, File snapDir, File logDir, int clientPort, int electionAlg, long myid, int tickTime, int initLimit, int syncLimit, int connectToLearnerMasterLimit, QuorumVerifier quorumConfig) throws IOException {
+ this(
+ quorumPeers,
+ snapDir,
+ logDir,
+ electionAlg,
+ myid,
+ tickTime,
+ initLimit,
+ syncLimit,
+ connectToLearnerMasterLimit,
+ false,
+ ServerCnxnFactory.createFactory(getClientAddress(quorumPeers, myid, clientPort), -1),
+ quorumConfig);
+ }
+
+ private static InetSocketAddress getClientAddress(Map<Long, QuorumServer> quorumPeers, long myid, int clientPort) throws IOException {
+ QuorumServer quorumServer = quorumPeers.get(myid);
+ if (null == quorumServer) {
+ throw new IOException("No QuorumServer correspoding to myid " + myid);
+ }
+ if (null == quorumServer.clientAddr) {
+ return new InetSocketAddress(clientPort);
+ }
+ if (quorumServer.clientAddr.getPort() != clientPort) {
+ throw new IOException("QuorumServer port "
+ + quorumServer.clientAddr.getPort()
+ + " does not match with given port "
+ + clientPort);
+ }
+ return quorumServer.clientAddr;
+ }
+
+ /**
+ * returns the highest zxid that this host has seen
+ *
+ * @return the highest zxid for this host
+ */
+ public long getLastLoggedZxid() {
+ if (!zkDb.isInitialized()) {
+ loadDataBase();
+ }
+ return zkDb.getDataTreeLastProcessedZxid();
+ }
+
+ public Follower follower;
+ public Leader leader;
+ public Observer observer;
+
+ protected Follower makeFollower(FileTxnSnapLog logFactory) throws IOException {
+ return new Follower(this, new FollowerZooKeeperServer(logFactory, this, this.zkDb));
+ }
+
+ protected Leader makeLeader(FileTxnSnapLog logFactory) throws IOException, X509Exception {
+ return new Leader(this, new LeaderZooKeeperServer(logFactory, this, this.zkDb));
+ }
+
+ protected Observer makeObserver(FileTxnSnapLog logFactory) throws IOException {
+ return new Observer(this, new ObserverZooKeeperServer(logFactory, this, this.zkDb));
+ }
+
+ @SuppressWarnings("deprecation")
+ protected Election createElectionAlgorithm(int electionAlgorithm) {
+ Election le = null;
+
+ //TODO: use a factory rather than a switch
+ switch (electionAlgorithm) {
+ case 1:
+ throw new UnsupportedOperationException("Election Algorithm 1 is not supported.");
+ case 2:
+ throw new UnsupportedOperationException("Election Algorithm 2 is not supported.");
+ case 3:
+ QuorumCnxManager qcm = createCnxnManager();
+ QuorumCnxManager oldQcm = qcmRef.getAndSet(qcm);
+ if (oldQcm != null) {
+ LOG.warn("Clobbering already-set QuorumCnxManager (restarting leader election?)");
+ oldQcm.halt();
+ }
+ QuorumCnxManager.Listener listener = qcm.listener;
+ if (listener != null) {
+ listener.start();
+ FastLeaderElection fle = new FastLeaderElection(this, qcm);
+ fle.start();
+ le = fle;
+ } else {
+ LOG.error("Null listener when initializing cnx manager");
+ }
+ break;
+ default:
+ assert false;
+ }
+ return le;
+ }
+
+ @SuppressWarnings("deprecation")
+ protected Election makeLEStrategy() {
+ LOG.debug("Initializing leader election protocol...");
+ return electionAlg;
+ }
+
+ protected synchronized void setLeader(Leader newLeader) {
+ leader = newLeader;
+ }
+
+ protected synchronized void setFollower(Follower newFollower) {
+ follower = newFollower;
+ }
+
+ protected synchronized void setObserver(Observer newObserver) {
+ observer = newObserver;
+ }
+
+ public synchronized ZooKeeperServer getActiveServer() {
+ if (leader != null) {
+ return leader.zk;
+ } else if (follower != null) {
+ return follower.zk;
+ } else if (observer != null) {
+ return observer.zk;
+ }
+ return null;
+ }
+
+ boolean shuttingDownLE = false;
+
+ public void setSuspended(boolean suspended) {
+ this.suspended.set(suspended);
+ }
+ private void checkSuspended() {
+ try {
+ while (suspended.get()) {
+ Thread.sleep(10);
+ }
+ } catch (InterruptedException err) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ public void run() {
+ updateThreadName();
+
+ LOG.debug("Starting quorum peer");
+ try {
+ jmxQuorumBean = new QuorumBean(this);
+ MBeanRegistry.getInstance().register(jmxQuorumBean, null);
+ for (QuorumServer s : getView().values()) {
+ ZKMBeanInfo p;
+ if (getMyId() == s.id) {
+ p = jmxLocalPeerBean = new LocalPeerBean(this);
+ try {
+ MBeanRegistry.getInstance().register(p, jmxQuorumBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxLocalPeerBean = null;
+ }
+ } else {
+ RemotePeerBean rBean = new RemotePeerBean(this, s);
+ try {
+ MBeanRegistry.getInstance().register(rBean, jmxQuorumBean);
+ jmxRemotePeerBean.put(s.id, rBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxQuorumBean = null;
+ }
+
+ try {
+ /*
+ * Main loop
+ */
+ while (running) {
+ if (unavailableStartTime == 0) {
+ unavailableStartTime = Time.currentElapsedTime();
+ }
+
+ switch (getPeerState()) {
+ case LOOKING:
+ LOG.info("LOOKING");
+ ServerMetrics.getMetrics().LOOKING_COUNT.add(1);
+
+ if (Boolean.getBoolean("readonlymode.enabled")) {
+ LOG.info("Attempting to start ReadOnlyZooKeeperServer");
+
+ // Create read-only server but don't start it immediately
+ final ReadOnlyZooKeeperServer roZk = new ReadOnlyZooKeeperServer(logFactory, this, this.zkDb);
+
+ // Instead of starting roZk immediately, wait some grace
+ // period before we decide we're partitioned.
+ //
+ // Thread is used here because otherwise it would require
+ // changes in each of election strategy classes which is
+ // unnecessary code coupling.
+ Thread roZkMgr = new Thread() {
+ public void run() {
+ try {
+ // lower-bound grace period to 2 secs
+ sleep(Math.max(2000, tickTime));
+ if (ServerState.LOOKING.equals(getPeerState())) {
+ roZk.startup();
+ }
+ } catch (InterruptedException e) {
+ LOG.info("Interrupted while attempting to start ReadOnlyZooKeeperServer, not started");
+ } catch (Exception e) {
+ LOG.error("FAILED to start ReadOnlyZooKeeperServer", e);
+ }
+ }
+ };
+ try {
+ roZkMgr.start();
+ reconfigFlagClear();
+ if (shuttingDownLE) {
+ shuttingDownLE = false;
+ startLeaderElection();
+ }
+ setCurrentVote(makeLEStrategy().lookForLeader());
+ checkSuspended();
+ } catch (Exception e) {
+ LOG.warn("Unexpected exception", e);
+ setPeerState(ServerState.LOOKING);
+ } finally {
+ // If the thread is in the the grace period, interrupt
+ // to come out of waiting.
+ roZkMgr.interrupt();
+ roZk.shutdown();
+ }
+ } else {
+ try {
+ reconfigFlagClear();
+ if (shuttingDownLE) {
+ shuttingDownLE = false;
+ startLeaderElection();
+ }
+ setCurrentVote(makeLEStrategy().lookForLeader());
+ } catch (Exception e) {
+ LOG.warn("Unexpected exception", e);
+ setPeerState(ServerState.LOOKING);
+ }
+ }
+ break;
+ case OBSERVING:
+ try {
+ LOG.info("OBSERVING");
+ setObserver(makeObserver(logFactory));
+ observer.observeLeader();
+ } catch (Exception e) {
+ LOG.warn("Unexpected exception", e);
+ } finally {
+ observer.shutdown();
+ setObserver(null);
+ updateServerState();
+
+ // Add delay jitter before we switch to LOOKING
+ // state to reduce the load of ObserverMaster
+ if (isRunning()) {
+ Observer.waitForObserverElectionDelay();
+ }
+ }
+ break;
+ case FOLLOWING:
+ try {
+ LOG.info("FOLLOWING");
+ setFollower(makeFollower(logFactory));
+ follower.followLeader();
+ } catch (Exception e) {
+ LOG.warn("Unexpected exception", e);
+ } finally {
+ follower.shutdown();
+ setFollower(null);
+ updateServerState();
+ }
+ break;
+ case LEADING:
+ LOG.info("LEADING");
+ try {
+ setLeader(makeLeader(logFactory));
+ leader.lead();
+ setLeader(null);
+ } catch (Exception e) {
+ LOG.warn("Unexpected exception", e);
+ } finally {
+ if (leader != null) {
+ leader.shutdown("Forcing shutdown");
+ setLeader(null);
+ }
+ updateServerState();
+ }
+ break;
+ }
+ }
+ } finally {
+ LOG.warn("QuorumPeer main thread exited");
+ MBeanRegistry instance = MBeanRegistry.getInstance();
+ instance.unregister(jmxQuorumBean);
+ instance.unregister(jmxLocalPeerBean);
+
+ for (RemotePeerBean remotePeerBean : jmxRemotePeerBean.values()) {
+ instance.unregister(remotePeerBean);
+ }
+
+ jmxQuorumBean = null;
+ jmxLocalPeerBean = null;
+ jmxRemotePeerBean = null;
+ }
+ }
+
+ private synchronized void updateServerState() {
+ if (!reconfigFlag) {
+ setPeerState(ServerState.LOOKING);
+ LOG.warn("PeerState set to LOOKING");
+ return;
+ }
+
+ if (getMyId() == getCurrentVote().getId()) {
+ setPeerState(ServerState.LEADING);
+ LOG.debug("PeerState set to LEADING");
+ } else if (getLearnerType() == LearnerType.PARTICIPANT) {
+ setPeerState(ServerState.FOLLOWING);
+ LOG.debug("PeerState set to FOLLOWING");
+ } else if (getLearnerType() == LearnerType.OBSERVER) {
+ setPeerState(ServerState.OBSERVING);
+ LOG.debug("PeerState set to OBSERVER");
+ } else { // currently shouldn't happen since there are only 2 learner types
+ setPeerState(ServerState.LOOKING);
+ LOG.debug("Should not be here");
+ }
+ reconfigFlag = false;
+ }
+
+ public void shutdown() {
+ running = false;
+ x509Util.close();
+ if (leader != null) {
+ leader.shutdown("quorum Peer shutdown");
+ }
+ if (follower != null) {
+ follower.shutdown();
+ }
+ shutdownServerCnxnFactory();
+ if (udpSocket != null) {
+ udpSocket.close();
+ }
+ if (jvmPauseMonitor != null) {
+ jvmPauseMonitor.serviceStop();
+ }
+
+ try {
+ adminServer.shutdown();
+ } catch (AdminServerException e) {
+ LOG.warn("Problem stopping AdminServer", e);
+ }
+
+ if (getElectionAlg() != null) {
+ this.interrupt();
+ getElectionAlg().shutdown();
+ }
+ try {
+ zkDb.close();
+ } catch (IOException ie) {
+ LOG.warn("Error closing logs ", ie);
+ }
+ }
+
+ /**
+ * A 'view' is a node's current opinion of the membership of the entire
+ * ensemble.
+ */
+ public Map<Long, QuorumPeer.QuorumServer> getView() {
+ return Collections.unmodifiableMap(getQuorumVerifier().getAllMembers());
+ }
+
+ /**
+ * Observers are not contained in this view, only nodes with
+ * PeerType=PARTICIPANT.
+ */
+ public Map<Long, QuorumPeer.QuorumServer> getVotingView() {
+ return getQuorumVerifier().getVotingMembers();
+ }
+
+ /**
+ * Returns only observers, no followers.
+ */
+ public Map<Long, QuorumPeer.QuorumServer> getObservingView() {
+ return getQuorumVerifier().getObservingMembers();
+ }
+
+ public synchronized Set<Long> getCurrentAndNextConfigVoters() {
+ Set<Long> voterIds = new HashSet<>(getQuorumVerifier().getVotingMembers().keySet());
+ if (getLastSeenQuorumVerifier() != null) {
+ voterIds.addAll(getLastSeenQuorumVerifier().getVotingMembers().keySet());
+ }
+ return voterIds;
+ }
+
+ /**
+ * Check if a node is in the current view. With static membership, the
+ * result of this check will never change; only when dynamic membership
+ * is introduced will this be more useful.
+ */
+ public boolean viewContains(Long sid) {
+ return this.getView().containsKey(sid);
+ }
+
+ /**
+ * Only used by QuorumStats at the moment
+ */
+ public String[] getQuorumPeers() {
+ List<String> l = new ArrayList<>();
+ synchronized (this) {
+ if (leader != null) {
+ for (LearnerHandler fh : leader.getLearners()) {
+ if (fh.getSocket() != null) {
+ String s = formatInetAddr((InetSocketAddress) fh.getSocket().getRemoteSocketAddress());
+ if (leader.isLearnerSynced(fh)) {
+ s += "*";
+ }
+ l.add(s);
+ }
+ }
+ } else if (follower != null) {
+ l.add(formatInetAddr((InetSocketAddress) follower.sock.getRemoteSocketAddress()));
+ }
+ }
+ return l.toArray(new String[0]);
+ }
+
+ public String getServerState() {
+ switch (getPeerState()) {
+ case LOOKING:
+ return QuorumStats.Provider.LOOKING_STATE;
+ case LEADING:
+ return QuorumStats.Provider.LEADING_STATE;
+ case FOLLOWING:
+ return QuorumStats.Provider.FOLLOWING_STATE;
+ case OBSERVING:
+ return QuorumStats.Provider.OBSERVING_STATE;
+ }
+ return QuorumStats.Provider.UNKNOWN_STATE;
+ }
+
+ /**
+ * set the id of this quorum peer.
+ */
+ public void setMyid(long myid) {
+ this.myid = myid;
+ }
+
+ public void setInitialConfig(String initialConfig) {
+ this.initialConfig = initialConfig;
+ }
+
+ public String getInitialConfig() {
+ return initialConfig;
+ }
+
+ /**
+ * Get the number of milliseconds of each tick
+ */
+ public int getTickTime() {
+ return tickTime;
+ }
+
+ /**
+ * Set the number of milliseconds of each tick
+ */
+ public void setTickTime(int tickTime) {
+ LOG.info("tickTime set to {}", tickTime);
+ this.tickTime = tickTime;
+ }
+
+ /** Maximum number of connections allowed from particular host (ip) */
+ public int getMaxClientCnxnsPerHost() {
+ if (cnxnFactory != null) {
+ return cnxnFactory.getMaxClientCnxnsPerHost();
+ }
+ if (secureCnxnFactory != null) {
+ return secureCnxnFactory.getMaxClientCnxnsPerHost();
+ }
+ return -1;
+ }
+
+ /** Whether local sessions are enabled */
+ public boolean areLocalSessionsEnabled() {
+ return localSessionsEnabled;
+ }
+
+ /** Whether to enable local sessions */
+ public void enableLocalSessions(boolean flag) {
+ LOG.info("Local sessions {}", (flag ? "enabled" : "disabled"));
+ localSessionsEnabled = flag;
+ }
+
+ /** Whether local sessions are allowed to upgrade to global sessions */
+ public boolean isLocalSessionsUpgradingEnabled() {
+ return localSessionsUpgradingEnabled;
+ }
+
+ /** Whether to allow local sessions to upgrade to global sessions */
+ public void enableLocalSessionsUpgrading(boolean flag) {
+ LOG.info("Local session upgrading {}", (flag ? "enabled" : "disabled"));
+ localSessionsUpgradingEnabled = flag;
+ }
+
+ /** minimum session timeout in milliseconds */
+ public int getMinSessionTimeout() {
+ return minSessionTimeout;
+ }
+
+ /** minimum session timeout in milliseconds */
+ public void setMinSessionTimeout(int min) {
+ LOG.info("minSessionTimeout set to {}", min);
+ this.minSessionTimeout = min;
+ }
+
+ /** maximum session timeout in milliseconds */
+ public int getMaxSessionTimeout() {
+ return maxSessionTimeout;
+ }
+
+ /** maximum session timeout in milliseconds */
+ public void setMaxSessionTimeout(int max) {
+ LOG.info("maxSessionTimeout set to {}", max);
+ this.maxSessionTimeout = max;
+ }
+
+ /** The server socket's listen backlog length */
+ public int getClientPortListenBacklog() {
+ return this.clientPortListenBacklog;
+ }
+
+ /** Sets the server socket's listen backlog length. */
+ public void setClientPortListenBacklog(int backlog) {
+ this.clientPortListenBacklog = backlog;
+ }
+
+ /**
+ * Get the number of ticks that the initial synchronization phase can take
+ */
+ public int getInitLimit() {
+ return initLimit;
+ }
+
+ /**
+ * Set the number of ticks that the initial synchronization phase can take
+ */
+ public void setInitLimit(int initLimit) {
+ LOG.info("initLimit set to {}", initLimit);
+ this.initLimit = initLimit;
+ }
+
+ /**
+ * Get the current tick
+ */
+ public int getTick() {
+ return tick.get();
+ }
+
+ public QuorumVerifier configFromString(String s) throws IOException, ConfigException {
+ Properties props = new Properties();
+ props.load(new StringReader(s));
+ return QuorumPeerConfig.parseDynamicConfig(props, electionType, false, false, getQuorumVerifier().getOraclePath());
+ }
+
+ /**
+ * Return QuorumVerifier object for the last committed configuration.
+ */
+ public QuorumVerifier getQuorumVerifier() {
+ synchronized (QV_LOCK) {
+ return quorumVerifier;
+ }
+ }
+
+ /**
+ * Return QuorumVerifier object for the last proposed configuration.
+ */
+ public QuorumVerifier getLastSeenQuorumVerifier() {
+ synchronized (QV_LOCK) {
+ return lastSeenQuorumVerifier;
+ }
+ }
+
+ public synchronized void restartLeaderElection(QuorumVerifier qvOLD, QuorumVerifier qvNEW) {
+ if (qvOLD == null || !qvOLD.equals(qvNEW)) {
+ LOG.warn("Restarting Leader Election");
+ getElectionAlg().shutdown();
+ shuttingDownLE = false;
+ startLeaderElection();
+ }
+ }
+
+ public String getNextDynamicConfigFilename() {
+ if (configFilename == null) {
+ LOG.warn("configFilename is null! This should only happen in tests.");
+ return null;
+ }
+ return configFilename + QuorumPeerConfig.nextDynamicConfigFileSuffix;
+ }
+
+ // On entry to this method, qcm must be non-null and the locks on both qcm and QV_LOCK
+ // must be held. We don't want quorumVerifier/lastSeenQuorumVerifier to change out from
+ // under us, so we have to hold QV_LOCK; and since the call to qcm.connectOne() will take
+ // the lock on qcm (and take QV_LOCK again inside that), the caller needs to have taken
+ // qcm outside QV_LOCK to avoid a deadlock against other callers of qcm.connectOne().
+ private void connectNewPeers(QuorumCnxManager qcm) {
+ if (quorumVerifier != null && lastSeenQuorumVerifier != null) {
+ Map<Long, QuorumServer> committedView = quorumVerifier.getAllMembers();
+ for (Entry<Long, QuorumServer> e : lastSeenQuorumVerifier.getAllMembers().entrySet()) {
+ if (e.getKey() != getMyId() && !committedView.containsKey(e.getKey())) {
+ qcm.connectOne(e.getKey());
+ }
+ }
+ }
+ }
+
+ public void setLastSeenQuorumVerifier(QuorumVerifier qv, boolean writeToDisk) {
+ if (!isReconfigEnabled()) {
+ LOG.info("Dynamic reconfig is disabled, we don't store the last seen config.");
+ return;
+ }
+
+ // If qcm is non-null, we may call qcm.connectOne(), which will take the lock on qcm
+ // and then take QV_LOCK. Take the locks in the same order to ensure that we don't
+ // deadlock against other callers of connectOne(). If qcmRef gets set in another
+ // thread while we're inside the synchronized block, that does no harm; if we didn't
+ // take a lock on qcm (because it was null when we sampled it), we won't call
+ // connectOne() on it. (Use of an AtomicReference is enough to guarantee visibility
+ // of updates that provably happen in another thread before entering this method.)
+ QuorumCnxManager qcm = qcmRef.get();
+ Object outerLockObject = (qcm != null) ? qcm : QV_LOCK;
+ synchronized (outerLockObject) {
+ synchronized (QV_LOCK) {
+ if (lastSeenQuorumVerifier != null && lastSeenQuorumVerifier.getVersion() > qv.getVersion()) {
+ LOG.error("setLastSeenQuorumVerifier called with stale config "
+ + qv.getVersion()
+ + ". Current version: "
+ + quorumVerifier.getVersion());
+ }
+ // assuming that a version uniquely identifies a configuration, so if
+ // version is the same, nothing to do here.
+ if (lastSeenQuorumVerifier != null && lastSeenQuorumVerifier.getVersion() == qv.getVersion()) {
+ return;
+ }
+ lastSeenQuorumVerifier = qv;
+ if (qcm != null) {
+ connectNewPeers(qcm);
+ }
+
+ if (writeToDisk) {
+ try {
+ String fileName = getNextDynamicConfigFilename();
+ if (fileName != null) {
+ QuorumPeerConfig.writeDynamicConfig(fileName, qv, true);
+ }
+ } catch (IOException e) {
+ LOG.error("Error writing next dynamic config file to disk", e);
+ }
+ }
+ }
+ }
+ }
+
+ public QuorumVerifier setQuorumVerifier(QuorumVerifier qv, boolean writeToDisk) {
+ synchronized (QV_LOCK) {
+ if ((quorumVerifier != null) && (quorumVerifier.getVersion() >= qv.getVersion())) {
+ // this is normal. For example - server found out about new config through FastLeaderElection gossiping
+ // and then got the same config in UPTODATE message so its already known
+ LOG.debug(
+ "{} setQuorumVerifier called with known or old config {}. Current version: {}",
+ getMyId(),
+ qv.getVersion(),
+ quorumVerifier.getVersion());
+ return quorumVerifier;
+ }
+ QuorumVerifier prevQV = quorumVerifier;
+ quorumVerifier = qv;
+ if (lastSeenQuorumVerifier == null || (qv.getVersion() > lastSeenQuorumVerifier.getVersion())) {
+ lastSeenQuorumVerifier = qv;
+ }
+
+ if (writeToDisk) {
+ // some tests initialize QuorumPeer without a static config file
+ if (configFilename != null) {
+ try {
+ String dynamicConfigFilename = makeDynamicConfigFilename(qv.getVersion());
+ QuorumPeerConfig.writeDynamicConfig(dynamicConfigFilename, qv, false);
+ QuorumPeerConfig.editStaticConfig(configFilename, dynamicConfigFilename, needEraseClientInfoFromStaticConfig());
+ } catch (IOException e) {
+ LOG.error("Error closing file", e);
+ }
+ } else {
+ LOG.info("writeToDisk == true but configFilename == null");
+ }
+ }
+
+ if (qv.getVersion() == lastSeenQuorumVerifier.getVersion()) {
+ QuorumPeerConfig.deleteFile(getNextDynamicConfigFilename());
+ }
+ QuorumServer qs = qv.getAllMembers().get(getMyId());
+ if (qs != null) {
+ setAddrs(qs.addr, qs.electionAddr, qs.clientAddr);
+ }
+ updateObserverMasterList();
+ return prevQV;
+ }
+ }
+
+ private String makeDynamicConfigFilename(long version) {
+ return configFilename + ".dynamic." + Long.toHexString(version);
+ }
+
+ private boolean needEraseClientInfoFromStaticConfig() {
+ QuorumServer server = quorumVerifier.getAllMembers().get(getMyId());
+ return (server != null && server.clientAddr != null && !server.isClientAddrFromStatic);
+ }
+
+ /**
+ * Get an instance of LeaderElection
+ */
+ public Election getElectionAlg() {
+ return electionAlg;
+ }
+
+ /**
+ * Get the synclimit
+ */
+ public int getSyncLimit() {
+ return syncLimit;
+ }
+
+ /**
+ * Set the synclimit
+ */
+ public void setSyncLimit(int syncLimit) {
+ LOG.info("syncLimit set to {}", syncLimit);
+ this.syncLimit = syncLimit;
+ }
+
+ /**
+ * Get the connectToLearnerMasterLimit
+ */
+ public int getConnectToLearnerMasterLimit() {
+ return connectToLearnerMasterLimit;
+ }
+
+ /**
+ * Set the connectToLearnerMasterLimit
+ */
+ public void setConnectToLearnerMasterLimit(int connectToLearnerMasterLimit) {
+ LOG.info("connectToLearnerMasterLimit set to {}", connectToLearnerMasterLimit);
+ this.connectToLearnerMasterLimit = connectToLearnerMasterLimit;
+ }
+
+ /**
+ * The syncEnabled can also be set via a system property.
+ */
+ public static final String SYNC_ENABLED = "zookeeper.observer.syncEnabled";
+
+ /**
+ * Return syncEnabled.
+ */
+ public boolean getSyncEnabled() {
+ if (System.getProperty(SYNC_ENABLED) != null) {
+ LOG.info("{}={}", SYNC_ENABLED, Boolean.getBoolean(SYNC_ENABLED));
+ return Boolean.getBoolean(SYNC_ENABLED);
+ } else {
+ return syncEnabled;
+ }
+ }
+
+ /**
+ * Set syncEnabled.
+ *
+ * @param syncEnabled
+ */
+ public void setSyncEnabled(boolean syncEnabled) {
+ this.syncEnabled = syncEnabled;
+ }
+
+ /**
+ * Gets the election type
+ */
+ public int getElectionType() {
+ return electionType;
+ }
+
+ /**
+ * Sets the election type
+ */
+ public void setElectionType(int electionType) {
+ this.electionType = electionType;
+ }
+
+ public boolean getQuorumListenOnAllIPs() {
+ return quorumListenOnAllIPs;
+ }
+
+ public void setQuorumListenOnAllIPs(boolean quorumListenOnAllIPs) {
+ this.quorumListenOnAllIPs = quorumListenOnAllIPs;
+ }
+
+ public void setCnxnFactory(ServerCnxnFactory cnxnFactory) {
+ this.cnxnFactory = cnxnFactory;
+ }
+
+ public void setSecureCnxnFactory(ServerCnxnFactory secureCnxnFactory) {
+ this.secureCnxnFactory = secureCnxnFactory;
+ }
+
+ public void setSslQuorum(boolean sslQuorum) {
+ if (sslQuorum) {
+ LOG.info("Using TLS encrypted quorum communication");
+ } else {
+ LOG.info("Using insecure (non-TLS) quorum communication");
+ }
+ this.sslQuorum = sslQuorum;
+ }
+
+ public void setUsePortUnification(boolean shouldUsePortUnification) {
+ LOG.info("Port unification {}", shouldUsePortUnification ? "enabled" : "disabled");
+ this.shouldUsePortUnification = shouldUsePortUnification;
+ }
+
+ private void startServerCnxnFactory() {
+ if (cnxnFactory != null) {
+ cnxnFactory.start();
+ }
+ if (secureCnxnFactory != null) {
+ secureCnxnFactory.start();
+ }
+ }
+
+ private void shutdownServerCnxnFactory() {
+ if (cnxnFactory != null) {
+ cnxnFactory.shutdown();
+ }
+ if (secureCnxnFactory != null) {
+ secureCnxnFactory.shutdown();
+ }
+ }
+
+ // Leader and learner will control the zookeeper server and pass it into QuorumPeer.
+ public void setZooKeeperServer(ZooKeeperServer zks) {
+ if (cnxnFactory != null) {
+ cnxnFactory.setZooKeeperServer(zks);
+ }
+ if (secureCnxnFactory != null) {
+ secureCnxnFactory.setZooKeeperServer(zks);
+ }
+ }
+
+ public void closeAllConnections() {
+ if (cnxnFactory != null) {
+ cnxnFactory.closeAll(ServerCnxn.DisconnectReason.SERVER_SHUTDOWN);
+ }
+ if (secureCnxnFactory != null) {
+ secureCnxnFactory.closeAll(ServerCnxn.DisconnectReason.SERVER_SHUTDOWN);
+ }
+ }
+
+ public int getClientPort() {
+ if (cnxnFactory != null) {
+ return cnxnFactory.getLocalPort();
+ }
+ return -1;
+ }
+
+ public int getSecureClientPort() {
+ if (secureCnxnFactory != null) {
+ return secureCnxnFactory.getLocalPort();
+ }
+ return -1;
+ }
+
+ public void setTxnFactory(FileTxnSnapLog factory) {
+ this.logFactory = factory;
+ }
+
+ public FileTxnSnapLog getTxnFactory() {
+ return this.logFactory;
+ }
+
+ /**
+ * set zk database for this node
+ * @param database
+ */
+ public void setZKDatabase(ZKDatabase database) {
+ this.zkDb = database;
+ }
+
+ protected ZKDatabase getZkDb() {
+ return zkDb;
+ }
+
+ public synchronized void initConfigInZKDatabase() {
+ if (zkDb != null) {
+ zkDb.initConfigInZKDatabase(getQuorumVerifier());
+ }
+ }
+
+ public boolean isRunning() {
+ return running;
+ }
+
+ /**
+ * get reference to QuorumCnxManager
+ */
+ public QuorumCnxManager getQuorumCnxManager() {
+ return qcmRef.get();
+ }
+ private long readLongFromFile(String name) throws IOException {
+ File file = new File(logFactory.getSnapDir(), name);
+ BufferedReader br = new BufferedReader(new FileReader(file));
+ String line = "";
+ try {
+ line = br.readLine();
+ return Long.parseLong(line);
+ } catch (NumberFormatException e) {
+ throw new IOException("Found " + line + " in " + file);
+ } finally {
+ br.close();
+ }
+ }
+
+ private long acceptedEpoch = -1;
+ private long currentEpoch = -1;
+
+ public static final String CURRENT_EPOCH_FILENAME = "currentEpoch";
+
+ public static final String ACCEPTED_EPOCH_FILENAME = "acceptedEpoch";
+
+ /**
+ * Write a long value to disk atomically. Either succeeds or an exception
+ * is thrown.
+ * @param name file name to write the long to
+ * @param value the long value to write to the named file
+ * @throws IOException if the file cannot be written atomically
+ */
+ // visibleForTest
+ void writeLongToFile(String name, final long value) throws IOException {
+ File file = new File(logFactory.getSnapDir(), name);
+ new AtomicFileWritingIdiom(file, new WriterStatement() {
+ @Override
+ public void write(Writer bw) throws IOException {
+ bw.write(Long.toString(value));
+ }
+ });
+ }
+
+ public long getCurrentEpoch() throws IOException {
+ if (currentEpoch == -1) {
+ currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME);
+ }
+ return currentEpoch;
+ }
+
+ public long getAcceptedEpoch() throws IOException {
+ if (acceptedEpoch == -1) {
+ acceptedEpoch = readLongFromFile(ACCEPTED_EPOCH_FILENAME);
+ }
+ return acceptedEpoch;
+ }
+
+ public void setCurrentEpoch(long e) throws IOException {
+ writeLongToFile(CURRENT_EPOCH_FILENAME, e);
+ currentEpoch = e;
+ }
+
+ public void setAcceptedEpoch(long e) throws IOException {
+ writeLongToFile(ACCEPTED_EPOCH_FILENAME, e);
+ acceptedEpoch = e;
+ }
+
+ public boolean processReconfig(QuorumVerifier qv, Long suggestedLeaderId, Long zxid, boolean restartLE) {
+ if (!isReconfigEnabled()) {
+ LOG.debug("Reconfig feature is disabled, skip reconfig processing.");
+ return false;
+ }
+
+ InetSocketAddress oldClientAddr = getClientAddress();
+
+ // update last committed quorum verifier, write the new config to disk
+ // and restart leader election if config changed.
+ QuorumVerifier prevQV = setQuorumVerifier(qv, true);
+
+ // There is no log record for the initial config, thus after syncing
+ // with leader
+ // /zookeeper/config is empty! it is also possible that last committed
+ // config is propagated during leader election
+ // without the propagation the corresponding log records.
+ // so we should explicitly do this (this is not necessary when we're
+ // already a Follower/Observer, only
+ // for Learner):
+ initConfigInZKDatabase();
+
+ if (prevQV.getVersion() < qv.getVersion() && !prevQV.equals(qv)) {
+ Map<Long, QuorumServer> newMembers = qv.getAllMembers();
+ updateRemotePeerMXBeans(newMembers);
+ if (restartLE) {
+ restartLeaderElection(prevQV, qv);
+ }
+
+ QuorumServer myNewQS = newMembers.get(getMyId());
+ if (myNewQS != null && myNewQS.clientAddr != null && !myNewQS.clientAddr.equals(oldClientAddr)) {
+ cnxnFactory.reconfigure(myNewQS.clientAddr);
+ updateThreadName();
+ }
+
+ boolean roleChange = updateLearnerType(qv);
+ boolean leaderChange = false;
+ if (suggestedLeaderId != null) {
+ // zxid should be non-null too
+ leaderChange = updateVote(suggestedLeaderId, zxid);
+ } else {
+ long currentLeaderId = getCurrentVote().getId();
+ QuorumServer myleaderInCurQV = prevQV.getVotingMembers().get(currentLeaderId);
+ QuorumServer myleaderInNewQV = qv.getVotingMembers().get(currentLeaderId);
+ leaderChange = (myleaderInCurQV == null
+ || myleaderInCurQV.addr == null
+ || myleaderInNewQV == null
+ || !myleaderInCurQV.addr.equals(myleaderInNewQV.addr));
+ // we don't have a designated leader - need to go into leader
+ // election
+ reconfigFlagClear();
+ }
+
+ return roleChange || leaderChange;
+ }
+ return false;
+
+ }
+
+ private void updateRemotePeerMXBeans(Map<Long, QuorumServer> newMembers) {
+ Set<Long> existingMembers = new HashSet<>(newMembers.keySet());
+ existingMembers.retainAll(jmxRemotePeerBean.keySet());
+ for (Long id : existingMembers) {
+ RemotePeerBean rBean = jmxRemotePeerBean.get(id);
+ rBean.setQuorumServer(newMembers.get(id));
+ }
+
+ Set<Long> joiningMembers = new HashSet<>(newMembers.keySet());
+ joiningMembers.removeAll(jmxRemotePeerBean.keySet());
+ joiningMembers.remove(getMyId()); // remove self as it is local bean
+ for (Long id : joiningMembers) {
+ QuorumServer qs = newMembers.get(id);
+ RemotePeerBean rBean = new RemotePeerBean(this, qs);
+ try {
+ MBeanRegistry.getInstance().register(rBean, jmxQuorumBean);
+ jmxRemotePeerBean.put(qs.id, rBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ }
+ }
+
+ Set<Long> leavingMembers = new HashSet<>(jmxRemotePeerBean.keySet());
+ leavingMembers.removeAll(newMembers.keySet());
+ for (Long id : leavingMembers) {
+ RemotePeerBean rBean = jmxRemotePeerBean.remove(id);
+ try {
+ MBeanRegistry.getInstance().unregister(rBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ }
+ }
+
+ private ArrayList<QuorumServer> observerMasters = new ArrayList<>();
+ private void updateObserverMasterList() {
+ if (observerMasterPort <= 0) {
+ return; // observer masters not enabled
+ }
+ observerMasters.clear();
+ StringBuilder sb = new StringBuilder();
+ for (QuorumServer server : quorumVerifier.getVotingMembers().values()) {
+ InetAddress address = server.addr.getReachableOrOne().getAddress();
+ InetSocketAddress addr = new InetSocketAddress(address, observerMasterPort);
+ observerMasters.add(new QuorumServer(server.id, addr));
+ sb.append(addr).append(",");
+ }
+ LOG.info("Updated learner master list to be {}", sb.toString());
+ Collections.shuffle(observerMasters);
+ // Reset the internal index of the observerMaster when
+ // the observerMaster List is refreshed
+ nextObserverMaster = 0;
+ }
+
+ private boolean useObserverMasters() {
+ return getLearnerType() == LearnerType.OBSERVER && observerMasters.size() > 0;
+ }
+
+ private int nextObserverMaster = 0;
+ private QuorumServer nextObserverMaster() {
+ if (nextObserverMaster >= observerMasters.size()) {
+ nextObserverMaster = 0;
+ // Add a reconnect delay only after the observer
+ // has exhausted trying to connect to all the masters
+ // from the observerMasterList
+ if (isRunning()) {
+ Observer.waitForReconnectDelay();
+ }
+ }
+ return observerMasters.get(nextObserverMaster++);
+ }
+
+ QuorumServer findLearnerMaster(QuorumServer leader) {
+ if (useObserverMasters()) {
+ return nextObserverMaster();
+ } else {
+ // Add delay jitter to reduce the load on the leader
+ if (isRunning()) {
+ Observer.waitForReconnectDelay();
+ }
+ return leader;
+ }
+ }
+
+ /**
+ * Vet a given learner master's information.
+ * Allows specification by server id, ip only, or ip and port
+ */
+ QuorumServer validateLearnerMaster(String desiredMaster) {
+ if (useObserverMasters()) {
+ Long sid;
+ try {
+ sid = Long.parseLong(desiredMaster);
+ } catch (NumberFormatException e) {
+ sid = null;
+ }
+ for (QuorumServer server : observerMasters) {
+ if (sid == null) {
+ for (InetSocketAddress address : server.addr.getAllAddresses()) {
+ String serverAddr = address.getAddress().getHostAddress() + ':' + address.getPort();
+ if (serverAddr.startsWith(desiredMaster)) {
+ return server;
+ }
+ }
+ } else {
+ if (sid.equals(server.id)) {
+ return server;
+ }
+ }
+ }
+ if (sid == null) {
+ LOG.info("could not find learner master address={}", desiredMaster);
+ } else {
+ LOG.warn("could not find learner master sid={}", sid);
+ }
+ } else {
+ LOG.info("cannot validate request, observer masters not enabled");
+ }
+ return null;
+ }
+
+ private boolean updateLearnerType(QuorumVerifier newQV) {
+ //check if I'm an observer in new config
+ if (newQV.getObservingMembers().containsKey(getMyId())) {
+ if (getLearnerType() != LearnerType.OBSERVER) {
+ setLearnerType(LearnerType.OBSERVER);
+ LOG.info("Becoming an observer");
+ reconfigFlagSet();
+ return true;
+ } else {
+ return false;
+ }
+ } else if (newQV.getVotingMembers().containsKey(getMyId())) {
+ if (getLearnerType() != LearnerType.PARTICIPANT) {
+ setLearnerType(LearnerType.PARTICIPANT);
+ LOG.info("Becoming a voting participant");
+ reconfigFlagSet();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ // I'm not in the view
+ if (getLearnerType() != LearnerType.PARTICIPANT) {
+ setLearnerType(LearnerType.PARTICIPANT);
+ LOG.info("Becoming a non-voting participant");
+ reconfigFlagSet();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateVote(long designatedLeader, long zxid) {
+ Vote currentVote = getCurrentVote();
+ if (currentVote != null && designatedLeader != currentVote.getId()) {
+ setCurrentVote(new Vote(designatedLeader, zxid));
+ reconfigFlagSet();
+ LOG.warn("Suggested leader: {}", designatedLeader);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates leader election info to avoid inconsistencies when
+ * a new server tries to join the ensemble.
+ *
+ * Here is the inconsistency scenario we try to solve by updating the peer
+ * epoch after following leader:
+ *
+ * Let's say we have an ensemble with 3 servers z1, z2 and z3.
+ *
+ * 1. z1, z2 were following z3 with peerEpoch to be 0xb8, the new epoch is
+ * 0xb9, aka current accepted epoch on disk.
+ * 2. z2 get restarted, which will use 0xb9 as it's peer epoch when loading
+ * the current accept epoch from disk.
+ * 3. z2 received notification from z1 and z3, which is following z3 with
+ * epoch 0xb8, so it started following z3 again with peer epoch 0xb8.
+ * 4. before z2 successfully connected to z3, z3 get restarted with new
+ * epoch 0xb9.
+ * 5. z2 will retry around a few round (default 5s) before giving up,
+ * meanwhile it will report z3 as leader.
+ * 6. z1 restarted, and looking with peer epoch 0xb9.
+ * 7. z1 voted z3, and z3 was elected as leader again with peer epoch 0xb9.
+ * 8. z2 successfully connected to z3 before giving up, but with peer
+ * epoch 0xb8.
+ * 9. z1 get restarted, looking for leader with peer epoch 0xba, but cannot
+ * join, because z2 is reporting peer epoch 0xb8, while z3 is reporting
+ * 0xb9.
+ *
+ * By updating the election vote after actually following leader, we can
+ * avoid this kind of stuck happened.
+ *
+ * Btw, the zxid and electionEpoch could be inconsistent because of the same
+ * reason, it's better to update these as well after syncing with leader, but
+ * that required protocol change which is non trivial. This problem is worked
+ * around by skipping comparing the zxid and electionEpoch when counting for
+ * votes for out of election servers during looking for leader.
+ *
+ * See https://issues.apache.org/jira/browse/ZOOKEEPER-1732
+ */
+ protected void updateElectionVote(long newEpoch) {
+ Vote currentVote = getCurrentVote();
+ if (currentVote != null) {
+ setCurrentVote(new Vote(currentVote.getId(), currentVote.getZxid(), currentVote.getElectionEpoch(), newEpoch, currentVote
+ .getState()));
+ }
+ }
+
+ private void updateThreadName() {
+ String plain = cnxnFactory != null
+ ? cnxnFactory.getLocalAddress() != null
+ ? formatInetAddr(cnxnFactory.getLocalAddress())
+ : "disabled"
+ : "disabled";
+ String secure = secureCnxnFactory != null ? formatInetAddr(secureCnxnFactory.getLocalAddress()) : "disabled";
+ setName(String.format("QuorumPeer[myid=%d](plain=%s)(secure=%s)", getMyId(), plain, secure));
+ }
+
+ /**
+ * Sets the time taken for leader election in milliseconds.
+ *
+ * @param electionTimeTaken time taken for leader election
+ */
+ void setElectionTimeTaken(long electionTimeTaken) {
+ this.electionTimeTaken = electionTimeTaken;
+ }
+
+ /**
+ * @return the time taken for leader election in milliseconds.
+ */
+ long getElectionTimeTaken() {
+ return electionTimeTaken;
+ }
+
+ void setQuorumServerSaslRequired(boolean serverSaslRequired) {
+ quorumServerSaslAuthRequired = serverSaslRequired;
+ LOG.info("{} set to {}", QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, serverSaslRequired);
+ }
+
+ void setQuorumLearnerSaslRequired(boolean learnerSaslRequired) {
+ quorumLearnerSaslAuthRequired = learnerSaslRequired;
+ LOG.info("{} set to {}", QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, learnerSaslRequired);
+ }
+
+ void setQuorumSaslEnabled(boolean enableAuth) {
+ quorumSaslEnableAuth = enableAuth;
+ if (!quorumSaslEnableAuth) {
+ LOG.info("QuorumPeer communication is not secured! (SASL auth disabled)");
+ } else {
+ LOG.info("{} set to {}", QuorumAuth.QUORUM_SASL_AUTH_ENABLED, enableAuth);
+ }
+ }
+
+ void setQuorumServicePrincipal(String servicePrincipal) {
+ quorumServicePrincipal = servicePrincipal;
+ LOG.info("{} set to {}", QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL, quorumServicePrincipal);
+ }
+
+ void setQuorumLearnerLoginContext(String learnerContext) {
+ quorumLearnerLoginContext = learnerContext;
+ LOG.info("{} set to {}", QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT, quorumLearnerLoginContext);
+ }
+
+ void setQuorumServerLoginContext(String serverContext) {
+ quorumServerLoginContext = serverContext;
+ LOG.info("{} set to {}", QuorumAuth.QUORUM_SERVER_SASL_LOGIN_CONTEXT, quorumServerLoginContext);
+ }
+
+ void setQuorumCnxnThreadsSize(int qCnxnThreadsSize) {
+ if (qCnxnThreadsSize > QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE) {
+ quorumCnxnThreadsSize = qCnxnThreadsSize;
+ }
+ LOG.info("quorum.cnxn.threads.size set to {}", quorumCnxnThreadsSize);
+ }
+
+ boolean isQuorumSaslAuthEnabled() {
+ return quorumSaslEnableAuth;
+ }
+
+ private boolean isQuorumServerSaslAuthRequired() {
+ return quorumServerSaslAuthRequired;
+ }
+
+ private boolean isQuorumLearnerSaslAuthRequired() {
+ return quorumLearnerSaslAuthRequired;
+ }
+
+ public QuorumCnxManager createCnxnManager() {
+ int timeout = quorumCnxnTimeoutMs > 0 ? quorumCnxnTimeoutMs : this.tickTime * this.syncLimit;
+ LOG.info("Using {}ms as the quorum cnxn socket timeout", timeout);
+ return new QuorumCnxManager(
+ this,
+ this.getMyId(),
+ this.getView(),
+ this.authServer,
+ this.authLearner,
+ timeout,
+ this.getQuorumListenOnAllIPs(),
+ this.quorumCnxnThreadsSize,
+ this.isQuorumSaslAuthEnabled());
+ }
+
+ boolean isLeader(long id) {
+ Vote vote = getCurrentVote();
+ return vote != null && id == vote.getId();
+ }
+
+ public boolean isReconfigEnabled() {
+ return reconfigEnabled;
+ }
+
+ @InterfaceAudience.Private
+ /**
+ * This is a metric that depends on the status of the peer.
+ */ public Integer getSynced_observers_metric() {
+ if (leader != null) {
+ return leader.getObservingLearners().size();
+ } else if (follower != null) {
+ return follower.getSyncedObserverSize();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Create a new QuorumPeer and apply all the values per the already-parsed config.
+ *
+ * @param config The appertained quorum peer config.
+ * @return A QuorumPeer instantiated with specified peer config. Note this peer
+ * is not fully initialized; caller should finish initialization through
+ * additional configurations (connection factory settings, etc).
+ *
+ * @throws IOException
+ */
+ public static QuorumPeer createFromConfig(QuorumPeerConfig config) throws IOException {
+ QuorumPeer quorumPeer = new QuorumPeer();
+ quorumPeer.setTxnFactory(new FileTxnSnapLog(config.getDataLogDir(), config.getDataDir()));
+ quorumPeer.enableLocalSessions(config.areLocalSessionsEnabled());
+ quorumPeer.enableLocalSessionsUpgrading(config.isLocalSessionsUpgradingEnabled());
+ quorumPeer.setElectionType(config.getElectionAlg());
+ quorumPeer.setMyid(config.getServerId());
+ quorumPeer.setTickTime(config.getTickTime());
+ quorumPeer.setMinSessionTimeout(config.getMinSessionTimeout());
+ quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout());
+ quorumPeer.setInitLimit(config.getInitLimit());
+ quorumPeer.setSyncLimit(config.getSyncLimit());
+ quorumPeer.setConnectToLearnerMasterLimit(config.getConnectToLearnerMasterLimit());
+ quorumPeer.setObserverMasterPort(config.getObserverMasterPort());
+ quorumPeer.setConfigFileName(config.getConfigFilename());
+ quorumPeer.setClientPortListenBacklog(config.getClientPortListenBacklog());
+ quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory()));
+ quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false);
+ if (config.getLastSeenQuorumVerifier() != null) {
+ quorumPeer.setLastSeenQuorumVerifier(config.getLastSeenQuorumVerifier(), false);
+ }
+ quorumPeer.initConfigInZKDatabase();
+ quorumPeer.setSslQuorum(config.isSslQuorum());
+ quorumPeer.setUsePortUnification(config.shouldUsePortUnification());
+ quorumPeer.setLearnerType(config.getPeerType());
+ quorumPeer.setSyncEnabled(config.getSyncEnabled());
+ quorumPeer.setQuorumListenOnAllIPs(config.getQuorumListenOnAllIPs());
+ if (config.sslQuorumReloadCertFiles) {
+ quorumPeer.getX509Util().enableCertFileReloading();
+ }
+ quorumPeer.setMultiAddressEnabled(config.isMultiAddressEnabled());
+ quorumPeer.setMultiAddressReachabilityCheckEnabled(config.isMultiAddressReachabilityCheckEnabled());
+ quorumPeer.setMultiAddressReachabilityCheckTimeoutMs(config.getMultiAddressReachabilityCheckTimeoutMs());
+
+ // sets quorum sasl authentication configurations
+ quorumPeer.setQuorumSaslEnabled(config.quorumEnableSasl);
+ if (quorumPeer.isQuorumSaslAuthEnabled()) {
+ quorumPeer.setQuorumServerSaslRequired(config.quorumServerRequireSasl);
+ quorumPeer.setQuorumLearnerSaslRequired(config.quorumLearnerRequireSasl);
+ quorumPeer.setQuorumServicePrincipal(config.quorumServicePrincipal);
+ quorumPeer.setQuorumServerLoginContext(config.quorumServerLoginContext);
+ quorumPeer.setQuorumLearnerLoginContext(config.quorumLearnerLoginContext);
+ }
+ quorumPeer.setQuorumCnxnThreadsSize(config.quorumCnxnThreadsSize);
+
+ if (config.jvmPauseMonitorToRun) {
+ quorumPeer.setJvmPauseMonitor(new JvmPauseMonitor(config));
+ }
+
+ return quorumPeer;
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java
new file mode 100644
index 00000000000..a96a395b03b
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.apache.zookeeper.ZooDefs.OpCode;
+import org.apache.zookeeper.jmx.MBeanRegistry;
+import org.apache.zookeeper.server.DataTreeBean;
+import org.apache.zookeeper.server.FinalRequestProcessor;
+import org.apache.zookeeper.server.PrepRequestProcessor;
+import org.apache.zookeeper.server.Request;
+import org.apache.zookeeper.server.RequestProcessor;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.apache.zookeeper.server.ZKDatabase;
+import org.apache.zookeeper.server.ZooKeeperServer;
+import org.apache.zookeeper.server.ZooKeeperServerBean;
+import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
+
+/**
+ * A ZooKeeperServer which comes into play when peer is partitioned from the
+ * majority. Handles read-only clients, but drops connections from not-read-only
+ * ones.
+ * <p>
+ * The very first processor in the chain of request processors is a
+ * ReadOnlyRequestProcessor which drops state-changing requests.
+ */
+public class ReadOnlyZooKeeperServer extends ZooKeeperServer {
+
+ protected final QuorumPeer self;
+ private volatile boolean shutdown = false;
+
+ ReadOnlyZooKeeperServer(FileTxnSnapLog logFactory, QuorumPeer self, ZKDatabase zkDb) {
+ super(
+ logFactory,
+ self.tickTime,
+ self.minSessionTimeout,
+ self.maxSessionTimeout,
+ self.clientPortListenBacklog,
+ zkDb,
+ self.getInitialConfig(),
+ self.isReconfigEnabled());
+ this.self = self;
+ }
+
+ @Override
+ protected void setupRequestProcessors() {
+ RequestProcessor finalProcessor = new FinalRequestProcessor(this);
+ RequestProcessor prepProcessor = new PrepRequestProcessor(this, finalProcessor);
+ ((PrepRequestProcessor) prepProcessor).start();
+ firstProcessor = new ReadOnlyRequestProcessor(this, prepProcessor);
+ ((ReadOnlyRequestProcessor) firstProcessor).start();
+ }
+
+ @Override
+ public synchronized void startup() {
+ // check to avoid startup follows shutdown
+ if (shutdown) {
+ LOG.warn("Not starting Read-only server as startup follows shutdown!");
+ return;
+ }
+ registerJMX(new ReadOnlyBean(this), self.jmxLocalPeerBean);
+ super.startup();
+ self.setZooKeeperServer(this);
+ self.adminServer.setZooKeeperServer(this);
+ LOG.info("Read-only server started");
+ }
+
+ @Override
+ public void createSessionTracker() {
+ sessionTracker = new LearnerSessionTracker(
+ this, getZKDatabase().getSessionWithTimeOuts(),
+ this.tickTime, self.getMyId(), self.areLocalSessionsEnabled(),
+ getZooKeeperServerListener());
+ }
+
+ @Override
+ protected void startSessionTracker() {
+ ((LearnerSessionTracker) sessionTracker).start();
+ }
+
+ @Override
+ protected void setLocalSessionFlag(Request si) {
+ switch (si.type) {
+ case OpCode.createSession:
+ if (self.areLocalSessionsEnabled()) {
+ si.setLocalSession(true);
+ }
+ break;
+ case OpCode.closeSession:
+ if (((UpgradeableSessionTracker) sessionTracker).isLocalSession(si.sessionId)) {
+ si.setLocalSession(true);
+ } else {
+ LOG.warn("Submitting global closeSession request for session 0x{} in ReadOnly mode",
+ Long.toHexString(si.sessionId));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void validateSession(ServerCnxn cnxn, long sessionId) throws IOException {
+ if (((LearnerSessionTracker) sessionTracker).isGlobalSession(sessionId)) {
+ String msg = "Refusing global session reconnection in RO mode " + cnxn.getRemoteSocketAddress();
+ LOG.info(msg);
+ throw new ServerCnxn.CloseRequestException(msg, ServerCnxn.DisconnectReason.RENEW_GLOBAL_SESSION_IN_RO_MODE);
+ }
+ }
+
+ @Override
+ protected void registerJMX() {
+ // register with JMX
+ try {
+ jmxDataTreeBean = new DataTreeBean(getZKDatabase().getDataTree());
+ MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxDataTreeBean = null;
+ }
+ }
+
+ public void registerJMX(ZooKeeperServerBean serverBean, LocalPeerBean localPeerBean) {
+ // register with JMX
+ try {
+ jmxServerBean = serverBean;
+ MBeanRegistry.getInstance().register(serverBean, localPeerBean);
+ } catch (Exception e) {
+ LOG.warn("Failed to register with JMX", e);
+ jmxServerBean = null;
+ }
+ }
+
+ @Override
+ protected void unregisterJMX() {
+ // unregister from JMX
+ try {
+ if (jmxDataTreeBean != null) {
+ MBeanRegistry.getInstance().unregister(jmxDataTreeBean);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ jmxDataTreeBean = null;
+ }
+
+ protected void unregisterJMX(ZooKeeperServer zks) {
+ // unregister from JMX
+ try {
+ if (jmxServerBean != null) {
+ MBeanRegistry.getInstance().unregister(jmxServerBean);
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to unregister with JMX", e);
+ }
+ jmxServerBean = null;
+ }
+
+ @Override
+ public String getState() {
+ return "read-only";
+ }
+
+ /**
+ * Returns the id of the associated QuorumPeer, which will do for a unique
+ * id of this server.
+ */
+ @Override
+ public long getServerId() {
+ return self.getMyId();
+ }
+
+ @Override
+ public synchronized void shutdown(boolean fullyShutDown) {
+ if (!canShutdown()) {
+ LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!");
+ } else {
+ shutdown = true;
+ unregisterJMX(this);
+
+ // set peer's server to null
+ self.setZooKeeperServer(null);
+ // clear all the connections
+ self.closeAllConnections();
+
+ self.adminServer.setZooKeeperServer(null);
+ }
+ // shutdown the server itself
+ super.shutdown(fullyShutDown);
+ }
+
+ @Override
+ public void dumpConf(PrintWriter pwriter) {
+ super.dumpConf(pwriter);
+
+ pwriter.print("initLimit=");
+ pwriter.println(self.getInitLimit());
+ pwriter.print("syncLimit=");
+ pwriter.println(self.getSyncLimit());
+ pwriter.print("electionAlg=");
+ pwriter.println(self.getElectionType());
+ pwriter.print("electionPort=");
+ pwriter.println(self.getElectionAddress().getAllPorts()
+ .stream().map(Objects::toString).collect(Collectors.joining("|")));
+ pwriter.print("quorumPort=");
+ pwriter.println(self.getQuorumAddress().getAllPorts()
+ .stream().map(Objects::toString).collect(Collectors.joining("|")));
+ pwriter.print("peerType=");
+ pwriter.println(self.getLearnerType().ordinal());
+ }
+
+ @Override
+ protected void setState(State state) {
+ this.state = state;
+ }
+
+}
diff --git a/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java
new file mode 100644
index 00000000000..d65ead216f0
--- /dev/null
+++ b/zookeeper-server/zookeeper-server-3.9.1/src/main/java/org/apache/zookeeper/server/quorum/SendAckRequestProcessor.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import java.io.Flushable;
+import java.io.IOException;
+import java.net.Socket;
+import org.apache.zookeeper.ZooDefs.OpCode;
+import org.apache.zookeeper.server.Request;
+import org.apache.zookeeper.server.RequestProcessor;
+import org.apache.zookeeper.server.ServerMetrics;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SendAckRequestProcessor implements RequestProcessor, Flushable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SendAckRequestProcessor.class);
+
+ final Learner learner;
+
+ SendAckRequestProcessor(Learner peer) {
+ this.learner = peer;
+ }
+
+ public void processRequest(Request si) {
+ if (si.type != OpCode.sync) {
+ QuorumPacket qp = new QuorumPacket(Leader.ACK, si.getHdr().getZxid(), null, null);
+ try {
+ si.logLatency(ServerMetrics.getMetrics().PROPOSAL_ACK_CREATION_LATENCY);
+
+ learner.writePacket(qp, false);
+ } catch (IOException e) {
+ LOG.warn("Closing connection to leader, exception during packet send", e);
+ try {
+ if (!learner.sock.isClosed()) {
+ learner.sock.close();
+ }
+ } catch (IOException e1) {
+ // Nothing to do, we are shutting things down, so an exception here is irrelevant
+ LOG.debug("Ignoring error closing the connection", e1);
+ }
+ }
+ }
+ }
+
+ public void flush() throws IOException {
+ try {
+ learner.writePacket(null, true);
+ } catch (IOException e) {
+ LOG.warn("Closing connection to leader, exception during packet send", e);
+ try {
+ Socket socket = learner.sock;
+ if (socket != null && !socket.isClosed()) {
+ learner.sock.close();
+ }
+ } catch (IOException e1) {
+ // Nothing to do, we are shutting things down, so an exception here is irrelevant
+ LOG.debug("Ignoring error closing the connection", e1);
+ }
+ }
+ }
+
+ public void shutdown() {
+ // Nothing needed
+ }
+
+}