pigeonhole-db 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Cargo.lock +899 -0
- data/Cargo.toml +45 -0
- data/LICENSE +21 -0
- data/README.md +22 -0
- data/extconf.rb +12 -0
- data/lib/pigeonhole/api.rb +615 -0
- data/lib/pigeonhole/index.rb +208 -0
- data/lib/pigeonhole/memory.rb +507 -0
- data/lib/pigeonhole/version.rb +6 -0
- data/lib/pigeonhole-db.rb +6 -0
- data/lib/pigeonhole.rb +47 -0
- data/sig/pigeonhole.rbs +1484 -0
- data/src/budget.rs +205 -0
- data/src/classes.rs +279 -0
- data/src/convert.rs +192 -0
- data/src/db.rs +685 -0
- data/src/error.rs +151 -0
- data/src/feed.rs +316 -0
- data/src/fork.rs +64 -0
- data/src/gvl.rs +112 -0
- data/src/index.rs +1157 -0
- data/src/lib.rs +534 -0
- data/src/memory.rs +2178 -0
- data/src/read.rs +312 -0
- data/src/table.rs +1161 -0
- data/src/vector.rs +262 -0
- data/src/walk.rs +406 -0
- data/vendor/bundle/ruby/3.4.0/bin/minitest +29 -0
- data/vendor/bundle/ruby/3.4.0/bin/rake +29 -0
- data/vendor/bundle/ruby/3.4.0/bin/rake-compiler +29 -0
- data/vendor/bundle/ruby/3.4.0/bin/rake-compiler-dock +29 -0
- data/vendor/bundle/ruby/3.4.0/bin/rb-sys-dock +29 -0
- data/vendor/bundle/ruby/3.4.0/bin/rbs +29 -0
- data/vendor/bundle/ruby/3.4.0/cache/drb-2.2.3.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/logger-1.7.0.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/minitest-6.0.6.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/prism-1.9.0.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/rake-13.4.2.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/rake-compiler-1.3.2.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/rake-compiler-dock-1.12.0.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/rb_sys-0.9.130.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/rbs-4.2.0.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/toml-rb-6.0.0.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/cache/tsort-0.2.0.gem +0 -0
- data/vendor/bundle/ruby/3.4.0/extensions/x86_64-linux/3.4.0/prism-1.9.0/gem.build_complete +0 -0
- data/vendor/bundle/ruby/3.4.0/extensions/x86_64-linux/3.4.0/prism-1.9.0/gem_make.out +44 -0
- data/vendor/bundle/ruby/3.4.0/extensions/x86_64-linux/3.4.0/prism-1.9.0/mkmf.log +74 -0
- data/vendor/bundle/ruby/3.4.0/extensions/x86_64-linux/3.4.0/prism-1.9.0/prism/prism.so +0 -0
- data/vendor/bundle/ruby/3.4.0/extensions/x86_64-linux/3.4.0/rbs-4.2.0/gem.build_complete +0 -0
- data/vendor/bundle/ruby/3.4.0/extensions/x86_64-linux/3.4.0/rbs-4.2.0/gem_make.out +41 -0
- data/vendor/bundle/ruby/3.4.0/extensions/x86_64-linux/3.4.0/rbs-4.2.0/mkmf.log +102 -0
- data/vendor/bundle/ruby/3.4.0/extensions/x86_64-linux/3.4.0/rbs-4.2.0/rbs_extension.so +0 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/LICENSE.txt +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/drb.gemspec +42 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/acl.rb +239 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/drb.rb +1993 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/eq.rb +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/extserv.rb +44 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/extservm.rb +94 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/gw.rb +161 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/observer.rb +26 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/ssl.rb +354 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/timeridconv.rb +97 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/unix.rb +118 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/version.rb +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb/weakidconv.rb +31 -0
- data/vendor/bundle/ruby/3.4.0/gems/drb-2.2.3/lib/drb.rb +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/BSDL +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/COPYING +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/README.md +104 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/lib/logger/errors.rb +9 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/lib/logger/formatter.rb +36 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/lib/logger/log_device.rb +265 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/lib/logger/period.rb +47 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/lib/logger/severity.rb +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/lib/logger/version.rb +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/logger-1.7.0/lib/logger.rb +789 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/History.rdoc +1860 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/Manifest.txt +41 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/README.rdoc +763 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/Rakefile +89 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/bin/minitest +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/design_rationale.rb +54 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/hoe/minitest.rb +30 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/assertions.rb +821 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/autorun.rb +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/benchmark.rb +452 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/bisect.rb +304 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/complete.rb +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/compress.rb +94 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/error_on_warning.rb +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/expectations.rb +321 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/find_minimal_combination.rb +127 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/hell.rb +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/manual_plugins.rb +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/parallel.rb +72 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/path_expander.rb +432 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/pride.rb +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/pride_plugin.rb +135 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/server.rb +49 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/server_plugin.rb +88 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/spec.rb +324 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/sprint.rb +105 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/sprint_plugin.rb +39 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/test.rb +232 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest/test_task.rb +331 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/lib/minitest.rb +1232 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/metametameta.rb +150 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_bisect.rb +249 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_find_minimal_combination.rb +138 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_minitest_assertions.rb +1729 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_minitest_benchmark.rb +151 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_minitest_reporter.rb +437 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_minitest_spec.rb +1095 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_minitest_test.rb +1295 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_minitest_test_task.rb +57 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_path_expander.rb +229 -0
- data/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.6/test/minitest/test_server.rb +146 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/BSDmakefile +58 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/CHANGELOG.md +786 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/CODE_OF_CONDUCT.md +76 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/CONTRIBUTING.md +58 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/LICENSE.md +7 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/Makefile +116 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/README.md +143 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/config.yml +4739 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/build_system.md +119 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/configuration.md +68 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/cruby_compilation.md +27 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/design.md +53 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/encoding.md +121 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/fuzzing.md +88 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/heredocs.md +36 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/javascript.md +118 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/local_variable_depth.md +229 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/mapping.md +117 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/parser_translation.md +24 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/parsing_rules.md +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/releasing.md +75 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/relocation.md +34 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/ripper_translation.md +63 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/ruby_api.md +45 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/ruby_parser_translation.md +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/serialization.md +233 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/docs/testing.md +55 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/ext/prism/Makefile +273 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/ext/prism/api_node.c +6945 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/ext/prism/api_pack.c +276 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/ext/prism/extconf.rb +127 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/ext/prism/extension.c +1427 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/ext/prism/extension.h +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/ast.h +8254 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/defines.h +260 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/diagnostic.h +458 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/encoding.h +283 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/node.h +129 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/options.h +488 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/pack.h +163 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/parser.h +936 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/prettyprint.h +34 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/regexp.h +43 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/static_literals.h +121 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_buffer.h +236 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_char.h +204 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_constant_pool.h +218 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_integer.h +130 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_list.h +103 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_memchr.h +29 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_newline_list.h +113 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_string.h +200 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_strncasecmp.h +32 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/util/pm_strpbrk.h +46 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism/version.h +29 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/include/prism.h +408 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/compiler.rb +801 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/desugar_compiler.rb +392 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/dispatcher.rb +2210 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/dot_visitor.rb +4767 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/dsl.rb +1003 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/ffi.rb +578 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/inspect_visitor.rb +2393 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/lex_compat.rb +911 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/mutation_compiler.rb +772 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/node.rb +19966 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/node_ext.rb +511 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/pack.rb +230 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/parse_result/comments.rb +188 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/parse_result/errors.rb +66 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/parse_result/newlines.rb +155 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/parse_result.rb +907 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/pattern.rb +269 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/polyfill/append_as_bytes.rb +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/polyfill/byteindex.rb +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/polyfill/scan_byte.rb +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/polyfill/unpack1.rb +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/polyfill/warn.rb +36 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/prism.so +0 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/reflection.rb +416 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/relocation.rb +505 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/serialize.rb +2400 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/string_query.rb +31 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/parser/builder.rb +62 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/parser/compiler.rb +2234 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/parser/lexer.rb +820 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/parser.rb +376 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/parser_current.rb +26 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/parser_versions.rb +36 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/ripper/filter.rb +53 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/ripper/lexer.rb +135 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/ripper/sexp.rb +126 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/ripper/shim.rb +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/ripper.rb +3520 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation/ruby_parser.rb +1964 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/translation.rb +18 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism/visitor.rb +813 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/lib/prism.rb +98 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/prism.gemspec +172 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/compiler.rbi +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/dsl.rbi +524 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/inspect_visitor.rbi +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/node.rbi +8750 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/node_ext.rbi +107 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/parse_result.rbi +404 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/reflection.rbi +58 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/string_query.rbi +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/translation/parser.rbi +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/translation/parser_versions.rbi +23 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/translation/ripper.rbi +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism/visitor.rbi +473 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/rbi/prism.rbi +63 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/compiler.rbs +9 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/dispatcher.rbs +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/dot_visitor.rbs +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/dsl.rbs +351 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/inspect_visitor.rbs +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/lex_compat.rbs +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/mutation_compiler.rbs +159 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/node.rbs +4042 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/node_ext.rbs +149 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/pack.rbs +43 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/parse_result/comments.rbs +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/parse_result.rbs +197 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/pattern.rbs +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/reflection.rbs +50 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/relocation.rbs +185 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/serialize.rbs +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/string_query.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism/visitor.rbs +169 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/sig/prism.rbs +272 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/diagnostic.c +854 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/encoding.c +5340 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/node.c +8685 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/options.c +338 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/pack.c +509 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/prettyprint.c +8957 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/prism.c +22679 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/regexp.c +790 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/serialize.c +2274 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/static_literals.c +617 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/token_type.c +703 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_buffer.c +357 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_char.c +318 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_constant_pool.c +342 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_integer.c +670 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_list.c +49 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_memchr.c +35 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_newline_list.c +125 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_string.c +381 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_strncasecmp.c +36 -0
- data/vendor/bundle/ruby/3.4.0/gems/prism-1.9.0/src/util/pm_strpbrk.c +206 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/History.rdoc +2454 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/MIT-LICENSE +21 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/README.rdoc +155 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/command_line_usage.rdoc +171 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/example/Rakefile1 +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/example/Rakefile2 +35 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/example/a.c +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/example/b.c +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/example/main.c +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/glossary.rdoc +42 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/jamis.rb +592 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/proto_rake.rdoc +127 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/rake.1 +156 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/rakefile.rdoc +635 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/doc/rational.rdoc +151 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/exe/rake +27 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb +847 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/backtrace.rb +25 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/clean.rb +78 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/cloneable.rb +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/cpu_counter.rb +122 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/default_loader.rb +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/dsl_definition.rb +196 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/early_time.rb +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/ext/core.rb +26 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/ext/string.rb +176 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/file_creation_task.rb +25 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/file_list.rb +435 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/file_task.rb +58 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/file_utils.rb +137 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/file_utils_ext.rb +135 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/invocation_chain.rb +57 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/invocation_exception_mixin.rb +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/late_time.rb +18 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/linked_list.rb +112 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/loaders/makefile.rb +54 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/multi_task.rb +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/name_space.rb +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/options.rb +31 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/packagetask.rb +222 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/phony.rb +16 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/private_reader.rb +21 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/promise.rb +100 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/pseudo_status.rb +30 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/rake_module.rb +67 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/rake_test_loader.rb +27 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/rule_recursion_overflow_error.rb +20 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/scope.rb +43 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb +434 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task_argument_error.rb +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task_arguments.rb +113 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task_manager.rb +333 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/tasklib.rb +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/testtask.rb +192 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/thread_history_display.rb +49 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/thread_pool.rb +157 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/trace_output.rb +23 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/version.rb +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/win32.rb +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake.rb +69 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/rake.gemspec +102 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/Gemfile +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/History.md +708 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/LICENSE.txt +20 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/README.md +505 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/Rakefile +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/appveyor.yml +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/bin/rake-compiler +24 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/cucumber.yml +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/compile.feature +79 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/cross-compile.feature +23 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/cross-package-multi.feature +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/cross-package.feature +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/java-compile.feature +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/java-no-native-compile.feature +33 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/java-package.feature +24 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/package.feature +40 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/step_definitions/compilation.rb +70 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/step_definitions/cross_compilation.rb +27 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/step_definitions/execution.rb +52 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/step_definitions/folders.rb +32 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/step_definitions/gem.rb +46 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/step_definitions/java_compilation.rb +7 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/support/env.rb +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/support/file_template_helpers.rb +137 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/support/generator_helpers.rb +123 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/features/support/platform_extension_helpers.rb +27 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/lib/rake/baseextensiontask.rb +90 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/lib/rake/compiler_config.rb +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/lib/rake/extensioncompiler.rb +51 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/lib/rake/extensiontask.rb +621 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/lib/rake/javaextensiontask.rb +321 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/tasks/bin/cross-ruby.rake +189 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/tasks/bootstrap.rake +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/tasks/common.rake +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/tasks/cucumber.rake +23 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/tasks/gem.rake +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-1.3.2/tasks/rspec.rake +9 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/CHANGELOG.md +446 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/CONTRIBUTING.md +109 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/Dockerfile.jruby +79 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/Dockerfile.mri.erb +282 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/Gemfile +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/LICENSE.txt +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/README.md +447 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/Rakefile +246 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/bin/rake-compiler-dock +18 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/buildkitd.toml +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/gem_helper.rb +54 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/mk_i686.rb +18 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/mk_musl_cross.sh +37 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/mk_osxcross.sh +45 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/mk_pkg_config.sh +24 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/parallel_docker_build.rb +169 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/patches/rake-compiler-1.3.1/0004-Enable-build-of-static-libruby.patch +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/patches/rake-compiler-1.3.1/0005-build-miniruby-first.patch +16 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/patches/rake-compiler-1.3.1/0006-ruby-4-rubyspec-capiext.patch +16 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/rcd-env.sh +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/runas +7 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/sigfw.c +45 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/strip_wrapper_codesign +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/strip_wrapper_vbox +30 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/build/sudoers +1 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/colors.rb +43 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/docker_check.rb +356 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/predefined_user_group.rb +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/starter.rb +206 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/version.rb +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock.rb +151 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/Dockerfile +66 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/README.md +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/binutils-mingw-w64-ignore-check-errors.patch +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/gcc-mingw-w64-only-c-c++.patch +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/mingw-w64-enable-ucrt.patch +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/rake-compiler-dock.gemspec +34 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/env/Dockerfile.alpine +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/env/Dockerfile.debian +24 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/fixtures/mig_test_rpc.defs +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/Gemfile +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/Rakefile +97 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/java/RcdTestExtService.java +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/java/RubyRcdTest.java +16 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/mri/extconf.rb +111 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/mri/rcd_test_ext.c +65 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/mri/rcd_test_ext.h +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/lib/rcd_test.rb +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/rcd_test.gemspec +28 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/test/test_basic.rb +49 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/test_environment_variables.rb +108 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/test_mig.rb +18 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/test_parallel_docker_build.rb +95 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/test_rubygems_plugins.rb +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/test_starter.rb +158 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.12.0/test/test_versions.rb +82 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/LICENSE-APACHE +190 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/LICENSE-MIT +21 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/README.md +42 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/certs/ianks.pem +21 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/exe/rb-sys-dock +495 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/cargo/config.rb +72 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/cargo/metadata.rb +158 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/cargo.rb +9 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/cargo_builder/link_flag_converter.rb +37 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/cargo_builder.rb +370 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/error.rb +43 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/extensiontask.rb +188 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/mkmf/config.rb +75 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/mkmf.rb +413 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/toolchain_info/data.rb +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/toolchain_info.rb +82 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/util/logger.rb +73 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys/version.rb +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rb_sys-0.9.130/lib/rb_sys.rb +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/BSDL +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/CHANGELOG.md +2463 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/COPYING +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/Dockerfile.jruby +53 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/README.md +240 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/Rakefile +1213 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/Steepfile +55 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/config.yml +920 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/array.rbs +4205 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/basic_object.rbs +376 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/binding.rbs +148 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/builtin.rbs +278 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/class.rbs +223 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/comparable.rbs +192 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/complex.rbs +812 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/constants.rbs +96 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/data.rbs +415 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/dir.rbs +993 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/encoding.rbs +1368 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/enumerable.rbs +2506 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/enumerator/arithmetic_sequence.rbs +70 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/enumerator/product.rbs +92 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/enumerator.rbs +705 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/env.rbs +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/errno.rbs +682 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/errors.rbs +789 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/exception.rbs +485 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/false_class.rbs +82 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/fiber.rbs +570 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/fiber_error.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/file.rbs +2045 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/file_constants.rbs +463 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/file_stat.rbs +534 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/file_test.rbs +331 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/float.rbs +1292 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/gc.rbs +788 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/global_variables.rbs +184 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/hash.rbs +2183 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/integer.rbs +1374 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/io/buffer.rbs +995 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/io/wait.rbs +48 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/io.rbs +3473 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/kernel.rbs +3172 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/marshal.rbs +207 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/match_data.rbs +637 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/math.rbs +770 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/method.rbs +422 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/module.rbs +1856 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/nil_class.rbs +210 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/numeric.rbs +832 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/object.rbs +108 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/object_space/weak_key_map.rbs +166 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/object_space.rbs +197 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/pathname.rbs +1312 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/proc.rbs +905 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/process.rbs +2316 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/ractor.rbs +914 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/random.rbs +255 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/range.rbs +1209 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rational.rbs +502 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rb_config.rbs +88 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rbs/ops.rbs +154 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rbs/unnamed/argf.rbs +1236 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rbs/unnamed/env_class.rbs +1214 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rbs/unnamed/main_class.rbs +123 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rbs/unnamed/random.rbs +186 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/refinement.rbs +59 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/regexp.rbs +1974 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/ruby.rbs +53 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/ruby_vm.rbs +809 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/basic_specification.rbs +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/config_file.rbs +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/dependency_installer.rbs +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/errors.rbs +112 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/installer.rbs +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/path_support.rbs +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/platform.rbs +7 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/request_set.rbs +49 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/requirement.rbs +138 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/rubygems.rbs +1108 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/source_list.rbs +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/specification.rbs +31 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/stream_ui.rbs +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/uninstaller.rbs +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/rubygems/version.rbs +133 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/set.rbs +751 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/signal.rbs +110 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/string.rbs +5532 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/struct.rbs +668 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/symbol.rbs +482 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/thread.rbs +1821 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/thread_group.rbs +79 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/time.rbs +1793 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/trace_point.rbs +483 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/true_class.rbs +98 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/unbound_method.rbs +337 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/core/warning.rbs +87 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/CONTRIBUTING.md +107 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/aliases.md +79 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/architecture.md +110 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/collection.md +192 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/config.md +171 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/data_and_struct.md +86 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/encoding.md +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/gem.md +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/inline.md +634 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/rbs_by_example.md +309 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/release.md +303 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/repo.md +125 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/rust.md +96 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/sigs.md +167 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/stdlib.md +155 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/syntax.md +952 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/tools.md +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/type_fingerprint.md +21 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/docs/wasm_serialization.md +80 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/exe/rbs +7 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/Makefile +273 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/ast_translation.c +1870 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/ast_translation.h +41 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/class_constants.c +189 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/class_constants.h +105 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/compat.h +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/extconf.rb +40 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/legacy_location.c +299 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/legacy_location.h +82 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/main.c +649 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/rbs_extension.h +16 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/rbs_string_bridging.c +9 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/ext/rbs_extension/rbs_string_bridging.h +24 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/goodcheck.yml +91 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/ast.h +1055 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/defines.h +104 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/lexer.h +208 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/location.h +40 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/parser.h +187 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/serialize.h +39 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/string.h +47 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/util/rbs_allocator.h +59 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/util/rbs_assert.h +20 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/util/rbs_buffer.h +83 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/util/rbs_constant_pool.h +155 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/util/rbs_encoding.h +282 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs/util/rbs_unescape.h +24 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/include/rbs.h +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ancestor_graph.rb +92 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/annotate/annotations.rb +199 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/annotate/formatter.rb +82 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/annotate/rdoc_annotator.rb +398 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/annotate/rdoc_source.rb +131 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/annotate.rb +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/annotation.rb +29 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/comment.rb +29 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/declarations.rb +472 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/directives.rb +49 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/members.rb +451 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/ruby/annotations.rb +451 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/ruby/comment_block.rb +247 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/ruby/declarations.rb +291 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/ruby/helpers/constant_helper.rb +28 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/ruby/helpers/location_helper.rb +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/ruby/members.rb +762 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/type_param.rb +235 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/ast/visitor.rb +137 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/buffer.rb +189 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/builtin_names.rb +58 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/cli/colored_io.rb +48 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/cli/diff.rb +84 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/cli/validate.rb +294 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/cli.rb +1253 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/cleaner.rb +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/config/lockfile.rb +92 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/config/lockfile_generator.rb +269 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/config.rb +81 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/installer.rb +32 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/sources/base.rb +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/sources/git.rb +265 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/sources/local.rb +81 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/sources/rubygems.rb +48 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/sources/stdlib.rb +50 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection/sources.rb +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/collection.rb +16 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/constant.rb +28 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/definition.rb +415 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/definition_builder/ancestor_builder.rb +681 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/definition_builder/method_builder.rb +295 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/definition_builder.rb +1057 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/diff.rb +131 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/environment/class_entry.rb +81 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/environment/module_entry.rb +91 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/environment/use_map.rb +77 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/environment.rb +1028 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/environment_loader.rb +167 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/environment_walker.rb +155 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/errors.rb +634 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/factory.rb +18 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/file_finder.rb +28 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/inline_parser/comment_association.rb +117 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/inline_parser.rb +568 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/location_aux.rb +170 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/locator.rb +247 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/method_type.rb +145 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/namespace.rb +154 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/parser/lex_result.rb +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/parser/token.rb +23 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/parser_aux.rb +144 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/prototype/helpers.rb +164 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/prototype/node_usage.rb +99 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/prototype/rb.rb +816 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/prototype/rbi.rb +793 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/prototype/runtime/helpers.rb +59 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/prototype/runtime/reflection.rb +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/prototype/runtime/value_object_generator.rb +278 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/prototype/runtime.rb +680 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/repository.rb +127 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/resolver/constant_resolver.rb +219 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/resolver/type_name_resolver.rb +167 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/rewriter.rb +70 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/sorter.rb +198 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/source.rb +99 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/substitution.rb +83 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/subtractor.rb +204 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test/errors.rb +80 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test/guaranteed.rb +30 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test/hook.rb +212 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test/observer.rb +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test/setup.rb +84 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test/setup_helper.rb +50 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test/tester.rb +167 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test/type_check.rb +457 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/test.rb +112 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/type_alias_dependency.rb +100 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/type_alias_regularity.rb +126 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/type_name.rb +122 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/types.rb +1646 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/unit_test/convertibles.rb +177 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/unit_test/spy.rb +138 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/unit_test/type_assertions.rb +383 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/unit_test/with_aliases.rb +145 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/unit_test.rb +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/validator.rb +186 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/variance_calculator.rb +189 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/vendorer.rb +71 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/version.rb +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/wasm/deserializer.rb +213 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/wasm/location.rb +61 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/wasm/parser.rb +174 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/wasm/runtime.rb +211 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/wasm/serialization_schema.rb +111 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs/writer.rb +424 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs.rb +117 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs_extension.so +0 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rbs_jars.rb +39 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rdoc/discover.rb +20 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/lib/rdoc_plugin/parser.rb +168 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/rbs.gemspec +77 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/annotation.json +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/comment.json +26 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/decls.json +326 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/function.json +98 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/location.json +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/members.json +266 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/methodType.json +50 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/typeParam.json +52 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/schema/types.json +317 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/ancestor_builder.rbs +163 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/ancestor_graph.rbs +60 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/annotate/annotations.rbs +102 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/annotate/formatter.rbs +24 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/annotate/rdoc_annotater.rbs +85 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/annotate/rdoc_source.rbs +32 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/annotation.rbs +27 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/ast/ruby/annotations.rbs +470 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/ast/ruby/comment_block.rbs +127 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/ast/ruby/declarations.rbs +158 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/ast/ruby/helpers/constant_helper.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/ast/ruby/helpers/location_helper.rbs +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/ast/ruby/members.rbs +198 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/buffer.rbs +108 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/builtin_names.rbs +44 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/cli/colored_io.rbs +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/cli/diff.rbs +15 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/cli/validate.rbs +47 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/cli.rbs +89 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/collection/cleaner.rbs +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/collection/config/lockfile.rbs +74 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/collection/config/lockfile_generator.rbs +68 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/collection/config.rbs +46 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/collection/installer.rbs +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/collection/sources.rbs +214 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/collection.rbs +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/comment.rbs +26 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/constant.rbs +21 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/declarations.rbs +274 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/definition.rbs +232 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/definition_builder.rbs +181 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/diff.rbs +28 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/directives.rbs +77 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/environment/class_entry.rbs +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/environment/module_entry.rbs +65 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/environment.rbs +286 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/environment_loader.rbs +111 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/environment_walker.rbs +65 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/errors.rbs +408 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/factory.rbs +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/file_finder.rbs +28 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/inline_parser/comment_association.rbs +71 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/inline_parser.rbs +126 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/location.rbs +135 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/locator.rbs +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/manifest.yaml +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/members.rbs +258 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/method_builder.rbs +89 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/method_types.rbs +58 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/namespace.rbs +161 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/parser.rbs +164 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/prototype/helpers.rbs +29 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/prototype/node_usage.rbs +20 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/prototype/rb.rbs +96 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/prototype/rbi.rbs +104 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/prototype/runtime.rbs +182 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/rbs.rbs +21 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/rdoc/rbs.rbs +67 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/repository.rbs +85 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/resolver/constant_resolver.rbs +92 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/resolver/context.rbs +34 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/resolver/type_name_resolver.rbs +61 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/rewriter.rbs +45 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/shims/bundler.rbs +38 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/shims/enumerable.rbs +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/shims/rubygems.rbs +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/sorter.rbs +41 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/source.rbs +48 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/substitution.rbs +48 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/subtractor.rbs +37 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/test/errors.rbs +52 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/test/guranteed.rbs +9 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/test/type_check.rbs +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/test.rbs +82 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/type_alias_dependency.rbs +53 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/type_alias_regularity.rbs +98 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/type_param.rbs +115 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/typename.rbs +89 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/types.rbs +588 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/unit_test/convertibles.rbs +154 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/unit_test/spy.rbs +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/unit_test/type_assertions.rbs +211 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/unit_test/with_aliases.rbs +136 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/use_map.rbs +35 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/util.rbs +9 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/validator.rbs +63 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/variance_calculator.rbs +87 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/vendorer.rbs +51 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/version.rbs +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/visitor.rbs +47 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/wasm/deserializer.rbs +66 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/wasm/serialization_schema.rbs +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/sig/writer.rbs +127 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/ast.c +1644 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/lexer.c +3223 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/lexer.re +187 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/lexstate.c +222 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/location.c +31 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/parser.c +4318 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/serialize.c +965 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/string.c +41 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/util/rbs_allocator.c +171 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/util/rbs_assert.c +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/util/rbs_buffer.c +54 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/util/rbs_constant_pool.c +268 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/util/rbs_encoding.c +21454 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/src/util/rbs_unescape.c +167 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/abbrev/0/abbrev.rbs +66 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/abbrev/0/array.rbs +26 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/base64/0/base64.rbs +355 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/benchmark/0/benchmark.rbs +452 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/bigdecimal/0/big_decimal.rbs +1647 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/bigdecimal-math/0/big_math.rbs +280 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/bigdecimal-math/0/manifest.yaml +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/cgi/0/core.rbs +911 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/cgi/0/manifest.yaml +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/cgi-escape/0/escape.rbs +171 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/coverage/0/coverage.rbs +266 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/csv/0/csv.rbs +3776 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/csv/0/manifest.yaml +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/date/0/date.rbs +1598 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/date/0/date_time.rbs +617 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/date/0/time.rbs +26 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/dbm/0/dbm.rbs +421 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/delegate/0/delegator.rbs +188 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/delegate/0/kernel.rbs +47 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/delegate/0/simple_delegator.rbs +96 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/did_you_mean/0/did_you_mean.rbs +344 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/digest/0/digest.rbs +693 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/erb/0/erb.rbs +933 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/etc/0/etc.rbs +884 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/fileutils/0/fileutils.rbs +1753 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/find/0/find.rbs +49 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/forwardable/0/forwardable.rbs +271 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/io-console/0/io-console.rbs +414 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/ipaddr/0/ipaddr.rbs +431 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/json/0/json.rbs +1963 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/kconv/0/kconv.rbs +166 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/logger/0/formatter.rbs +45 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/logger/0/log_device.rbs +100 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/logger/0/logger.rbs +796 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/logger/0/manifest.yaml +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/logger/0/period.rbs +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/logger/0/severity.rbs +34 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/kernel.rbs +42 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/abstract_reporter.rbs +52 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/assertion.rbs +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/assertions.rbs +590 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/backtrace_filter.rbs +23 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/bench_spec.rbs +102 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/benchmark.rbs +259 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/composite_reporter.rbs +25 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/compress.rbs +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/error_on_warning.rbs +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/expectation.rbs +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/expectations.rbs +21 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/guard.rbs +64 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/mock.rbs +64 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/parallel/executor.rbs +46 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/parallel/test/class_methods.rbs +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/parallel/test.rbs +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/parallel.rbs +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/pride_io.rbs +62 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/pride_lol.rbs +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/progress_reporter.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/reportable.rbs +53 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/reporter.rbs +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/result.rbs +28 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/runnable.rbs +163 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/skip.rbs +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/spec/dsl/instance_methods.rbs +48 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/spec/dsl.rbs +129 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/spec.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/statistics_reporter.rbs +81 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/summary_reporter.rbs +18 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/test/lifecycle_hooks.rbs +92 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/test.rbs +69 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/unexpected_error.rbs +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/unexpected_warning.rbs +6 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/unit/test_case.rbs +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest/unit.rbs +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/minitest/0/minitest.rbs +115 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/monitor/0/monitor.rbs +363 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/mutex_m/0/mutex_m.rbs +104 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/net-http/0/manifest.yaml +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/net-http/0/net-http.rbs +5580 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/net-protocol/0/manifest.yaml +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/net-protocol/0/net-protocol.rbs +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/net-smtp/0/manifest.yaml +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/net-smtp/0/net-smtp.rbs +55 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/nkf/0/nkf.rbs +402 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/objspace/0/objspace.rbs +470 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/observable/0/observable.rbs +217 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/open-uri/0/manifest.yaml +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/open-uri/0/open-uri.rbs +433 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/open3/0/open3.rbs +606 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/openssl/0/manifest.yaml +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/openssl/0/openssl.rbs +12237 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/optparse/0/optparse.rbs +1734 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/pathname/0/pathname.rbs +36 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/pp/0/manifest.yaml +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/pp/0/pp.rbs +301 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/prettyprint/0/prettyprint.rbs +383 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/pstore/0/pstore.rbs +608 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/psych/0/core_ext.rbs +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/psych/0/dbm.rbs +237 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/psych/0/manifest.yaml +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/psych/0/psych.rbs +455 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/psych/0/store.rbs +57 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/pty/0/pty.rbs +240 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/random-formatter/0/random-formatter.rbs +277 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/code_object.rbs +52 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/comment.rbs +61 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/context.rbs +153 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/markup.rbs +117 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/options.rbs +76 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/parser.rbs +56 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/rdoc.rbs +393 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/ri.rbs +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/store.rbs +48 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/rdoc/0/top_level.rbs +97 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/resolv/0/manifest.yaml +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/resolv/0/resolv.rbs +1787 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/ripper/0/ripper.rbs +1654 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/securerandom/0/manifest.yaml +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/securerandom/0/securerandom.rbs +49 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/shellwords/0/shellwords.rbs +229 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/singleton/0/singleton.rbs +137 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/addrinfo.rbs +666 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/basic_socket.rbs +590 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/constants.rbs +2295 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/ip_socket.rbs +94 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/socket.rbs +4170 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/socket_error.rbs +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/tcp_server.rbs +192 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/tcp_socket.rbs +87 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/udp_socket.rbs +133 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/unix_server.rbs +169 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/socket/0/unix_socket.rbs +172 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/stringio/0/stringio.rbs +1682 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/strscan/0/string_scanner.rbs +1648 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/tempfile/0/manifest.yaml +3 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/tempfile/0/tempfile.rbs +483 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/time/0/time.rbs +434 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/timeout/0/timeout.rbs +132 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/tmpdir/0/tmpdir.rbs +69 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/tsort/0/cyclic.rbs +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/tsort/0/interfaces.rbs +20 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/tsort/0/tsort.rbs +410 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/common.rbs +621 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/file.rbs +118 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/ftp.rbs +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/generic.rbs +1111 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/http.rbs +104 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/https.rbs +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/ldap.rbs +230 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/ldaps.rbs +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/mailto.rbs +92 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/rfc2396_parser.rbs +189 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/rfc3986_parser.rbs +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/ws.rbs +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/uri/0/wss.rbs +9 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/yaml/0/manifest.yaml +2 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/yaml/0/yaml.rbs +1 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/buf_error.rbs +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/data_error.rbs +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/deflate.rbs +211 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/error.rbs +20 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/gzip_file/crc_error.rbs +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/gzip_file/error.rbs +23 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/gzip_file/length_error.rbs +12 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/gzip_file/no_footer.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/gzip_file.rbs +156 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/gzip_reader.rbs +293 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/gzip_writer.rbs +169 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/inflate.rbs +180 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/mem_error.rbs +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/need_dict.rbs +13 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/stream_end.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/stream_error.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/version_error.rbs +11 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/zlib.rbs +449 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/stdlib/zlib/0/zstream.rbs +200 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/wasm/README.md +109 -0
- data/vendor/bundle/ruby/3.4.0/gems/rbs-4.2.0/wasm/rbs_wasm.c +479 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/LICENSE +19 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/README.md +135 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/datetime.rb +54 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/dumper.rb +182 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/errors.rb +17 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/inline_table.rb +18 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/keyvalue.rb +45 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/parser.rb +296 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/string.rb +41 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/table.rb +34 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/table_array.rb +43 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb/version.rb +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/toml-rb-6.0.0/lib/toml-rb.rb +90 -0
- data/vendor/bundle/ruby/3.4.0/gems/tsort-0.2.0/Gemfile +4 -0
- data/vendor/bundle/ruby/3.4.0/gems/tsort-0.2.0/LICENSE.txt +22 -0
- data/vendor/bundle/ruby/3.4.0/gems/tsort-0.2.0/README.md +72 -0
- data/vendor/bundle/ruby/3.4.0/gems/tsort-0.2.0/Rakefile +10 -0
- data/vendor/bundle/ruby/3.4.0/gems/tsort-0.2.0/bin/console +14 -0
- data/vendor/bundle/ruby/3.4.0/gems/tsort-0.2.0/bin/setup +8 -0
- data/vendor/bundle/ruby/3.4.0/gems/tsort-0.2.0/lib/tsort.rb +455 -0
- data/vendor/bundle/ruby/3.4.0/gems/tsort-0.2.0/tsort.gemspec +29 -0
- data/vendor/bundle/ruby/3.4.0/specifications/drb-2.2.3.gemspec +22 -0
- data/vendor/bundle/ruby/3.4.0/specifications/logger-1.7.0.gemspec +22 -0
- data/vendor/bundle/ruby/3.4.0/specifications/minitest-6.0.6.gemspec +35 -0
- data/vendor/bundle/ruby/3.4.0/specifications/prism-1.9.0.gemspec +24 -0
- data/vendor/bundle/ruby/3.4.0/specifications/rake-13.4.2.gemspec +26 -0
- data/vendor/bundle/ruby/3.4.0/specifications/rake-compiler-1.3.2.gemspec +33 -0
- data/vendor/bundle/ruby/3.4.0/specifications/rake-compiler-dock-1.12.0.gemspec +28 -0
- data/vendor/bundle/ruby/3.4.0/specifications/rb_sys-0.9.130.gemspec +29 -0
- data/vendor/bundle/ruby/3.4.0/specifications/rbs-4.2.0.gemspec +33 -0
- data/vendor/bundle/ruby/3.4.0/specifications/toml-rb-6.0.0.gemspec +27 -0
- data/vendor/bundle/ruby/3.4.0/specifications/tsort-0.2.0.gemspec +23 -0
- data/vendor/cache/Cargo.toml +21 -0
- data/vendor/cache/LICENSE +21 -0
- data/vendor/cache/README.md +5 -0
- data/vendor/cache/src/hash.rs +85 -0
- data/vendor/cache/src/lib.rs +751 -0
- data/vendor/cache/src/loom_tests.rs +340 -0
- data/vendor/cache/src/s3fifo.rs +648 -0
- data/vendor/cache/src/sync.rs +25 -0
- data/vendor/cache/src/tests.rs +260 -0
- data/vendor/compaction/Cargo.toml +32 -0
- data/vendor/compaction/LICENSE +21 -0
- data/vendor/compaction/README.md +5 -0
- data/vendor/compaction/src/blob.rs +478 -0
- data/vendor/compaction/src/cursor.rs +700 -0
- data/vendor/compaction/src/gc.rs +802 -0
- data/vendor/compaction/src/job.rs +861 -0
- data/vendor/compaction/src/lib.rs +102 -0
- data/vendor/compaction/src/merge.rs +995 -0
- data/vendor/compaction/src/picker.rs +681 -0
- data/vendor/compaction/src/resolver.rs +1346 -0
- data/vendor/engine/Cargo.toml +56 -0
- data/vendor/engine/LICENSE +21 -0
- data/vendor/engine/README.md +5 -0
- data/vendor/engine/src/admission.rs +421 -0
- data/vendor/engine/src/catalog.rs +1233 -0
- data/vendor/engine/src/check.rs +1386 -0
- data/vendor/engine/src/clock.rs +244 -0
- data/vendor/engine/src/clock_loom.rs +482 -0
- data/vendor/engine/src/close_gate.rs +325 -0
- data/vendor/engine/src/compact.rs +1509 -0
- data/vendor/engine/src/engine/hooks.rs +1654 -0
- data/vendor/engine/src/engine/info.rs +229 -0
- data/vendor/engine/src/engine.rs +6114 -0
- data/vendor/engine/src/error.rs +490 -0
- data/vendor/engine/src/feed.rs +960 -0
- data/vendor/engine/src/feed_reader.rs +1600 -0
- data/vendor/engine/src/flush.rs +839 -0
- data/vendor/engine/src/flushed_roots.rs +158 -0
- data/vendor/engine/src/flushed_through_loom.rs +77 -0
- data/vendor/engine/src/health.rs +201 -0
- data/vendor/engine/src/large.rs +372 -0
- data/vendor/engine/src/large_settle.rs +201 -0
- data/vendor/engine/src/lib.rs +112 -0
- data/vendor/engine/src/load_slot.rs +324 -0
- data/vendor/engine/src/maintenance.rs +935 -0
- data/vendor/engine/src/manifest/flight.rs +563 -0
- data/vendor/engine/src/manifest.rs +1516 -0
- data/vendor/engine/src/nonblocking.rs +603 -0
- data/vendor/engine/src/options.rs +334 -0
- data/vendor/engine/src/position.rs +125 -0
- data/vendor/engine/src/purge_guard.rs +220 -0
- data/vendor/engine/src/read/keys.rs +1049 -0
- data/vendor/engine/src/read/knn.rs +423 -0
- data/vendor/engine/src/read/reduce.rs +2057 -0
- data/vendor/engine/src/read/traverse.rs +1013 -0
- data/vendor/engine/src/read.rs +2429 -0
- data/vendor/engine/src/row_cache.rs +1101 -0
- data/vendor/engine/src/shard/tablets.rs +3582 -0
- data/vendor/engine/src/shard.rs +12215 -0
- data/vendor/engine/src/snapshot.rs +1429 -0
- data/vendor/engine/src/source.rs +1379 -0
- data/vendor/engine/src/timer_state.rs +226 -0
- data/vendor/engine/src/waker.rs +124 -0
- data/vendor/engine/src/watermark_waiters.rs +674 -0
- data/vendor/engine/src/write.rs +1321 -0
- data/vendor/format/Cargo.toml +40 -0
- data/vendor/format/LICENSE +21 -0
- data/vendor/format/README.md +5 -0
- data/vendor/format/src/blob.rs +101 -0
- data/vendor/format/src/block.rs +1120 -0
- data/vendor/format/src/bloom.rs +456 -0
- data/vendor/format/src/buckets.rs +552 -0
- data/vendor/format/src/bytes.rs +101 -0
- data/vendor/format/src/checksum.rs +132 -0
- data/vendor/format/src/compress.rs +196 -0
- data/vendor/format/src/cursor.rs +110 -0
- data/vendor/format/src/distance.rs +598 -0
- data/vendor/format/src/error.rs +93 -0
- data/vendor/format/src/exact.rs +602 -0
- data/vendor/format/src/feed.rs +285 -0
- data/vendor/format/src/filter.rs +157 -0
- data/vendor/format/src/half.rs +182 -0
- data/vendor/format/src/hash.rs +127 -0
- data/vendor/format/src/hll.rs +500 -0
- data/vendor/format/src/ids.rs +95 -0
- data/vendor/format/src/key.rs +620 -0
- data/vendor/format/src/kind_params.rs +101 -0
- data/vendor/format/src/lib.rs +54 -0
- data/vendor/format/src/manifest.rs +1287 -0
- data/vendor/format/src/roaring.rs +865 -0
- data/vendor/format/src/scan.rs +297 -0
- data/vendor/format/src/shm.rs +805 -0
- data/vendor/format/src/sst.rs +233 -0
- data/vendor/format/src/superblock.rs +345 -0
- data/vendor/format/src/value.rs +163 -0
- data/vendor/format/src/varint.rs +87 -0
- data/vendor/format/src/vector.rs +936 -0
- data/vendor/format/src/version.rs +92 -0
- data/vendor/format/src/wal.rs +999 -0
- data/vendor/io/Cargo.toml +29 -0
- data/vendor/io/LICENSE +21 -0
- data/vendor/io/README.md +5 -0
- data/vendor/io/src/async_poll.rs +44 -0
- data/vendor/io/src/buf.rs +694 -0
- data/vendor/io/src/completion.rs +444 -0
- data/vendor/io/src/error.rs +110 -0
- data/vendor/io/src/file.rs +206 -0
- data/vendor/io/src/lib.rs +53 -0
- data/vendor/io/src/nowait.rs +256 -0
- data/vendor/io/src/os/mod.rs +149 -0
- data/vendor/io/src/os/unix.rs +897 -0
- data/vendor/io/src/os/windows.rs +552 -0
- data/vendor/io/src/own.rs +257 -0
- data/vendor/io/src/pread.rs +854 -0
- data/vendor/io/src/ring_wake.rs +113 -0
- data/vendor/io/src/shared.rs +410 -0
- data/vendor/io/src/sim.rs +1545 -0
- data/vendor/io/src/sys.rs +214 -0
- data/vendor/io/src/uring.rs +1839 -0
- data/vendor/io/src/vfs.rs +154 -0
- data/vendor/memory/Cargo.toml +34 -0
- data/vendor/memory/LICENSE +21 -0
- data/vendor/memory/README.md +11 -0
- data/vendor/memory/src/consolidate.rs +640 -0
- data/vendor/memory/src/dedup.rs +231 -0
- data/vendor/memory/src/error.rs +131 -0
- data/vendor/memory/src/feed_loop.rs +572 -0
- data/vendor/memory/src/forget.rs +724 -0
- data/vendor/memory/src/hooks.rs +109 -0
- data/vendor/memory/src/ivf.rs +204 -0
- data/vendor/memory/src/layout.rs +340 -0
- data/vendor/memory/src/lib.rs +61 -0
- data/vendor/memory/src/recall.rs +1182 -0
- data/vendor/memory/src/store.rs +1785 -0
- data/vendor/memory/src/watch.rs +339 -0
- data/vendor/memtable/Cargo.toml +24 -0
- data/vendor/memtable/LICENSE +21 -0
- data/vendor/memtable/README.md +5 -0
- data/vendor/memtable/src/lib.rs +1658 -0
- data/vendor/memtable/src/loom_tests.rs +274 -0
- data/vendor/memtable/src/mem.rs +344 -0
- data/vendor/memtable/src/tests.rs +1061 -0
- data/vendor/pager/Cargo.toml +23 -0
- data/vendor/pager/LICENSE +21 -0
- data/vendor/pager/README.md +5 -0
- data/vendor/pager/src/alloc.rs +1008 -0
- data/vendor/pager/src/lib.rs +1710 -0
- data/vendor/pigeonhole/Cargo.toml +42 -0
- data/vendor/pigeonhole/LICENSE +21 -0
- data/vendor/pigeonhole/README.md +99 -0
- data/vendor/pigeonhole/src/cell.rs +829 -0
- data/vendor/pigeonhole/src/check.rs +368 -0
- data/vendor/pigeonhole/src/db.rs +2324 -0
- data/vendor/pigeonhole/src/doc_support.rs +89 -0
- data/vendor/pigeonhole/src/error.rs +257 -0
- data/vendor/pigeonhole/src/feed.rs +696 -0
- data/vendor/pigeonhole/src/info.rs +425 -0
- data/vendor/pigeonhole/src/lib.rs +202 -0
- data/vendor/pigeonhole/src/merge.rs +135 -0
- data/vendor/pigeonhole/src/metrics.rs +476 -0
- data/vendor/pigeonhole/src/nonblocking.rs +666 -0
- data/vendor/pigeonhole/src/options.rs +2075 -0
- data/vendor/pigeonhole/src/read/budget.rs +584 -0
- data/vendor/pigeonhole/src/read.rs +2463 -0
- data/vendor/pigeonhole/src/recovery.rs +876 -0
- data/vendor/pigeonhole/src/retry.rs +358 -0
- data/vendor/pigeonhole/src/set.rs +103 -0
- data/vendor/pigeonhole/src/table.rs +861 -0
- data/vendor/pigeonhole/src/vector.rs +351 -0
- data/vendor/pigeonhole/src/write.rs +2815 -0
- data/vendor/runtime/Cargo.toml +21 -0
- data/vendor/runtime/LICENSE +21 -0
- data/vendor/runtime/README.md +5 -0
- data/vendor/runtime/src/completion.rs +629 -0
- data/vendor/runtime/src/drivers.rs +1175 -0
- data/vendor/runtime/src/lib.rs +1546 -0
- data/vendor/runtime/src/pool.rs +992 -0
- data/vendor/runtime/src/sched.rs +809 -0
- data/vendor/runtime/src/signal.rs +532 -0
- data/vendor/runtime/src/submit_gate.rs +144 -0
- data/vendor/shm/Cargo.toml +24 -0
- data/vendor/shm/LICENSE +21 -0
- data/vendor/shm/README.md +5 -0
- data/vendor/shm/src/lib.rs +427 -0
- data/vendor/shm/src/lock.rs +153 -0
- data/vendor/shm/src/region.rs +1609 -0
- data/vendor/sst/Cargo.toml +26 -0
- data/vendor/sst/LICENSE +21 -0
- data/vendor/sst/README.md +5 -0
- data/vendor/sst/src/blob.rs +380 -0
- data/vendor/sst/src/iter.rs +803 -0
- data/vendor/sst/src/lib.rs +695 -0
- data/vendor/sst/src/reader.rs +814 -0
- data/vendor/sst/src/writer.rs +596 -0
- data/vendor/text/Cargo.toml +26 -0
- data/vendor/text/LICENSE +21 -0
- data/vendor/text/README.md +9 -0
- data/vendor/text/src/error.rs +108 -0
- data/vendor/text/src/fuzzy.rs +265 -0
- data/vendor/text/src/highlight.rs +236 -0
- data/vendor/text/src/index.rs +1152 -0
- data/vendor/text/src/layout.rs +424 -0
- data/vendor/text/src/lib.rs +75 -0
- data/vendor/text/src/maxscore.rs +658 -0
- data/vendor/text/src/query.rs +450 -0
- data/vendor/text/src/search.rs +569 -0
- data/vendor/text/src/sloppy.rs +460 -0
- data/vendor/text/src/tokenizer.rs +163 -0
- data/vendor/vector/Cargo.toml +28 -0
- data/vendor/vector/LICENSE +21 -0
- data/vendor/vector/README.md +9 -0
- data/vendor/vector/src/error.rs +63 -0
- data/vendor/vector/src/half.rs +167 -0
- data/vendor/vector/src/hooks.rs +135 -0
- data/vendor/vector/src/index.rs +2690 -0
- data/vendor/vector/src/layout.rs +102 -0
- data/vendor/vector/src/lib.rs +52 -0
- data/vendor/vector/src/point.rs +500 -0
- data/vendor/vector/src/rebalancer.rs +200 -0
- data/vendor/vector/src/registry.rs +81 -0
- data/vendor/wal/Cargo.toml +25 -0
- data/vendor/wal/LICENSE +21 -0
- data/vendor/wal/README.md +5 -0
- data/vendor/wal/src/feed.rs +610 -0
- data/vendor/wal/src/foreground.rs +122 -0
- data/vendor/wal/src/lib.rs +458 -0
- data/vendor/wal/src/mem.rs +166 -0
- data/vendor/wal/src/recovery.rs +426 -0
- data/vendor/wal/src/stream.rs +2382 -0
- metadata +1310 -0
|
@@ -0,0 +1,3582 @@
|
|
|
1
|
+
//! Tablet splits, merges and moves, and the balancer that decides them (spec: Tablets and
|
|
2
|
+
//! Rebalancing; issue #38).
|
|
3
|
+
//!
|
|
4
|
+
//! Every change runs on the shard that owns the tablets, one at a time, on the same path:
|
|
5
|
+
//!
|
|
6
|
+
//! 1. **Start.** The tablets join `moving`. From now on the shard parks single-shard commits
|
|
7
|
+
//! that touch them (unlogged, so they hold no seqno and never block the watermark) and
|
|
8
|
+
//! refuses PREPAREs that touch them with [`PrepareError::Moved`]; the coordinator retries
|
|
9
|
+
//! such a commit once the tablet map has changed. Their memtables are frozen and flushed.
|
|
10
|
+
//! 2. **Drain.** The change waits until every memtable of the tablets is in SSTs, no prepared
|
|
11
|
+
//! share and no compaction touches them. Shares decided after the freeze land in a fresh
|
|
12
|
+
//! memtable, which is frozen and flushed again.
|
|
13
|
+
//! 3. **Commit.** One manifest request (`ReqKind::Tablets`) retires the old tablets and adds
|
|
14
|
+
//! the new ones (`PutTablet`/`DropTablet`; children reference their parent's SSTs until
|
|
15
|
+
//! compaction rewrites them, D13) and hands out owners. Its view publishes the new tablet
|
|
16
|
+
//! map, memtables and SST set together, so reads are never blocked and never see a
|
|
17
|
+
//! partial change. A shard receiving tablets has its default-timestamp floor raised first
|
|
18
|
+
//! (D11: the floor travels with the tablet).
|
|
19
|
+
//! 4. **Forward.** Parked commits are routed again through the new map: to this shard's
|
|
20
|
+
//! queue, to the new owner, or through two-phase commit when their rows now span shards.
|
|
21
|
+
|
|
22
|
+
use super::*;
|
|
23
|
+
#[cfg(feature = "test-hooks")]
|
|
24
|
+
use crate::engine::hooks::LoadTerm;
|
|
25
|
+
|
|
26
|
+
use pigeonhole_format::key::{Escaped, row_prefix_len};
|
|
27
|
+
use pigeonhole_format::manifest::SstMeta;
|
|
28
|
+
|
|
29
|
+
use crate::catalog::Catalog;
|
|
30
|
+
use crate::manifest::{ReqKind, TabletChange, TabletEdits};
|
|
31
|
+
use crate::snapshot::TabletEntry;
|
|
32
|
+
|
|
33
|
+
/// Row samples kept per tablet and balancer interval (split points for write skew).
|
|
34
|
+
const SAMPLES: usize = 64;
|
|
35
|
+
|
|
36
|
+
/// Weight of the newest interval in the moving averages of write load (#103, D214): one
|
|
37
|
+
/// interval's noise never decides a move on its own.
|
|
38
|
+
const LOAD_ALPHA: f64 = 0.2;
|
|
39
|
+
|
|
40
|
+
/// Consecutive balancer passes a shard's load must stay skewed (above `balance_skew` times
|
|
41
|
+
/// the mean, and at least `balance_min_writes`) before it starts acting on write skew
|
|
42
|
+
/// (D214): short-term noise never moves a tablet.
|
|
43
|
+
const SKEW_PERSIST: u64 = 10;
|
|
44
|
+
|
|
45
|
+
/// Where a shard that is acting on write skew stops (D214): it keeps shedding load after
|
|
46
|
+
/// each dwell until its load falls below `1 + SKEW_STOP * (balance_skew - 1)` times the mean
|
|
47
|
+
/// (1.1 at the default skew of 1.25), then needs `SKEW_PERSIST` skewed passes to start
|
|
48
|
+
/// again.
|
|
49
|
+
const SKEW_STOP: f64 = 0.4;
|
|
50
|
+
|
|
51
|
+
/// Weight of the newest pass in the slow moving average of the mean write load (D224).
|
|
52
|
+
const MEAN_SLOW_ALPHA: f64 = 0.05;
|
|
53
|
+
|
|
54
|
+
/// The mean write load is falling while it is below this fraction of its slow average
|
|
55
|
+
/// (D224, #582). Skew that appears because other shards' load fell, not because this shard's
|
|
56
|
+
/// rose, is the tail of a closed-loop load: the lighter shards finished their work, and the
|
|
57
|
+
/// busy ones look skewed only beside them. Such a pass neither counts toward
|
|
58
|
+
/// `SKEW_PERSIST` nor resets it, and moves no tablet. If the others' load stays down, the
|
|
59
|
+
/// slow average catches up in a few seconds and the skew counts again.
|
|
60
|
+
const MEAN_FALL: f64 = 0.9;
|
|
61
|
+
|
|
62
|
+
/// How long a tablet must go unwritten, and have been on its shard, before it counts as cold
|
|
63
|
+
/// for merges and consolidation (D224): `COLD_PASSES` balancer passes or as many base
|
|
64
|
+
/// intervals of clock (10 s at the default), whichever comes first, so an idle shard backing
|
|
65
|
+
/// off its passes still merges. Longer than a closed-loop load's phases, so a tablet idle only
|
|
66
|
+
/// through a phase's tail (its shard finished its work first) is not merged or moved away.
|
|
67
|
+
pub(crate) const COLD_PASSES: u64 = 100;
|
|
68
|
+
|
|
69
|
+
/// Weight of the newest interval in the slow moving averages of write load (D224): a time
|
|
70
|
+
/// constant of about 10 s at the default interval, longer than a closed-loop load's phases, so
|
|
71
|
+
/// a shard's slow load follows its share of the work rather than its rate while busy.
|
|
72
|
+
const SLOW_ALPHA: f64 = 0.01;
|
|
73
|
+
|
|
74
|
+
/// Balancer passes the slow skew path waits after a change before deciding again (D224):
|
|
75
|
+
/// about the slow averages' time constant, so they show the change first.
|
|
76
|
+
const SLOW_DWELL: u64 = 100;
|
|
77
|
+
|
|
78
|
+
/// Balancer passes a shard waits after starting a move or a skew split, and a tablet stays
|
|
79
|
+
/// put after it arrived on a shard, before write skew moves or splits again (#103): the
|
|
80
|
+
/// moving averages catch up with a change before the next one is decided.
|
|
81
|
+
const DWELL_PASSES: u64 = 10;
|
|
82
|
+
|
|
83
|
+
/// The longest balancer interval of an idle shard: passes that find nothing to do, with no
|
|
84
|
+
/// writes and no tablet change since the last one, double the interval up to this (10 s).
|
|
85
|
+
const BALANCE_IDLE_CAP_NANOS: u64 = 10_000_000_000;
|
|
86
|
+
|
|
87
|
+
/// Packing (D217): the busy fraction each active shard aims for. With `B` the summed busy
|
|
88
|
+
/// fraction of all shards, `ceil(B / PACK_TARGET)` shards stay active, so each has headroom
|
|
89
|
+
/// before commits queue.
|
|
90
|
+
const PACK_TARGET: f64 = 0.5;
|
|
91
|
+
|
|
92
|
+
/// Packing grows the active shards once `B / k` stays above this for `PACK_GROW_PASSES`.
|
|
93
|
+
const PACK_SPREAD: f64 = 0.75;
|
|
94
|
+
|
|
95
|
+
/// Packing (D257): the commits in flight each active shard is sized for. With `C` the mean
|
|
96
|
+
/// number of client commit rounds in flight over the store (every shard's rise of
|
|
97
|
+
/// `commit_inflight_nanos` over the interval, Little's law; a cross-shard commit counts at
|
|
98
|
+
/// its coordinator and at each participant, D262), `round(C / PACK_CONCURRENCY)` shards
|
|
99
|
+
/// stay active, at least one: one writer with one commit in flight gains nothing
|
|
100
|
+
/// from a second shard and pays a wake per write on a thin one (ycsb-a at one client fell
|
|
101
|
+
/// 180K → 130K ops/s spread over 13 shards on the AX102, #734's follow-up), while sixteen
|
|
102
|
+
/// clients with about twelve commits in flight (a get is a quarter of their loop) keep
|
|
103
|
+
/// sixteen. Rounded, so a lone pure writer (`C` → 1) stays on one shard where a ceiling
|
|
104
|
+
/// would give it two. The concurrency is the demand side: it grows `k` and gates a
|
|
105
|
+
/// shrink; the stall and saturation floors (D255, D256) are the capacity side and grow
|
|
106
|
+
/// `k` past it.
|
|
107
|
+
const PACK_CONCURRENCY: f64 = 0.75;
|
|
108
|
+
|
|
109
|
+
/// Consecutive passes over `PACK_SPREAD` before more shards become active (1 s): a burst
|
|
110
|
+
/// spreads quickly.
|
|
111
|
+
const PACK_GROW_PASSES: u64 = 10;
|
|
112
|
+
|
|
113
|
+
/// Consecutive passes with `B / (k - 1)` below `PACK_TARGET` before fewer shards stay active
|
|
114
|
+
/// (5 s): packing is slow, so a pause between bursts does not reshuffle.
|
|
115
|
+
const PACK_SHRINK_PASSES: u64 = 50;
|
|
116
|
+
|
|
117
|
+
/// Balancer passes on which the store must have written before its first shrink from the
|
|
118
|
+
/// unpacked start (30 s at the base interval, a slow-average window, D255, #734): a load's
|
|
119
|
+
/// phases (a load, a settle, a warm-up) dip between them for a few seconds, and a pack on
|
|
120
|
+
/// a dip moves every tablet twice, the second time while the load is measured. On the
|
|
121
|
+
/// AX102 the dip packed 16 shards to 2 at about 21 s and D251's regrowth then moved
|
|
122
|
+
/// tablets back out until about 90 s, a put p99.9 of 290-362 µs against 73 with the moves
|
|
123
|
+
/// out of the measured phase. A light store still packs, 30 s in.
|
|
124
|
+
const PACK_FIRST_SHRINK_PASSES: u64 = 300;
|
|
125
|
+
|
|
126
|
+
/// Packing (D251): an active shard whose thread is busier than this (turn busy, without
|
|
127
|
+
/// D248's sync term) also calls for more active shards, whatever the average (`PACK_SPREAD`
|
|
128
|
+
/// looks only at `B / k`, which a saturated shard beside an idle one never passes), and while
|
|
129
|
+
/// one is, the active shards never shrink.
|
|
130
|
+
const PACK_HOT: f64 = 0.9;
|
|
131
|
+
|
|
132
|
+
/// How much a hot shard's turn busy fraction must fall (millionths) after a hot-triggered
|
|
133
|
+
/// growth before another one (D251): shedding helped.
|
|
134
|
+
const PACK_HOT_COOLED: u64 = 20_000;
|
|
135
|
+
|
|
136
|
+
/// The fast write average over the slow one above which the load is still ramping (D255):
|
|
137
|
+
/// the shrink counter pauses then (it does not reset: a periodic batch writer reads as
|
|
138
|
+
/// ramping after every batch, and a reset would never let it pack). The first pass of a
|
|
139
|
+
/// load read its ramp as a light store and cut the active shards from 16 to 2 or 3 on the
|
|
140
|
+
/// AX102, and the full load then sat there (#734).
|
|
141
|
+
const PACK_RAMP: f64 = 1.25;
|
|
142
|
+
|
|
143
|
+
/// Consecutive ramping passes after which the shrink counter runs anyway (60 s at the base
|
|
144
|
+
/// interval): a load that only ever grows still packs.
|
|
145
|
+
const PACK_RAMP_MAX: u64 = 600;
|
|
146
|
+
|
|
147
|
+
/// Balancer passes after a shrink within which a stall-driven growth undoes it (60 s at
|
|
148
|
+
/// the base interval, D255): the growth goes straight back to the count before the shrink.
|
|
149
|
+
/// Regrowing one shard per ten passes took most of a 16 GiB load on the AX102 after one
|
|
150
|
+
/// mistaken shrink from 16 to 2.
|
|
151
|
+
const PACK_SHRINK_UNDO: u64 = 600;
|
|
152
|
+
|
|
153
|
+
/// A shard reads stalled when its writers stalled on at least `PACK_STALL_PASSES` of the
|
|
154
|
+
/// last `PACK_STALL_WINDOW` balancer passes (3 of 10: about 3 stalls a second at the base
|
|
155
|
+
/// interval, or one wait spanning 0.3 s, D255). A buffered load's stalls are short and a few
|
|
156
|
+
/// per second per shard: a pass-by-pass reading smoothed by the moving average read them at
|
|
157
|
+
/// about half and never hot (the buffered riders row on the AX102: thousands of write stalls,
|
|
158
|
+
/// the top shards' stall terms at 0), while any single stall must not count as capacity (a
|
|
159
|
+
/// light store with one burst would spread). The term is 0 or 1 and the hot test reads it
|
|
160
|
+
/// as published, so the decision is this count, not a moving average's tail.
|
|
161
|
+
const PACK_STALL_WINDOW: u32 = 10;
|
|
162
|
+
const PACK_STALL_PASSES: u32 = 3;
|
|
163
|
+
|
|
164
|
+
/// Balancer passes with no active shard stalling after which a stall-driven floor expires
|
|
165
|
+
/// (30 s at the base interval), and the cap of its doubling after each expiry (D255).
|
|
166
|
+
const PACK_FLOOR_QUIET: u64 = 300;
|
|
167
|
+
const PACK_FLOOR_QUIET_MAX: u64 = 300 * 16;
|
|
168
|
+
|
|
169
|
+
/// Slots reserved on shards receiving tablets (`LoadSlot::reserved`).
|
|
170
|
+
type Reserved = Vec<(ShardId, usize)>;
|
|
171
|
+
|
|
172
|
+
/// A change to tablets this shard owns.
|
|
173
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
174
|
+
pub(crate) enum TabletOpKind {
|
|
175
|
+
/// Split `tablet` at `keys` (sorted, strictly inside its range); child `i` covers
|
|
176
|
+
/// `[keys[i - 1], keys[i])` and goes to `owners[i]`.
|
|
177
|
+
Split {
|
|
178
|
+
tablet: TabletId,
|
|
179
|
+
keys: Vec<Vec<u8>>,
|
|
180
|
+
owners: Vec<ShardId>,
|
|
181
|
+
},
|
|
182
|
+
/// Merge `left` with `right`, its right neighbour of the same table; both on this shard.
|
|
183
|
+
Merge { left: TabletId, right: TabletId },
|
|
184
|
+
/// Hand `tablet` to shard `to`.
|
|
185
|
+
Move { tablet: TabletId, to: ShardId },
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
impl TabletOpKind {
|
|
189
|
+
/// The tablets the change retires (or hands away).
|
|
190
|
+
fn tablets(&self) -> Vec<TabletId> {
|
|
191
|
+
match self {
|
|
192
|
+
TabletOpKind::Split { tablet, .. } | TabletOpKind::Move { tablet, .. } => {
|
|
193
|
+
vec![*tablet]
|
|
194
|
+
}
|
|
195
|
+
TabletOpKind::Merge { left, right } => vec![*left, *right],
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// The other shards receiving a tablet.
|
|
200
|
+
fn targets(&self, me: ShardId) -> Vec<ShardId> {
|
|
201
|
+
let mut out = match self {
|
|
202
|
+
TabletOpKind::Split { owners, .. } => owners.clone(),
|
|
203
|
+
TabletOpKind::Move { to, .. } => vec![*to],
|
|
204
|
+
TabletOpKind::Merge { .. } => Vec::new(),
|
|
205
|
+
};
|
|
206
|
+
out.retain(|s| *s != me);
|
|
207
|
+
out.sort_unstable();
|
|
208
|
+
out.dedup();
|
|
209
|
+
out
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// The change in progress on a shard.
|
|
214
|
+
#[derive(Debug)]
|
|
215
|
+
pub(crate) struct ActiveOp {
|
|
216
|
+
kind: TabletOpKind,
|
|
217
|
+
reply: Option<Notifier<Result<()>>>,
|
|
218
|
+
/// The tablets as they were when the change started; the commit checks they are still
|
|
219
|
+
/// so (a table drop may have raced it).
|
|
220
|
+
entries: Vec<TabletEntry>,
|
|
221
|
+
/// The manifest request is in flight.
|
|
222
|
+
committing: bool,
|
|
223
|
+
/// Slots reserved on each receiving shard (`LoadSlot::reserved`), released when the
|
|
224
|
+
/// change ends.
|
|
225
|
+
reserved: Reserved,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/// Writes to one tablet during the current balancer interval.
|
|
229
|
+
#[derive(Debug, Default)]
|
|
230
|
+
pub(crate) struct TabletLoad {
|
|
231
|
+
/// Rows written this interval and the one before.
|
|
232
|
+
writes: u64,
|
|
233
|
+
prev_writes: u64,
|
|
234
|
+
/// Rows seen by the reservoir this interval.
|
|
235
|
+
seen: u64,
|
|
236
|
+
/// A uniform sample of the rows written this interval: the first `sampled`. Buffers past
|
|
237
|
+
/// it are earlier intervals' rows, kept for reuse so a new interval's samples do not
|
|
238
|
+
/// allocate (#320).
|
|
239
|
+
samples: Vec<Vec<u8>>,
|
|
240
|
+
sampled: usize,
|
|
241
|
+
/// Moving average of `writes` over intervals (`LOAD_ALPHA`).
|
|
242
|
+
ewma: f64,
|
|
243
|
+
/// Slow moving average of `writes` (`SLOW_ALPHA`, D224).
|
|
244
|
+
slow: f64,
|
|
245
|
+
/// The balancer pass and clock of the last interval with writes (D224).
|
|
246
|
+
last_write: (u64, u64),
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
impl TabletLoad {
|
|
250
|
+
/// This interval's row samples.
|
|
251
|
+
fn live_samples(&self) -> &[Vec<u8>] {
|
|
252
|
+
&self.samples[..self.sampled]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// The balancer settings, copied from `EngineOptions` at open.
|
|
257
|
+
#[derive(Debug, Clone, Copy, Default)]
|
|
258
|
+
pub(crate) struct BalanceConfig {
|
|
259
|
+
/// `EngineOptions::tablet_changes`: off, nothing splits, merges or moves.
|
|
260
|
+
pub enabled: bool,
|
|
261
|
+
pub interval_nanos: u64,
|
|
262
|
+
pub min_writes: u64,
|
|
263
|
+
pub skew: f64,
|
|
264
|
+
pub split_bytes: u64,
|
|
265
|
+
/// `EngineOptions::balance_pack`, engine-owned shards only (D217).
|
|
266
|
+
pub pack: bool,
|
|
267
|
+
/// Unwritten passes before a tablet is cold (`EngineOptions::balance_cold_passes`, D224).
|
|
268
|
+
pub cold_passes: u64,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
pub(crate) use crate::load_slot::LoadSlot;
|
|
272
|
+
|
|
273
|
+
/// Where a member's rows live.
|
|
274
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
275
|
+
enum Routing {
|
|
276
|
+
Owned,
|
|
277
|
+
/// A row is in a tablet this shard is splitting, merging or moving.
|
|
278
|
+
Moving,
|
|
279
|
+
/// A row belongs to another shard (the router used an older tablet map).
|
|
280
|
+
Elsewhere,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
impl ShardState {
|
|
284
|
+
// ---- routing at admission ----
|
|
285
|
+
|
|
286
|
+
/// Admission routing for one member: returns it if this shard should run it now; parks,
|
|
287
|
+
/// forwards or refuses it otherwise.
|
|
288
|
+
pub(super) fn route_member(
|
|
289
|
+
&mut self,
|
|
290
|
+
m: Member,
|
|
291
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
292
|
+
) -> Option<Member> {
|
|
293
|
+
match m.kind {
|
|
294
|
+
MemberKind::CommitRecord { .. } => Some(m),
|
|
295
|
+
MemberKind::Prepare { .. } => self.route_prepare(m, ctx),
|
|
296
|
+
MemberKind::Single => self.route_by_map(m, ctx),
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// A PREPARE's admission routing: routed as any member (D263); a test hook can refuse it
|
|
301
|
+
/// as overlapping an undecided share.
|
|
302
|
+
#[inline(never)]
|
|
303
|
+
fn route_prepare(&mut self, m: Member, ctx: &mut ShardContext<'_, ShardMsg>) -> Option<Member> {
|
|
304
|
+
#[cfg(feature = "test-hooks")]
|
|
305
|
+
{
|
|
306
|
+
let forced = &self.shared.metrics[usize::from(self.id.0)]
|
|
307
|
+
.hooks
|
|
308
|
+
.refuse_prepares;
|
|
309
|
+
if forced.load(Ordering::Acquire) > 0 {
|
|
310
|
+
forced.fetch_sub(1, Ordering::AcqRel);
|
|
311
|
+
// Nothing here overlaps it, so the abort's decision wakes its coordinator.
|
|
312
|
+
self.refuse_for_overlap(m, ctx);
|
|
313
|
+
return None;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// A PREPARE carries no timestamp: its coordinator picks one at the decision, above
|
|
317
|
+
// every participant's floor then (D263). Per-row order comes from admission's
|
|
318
|
+
// overlap rule (`overlaps_undecided`).
|
|
319
|
+
self.route_by_map(m, ctx)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/// Routing through the tablet map for a single or a PREPARE: returns it if this shard
|
|
323
|
+
/// should run it now; parks, forwards or refuses it otherwise. Inlined into both callers:
|
|
324
|
+
/// a single's commit path pays no call for it.
|
|
325
|
+
#[inline(always)]
|
|
326
|
+
fn route_by_map(&mut self, m: Member, ctx: &mut ShardContext<'_, ShardMsg>) -> Option<Member> {
|
|
327
|
+
let single = matches!(m.kind, MemberKind::Single);
|
|
328
|
+
if self.moving.is_empty() && m.map_version >= self.lost_version {
|
|
329
|
+
return Some(m);
|
|
330
|
+
}
|
|
331
|
+
let mut routing = self.routing_of(&m);
|
|
332
|
+
if routing == Routing::Elsewhere {
|
|
333
|
+
// Our map may be the stale one.
|
|
334
|
+
self.refresh_tablets();
|
|
335
|
+
routing = self.routing_of(&m);
|
|
336
|
+
}
|
|
337
|
+
if single && routing == Routing::Owned && !self.parked.is_empty() {
|
|
338
|
+
// Per-row submission order: behind a parked commit on any of the same rows.
|
|
339
|
+
let rows = member_rows(&m);
|
|
340
|
+
if rows.iter().any(|h| self.parked_rows.contains(h)) {
|
|
341
|
+
routing = Routing::Moving;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
match (routing, single) {
|
|
345
|
+
(Routing::Owned, _) => Some(m),
|
|
346
|
+
(Routing::Moving, true) => {
|
|
347
|
+
self.parked_rows.extend(member_rows(&m));
|
|
348
|
+
self.parked.push(m);
|
|
349
|
+
self.arm_tablet_timer(ctx);
|
|
350
|
+
None
|
|
351
|
+
}
|
|
352
|
+
(Routing::Elsewhere, true) => self.forward(m, ctx),
|
|
353
|
+
(_, false) => {
|
|
354
|
+
self.shared.metrics[usize::from(self.id.0)].count_refusal(CrossRefusal::Moved);
|
|
355
|
+
self.refuse_prepare(m, PrepareError::Moved, ctx);
|
|
356
|
+
None
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fn routing_of(&self, m: &Member) -> Routing {
|
|
362
|
+
let prepare = matches!(m.kind, MemberKind::Prepare { .. });
|
|
363
|
+
let mut out = Routing::Owned;
|
|
364
|
+
// A share routed with an older tablet map may name rows this shard no longer owns,
|
|
365
|
+
// and its reads may now belong to a shard outside the commit (nobody would validate
|
|
366
|
+
// them): it runs again through the new map only then (#102). A read another
|
|
367
|
+
// participant owns now is validated there, under that shard's current map.
|
|
368
|
+
let participants: Option<&[ShardId]> = (prepare && m.map_version < self.tablets.version())
|
|
369
|
+
.then(|| {
|
|
370
|
+
self.prepared
|
|
371
|
+
.get(&m.seqno)
|
|
372
|
+
.map_or(&[][..], |s| s.participants.as_slice())
|
|
373
|
+
});
|
|
374
|
+
let mut look = |table: TableId, row: &[u8], read_by_others: bool| {
|
|
375
|
+
if let Some((tablet, owner)) = self.tablets.route(table, row) {
|
|
376
|
+
if self.moving.contains(&tablet) {
|
|
377
|
+
out = Routing::Moving;
|
|
378
|
+
} else if owner != self.id
|
|
379
|
+
&& out == Routing::Owned
|
|
380
|
+
&& (!read_by_others || participants.is_some_and(|p| !p.contains(&owner)))
|
|
381
|
+
{
|
|
382
|
+
out = Routing::Elsewhere;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
if let Ok(batch) = m.bytes.batch() {
|
|
387
|
+
for mu in batch.iter().flatten() {
|
|
388
|
+
look(mu.table, mu.row, false);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// A PREPARE carries the whole commit's reads; each participant validates its own.
|
|
392
|
+
if let Some(reads) = m.validate.as_ref().map(|v| &v.reads) {
|
|
393
|
+
for r in reads {
|
|
394
|
+
look(r.table, &r.row, prepare);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if let Some((table, row, _)) = m.predicate.as_deref() {
|
|
398
|
+
look(*table, row, false);
|
|
399
|
+
}
|
|
400
|
+
out
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/// A PREPARE that would wait for arena room, refused instead (D245, ICR 0042); its
|
|
404
|
+
/// coordinator hears a room `Wake` once this shard's room wait ends.
|
|
405
|
+
#[inline(never)]
|
|
406
|
+
pub(super) fn refuse_for_room(
|
|
407
|
+
&mut self,
|
|
408
|
+
m: Member,
|
|
409
|
+
coordinator: ShardId,
|
|
410
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
411
|
+
) {
|
|
412
|
+
#[cfg(feature = "test-hooks")]
|
|
413
|
+
self.shared
|
|
414
|
+
.hooks
|
|
415
|
+
.room_refusals
|
|
416
|
+
.fetch_add(1, Ordering::Relaxed);
|
|
417
|
+
self.shared.metrics[usize::from(self.id.0)].count_refusal(CrossRefusal::Room);
|
|
418
|
+
if !self.room_refused.contains(&coordinator) {
|
|
419
|
+
self.room_refused.push(coordinator);
|
|
420
|
+
}
|
|
421
|
+
self.room_refused_free = self.arena.free_bytes().saturating_sub(self.reserved);
|
|
422
|
+
self.refuse_prepare(m, PrepareError::Room(self.room_generation), ctx);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/// Whether room may fit a share this shard refused for room (D245): its room wait ended,
|
|
426
|
+
/// or nothing waits or flushes and more is free than at the latest refusal (after
|
|
427
|
+
/// reclaiming the memtables readers left).
|
|
428
|
+
#[inline(never)]
|
|
429
|
+
pub(super) fn room_freed_since_refusal(&mut self) -> bool {
|
|
430
|
+
if self.room_wait.is_none() {
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
if !self.pending.is_empty() || self.flush_running || !self.flush_queue.is_empty() {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
self.reclaim_retired();
|
|
437
|
+
self.refresh_free();
|
|
438
|
+
self.arena.free_bytes().saturating_sub(self.reserved) > self.room_refused_free
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// Tells the coordinators this shard refused for room that its room will not free: it
|
|
442
|
+
/// waits on a frozen clock with nothing in flight (#70). Their retries give up with
|
|
443
|
+
/// `Busy`, as a waiting single is refused (D301).
|
|
444
|
+
#[inline(never)]
|
|
445
|
+
pub(super) fn notify_room_gone(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
446
|
+
self.room_generation += 1;
|
|
447
|
+
let generation = self.room_generation;
|
|
448
|
+
for c in std::mem::take(&mut self.room_refused) {
|
|
449
|
+
trace!(
|
|
450
|
+
"shard {} room gone ({generation}): tells {}",
|
|
451
|
+
self.id.0, c.0
|
|
452
|
+
);
|
|
453
|
+
self.send(
|
|
454
|
+
c,
|
|
455
|
+
ShardMsg::Wake {
|
|
456
|
+
from: self.id,
|
|
457
|
+
reason: WakeReason::RoomGone { generation },
|
|
458
|
+
},
|
|
459
|
+
ctx,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/// Tells the coordinators this shard refused for room that it has room again (D245).
|
|
465
|
+
#[inline(never)]
|
|
466
|
+
pub(super) fn notify_room_freed(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
467
|
+
self.room_generation += 1;
|
|
468
|
+
let generation = self.room_generation;
|
|
469
|
+
for c in std::mem::take(&mut self.room_refused) {
|
|
470
|
+
trace!(
|
|
471
|
+
"shard {} room freed ({generation}): tells {}",
|
|
472
|
+
self.id.0, c.0
|
|
473
|
+
);
|
|
474
|
+
self.send(
|
|
475
|
+
c,
|
|
476
|
+
ShardMsg::Wake {
|
|
477
|
+
from: self.id,
|
|
478
|
+
reason: WakeReason::Room { generation },
|
|
479
|
+
},
|
|
480
|
+
ctx,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/// The rows a conditional member writes, reads, or tests (`hash_row`), deduplicated: what
|
|
486
|
+
/// `conflicts_with_group` checks against undecided shares.
|
|
487
|
+
fn member_row_hashes(m: &Member) -> Vec<u64> {
|
|
488
|
+
let mut rows = Vec::new();
|
|
489
|
+
if let Ok(batch) = m.bytes.batch() {
|
|
490
|
+
for mu in batch.iter().flatten() {
|
|
491
|
+
rows.push(hash_row(mu.table, mu.row));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if let Some(v) = &m.validate {
|
|
495
|
+
for r in &v.reads {
|
|
496
|
+
rows.push(hash_row(r.table, &r.row));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if let Some((table, row, _)) = m.predicate.as_deref() {
|
|
500
|
+
rows.push(hash_row(*table, row));
|
|
501
|
+
}
|
|
502
|
+
rows.sort_unstable();
|
|
503
|
+
rows.dedup();
|
|
504
|
+
rows
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/// A PREPARE overlapping an undecided share, refused rather than held (D249, ICR 0045).
|
|
508
|
+
/// Its coordinator is woken once no undecided share here overlaps it; if one of the
|
|
509
|
+
/// shares it overlaps belongs to an older commit, only once that commit has run its next
|
|
510
|
+
/// attempt here (or its last, or retried elsewhere), so the older one goes first.
|
|
511
|
+
#[cold]
|
|
512
|
+
#[inline(never)]
|
|
513
|
+
pub(super) fn refuse_for_overlap(&mut self, m: Member, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
514
|
+
self.refuse_waiting(m, OverlapWait::Shares, ctx);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/// [`refuse_for_overlap`](Self::refuse_for_overlap), waiting for `wait` (D267): a
|
|
518
|
+
/// refusal by a reader's fence waits behind that reader, and one by a waiting single
|
|
519
|
+
/// until the single is logged, rather than for shares that may already be gone, which
|
|
520
|
+
/// would wake it at once to be refused again.
|
|
521
|
+
#[cold]
|
|
522
|
+
#[inline(never)]
|
|
523
|
+
pub(super) fn refuse_waiting(
|
|
524
|
+
&mut self,
|
|
525
|
+
m: Member,
|
|
526
|
+
wait: OverlapWait,
|
|
527
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
528
|
+
) {
|
|
529
|
+
let MemberKind::Prepare { coordinator } = m.kind else {
|
|
530
|
+
return;
|
|
531
|
+
};
|
|
532
|
+
let rows = Self::member_row_hashes(&m);
|
|
533
|
+
let priority = self
|
|
534
|
+
.prepared
|
|
535
|
+
.get(&m.seqno)
|
|
536
|
+
.map_or((0, m.seqno), |s| s.priority);
|
|
537
|
+
// The oldest commit among the undecided shares it overlaps.
|
|
538
|
+
let mut oldest: Option<(CommitPriority, Seqno)> = None;
|
|
539
|
+
for (seqno, share) in &self.prepared {
|
|
540
|
+
if *seqno == m.seqno || !share.tracked {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
let overlaps = share
|
|
544
|
+
.bytes
|
|
545
|
+
.batch()
|
|
546
|
+
.iter()
|
|
547
|
+
.flatten()
|
|
548
|
+
.any(|mu| rows.binary_search(&hash_row(mu.table, mu.row)).is_ok());
|
|
549
|
+
if overlaps && oldest.is_none_or(|(p, _)| share.priority < p) {
|
|
550
|
+
oldest = Some((share.priority, *seqno));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
let (behind, single) = match wait {
|
|
554
|
+
OverlapWait::Shares => (oldest.filter(|(p, _)| *p < priority), 0),
|
|
555
|
+
OverlapWait::Fence(reader) => (Some((reader, 0)), 0),
|
|
556
|
+
OverlapWait::Single(id) => (None, id),
|
|
557
|
+
};
|
|
558
|
+
// D267: a commit that reads cannot chain; older than every undecided share on its
|
|
559
|
+
// rows, it fences them, so younger blind PREPAREs stop chaining there and the row
|
|
560
|
+
// drains for its retry.
|
|
561
|
+
if behind.is_none()
|
|
562
|
+
&& oldest.is_none_or(|(p, _)| p > priority)
|
|
563
|
+
&& (m.validate.as_ref().is_some_and(|v| !v.reads.is_empty()) || m.predicate.is_some())
|
|
564
|
+
{
|
|
565
|
+
self.reader_fences.retain(|(p, _)| *p != priority);
|
|
566
|
+
self.reader_fences.push((priority, rows.clone()));
|
|
567
|
+
}
|
|
568
|
+
trace!(
|
|
569
|
+
"shard {} refuses {} {priority:?} over an undecided overlap (behind {behind:?})",
|
|
570
|
+
self.id.0, m.seqno
|
|
571
|
+
);
|
|
572
|
+
self.shared.metrics[usize::from(self.id.0)].count_refusal(CrossRefusal::PendingOverlap);
|
|
573
|
+
let validate = m.validate.clone().filter(|v| !v.reads.is_empty());
|
|
574
|
+
self.overlap_refused.push(OverlapRefusal {
|
|
575
|
+
coordinator,
|
|
576
|
+
priority,
|
|
577
|
+
rows,
|
|
578
|
+
behind,
|
|
579
|
+
single,
|
|
580
|
+
validate,
|
|
581
|
+
});
|
|
582
|
+
self.refuse_prepare(m, PrepareError::PendingOverlap, ctx);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/// The rows `m` writes (`hash_row`), sorted and deduplicated.
|
|
586
|
+
fn written_rows(m: &Member) -> Vec<u64> {
|
|
587
|
+
let mut rows: Vec<u64> = m
|
|
588
|
+
.bytes
|
|
589
|
+
.batch()
|
|
590
|
+
.map(|b| {
|
|
591
|
+
b.iter()
|
|
592
|
+
.flatten()
|
|
593
|
+
.map(|mu| hash_row(mu.table, mu.row))
|
|
594
|
+
.collect()
|
|
595
|
+
})
|
|
596
|
+
.unwrap_or_default();
|
|
597
|
+
rows.sort_unstable();
|
|
598
|
+
rows.dedup();
|
|
599
|
+
rows
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/// The rows `m` reads (validated reads, predicate row), sorted and deduplicated.
|
|
603
|
+
fn read_rows(m: &Member) -> Vec<u64> {
|
|
604
|
+
let mut rows: Vec<u64> = m
|
|
605
|
+
.validate
|
|
606
|
+
.as_ref()
|
|
607
|
+
.map(|v| v.reads.iter().map(|r| hash_row(r.table, &r.row)).collect())
|
|
608
|
+
.unwrap_or_default();
|
|
609
|
+
if let Some((table, row, _)) = m.predicate.as_deref() {
|
|
610
|
+
rows.push(hash_row(*table, row));
|
|
611
|
+
}
|
|
612
|
+
// D314: a returning member reads its row at apply (simp724's Q2 on #1440).
|
|
613
|
+
if let Reply::Returning(_) = &m.reply
|
|
614
|
+
&& let Ok(batch) = m.bytes.batch()
|
|
615
|
+
{
|
|
616
|
+
rows.extend(batch.iter().flatten().map(|mu| hash_row(mu.table, mu.row)));
|
|
617
|
+
}
|
|
618
|
+
rows.sort_unstable();
|
|
619
|
+
rows.dedup();
|
|
620
|
+
rows
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/// The tracked undecided shares here that write or read one of `rows` (sorted), with
|
|
624
|
+
/// their priorities, in seqno order (`prepared` is a hash map; a seed replays).
|
|
625
|
+
fn shares_on(&self, rows: &[u64], skip: Seqno) -> Vec<(Seqno, CommitPriority)> {
|
|
626
|
+
let mut on: Vec<(Seqno, CommitPriority)> = self
|
|
627
|
+
.prepared
|
|
628
|
+
.iter()
|
|
629
|
+
.filter(|(seqno, share)| {
|
|
630
|
+
**seqno != skip
|
|
631
|
+
&& share.tracked
|
|
632
|
+
&& (share.reads.iter().any(|h| rows.binary_search(h).is_ok())
|
|
633
|
+
|| share
|
|
634
|
+
.bytes
|
|
635
|
+
.batch()
|
|
636
|
+
.iter()
|
|
637
|
+
.flatten()
|
|
638
|
+
.any(|mu| rows.binary_search(&hash_row(mu.table, mu.row)).is_ok()))
|
|
639
|
+
})
|
|
640
|
+
.map(|(seqno, share)| (*seqno, share.priority))
|
|
641
|
+
.collect();
|
|
642
|
+
on.sort_unstable();
|
|
643
|
+
on
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/// Whether blind PREPARE `m` writes a row a refused reading commit older than it fences
|
|
647
|
+
/// (D267): it is refused, chained or not, so the row drains for that commit's retry.
|
|
648
|
+
/// The oldest such commit, which the refusal waits behind. A PREPARE that reads cannot
|
|
649
|
+
/// chain, so it cannot keep the row from draining: it is not fenced, and D249 orders
|
|
650
|
+
/// it as before (fenced, every reading commit on a hot row waited out an older one's
|
|
651
|
+
/// whole retry).
|
|
652
|
+
#[inline]
|
|
653
|
+
pub(super) fn fenced(&self, m: &Member) -> Option<CommitPriority> {
|
|
654
|
+
if self.reader_fences.is_empty()
|
|
655
|
+
|| m.validate.as_ref().is_some_and(|v| !v.reads.is_empty())
|
|
656
|
+
|| m.predicate.is_some()
|
|
657
|
+
{
|
|
658
|
+
return None;
|
|
659
|
+
}
|
|
660
|
+
self.fenced_slow(m)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
#[cold]
|
|
664
|
+
#[inline(never)]
|
|
665
|
+
fn fenced_slow(&self, m: &Member) -> Option<CommitPriority> {
|
|
666
|
+
let priority = self.prepared.get(&m.seqno)?.priority;
|
|
667
|
+
let rows = Self::written_rows(m);
|
|
668
|
+
self.reader_fences
|
|
669
|
+
.iter()
|
|
670
|
+
.filter(|(p, fenced)| {
|
|
671
|
+
*p < priority && fenced.iter().any(|h| rows.binary_search(h).is_ok())
|
|
672
|
+
})
|
|
673
|
+
.map(|(p, _)| *p)
|
|
674
|
+
.min()
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/// Single `m` starts waiting for the undecided shares on its rows (D267): it waits for
|
|
678
|
+
/// those only; shares chained on its rows later wait for it.
|
|
679
|
+
#[cold]
|
|
680
|
+
#[inline(never)]
|
|
681
|
+
pub(super) fn start_share_wait(&mut self, m: &mut Member) {
|
|
682
|
+
let writes = Self::written_rows(m);
|
|
683
|
+
let reads = Self::read_rows(m);
|
|
684
|
+
let mut ahead: Vec<Seqno> = self
|
|
685
|
+
.shares_on(&writes, 0)
|
|
686
|
+
.into_iter()
|
|
687
|
+
.map(|(seqno, _)| seqno)
|
|
688
|
+
.collect();
|
|
689
|
+
// A row it reads waits only for the shares writing it.
|
|
690
|
+
for (seqno, share) in &self.prepared {
|
|
691
|
+
if share.tracked
|
|
692
|
+
&& share
|
|
693
|
+
.bytes
|
|
694
|
+
.batch()
|
|
695
|
+
.iter()
|
|
696
|
+
.flatten()
|
|
697
|
+
.any(|mu| reads.binary_search(&hash_row(mu.table, mu.row)).is_ok())
|
|
698
|
+
{
|
|
699
|
+
ahead.push(*seqno);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
ahead.sort_unstable();
|
|
703
|
+
ahead.dedup();
|
|
704
|
+
let mut rows = writes.clone();
|
|
705
|
+
rows.extend(&reads);
|
|
706
|
+
rows.sort_unstable();
|
|
707
|
+
rows.dedup();
|
|
708
|
+
self.next_share_wait = self.next_share_wait.wrapping_add(1).max(1);
|
|
709
|
+
let id = self.next_share_wait;
|
|
710
|
+
for h in &rows {
|
|
711
|
+
self.single_rows.entry(*h).or_default().push(id);
|
|
712
|
+
}
|
|
713
|
+
self.waiting_singles.insert(
|
|
714
|
+
id,
|
|
715
|
+
WaitingSingle {
|
|
716
|
+
ahead,
|
|
717
|
+
writes,
|
|
718
|
+
reads,
|
|
719
|
+
rows,
|
|
720
|
+
},
|
|
721
|
+
);
|
|
722
|
+
m.share_wait = id;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/// Whether waiting single `m` still has a share it waits for undecided here.
|
|
726
|
+
#[cold]
|
|
727
|
+
#[inline(never)]
|
|
728
|
+
pub(super) fn single_ahead_undecided(&self, m: &Member) -> bool {
|
|
729
|
+
self.waiting_singles.get(&m.share_wait).is_some_and(|w| {
|
|
730
|
+
w.ahead
|
|
731
|
+
.iter()
|
|
732
|
+
.any(|s| self.prepared.get(s).is_some_and(|share| share.tracked))
|
|
733
|
+
})
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/// A waiting single that writes a row PREPARE `m` reads (D267), or reads a row `m`
|
|
737
|
+
/// writes (D271, perf287's M1), if any.
|
|
738
|
+
#[inline]
|
|
739
|
+
pub(super) fn overlaps_waiting_single(&self, m: &Member) -> Option<u32> {
|
|
740
|
+
if self.waiting_singles.is_empty() {
|
|
741
|
+
return None;
|
|
742
|
+
}
|
|
743
|
+
self.overlaps_waiting_single_slow(m)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
#[cold]
|
|
747
|
+
#[inline(never)]
|
|
748
|
+
fn overlaps_waiting_single_slow(&self, m: &Member) -> Option<u32> {
|
|
749
|
+
let on = |rows: Vec<u64>, of: fn(&WaitingSingle) -> &[u64]| {
|
|
750
|
+
rows.into_iter().find_map(|h| {
|
|
751
|
+
self.single_rows.get(&h).and_then(|ids| {
|
|
752
|
+
ids.iter()
|
|
753
|
+
.copied()
|
|
754
|
+
.find(|id| of(&self.waiting_singles[id]).binary_search(&h).is_ok())
|
|
755
|
+
})
|
|
756
|
+
})
|
|
757
|
+
};
|
|
758
|
+
on(Self::read_rows(m), |w| &w.writes).or_else(|| on(Self::written_rows(m), |w| &w.reads))
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/// Waiting single `id` was logged, or failed (D267): the shares chained behind it count
|
|
762
|
+
/// one less.
|
|
763
|
+
#[cold]
|
|
764
|
+
#[inline(never)]
|
|
765
|
+
pub(super) fn end_single_wait(&mut self, id: u32, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
766
|
+
let Some(w) = self.waiting_singles.remove(&id) else {
|
|
767
|
+
return;
|
|
768
|
+
};
|
|
769
|
+
for h in &w.rows {
|
|
770
|
+
if let Some(ids) = self.single_rows.get_mut(h) {
|
|
771
|
+
ids.retain(|i| *i != id);
|
|
772
|
+
if ids.is_empty() {
|
|
773
|
+
self.single_rows.remove(h);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
// The PREPAREs it refused may retry (R4: not before, or they spin).
|
|
778
|
+
let mut i = 0;
|
|
779
|
+
while i < self.overlap_refused.len() {
|
|
780
|
+
if self.overlap_refused[i].single == id {
|
|
781
|
+
let r = self.overlap_refused.swap_remove(i);
|
|
782
|
+
self.wake_overlap(r, ctx);
|
|
783
|
+
} else {
|
|
784
|
+
i += 1;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/// After a decision here (D249): wakes the coordinators refused over an overlap that has
|
|
790
|
+
/// cleared. One waiting behind an older commit is woken once that commit's next attempt
|
|
791
|
+
/// here (a later seqno than the share it met) commits, or by its last; any other once
|
|
792
|
+
/// no undecided share here holds its rows. Not by an aborted attempt (D263): the older
|
|
793
|
+
/// commit's attempt may have been refused elsewhere over the younger one's share, at the
|
|
794
|
+
/// younger one's own coordinator, and woken together they would cross again on every
|
|
795
|
+
/// round. The younger one holds no share while it waits, so the older one's retry
|
|
796
|
+
/// meets none of its shares.
|
|
797
|
+
#[inline(never)]
|
|
798
|
+
pub(super) fn release_overlaps(
|
|
799
|
+
&mut self,
|
|
800
|
+
seqno: Seqno,
|
|
801
|
+
priority: CommitPriority,
|
|
802
|
+
commit: bool,
|
|
803
|
+
last: bool,
|
|
804
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
805
|
+
) {
|
|
806
|
+
if last && !self.reader_fences.is_empty() {
|
|
807
|
+
// Its last attempt: a fence it set here is done (D267).
|
|
808
|
+
self.reader_fences.retain(|(p, _)| *p != priority);
|
|
809
|
+
}
|
|
810
|
+
let mut i = 0;
|
|
811
|
+
while i < self.overlap_refused.len() {
|
|
812
|
+
let r = &self.overlap_refused[i];
|
|
813
|
+
let go = r.single == 0
|
|
814
|
+
&& match r.behind {
|
|
815
|
+
Some((older, met)) => older == priority && (last || (commit && seqno > met)),
|
|
816
|
+
None => !r.rows.iter().any(|h| {
|
|
817
|
+
self.pending_rows.contains_key(h) || self.pending_reads.contains_key(h)
|
|
818
|
+
}),
|
|
819
|
+
};
|
|
820
|
+
if go {
|
|
821
|
+
let r = self.overlap_refused.swap_remove(i);
|
|
822
|
+
self.wake_overlap(r, ctx);
|
|
823
|
+
} else {
|
|
824
|
+
i += 1;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/// A share of the commit of `priority`, writing `written` (`hash_row`, sorted), committed
|
|
830
|
+
/// here (D276): a refused commit that reads one of those rows and now fails validation
|
|
831
|
+
/// on it would fail its retry the same way (its snapshot stays, and a version above it
|
|
832
|
+
/// stays above it), so its coordinator is told to fail it now, without another round.
|
|
833
|
+
#[cold]
|
|
834
|
+
#[inline(never)]
|
|
835
|
+
pub(super) fn doom_overlaps(
|
|
836
|
+
&mut self,
|
|
837
|
+
priority: CommitPriority,
|
|
838
|
+
written: &[u64],
|
|
839
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
840
|
+
) {
|
|
841
|
+
let mut refused = std::mem::take(&mut self.overlap_refused);
|
|
842
|
+
let mut i = 0;
|
|
843
|
+
while i < refused.len() {
|
|
844
|
+
let r = &refused[i];
|
|
845
|
+
let doomed = r.priority != priority
|
|
846
|
+
&& r.validate.as_deref().is_some_and(|v| {
|
|
847
|
+
v.reads.iter().any(|read| {
|
|
848
|
+
written
|
|
849
|
+
.binary_search(&hash_row(read.table, &read.row))
|
|
850
|
+
.is_ok()
|
|
851
|
+
&& self.conflicts(v.seqno, read).unwrap_or(false)
|
|
852
|
+
})
|
|
853
|
+
});
|
|
854
|
+
if doomed {
|
|
855
|
+
let r = refused.swap_remove(i);
|
|
856
|
+
trace!(
|
|
857
|
+
"shard {} dooms {:?}: tells {}",
|
|
858
|
+
self.id.0, r.priority, r.coordinator.0
|
|
859
|
+
);
|
|
860
|
+
// Through the queue, as `wake_overlap`.
|
|
861
|
+
let _ = ctx.submitter(r.coordinator).submit(ShardMsg::Wake {
|
|
862
|
+
from: self.id,
|
|
863
|
+
reason: WakeReason::Doomed {
|
|
864
|
+
priority: r.priority,
|
|
865
|
+
},
|
|
866
|
+
});
|
|
867
|
+
} else {
|
|
868
|
+
i += 1;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
refused.append(&mut self.overlap_refused);
|
|
872
|
+
self.overlap_refused = refused;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/// The commit of `priority` retries without this shard (D249): the commits waiting here
|
|
876
|
+
/// behind it are woken.
|
|
877
|
+
pub(super) fn release_overlaps_behind(
|
|
878
|
+
&mut self,
|
|
879
|
+
priority: CommitPriority,
|
|
880
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
881
|
+
) {
|
|
882
|
+
// It retries without this shard: a fence it set here is done (D267).
|
|
883
|
+
self.reader_fences.retain(|(p, _)| *p != priority);
|
|
884
|
+
let mut i = 0;
|
|
885
|
+
while i < self.overlap_refused.len() {
|
|
886
|
+
if self.overlap_refused[i]
|
|
887
|
+
.behind
|
|
888
|
+
.is_some_and(|(older, _)| older == priority)
|
|
889
|
+
{
|
|
890
|
+
let r = self.overlap_refused.swap_remove(i);
|
|
891
|
+
self.wake_overlap(r, ctx);
|
|
892
|
+
} else {
|
|
893
|
+
i += 1;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
fn wake_overlap(&mut self, r: OverlapRefusal, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
899
|
+
trace!(
|
|
900
|
+
"shard {} overlap cleared for {:?}: tells {}",
|
|
901
|
+
self.id.0, r.priority, r.coordinator.0
|
|
902
|
+
);
|
|
903
|
+
// Through the queue even to this shard: it must follow what this shard queued for the
|
|
904
|
+
// same commit before it, or the coordinator, not yet waiting, drops it (D249).
|
|
905
|
+
let _ = ctx.submitter(r.coordinator).submit(ShardMsg::Wake {
|
|
906
|
+
from: self.id,
|
|
907
|
+
reason: WakeReason::Overlap {
|
|
908
|
+
priority: r.priority,
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/// A coordinator's side of a participant's wake (D245, D249): the retries it wakes run
|
|
914
|
+
/// now (`RetryWait::woken_by`). Room is remembered per shard by generation, for a retry
|
|
915
|
+
/// still in its abort round; an overlap wake that came before the refused round was
|
|
916
|
+
/// decided is kept for that commit's retry.
|
|
917
|
+
pub(super) fn on_wake(
|
|
918
|
+
&mut self,
|
|
919
|
+
from: ShardId,
|
|
920
|
+
reason: WakeReason,
|
|
921
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
922
|
+
) {
|
|
923
|
+
if let WakeReason::Room { generation } = reason {
|
|
924
|
+
let seen = &mut self.room_freed[usize::from(from.0)];
|
|
925
|
+
*seen = (*seen).max(generation);
|
|
926
|
+
}
|
|
927
|
+
if let WakeReason::RoomGone { generation } = reason {
|
|
928
|
+
let seen = &mut self.room_gone[usize::from(from.0)];
|
|
929
|
+
*seen = (*seen).max(generation);
|
|
930
|
+
}
|
|
931
|
+
if let WakeReason::Doomed { priority } = reason {
|
|
932
|
+
self.on_doomed(priority, ctx);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
let (ready, wait): (Vec<CoordinateReq>, Vec<CoordinateReq>) =
|
|
936
|
+
std::mem::take(&mut self.retries)
|
|
937
|
+
.into_iter()
|
|
938
|
+
.partition(|r| {
|
|
939
|
+
r.wait
|
|
940
|
+
.is_some_and(|w| w.woken_by(from, reason, (r.submitted_at, r.first_seqno)))
|
|
941
|
+
});
|
|
942
|
+
self.retries = wait;
|
|
943
|
+
trace!(
|
|
944
|
+
"shard {} wake from {} ({reason:?}): {} retries ready",
|
|
945
|
+
self.id.0,
|
|
946
|
+
from.0,
|
|
947
|
+
ready.len()
|
|
948
|
+
);
|
|
949
|
+
if let WakeReason::Overlap { priority } = reason
|
|
950
|
+
&& ready.is_empty()
|
|
951
|
+
&& self
|
|
952
|
+
.coord
|
|
953
|
+
.values()
|
|
954
|
+
.any(|c| c.priority == priority && c.refused == Some(RetryWait::Overlap { from }))
|
|
955
|
+
{
|
|
956
|
+
self.overlap_released.push((from, priority));
|
|
957
|
+
}
|
|
958
|
+
for mut r in ready {
|
|
959
|
+
r.wait = None;
|
|
960
|
+
if self.closing {
|
|
961
|
+
r.reply.notify(Err(Error::Closed));
|
|
962
|
+
} else if matches!(reason, WakeReason::RoomGone { .. }) {
|
|
963
|
+
// Its room will not free (D301).
|
|
964
|
+
trace!(
|
|
965
|
+
"shard {} gives up on a room retry: room gone on {}",
|
|
966
|
+
self.id.0, from.0
|
|
967
|
+
);
|
|
968
|
+
r.reply.notify(Err(Error::Busy));
|
|
969
|
+
} else {
|
|
970
|
+
self.retry(r, ctx);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
self.try_finish_close(ctx);
|
|
974
|
+
self.arm_tablet_timer(ctx);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/// A participant found the commit of `priority` doomed (D276): a retry waiting for its
|
|
978
|
+
/// wake fails now; one whose round is still undecided or aborting fails at the round's
|
|
979
|
+
/// end (`on_applied`). Otherwise it has ended, or it runs a later round: that round's
|
|
980
|
+
/// PREPARE there either came after the doom and fails validation, or was refused again
|
|
981
|
+
/// and doomed in the same pass.
|
|
982
|
+
fn on_doomed(&mut self, priority: CommitPriority, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
983
|
+
if let Some(i) = self
|
|
984
|
+
.retries
|
|
985
|
+
.iter()
|
|
986
|
+
.position(|r| (r.submitted_at, r.first_seqno) == priority)
|
|
987
|
+
{
|
|
988
|
+
let r = self.retries.swap_remove(i);
|
|
989
|
+
self.overlap_released.retain(|(_, p)| *p != priority);
|
|
990
|
+
self.fail_doomed(r.submitted_at, r.first_seqno, &r.prev_shards, r.reply, ctx);
|
|
991
|
+
} else if self
|
|
992
|
+
.coord
|
|
993
|
+
.values()
|
|
994
|
+
.any(|c| c.priority == priority && !c.committing)
|
|
995
|
+
&& !self.doomed.contains(&priority)
|
|
996
|
+
{
|
|
997
|
+
self.doomed.push(priority);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/// Ends a doomed commit (D276) with `Conflict`: its last participants hear `Released`,
|
|
1002
|
+
/// so fences it set and commits waiting behind it there go on, as for any commit that
|
|
1003
|
+
/// runs no further attempt.
|
|
1004
|
+
pub(super) fn fail_doomed(
|
|
1005
|
+
&mut self,
|
|
1006
|
+
submitted_at: u64,
|
|
1007
|
+
first_seqno: Seqno,
|
|
1008
|
+
shards: &[ShardId],
|
|
1009
|
+
reply: Notifier<Result<Settled>>,
|
|
1010
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
1011
|
+
) {
|
|
1012
|
+
trace!(
|
|
1013
|
+
"shard {} fails doomed {:?}",
|
|
1014
|
+
self.id.0,
|
|
1015
|
+
(submitted_at, first_seqno)
|
|
1016
|
+
);
|
|
1017
|
+
self.shared.metrics[usize::from(self.id.0)]
|
|
1018
|
+
.cross
|
|
1019
|
+
.failed_fast
|
|
1020
|
+
.fetch_add(1, Ordering::Relaxed);
|
|
1021
|
+
self.release_shards(submitted_at, first_seqno, shards, ctx);
|
|
1022
|
+
reply.notify(Err(Error::Conflict));
|
|
1023
|
+
self.try_finish_close(ctx);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/// A PREPARE refused without a record: for rows this shard is moving or no longer owns
|
|
1027
|
+
/// (`Moved`: the coordinator retries through the new tablet map), for arena room or an
|
|
1028
|
+
/// undecided share's rows (the coordinator retries on this shard's wake).
|
|
1029
|
+
fn refuse_prepare(
|
|
1030
|
+
&mut self,
|
|
1031
|
+
m: Member,
|
|
1032
|
+
error: PrepareError,
|
|
1033
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
1034
|
+
) {
|
|
1035
|
+
let MemberKind::Prepare { coordinator } = m.kind else {
|
|
1036
|
+
return;
|
|
1037
|
+
};
|
|
1038
|
+
if let Some(share) = self.prepared.remove(&m.seqno) {
|
|
1039
|
+
if share.tracked {
|
|
1040
|
+
self.track_share_rows(share.bytes.batch(), false);
|
|
1041
|
+
self.track_share_reads(&share.reads, false);
|
|
1042
|
+
}
|
|
1043
|
+
self.release_room(share.reserved);
|
|
1044
|
+
}
|
|
1045
|
+
self.release_room(m.reserved);
|
|
1046
|
+
self.send(
|
|
1047
|
+
coordinator,
|
|
1048
|
+
ShardMsg::Prepared {
|
|
1049
|
+
seqno: m.seqno,
|
|
1050
|
+
from: self.id,
|
|
1051
|
+
error: Some(error),
|
|
1052
|
+
},
|
|
1053
|
+
ctx,
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/// Routes a single-shard member again through the current tablet map. Returns it when it
|
|
1058
|
+
/// is this shard's; otherwise submits it to its owner (or to two-phase commit when its
|
|
1059
|
+
/// rows now span shards) and returns `None`.
|
|
1060
|
+
pub(super) fn forward(
|
|
1061
|
+
&mut self,
|
|
1062
|
+
mut m: Member,
|
|
1063
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
1064
|
+
) -> Option<Member> {
|
|
1065
|
+
if m.share_wait != 0 {
|
|
1066
|
+
// It leaves: what it waited for here no longer orders it, and the shares chained
|
|
1067
|
+
// behind it here go on (D267).
|
|
1068
|
+
self.end_single_wait(m.share_wait, ctx);
|
|
1069
|
+
m.share_wait = 0;
|
|
1070
|
+
}
|
|
1071
|
+
let view = self.shared.view.load_full();
|
|
1072
|
+
let shards = commit_shards(
|
|
1073
|
+
&view,
|
|
1074
|
+
m.bytes.as_slice(),
|
|
1075
|
+
m.validate.as_ref().map(|v| v.reads.as_slice()),
|
|
1076
|
+
m.predicate.as_deref().map(|(t, r, _)| (*t, r.as_slice())),
|
|
1077
|
+
);
|
|
1078
|
+
let shards = match shards {
|
|
1079
|
+
Ok(s) => s,
|
|
1080
|
+
Err(e) => {
|
|
1081
|
+
self.release_prev_shards(&m, ctx);
|
|
1082
|
+
m.reply.resolve(Err(e));
|
|
1083
|
+
return None;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
if shards.len() <= 1 {
|
|
1087
|
+
// A retried cross-shard commit now on one shard runs no further two-phase attempt
|
|
1088
|
+
// at its earlier participants: commits refused there behind it (D249's
|
|
1089
|
+
// older-first) are released now, or they would wait for the write-stall timeout
|
|
1090
|
+
// (#772).
|
|
1091
|
+
self.release_prev_shards(&m, ctx);
|
|
1092
|
+
}
|
|
1093
|
+
match shards.as_slice() {
|
|
1094
|
+
[] => Some(m),
|
|
1095
|
+
[s] if *s == self.id => {
|
|
1096
|
+
self.refresh_tablets();
|
|
1097
|
+
Some(m)
|
|
1098
|
+
}
|
|
1099
|
+
[s] => {
|
|
1100
|
+
let s = *s;
|
|
1101
|
+
let mut req = member_req(m);
|
|
1102
|
+
req.map_version = view.tablets.version();
|
|
1103
|
+
if let Err(pigeonhole_runtime::Error::Closed) =
|
|
1104
|
+
ctx.submitter(s).submit(ShardMsg::Commit(req))
|
|
1105
|
+
{
|
|
1106
|
+
// The message (and its reply) is dropped with the closed queue: the
|
|
1107
|
+
// waiter sees `Closed`.
|
|
1108
|
+
}
|
|
1109
|
+
None
|
|
1110
|
+
}
|
|
1111
|
+
_ => {
|
|
1112
|
+
let req = member_req(m);
|
|
1113
|
+
let Reply::Commit(reply) = req.reply else {
|
|
1114
|
+
req.reply.resolve(Err(Error::InvalidArgument(
|
|
1115
|
+
"check_and_mutate touches one row".to_owned(),
|
|
1116
|
+
)));
|
|
1117
|
+
return None;
|
|
1118
|
+
};
|
|
1119
|
+
let parts = match split_by_shard(&view, &req.bytes, &shards) {
|
|
1120
|
+
Ok(p) => p,
|
|
1121
|
+
Err(e) => {
|
|
1122
|
+
reply.notify(Err(e));
|
|
1123
|
+
return None;
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
let retried = req.retry.map(|r| *r).unwrap_or_default();
|
|
1127
|
+
let creq = CoordinateReq {
|
|
1128
|
+
parts,
|
|
1129
|
+
durability: req.durability,
|
|
1130
|
+
reply,
|
|
1131
|
+
submitted_at: req.submitted_at,
|
|
1132
|
+
validate: req.validate,
|
|
1133
|
+
map_version: view.tablets.version(),
|
|
1134
|
+
epoch: 0,
|
|
1135
|
+
first_seqno: retried.first_seqno,
|
|
1136
|
+
prev_shards: retried.prev_shards,
|
|
1137
|
+
attempts: retried.attempts,
|
|
1138
|
+
wait: None,
|
|
1139
|
+
};
|
|
1140
|
+
if shards[0] == self.id {
|
|
1141
|
+
self.start_coordination(creq, ctx);
|
|
1142
|
+
} else {
|
|
1143
|
+
let _ = ctx
|
|
1144
|
+
.submitter(shards[0])
|
|
1145
|
+
.submit(ShardMsg::Coordinate(Box::new(creq)));
|
|
1146
|
+
}
|
|
1147
|
+
None
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/// Sends `Released` to the earlier participants of a retried cross-shard commit that runs
|
|
1153
|
+
/// no further two-phase attempt (D249, #772).
|
|
1154
|
+
fn release_prev_shards(&mut self, m: &Member, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1155
|
+
if let Some(r) = m.retry.as_deref() {
|
|
1156
|
+
self.release_shards(m.submitted_at, r.first_seqno, &r.prev_shards.clone(), ctx);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/// `Released` for the commit of priority `(submitted_at, first_seqno)` to `shards`.
|
|
1161
|
+
fn release_shards(
|
|
1162
|
+
&mut self,
|
|
1163
|
+
submitted_at: u64,
|
|
1164
|
+
first_seqno: Seqno,
|
|
1165
|
+
shards: &[ShardId],
|
|
1166
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
1167
|
+
) {
|
|
1168
|
+
let priority = (submitted_at, first_seqno);
|
|
1169
|
+
for p in shards {
|
|
1170
|
+
self.send(*p, ShardMsg::Released { priority }, ctx);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/// Cross-shard commits refused by a participant moving a tablet: retried once the tablet
|
|
1175
|
+
/// map is newer than the one they were routed with.
|
|
1176
|
+
pub(super) fn run_retries(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1177
|
+
if self.retries.is_empty() {
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if self.closing {
|
|
1181
|
+
for r in self.retries.drain(..) {
|
|
1182
|
+
r.reply.notify(Err(Error::Closed));
|
|
1183
|
+
}
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
// After a shard's death no commit is accepted (D227): one waiting for a participant's
|
|
1187
|
+
// room, or for an overlap there to clear, may wait on the dead one, so it ends now
|
|
1188
|
+
// rather than at the timeout (D245, D249).
|
|
1189
|
+
let waits = |r: &CoordinateReq| {
|
|
1190
|
+
matches!(
|
|
1191
|
+
r.wait,
|
|
1192
|
+
Some(RetryWait::Room { .. } | RetryWait::Overlap { .. })
|
|
1193
|
+
)
|
|
1194
|
+
};
|
|
1195
|
+
if self.shared.shard_died.load(Ordering::Acquire) && self.retries.iter().any(waits) {
|
|
1196
|
+
let (dead, keep): (Vec<CoordinateReq>, Vec<CoordinateReq>) =
|
|
1197
|
+
std::mem::take(&mut self.retries)
|
|
1198
|
+
.into_iter()
|
|
1199
|
+
.partition(waits);
|
|
1200
|
+
self.retries = keep;
|
|
1201
|
+
for r in dead {
|
|
1202
|
+
r.reply.notify(Err(Error::Closed));
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
let version = self.shared.view.load().tablets.version();
|
|
1206
|
+
let epoch = self.shared.tablet_epoch.load(Ordering::Acquire);
|
|
1207
|
+
let (ready, wait): (Vec<CoordinateReq>, Vec<CoordinateReq>) =
|
|
1208
|
+
std::mem::take(&mut self.retries)
|
|
1209
|
+
.into_iter()
|
|
1210
|
+
.partition(|r| !waits(r) && (r.map_version < version || r.epoch < epoch));
|
|
1211
|
+
self.retries = wait;
|
|
1212
|
+
if !self.retries.is_empty() {
|
|
1213
|
+
trace!(
|
|
1214
|
+
"shard {} retries waiting: {:?} (map v{version}, epoch {epoch})",
|
|
1215
|
+
self.id.0,
|
|
1216
|
+
self.retries
|
|
1217
|
+
.iter()
|
|
1218
|
+
.map(|r| {
|
|
1219
|
+
(
|
|
1220
|
+
r.wait,
|
|
1221
|
+
(r.submitted_at, r.first_seqno),
|
|
1222
|
+
r.map_version,
|
|
1223
|
+
r.epoch,
|
|
1224
|
+
)
|
|
1225
|
+
})
|
|
1226
|
+
.collect::<Vec<_>>()
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
for r in ready {
|
|
1230
|
+
self.retry(r, ctx);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/// Runs a refused cross-shard commit again through the current tablet map.
|
|
1235
|
+
pub(super) fn retry(&mut self, r: CoordinateReq, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1236
|
+
let mut builder = BatchBuilder::new();
|
|
1237
|
+
let mut failed = None;
|
|
1238
|
+
for (_, part) in &r.parts {
|
|
1239
|
+
for mu in part.batch().iter() {
|
|
1240
|
+
let pushed = mu.map_err(Error::from).and_then(|mu| {
|
|
1241
|
+
builder
|
|
1242
|
+
.push(
|
|
1243
|
+
mu.table,
|
|
1244
|
+
mu.family,
|
|
1245
|
+
mu.kind,
|
|
1246
|
+
mu.row,
|
|
1247
|
+
mu.qualifier,
|
|
1248
|
+
mu.ts,
|
|
1249
|
+
mu.value,
|
|
1250
|
+
)
|
|
1251
|
+
.map_err(Error::from)
|
|
1252
|
+
});
|
|
1253
|
+
if let Err(e) = pushed {
|
|
1254
|
+
failed = Some(e);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if let Some(e) = failed {
|
|
1259
|
+
self.release_shards(r.submitted_at, r.first_seqno, &r.prev_shards, ctx);
|
|
1260
|
+
r.reply.notify(Err(e));
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
let m = Member::single(CommitReq {
|
|
1264
|
+
bytes: builder,
|
|
1265
|
+
durability: r.durability,
|
|
1266
|
+
reply: Reply::Commit(r.reply),
|
|
1267
|
+
submitted_at: r.submitted_at,
|
|
1268
|
+
validate: r.validate,
|
|
1269
|
+
predicate: None,
|
|
1270
|
+
retry: Some(Box::new(Retried {
|
|
1271
|
+
first_seqno: r.first_seqno,
|
|
1272
|
+
prev_shards: r.prev_shards,
|
|
1273
|
+
attempts: r.attempts,
|
|
1274
|
+
})),
|
|
1275
|
+
map_version: r.map_version,
|
|
1276
|
+
});
|
|
1277
|
+
if let Some(m) = self.forward(m, ctx) {
|
|
1278
|
+
self.pending.push(m);
|
|
1279
|
+
let _ = ctx.submitter(self.id).submit(ShardMsg::Kick);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/// Fails with `Busy` every parked commit and every waiting retry that was submitted
|
|
1284
|
+
/// `write_stall_timeout_nanos` ago (#102): a change can wait on a long compaction or a
|
|
1285
|
+
/// slow flush, and a commit that keeps meeting changes must not wait for ever.
|
|
1286
|
+
pub(super) fn expire_tablet_waits(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1287
|
+
if self.parked.is_empty() && self.retries.is_empty() {
|
|
1288
|
+
if let Some((_, t)) = self.tablet_timer.take() {
|
|
1289
|
+
t.cancel();
|
|
1290
|
+
}
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
let now = ctx.now_nanos();
|
|
1294
|
+
let timeout = self.shared.write_stall_timeout_nanos;
|
|
1295
|
+
let late = |submitted_at: u64| now.saturating_sub(submitted_at) >= timeout;
|
|
1296
|
+
if self.parked.iter().any(|m| late(m.submitted_at)) {
|
|
1297
|
+
let (expired, kept): (Vec<Member>, Vec<Member>) = std::mem::take(&mut self.parked)
|
|
1298
|
+
.into_iter()
|
|
1299
|
+
.partition(|m| late(m.submitted_at));
|
|
1300
|
+
self.parked = kept;
|
|
1301
|
+
self.parked_rows = self.parked.iter().flat_map(member_rows).collect();
|
|
1302
|
+
for m in expired {
|
|
1303
|
+
trace!("shard {} parked commit times out", self.id.0);
|
|
1304
|
+
m.reply.resolve(Err(Error::Busy));
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if self.retries.iter().any(|r| late(r.submitted_at)) {
|
|
1308
|
+
let (expired, kept): (Vec<CoordinateReq>, Vec<CoordinateReq>) =
|
|
1309
|
+
std::mem::take(&mut self.retries)
|
|
1310
|
+
.into_iter()
|
|
1311
|
+
.partition(|r| late(r.submitted_at));
|
|
1312
|
+
self.retries = kept;
|
|
1313
|
+
for r in expired {
|
|
1314
|
+
trace!("shard {} retried commit times out", self.id.0);
|
|
1315
|
+
// It runs no further attempt: commits waiting behind it go (#772).
|
|
1316
|
+
self.release_shards(r.submitted_at, r.first_seqno, &r.prev_shards, ctx);
|
|
1317
|
+
r.reply.notify(Err(Error::Busy));
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
self.arm_tablet_timer(ctx);
|
|
1321
|
+
self.try_finish_close(ctx);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/// Arms (or re-arms) the timer that ends the oldest parked commit or retry.
|
|
1325
|
+
pub(super) fn arm_tablet_timer(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1326
|
+
let oldest = self
|
|
1327
|
+
.parked
|
|
1328
|
+
.iter()
|
|
1329
|
+
.map(|m| m.submitted_at)
|
|
1330
|
+
.chain(self.retries.iter().map(|r| r.submitted_at))
|
|
1331
|
+
.min();
|
|
1332
|
+
let Some(oldest) = oldest else {
|
|
1333
|
+
if let Some((_, t)) = self.tablet_timer.take() {
|
|
1334
|
+
t.cancel();
|
|
1335
|
+
}
|
|
1336
|
+
return;
|
|
1337
|
+
};
|
|
1338
|
+
let deadline = oldest.saturating_add(self.shared.write_stall_timeout_nanos);
|
|
1339
|
+
// A timer that gave up on a clock still frozen is not armed again: the change's
|
|
1340
|
+
// progress releases the commits instead.
|
|
1341
|
+
if let Some((at, t)) = &self.tablet_timer
|
|
1342
|
+
&& ((*at <= deadline && !t.finished()) || t.frozen(ctx.now_nanos()))
|
|
1343
|
+
{
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if let Some((_, t)) = self.tablet_timer.take() {
|
|
1347
|
+
t.cancel();
|
|
1348
|
+
}
|
|
1349
|
+
trace!("shard {} tablet timer armed for {deadline}", self.id.0);
|
|
1350
|
+
let state = TimerState::new();
|
|
1351
|
+
ctx.spawn(Box::new(ClockTimer::new(
|
|
1352
|
+
&self.shared.vfs,
|
|
1353
|
+
deadline,
|
|
1354
|
+
Arc::clone(&state),
|
|
1355
|
+
ctx.submitter(self.id).clone(),
|
|
1356
|
+
ShardMsg::Kick,
|
|
1357
|
+
)));
|
|
1358
|
+
self.tablet_timer = Some((deadline, state));
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// ---- the change ----
|
|
1362
|
+
|
|
1363
|
+
/// Queues a tablet change and starts it if none is running.
|
|
1364
|
+
pub(super) fn request_tablet_op(
|
|
1365
|
+
&mut self,
|
|
1366
|
+
kind: TabletOpKind,
|
|
1367
|
+
reply: Option<Notifier<Result<()>>>,
|
|
1368
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
1369
|
+
) {
|
|
1370
|
+
if !self.tablets_on() {
|
|
1371
|
+
if let Some(r) = reply {
|
|
1372
|
+
r.notify(Err(Error::Unsupported("tablet changes are off")));
|
|
1373
|
+
}
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if self.closing {
|
|
1377
|
+
if let Some(r) = reply {
|
|
1378
|
+
r.notify(Err(Error::Closed));
|
|
1379
|
+
}
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
self.op_queue.push_back((kind, reply));
|
|
1383
|
+
self.start_next_op(ctx);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
fn start_next_op(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1387
|
+
while self.op.is_none() {
|
|
1388
|
+
let Some((kind, reply)) = self.op_queue.pop_front() else {
|
|
1389
|
+
return;
|
|
1390
|
+
};
|
|
1391
|
+
self.refresh_tablets();
|
|
1392
|
+
match self.validate_op(&kind) {
|
|
1393
|
+
Err(e) => {
|
|
1394
|
+
balance!("shard {} tablet change {kind:?} refused: {e}", self.id.0);
|
|
1395
|
+
if let Some(r) = reply {
|
|
1396
|
+
r.notify(Err(e));
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
Ok((entries, reserved)) => {
|
|
1400
|
+
balance!("shard {} tablet change {kind:?} starts", self.id.0);
|
|
1401
|
+
self.moving = kind.tablets().into_iter().collect();
|
|
1402
|
+
self.op = Some(ActiveOp {
|
|
1403
|
+
kind,
|
|
1404
|
+
reply,
|
|
1405
|
+
entries,
|
|
1406
|
+
committing: false,
|
|
1407
|
+
reserved,
|
|
1408
|
+
});
|
|
1409
|
+
if self.freeze_moving().is_err() {
|
|
1410
|
+
self.poison(crate::health::FailureCause::Internal);
|
|
1411
|
+
}
|
|
1412
|
+
self.spawn_flush(ctx);
|
|
1413
|
+
self.progress_op(ctx);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/// Checks a change against the current tablet map; returns the tablets it touches and the
|
|
1420
|
+
/// slots it reserved on the shards receiving tablets.
|
|
1421
|
+
fn validate_op(&self, kind: &TabletOpKind) -> Result<(Vec<TabletEntry>, Reserved)> {
|
|
1422
|
+
if self.poisoned || self.shared.pager_poisoned.load(Ordering::Acquire) {
|
|
1423
|
+
return Err(poisoned_error());
|
|
1424
|
+
}
|
|
1425
|
+
let view = self.shared.view.load();
|
|
1426
|
+
let shards = self.shared.shards;
|
|
1427
|
+
let entry = |id: TabletId| -> Result<TabletEntry> {
|
|
1428
|
+
view.tablets
|
|
1429
|
+
.entry(id)
|
|
1430
|
+
.filter(|t| t.shard == self.id)
|
|
1431
|
+
.cloned()
|
|
1432
|
+
.ok_or_else(|| {
|
|
1433
|
+
Error::InvalidArgument(format!("tablet {} is not on shard {}", id.0, self.id.0))
|
|
1434
|
+
})
|
|
1435
|
+
};
|
|
1436
|
+
match kind {
|
|
1437
|
+
TabletOpKind::Split {
|
|
1438
|
+
tablet,
|
|
1439
|
+
keys,
|
|
1440
|
+
owners,
|
|
1441
|
+
} => {
|
|
1442
|
+
let t = entry(*tablet)?;
|
|
1443
|
+
if keys.is_empty()
|
|
1444
|
+
|| owners.len() != keys.len() + 1
|
|
1445
|
+
|| owners.iter().any(|s| usize::from(s.0) >= shards)
|
|
1446
|
+
{
|
|
1447
|
+
return Err(Error::InvalidArgument("malformed split".to_owned()));
|
|
1448
|
+
}
|
|
1449
|
+
// Above the tablet's start, then above the previous key.
|
|
1450
|
+
let mut prev: &[u8] = &t.start;
|
|
1451
|
+
for k in keys {
|
|
1452
|
+
if k.as_slice() <= prev {
|
|
1453
|
+
return Err(Error::InvalidArgument(
|
|
1454
|
+
"split keys must increase strictly inside the tablet".to_owned(),
|
|
1455
|
+
));
|
|
1456
|
+
}
|
|
1457
|
+
prev = k;
|
|
1458
|
+
}
|
|
1459
|
+
if t.end.as_ref().is_some_and(|e| prev >= e.as_slice()) {
|
|
1460
|
+
return Err(Error::InvalidArgument(
|
|
1461
|
+
"split keys must increase strictly inside the tablet".to_owned(),
|
|
1462
|
+
));
|
|
1463
|
+
}
|
|
1464
|
+
// D28: refuse a split whose view would not fit the shared-memory buffer.
|
|
1465
|
+
let families = view.catalog.family_ids_of(t.table).len();
|
|
1466
|
+
let key_bytes: usize = keys.iter().map(Vec::len).sum();
|
|
1467
|
+
let grow = keys.len() * (32 + 24 * families)
|
|
1468
|
+
+ 2 * key_bytes
|
|
1469
|
+
+ t.start.len()
|
|
1470
|
+
+ t.end.as_ref().map_or(0, Vec::len)
|
|
1471
|
+
+ 16;
|
|
1472
|
+
if view.to_record().encoded_len() + grow > self.shared.view_capacity {
|
|
1473
|
+
self.shared.metrics[usize::from(self.id.0)]
|
|
1474
|
+
.refused_view
|
|
1475
|
+
.fetch_add(1, Ordering::Relaxed);
|
|
1476
|
+
return Err(Error::Unsupported(
|
|
1477
|
+
"the tablet map would not fit the shared-memory view buffer (D28)",
|
|
1478
|
+
));
|
|
1479
|
+
}
|
|
1480
|
+
// Each shard keeps its slots within its arena's chunks.
|
|
1481
|
+
let mut extra: Vec<(ShardId, usize)> = Vec::new();
|
|
1482
|
+
for o in owners {
|
|
1483
|
+
if extra.iter().any(|(s, _)| s == o) {
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
let children = owners.iter().filter(|x| *x == o).count();
|
|
1487
|
+
// The parent's slots leave this shard as its children arrive.
|
|
1488
|
+
let freed = if *o == self.id { families } else { 0 };
|
|
1489
|
+
extra.push((*o, (families * children).saturating_sub(freed)));
|
|
1490
|
+
}
|
|
1491
|
+
let reserved = self.reserve_slots(&view, &extra)?;
|
|
1492
|
+
Ok((vec![t], reserved))
|
|
1493
|
+
}
|
|
1494
|
+
TabletOpKind::Merge { left, right } => {
|
|
1495
|
+
let l = entry(*left)?;
|
|
1496
|
+
let r = entry(*right)?;
|
|
1497
|
+
if l.table != r.table || l.end.as_deref() != Some(r.start.as_slice()) {
|
|
1498
|
+
return Err(Error::InvalidArgument(
|
|
1499
|
+
"only adjacent tablets of one table merge".to_owned(),
|
|
1500
|
+
));
|
|
1501
|
+
}
|
|
1502
|
+
Ok((vec![l, r], Vec::new()))
|
|
1503
|
+
}
|
|
1504
|
+
TabletOpKind::Move { tablet, to } => {
|
|
1505
|
+
let t = entry(*tablet)?;
|
|
1506
|
+
if *to == self.id || usize::from(to.0) >= shards {
|
|
1507
|
+
return Err(Error::InvalidArgument(format!(
|
|
1508
|
+
"cannot move tablet {} to shard {}",
|
|
1509
|
+
tablet.0, to.0
|
|
1510
|
+
)));
|
|
1511
|
+
}
|
|
1512
|
+
let families = view.catalog.family_ids_of(t.table).len();
|
|
1513
|
+
let reserved = self.reserve_slots(&view, &[(*to, families)])?;
|
|
1514
|
+
Ok((vec![t], reserved))
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
/// The most `(tablet, family)` slots a shard should hold: a quarter of its arena's
|
|
1520
|
+
/// chunks. Every slot written to takes a memtable chunk (and usually two before it
|
|
1521
|
+
/// freezes), and frozen memtables keep theirs until flushed and released.
|
|
1522
|
+
fn max_slots(&self) -> usize {
|
|
1523
|
+
crate::engine::max_slots(self.arena.region().len(), self.chunk_size)
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/// Slots `shard` holds in `view`.
|
|
1527
|
+
fn slots_in(view: &View, shard: ShardId) -> usize {
|
|
1528
|
+
view.tablets
|
|
1529
|
+
.iter()
|
|
1530
|
+
.filter(|t| t.shard == shard)
|
|
1531
|
+
.map(|t| view.catalog.family_ids_of(t.table).len())
|
|
1532
|
+
.sum()
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/// Whether `shard` can take `extra` more slots under [`ShardState::max_slots`], counting
|
|
1536
|
+
/// the slots other changes reserved there.
|
|
1537
|
+
fn slots_fit(&self, view: &View, shard: ShardId, extra: usize) -> bool {
|
|
1538
|
+
let reserved = self.shared.loads[usize::from(shard.0)].reserved();
|
|
1539
|
+
Self::slots_in(view, shard) + reserved + extra <= self.max_slots()
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/// Reserves `extra` slots on each shard (all or none), so a change running on another
|
|
1543
|
+
/// shard sees them before this one commits (#103).
|
|
1544
|
+
fn reserve_slots(&self, view: &View, extra: &[(ShardId, usize)]) -> Result<Reserved> {
|
|
1545
|
+
let mut out: Reserved = Vec::new();
|
|
1546
|
+
for &(shard, n) in extra {
|
|
1547
|
+
if n == 0 {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
let before = self.shared.loads[usize::from(shard.0)].reserve(n);
|
|
1551
|
+
out.push((shard, n));
|
|
1552
|
+
if Self::slots_in(view, shard) + before + n > self.max_slots() {
|
|
1553
|
+
self.release_slots(&out);
|
|
1554
|
+
self.shared.metrics[usize::from(self.id.0)]
|
|
1555
|
+
.refused_slots
|
|
1556
|
+
.fetch_add(1, Ordering::Relaxed);
|
|
1557
|
+
return Err(Error::Unsupported(
|
|
1558
|
+
"a shard would hold more tablets than its memtable arena serves",
|
|
1559
|
+
));
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
Ok(out)
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
fn release_slots(&self, reserved: &[(ShardId, usize)]) {
|
|
1566
|
+
for &(shard, n) in reserved {
|
|
1567
|
+
self.shared.loads[usize::from(shard.0)].release(n);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/// Moves the running change on: commits it once its tablets are drained.
|
|
1572
|
+
pub(super) fn progress_op(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1573
|
+
let Some(op) = &self.op else {
|
|
1574
|
+
return;
|
|
1575
|
+
};
|
|
1576
|
+
if op.committing {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
if self.poisoned || self.shared.pager_poisoned.load(Ordering::Acquire) {
|
|
1580
|
+
self.abort_op(poisoned_error(), ctx);
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
let mut busy = false;
|
|
1584
|
+
let mut refreeze = false;
|
|
1585
|
+
for (key, slot) in &self.memtables {
|
|
1586
|
+
if !self.moving.contains(&key.0) {
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
if !slot.active.table.is_empty() {
|
|
1590
|
+
busy = true;
|
|
1591
|
+
refreeze = true;
|
|
1592
|
+
}
|
|
1593
|
+
if !slot.frozen.is_empty() {
|
|
1594
|
+
busy = true;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
if self
|
|
1598
|
+
.compaction
|
|
1599
|
+
.is_some_and(|(t, _)| self.moving.contains(&t))
|
|
1600
|
+
{
|
|
1601
|
+
busy = true;
|
|
1602
|
+
}
|
|
1603
|
+
if !busy && self.shares_touch_moving() {
|
|
1604
|
+
busy = true;
|
|
1605
|
+
}
|
|
1606
|
+
trace!(
|
|
1607
|
+
"shard {} tablet change waits: busy={busy} refreeze={refreeze} deferred={} flush_running={}",
|
|
1608
|
+
self.id.0, self.freeze_deferred, self.flush_running
|
|
1609
|
+
);
|
|
1610
|
+
if refreeze {
|
|
1611
|
+
if self.freeze_moving().is_err() {
|
|
1612
|
+
self.poison(crate::health::FailureCause::Internal);
|
|
1613
|
+
}
|
|
1614
|
+
self.spawn_flush(ctx);
|
|
1615
|
+
}
|
|
1616
|
+
if busy {
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
self.commit_op(ctx);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
/// Freezes the memtables of the tablets being changed (`freeze` freezes them whatever
|
|
1623
|
+
/// they hold).
|
|
1624
|
+
fn freeze_moving(&mut self) -> Result<()> {
|
|
1625
|
+
#[cfg(feature = "test-hooks")]
|
|
1626
|
+
if self
|
|
1627
|
+
.shared
|
|
1628
|
+
.hooks
|
|
1629
|
+
.faults
|
|
1630
|
+
.take(crate::engine::hooks::Fault::NextTabletFreeze)
|
|
1631
|
+
{
|
|
1632
|
+
return Err(Error::Busy);
|
|
1633
|
+
}
|
|
1634
|
+
self.freeze(false)
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/// Whether a prepared, undecided share writes or reads a tablet being changed.
|
|
1638
|
+
fn shares_touch_moving(&self) -> bool {
|
|
1639
|
+
self.prepared.values().any(|share| {
|
|
1640
|
+
share.bytes.batch().iter().flatten().any(|mu| {
|
|
1641
|
+
self.tablets
|
|
1642
|
+
.route(mu.table, mu.row)
|
|
1643
|
+
.is_some_and(|(t, _)| self.moving.contains(&t))
|
|
1644
|
+
})
|
|
1645
|
+
})
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
fn commit_op(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1649
|
+
let Some(op) = self.op.as_mut() else {
|
|
1650
|
+
return;
|
|
1651
|
+
};
|
|
1652
|
+
op.committing = true;
|
|
1653
|
+
let kind = op.kind.clone();
|
|
1654
|
+
let entries = op.entries.clone();
|
|
1655
|
+
// A tablet changes shard only once its memtables are flushed (`progress_op`): the
|
|
1656
|
+
// flushed-through seqno (`Shared::flushed_through`) relies on it, since no write at or
|
|
1657
|
+
// below it may leave this shard's memtables for another's (#724).
|
|
1658
|
+
debug_assert!(
|
|
1659
|
+
self.memtables
|
|
1660
|
+
.iter()
|
|
1661
|
+
.filter(|(k, _)| self.moving.contains(&k.0))
|
|
1662
|
+
.all(|(_, s)| s.active.table.is_empty() && s.frozen.is_empty()),
|
|
1663
|
+
"shard {}: a tablet change commits with unflushed memtables on its tablets",
|
|
1664
|
+
self.id.0
|
|
1665
|
+
);
|
|
1666
|
+
// D11, D271: default timestamps never go backwards on a tablet. The new owner's next
|
|
1667
|
+
// stamp is above every one the allocator handed out, so above the tablet's own.
|
|
1668
|
+
self.shared.clock.raise();
|
|
1669
|
+
balance!("shard {} tablet change {kind:?} commits", self.id.0);
|
|
1670
|
+
let me = self.id;
|
|
1671
|
+
let change: TabletChange =
|
|
1672
|
+
Box::new(move |catalog| tablet_change(catalog, &kind, &entries, me));
|
|
1673
|
+
let submitter = ctx.submitter(self.id).clone();
|
|
1674
|
+
let req = ManifestReq {
|
|
1675
|
+
kind: ReqKind::Tablets(change),
|
|
1676
|
+
readers: Vec::new(),
|
|
1677
|
+
flushed_roots: Vec::new(),
|
|
1678
|
+
compactions: Vec::new(),
|
|
1679
|
+
rewrite_snapshot: manifest::Rewrite::AsNeeded,
|
|
1680
|
+
dropped_ok: Vec::new(),
|
|
1681
|
+
on_refusal: Vec::new(),
|
|
1682
|
+
reply: Box::new(move |result| {
|
|
1683
|
+
let _ = submitter.submit(ShardMsg::TabletOpDone { result });
|
|
1684
|
+
}),
|
|
1685
|
+
};
|
|
1686
|
+
manifest::submit(&self.shared, self.id, req);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
/// The change's manifest commit finished.
|
|
1690
|
+
pub(super) fn on_tablet_op_done(
|
|
1691
|
+
&mut self,
|
|
1692
|
+
result: Result<ManifestVersion>,
|
|
1693
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
1694
|
+
) {
|
|
1695
|
+
let Some(op) = self.op.take() else {
|
|
1696
|
+
return;
|
|
1697
|
+
};
|
|
1698
|
+
self.finished_op();
|
|
1699
|
+
let moving: Vec<TabletId> = std::mem::take(&mut self.moving).into_iter().collect();
|
|
1700
|
+
// The view now holds the slots (or the change failed): the reservation goes.
|
|
1701
|
+
self.refresh_tablets();
|
|
1702
|
+
self.release_slots(&op.reserved);
|
|
1703
|
+
match result {
|
|
1704
|
+
Ok(_) => {
|
|
1705
|
+
balance!("shard {} tablet change {:?} done", self.id.0, op.kind);
|
|
1706
|
+
self.refresh_tablets();
|
|
1707
|
+
if self.drop_tablets(&moving).is_err() {
|
|
1708
|
+
self.poison(crate::health::FailureCause::Internal);
|
|
1709
|
+
}
|
|
1710
|
+
for t in &moving {
|
|
1711
|
+
self.loads.remove(t);
|
|
1712
|
+
}
|
|
1713
|
+
if !op.kind.targets(self.id).is_empty() {
|
|
1714
|
+
self.lost_version = self.tablets.version();
|
|
1715
|
+
}
|
|
1716
|
+
let metrics = &self.shared.metrics[usize::from(self.id.0)];
|
|
1717
|
+
match op.kind {
|
|
1718
|
+
TabletOpKind::Split { .. } => &metrics.splits,
|
|
1719
|
+
TabletOpKind::Merge { .. } => &metrics.merges,
|
|
1720
|
+
TabletOpKind::Move { .. } => &metrics.moves,
|
|
1721
|
+
}
|
|
1722
|
+
.fetch_add(1, Ordering::Relaxed);
|
|
1723
|
+
self.release_parked(ctx);
|
|
1724
|
+
if let Some(r) = op.reply {
|
|
1725
|
+
r.notify(Ok(()));
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
Err(e) => {
|
|
1729
|
+
balance!(
|
|
1730
|
+
"shard {} tablet change {:?} failed: {e}",
|
|
1731
|
+
self.id.0,
|
|
1732
|
+
op.kind
|
|
1733
|
+
);
|
|
1734
|
+
self.release_parked(ctx);
|
|
1735
|
+
if let Some(r) = op.reply {
|
|
1736
|
+
r.notify(Err(e));
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
self.start_next_op(ctx);
|
|
1741
|
+
self.try_finish_close(ctx);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/// Gives up the running change before its commit (a failed flush, a poisoned pager).
|
|
1745
|
+
pub(super) fn abort_op(&mut self, e: Error, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1746
|
+
if self.op.as_ref().is_none_or(|op| op.committing) {
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
let op = self.op.take().expect("checked");
|
|
1750
|
+
self.finished_op();
|
|
1751
|
+
self.release_slots(&op.reserved);
|
|
1752
|
+
self.moving.clear();
|
|
1753
|
+
self.release_parked(ctx);
|
|
1754
|
+
if let Some(r) = op.reply {
|
|
1755
|
+
r.notify(Err(e));
|
|
1756
|
+
}
|
|
1757
|
+
self.start_next_op(ctx);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
/// A change finished or was given up: commits refused meanwhile may run again.
|
|
1761
|
+
fn finished_op(&self) {
|
|
1762
|
+
self.shared.tablet_epoch.fetch_add(1, Ordering::AcqRel);
|
|
1763
|
+
self.shared.broadcast(|| ShardMsg::Maintain);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/// Routes every parked commit again, ahead of whatever is pending.
|
|
1767
|
+
fn release_parked(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
1768
|
+
self.parked_rows.clear();
|
|
1769
|
+
let parked = std::mem::take(&mut self.parked);
|
|
1770
|
+
let mut mine = Vec::with_capacity(parked.len());
|
|
1771
|
+
for m in parked {
|
|
1772
|
+
if let Some(m) = self.forward(m, ctx) {
|
|
1773
|
+
mine.push(m);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
mine.append(&mut self.pending);
|
|
1777
|
+
self.pending = mine;
|
|
1778
|
+
if !self.pending.is_empty() {
|
|
1779
|
+
let _ = ctx.submitter(self.id).submit(ShardMsg::Kick);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// ---- load tracking and the balancer ----
|
|
1784
|
+
|
|
1785
|
+
/// Counts a row written to `tablet` and samples it (reservoir sampling).
|
|
1786
|
+
pub(super) fn note_write(&mut self, tablet: TabletId, row: &[u8]) {
|
|
1787
|
+
self.window_writes += 1;
|
|
1788
|
+
self.rng ^= self.rng << 13;
|
|
1789
|
+
self.rng ^= self.rng >> 7;
|
|
1790
|
+
self.rng ^= self.rng << 17;
|
|
1791
|
+
let r = self.rng;
|
|
1792
|
+
let l = self.loads.entry(tablet).or_default();
|
|
1793
|
+
l.writes += 1;
|
|
1794
|
+
l.seen += 1;
|
|
1795
|
+
if l.sampled < SAMPLES {
|
|
1796
|
+
match l.samples.get_mut(l.sampled) {
|
|
1797
|
+
Some(s) => {
|
|
1798
|
+
s.clear();
|
|
1799
|
+
s.extend_from_slice(row);
|
|
1800
|
+
}
|
|
1801
|
+
None => l.samples.push(row.to_vec()),
|
|
1802
|
+
}
|
|
1803
|
+
l.sampled += 1;
|
|
1804
|
+
} else {
|
|
1805
|
+
let j = (r % l.seen) as usize;
|
|
1806
|
+
if j < SAMPLES {
|
|
1807
|
+
let s = &mut l.samples[j];
|
|
1808
|
+
s.clear();
|
|
1809
|
+
s.extend_from_slice(row);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
/// Memtable bytes of this shard, and per tablet (empty active memtables count 0).
|
|
1815
|
+
fn mem_bytes(&self) -> (u64, HashMap<TabletId, u64>) {
|
|
1816
|
+
let mut total = 0;
|
|
1817
|
+
let mut per = HashMap::new();
|
|
1818
|
+
for (key, slot) in &self.memtables {
|
|
1819
|
+
// An empty active memtable holds no writes, whatever it has allocated: its tablet
|
|
1820
|
+
// can still be cold (a slot now keeps its memtable after a flush).
|
|
1821
|
+
let active = if slot.active.table.is_empty() {
|
|
1822
|
+
0
|
|
1823
|
+
} else {
|
|
1824
|
+
slot.active.table.allocated_bytes() as u64
|
|
1825
|
+
};
|
|
1826
|
+
let b = active
|
|
1827
|
+
+ slot
|
|
1828
|
+
.frozen
|
|
1829
|
+
.iter()
|
|
1830
|
+
.map(|m| m.table.allocated_bytes() as u64)
|
|
1831
|
+
.sum::<u64>();
|
|
1832
|
+
total += b;
|
|
1833
|
+
*per.entry(key.0).or_insert(0) += b;
|
|
1834
|
+
}
|
|
1835
|
+
(total, per)
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/// Ends a balancer interval when it is due (or at once when `force`): publishes this
|
|
1839
|
+
/// shard's load and starts a split, move or merge when one is warranted.
|
|
1840
|
+
pub(super) fn maybe_balance(
|
|
1841
|
+
&mut self,
|
|
1842
|
+
force: bool,
|
|
1843
|
+
reply: Option<Notifier<Result<()>>>,
|
|
1844
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
1845
|
+
) {
|
|
1846
|
+
let cfg = self.shared.balance;
|
|
1847
|
+
// A read-only open (ICR 0059) changes no tablet.
|
|
1848
|
+
if !cfg.enabled || self.shared.no_writes {
|
|
1849
|
+
if let Some(r) = reply {
|
|
1850
|
+
r.notify(Err(Error::Unsupported("tablet changes are off")));
|
|
1851
|
+
}
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if !force && cfg.interval_nanos == 0 {
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
let now = ctx.now_nanos();
|
|
1858
|
+
let base = cfg.interval_nanos.max(1);
|
|
1859
|
+
let epoch = self.shared.tablet_epoch.load(Ordering::Acquire);
|
|
1860
|
+
// A write or a tablet change (here or on any shard) ends an idle backoff: the next
|
|
1861
|
+
// pass is due one base interval from now.
|
|
1862
|
+
let active = self.window_writes > 0
|
|
1863
|
+
|| self.op.is_some()
|
|
1864
|
+
|| !self.op_queue.is_empty()
|
|
1865
|
+
|| epoch != self.balance_epoch;
|
|
1866
|
+
if active && self.balance_interval > base {
|
|
1867
|
+
self.balance_interval = base;
|
|
1868
|
+
let due = now.saturating_add(base);
|
|
1869
|
+
if due < self.next_balance {
|
|
1870
|
+
self.next_balance = due;
|
|
1871
|
+
self.cancel_balance_timer();
|
|
1872
|
+
self.arm_balance_timer(ctx);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if !force && now < self.next_balance {
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
self.next_balance = now.saturating_add(base);
|
|
1879
|
+
let wrote = self.window_writes > 0;
|
|
1880
|
+
self.balance_pass += 1;
|
|
1881
|
+
// Moving averages of the write load: a shard's (published for the other shards) and
|
|
1882
|
+
// each tablet's (#103). One interval's swing, or a flush, never decides a move.
|
|
1883
|
+
self.load_ewma =
|
|
1884
|
+
LOAD_ALPHA * self.window_writes as f64 + (1.0 - LOAD_ALPHA) * self.load_ewma;
|
|
1885
|
+
self.load_slow =
|
|
1886
|
+
SLOW_ALPHA * self.window_writes as f64 + (1.0 - SLOW_ALPHA) * self.load_slow;
|
|
1887
|
+
let (pass, clock) = (self.balance_pass, now);
|
|
1888
|
+
for l in self.loads.values_mut() {
|
|
1889
|
+
if l.writes > 0 {
|
|
1890
|
+
l.last_write = (pass, clock);
|
|
1891
|
+
}
|
|
1892
|
+
l.ewma = LOAD_ALPHA * l.writes as f64 + (1.0 - LOAD_ALPHA) * l.ewma;
|
|
1893
|
+
l.slow = SLOW_ALPHA * l.writes as f64 + (1.0 - SLOW_ALPHA) * l.slow;
|
|
1894
|
+
}
|
|
1895
|
+
self.note_arrivals(now);
|
|
1896
|
+
let (_, mem_per) = self.mem_bytes();
|
|
1897
|
+
self.measure_busy(now);
|
|
1898
|
+
self.shared.loads[usize::from(self.id.0)]
|
|
1899
|
+
.set_turn_busy((self.turn_ewma * 1e6).round() as u64);
|
|
1900
|
+
self.shared.loads[usize::from(self.id.0)]
|
|
1901
|
+
.set_stall_busy((self.stall_term * 1e6).round() as u64);
|
|
1902
|
+
self.shared.loads[usize::from(self.id.0)]
|
|
1903
|
+
.set_inflight((self.inflight_ewma * 1e6).round() as u64);
|
|
1904
|
+
self.shared.loads[usize::from(self.id.0)].publish(
|
|
1905
|
+
self.load_ewma.round() as u64,
|
|
1906
|
+
self.load_slow.round() as u64,
|
|
1907
|
+
(self.busy_ewma * 1e6).round() as u64,
|
|
1908
|
+
now.max(1),
|
|
1909
|
+
self.balance_pass,
|
|
1910
|
+
);
|
|
1911
|
+
let store_writes = self.track_pack(now);
|
|
1912
|
+
#[cfg(feature = "test-hooks")]
|
|
1913
|
+
self.shared.metrics[usize::from(self.id.0)]
|
|
1914
|
+
.hooks
|
|
1915
|
+
.pack_k
|
|
1916
|
+
.store(self.active_shards() as u64 + 1, Ordering::Relaxed);
|
|
1917
|
+
self.track_skew(now);
|
|
1918
|
+
let idle = self.may_change();
|
|
1919
|
+
// A change waits for a running compaction on its tablets (#102: a running change
|
|
1920
|
+
// parks their writes, and a compaction can run for minutes). It is decided again as
|
|
1921
|
+
// soon as this one ends, before the next one is picked, so back-to-back compactions
|
|
1922
|
+
// never starve it.
|
|
1923
|
+
self.change_waits = false;
|
|
1924
|
+
let (decision, cleanups) = if idle {
|
|
1925
|
+
let (kind, cleanups) = self.decide(now, &mem_per);
|
|
1926
|
+
let kind = kind.filter(|kind| {
|
|
1927
|
+
let waits = self.waits_on_compaction(kind);
|
|
1928
|
+
self.change_waits |= waits;
|
|
1929
|
+
!waits
|
|
1930
|
+
});
|
|
1931
|
+
(kind, cleanups)
|
|
1932
|
+
} else {
|
|
1933
|
+
(None, Vec::new())
|
|
1934
|
+
};
|
|
1935
|
+
self.window_writes = 0;
|
|
1936
|
+
for l in self.loads.values_mut() {
|
|
1937
|
+
l.prev_writes = l.writes;
|
|
1938
|
+
l.writes = 0;
|
|
1939
|
+
l.seen = 0;
|
|
1940
|
+
l.sampled = 0;
|
|
1941
|
+
}
|
|
1942
|
+
let decision_none = decision.is_none();
|
|
1943
|
+
match decision {
|
|
1944
|
+
Some(kind) => {
|
|
1945
|
+
balance!("shard {} balancer: {kind:?}", self.id.0);
|
|
1946
|
+
self.start_change(kind, reply, ctx);
|
|
1947
|
+
}
|
|
1948
|
+
None => {
|
|
1949
|
+
if let Some(r) = reply {
|
|
1950
|
+
r.notify(Ok(()));
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
// Idle backoff: a pass that found nothing to do, after no writes and no tablet change,
|
|
1955
|
+
// doubles the interval (up to `BALANCE_IDLE_CAP_NANOS`, never below the base), so an
|
|
1956
|
+
// idle shard wakes rarely; anything else returns to the base interval.
|
|
1957
|
+
// Shard 0 decides the active shard count for every shard: it keeps the base interval
|
|
1958
|
+
// while the store writes, even when it writes nothing itself (D217).
|
|
1959
|
+
let quiet = idle
|
|
1960
|
+
&& !wrote
|
|
1961
|
+
&& !store_writes
|
|
1962
|
+
&& decision_none
|
|
1963
|
+
&& cleanups.is_empty()
|
|
1964
|
+
&& epoch == self.balance_epoch
|
|
1965
|
+
&& self.op.is_none();
|
|
1966
|
+
let current = self.balance_interval.max(base);
|
|
1967
|
+
self.balance_interval = if quiet {
|
|
1968
|
+
current
|
|
1969
|
+
.saturating_mul(2)
|
|
1970
|
+
.min(BALANCE_IDLE_CAP_NANOS.max(base))
|
|
1971
|
+
} else {
|
|
1972
|
+
base
|
|
1973
|
+
};
|
|
1974
|
+
self.balance_epoch = self.shared.tablet_epoch.load(Ordering::Acquire);
|
|
1975
|
+
self.next_balance = now.saturating_add(self.balance_interval);
|
|
1976
|
+
if !cleanups.is_empty() {
|
|
1977
|
+
for c in cleanups {
|
|
1978
|
+
if !self.cleanups.contains(&c) {
|
|
1979
|
+
self.cleanups.push(c);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
self.maintain(ctx);
|
|
1983
|
+
}
|
|
1984
|
+
self.arm_balance_timer(ctx);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/// Whether the balancer may start a tablet change now.
|
|
1988
|
+
fn may_change(&self) -> bool {
|
|
1989
|
+
self.op.is_none()
|
|
1990
|
+
&& self.op_queue.is_empty()
|
|
1991
|
+
&& !self.closing
|
|
1992
|
+
&& !self.replaying
|
|
1993
|
+
&& !self.poisoned
|
|
1994
|
+
&& !self.shared.closing.load(Ordering::Acquire)
|
|
1995
|
+
&& !self.shared.pager_poisoned.load(Ordering::Acquire)
|
|
1996
|
+
&& self.shared.full_compactions.load(Ordering::Acquire) == 0
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
/// The compaction a change waited on ended: decides again (on this interval's loads)
|
|
2000
|
+
/// and starts the change, before the next compaction is picked.
|
|
2001
|
+
pub(super) fn start_waiting_change(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
2002
|
+
// With a compaction running, `claim_targets` would pass a change on its tablets
|
|
2003
|
+
// without claiming, and the change would start during the compaction (#102, D270).
|
|
2004
|
+
debug_assert!(
|
|
2005
|
+
self.compaction.is_none(),
|
|
2006
|
+
"a waiting change decided again while a compaction runs"
|
|
2007
|
+
);
|
|
2008
|
+
self.change_waits = false;
|
|
2009
|
+
if !self.may_change() {
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
let (_, mem_per) = self.mem_bytes();
|
|
2013
|
+
let (kind, _) = self.decide(ctx.now_nanos(), &mem_per);
|
|
2014
|
+
if let Some(kind) = kind {
|
|
2015
|
+
balance!("shard {} balancer, after a compaction: {kind:?}", self.id.0);
|
|
2016
|
+
self.start_change(kind, None, ctx);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
/// Starts a change the balancer chose.
|
|
2021
|
+
fn start_change(
|
|
2022
|
+
&mut self,
|
|
2023
|
+
kind: TabletOpKind,
|
|
2024
|
+
reply: Option<Notifier<Result<()>>>,
|
|
2025
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
2026
|
+
) {
|
|
2027
|
+
// Anything but a size split (a move, or a split over shards) starts the shard's
|
|
2028
|
+
// dwell: the loads settle before write skew acts again.
|
|
2029
|
+
let size_split = matches!(&kind, TabletOpKind::Split { owners, .. }
|
|
2030
|
+
if owners.iter().all(|o| *o == self.id));
|
|
2031
|
+
if !size_split {
|
|
2032
|
+
self.skew_pass = Some(self.balance_pass);
|
|
2033
|
+
}
|
|
2034
|
+
if self.chose_slow.get() {
|
|
2035
|
+
self.slow_pass = Some(self.balance_pass);
|
|
2036
|
+
}
|
|
2037
|
+
self.request_tablet_op(kind, reply, ctx);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
/// Records the balancer pass at which each tablet this shard owns first appeared here
|
|
2041
|
+
/// (the tablets it held at its first pass count as settled).
|
|
2042
|
+
fn note_arrivals(&mut self, now: u64) {
|
|
2043
|
+
let view = self.shared.view.load();
|
|
2044
|
+
let first = self.balance_pass <= 1;
|
|
2045
|
+
let pass = if first { 0 } else { self.balance_pass };
|
|
2046
|
+
let owned: HashSet<TabletId> = view
|
|
2047
|
+
.tablets
|
|
2048
|
+
.iter()
|
|
2049
|
+
.filter(|t| t.shard == self.id)
|
|
2050
|
+
.map(|t| t.id)
|
|
2051
|
+
.collect();
|
|
2052
|
+
self.arrived.retain(|t, _| owned.contains(t));
|
|
2053
|
+
self.arrived_at.retain(|t, _| owned.contains(t));
|
|
2054
|
+
for t in owned {
|
|
2055
|
+
self.arrived_at
|
|
2056
|
+
.entry(t)
|
|
2057
|
+
.or_insert(if first { 0 } else { now });
|
|
2058
|
+
self.arrived.entry(t).or_insert(pass);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
/// Updates this shard's moving average of how busy it was since the last pass (D217,
|
|
2063
|
+
/// D248, D255): the largest of its thread's busy time, its group syncs' utilization and
|
|
2064
|
+
/// whether its writers are stalling, each over the time that passed. Utilization is the
|
|
2065
|
+
/// syncs' in-flight time (summed over the syncs in flight) over that time, capped at 1,
|
|
2066
|
+
/// so a shard whose durable commits keep a sync in flight reads busy although its thread
|
|
2067
|
+
/// mostly waits. Not divided by `group_sync_depth`: a shard's concurrent syncs fold into
|
|
2068
|
+
/// one journal commit (D221), so they are one server, not two (D248's A/B). The stall
|
|
2069
|
+
/// term is 1 when the writers stalled (a wait for room or L0 pacing began or was open) on
|
|
2070
|
+
/// at least `PACK_STALL_PASSES` of the last `PACK_STALL_WINDOW` passes, else 0 (D255):
|
|
2071
|
+
/// with flush and compaction on dedicated threads (D252) a shard carrying a full write
|
|
2072
|
+
/// load runs cheap turns and reads idle, so packing kept whole tables on one shard; a
|
|
2073
|
+
/// shard whose writers wait is hot in the sense that counts, more shards would take the
|
|
2074
|
+
/// load. The pool's slices for this shard's flushes and compactions add to the turn
|
|
2075
|
+
/// term (D256; before D252 they ran inside the turns and counted there), and the sum
|
|
2076
|
+
/// may pass 1 (two pool threads serving one shard). `max` over that sum, the syncs and
|
|
2077
|
+
/// the stalls, since a turn often runs while a sync is in flight or writers wait. All
|
|
2078
|
+
/// of them count only while the runtime counts turn time (engine-owned shards): an
|
|
2079
|
+
/// application-owned store stays unpacked, as before. The thread's spin before parking
|
|
2080
|
+
/// (D198) is measured and traced but is no term since D257: a thin shard spins after
|
|
2081
|
+
/// every message, so the term grew with the spread it justified (one client over 13
|
|
2082
|
+
/// shards read 6.5 shards' worth). Its replacement is measured here too, apart from the
|
|
2083
|
+
/// busy fraction: the mean number of client commit rounds in flight at this shard, the
|
|
2084
|
+
/// rise of `commit_inflight_nanos` over the interval (Little's law; a closed-loop
|
|
2085
|
+
/// client's worth is at most 1 per shard its commit occupies, whatever the shard
|
|
2086
|
+
/// count), which shard 0 sums over the shards into the store's concurrency (D257,
|
|
2087
|
+
/// D262, `decide_pack`).
|
|
2088
|
+
fn measure_busy(&mut self, now: u64) {
|
|
2089
|
+
let me = usize::from(self.id.0);
|
|
2090
|
+
let (busy, spin, pool) = self
|
|
2091
|
+
.shared
|
|
2092
|
+
.submitters
|
|
2093
|
+
.get()
|
|
2094
|
+
.and_then(|s| s.get(me))
|
|
2095
|
+
.map_or((0, 0, 0), |s| {
|
|
2096
|
+
(s.busy_nanos(), s.spin_nanos(), s.pool_busy_nanos())
|
|
2097
|
+
});
|
|
2098
|
+
let sync = self.sync_busy_nanos(now);
|
|
2099
|
+
let inflight = self.shared.metrics[me]
|
|
2100
|
+
.commit_inflight_nanos
|
|
2101
|
+
.load(Ordering::Relaxed);
|
|
2102
|
+
let mark = self.busy_mark.filter(|m| now > m.at);
|
|
2103
|
+
let elapsed = mark.map(|m| (now - m.at) as f64);
|
|
2104
|
+
// The thread's busy time (none counted for an application-owned shard).
|
|
2105
|
+
let turns = mark.map(|m| busy.saturating_sub(m.turns) as f64 / elapsed.unwrap_or(1.0));
|
|
2106
|
+
let counted = busy > 0;
|
|
2107
|
+
// A chosen turn busy, as an engine-owned shard measures it.
|
|
2108
|
+
#[cfg(feature = "test-hooks")]
|
|
2109
|
+
let (turns, counted) = match self.shared.metrics[me].hooks.load_override(LoadTerm::Busy) {
|
|
2110
|
+
None => (turns, counted),
|
|
2111
|
+
Some(v) => (Some(v), true),
|
|
2112
|
+
};
|
|
2113
|
+
// A counted term over the interval, from its counter's rise (0 for an
|
|
2114
|
+
// application-owned shard, which counts no turns).
|
|
2115
|
+
let term = |now_v: u64, was: fn(&BusyMark) -> u64| match (mark, elapsed) {
|
|
2116
|
+
(Some(m), Some(e)) if counted => now_v.saturating_sub(was(&m)) as f64 / e,
|
|
2117
|
+
_ => 0.0,
|
|
2118
|
+
};
|
|
2119
|
+
let syncs = term(sync, |m| m.sync);
|
|
2120
|
+
// The thread's spin before parking (D198) over the interval: traced, no term (D257).
|
|
2121
|
+
let spins = term(spin, |m| m.spin);
|
|
2122
|
+
// The client commits in flight at this shard over the interval (D257). Counted on
|
|
2123
|
+
// the shard's own clock reads, so it needs no engine-owned thread: it is the
|
|
2124
|
+
// concurrency of the writers, which any clock that moves between a submit and its
|
|
2125
|
+
// reply measures.
|
|
2126
|
+
let inflights = match (mark, elapsed) {
|
|
2127
|
+
(Some(m), Some(e)) => Some(inflight.saturating_sub(m.inflight) as f64 / e),
|
|
2128
|
+
_ => None,
|
|
2129
|
+
};
|
|
2130
|
+
// A chosen concurrency (D257's packing tests), measured whether or not the clock
|
|
2131
|
+
// moved, as the chosen busy fraction is.
|
|
2132
|
+
#[cfg(feature = "test-hooks")]
|
|
2133
|
+
let inflights = self.shared.metrics[me]
|
|
2134
|
+
.hooks
|
|
2135
|
+
.load_override(LoadTerm::Inflight)
|
|
2136
|
+
.or(inflights);
|
|
2137
|
+
// The pool's slices for this shard over the interval (D256).
|
|
2138
|
+
let pools = term(pool, |m| m.pool);
|
|
2139
|
+
// A chosen pool term (D256's packing tests).
|
|
2140
|
+
#[cfg(feature = "test-hooks")]
|
|
2141
|
+
let pools = self.shared.metrics[me]
|
|
2142
|
+
.hooks
|
|
2143
|
+
.load_override(LoadTerm::Pool)
|
|
2144
|
+
.unwrap_or(pools);
|
|
2145
|
+
// This pass's stall bit, then the count over the window.
|
|
2146
|
+
let m = &self.shared.metrics[me];
|
|
2147
|
+
let events =
|
|
2148
|
+
m.room_stalls.load(Ordering::Relaxed) + m.pacing_stalls.load(Ordering::Relaxed);
|
|
2149
|
+
let stalled_now = events > self.stall_events_mark || self.stall_open();
|
|
2150
|
+
self.stall_events_mark = events;
|
|
2151
|
+
self.stall_history = (self.stall_history << 1) | u32::from(stalled_now);
|
|
2152
|
+
let window = self.stall_history & ((1u32 << PACK_STALL_WINDOW) - 1);
|
|
2153
|
+
let stalls = if counted && window.count_ones() >= PACK_STALL_PASSES {
|
|
2154
|
+
1.0
|
|
2155
|
+
} else {
|
|
2156
|
+
0.0
|
|
2157
|
+
};
|
|
2158
|
+
// A chosen stall term (D255's packing tests).
|
|
2159
|
+
#[cfg(feature = "test-hooks")]
|
|
2160
|
+
let stalls = self.shared.metrics[me]
|
|
2161
|
+
.hooks
|
|
2162
|
+
.load_override(LoadTerm::Stalled)
|
|
2163
|
+
.unwrap_or(stalls);
|
|
2164
|
+
let measured = turns.map(|t| (t + pools).max(syncs.min(1.0)).max(stalls));
|
|
2165
|
+
if let Some(frac) = measured {
|
|
2166
|
+
self.busy_ewma = LOAD_ALPHA * frac + (1.0 - LOAD_ALPHA) * self.busy_ewma;
|
|
2167
|
+
}
|
|
2168
|
+
if let Some(c) = inflights {
|
|
2169
|
+
self.inflight_ewma = LOAD_ALPHA * c + (1.0 - LOAD_ALPHA) * self.inflight_ewma;
|
|
2170
|
+
}
|
|
2171
|
+
if let Some(t) = turns {
|
|
2172
|
+
self.turn_ewma = LOAD_ALPHA * t.min(1.0) + (1.0 - LOAD_ALPHA) * self.turn_ewma;
|
|
2173
|
+
}
|
|
2174
|
+
self.stall_term = stalls;
|
|
2175
|
+
self.busy_mark = Some(BusyMark {
|
|
2176
|
+
turns: busy,
|
|
2177
|
+
spin,
|
|
2178
|
+
pool,
|
|
2179
|
+
sync,
|
|
2180
|
+
inflight,
|
|
2181
|
+
at: now,
|
|
2182
|
+
});
|
|
2183
|
+
trace!(
|
|
2184
|
+
"shard {} balancer: busy turn {:.2} pool {:.2} sync {:.2} stall {:.2} (events {}, window {:010b}) spin {:.2} in flight {:.2} -> ewma {:.2}, turn {:.2}, in flight {:.2}",
|
|
2185
|
+
self.id.0,
|
|
2186
|
+
turns.unwrap_or(0.0),
|
|
2187
|
+
pools,
|
|
2188
|
+
syncs,
|
|
2189
|
+
stalls,
|
|
2190
|
+
events,
|
|
2191
|
+
window,
|
|
2192
|
+
spins,
|
|
2193
|
+
inflights.unwrap_or(0.0),
|
|
2194
|
+
self.busy_ewma,
|
|
2195
|
+
self.turn_ewma,
|
|
2196
|
+
self.inflight_ewma
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
/// The shards that stay active under packing (D217): `0..k`. Every shard, without
|
|
2201
|
+
/// packing or before it has decided.
|
|
2202
|
+
pub(super) fn active_shards(&self) -> usize {
|
|
2203
|
+
let n = self.shared.shards;
|
|
2204
|
+
let k = self.shared.pack_k.load(Ordering::Acquire);
|
|
2205
|
+
if self.shared.balance.pack && k > 0 {
|
|
2206
|
+
k.min(n)
|
|
2207
|
+
} else {
|
|
2208
|
+
n
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
/// Shard 0 decides how many shards stay active and publishes it; the others read it
|
|
2213
|
+
/// (`active_shards`), so every shard uses the same `k` (D217). Each shard keeping its own
|
|
2214
|
+
/// hysteresis over loads read at different passes disagreed for good. Returns whether the
|
|
2215
|
+
/// store writes enough for packing to act (shard 0 then keeps its base interval).
|
|
2216
|
+
fn track_pack(&mut self, now: u64) -> bool {
|
|
2217
|
+
if self.id.0 != 0 {
|
|
2218
|
+
return false;
|
|
2219
|
+
}
|
|
2220
|
+
let writing = self.decide_pack(now);
|
|
2221
|
+
self.shared.pack_k.store(self.pack_k, Ordering::Release);
|
|
2222
|
+
self.shared
|
|
2223
|
+
.pack_target
|
|
2224
|
+
.store(self.pack_target, Ordering::Release);
|
|
2225
|
+
writing
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
/// Updates how many shards stay active (D217): shard 0 decides `k` for every shard and
|
|
2229
|
+
/// publishes it (`Shared::pack_k`), from the published busy fractions,
|
|
2230
|
+
/// `ceil(B / PACK_TARGET)`, or the published commits in flight,
|
|
2231
|
+
/// `round(C / PACK_CONCURRENCY)` (D257), whichever is more. It grows (at least by one)
|
|
2232
|
+
/// after `PACK_GROW_PASSES` passes with the active shards above `PACK_SPREAD` busy on
|
|
2233
|
+
/// average, or fewer than the concurrency calls for, or with one of them above
|
|
2234
|
+
/// `PACK_HOT` (D251), and shrinks after `PACK_SHRINK_PASSES` passes with one fewer shard
|
|
2235
|
+
/// enough to stay under `PACK_TARGET`, more than the concurrency calls for, and none
|
|
2236
|
+
/// above `PACK_HOT`. The first shrink from the unpacked start waits for
|
|
2237
|
+
/// `PACK_FIRST_SHRINK_PASSES` of writing (D255) unless the slow concurrency has agreed
|
|
2238
|
+
/// with the fast one for a shrink count (D257): that first pack then decides on
|
|
2239
|
+
/// `PACK_GROW_PASSES` and counts through a write ramp, since one client's gate row
|
|
2240
|
+
/// measures ten seconds after a load and a few seconds of warm-up, inside which the
|
|
2241
|
+
/// 30 s wait, the 50 passes and the ramp guard each put the pack. Never fewer than the
|
|
2242
|
+
/// shards the tablets' memtable slots need (`max_slots` each), and at once up to that.
|
|
2243
|
+
/// Only while the store writes (`balance_min_writes` over all shards) and some shard
|
|
2244
|
+
/// reports busy time: an idle store, or a clock that does not move while shards work (the
|
|
2245
|
+
/// simulated VFS), keeps its placement.
|
|
2246
|
+
fn decide_pack(&mut self, now: u64) -> bool {
|
|
2247
|
+
let n = self.shared.shards;
|
|
2248
|
+
if !self.shared.balance.pack || n < 2 {
|
|
2249
|
+
return false;
|
|
2250
|
+
}
|
|
2251
|
+
if self.pack_k == 0 {
|
|
2252
|
+
self.pack_k = n;
|
|
2253
|
+
}
|
|
2254
|
+
self.pack_target = self.pack_k;
|
|
2255
|
+
let stale = self.shared.balance.interval_nanos.saturating_mul(3).max(1);
|
|
2256
|
+
// Over every shard's fresh report (a stale one reads 0): the sum, and the sum of the
|
|
2257
|
+
// fractions (millionths each).
|
|
2258
|
+
let loads = &self.shared.loads;
|
|
2259
|
+
let sum = |read: fn(&LoadSlot, u64, u64) -> Option<u64>| -> u64 {
|
|
2260
|
+
loads.iter().map(|l| read(l, now, stale).unwrap_or(0)).sum()
|
|
2261
|
+
};
|
|
2262
|
+
let sum_fractions = |read: fn(&LoadSlot, u64, u64) -> Option<u64>| -> f64 {
|
|
2263
|
+
loads
|
|
2264
|
+
.iter()
|
|
2265
|
+
.map(|l| read(l, now, stale).unwrap_or(0) as f64 / 1e6)
|
|
2266
|
+
.sum()
|
|
2267
|
+
};
|
|
2268
|
+
let busy = sum_fractions(LoadSlot::read_busy_fresh);
|
|
2269
|
+
let writes = sum(LoadSlot::read_fresh);
|
|
2270
|
+
let writes_slow = sum(LoadSlot::read_slow_fresh);
|
|
2271
|
+
// D257: the store's concurrency, the mean client commits in flight over every shard,
|
|
2272
|
+
// and the active shards it calls for. For `t` closed-loop clients it is at most `t`
|
|
2273
|
+
// whatever `k` is, so unlike the spin term it replaced it never justifies the spread
|
|
2274
|
+
// it caused.
|
|
2275
|
+
let inflight = sum_fractions(LoadSlot::read_inflight_fresh);
|
|
2276
|
+
let k_for = |inflight: f64| ((inflight / PACK_CONCURRENCY).round() as usize).clamp(1, n);
|
|
2277
|
+
let k_c = k_for(inflight);
|
|
2278
|
+
// The slow concurrency (D257): when it calls for the same active shards as the
|
|
2279
|
+
// fast one, the concurrency has held for the slow window and the first shrink from
|
|
2280
|
+
// the unpacked start need not wait for `PACK_FIRST_SHRINK_PASSES`. A dip after a
|
|
2281
|
+
// heavy phase still waits: the slow average remembers the phase for about the
|
|
2282
|
+
// window. A concurrency that reads 0 (the simulated clock, an application-owned
|
|
2283
|
+
// store) settles nothing, so the window applies as before.
|
|
2284
|
+
self.inflight_slow = SLOW_ALPHA * inflight + (1.0 - SLOW_ALPHA) * self.inflight_slow;
|
|
2285
|
+
let k_c_slow = k_for(self.inflight_slow);
|
|
2286
|
+
// Agreement must hold for a shrink count: the fast average crosses the slow one on
|
|
2287
|
+
// its way back up after a dip, and a count the dip accrued must not fire then.
|
|
2288
|
+
self.settled_passes = if inflight > 0.0 && k_c_slow == k_c {
|
|
2289
|
+
self.settled_passes.saturating_add(1)
|
|
2290
|
+
} else {
|
|
2291
|
+
0
|
|
2292
|
+
};
|
|
2293
|
+
let settled = self.settled_passes >= PACK_SHRINK_PASSES;
|
|
2294
|
+
let writing = writes >= self.shared.balance.min_writes.max(1);
|
|
2295
|
+
if writing {
|
|
2296
|
+
self.writing_passes = self.writing_passes.saturating_add(1);
|
|
2297
|
+
}
|
|
2298
|
+
if busy <= 0.0 || !writing {
|
|
2299
|
+
self.pack_up = 0;
|
|
2300
|
+
self.pack_down = 0;
|
|
2301
|
+
return false;
|
|
2302
|
+
}
|
|
2303
|
+
// The active shards must hold every tablet's memtable slots: without the floor, the
|
|
2304
|
+
// first full shard strands the rest of the written tablets across the inactive ones.
|
|
2305
|
+
let floor = {
|
|
2306
|
+
let view = self.shared.view.load();
|
|
2307
|
+
let slots: usize = view
|
|
2308
|
+
.tablets
|
|
2309
|
+
.iter()
|
|
2310
|
+
.map(|t| view.catalog.family_ids_of(t.table).len())
|
|
2311
|
+
.sum();
|
|
2312
|
+
slots.div_ceil(self.max_slots().max(1))
|
|
2313
|
+
};
|
|
2314
|
+
let k = self.pack_k;
|
|
2315
|
+
let want = ((busy / PACK_TARGET).ceil() as usize)
|
|
2316
|
+
.max(k_c)
|
|
2317
|
+
.max(floor)
|
|
2318
|
+
.clamp(1, n);
|
|
2319
|
+
if k < floor {
|
|
2320
|
+
self.pack_k = want;
|
|
2321
|
+
self.pack_target = want;
|
|
2322
|
+
balance!(
|
|
2323
|
+
"shard {} balancer: active shards {k} -> {want} (slots need {floor})",
|
|
2324
|
+
self.id.0
|
|
2325
|
+
);
|
|
2326
|
+
self.pack_up = 0;
|
|
2327
|
+
self.pack_down = 0;
|
|
2328
|
+
return true;
|
|
2329
|
+
}
|
|
2330
|
+
// D251: an active shard whose thread is saturated (turn busy, not D248's sync term:
|
|
2331
|
+
// a shard whose syncs run back to back may not cool however many shards share the
|
|
2332
|
+
// load) grows `k` too, and holds off a shrink; so does one whose writers stall
|
|
2333
|
+
// (D255: their waits for room and L0 depth do ease once it sheds tablets). A
|
|
2334
|
+
// hot-triggered growth repeats only once the hot shard has cooled since the last
|
|
2335
|
+
// one (what it shed helped), or another shard is hot: a hot spot more shards cannot
|
|
2336
|
+
// cool (one unsplittable tablet) grows `k` once.
|
|
2337
|
+
let hottest = self.shared.loads[..k.min(n)]
|
|
2338
|
+
.iter()
|
|
2339
|
+
.enumerate()
|
|
2340
|
+
.map(|(i, l)| {
|
|
2341
|
+
let turn = l.read_turn_busy_fresh(now, stale).unwrap_or(0);
|
|
2342
|
+
let stall = l.read_stall_busy_fresh(now, stale).unwrap_or(0);
|
|
2343
|
+
(turn.max(stall), stall > turn, i)
|
|
2344
|
+
})
|
|
2345
|
+
.max()
|
|
2346
|
+
.filter(|(b, _, _)| *b as f64 / 1e6 > PACK_HOT);
|
|
2347
|
+
let hot = hottest.is_some();
|
|
2348
|
+
// A stall-driven hot shard grows `k` again while it still has tablets to shed
|
|
2349
|
+
// (D255): its stall fraction stays at 1 however much it sheds, so D251's cooled
|
|
2350
|
+
// check would hold it to one growth, and on the AX102 a load cut to 3 shards regrew
|
|
2351
|
+
// one shard per 20 s. The cooled check keeps holding thread-saturated hot spots.
|
|
2352
|
+
let view = self.shared.view.load();
|
|
2353
|
+
let hot_grows = hottest.is_some_and(|(b, stalled, h)| {
|
|
2354
|
+
let sheds = stalled
|
|
2355
|
+
&& view
|
|
2356
|
+
.tablets
|
|
2357
|
+
.iter()
|
|
2358
|
+
.filter(|t| usize::from(t.shard.0) == h)
|
|
2359
|
+
.count()
|
|
2360
|
+
> 1;
|
|
2361
|
+
sheds
|
|
2362
|
+
|| self
|
|
2363
|
+
.hot_grew
|
|
2364
|
+
.is_none_or(|(shard, was)| shard != h || b + PACK_HOT_COOLED < was)
|
|
2365
|
+
});
|
|
2366
|
+
if !hot {
|
|
2367
|
+
self.hot_grew = None;
|
|
2368
|
+
}
|
|
2369
|
+
// D255: the active shards a stall-driven growth reached stay active while the store
|
|
2370
|
+
// writes at least half of what it did then. The shards it spread to run cheap turns
|
|
2371
|
+
// once their writers stop stalling, so the busy sum alone would call for a shrink,
|
|
2372
|
+
// the stalls would come back, and `k` would swing between the two. The floor also
|
|
2373
|
+
// expires after a quiet window with no active shard stalling (the cause went away
|
|
2374
|
+
// with the load unchanged), each expiry doubling the next window.
|
|
2375
|
+
// A pass with a stalling or a saturated shard is not quiet (D255, D256): the floor
|
|
2376
|
+
// expires only once neither cause has shown for a window. A stalling shard reads
|
|
2377
|
+
// hot: its stall term is 0 or 1 (D255), and 1 is past `PACK_HOT`.
|
|
2378
|
+
if hot {
|
|
2379
|
+
self.floor_quiet = 0;
|
|
2380
|
+
} else {
|
|
2381
|
+
self.floor_quiet = self.floor_quiet.saturating_add(1);
|
|
2382
|
+
}
|
|
2383
|
+
let quiet_needed = self.floor_quiet_needed.max(PACK_FLOOR_QUIET);
|
|
2384
|
+
let stall_floor = match self.stall_floor {
|
|
2385
|
+
Some((floor_k, writes_then))
|
|
2386
|
+
if writes.saturating_mul(2) >= writes_then && self.floor_quiet < quiet_needed =>
|
|
2387
|
+
{
|
|
2388
|
+
floor_k
|
|
2389
|
+
}
|
|
2390
|
+
Some(_) => {
|
|
2391
|
+
self.stall_floor = None;
|
|
2392
|
+
if self.floor_quiet >= quiet_needed {
|
|
2393
|
+
// Expired on a quiet window: a floor set again within one window is
|
|
2394
|
+
// the same cause coming back and doubles the next window (below).
|
|
2395
|
+
self.floor_expired_pass = self.balance_pass.max(1);
|
|
2396
|
+
} else {
|
|
2397
|
+
// Released by a write drop: the next floor starts from the base window.
|
|
2398
|
+
self.floor_expired_pass = 0;
|
|
2399
|
+
self.floor_quiet_needed = PACK_FLOOR_QUIET;
|
|
2400
|
+
}
|
|
2401
|
+
0
|
|
2402
|
+
}
|
|
2403
|
+
None => 0,
|
|
2404
|
+
};
|
|
2405
|
+
let floor = floor.max(stall_floor.min(n));
|
|
2406
|
+
let spread = busy / k as f64 > PACK_SPREAD;
|
|
2407
|
+
// D257: fewer active shards than the commits in flight call for grow them; more
|
|
2408
|
+
// than they call for is one of the conditions of a shrink.
|
|
2409
|
+
let concurrent = k < k_c;
|
|
2410
|
+
// No shrink while the load is still ramping (D255, #734): the fast write average
|
|
2411
|
+
// above the slow one. The first pass of a load read its ramp as a light store.
|
|
2412
|
+
let ramping = writes_slow > 0 && writes as f64 > PACK_RAMP * writes_slow as f64;
|
|
2413
|
+
self.ramp_passes = if ramping {
|
|
2414
|
+
self.ramp_passes.saturating_add(1)
|
|
2415
|
+
} else {
|
|
2416
|
+
0
|
|
2417
|
+
};
|
|
2418
|
+
let ramping = ramping && self.ramp_passes <= PACK_RAMP_MAX;
|
|
2419
|
+
// Shard 0 decides the packing (D217): its pass state, when an input to the decision
|
|
2420
|
+
// changes or once a second (#1188).
|
|
2421
|
+
if self.id.0 == 0 && balance_logging() {
|
|
2422
|
+
let key = format!(
|
|
2423
|
+
"k {k} k_c {k_c}/{k_c_slow} settled {} ramping {ramping} hottest {} floor {floor} up {} down {}",
|
|
2424
|
+
self.settled_passes >= PACK_SHRINK_PASSES,
|
|
2425
|
+
hottest.is_some(),
|
|
2426
|
+
self.pack_up > 0,
|
|
2427
|
+
self.pack_down > 0
|
|
2428
|
+
);
|
|
2429
|
+
let due = self.balance_logged.as_ref().is_none_or(|(last, at)| {
|
|
2430
|
+
*last != key || at.elapsed() >= std::time::Duration::from_secs(1)
|
|
2431
|
+
});
|
|
2432
|
+
if due {
|
|
2433
|
+
balance!(
|
|
2434
|
+
"shard 0 pass {}: k {k} busy {busy:.2} in flight {inflight:.2} (k_c {k_c}, slow {:.2} k_c {k_c_slow}, settled {} of {}) writes {writes} (slow {writes_slow}, ramping {ramping}) floor {floor} hottest {:?} up {} down {} stall floor {:?}",
|
|
2435
|
+
self.balance_pass,
|
|
2436
|
+
self.inflight_slow,
|
|
2437
|
+
self.settled_passes,
|
|
2438
|
+
PACK_SHRINK_PASSES,
|
|
2439
|
+
hottest,
|
|
2440
|
+
self.pack_up,
|
|
2441
|
+
self.pack_down,
|
|
2442
|
+
self.stall_floor
|
|
2443
|
+
);
|
|
2444
|
+
self.balance_logged = Some((key, std::time::Instant::now()));
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
trace!(
|
|
2448
|
+
"shard {} balancer pass: k {k} busy {busy:.2} in flight {inflight:.2} (k_c {k_c}, slow {:.2} k_c {k_c_slow}, settled {} of {}) writes {writes} (slow {writes_slow}, ramping {ramping}) floor {floor} (quiet {} of {}) hottest {:?} up {} down {}",
|
|
2449
|
+
self.id.0,
|
|
2450
|
+
self.inflight_slow,
|
|
2451
|
+
self.settled_passes,
|
|
2452
|
+
PACK_SHRINK_PASSES,
|
|
2453
|
+
self.floor_quiet,
|
|
2454
|
+
self.floor_quiet_needed.max(PACK_FLOOR_QUIET),
|
|
2455
|
+
hottest,
|
|
2456
|
+
self.pack_up,
|
|
2457
|
+
self.pack_down
|
|
2458
|
+
);
|
|
2459
|
+
if k < n && (spread || concurrent || hot_grows) {
|
|
2460
|
+
self.pack_up += 1;
|
|
2461
|
+
// Where the growth is heading, for the bench's warm-up (D242's amendment): it
|
|
2462
|
+
// does not measure across a growth whose passes are still accruing.
|
|
2463
|
+
self.pack_target = want.max(k + 1).min(n);
|
|
2464
|
+
} else {
|
|
2465
|
+
self.pack_up = 0;
|
|
2466
|
+
}
|
|
2467
|
+
// No shrink on a pass with a hot shard, stalling ones included (D255): a pass on which
|
|
2468
|
+
// an active shard's writers stall is no light pass, whatever the busy sum reads. On
|
|
2469
|
+
// the AX102 a write lull in the middle of a 16 GiB load (the loader's writers waiting
|
|
2470
|
+
// on room: writes 2,479 against 10–48K around it, busy 0.27) completed the count the
|
|
2471
|
+
// ramp had let accrue, and D217 shrank 16 → 2 mid-load.
|
|
2472
|
+
if !hot && k > floor.max(1) && k > k_c && busy / ((k - 1) as f64) < PACK_TARGET {
|
|
2473
|
+
// A ramping pass pauses the shrink count: a light pass advances it only while
|
|
2474
|
+
// the load is not ramping, and a hot, busy or stalling pass resets it ramping
|
|
2475
|
+
// or not (ctr274's review: a count carried across a bursty heavy load's bursts
|
|
2476
|
+
// would shrink and grow in a slow cycle; a lull whose writers wait on room is
|
|
2477
|
+
// no light pass).
|
|
2478
|
+
// A settled concurrency counts through a write ramp (D257): a ramp fools the
|
|
2479
|
+
// busy sum, which reads a load's start as light, not the commits in flight,
|
|
2480
|
+
// which read one client as one client at any rate; and a cold start's slow
|
|
2481
|
+
// write average reads every steady load as ramping for its first 16 s.
|
|
2482
|
+
if !ramping || settled {
|
|
2483
|
+
self.pack_down += 1;
|
|
2484
|
+
}
|
|
2485
|
+
// Where the first pack from the unpacked start is heading, for the bench's
|
|
2486
|
+
// warm-up (#983): it moves every tablet, and from all `n` shards the store reads
|
|
2487
|
+
// as settled while it accrues its count. Later shrinks are not published: their
|
|
2488
|
+
// last steps are slow and the warm-up's quiet window covers their moves.
|
|
2489
|
+
if k == n {
|
|
2490
|
+
self.pack_target = want.min(k - 1).max(floor.max(1));
|
|
2491
|
+
}
|
|
2492
|
+
} else {
|
|
2493
|
+
self.pack_down = 0;
|
|
2494
|
+
}
|
|
2495
|
+
// The first pack from the unpacked start on a settled concurrency decides on the
|
|
2496
|
+
// growth count, not the shrink count (D257): the concurrency agreed for a shrink
|
|
2497
|
+
// count already, and a client's placement must settle inside the few seconds a
|
|
2498
|
+
// warm-up gives it before its measured phase (D242's 2 s quiet). Later shrinks
|
|
2499
|
+
// keep D217's 50 passes, so a short dip in a running load does not flap.
|
|
2500
|
+
let shrink_passes = if settled && k == n {
|
|
2501
|
+
PACK_GROW_PASSES
|
|
2502
|
+
} else {
|
|
2503
|
+
PACK_SHRINK_PASSES
|
|
2504
|
+
};
|
|
2505
|
+
let why = if k < floor {
|
|
2506
|
+
self.pack_k = want.max(floor).min(n);
|
|
2507
|
+
"the stall floor"
|
|
2508
|
+
} else if self.pack_up >= PACK_GROW_PASSES {
|
|
2509
|
+
self.pack_k = want.max(k + 1).min(n);
|
|
2510
|
+
// Whatever called for it, a growth with a hot shard counts as its growth.
|
|
2511
|
+
match hottest {
|
|
2512
|
+
Some((b, stalled, h)) => {
|
|
2513
|
+
self.hot_grew = Some((h, b));
|
|
2514
|
+
if stalled {
|
|
2515
|
+
// A shrink that stalls writers within a minute, with the store
|
|
2516
|
+
// writing twice what it did then, was wrong (the load came back):
|
|
2517
|
+
// back to the count before it in one step (D255).
|
|
2518
|
+
if let Some((from, at, writes_then)) = self.shrunk_from.take()
|
|
2519
|
+
&& self.balance_pass.saturating_sub(at) <= PACK_SHRINK_UNDO
|
|
2520
|
+
&& writes >= writes_then.saturating_mul(2)
|
|
2521
|
+
{
|
|
2522
|
+
self.pack_k = self.pack_k.max(from.min(n));
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
// The count a hot shard grew the store to is the floor while the
|
|
2526
|
+
// store keeps writing at least half its rate then (D255 for a
|
|
2527
|
+
// stalling shard, D256 for a saturated one): the shards it spread to
|
|
2528
|
+
// run cheap turns, so the busy sum alone would shrink it back and the
|
|
2529
|
+
// stall or the saturation would return.
|
|
2530
|
+
if self.stall_floor.is_none() {
|
|
2531
|
+
// A floor set within one quiet window of the last expiry is the
|
|
2532
|
+
// same cause coming back: the window doubles, up to the cap. One
|
|
2533
|
+
// set later starts from the base again.
|
|
2534
|
+
let since = self.balance_pass.saturating_sub(self.floor_expired_pass);
|
|
2535
|
+
self.floor_quiet_needed =
|
|
2536
|
+
if self.floor_expired_pass != 0 && since <= quiet_needed {
|
|
2537
|
+
(quiet_needed * 2).min(PACK_FLOOR_QUIET_MAX)
|
|
2538
|
+
} else {
|
|
2539
|
+
PACK_FLOOR_QUIET
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
self.stall_floor = Some((self.pack_k, writes));
|
|
2543
|
+
if stalled {
|
|
2544
|
+
"a shard whose writers stall (D255)"
|
|
2545
|
+
} else {
|
|
2546
|
+
"a shard whose thread is saturated (D251)"
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
None if spread => "the busy sum over the active shards (D217)",
|
|
2550
|
+
None => {
|
|
2551
|
+
debug_assert!(concurrent);
|
|
2552
|
+
"the commits in flight (D257)"
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
} else if self.pack_down >= shrink_passes
|
|
2556
|
+
&& (k < n || settled || self.writing_passes >= PACK_FIRST_SHRINK_PASSES)
|
|
2557
|
+
{
|
|
2558
|
+
self.pack_k = want.min(k - 1).max(floor.max(1));
|
|
2559
|
+
self.shrunk_from = Some((k, self.balance_pass, writes));
|
|
2560
|
+
"a busy sum and commits in flight low enough for one fewer (D217, D257)"
|
|
2561
|
+
} else {
|
|
2562
|
+
return true;
|
|
2563
|
+
};
|
|
2564
|
+
// A shrink lands where it decided; a growth's undo may land above its target.
|
|
2565
|
+
self.pack_target = if self.pack_k < k {
|
|
2566
|
+
self.pack_k
|
|
2567
|
+
} else {
|
|
2568
|
+
self.pack_target.max(self.pack_k)
|
|
2569
|
+
};
|
|
2570
|
+
balance!(
|
|
2571
|
+
"shard {} balancer: active shards {k} -> {} (busy {busy:.2}, in flight {inflight:.2}, floor {floor}, writes {writes}): {why}",
|
|
2572
|
+
self.id.0,
|
|
2573
|
+
self.pack_k
|
|
2574
|
+
);
|
|
2575
|
+
self.pack_up = 0;
|
|
2576
|
+
self.pack_down = 0;
|
|
2577
|
+
true
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
/// Updates whether this shard acts on write skew (D214, with hysteresis): it starts after
|
|
2581
|
+
/// `SKEW_PERSIST` consecutive passes above `balance_skew` times the mean (and at least
|
|
2582
|
+
/// `balance_min_writes`), and stops once its load falls below the stop ratio or
|
|
2583
|
+
/// `balance_min_writes`. Counted on every pass, dwell or not.
|
|
2584
|
+
fn track_skew(&mut self, now: u64) {
|
|
2585
|
+
let cfg = self.shared.balance;
|
|
2586
|
+
// The slow path (D224): skew on the slow averages, whatever the mean does.
|
|
2587
|
+
self.slow_run = match self.published_slow_loads(now) {
|
|
2588
|
+
Some(slow) => {
|
|
2589
|
+
let my = slow[usize::from(self.id.0)] as f64;
|
|
2590
|
+
let mean = slow.iter().sum::<u64>() as f64 / slow.len() as f64;
|
|
2591
|
+
if my >= cfg.min_writes.max(1) as f64 && my > cfg.skew * mean {
|
|
2592
|
+
self.slow_run + 1
|
|
2593
|
+
} else {
|
|
2594
|
+
0
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
None => 0,
|
|
2598
|
+
};
|
|
2599
|
+
let Some((my, mean)) = self.shard_loads(now) else {
|
|
2600
|
+
self.skew_run = 0;
|
|
2601
|
+
self.skew_active = false;
|
|
2602
|
+
self.mean_falling = false;
|
|
2603
|
+
return;
|
|
2604
|
+
};
|
|
2605
|
+
self.mean_slow = MEAN_SLOW_ALPHA * mean + (1.0 - MEAN_SLOW_ALPHA) * self.mean_slow;
|
|
2606
|
+
self.mean_falling = mean < MEAN_FALL * self.mean_slow;
|
|
2607
|
+
let min = cfg.min_writes.max(1) as f64;
|
|
2608
|
+
if self.skew_active {
|
|
2609
|
+
if my < min || my < Self::skew_stop(cfg.skew) * mean {
|
|
2610
|
+
balance!(
|
|
2611
|
+
"shard {} write skew ends: writes {my:.0} against a mean of {mean:.0}",
|
|
2612
|
+
self.id.0
|
|
2613
|
+
);
|
|
2614
|
+
self.skew_active = false;
|
|
2615
|
+
self.skew_run = 0;
|
|
2616
|
+
}
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
// A pass while the mean is falling neither counts nor resets the persistence (D224):
|
|
2620
|
+
// under a closed-loop load it is a phase's tail, which must not trigger, and a bursty
|
|
2621
|
+
// load whose mean dips between bursts must still reach `SKEW_PERSIST`.
|
|
2622
|
+
let run_before = self.skew_run;
|
|
2623
|
+
if !self.mean_falling {
|
|
2624
|
+
if my >= min && my > cfg.skew * mean {
|
|
2625
|
+
self.skew_run += 1;
|
|
2626
|
+
} else {
|
|
2627
|
+
self.skew_run = 0;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
if self.skew_run >= SKEW_PERSIST {
|
|
2631
|
+
balance!(
|
|
2632
|
+
"shard {} write skew: writes {my:.0} against a mean of {mean:.0} for {} passes",
|
|
2633
|
+
self.id.0,
|
|
2634
|
+
self.skew_run
|
|
2635
|
+
);
|
|
2636
|
+
self.skew_active = true;
|
|
2637
|
+
} else if run_before == 0 && self.skew_run == 1 {
|
|
2638
|
+
balance!(
|
|
2639
|
+
"shard {} write skew starts: writes {my:.0} against a mean of {mean:.0}",
|
|
2640
|
+
self.id.0
|
|
2641
|
+
);
|
|
2642
|
+
} else if run_before > 0 && self.skew_run == 0 {
|
|
2643
|
+
balance!(
|
|
2644
|
+
"shard {} write skew run resets after {run_before} passes: writes {my:.0} \
|
|
2645
|
+
against a mean of {mean:.0}",
|
|
2646
|
+
self.id.0
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
/// The load ratio below which a shard acting on write skew stops (D214).
|
|
2652
|
+
fn skew_stop(skew: f64) -> f64 {
|
|
2653
|
+
1.0 + SKEW_STOP * (skew - 1.0)
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
/// The published write loads of the active shards (every shard without packing, D217;
|
|
2657
|
+
/// stale ones count as 0), or `None` with a single active shard or when this shard is not
|
|
2658
|
+
/// active (it only hands its tablets to the active ones).
|
|
2659
|
+
fn published_loads(&self, now: u64) -> Option<Vec<u64>> {
|
|
2660
|
+
let n = self.active_shards();
|
|
2661
|
+
if n < 2 || usize::from(self.id.0) >= n {
|
|
2662
|
+
return None;
|
|
2663
|
+
}
|
|
2664
|
+
let stale = self.shared.balance.interval_nanos.saturating_mul(3).max(1);
|
|
2665
|
+
Some(
|
|
2666
|
+
self.shared.loads[..n]
|
|
2667
|
+
.iter()
|
|
2668
|
+
.map(|slot| slot.read_fresh(now, stale).unwrap_or(0))
|
|
2669
|
+
.collect(),
|
|
2670
|
+
)
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
/// The published slow write loads of the active shards (D224), as
|
|
2674
|
+
/// [`published_loads`](Self::published_loads). A shard backing off while idle publishes
|
|
2675
|
+
/// rarely, so a slow load stays current for twice the longest backoff.
|
|
2676
|
+
fn published_slow_loads(&self, now: u64) -> Option<Vec<u64>> {
|
|
2677
|
+
let n = self.active_shards();
|
|
2678
|
+
if n < 2 || usize::from(self.id.0) >= n {
|
|
2679
|
+
return None;
|
|
2680
|
+
}
|
|
2681
|
+
let stale = BALANCE_IDLE_CAP_NANOS
|
|
2682
|
+
.max(self.shared.balance.interval_nanos)
|
|
2683
|
+
.saturating_mul(2);
|
|
2684
|
+
Some(
|
|
2685
|
+
self.shared.loads[..n]
|
|
2686
|
+
.iter()
|
|
2687
|
+
.map(|slot| slot.read_slow_fresh(now, stale).unwrap_or(0))
|
|
2688
|
+
.collect(),
|
|
2689
|
+
)
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
/// This shard's published load and the mean over the active shards.
|
|
2693
|
+
fn shard_loads(&self, now: u64) -> Option<(f64, f64)> {
|
|
2694
|
+
let loads = self.published_loads(now)?;
|
|
2695
|
+
let mean = loads.iter().sum::<u64>() as f64 / loads.len() as f64;
|
|
2696
|
+
Some((loads[usize::from(self.id.0)] as f64, mean))
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
/// Whether write skew may move or split `t` this pass (it has dwelt here long enough).
|
|
2700
|
+
fn settled(&self, t: TabletId) -> bool {
|
|
2701
|
+
self.arrived
|
|
2702
|
+
.get(&t)
|
|
2703
|
+
.is_none_or(|p| *p == 0 || *p + DWELL_PASSES <= self.balance_pass)
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
/// Wakes the shard for its next balancer pass even when no message arrives (#103), so
|
|
2707
|
+
/// an idle shard still merges cold tablets. Like the other clock timers (D126), one that
|
|
2708
|
+
/// gave up on a frozen clock is not armed again while the clock stays frozen.
|
|
2709
|
+
fn arm_balance_timer(&mut self, ctx: &mut ShardContext<'_, ShardMsg>) {
|
|
2710
|
+
let interval = self.shared.balance.interval_nanos;
|
|
2711
|
+
if interval == 0 || interval == u64::MAX || self.closing || self.next_balance == u64::MAX {
|
|
2712
|
+
return;
|
|
2713
|
+
}
|
|
2714
|
+
let now = ctx.now_nanos();
|
|
2715
|
+
if self.balance_timer.as_ref().is_some_and(|t| t.holds(now)) {
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
let state = TimerState::new();
|
|
2719
|
+
ctx.spawn(Box::new(ClockTimer::new(
|
|
2720
|
+
&self.shared.vfs,
|
|
2721
|
+
self.next_balance,
|
|
2722
|
+
Arc::clone(&state),
|
|
2723
|
+
ctx.submitter(self.id).clone(),
|
|
2724
|
+
ShardMsg::Kick,
|
|
2725
|
+
)));
|
|
2726
|
+
self.balance_timer = Some(state);
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
/// Cancels the balancer's wakeup (close).
|
|
2730
|
+
pub(super) fn cancel_balance_timer(&mut self) {
|
|
2731
|
+
if let Some(t) = self.balance_timer.take() {
|
|
2732
|
+
t.cancel();
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
/// Starts a rewrite of the first queued cleanup slot that still holds an SST sticking out
|
|
2737
|
+
/// of its tablet (#95): the rewrite drops the sibling's rows, so a later balancer pass can
|
|
2738
|
+
/// merge. Slots that no longer need one, or whose tablet left this shard or is being
|
|
2739
|
+
/// changed, are dropped (the balancer queues them again while the merge is refused).
|
|
2740
|
+
pub(super) fn start_cleanup(
|
|
2741
|
+
&mut self,
|
|
2742
|
+
view: &Arc<View>,
|
|
2743
|
+
busy: &[SstId],
|
|
2744
|
+
ctx: &mut ShardContext<'_, ShardMsg>,
|
|
2745
|
+
) {
|
|
2746
|
+
let last = self.shared.picker.max_levels.max(2) - 1;
|
|
2747
|
+
let now = ctx.now_nanos();
|
|
2748
|
+
// Slots waiting out a failed compaction's backoff stay queued (issue #141).
|
|
2749
|
+
let mut later = Vec::new();
|
|
2750
|
+
while !self.cleanups.is_empty() {
|
|
2751
|
+
let key = self.cleanups.remove(0);
|
|
2752
|
+
if let Some(&(_, until)) = self.slot_backoff.get(&key)
|
|
2753
|
+
&& until > now
|
|
2754
|
+
{
|
|
2755
|
+
later.push(key);
|
|
2756
|
+
continue;
|
|
2757
|
+
}
|
|
2758
|
+
let Some(tablet) = view.tablets.entry(key.0).filter(|t| t.shard == self.id) else {
|
|
2759
|
+
continue;
|
|
2760
|
+
};
|
|
2761
|
+
if self.moving.contains(&key.0)
|
|
2762
|
+
|| view
|
|
2763
|
+
.catalog
|
|
2764
|
+
.family(key.1)
|
|
2765
|
+
.is_none_or(|m| m.merge == MergeKind::Unknown)
|
|
2766
|
+
{
|
|
2767
|
+
continue;
|
|
2768
|
+
}
|
|
2769
|
+
let Some(fam) = view.ssts.family(key.0, key.1) else {
|
|
2770
|
+
continue;
|
|
2771
|
+
};
|
|
2772
|
+
let levels = fam.levels_meta();
|
|
2773
|
+
if !sticks_out_of(tablet, &levels) {
|
|
2774
|
+
continue;
|
|
2775
|
+
}
|
|
2776
|
+
let Some(task) = compact::plan_full(
|
|
2777
|
+
tablet,
|
|
2778
|
+
key.1,
|
|
2779
|
+
&levels,
|
|
2780
|
+
last,
|
|
2781
|
+
busy,
|
|
2782
|
+
true,
|
|
2783
|
+
// A cleanup is no full compaction: `None` below means busy inputs only.
|
|
2784
|
+
false,
|
|
2785
|
+
self.shared.picker.target_sst_bytes,
|
|
2786
|
+
// Rewritten anyway: it holds rows outside the tablet.
|
|
2787
|
+
|_| false,
|
|
2788
|
+
) else {
|
|
2789
|
+
// Its inputs are busy: try again after the running work.
|
|
2790
|
+
self.cleanups.insert(0, key);
|
|
2791
|
+
break;
|
|
2792
|
+
};
|
|
2793
|
+
trace!("shard {} cleanup rewrite of {key:?}", self.id.0);
|
|
2794
|
+
if let Err(e) = self.start_compaction(view, key, task, ctx) {
|
|
2795
|
+
trace!("shard {} compaction start failed: {e}", self.id.0);
|
|
2796
|
+
self.back_off_compaction(key, ctx);
|
|
2797
|
+
}
|
|
2798
|
+
break;
|
|
2799
|
+
}
|
|
2800
|
+
// A cleanup passed over for its slot's backoff runs when the backoff ends, even on
|
|
2801
|
+
// a shard that is idle by then.
|
|
2802
|
+
if let Some(until) = later
|
|
2803
|
+
.iter()
|
|
2804
|
+
.filter_map(|k| self.slot_backoff.get(k).map(|&(_, until)| until))
|
|
2805
|
+
.min()
|
|
2806
|
+
{
|
|
2807
|
+
self.arm_compaction_retry(until, ctx);
|
|
2808
|
+
}
|
|
2809
|
+
self.cleanups.extend(later);
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
/// The balancer's choice for this interval, if any: a size split, a write-skew (or
|
|
2813
|
+
/// memtable-skew) move or split, or a merge of two small, cold neighbours. With no
|
|
2814
|
+
/// choice, also the slots to rewrite so that a refused merge can go through later.
|
|
2815
|
+
fn decide(
|
|
2816
|
+
&self,
|
|
2817
|
+
now: u64,
|
|
2818
|
+
mem_per: &HashMap<TabletId, u64>,
|
|
2819
|
+
) -> (Option<TabletOpKind>, Vec<(TabletId, FamilyId)>) {
|
|
2820
|
+
let mut cleanups = Vec::new();
|
|
2821
|
+
(self.choose(now, mem_per, &mut cleanups), cleanups)
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
fn choose(
|
|
2825
|
+
&self,
|
|
2826
|
+
now: u64,
|
|
2827
|
+
mem_per: &HashMap<TabletId, u64>,
|
|
2828
|
+
cleanups: &mut Vec<(TabletId, FamilyId)>,
|
|
2829
|
+
) -> Option<TabletOpKind> {
|
|
2830
|
+
let cfg = self.shared.balance;
|
|
2831
|
+
let view = self.shared.view.load_full();
|
|
2832
|
+
let owned: Vec<TabletEntry> = view
|
|
2833
|
+
.tablets
|
|
2834
|
+
.iter()
|
|
2835
|
+
.filter(|t| t.shard == self.id)
|
|
2836
|
+
.cloned()
|
|
2837
|
+
.collect();
|
|
2838
|
+
if owned.is_empty() {
|
|
2839
|
+
return None;
|
|
2840
|
+
}
|
|
2841
|
+
let refs = view.catalog.sst_refs();
|
|
2842
|
+
// Every `(tablet, family)` slot written to takes memtable chunks: a shard keeps its
|
|
2843
|
+
// slots within `max_slots`, so splits never starve writers of memtables.
|
|
2844
|
+
let room_for = |t: &TabletEntry| {
|
|
2845
|
+
self.slots_fit(&view, self.id, view.catalog.family_ids_of(t.table).len())
|
|
2846
|
+
};
|
|
2847
|
+
// 1. Size: a tablet holding `tablet_split_bytes` of SSTs splits in two (not while it
|
|
2848
|
+
// still shares SSTs with a sibling: their bytes would count twice).
|
|
2849
|
+
for t in &owned {
|
|
2850
|
+
let (bytes, shared) = live_bytes(&view, t, &refs);
|
|
2851
|
+
if bytes < cfg.split_bytes || shared {
|
|
2852
|
+
continue;
|
|
2853
|
+
}
|
|
2854
|
+
if !room_for(t) {
|
|
2855
|
+
self.shared.metrics[usize::from(self.id.0)]
|
|
2856
|
+
.refused_slots
|
|
2857
|
+
.fetch_add(1, Ordering::Relaxed);
|
|
2858
|
+
balance!(
|
|
2859
|
+
"shard {} balancer: tablet {} is due a size split but the shard holds \
|
|
2860
|
+
{} of its {} slots",
|
|
2861
|
+
self.id.0,
|
|
2862
|
+
t.id.0,
|
|
2863
|
+
Self::slots_in(&view, self.id),
|
|
2864
|
+
self.max_slots()
|
|
2865
|
+
);
|
|
2866
|
+
continue;
|
|
2867
|
+
}
|
|
2868
|
+
if let Some(key) = self.size_split_key(&view, t) {
|
|
2869
|
+
return Some(TabletOpKind::Split {
|
|
2870
|
+
tablet: t.id,
|
|
2871
|
+
keys: vec![key],
|
|
2872
|
+
owners: vec![self.id, self.id],
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
let dwelling = self
|
|
2877
|
+
.skew_pass
|
|
2878
|
+
.is_some_and(|p| p + DWELL_PASSES > self.balance_pass);
|
|
2879
|
+
// Packing (D217): a shard outside the active ones (`0..k`) hands its written tablets,
|
|
2880
|
+
// one per dwell, to the least busy active shard with room. Cold tablets stay: they
|
|
2881
|
+
// cost no wakeups. A shard a skew change claimed is no target until its load shows
|
|
2882
|
+
// (D270), as for skew changes (D224). A packing move claims nothing itself: packing
|
|
2883
|
+
// means handing load to the active shards, and claims would hold its moves to one
|
|
2884
|
+
// per target per dwell.
|
|
2885
|
+
let active = self.active_shards();
|
|
2886
|
+
if usize::from(self.id.0) >= active && !dwelling {
|
|
2887
|
+
let stale = cfg.interval_nanos.saturating_mul(3).max(1);
|
|
2888
|
+
let mut targets: Vec<(u64, usize)> = self.shared.loads[..active]
|
|
2889
|
+
.iter()
|
|
2890
|
+
.enumerate()
|
|
2891
|
+
.map(|(i, l)| (l.read_busy_fresh(now, stale).unwrap_or(0), i))
|
|
2892
|
+
.collect();
|
|
2893
|
+
targets.sort_unstable();
|
|
2894
|
+
let written = owned
|
|
2895
|
+
.iter()
|
|
2896
|
+
.filter(|t| self.settled(t.id))
|
|
2897
|
+
.filter(|t| self.loads.get(&t.id).is_some_and(|l| l.ewma >= 1.0))
|
|
2898
|
+
.max_by(|a, b| {
|
|
2899
|
+
let w = |t: &TabletEntry| self.loads.get(&t.id).map_or(0.0, |l| l.ewma);
|
|
2900
|
+
w(a).total_cmp(&w(b)).then(b.id.cmp(&a.id))
|
|
2901
|
+
});
|
|
2902
|
+
if let Some(t) = written {
|
|
2903
|
+
let families = view.catalog.family_ids_of(t.table).len();
|
|
2904
|
+
if let Some((_, to)) = targets.iter().find(|(_, i)| {
|
|
2905
|
+
self.shared.loads[*i].claimable(now)
|
|
2906
|
+
&& self.slots_fit(&view, ShardId(*i as u16), families)
|
|
2907
|
+
}) {
|
|
2908
|
+
return Some(TabletOpKind::Move {
|
|
2909
|
+
tablet: t.id,
|
|
2910
|
+
to: ShardId(*to as u16),
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
// 2. Skew across shards, on the moving averages of write load (#103; memtable bytes
|
|
2916
|
+
// swing with every flush and no longer count). Only once the skew persisted
|
|
2917
|
+
// (`track_skew`, D214), not during the shard's dwell after its last move, and only
|
|
2918
|
+
// for tablets that dwelt here long enough. Under packing, only among the active
|
|
2919
|
+
// shards (D217).
|
|
2920
|
+
// The slow path (D224) acts on the slow averages instead, once per `SLOW_DWELL`:
|
|
2921
|
+
// under a closed-loop load the fast averages show skew only in each phase's tail
|
|
2922
|
+
// (where it doesn't count), the slow ones the shards' shares of the work.
|
|
2923
|
+
self.chose_slow.set(false);
|
|
2924
|
+
let slow_dwelling = self
|
|
2925
|
+
.slow_pass
|
|
2926
|
+
.is_some_and(|p| p + SLOW_DWELL > self.balance_pass);
|
|
2927
|
+
for slow in [false, true] {
|
|
2928
|
+
let (ready, loads) = if slow {
|
|
2929
|
+
// Only while the fast averages agree this shard is above the stop ratio: once
|
|
2930
|
+
// the fast path balanced it, the slow averages still show the old skew for a
|
|
2931
|
+
// while, and acting on them would overshoot.
|
|
2932
|
+
let agrees = self
|
|
2933
|
+
.shard_loads(now)
|
|
2934
|
+
.is_some_and(|(my, mean)| my > Self::skew_stop(cfg.skew) * mean);
|
|
2935
|
+
(
|
|
2936
|
+
self.slow_run >= SKEW_PERSIST && !slow_dwelling && agrees,
|
|
2937
|
+
self.published_slow_loads(now),
|
|
2938
|
+
)
|
|
2939
|
+
} else {
|
|
2940
|
+
(
|
|
2941
|
+
self.skew_active && !self.mean_falling,
|
|
2942
|
+
self.published_loads(now),
|
|
2943
|
+
)
|
|
2944
|
+
};
|
|
2945
|
+
let Some(writes) = loads.filter(|_| ready && !dwelling) else {
|
|
2946
|
+
continue;
|
|
2947
|
+
};
|
|
2948
|
+
let settled: Vec<TabletEntry> = owned
|
|
2949
|
+
.iter()
|
|
2950
|
+
.filter(|t| self.settled(t.id))
|
|
2951
|
+
.cloned()
|
|
2952
|
+
.collect();
|
|
2953
|
+
let tablet_writes: HashMap<TabletId, u64> = settled
|
|
2954
|
+
.iter()
|
|
2955
|
+
.map(|t| {
|
|
2956
|
+
let w = self
|
|
2957
|
+
.loads
|
|
2958
|
+
.get(&t.id)
|
|
2959
|
+
.map_or(0.0, |l| if slow { l.slow } else { l.ewma });
|
|
2960
|
+
(t.id, w.round() as u64)
|
|
2961
|
+
})
|
|
2962
|
+
.collect();
|
|
2963
|
+
let splits_ok = |op: &TabletOpKind| match op {
|
|
2964
|
+
TabletOpKind::Split { tablet, .. } => {
|
|
2965
|
+
owned.iter().find(|t| t.id == *tablet).is_some_and(room_for)
|
|
2966
|
+
}
|
|
2967
|
+
_ => true,
|
|
2968
|
+
};
|
|
2969
|
+
// The slow path makes one change per dwell, only past the trigger itself.
|
|
2970
|
+
let ratio = if slow {
|
|
2971
|
+
cfg.skew
|
|
2972
|
+
} else {
|
|
2973
|
+
Self::skew_stop(cfg.skew)
|
|
2974
|
+
};
|
|
2975
|
+
if let Some(op) = self.skew_op(&view, &settled, &writes, &tablet_writes, ratio, now)
|
|
2976
|
+
&& splits_ok(&op)
|
|
2977
|
+
&& self.claim_targets(&op, if slow { SLOW_DWELL } else { DWELL_PASSES }, now)
|
|
2978
|
+
{
|
|
2979
|
+
self.chose_slow.set(slow);
|
|
2980
|
+
return Some(op);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
// 3. Merge two adjacent cold tablets of one table that are small together.
|
|
2984
|
+
let small_together = |a: &TabletEntry, b: &TabletEntry| {
|
|
2985
|
+
live_bytes(&view, a, &refs).0 + live_bytes(&view, b, &refs).0 < cfg.split_bytes / 4
|
|
2986
|
+
};
|
|
2987
|
+
let cold_nanos = cfg.interval_nanos.saturating_mul(cfg.cold_passes);
|
|
2988
|
+
let long_ago = |(pass, clock): (u64, u64)| {
|
|
2989
|
+
pass + cfg.cold_passes <= self.balance_pass || clock.saturating_add(cold_nanos) <= now
|
|
2990
|
+
};
|
|
2991
|
+
let cold = |id: TabletId| {
|
|
2992
|
+
self.loads.get(&id).is_none_or(|l| {
|
|
2993
|
+
l.writes == 0
|
|
2994
|
+
&& l.prev_writes == 0
|
|
2995
|
+
&& (l.last_write == (0, 0) || long_ago(l.last_write))
|
|
2996
|
+
}) && long_ago((
|
|
2997
|
+
self.arrived.get(&id).copied().unwrap_or(0),
|
|
2998
|
+
self.arrived_at.get(&id).copied().unwrap_or(0),
|
|
2999
|
+
)) && mem_per.get(&id).copied().unwrap_or(0) == 0
|
|
3000
|
+
};
|
|
3001
|
+
for pair in owned.windows(2) {
|
|
3002
|
+
let (l, r) = (&pair[0], &pair[1]);
|
|
3003
|
+
if l.table != r.table
|
|
3004
|
+
|| l.end.as_deref() != Some(r.start.as_slice())
|
|
3005
|
+
|| !cold(l.id)
|
|
3006
|
+
|| !cold(r.id)
|
|
3007
|
+
{
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
if !small_together(l, r) {
|
|
3011
|
+
continue;
|
|
3012
|
+
}
|
|
3013
|
+
if merged_ssts(&view.catalog, l, r).is_ok() {
|
|
3014
|
+
return Some(TabletOpKind::Merge {
|
|
3015
|
+
left: l.id,
|
|
3016
|
+
right: r.id,
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
// Refused while a side still holds an SST inherited from the split, with its
|
|
3020
|
+
// sibling's rows: nothing else rewrites a cold tablet's SSTs, so ask for it.
|
|
3021
|
+
for t in [l, r] {
|
|
3022
|
+
for f in view.catalog.family_ids_of(t.table) {
|
|
3023
|
+
if view
|
|
3024
|
+
.ssts
|
|
3025
|
+
.family(t.id, f)
|
|
3026
|
+
.is_some_and(|fam| sticks_out_of(t, &fam.levels_meta()))
|
|
3027
|
+
{
|
|
3028
|
+
cleanups.push((t.id, f));
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
// 4. Consolidate (#103): a cold tablet whose left neighbour lives on another shard
|
|
3034
|
+
// moves there when the two are small together, so that shard can merge them.
|
|
3035
|
+
// Only right to left, so two shards never swap tablets, and only tablets that
|
|
3036
|
+
// dwelt here (a split's child is cold until its first writes arrive).
|
|
3037
|
+
for t in &owned {
|
|
3038
|
+
if !cold(t.id) || t.start.is_empty() || !self.settled(t.id) {
|
|
3039
|
+
continue;
|
|
3040
|
+
}
|
|
3041
|
+
let Some(left) = view
|
|
3042
|
+
.tablets
|
|
3043
|
+
.tablets_of(t.table)
|
|
3044
|
+
.iter()
|
|
3045
|
+
.find(|l| l.end.as_deref() == Some(t.start.as_slice()))
|
|
3046
|
+
else {
|
|
3047
|
+
continue;
|
|
3048
|
+
};
|
|
3049
|
+
// Never onto a shard packing is emptying (D217).
|
|
3050
|
+
if left.shard == self.id || usize::from(left.shard.0) >= active {
|
|
3051
|
+
continue;
|
|
3052
|
+
}
|
|
3053
|
+
let families = view.catalog.family_ids_of(t.table).len();
|
|
3054
|
+
if small_together(left, t) && self.slots_fit(&view, left.shard, families) {
|
|
3055
|
+
return Some(TabletOpKind::Move {
|
|
3056
|
+
tablet: t.id,
|
|
3057
|
+
to: left.shard,
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
None
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
/// Whether `op` touches the tablet a running compaction is on: it waits for the
|
|
3065
|
+
/// compaction's end and is decided again then (#102).
|
|
3066
|
+
fn waits_on_compaction(&self, op: &TabletOpKind) -> bool {
|
|
3067
|
+
matches!(self.compaction, Some((t, _)) if op.tablets().contains(&t))
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
/// Claims every shard a skew change hands load to (D224), for `dwell` of their passes or
|
|
3071
|
+
/// as many base intervals of clock, whichever comes first (the slow path's dwell for its
|
|
3072
|
+
/// changes). False if another change claimed one first: the change waits for a later
|
|
3073
|
+
/// pass. A claim whose change is refused simply runs out.
|
|
3074
|
+
///
|
|
3075
|
+
/// A change that waits on a compaction claims nothing and reads as claimed (D270): it
|
|
3076
|
+
/// claims when it is decided again at the compaction's end. So a caller that starts the
|
|
3077
|
+
/// change it gets must not have a compaction running on its tablets: `maybe_balance`
|
|
3078
|
+
/// filters waiting changes out, and `start_waiting_change` runs with no compaction.
|
|
3079
|
+
fn claim_targets(&self, op: &TabletOpKind, dwell: u64, now: u64) -> bool {
|
|
3080
|
+
if self.waits_on_compaction(op) {
|
|
3081
|
+
return true;
|
|
3082
|
+
}
|
|
3083
|
+
let dwell_nanos = self.shared.balance.interval_nanos.saturating_mul(dwell);
|
|
3084
|
+
let to: Vec<ShardId> = match op {
|
|
3085
|
+
TabletOpKind::Move { to, .. } => vec![*to],
|
|
3086
|
+
TabletOpKind::Split { owners, .. } => {
|
|
3087
|
+
owners.iter().copied().filter(|o| *o != self.id).collect()
|
|
3088
|
+
}
|
|
3089
|
+
_ => Vec::new(),
|
|
3090
|
+
};
|
|
3091
|
+
to.iter()
|
|
3092
|
+
.all(|s| self.shared.loads[usize::from(s.0)].claim(dwell, dwell_nanos, now))
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
/// A move or split that evens out `loads` (per shard), given this shard's tablets'
|
|
3096
|
+
/// shares of it (`per_tablet`), when this shard is skewed past the threshold.
|
|
3097
|
+
fn skew_op(
|
|
3098
|
+
&self,
|
|
3099
|
+
view: &View,
|
|
3100
|
+
owned: &[TabletEntry],
|
|
3101
|
+
loads: &[u64],
|
|
3102
|
+
per_tablet: &HashMap<TabletId, u64>,
|
|
3103
|
+
ratio: f64,
|
|
3104
|
+
now: u64,
|
|
3105
|
+
) -> Option<TabletOpKind> {
|
|
3106
|
+
let min = self.shared.balance.min_writes;
|
|
3107
|
+
let me = usize::from(self.id.0);
|
|
3108
|
+
let my = loads[me];
|
|
3109
|
+
let total: u64 = loads.iter().sum();
|
|
3110
|
+
let mean = total as f64 / loads.len() as f64;
|
|
3111
|
+
if my < min.max(1) || (my as f64) <= ratio * mean {
|
|
3112
|
+
return None;
|
|
3113
|
+
}
|
|
3114
|
+
// A shard another skew change just handed load is not a target until its published
|
|
3115
|
+
// load shows it (D224): shards balancing at once would otherwise all shed into the
|
|
3116
|
+
// same cold shard.
|
|
3117
|
+
let open = |i: usize| self.shared.loads[i].claimable(now);
|
|
3118
|
+
let (cold, low) = loads
|
|
3119
|
+
.iter()
|
|
3120
|
+
.enumerate()
|
|
3121
|
+
.filter(|(i, _)| *i != me && open(*i))
|
|
3122
|
+
.min_by_key(|(i, l)| (**l, *i))
|
|
3123
|
+
.map(|(i, l)| (i, *l))?;
|
|
3124
|
+
// With the claimed shards left out, the coldest one left may not be cold at all.
|
|
3125
|
+
if low as f64 >= mean {
|
|
3126
|
+
return None;
|
|
3127
|
+
}
|
|
3128
|
+
let gap = my - low;
|
|
3129
|
+
let mut tablets: Vec<(TabletId, u64)> = owned
|
|
3130
|
+
.iter()
|
|
3131
|
+
.map(|t| (t.id, per_tablet.get(&t.id).copied().unwrap_or(0)))
|
|
3132
|
+
.filter(|(_, w)| *w > 0)
|
|
3133
|
+
.collect();
|
|
3134
|
+
tablets.sort_by_key(|(id, w)| (std::cmp::Reverse(*w), *id));
|
|
3135
|
+
// A tablet carrying less than the gap moves: the one closest to half of it.
|
|
3136
|
+
let to = ShardId(cold as u16);
|
|
3137
|
+
let fits = |id: TabletId| {
|
|
3138
|
+
owned.iter().find(|t| t.id == id).is_some_and(|t| {
|
|
3139
|
+
self.slots_fit(view, to, view.catalog.family_ids_of(t.table).len())
|
|
3140
|
+
})
|
|
3141
|
+
};
|
|
3142
|
+
if let Some((t, _)) = tablets
|
|
3143
|
+
.iter()
|
|
3144
|
+
.filter(|(id, w)| *w < gap && *w >= gap / 8 && fits(*id))
|
|
3145
|
+
.min_by_key(|(id, w)| ((gap / 2).abs_diff(*w), *id))
|
|
3146
|
+
{
|
|
3147
|
+
return Some(TabletOpKind::Move { tablet: *t, to });
|
|
3148
|
+
}
|
|
3149
|
+
// No tablet carries an eighth of the gap, and none dominates (the hottest is below the
|
|
3150
|
+
// gap): many small tablets, as a full shard of equal ones (D251). Splitting one would
|
|
3151
|
+
// not help, and needs a slot this shard may not have: the largest that fits moves.
|
|
3152
|
+
if tablets.first().is_some_and(|(_, w)| *w < gap)
|
|
3153
|
+
&& let Some((t, _)) = tablets.iter().find(|(id, w)| *w < gap && fits(*id))
|
|
3154
|
+
{
|
|
3155
|
+
return Some(TabletOpKind::Move { tablet: *t, to });
|
|
3156
|
+
}
|
|
3157
|
+
// One tablet dominates: split it over this shard and the coldest shards below the
|
|
3158
|
+
// mean, about `my / mean` children (D214): a shard at 1.3x the mean sheds into one
|
|
3159
|
+
// other shard, one carrying all the load into every shard.
|
|
3160
|
+
let (hot, _) = *tablets.first()?;
|
|
3161
|
+
let entry = owned.iter().find(|t| t.id == hot)?;
|
|
3162
|
+
let mut targets: Vec<(u64, usize)> = loads
|
|
3163
|
+
.iter()
|
|
3164
|
+
.enumerate()
|
|
3165
|
+
.filter(|(i, l)| *i != me && open(*i) && (**l as f64) < mean)
|
|
3166
|
+
.map(|(i, l)| (*l, i))
|
|
3167
|
+
.collect();
|
|
3168
|
+
if targets.is_empty() {
|
|
3169
|
+
// Every shard below the mean is claimed by another skew change (D224).
|
|
3170
|
+
return None;
|
|
3171
|
+
}
|
|
3172
|
+
targets.sort_unstable();
|
|
3173
|
+
let parts = ((my as f64 / mean.max(1.0)).round() as usize).clamp(2, targets.len() + 1);
|
|
3174
|
+
let mut owners = vec![self.id];
|
|
3175
|
+
owners.extend(
|
|
3176
|
+
targets
|
|
3177
|
+
.iter()
|
|
3178
|
+
.take(parts - 1)
|
|
3179
|
+
.map(|(_, i)| ShardId(*i as u16)),
|
|
3180
|
+
);
|
|
3181
|
+
let keys = self.skew_split_keys(entry, owners.len());
|
|
3182
|
+
if keys.is_empty() {
|
|
3183
|
+
return None;
|
|
3184
|
+
}
|
|
3185
|
+
owners.truncate(keys.len() + 1);
|
|
3186
|
+
Some(TabletOpKind::Split {
|
|
3187
|
+
tablet: hot,
|
|
3188
|
+
keys,
|
|
3189
|
+
owners,
|
|
3190
|
+
})
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
/// Up to `parts - 1` split keys from the rows written to `t` this interval (quantiles of
|
|
3194
|
+
/// the sample), strictly inside its range.
|
|
3195
|
+
fn skew_split_keys(&self, t: &TabletEntry, parts: usize) -> Vec<Vec<u8>> {
|
|
3196
|
+
let Some(load) = self.loads.get(&t.id) else {
|
|
3197
|
+
return Vec::new();
|
|
3198
|
+
};
|
|
3199
|
+
let mut rows: Vec<&[u8]> = load
|
|
3200
|
+
.live_samples()
|
|
3201
|
+
.iter()
|
|
3202
|
+
.map(Vec::as_slice)
|
|
3203
|
+
.filter(|r| inside(t, r))
|
|
3204
|
+
.collect();
|
|
3205
|
+
rows.sort_unstable();
|
|
3206
|
+
rows.dedup();
|
|
3207
|
+
quantiles(&rows, parts)
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
/// A split key near the middle of `t`'s SSTs (their boundary rows) and recent writes.
|
|
3211
|
+
fn size_split_key(&self, view: &View, t: &TabletEntry) -> Option<Vec<u8>> {
|
|
3212
|
+
let mut rows: Vec<Vec<u8>> = Vec::new();
|
|
3213
|
+
for f in view.catalog.family_ids_of(t.table) {
|
|
3214
|
+
if let Some(fam) = view.ssts.family(t.id, f) {
|
|
3215
|
+
for sst in fam.iter() {
|
|
3216
|
+
for key in [&sst.meta.smallest_key, &sst.meta.largest_key] {
|
|
3217
|
+
if let Some(r) = row_of_key(key) {
|
|
3218
|
+
rows.push(r);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
if let Some(l) = self.loads.get(&t.id) {
|
|
3225
|
+
rows.extend(l.live_samples().iter().cloned());
|
|
3226
|
+
}
|
|
3227
|
+
let mut rows: Vec<&[u8]> = rows
|
|
3228
|
+
.iter()
|
|
3229
|
+
.map(Vec::as_slice)
|
|
3230
|
+
.filter(|r| inside(t, r))
|
|
3231
|
+
.collect();
|
|
3232
|
+
rows.sort_unstable();
|
|
3233
|
+
rows.dedup();
|
|
3234
|
+
quantiles(&rows, 2).pop()
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
/// `parts - 1` evenly spaced rows of `rows` (sorted, distinct).
|
|
3239
|
+
fn quantiles(rows: &[&[u8]], parts: usize) -> Vec<Vec<u8>> {
|
|
3240
|
+
let parts = parts.min(rows.len());
|
|
3241
|
+
let mut out: Vec<Vec<u8>> = Vec::new();
|
|
3242
|
+
for i in 1..parts {
|
|
3243
|
+
let r = rows[i * rows.len() / parts];
|
|
3244
|
+
if out.last().is_none_or(|l| l.as_slice() < r) {
|
|
3245
|
+
out.push(r.to_vec());
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
out
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
/// Whether `row` lies strictly inside `t` (a split there leaves both sides non-empty).
|
|
3252
|
+
fn inside(t: &TabletEntry, row: &[u8]) -> bool {
|
|
3253
|
+
row > t.start.as_slice() && t.end.as_ref().is_none_or(|e| row < e.as_slice())
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
/// Whether any SST of a slot of `t` holds rows outside `t`.
|
|
3257
|
+
fn sticks_out_of(t: &TabletEntry, levels: &pigeonhole_compaction::Levels) -> bool {
|
|
3258
|
+
compact::tablet_range(t).is_ok_and(|range| {
|
|
3259
|
+
levels
|
|
3260
|
+
.levels
|
|
3261
|
+
.iter()
|
|
3262
|
+
.flatten()
|
|
3263
|
+
.any(|m| compact::sticks_out(m, &range))
|
|
3264
|
+
})
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
/// The unescaped row of an internal key.
|
|
3268
|
+
fn row_of_key(key: &[u8]) -> Option<Vec<u8>> {
|
|
3269
|
+
let n = row_prefix_len(key).ok()?;
|
|
3270
|
+
let mut out = Vec::new();
|
|
3271
|
+
Escaped::new(key.get(..n.checked_sub(2)?)?).unescape_into(&mut out);
|
|
3272
|
+
Some(out)
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
/// Bytes of SSTs `t` references, and whether any of them is shared with another tablet.
|
|
3276
|
+
fn live_bytes(view: &View, t: &TabletEntry, refs: &HashMap<SstId, usize>) -> (u64, bool) {
|
|
3277
|
+
let mut bytes = 0;
|
|
3278
|
+
let mut shared = false;
|
|
3279
|
+
for f in view.catalog.family_ids_of(t.table) {
|
|
3280
|
+
if let Some(fam) = view.ssts.family(t.id, f) {
|
|
3281
|
+
for sst in fam.iter() {
|
|
3282
|
+
bytes += sst.meta.len;
|
|
3283
|
+
shared |= refs.get(&sst.meta.id).copied().unwrap_or(0) > 1;
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
(bytes, shared)
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
/// The rows (hashed with their table) a member writes or reads.
|
|
3291
|
+
fn member_rows(m: &Member) -> Vec<u64> {
|
|
3292
|
+
let mut out = Vec::new();
|
|
3293
|
+
if let Ok(batch) = m.bytes.batch() {
|
|
3294
|
+
for mu in batch.iter().flatten() {
|
|
3295
|
+
out.push(hash_row(mu.table, mu.row));
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
if let Some(reads) = m.validate.as_ref().map(|v| &v.reads) {
|
|
3299
|
+
for r in reads {
|
|
3300
|
+
out.push(hash_row(r.table, &r.row));
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
if let Some((table, row, _)) = m.predicate.as_deref() {
|
|
3304
|
+
out.push(hash_row(*table, row));
|
|
3305
|
+
}
|
|
3306
|
+
out
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
/// A single-shard member back as the request it came from.
|
|
3310
|
+
fn member_req(m: Member) -> CommitReq {
|
|
3311
|
+
let bytes = match m.bytes {
|
|
3312
|
+
Bytes::Own(b) => b,
|
|
3313
|
+
Bytes::Shared(b) => (*b).clone(),
|
|
3314
|
+
Bytes::Streams(_) => BatchBuilder::new(),
|
|
3315
|
+
};
|
|
3316
|
+
CommitReq {
|
|
3317
|
+
bytes,
|
|
3318
|
+
durability: m.durability,
|
|
3319
|
+
reply: m.reply,
|
|
3320
|
+
submitted_at: m.submitted_at,
|
|
3321
|
+
validate: m.validate,
|
|
3322
|
+
predicate: m.predicate,
|
|
3323
|
+
retry: m.retry,
|
|
3324
|
+
map_version: m.map_version,
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
/// The shards a commit's rows (written and read, and its predicate row) route to through
|
|
3329
|
+
/// `view`, in first-appearance order.
|
|
3330
|
+
pub(crate) fn commit_shards(
|
|
3331
|
+
view: &View,
|
|
3332
|
+
bytes: &[u8],
|
|
3333
|
+
reads: Option<&[crate::write::ReadKey]>,
|
|
3334
|
+
predicate: Option<(TableId, &[u8])>,
|
|
3335
|
+
) -> Result<Vec<ShardId>> {
|
|
3336
|
+
let mut out: Vec<ShardId> = Vec::new();
|
|
3337
|
+
let mut add = |s: ShardId| {
|
|
3338
|
+
if !out.contains(&s) {
|
|
3339
|
+
out.push(s);
|
|
3340
|
+
}
|
|
3341
|
+
};
|
|
3342
|
+
let mut wrote = false;
|
|
3343
|
+
for mu in BatchRef::new(bytes)?.iter() {
|
|
3344
|
+
let mu = mu?;
|
|
3345
|
+
if let Some((_, s)) = view.tablets.route(mu.table, mu.row) {
|
|
3346
|
+
wrote = true;
|
|
3347
|
+
add(s);
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
if !wrote
|
|
3351
|
+
&& let Some((table, row)) = predicate
|
|
3352
|
+
&& let Some((_, s)) = view.tablets.route(table, row)
|
|
3353
|
+
{
|
|
3354
|
+
add(s);
|
|
3355
|
+
}
|
|
3356
|
+
for r in reads.unwrap_or_default() {
|
|
3357
|
+
if let Some((_, s)) = view.tablets.route(r.table, &r.row) {
|
|
3358
|
+
add(s);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
Ok(out)
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
/// Splits a batch into one builder per shard, in `shards` order.
|
|
3365
|
+
pub(crate) fn split_by_shard(
|
|
3366
|
+
view: &View,
|
|
3367
|
+
builder: &BatchBuilder,
|
|
3368
|
+
shards: &[ShardId],
|
|
3369
|
+
) -> Result<Vec<(ShardId, Arc<BatchBuilder>)>> {
|
|
3370
|
+
let mut parts: Vec<(ShardId, BatchBuilder)> =
|
|
3371
|
+
shards.iter().map(|s| (*s, BatchBuilder::new())).collect();
|
|
3372
|
+
for m in builder.batch().iter() {
|
|
3373
|
+
let m = m?;
|
|
3374
|
+
let Some((_, shard)) = view.tablets.route(m.table, m.row) else {
|
|
3375
|
+
return Err(Error::TableNotFound(format!("table {}", m.table.0)));
|
|
3376
|
+
};
|
|
3377
|
+
let Some(part) = parts.iter_mut().find(|(s, _)| *s == shard) else {
|
|
3378
|
+
return Err(Error::Corruption(
|
|
3379
|
+
"a row routed to an unlisted shard".to_owned(),
|
|
3380
|
+
));
|
|
3381
|
+
};
|
|
3382
|
+
part.1
|
|
3383
|
+
.push(m.table, m.family, m.kind, m.row, m.qualifier, m.ts, m.value)?;
|
|
3384
|
+
}
|
|
3385
|
+
Ok(parts.into_iter().map(|(s, b)| (s, Arc::new(b))).collect())
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
/// The row-prefix bounds of `[start, end)`.
|
|
3389
|
+
fn prefix_bounds(start: &[u8], end: Option<&[u8]>) -> Result<(Vec<u8>, Option<Vec<u8>>)> {
|
|
3390
|
+
let mut s = Vec::new();
|
|
3391
|
+
if !start.is_empty() {
|
|
3392
|
+
encode_row_prefix(&mut s, start)?;
|
|
3393
|
+
}
|
|
3394
|
+
let e = match end {
|
|
3395
|
+
Some(e) => {
|
|
3396
|
+
let mut k = Vec::new();
|
|
3397
|
+
encode_row_prefix(&mut k, e)?;
|
|
3398
|
+
Some(k)
|
|
3399
|
+
}
|
|
3400
|
+
None => None,
|
|
3401
|
+
};
|
|
3402
|
+
Ok((s, e))
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
/// Whether an SST holds rows within the row-prefix range `[start, end)`.
|
|
3406
|
+
fn overlaps(meta: &SstMeta, start: &[u8], end: Option<&[u8]>) -> bool {
|
|
3407
|
+
let first = crate::snapshot::row_of(&meta.smallest_key);
|
|
3408
|
+
let last = crate::snapshot::row_of(&meta.largest_key);
|
|
3409
|
+
last >= start && end.is_none_or(|e| first < e)
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
/// The SSTs of a merge of `l` and `r`, per family as `(level, meta)`, each once.
|
|
3413
|
+
///
|
|
3414
|
+
/// Refused while a sibling still has to compact its copy of an SST they shared after a split
|
|
3415
|
+
/// (D13): an SST only one side references must hold none of the other side's rows (the other
|
|
3416
|
+
/// side rewrote them; the merged tablet would see them twice), and no level of the result may
|
|
3417
|
+
/// hold overlapping SSTs.
|
|
3418
|
+
fn merged_ssts(
|
|
3419
|
+
catalog: &Catalog,
|
|
3420
|
+
l: &TabletEntry,
|
|
3421
|
+
r: &TabletEntry,
|
|
3422
|
+
) -> Result<Vec<(FamilyId, u8, Arc<SstMeta>)>> {
|
|
3423
|
+
let (ls, le) = prefix_bounds(&l.start, l.end.as_deref())?;
|
|
3424
|
+
let (rs, re) = prefix_bounds(&r.start, r.end.as_deref())?;
|
|
3425
|
+
let pending = || {
|
|
3426
|
+
Error::InvalidArgument(
|
|
3427
|
+
"a sibling has not compacted a shared SST yet; merge after compaction".to_owned(),
|
|
3428
|
+
)
|
|
3429
|
+
};
|
|
3430
|
+
let mut out = Vec::new();
|
|
3431
|
+
for f in catalog.family_ids_of(l.table) {
|
|
3432
|
+
let ids = |t: &TabletEntry| -> HashSet<SstId> {
|
|
3433
|
+
catalog
|
|
3434
|
+
.ssts
|
|
3435
|
+
.get(&(t.id, f))
|
|
3436
|
+
.map(|list| list.iter().map(|(_, m)| m.id).collect())
|
|
3437
|
+
.unwrap_or_default()
|
|
3438
|
+
};
|
|
3439
|
+
let (lids, rids) = (ids(l), ids(r));
|
|
3440
|
+
let mut seen: HashSet<SstId> = HashSet::new();
|
|
3441
|
+
let mut levels: BTreeMap<u8, Vec<Arc<SstMeta>>> = BTreeMap::new();
|
|
3442
|
+
for t in [l, r] {
|
|
3443
|
+
if let Some(list) = catalog.ssts.get(&(t.id, f)) {
|
|
3444
|
+
for (level, meta) in list.iter() {
|
|
3445
|
+
let only_left = lids.contains(&meta.id) && !rids.contains(&meta.id);
|
|
3446
|
+
let only_right = rids.contains(&meta.id) && !lids.contains(&meta.id);
|
|
3447
|
+
if (only_left && overlaps(meta, &rs, re.as_deref()))
|
|
3448
|
+
|| (only_right && overlaps(meta, &ls, le.as_deref()))
|
|
3449
|
+
{
|
|
3450
|
+
return Err(pending());
|
|
3451
|
+
}
|
|
3452
|
+
if seen.insert(meta.id) {
|
|
3453
|
+
levels.entry(*level).or_default().push(Arc::clone(meta));
|
|
3454
|
+
out.push((f, *level, Arc::clone(meta)));
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
for (level, list) in &mut levels {
|
|
3460
|
+
if *level == 0 {
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3463
|
+
list.sort_by(|a, b| a.smallest_key.cmp(&b.smallest_key));
|
|
3464
|
+
if list
|
|
3465
|
+
.windows(2)
|
|
3466
|
+
.any(|w| w[0].largest_key >= w[1].smallest_key)
|
|
3467
|
+
{
|
|
3468
|
+
return Err(Error::InvalidArgument(
|
|
3469
|
+
"the tablets' SSTs overlap within a level; merge after compaction".to_owned(),
|
|
3470
|
+
));
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
Ok(out)
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
/// The manifest edits and owners of a tablet change, against the catalog at commit time.
|
|
3478
|
+
fn tablet_change(
|
|
3479
|
+
catalog: &mut Catalog,
|
|
3480
|
+
kind: &TabletOpKind,
|
|
3481
|
+
entries: &[TabletEntry],
|
|
3482
|
+
me: ShardId,
|
|
3483
|
+
) -> Result<TabletEdits> {
|
|
3484
|
+
for e in entries {
|
|
3485
|
+
match catalog.tablet(e.id) {
|
|
3486
|
+
Some(t) if t.table == e.table && t.start == e.start && t.end == e.end => {}
|
|
3487
|
+
_ => {
|
|
3488
|
+
return Err(Error::TableNotFound(format!(
|
|
3489
|
+
"tablet {} changed during the split, merge or move",
|
|
3490
|
+
e.id.0
|
|
3491
|
+
)));
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
let table = entries[0].table;
|
|
3496
|
+
let families = catalog.family_ids_of(table);
|
|
3497
|
+
match kind {
|
|
3498
|
+
TabletOpKind::Move { tablet, to } => Ok((Vec::new(), vec![(*tablet, *to)])),
|
|
3499
|
+
TabletOpKind::Split {
|
|
3500
|
+
tablet,
|
|
3501
|
+
keys,
|
|
3502
|
+
owners,
|
|
3503
|
+
} => {
|
|
3504
|
+
let parent = &entries[0];
|
|
3505
|
+
let mut starts: Vec<Vec<u8>> = vec![parent.start.clone()];
|
|
3506
|
+
starts.extend(keys.iter().cloned());
|
|
3507
|
+
let mut ends: Vec<Option<Vec<u8>>> = keys.iter().cloned().map(Some).collect();
|
|
3508
|
+
ends.push(parent.end.clone());
|
|
3509
|
+
let mut edits = Vec::new();
|
|
3510
|
+
let mut out = Vec::new();
|
|
3511
|
+
for (i, (start, end)) in starts.into_iter().zip(ends).enumerate() {
|
|
3512
|
+
let id = catalog.alloc_tablet();
|
|
3513
|
+
let (s, e) = prefix_bounds(&start, end.as_deref())?;
|
|
3514
|
+
edits.push(Edit::PutTablet {
|
|
3515
|
+
tablet: id,
|
|
3516
|
+
table,
|
|
3517
|
+
start,
|
|
3518
|
+
end,
|
|
3519
|
+
});
|
|
3520
|
+
out.push((id, owners[i]));
|
|
3521
|
+
for &f in &families {
|
|
3522
|
+
if let Some(list) = catalog.ssts.get(&(parent.id, f)) {
|
|
3523
|
+
for (level, meta) in list.iter() {
|
|
3524
|
+
if overlaps(meta, &s, e.as_deref()) {
|
|
3525
|
+
edits.push(Edit::AddSst {
|
|
3526
|
+
tablet: id,
|
|
3527
|
+
family: f,
|
|
3528
|
+
level: *level,
|
|
3529
|
+
meta: (**meta).clone(),
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
if let Some(seqno) = catalog.flushed.get(&(parent.id, f)) {
|
|
3535
|
+
edits.push(Edit::SetFlushed {
|
|
3536
|
+
tablet: id,
|
|
3537
|
+
family: f,
|
|
3538
|
+
seqno: *seqno,
|
|
3539
|
+
});
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
edits.push(Edit::DropTablet { tablet: *tablet });
|
|
3544
|
+
Ok((edits, out))
|
|
3545
|
+
}
|
|
3546
|
+
TabletOpKind::Merge { left, right } => {
|
|
3547
|
+
let (l, r) = (&entries[0], &entries[1]);
|
|
3548
|
+
let ssts = merged_ssts(catalog, l, r)?;
|
|
3549
|
+
let id = catalog.alloc_tablet();
|
|
3550
|
+
let mut edits = vec![Edit::PutTablet {
|
|
3551
|
+
tablet: id,
|
|
3552
|
+
table,
|
|
3553
|
+
start: l.start.clone(),
|
|
3554
|
+
end: r.end.clone(),
|
|
3555
|
+
}];
|
|
3556
|
+
for (f, level, meta) in ssts {
|
|
3557
|
+
edits.push(Edit::AddSst {
|
|
3558
|
+
tablet: id,
|
|
3559
|
+
family: f,
|
|
3560
|
+
level,
|
|
3561
|
+
meta: (*meta).clone(),
|
|
3562
|
+
});
|
|
3563
|
+
}
|
|
3564
|
+
for &f in &families {
|
|
3565
|
+
let seqno = [left, right]
|
|
3566
|
+
.iter()
|
|
3567
|
+
.filter_map(|t| catalog.flushed.get(&(**t, f)).copied())
|
|
3568
|
+
.max();
|
|
3569
|
+
if let Some(seqno) = seqno {
|
|
3570
|
+
edits.push(Edit::SetFlushed {
|
|
3571
|
+
tablet: id,
|
|
3572
|
+
family: f,
|
|
3573
|
+
seqno,
|
|
3574
|
+
});
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
edits.push(Edit::DropTablet { tablet: *left });
|
|
3578
|
+
edits.push(Edit::DropTablet { tablet: *right });
|
|
3579
|
+
Ok((edits, vec![(id, me)]))
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
}
|