kenaz 0.3.3 → 0.3.6
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.
- package/core/.agents/skills/typesafe-ai/LICENSE +21 -21
- package/core/.agents/skills/typesafe-ai/SKILL.md +149 -149
- package/core/.claude/commands/Ansuz.md +15 -2
- package/core/.claude/hooks/__tests__/task-2395-project-registration.test.js +84 -0
- package/core/.claude/hooks/handoff-staleness.js +2 -2
- package/core/.claude/hooks/session-init.ps1 +49 -49
- package/core/.claude/hooks/turn-context.js +50 -2
- package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -26
- package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -97
- package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -101
- package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -31
- package/core/.claude/skills/ui-ux-pro-max/data/products.csv +96 -96
- package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -68
- package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +57 -57
- package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -253
- package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -106
- package/core/.collaboration/core/task-api.js +1 -4
- package/core/.formation/README.md +21 -146
- package/core/.formation/bifrost/codex-doctor.js +37 -2
- package/core/.formation/bifrost/codex-doctor.test.js +18 -0
- package/core/.formation/bifrost/launch.js +1 -1
- package/core/.formation/bifrost/openai/codex-hook-shim.js +6 -0
- package/core/.formation/bifrost/openai/codex-hook-shim.test.js +11 -1
- package/core/.formation/bifrost/setup-codex.js +3 -0
- package/core/.formation/native/claude/README.md +1 -1
- package/core/.formation/role-map.yaml +3 -3
- package/core/.formation/spec/task_operations.parity.test.js +0 -1
- package/core/.formation/spec/task_operations.yaml +3 -3
- package/core/.formation/tests/hello.txt +2 -2
- package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +4 -4
- package/core/.formation/tools/dashboard-models-chain.test.js +0 -6
- package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +1 -9
- package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +2 -2
- package/core/.formation/tools/dispatch.js +33 -554
- package/core/.formation/tools/mcp-allowlist.test.js +0 -1
- package/core/.formation/tools/post-complete/task2345.test.js +0 -5
- package/core/.formation/tools/pre-dispatch-snapshot.test.js +0 -100
- package/core/.formation/tools/prompt.js +33 -1623
- package/core/.formation/tools/prompt.test.js +26 -594
- package/core/.formation/unit/do.yaml +2 -4
- package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -163
- package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -393
- package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -102
- package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -412
- package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -144
- package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -86
- package/core/.kenaz/muninn_episode_write.ps1 +17 -17
- package/core/.kenaz/specialist/field_report_watermark.json +1 -1
- package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
- package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
- package/core/.kenaz/specialist/t1777_probe.txt +1 -1
- package/core/.matrix/index.matrix +5 -5
- package/core/.rule/general/agent-workflow.yaml +4 -4
- package/core/.rule/general/file-size-threshold.yaml +16 -4
- package/core/.rule/general/rust-verify-target.yaml +0 -1
- package/core/.rule/general/system-invariants.yaml +8 -25
- package/core/.specialist/Ansuz/SYSTEM_DOC.md +17 -42
- package/core/.specialist/Ansuz/capability.yaml +7 -14
- package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +4 -11
- package/core/.specialist/Ansuz/elixir.yaml +3 -4
- package/core/.specialist/Ansuz/identity_contract.yaml +1 -1
- package/core/.specialist/Ansuz/knowledge.yaml +7 -10
- package/core/.specialist/Ansuz/rule.yaml +2 -2
- package/core/.specialist/Ansuz/tools/verify-and-land.js +22 -0
- package/core/.specialist/Ansuz/tools/verify-and-land.test.js +59 -5
- package/core/.specialist/Ansuz/workflow.yaml +7 -11
- package/core/.specialist/Forseti/core.yaml +1 -1
- package/core/.specialist/Heimdall/SKILL.md +1 -1
- package/core/.specialist/Heimdall/core.yaml +2 -2
- package/core/.specialist/Huginn/rule.yaml +1 -1
- package/core/.specialist/tools/README.md +2 -2
- package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +0 -1
- package/core/.system/collaboration/README.md +1 -1
- package/core/.system/formation/LEGACY_SKILLS.md +2 -0
- package/core/.system/formation/POST_COMPLETE.md +2 -0
- package/core/.system/formation/README.md +2 -0
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -355
- package/core/dev/JEV_INTEGRATION_PLAN.md +189 -189
- package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -13
- package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -9
- package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -14
- package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -67
- package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -52
- package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -31
- package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -39
- package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -33
- package/core/dev/jev-eval/README.md +99 -99
- package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -58
- package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -32
- package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -48
- package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -52
- package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -47
- package/core/dev/pipeline/tasks/.gitkeep +1 -1
- package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -27
- package/core/dev/scripts/check-file-size.js +44 -17
- package/core/dev/scripts/init_git.sh +0 -0
- package/core/dev/scripts/init_project.sh +0 -0
- package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -536
- package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -1978
- package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -140
- package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -173
- package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -168
- package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -126
- package/core/dev/scripts/multi_agent_scheduler.sh +0 -0
- package/core/dev/scripts/run_once.sh +0 -0
- package/core/dev/scripts/setup_mcp.sh +0 -0
- package/core/dev/scripts/setup_rag.sh +0 -0
- package/core/dev/scripts/start_dev_agent.sh +0 -0
- package/core/dev/scripts/start_multi_agent.sh +0 -0
- package/core/dev/scripts/sync_task_queue.sh +0 -0
- package/core/k-cli/README.md +1 -94
- package/core/k-cli/test/TEST_SPEC.md +5 -22
- package/core/k-cli/test/run.sh +1 -67
- package/core/plugins/kenaz/commands/Amelia.md +1 -1
- package/core/plugins/kenaz/commands/Ansuz.md +16 -3
- package/core/plugins/kenaz/scripts/kenaz-mode.js +11 -1
- package/core/plugins/kenaz/scripts/sync.js +6 -2
- package/core/plugins/kenaz/scripts/ygg.js +13 -1
- package/core/plugins/kenaz/skills/product_manager/SKILL.md +12 -12
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -26
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -97
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -101
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -31
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +96 -96
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -68
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +57 -57
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -253
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -106
- package/lib/cli.js +11 -4
- package/package.json +11 -7
- package/vendor/install-kenaz-cli.js +18 -7
- package/core/.formation/legion/forge.yaml +0 -299
- package/core/.formation/legion/hive.yaml +0 -238
- package/core/.formation/squad/blitz.yaml +0 -192
- package/core/.formation/squad/strategy-tribunal.yaml +0 -354
- package/core/.formation/squad/strategy.yaml +0 -349
- package/core/.formation/squad/sweep.yaml +0 -208
- package/core/.formation/tools/broadcast.js +0 -103
- package/core/.formation/tools/dispatch-dryrun.test.js +0 -54
- package/core/.formation/tools/dispatch-notifier.js +0 -84
- package/core/.formation/tools/formations.js +0 -1203
- package/core/.formation/tools/headless-branch-recovery.test.js +0 -64
- package/core/.formation/tools/headless-dryrun.test.js +0 -79
- package/core/.formation/tools/headless-v2.js +0 -659
- package/core/.formation/tools/headless.js +0 -1071
- package/core/.formation/tools/legacy-scheduler.js +0 -1994
- package/core/.formation/tools/logger.js +0 -273
- package/core/.formation/tools/memory-loader.js +0 -422
- package/core/.formation/tools/process-state.js +0 -33
- package/core/.formation/tools/remote-conflict.js +0 -98
- package/core/.formation/tools/runner.js +0 -1332
- package/core/.formation/tools/task-utils.js +0 -344
- package/core/.formation/tools/task2028-headless-worktree.test.js +0 -112
- package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +0 -123
- package/core/.formation/tools/worktree-leak.test.js +0 -162
- package/core/k-cli/dist/alaya/run.js +0 -91
- package/core/k-cli/dist/core/output.js +0 -24
- package/core/k-cli/dist/core/process.js +0 -38
- package/core/k-cli/dist/core/project.js +0 -80
- package/core/k-cli/dist/core/spawn.js +0 -268
- package/core/k-cli/dist/core/types.js +0 -2
- package/core/k-cli/dist/dashboard/update.js +0 -53
- package/core/k-cli/dist/dashboard/version.js +0 -20
- package/core/k-cli/dist/dispatch/run.js +0 -241
- package/core/k-cli/dist/dispatch/status.js +0 -103
- package/core/k-cli/dist/index.js +0 -283
- package/core/k-cli/dist/project/initialize.js +0 -301
- package/core/k-cli/dist/project/install.js +0 -89
- package/core/k-cli/dist/project/status.js +0 -138
- package/core/k-cli/dist/specialist/list.js +0 -100
- package/core/k-cli/dist/specialist/run.js +0 -93
- package/core/k-cli/dist/task/create.js +0 -194
- package/core/k-cli/dist/task/delete.js +0 -98
- package/core/k-cli/dist/task/get.js +0 -55
- package/core/k-cli/dist/task/list.js +0 -58
- package/core/k-cli/dist/task/submit.js +0 -156
- package/core/k-cli/dist/ygg/run.js +0 -51
- package/core/k-cli/node_modules/.bin/yaml +0 -16
- package/core/k-cli/node_modules/.bin/yaml.cmd +0 -17
- package/core/k-cli/node_modules/.bin/yaml.ps1 +0 -28
- package/core/k-cli/node_modules/.package-lock.json +0 -23
- package/core/k-cli/node_modules/yaml/LICENSE +0 -13
- package/core/k-cli/node_modules/yaml/README.md +0 -172
- package/core/k-cli/node_modules/yaml/bin.mjs +0 -11
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-collection.js +0 -88
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -43
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-node.js +0 -109
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-scalar.js +0 -86
- package/core/k-cli/node_modules/yaml/browser/dist/compose/composer.js +0 -217
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -115
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +0 -198
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +0 -49
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-end.js +0 -37
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +0 -207
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +0 -223
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-props.js +0 -146
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-contains-newline.js +0 -34
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +0 -26
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +0 -15
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-map-includes.js +0 -13
- package/core/k-cli/node_modules/yaml/browser/dist/doc/Document.js +0 -335
- package/core/k-cli/node_modules/yaml/browser/dist/doc/anchors.js +0 -71
- package/core/k-cli/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -55
- package/core/k-cli/node_modules/yaml/browser/dist/doc/createNode.js +0 -88
- package/core/k-cli/node_modules/yaml/browser/dist/doc/directives.js +0 -176
- package/core/k-cli/node_modules/yaml/browser/dist/errors.js +0 -57
- package/core/k-cli/node_modules/yaml/browser/dist/index.js +0 -17
- package/core/k-cli/node_modules/yaml/browser/dist/log.js +0 -11
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Alias.js +0 -114
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Collection.js +0 -147
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Node.js +0 -38
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Pair.js +0 -36
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Scalar.js +0 -24
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLMap.js +0 -144
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +0 -113
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +0 -63
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/identity.js +0 -36
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/toJS.js +0 -37
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-scalar.js +0 -214
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-stringify.js +0 -61
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-visit.js +0 -97
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst.js +0 -98
- package/core/k-cli/node_modules/yaml/browser/dist/parse/lexer.js +0 -717
- package/core/k-cli/node_modules/yaml/browser/dist/parse/line-counter.js +0 -39
- package/core/k-cli/node_modules/yaml/browser/dist/parse/parser.js +0 -967
- package/core/k-cli/node_modules/yaml/browser/dist/public-api.js +0 -102
- package/core/k-cli/node_modules/yaml/browser/dist/schema/Schema.js +0 -37
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/map.js +0 -17
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/null.js +0 -15
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/seq.js +0 -17
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/string.js +0 -14
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/bool.js +0 -19
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/float.js +0 -43
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/int.js +0 -38
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/schema.js +0 -23
- package/core/k-cli/node_modules/yaml/browser/dist/schema/json/schema.js +0 -62
- package/core/k-cli/node_modules/yaml/browser/dist/schema/tags.js +0 -96
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +0 -58
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +0 -26
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +0 -46
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +0 -71
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -64
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +0 -74
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +0 -78
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -39
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +0 -93
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +0 -101
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +0 -146
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringify.js +0 -129
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +0 -153
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyComment.js +0 -20
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +0 -85
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +0 -24
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyPair.js +0 -150
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyString.js +0 -336
- package/core/k-cli/node_modules/yaml/browser/dist/util.js +0 -11
- package/core/k-cli/node_modules/yaml/browser/dist/visit.js +0 -233
- package/core/k-cli/node_modules/yaml/browser/index.js +0 -5
- package/core/k-cli/node_modules/yaml/browser/package.json +0 -3
- package/core/k-cli/node_modules/yaml/dist/cli.d.ts +0 -8
- package/core/k-cli/node_modules/yaml/dist/cli.mjs +0 -201
- package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.d.ts +0 -11
- package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.js +0 -90
- package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.d.ts +0 -7
- package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.js +0 -45
- package/core/k-cli/node_modules/yaml/dist/compose/compose-node.d.ts +0 -29
- package/core/k-cli/node_modules/yaml/dist/compose/compose-node.js +0 -112
- package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.d.ts +0 -5
- package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.js +0 -88
- package/core/k-cli/node_modules/yaml/dist/compose/composer.d.ts +0 -63
- package/core/k-cli/node_modules/yaml/dist/compose/composer.js +0 -222
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.d.ts +0 -6
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.js +0 -117
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +0 -11
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.js +0 -200
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +0 -6
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.js +0 -51
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.d.ts +0 -6
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.js +0 -39
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +0 -7
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.js +0 -209
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +0 -10
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.js +0 -225
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.d.ts +0 -23
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.js +0 -148
- package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.js +0 -36
- package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.js +0 -28
- package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +0 -3
- package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.js +0 -17
- package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.js +0 -15
- package/core/k-cli/node_modules/yaml/dist/doc/Document.d.ts +0 -141
- package/core/k-cli/node_modules/yaml/dist/doc/Document.js +0 -337
- package/core/k-cli/node_modules/yaml/dist/doc/anchors.d.ts +0 -24
- package/core/k-cli/node_modules/yaml/dist/doc/anchors.js +0 -76
- package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.d.ts +0 -9
- package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.js +0 -57
- package/core/k-cli/node_modules/yaml/dist/doc/createNode.d.ts +0 -17
- package/core/k-cli/node_modules/yaml/dist/doc/createNode.js +0 -90
- package/core/k-cli/node_modules/yaml/dist/doc/directives.d.ts +0 -49
- package/core/k-cli/node_modules/yaml/dist/doc/directives.js +0 -178
- package/core/k-cli/node_modules/yaml/dist/errors.d.ts +0 -21
- package/core/k-cli/node_modules/yaml/dist/errors.js +0 -62
- package/core/k-cli/node_modules/yaml/dist/index.d.ts +0 -25
- package/core/k-cli/node_modules/yaml/dist/index.js +0 -50
- package/core/k-cli/node_modules/yaml/dist/log.d.ts +0 -3
- package/core/k-cli/node_modules/yaml/dist/log.js +0 -19
- package/core/k-cli/node_modules/yaml/dist/nodes/Alias.d.ts +0 -29
- package/core/k-cli/node_modules/yaml/dist/nodes/Alias.js +0 -116
- package/core/k-cli/node_modules/yaml/dist/nodes/Collection.d.ts +0 -73
- package/core/k-cli/node_modules/yaml/dist/nodes/Collection.js +0 -151
- package/core/k-cli/node_modules/yaml/dist/nodes/Node.d.ts +0 -53
- package/core/k-cli/node_modules/yaml/dist/nodes/Node.js +0 -40
- package/core/k-cli/node_modules/yaml/dist/nodes/Pair.d.ts +0 -22
- package/core/k-cli/node_modules/yaml/dist/nodes/Pair.js +0 -39
- package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.d.ts +0 -43
- package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.js +0 -27
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.d.ts +0 -53
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.js +0 -147
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +0 -60
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.js +0 -115
- package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.js +0 -65
- package/core/k-cli/node_modules/yaml/dist/nodes/identity.d.ts +0 -23
- package/core/k-cli/node_modules/yaml/dist/nodes/identity.js +0 -53
- package/core/k-cli/node_modules/yaml/dist/nodes/toJS.d.ts +0 -29
- package/core/k-cli/node_modules/yaml/dist/nodes/toJS.js +0 -39
- package/core/k-cli/node_modules/yaml/dist/options.d.ts +0 -350
- package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.d.ts +0 -64
- package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.js +0 -218
- package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.d.ts +0 -8
- package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.js +0 -63
- package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.d.ts +0 -39
- package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.js +0 -99
- package/core/k-cli/node_modules/yaml/dist/parse/cst.d.ts +0 -109
- package/core/k-cli/node_modules/yaml/dist/parse/cst.js +0 -112
- package/core/k-cli/node_modules/yaml/dist/parse/lexer.d.ts +0 -87
- package/core/k-cli/node_modules/yaml/dist/parse/lexer.js +0 -719
- package/core/k-cli/node_modules/yaml/dist/parse/line-counter.d.ts +0 -22
- package/core/k-cli/node_modules/yaml/dist/parse/line-counter.js +0 -41
- package/core/k-cli/node_modules/yaml/dist/parse/parser.d.ts +0 -84
- package/core/k-cli/node_modules/yaml/dist/parse/parser.js +0 -972
- package/core/k-cli/node_modules/yaml/dist/public-api.d.ts +0 -44
- package/core/k-cli/node_modules/yaml/dist/public-api.js +0 -107
- package/core/k-cli/node_modules/yaml/dist/schema/Schema.d.ts +0 -17
- package/core/k-cli/node_modules/yaml/dist/schema/Schema.js +0 -39
- package/core/k-cli/node_modules/yaml/dist/schema/common/map.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/common/map.js +0 -19
- package/core/k-cli/node_modules/yaml/dist/schema/common/null.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/common/null.js +0 -17
- package/core/k-cli/node_modules/yaml/dist/schema/common/seq.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/common/seq.js +0 -19
- package/core/k-cli/node_modules/yaml/dist/schema/common/string.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/common/string.js +0 -16
- package/core/k-cli/node_modules/yaml/dist/schema/core/bool.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/core/bool.js +0 -21
- package/core/k-cli/node_modules/yaml/dist/schema/core/float.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/core/float.js +0 -47
- package/core/k-cli/node_modules/yaml/dist/schema/core/int.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/core/int.js +0 -42
- package/core/k-cli/node_modules/yaml/dist/schema/core/schema.d.ts +0 -1
- package/core/k-cli/node_modules/yaml/dist/schema/core/schema.js +0 -25
- package/core/k-cli/node_modules/yaml/dist/schema/json/schema.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/json/schema.js +0 -64
- package/core/k-cli/node_modules/yaml/dist/schema/json-schema.d.ts +0 -69
- package/core/k-cli/node_modules/yaml/dist/schema/tags.d.ts +0 -48
- package/core/k-cli/node_modules/yaml/dist/schema/tags.js +0 -99
- package/core/k-cli/node_modules/yaml/dist/schema/types.d.ts +0 -92
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.js +0 -70
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +0 -7
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.js +0 -29
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.js +0 -50
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +0 -5
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.js +0 -76
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -68
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +0 -22
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.js +0 -77
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +0 -10
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +0 -82
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +0 -1
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -41
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +0 -28
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.js +0 -96
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +0 -6
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +0 -105
- package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +0 -34
- package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.js +0 -151
- package/core/k-cli/node_modules/yaml/dist/stringify/stringify.d.ts +0 -21
- package/core/k-cli/node_modules/yaml/dist/stringify/stringify.js +0 -132
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +0 -17
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.js +0 -155
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.d.ts +0 -10
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.js +0 -24
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.js +0 -87
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.js +0 -26
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.d.ts +0 -3
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.js +0 -152
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.d.ts +0 -9
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.js +0 -338
- package/core/k-cli/node_modules/yaml/dist/test-events.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/test-events.js +0 -134
- package/core/k-cli/node_modules/yaml/dist/util.d.ts +0 -16
- package/core/k-cli/node_modules/yaml/dist/util.js +0 -28
- package/core/k-cli/node_modules/yaml/dist/visit.d.ts +0 -102
- package/core/k-cli/node_modules/yaml/dist/visit.js +0 -236
- package/core/k-cli/node_modules/yaml/package.json +0 -97
- package/core/k-cli/node_modules/yaml/util.js +0 -2
|
@@ -1,393 +1,393 @@
|
|
|
1
|
-
diff --git a/src/include/storage/shadow_file.h b/src/include/storage/shadow_file.h
|
|
2
|
-
index 6b186b167..a968b3af6 100644
|
|
3
|
-
--- a/src/include/storage/shadow_file.h
|
|
4
|
-
+++ b/src/include/storage/shadow_file.h
|
|
5
|
-
@@ -1,5 +1,9 @@
|
|
6
|
-
#pragma once
|
|
7
|
-
|
|
8
|
-
+#include <atomic>
|
|
9
|
-
+#include <functional>
|
|
10
|
-
+#include <shared_mutex>
|
|
11
|
-
+
|
|
12
|
-
#include "common/types/uuid.h"
|
|
13
|
-
#include "storage/file_handle.h"
|
|
14
|
-
|
|
15
|
-
@@ -24,11 +28,17 @@ struct ShadowFileHeader {
|
|
16
|
-
static_assert(std::is_trivially_copyable_v<ShadowFileHeader>);
|
|
17
|
-
|
|
18
|
-
class BufferManager;
|
|
19
|
-
-// NOTE: This class is NOT thread-safe for now, as we are not checkpointing in parallel yet.
|
|
20
|
-
+// NOTE: Only the checkpointing thread may create, publish or clear shadow pages. Read
|
|
21
|
-
+// transactions running concurrently with a checkpoint may only use readShadowVersionIfExists.
|
|
22
|
-
class ShadowFile {
|
|
23
|
-
public:
|
|
24
|
-
ShadowFile(BufferManager& bm, common::VirtualFileSystem* vfs, const std::string& databasePath);
|
|
25
|
-
|
|
26
|
-
+ // Mutex protocol: hasShadowPage, getShadowPage, clearShadowPage, createShadowPage and
|
|
27
|
-
+ // publishShadowPage are for the checkpointing thread only. It is the only writer of
|
|
28
|
-
+ // shadowPagesMap, so it may read the map without taking mtx; the functions that modify the
|
|
29
|
-
+ // map take mtx exclusively. Read transactions running concurrently with a checkpoint must not
|
|
30
|
-
+ // call these and must use readShadowVersionIfExists, which takes mtx shared.
|
|
31
|
-
// TODO(Guodong): Remove originalFile param.
|
|
32
|
-
bool hasShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage) const {
|
|
33
|
-
return shadowPagesMap.contains(originalFile) &&
|
|
34
|
-
@@ -37,8 +47,21 @@ class ShadowFile {
|
|
35
|
-
void clearShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage);
|
|
36
|
-
common::page_idx_t getShadowPage(common::file_idx_t originalFile,
|
|
37
|
-
common::page_idx_t originalPage) const;
|
|
38
|
-
- common::page_idx_t getOrCreateShadowPage(common::file_idx_t originalFile,
|
|
39
|
-
+ // Adds a new shadow page for originalPage. The page is not visible to
|
|
40
|
-
+ // readShadowVersionIfExists (nor hasShadowPage) until publishShadowPage is called, so the
|
|
41
|
-
+ // caller can fill it with the contents of the original page first. Must not be called while
|
|
42
|
-
+ // a published shadow page of a column data page is pinned (see mtx).
|
|
43
|
-
+ common::page_idx_t createShadowPage(common::file_idx_t originalFile,
|
|
44
|
-
common::page_idx_t originalPage);
|
|
45
|
-
+ void publishShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage,
|
|
46
|
-
+ common::page_idx_t shadowPageIdx);
|
|
47
|
-
+ // Reads the shadow version of a page if the current checkpoint has one, and returns false
|
|
48
|
-
+ // without calling readOp otherwise. Unlike the functions above, this is safe to call from
|
|
49
|
-
+ // read transactions running concurrently with a checkpoint: a checkpoint publishes the new
|
|
50
|
-
+ // metadata of in-place updated column chunks before the shadow pages are applied to the data
|
|
51
|
-
+ // file, so those readers must see the shadow version of such pages.
|
|
52
|
-
+ bool readShadowVersionIfExists(common::file_idx_t originalFile, common::page_idx_t originalPage,
|
|
53
|
-
+ const std::function<void(uint8_t*)>& readOp) const;
|
|
54
|
-
|
|
55
|
-
FileHandle& getShadowingFH() const { return *shadowingFH; }
|
|
56
|
-
|
|
57
|
-
@@ -83,6 +106,15 @@ class ShadowFile {
|
|
58
|
-
std::unordered_map<common::page_idx_t, common::page_idx_t>>
|
|
59
|
-
shadowPagesMap;
|
|
60
|
-
std::vector<ShadowPageRecord> shadowPageRecords;
|
|
61
|
-
+ // Protects shadowPagesMap against concurrent readers (see readShadowVersionIfExists). Only
|
|
62
|
-
+ // the checkpointing thread modifies the map.
|
|
63
|
-
+ // Readers hold mtx shared while spinning in optimisticRead on a locked shadow page of a page
|
|
64
|
-
+ // they reach through ColumnReadWriter::readFromPage (column data pages). The checkpointing
|
|
65
|
-
+ // thread therefore must not hold such a published shadow page pinned while it takes mtx
|
|
66
|
-
+ // exclusively (create, publish, clear or reset), or it would deadlock with those readers.
|
|
67
|
-
+ mutable std::shared_mutex mtx;
|
|
68
|
-
+ // Fast path for readers: false when there is no shadow page.
|
|
69
|
-
+ std::atomic<bool> hasShadowPages{false};
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
} // namespace storage
|
|
73
|
-
diff --git a/src/storage/shadow_file.cpp b/src/storage/shadow_file.cpp
|
|
74
|
-
index 9811166c6..8984d1eb8 100644
|
|
75
|
-
--- a/src/storage/shadow_file.cpp
|
|
76
|
-
+++ b/src/storage/shadow_file.cpp
|
|
77
|
-
@@ -45,6 +45,7 @@ void ShadowFile::setDatabasePath(const std::string& databasePath_) {
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
void ShadowFile::clearShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
|
|
81
|
-
+ std::unique_lock lck{mtx};
|
|
82
|
-
if (hasShadowPage(originalFile, originalPage)) {
|
|
83
|
-
shadowPagesMap.at(originalFile).erase(originalPage);
|
|
84
|
-
if (shadowPagesMap.at(originalFile).empty()) {
|
|
85
|
-
@@ -53,20 +54,44 @@ void ShadowFile::clearShadowPage(file_idx_t originalFile, page_idx_t originalPag
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
-page_idx_t ShadowFile::getOrCreateShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
|
|
90
|
-
- if (hasShadowPage(originalFile, originalPage)) {
|
|
91
|
-
- return shadowPagesMap[originalFile][originalPage];
|
|
92
|
-
- }
|
|
93
|
-
+page_idx_t ShadowFile::createShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
|
|
94
|
-
+ DASSERT(!hasShadowPage(originalFile, originalPage));
|
|
95
|
-
+ std::unique_lock lck{mtx};
|
|
96
|
-
const auto shadowPageIdx = getOrCreateShadowingFH()->addNewPage();
|
|
97
|
-
- shadowPagesMap[originalFile][originalPage] = shadowPageIdx;
|
|
98
|
-
+ // Records must stay in the same order as the shadow pages (see applyShadowPages). Readers
|
|
99
|
-
+ // only use shadowPagesMap, so the page is not visible to them until it is published.
|
|
100
|
-
shadowPageRecords.push_back({originalFile, originalPage});
|
|
101
|
-
return shadowPageIdx;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
+void ShadowFile::publishShadowPage(file_idx_t originalFile, page_idx_t originalPage,
|
|
105
|
-
+ page_idx_t shadowPageIdx) {
|
|
106
|
-
+ std::unique_lock lck{mtx};
|
|
107
|
-
+ shadowPagesMap[originalFile][originalPage] = shadowPageIdx;
|
|
108
|
-
+ // Pairs with the acquire load in readShadowVersionIfExists.
|
|
109
|
-
+ hasShadowPages.store(true, std::memory_order_release);
|
|
110
|
-
+}
|
|
111
|
-
+
|
|
112
|
-
page_idx_t ShadowFile::getShadowPage(file_idx_t originalFile, page_idx_t originalPage) const {
|
|
113
|
-
DASSERT(hasShadowPage(originalFile, originalPage));
|
|
114
|
-
return shadowPagesMap.at(originalFile).at(originalPage);
|
|
115
|
-
}
|
|
116
|
-
+
|
|
117
|
-
+bool ShadowFile::readShadowVersionIfExists(file_idx_t originalFile, page_idx_t originalPage,
|
|
118
|
-
+ const std::function<void(uint8_t*)>& readOp) const {
|
|
119
|
-
+ if (!hasShadowPages.load(std::memory_order_acquire)) {
|
|
120
|
-
+ return false;
|
|
121
|
-
+ }
|
|
122
|
-
+ std::shared_lock lck{mtx};
|
|
123
|
-
+ if (!hasShadowPage(originalFile, originalPage)) {
|
|
124
|
-
+ return false;
|
|
125
|
-
+ }
|
|
126
|
-
+ // Optimistic reads let concurrent readers share the page and retry if the checkpointer is
|
|
127
|
-
+ // updating it at the same time.
|
|
128
|
-
+ shadowingFH->optimisticReadPage(shadowPagesMap.at(originalFile).at(originalPage), readOp);
|
|
129
|
-
+ return true;
|
|
130
|
-
+}
|
|
131
|
-
+
|
|
132
|
-
void ShadowFile::applyShadowPages(StorageManager& storageManager, ClientContext& context) const {
|
|
133
|
-
const auto pageBuffer = std::make_unique<uint8_t[]>(LBUG_PAGE_SIZE);
|
|
134
|
-
page_idx_t shadowPageIdx = 1; // Skip header page.
|
|
135
|
-
@@ -191,6 +216,8 @@ void ShadowFile::flushAll(main::ClientContext& context) const {
|
|
136
|
-
|
|
137
|
-
void ShadowFile::clear(BufferManager& bm) {
|
|
138
|
-
DASSERT(shadowingFH);
|
|
139
|
-
+ // Concurrent readers may be reading shadow pages (see readShadowVersionIfExists).
|
|
140
|
-
+ std::unique_lock lck{mtx};
|
|
141
|
-
// Evict pages, truncate to zero pages (which also truncates the on-disk file), then unlink
|
|
142
|
-
// the path and drop the stale fd so no leftover .shadow file exists at rest (a present
|
|
143
|
-
// .shadow is treated as "checkpoint in progress" by recovery/startup probes). The
|
|
144
|
-
@@ -203,6 +230,7 @@ void ShadowFile::clear(BufferManager& bm) {
|
|
145
|
-
vfs->removeFileIfExists(shadowFilePath);
|
|
146
|
-
shadowPagesMap.clear();
|
|
147
|
-
shadowPageRecords.clear();
|
|
148
|
-
+ hasShadowPages.store(false, std::memory_order_relaxed);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
void ShadowFile::reset() {
|
|
152
|
-
@@ -218,6 +246,7 @@ void ShadowFile::reset() {
|
|
153
|
-
if (shadowingFH == nullptr) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
+ std::unique_lock lck{mtx};
|
|
157
|
-
// If clear() already ran (the normal checkpoint flow calls both), the file was truncated,
|
|
158
|
-
// unlinked and the fd dropped; just make sure the in-memory maps are empty.
|
|
159
|
-
if (shadowingFH->getFileInfo() != nullptr) {
|
|
160
|
-
@@ -228,6 +257,7 @@ void ShadowFile::reset() {
|
|
161
|
-
}
|
|
162
|
-
shadowPagesMap.clear();
|
|
163
|
-
shadowPageRecords.clear();
|
|
164
|
-
+ hasShadowPages.store(false, std::memory_order_relaxed);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
FileHandle* ShadowFile::getOrCreateShadowingFH() {
|
|
168
|
-
diff --git a/src/storage/shadow_utils.cpp b/src/storage/shadow_utils.cpp
|
|
169
|
-
index 572990231..57538f3ff 100644
|
|
170
|
-
--- a/src/storage/shadow_utils.cpp
|
|
171
|
-
+++ b/src/storage/shadow_utils.cpp
|
|
172
|
-
@@ -12,14 +12,16 @@ namespace storage {
|
|
173
|
-
ShadowPageAndFrame ShadowUtils::createShadowVersionIfNecessaryAndPinPage(page_idx_t originalPage,
|
|
174
|
-
bool skipReadingOriginalPage, FileHandle& fileHandle, ShadowFile& shadowFile) {
|
|
175
|
-
DASSERT(!fileHandle.isInMemoryMode());
|
|
176
|
-
- const auto hasShadowPage = shadowFile.hasShadowPage(fileHandle.getFileIndex(), originalPage);
|
|
177
|
-
- auto shadowPage = shadowFile.getOrCreateShadowPage(fileHandle.getFileIndex(), originalPage);
|
|
178
|
-
+ const auto fileIdx = fileHandle.getFileIndex();
|
|
179
|
-
+ page_idx_t shadowPage = INVALID_PAGE_IDX;
|
|
180
|
-
uint8_t* shadowFrame = nullptr;
|
|
181
|
-
try {
|
|
182
|
-
- if (hasShadowPage) {
|
|
183
|
-
+ if (shadowFile.hasShadowPage(fileIdx, originalPage)) {
|
|
184
|
-
+ shadowPage = shadowFile.getShadowPage(fileIdx, originalPage);
|
|
185
|
-
shadowFrame =
|
|
186
|
-
shadowFile.getShadowingFH().pinPage(shadowPage, PageReadPolicy::READ_PAGE);
|
|
187
|
-
} else {
|
|
188
|
-
+ shadowPage = shadowFile.createShadowPage(fileIdx, originalPage);
|
|
189
|
-
shadowFrame =
|
|
190
|
-
shadowFile.getShadowingFH().pinPage(shadowPage, PageReadPolicy::DONT_READ_PAGE);
|
|
191
|
-
if (!skipReadingOriginalPage) {
|
|
192
|
-
@@ -27,6 +29,11 @@ ShadowPageAndFrame ShadowUtils::createShadowVersionIfNecessaryAndPinPage(page_id
|
|
193
|
-
memcpy(shadowFrame, frame, LBUG_PAGE_SIZE);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
+ // Publish the shadow page only once it holds the original contents, since read
|
|
197
|
-
+ // transactions may read shadow pages concurrently (see
|
|
198
|
-
+ // ShadowFile::readShadowVersionIfExists). The page stays locked until it is unpinned,
|
|
199
|
-
+ // so concurrent optimistic readers wait for the caller's update to finish.
|
|
200
|
-
+ shadowFile.publishShadowPage(fileIdx, originalPage, shadowPage);
|
|
201
|
-
}
|
|
202
|
-
// The shadow page existing already does not mean that it's already dirty
|
|
203
|
-
// It may have been flushed to disk to free memory and then read again
|
|
204
|
-
diff --git a/src/storage/table/column_reader_writer.cpp b/src/storage/table/column_reader_writer.cpp
|
|
205
|
-
index fb71533d3..3e768de2f 100644
|
|
206
|
-
--- a/src/storage/table/column_reader_writer.cpp
|
|
207
|
-
+++ b/src/storage/table/column_reader_writer.cpp
|
|
208
|
-
@@ -8,6 +8,7 @@
|
|
209
|
-
#include "common/vector/value_vector.h"
|
|
210
|
-
#include "storage/compression/float_compression.h"
|
|
211
|
-
#include "storage/file_handle.h"
|
|
212
|
-
+#include "storage/shadow_file.h"
|
|
213
|
-
#include "storage/shadow_utils.h"
|
|
214
|
-
#include "storage/storage_utils.h"
|
|
215
|
-
#include "storage/table/column_chunk_data.h"
|
|
216
|
-
@@ -462,6 +463,12 @@ void ColumnReadWriter::readFromPage(page_idx_t pageIdx,
|
|
217
|
-
if (pageIdx == INVALID_PAGE_IDX) {
|
|
218
|
-
return readFunc(nullptr);
|
|
219
|
-
}
|
|
220
|
-
+ // A checkpoint in progress may already have published metadata which refers to in-place
|
|
221
|
-
+ // updates that so far only exist in shadow pages.
|
|
222
|
-
+ if (shadowFile &&
|
|
223
|
-
+ shadowFile->readShadowVersionIfExists(dataFH->getFileIndex(), pageIdx, readFunc)) {
|
|
224
|
-
+ return;
|
|
225
|
-
+ }
|
|
226
|
-
dataFH->optimisticReadPage(pageIdx, readFunc);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
diff --git a/src/transaction/transaction_manager.cpp b/src/transaction/transaction_manager.cpp
|
|
230
|
-
index 32e155213..48cbd8a2a 100644
|
|
231
|
-
--- a/src/transaction/transaction_manager.cpp
|
|
232
|
-
+++ b/src/transaction/transaction_manager.cpp
|
|
233
|
-
@@ -303,8 +303,10 @@ void TransactionManager::tryCheckpoint(main::ClientContext& clientContext) {
|
|
234
|
-
void TransactionManager::checkpointNoLock(main::ClientContext& clientContext) {
|
|
235
|
-
QueryProgressScope progress{clientContext, 0.01};
|
|
236
|
-
// We only need to wait for active write transactions to leave the system before
|
|
237
|
-
- // checkpointing. Read transactions can continue safely because they use MVCC snapshot
|
|
238
|
-
- // isolation and shadow pages are applied with per-page locking.
|
|
239
|
-
+ // checkpointing. Read transactions can continue because they use MVCC snapshot isolation.
|
|
240
|
-
+ // Column chunk metadata of in-place checkpointed data is published before the shadow pages
|
|
241
|
-
+ // are applied, so column reads resolve such in-flight pages through the shadow file (see
|
|
242
|
-
+ // ColumnReadWriter::readFromPage); applying shadow pages uses per-page locking.
|
|
243
|
-
UniqLock writeGate;
|
|
244
|
-
try {
|
|
245
|
-
writeGate = stopNewWriteTransactionsAndWaitUntilAllWriteTransactionsLeave();
|
|
246
|
-
diff --git a/test/transaction/checkpoint_test.cpp b/test/transaction/checkpoint_test.cpp
|
|
247
|
-
index 0a9899dca..277756061 100644
|
|
248
|
-
--- a/test/transaction/checkpoint_test.cpp
|
|
249
|
-
+++ b/test/transaction/checkpoint_test.cpp
|
|
250
|
-
@@ -2,14 +2,20 @@
|
|
251
|
-
#include <chrono>
|
|
252
|
-
#include <cstdlib>
|
|
253
|
-
#include <fstream>
|
|
254
|
-
+#include <functional>
|
|
255
|
-
#include <future>
|
|
256
|
-
#include <mutex>
|
|
257
|
-
#include <thread>
|
|
258
|
-
+#include <unordered_set>
|
|
259
|
-
|
|
260
|
-
#include "api_test/private_api_test.h"
|
|
261
|
-
+#include "catalog/catalog.h"
|
|
262
|
-
+#include "catalog/catalog_entry/table_catalog_entry.h"
|
|
263
|
-
#include "common/exception/runtime.h"
|
|
264
|
-
#include "storage/checkpointer.h"
|
|
265
|
-
#include "storage/storage_manager.h"
|
|
266
|
-
+#include "storage/table/node_table.h"
|
|
267
|
-
+#include "storage/table/string_chunk_data.h"
|
|
268
|
-
#include "storage/wal/wal.h"
|
|
269
|
-
#include "test_env.h"
|
|
270
|
-
#include "transaction/transaction_manager.h"
|
|
271
|
-
@@ -308,6 +314,122 @@ TEST_F(FlakyCheckpointerTest, ShadowFileDatabaseIDMismatchCorruptedDB) {
|
|
272
|
-
EXPECT_THROW(createDBAndConn(), InternalException);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
+// A checkpoint publishes the new chunk metadata of a node group during its storage phase, but
|
|
276
|
-
+// in-place updates to existing pages are only written to the shadow file until the shadow pages
|
|
277
|
-
+// are applied at the end of the checkpoint. Read-only transactions are not blocked by a
|
|
278
|
-
+// checkpoint, so a scan that starts in between must not observe the new metadata together with
|
|
279
|
-
+// the old page contents (e.g. appended string dictionary offsets that are not on disk yet).
|
|
280
|
-
+class CheckpointerWithReadBeforeApplyingShadowPages final : public Checkpointer {
|
|
281
|
-
+public:
|
|
282
|
-
+ CheckpointerWithReadBeforeApplyingShadowPages(main::ClientContext& clientContext,
|
|
283
|
-
+ std::function<void()> readFunc)
|
|
284
|
-
+ : Checkpointer(clientContext), readFunc(std::move(readFunc)) {}
|
|
285
|
-
+
|
|
286
|
-
+ void logCheckpointAndApplyShadowPages(bool walRotated) override {
|
|
287
|
-
+ readFunc();
|
|
288
|
-
+ Checkpointer::logCheckpointAndApplyShadowPages(walRotated);
|
|
289
|
-
+ }
|
|
290
|
-
+
|
|
291
|
-
+private:
|
|
292
|
-
+ std::function<void()> readFunc;
|
|
293
|
-
+};
|
|
294
|
-
+
|
|
295
|
-
+#ifndef __SINGLE_THREADED__
|
|
296
|
-
+TEST_F(FlakyCheckpointerTest, ReadBeforeShadowPagesAreAppliedSeesCommittedStrings) {
|
|
297
|
-
+ if (inMemMode || systemConfig->checkpointThreshold == 0) {
|
|
298
|
-
+ GTEST_SKIP();
|
|
299
|
-
+ }
|
|
300
|
-
+ conn->query("CALL force_checkpoint_on_close=false;");
|
|
301
|
-
+ conn->query("CALL auto_checkpoint=false;");
|
|
302
|
-
+ ASSERT_TRUE(
|
|
303
|
-
+ conn->query("CREATE NODE TABLE test(id INT64 PRIMARY KEY, name STRING);")->isSuccess());
|
|
304
|
-
+ // Strings longer than the inline limit, few enough that the appended values below fit in
|
|
305
|
-
+ // the pages already allocated to the dictionary, so the checkpoint updates them in place.
|
|
306
|
-
+ auto getName = [](uint64_t i) { return std::format("persistent string value {:04}", i); };
|
|
307
|
-
+ auto insertRows = [&](uint64_t start, uint64_t end) {
|
|
308
|
-
+ for (auto i = start; i < end; i++) {
|
|
309
|
-
+ auto res =
|
|
310
|
-
+ conn->query(std::format("CREATE (:test {{id: {}, name: '{}'}});", i, getName(i)));
|
|
311
|
-
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
312
|
-
+ }
|
|
313
|
-
+ };
|
|
314
|
-
+ constexpr uint64_t numInitialRows = 20;
|
|
315
|
-
+ constexpr uint64_t numRows = 25;
|
|
316
|
-
+ insertRows(0, numInitialRows);
|
|
317
|
-
+ ASSERT_TRUE(conn->query("CHECKPOINT;")->isSuccess());
|
|
318
|
-
+ insertRows(numInitialRows, numRows);
|
|
319
|
-
+
|
|
320
|
-
+ // Pages of the persistent `name` column chunk (dictionary offsets, string data and indices).
|
|
321
|
-
+ // An in-place checkpoint of the column updates these pages through the shadow file, while an
|
|
322
|
-
+ // out-of-place checkpoint writes new pages and leaves these untouched.
|
|
323
|
-
+ auto context = getClientContext(*conn);
|
|
324
|
-
+ auto storageManager = StorageManager::Get(*context);
|
|
325
|
-
+ const auto tableEntry = catalog::Catalog::Get(*context)->getTableCatalogEntry(
|
|
326
|
-
+ &DUMMY_CHECKPOINT_TRANSACTION, "test");
|
|
327
|
-
+ auto& nodeTable = storageManager->getTable(tableEntry->getTableID())->cast<NodeTable>();
|
|
328
|
-
+ const auto* persistentGroup = nodeTable.getNodeGroup(0)->getChunkedNodeGroup(0);
|
|
329
|
-
+ ASSERT_EQ(persistentGroup->getResidencyState(), ResidencyState::ON_DISK);
|
|
330
|
-
+ std::unordered_set<page_idx_t> namePages;
|
|
331
|
-
+ for (const auto* segment :
|
|
332
|
-
+ persistentGroup->getColumnChunk(tableEntry->getColumnID("name")).getSegments()) {
|
|
333
|
-
+ const auto& stringChunk = segment->cast<StringChunkData>();
|
|
334
|
-
+ for (const auto* chunk :
|
|
335
|
-
+ std::initializer_list<const ColumnChunkData*>{stringChunk.getIndexColumnChunk(),
|
|
336
|
-
+ stringChunk.getDictionaryChunk().getOffsetChunk(),
|
|
337
|
-
+ stringChunk.getDictionaryChunk().getStringDataChunk()}) {
|
|
338
|
-
+ const auto& metadata = chunk->getMetadata();
|
|
339
|
-
+ for (auto i = 0u; i < metadata.getNumPages(); i++) {
|
|
340
|
-
+ namePages.insert(metadata.getStartPageIdx() + i);
|
|
341
|
-
+ }
|
|
342
|
-
+ }
|
|
343
|
-
+ }
|
|
344
|
-
+ ASSERT_FALSE(namePages.empty());
|
|
345
|
-
+
|
|
346
|
-
+ bool readRan = false;
|
|
347
|
-
+ uint64_t numShadowedNamePages = 0;
|
|
348
|
-
+ std::string readError;
|
|
349
|
-
+ std::vector<std::string> readNames;
|
|
350
|
-
+ auto readFunc = [&]() {
|
|
351
|
-
+ // Make sure the `name` column was checkpointed in place, i.e. its updated pages are still
|
|
352
|
-
+ // pending in the shadow file; otherwise the read below would not read shadow pages.
|
|
353
|
-
+ auto& shadowFile = storageManager->getShadowFile();
|
|
354
|
-
+ for (const auto pageIdx : namePages) {
|
|
355
|
-
+ if (shadowFile.hasShadowPage(storageManager->getDataFH()->getFileIndex(), pageIdx)) {
|
|
356
|
-
+ numShadowedNamePages++;
|
|
357
|
-
+ }
|
|
358
|
-
+ }
|
|
359
|
-
+ // Run on a separate thread with its own connection, like a concurrent reader would.
|
|
360
|
-
+ std::thread reader([&]() {
|
|
361
|
-
+ auto readConn = std::make_unique<main::Connection>(database.get());
|
|
362
|
-
+ auto res = readConn->query("MATCH (t:test) RETURN t.name ORDER BY t.id;");
|
|
363
|
-
+ readRan = true;
|
|
364
|
-
+ if (!res->isSuccess()) {
|
|
365
|
-
+ readError = res->getErrorMessage();
|
|
366
|
-
+ return;
|
|
367
|
-
+ }
|
|
368
|
-
+ while (res->hasNext()) {
|
|
369
|
-
+ readNames.push_back(res->getNext()->getValue(0)->getValue<std::string>());
|
|
370
|
-
+ }
|
|
371
|
-
+ });
|
|
372
|
-
+ reader.join();
|
|
373
|
-
+ };
|
|
374
|
-
+ FlakyCheckpointer checkpointer([&](main::ClientContext& clientContext) {
|
|
375
|
-
+ return std::make_unique<CheckpointerWithReadBeforeApplyingShadowPages>(clientContext,
|
|
376
|
-
+ readFunc);
|
|
377
|
-
+ });
|
|
378
|
-
+ checkpointer.setCheckpointer(*context);
|
|
379
|
-
+ auto res = conn->query("CHECKPOINT;");
|
|
380
|
-
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
381
|
-
+ ASSERT_TRUE(readRan);
|
|
382
|
-
+ ASSERT_GT(numShadowedNamePages, 0u);
|
|
383
|
-
+ ASSERT_TRUE(readError.empty()) << readError;
|
|
384
|
-
+ ASSERT_EQ(readNames.size(), numRows);
|
|
385
|
-
+ for (auto i = 0u; i < numRows; i++) {
|
|
386
|
-
+ EXPECT_EQ(readNames[i], getName(i));
|
|
387
|
-
+ }
|
|
388
|
-
+}
|
|
389
|
-
+#endif // __SINGLE_THREADED__
|
|
390
|
-
+
|
|
391
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
392
|
-
// ReviewFixesTest
|
|
393
|
-
// Targeted regression tests for the three fixes made in response to adsharma's
|
|
1
|
+
diff --git a/src/include/storage/shadow_file.h b/src/include/storage/shadow_file.h
|
|
2
|
+
index 6b186b167..a968b3af6 100644
|
|
3
|
+
--- a/src/include/storage/shadow_file.h
|
|
4
|
+
+++ b/src/include/storage/shadow_file.h
|
|
5
|
+
@@ -1,5 +1,9 @@
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
+#include <atomic>
|
|
9
|
+
+#include <functional>
|
|
10
|
+
+#include <shared_mutex>
|
|
11
|
+
+
|
|
12
|
+
#include "common/types/uuid.h"
|
|
13
|
+
#include "storage/file_handle.h"
|
|
14
|
+
|
|
15
|
+
@@ -24,11 +28,17 @@ struct ShadowFileHeader {
|
|
16
|
+
static_assert(std::is_trivially_copyable_v<ShadowFileHeader>);
|
|
17
|
+
|
|
18
|
+
class BufferManager;
|
|
19
|
+
-// NOTE: This class is NOT thread-safe for now, as we are not checkpointing in parallel yet.
|
|
20
|
+
+// NOTE: Only the checkpointing thread may create, publish or clear shadow pages. Read
|
|
21
|
+
+// transactions running concurrently with a checkpoint may only use readShadowVersionIfExists.
|
|
22
|
+
class ShadowFile {
|
|
23
|
+
public:
|
|
24
|
+
ShadowFile(BufferManager& bm, common::VirtualFileSystem* vfs, const std::string& databasePath);
|
|
25
|
+
|
|
26
|
+
+ // Mutex protocol: hasShadowPage, getShadowPage, clearShadowPage, createShadowPage and
|
|
27
|
+
+ // publishShadowPage are for the checkpointing thread only. It is the only writer of
|
|
28
|
+
+ // shadowPagesMap, so it may read the map without taking mtx; the functions that modify the
|
|
29
|
+
+ // map take mtx exclusively. Read transactions running concurrently with a checkpoint must not
|
|
30
|
+
+ // call these and must use readShadowVersionIfExists, which takes mtx shared.
|
|
31
|
+
// TODO(Guodong): Remove originalFile param.
|
|
32
|
+
bool hasShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage) const {
|
|
33
|
+
return shadowPagesMap.contains(originalFile) &&
|
|
34
|
+
@@ -37,8 +47,21 @@ class ShadowFile {
|
|
35
|
+
void clearShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage);
|
|
36
|
+
common::page_idx_t getShadowPage(common::file_idx_t originalFile,
|
|
37
|
+
common::page_idx_t originalPage) const;
|
|
38
|
+
- common::page_idx_t getOrCreateShadowPage(common::file_idx_t originalFile,
|
|
39
|
+
+ // Adds a new shadow page for originalPage. The page is not visible to
|
|
40
|
+
+ // readShadowVersionIfExists (nor hasShadowPage) until publishShadowPage is called, so the
|
|
41
|
+
+ // caller can fill it with the contents of the original page first. Must not be called while
|
|
42
|
+
+ // a published shadow page of a column data page is pinned (see mtx).
|
|
43
|
+
+ common::page_idx_t createShadowPage(common::file_idx_t originalFile,
|
|
44
|
+
common::page_idx_t originalPage);
|
|
45
|
+
+ void publishShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage,
|
|
46
|
+
+ common::page_idx_t shadowPageIdx);
|
|
47
|
+
+ // Reads the shadow version of a page if the current checkpoint has one, and returns false
|
|
48
|
+
+ // without calling readOp otherwise. Unlike the functions above, this is safe to call from
|
|
49
|
+
+ // read transactions running concurrently with a checkpoint: a checkpoint publishes the new
|
|
50
|
+
+ // metadata of in-place updated column chunks before the shadow pages are applied to the data
|
|
51
|
+
+ // file, so those readers must see the shadow version of such pages.
|
|
52
|
+
+ bool readShadowVersionIfExists(common::file_idx_t originalFile, common::page_idx_t originalPage,
|
|
53
|
+
+ const std::function<void(uint8_t*)>& readOp) const;
|
|
54
|
+
|
|
55
|
+
FileHandle& getShadowingFH() const { return *shadowingFH; }
|
|
56
|
+
|
|
57
|
+
@@ -83,6 +106,15 @@ class ShadowFile {
|
|
58
|
+
std::unordered_map<common::page_idx_t, common::page_idx_t>>
|
|
59
|
+
shadowPagesMap;
|
|
60
|
+
std::vector<ShadowPageRecord> shadowPageRecords;
|
|
61
|
+
+ // Protects shadowPagesMap against concurrent readers (see readShadowVersionIfExists). Only
|
|
62
|
+
+ // the checkpointing thread modifies the map.
|
|
63
|
+
+ // Readers hold mtx shared while spinning in optimisticRead on a locked shadow page of a page
|
|
64
|
+
+ // they reach through ColumnReadWriter::readFromPage (column data pages). The checkpointing
|
|
65
|
+
+ // thread therefore must not hold such a published shadow page pinned while it takes mtx
|
|
66
|
+
+ // exclusively (create, publish, clear or reset), or it would deadlock with those readers.
|
|
67
|
+
+ mutable std::shared_mutex mtx;
|
|
68
|
+
+ // Fast path for readers: false when there is no shadow page.
|
|
69
|
+
+ std::atomic<bool> hasShadowPages{false};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
} // namespace storage
|
|
73
|
+
diff --git a/src/storage/shadow_file.cpp b/src/storage/shadow_file.cpp
|
|
74
|
+
index 9811166c6..8984d1eb8 100644
|
|
75
|
+
--- a/src/storage/shadow_file.cpp
|
|
76
|
+
+++ b/src/storage/shadow_file.cpp
|
|
77
|
+
@@ -45,6 +45,7 @@ void ShadowFile::setDatabasePath(const std::string& databasePath_) {
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void ShadowFile::clearShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
|
|
81
|
+
+ std::unique_lock lck{mtx};
|
|
82
|
+
if (hasShadowPage(originalFile, originalPage)) {
|
|
83
|
+
shadowPagesMap.at(originalFile).erase(originalPage);
|
|
84
|
+
if (shadowPagesMap.at(originalFile).empty()) {
|
|
85
|
+
@@ -53,20 +54,44 @@ void ShadowFile::clearShadowPage(file_idx_t originalFile, page_idx_t originalPag
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
-page_idx_t ShadowFile::getOrCreateShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
|
|
90
|
+
- if (hasShadowPage(originalFile, originalPage)) {
|
|
91
|
+
- return shadowPagesMap[originalFile][originalPage];
|
|
92
|
+
- }
|
|
93
|
+
+page_idx_t ShadowFile::createShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
|
|
94
|
+
+ DASSERT(!hasShadowPage(originalFile, originalPage));
|
|
95
|
+
+ std::unique_lock lck{mtx};
|
|
96
|
+
const auto shadowPageIdx = getOrCreateShadowingFH()->addNewPage();
|
|
97
|
+
- shadowPagesMap[originalFile][originalPage] = shadowPageIdx;
|
|
98
|
+
+ // Records must stay in the same order as the shadow pages (see applyShadowPages). Readers
|
|
99
|
+
+ // only use shadowPagesMap, so the page is not visible to them until it is published.
|
|
100
|
+
shadowPageRecords.push_back({originalFile, originalPage});
|
|
101
|
+
return shadowPageIdx;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
+void ShadowFile::publishShadowPage(file_idx_t originalFile, page_idx_t originalPage,
|
|
105
|
+
+ page_idx_t shadowPageIdx) {
|
|
106
|
+
+ std::unique_lock lck{mtx};
|
|
107
|
+
+ shadowPagesMap[originalFile][originalPage] = shadowPageIdx;
|
|
108
|
+
+ // Pairs with the acquire load in readShadowVersionIfExists.
|
|
109
|
+
+ hasShadowPages.store(true, std::memory_order_release);
|
|
110
|
+
+}
|
|
111
|
+
+
|
|
112
|
+
page_idx_t ShadowFile::getShadowPage(file_idx_t originalFile, page_idx_t originalPage) const {
|
|
113
|
+
DASSERT(hasShadowPage(originalFile, originalPage));
|
|
114
|
+
return shadowPagesMap.at(originalFile).at(originalPage);
|
|
115
|
+
}
|
|
116
|
+
+
|
|
117
|
+
+bool ShadowFile::readShadowVersionIfExists(file_idx_t originalFile, page_idx_t originalPage,
|
|
118
|
+
+ const std::function<void(uint8_t*)>& readOp) const {
|
|
119
|
+
+ if (!hasShadowPages.load(std::memory_order_acquire)) {
|
|
120
|
+
+ return false;
|
|
121
|
+
+ }
|
|
122
|
+
+ std::shared_lock lck{mtx};
|
|
123
|
+
+ if (!hasShadowPage(originalFile, originalPage)) {
|
|
124
|
+
+ return false;
|
|
125
|
+
+ }
|
|
126
|
+
+ // Optimistic reads let concurrent readers share the page and retry if the checkpointer is
|
|
127
|
+
+ // updating it at the same time.
|
|
128
|
+
+ shadowingFH->optimisticReadPage(shadowPagesMap.at(originalFile).at(originalPage), readOp);
|
|
129
|
+
+ return true;
|
|
130
|
+
+}
|
|
131
|
+
+
|
|
132
|
+
void ShadowFile::applyShadowPages(StorageManager& storageManager, ClientContext& context) const {
|
|
133
|
+
const auto pageBuffer = std::make_unique<uint8_t[]>(LBUG_PAGE_SIZE);
|
|
134
|
+
page_idx_t shadowPageIdx = 1; // Skip header page.
|
|
135
|
+
@@ -191,6 +216,8 @@ void ShadowFile::flushAll(main::ClientContext& context) const {
|
|
136
|
+
|
|
137
|
+
void ShadowFile::clear(BufferManager& bm) {
|
|
138
|
+
DASSERT(shadowingFH);
|
|
139
|
+
+ // Concurrent readers may be reading shadow pages (see readShadowVersionIfExists).
|
|
140
|
+
+ std::unique_lock lck{mtx};
|
|
141
|
+
// Evict pages, truncate to zero pages (which also truncates the on-disk file), then unlink
|
|
142
|
+
// the path and drop the stale fd so no leftover .shadow file exists at rest (a present
|
|
143
|
+
// .shadow is treated as "checkpoint in progress" by recovery/startup probes). The
|
|
144
|
+
@@ -203,6 +230,7 @@ void ShadowFile::clear(BufferManager& bm) {
|
|
145
|
+
vfs->removeFileIfExists(shadowFilePath);
|
|
146
|
+
shadowPagesMap.clear();
|
|
147
|
+
shadowPageRecords.clear();
|
|
148
|
+
+ hasShadowPages.store(false, std::memory_order_relaxed);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
void ShadowFile::reset() {
|
|
152
|
+
@@ -218,6 +246,7 @@ void ShadowFile::reset() {
|
|
153
|
+
if (shadowingFH == nullptr) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
+ std::unique_lock lck{mtx};
|
|
157
|
+
// If clear() already ran (the normal checkpoint flow calls both), the file was truncated,
|
|
158
|
+
// unlinked and the fd dropped; just make sure the in-memory maps are empty.
|
|
159
|
+
if (shadowingFH->getFileInfo() != nullptr) {
|
|
160
|
+
@@ -228,6 +257,7 @@ void ShadowFile::reset() {
|
|
161
|
+
}
|
|
162
|
+
shadowPagesMap.clear();
|
|
163
|
+
shadowPageRecords.clear();
|
|
164
|
+
+ hasShadowPages.store(false, std::memory_order_relaxed);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
FileHandle* ShadowFile::getOrCreateShadowingFH() {
|
|
168
|
+
diff --git a/src/storage/shadow_utils.cpp b/src/storage/shadow_utils.cpp
|
|
169
|
+
index 572990231..57538f3ff 100644
|
|
170
|
+
--- a/src/storage/shadow_utils.cpp
|
|
171
|
+
+++ b/src/storage/shadow_utils.cpp
|
|
172
|
+
@@ -12,14 +12,16 @@ namespace storage {
|
|
173
|
+
ShadowPageAndFrame ShadowUtils::createShadowVersionIfNecessaryAndPinPage(page_idx_t originalPage,
|
|
174
|
+
bool skipReadingOriginalPage, FileHandle& fileHandle, ShadowFile& shadowFile) {
|
|
175
|
+
DASSERT(!fileHandle.isInMemoryMode());
|
|
176
|
+
- const auto hasShadowPage = shadowFile.hasShadowPage(fileHandle.getFileIndex(), originalPage);
|
|
177
|
+
- auto shadowPage = shadowFile.getOrCreateShadowPage(fileHandle.getFileIndex(), originalPage);
|
|
178
|
+
+ const auto fileIdx = fileHandle.getFileIndex();
|
|
179
|
+
+ page_idx_t shadowPage = INVALID_PAGE_IDX;
|
|
180
|
+
uint8_t* shadowFrame = nullptr;
|
|
181
|
+
try {
|
|
182
|
+
- if (hasShadowPage) {
|
|
183
|
+
+ if (shadowFile.hasShadowPage(fileIdx, originalPage)) {
|
|
184
|
+
+ shadowPage = shadowFile.getShadowPage(fileIdx, originalPage);
|
|
185
|
+
shadowFrame =
|
|
186
|
+
shadowFile.getShadowingFH().pinPage(shadowPage, PageReadPolicy::READ_PAGE);
|
|
187
|
+
} else {
|
|
188
|
+
+ shadowPage = shadowFile.createShadowPage(fileIdx, originalPage);
|
|
189
|
+
shadowFrame =
|
|
190
|
+
shadowFile.getShadowingFH().pinPage(shadowPage, PageReadPolicy::DONT_READ_PAGE);
|
|
191
|
+
if (!skipReadingOriginalPage) {
|
|
192
|
+
@@ -27,6 +29,11 @@ ShadowPageAndFrame ShadowUtils::createShadowVersionIfNecessaryAndPinPage(page_id
|
|
193
|
+
memcpy(shadowFrame, frame, LBUG_PAGE_SIZE);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
+ // Publish the shadow page only once it holds the original contents, since read
|
|
197
|
+
+ // transactions may read shadow pages concurrently (see
|
|
198
|
+
+ // ShadowFile::readShadowVersionIfExists). The page stays locked until it is unpinned,
|
|
199
|
+
+ // so concurrent optimistic readers wait for the caller's update to finish.
|
|
200
|
+
+ shadowFile.publishShadowPage(fileIdx, originalPage, shadowPage);
|
|
201
|
+
}
|
|
202
|
+
// The shadow page existing already does not mean that it's already dirty
|
|
203
|
+
// It may have been flushed to disk to free memory and then read again
|
|
204
|
+
diff --git a/src/storage/table/column_reader_writer.cpp b/src/storage/table/column_reader_writer.cpp
|
|
205
|
+
index fb71533d3..3e768de2f 100644
|
|
206
|
+
--- a/src/storage/table/column_reader_writer.cpp
|
|
207
|
+
+++ b/src/storage/table/column_reader_writer.cpp
|
|
208
|
+
@@ -8,6 +8,7 @@
|
|
209
|
+
#include "common/vector/value_vector.h"
|
|
210
|
+
#include "storage/compression/float_compression.h"
|
|
211
|
+
#include "storage/file_handle.h"
|
|
212
|
+
+#include "storage/shadow_file.h"
|
|
213
|
+
#include "storage/shadow_utils.h"
|
|
214
|
+
#include "storage/storage_utils.h"
|
|
215
|
+
#include "storage/table/column_chunk_data.h"
|
|
216
|
+
@@ -462,6 +463,12 @@ void ColumnReadWriter::readFromPage(page_idx_t pageIdx,
|
|
217
|
+
if (pageIdx == INVALID_PAGE_IDX) {
|
|
218
|
+
return readFunc(nullptr);
|
|
219
|
+
}
|
|
220
|
+
+ // A checkpoint in progress may already have published metadata which refers to in-place
|
|
221
|
+
+ // updates that so far only exist in shadow pages.
|
|
222
|
+
+ if (shadowFile &&
|
|
223
|
+
+ shadowFile->readShadowVersionIfExists(dataFH->getFileIndex(), pageIdx, readFunc)) {
|
|
224
|
+
+ return;
|
|
225
|
+
+ }
|
|
226
|
+
dataFH->optimisticReadPage(pageIdx, readFunc);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
diff --git a/src/transaction/transaction_manager.cpp b/src/transaction/transaction_manager.cpp
|
|
230
|
+
index 32e155213..48cbd8a2a 100644
|
|
231
|
+
--- a/src/transaction/transaction_manager.cpp
|
|
232
|
+
+++ b/src/transaction/transaction_manager.cpp
|
|
233
|
+
@@ -303,8 +303,10 @@ void TransactionManager::tryCheckpoint(main::ClientContext& clientContext) {
|
|
234
|
+
void TransactionManager::checkpointNoLock(main::ClientContext& clientContext) {
|
|
235
|
+
QueryProgressScope progress{clientContext, 0.01};
|
|
236
|
+
// We only need to wait for active write transactions to leave the system before
|
|
237
|
+
- // checkpointing. Read transactions can continue safely because they use MVCC snapshot
|
|
238
|
+
- // isolation and shadow pages are applied with per-page locking.
|
|
239
|
+
+ // checkpointing. Read transactions can continue because they use MVCC snapshot isolation.
|
|
240
|
+
+ // Column chunk metadata of in-place checkpointed data is published before the shadow pages
|
|
241
|
+
+ // are applied, so column reads resolve such in-flight pages through the shadow file (see
|
|
242
|
+
+ // ColumnReadWriter::readFromPage); applying shadow pages uses per-page locking.
|
|
243
|
+
UniqLock writeGate;
|
|
244
|
+
try {
|
|
245
|
+
writeGate = stopNewWriteTransactionsAndWaitUntilAllWriteTransactionsLeave();
|
|
246
|
+
diff --git a/test/transaction/checkpoint_test.cpp b/test/transaction/checkpoint_test.cpp
|
|
247
|
+
index 0a9899dca..277756061 100644
|
|
248
|
+
--- a/test/transaction/checkpoint_test.cpp
|
|
249
|
+
+++ b/test/transaction/checkpoint_test.cpp
|
|
250
|
+
@@ -2,14 +2,20 @@
|
|
251
|
+
#include <chrono>
|
|
252
|
+
#include <cstdlib>
|
|
253
|
+
#include <fstream>
|
|
254
|
+
+#include <functional>
|
|
255
|
+
#include <future>
|
|
256
|
+
#include <mutex>
|
|
257
|
+
#include <thread>
|
|
258
|
+
+#include <unordered_set>
|
|
259
|
+
|
|
260
|
+
#include "api_test/private_api_test.h"
|
|
261
|
+
+#include "catalog/catalog.h"
|
|
262
|
+
+#include "catalog/catalog_entry/table_catalog_entry.h"
|
|
263
|
+
#include "common/exception/runtime.h"
|
|
264
|
+
#include "storage/checkpointer.h"
|
|
265
|
+
#include "storage/storage_manager.h"
|
|
266
|
+
+#include "storage/table/node_table.h"
|
|
267
|
+
+#include "storage/table/string_chunk_data.h"
|
|
268
|
+
#include "storage/wal/wal.h"
|
|
269
|
+
#include "test_env.h"
|
|
270
|
+
#include "transaction/transaction_manager.h"
|
|
271
|
+
@@ -308,6 +314,122 @@ TEST_F(FlakyCheckpointerTest, ShadowFileDatabaseIDMismatchCorruptedDB) {
|
|
272
|
+
EXPECT_THROW(createDBAndConn(), InternalException);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
+// A checkpoint publishes the new chunk metadata of a node group during its storage phase, but
|
|
276
|
+
+// in-place updates to existing pages are only written to the shadow file until the shadow pages
|
|
277
|
+
+// are applied at the end of the checkpoint. Read-only transactions are not blocked by a
|
|
278
|
+
+// checkpoint, so a scan that starts in between must not observe the new metadata together with
|
|
279
|
+
+// the old page contents (e.g. appended string dictionary offsets that are not on disk yet).
|
|
280
|
+
+class CheckpointerWithReadBeforeApplyingShadowPages final : public Checkpointer {
|
|
281
|
+
+public:
|
|
282
|
+
+ CheckpointerWithReadBeforeApplyingShadowPages(main::ClientContext& clientContext,
|
|
283
|
+
+ std::function<void()> readFunc)
|
|
284
|
+
+ : Checkpointer(clientContext), readFunc(std::move(readFunc)) {}
|
|
285
|
+
+
|
|
286
|
+
+ void logCheckpointAndApplyShadowPages(bool walRotated) override {
|
|
287
|
+
+ readFunc();
|
|
288
|
+
+ Checkpointer::logCheckpointAndApplyShadowPages(walRotated);
|
|
289
|
+
+ }
|
|
290
|
+
+
|
|
291
|
+
+private:
|
|
292
|
+
+ std::function<void()> readFunc;
|
|
293
|
+
+};
|
|
294
|
+
+
|
|
295
|
+
+#ifndef __SINGLE_THREADED__
|
|
296
|
+
+TEST_F(FlakyCheckpointerTest, ReadBeforeShadowPagesAreAppliedSeesCommittedStrings) {
|
|
297
|
+
+ if (inMemMode || systemConfig->checkpointThreshold == 0) {
|
|
298
|
+
+ GTEST_SKIP();
|
|
299
|
+
+ }
|
|
300
|
+
+ conn->query("CALL force_checkpoint_on_close=false;");
|
|
301
|
+
+ conn->query("CALL auto_checkpoint=false;");
|
|
302
|
+
+ ASSERT_TRUE(
|
|
303
|
+
+ conn->query("CREATE NODE TABLE test(id INT64 PRIMARY KEY, name STRING);")->isSuccess());
|
|
304
|
+
+ // Strings longer than the inline limit, few enough that the appended values below fit in
|
|
305
|
+
+ // the pages already allocated to the dictionary, so the checkpoint updates them in place.
|
|
306
|
+
+ auto getName = [](uint64_t i) { return std::format("persistent string value {:04}", i); };
|
|
307
|
+
+ auto insertRows = [&](uint64_t start, uint64_t end) {
|
|
308
|
+
+ for (auto i = start; i < end; i++) {
|
|
309
|
+
+ auto res =
|
|
310
|
+
+ conn->query(std::format("CREATE (:test {{id: {}, name: '{}'}});", i, getName(i)));
|
|
311
|
+
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
312
|
+
+ }
|
|
313
|
+
+ };
|
|
314
|
+
+ constexpr uint64_t numInitialRows = 20;
|
|
315
|
+
+ constexpr uint64_t numRows = 25;
|
|
316
|
+
+ insertRows(0, numInitialRows);
|
|
317
|
+
+ ASSERT_TRUE(conn->query("CHECKPOINT;")->isSuccess());
|
|
318
|
+
+ insertRows(numInitialRows, numRows);
|
|
319
|
+
+
|
|
320
|
+
+ // Pages of the persistent `name` column chunk (dictionary offsets, string data and indices).
|
|
321
|
+
+ // An in-place checkpoint of the column updates these pages through the shadow file, while an
|
|
322
|
+
+ // out-of-place checkpoint writes new pages and leaves these untouched.
|
|
323
|
+
+ auto context = getClientContext(*conn);
|
|
324
|
+
+ auto storageManager = StorageManager::Get(*context);
|
|
325
|
+
+ const auto tableEntry = catalog::Catalog::Get(*context)->getTableCatalogEntry(
|
|
326
|
+
+ &DUMMY_CHECKPOINT_TRANSACTION, "test");
|
|
327
|
+
+ auto& nodeTable = storageManager->getTable(tableEntry->getTableID())->cast<NodeTable>();
|
|
328
|
+
+ const auto* persistentGroup = nodeTable.getNodeGroup(0)->getChunkedNodeGroup(0);
|
|
329
|
+
+ ASSERT_EQ(persistentGroup->getResidencyState(), ResidencyState::ON_DISK);
|
|
330
|
+
+ std::unordered_set<page_idx_t> namePages;
|
|
331
|
+
+ for (const auto* segment :
|
|
332
|
+
+ persistentGroup->getColumnChunk(tableEntry->getColumnID("name")).getSegments()) {
|
|
333
|
+
+ const auto& stringChunk = segment->cast<StringChunkData>();
|
|
334
|
+
+ for (const auto* chunk :
|
|
335
|
+
+ std::initializer_list<const ColumnChunkData*>{stringChunk.getIndexColumnChunk(),
|
|
336
|
+
+ stringChunk.getDictionaryChunk().getOffsetChunk(),
|
|
337
|
+
+ stringChunk.getDictionaryChunk().getStringDataChunk()}) {
|
|
338
|
+
+ const auto& metadata = chunk->getMetadata();
|
|
339
|
+
+ for (auto i = 0u; i < metadata.getNumPages(); i++) {
|
|
340
|
+
+ namePages.insert(metadata.getStartPageIdx() + i);
|
|
341
|
+
+ }
|
|
342
|
+
+ }
|
|
343
|
+
+ }
|
|
344
|
+
+ ASSERT_FALSE(namePages.empty());
|
|
345
|
+
+
|
|
346
|
+
+ bool readRan = false;
|
|
347
|
+
+ uint64_t numShadowedNamePages = 0;
|
|
348
|
+
+ std::string readError;
|
|
349
|
+
+ std::vector<std::string> readNames;
|
|
350
|
+
+ auto readFunc = [&]() {
|
|
351
|
+
+ // Make sure the `name` column was checkpointed in place, i.e. its updated pages are still
|
|
352
|
+
+ // pending in the shadow file; otherwise the read below would not read shadow pages.
|
|
353
|
+
+ auto& shadowFile = storageManager->getShadowFile();
|
|
354
|
+
+ for (const auto pageIdx : namePages) {
|
|
355
|
+
+ if (shadowFile.hasShadowPage(storageManager->getDataFH()->getFileIndex(), pageIdx)) {
|
|
356
|
+
+ numShadowedNamePages++;
|
|
357
|
+
+ }
|
|
358
|
+
+ }
|
|
359
|
+
+ // Run on a separate thread with its own connection, like a concurrent reader would.
|
|
360
|
+
+ std::thread reader([&]() {
|
|
361
|
+
+ auto readConn = std::make_unique<main::Connection>(database.get());
|
|
362
|
+
+ auto res = readConn->query("MATCH (t:test) RETURN t.name ORDER BY t.id;");
|
|
363
|
+
+ readRan = true;
|
|
364
|
+
+ if (!res->isSuccess()) {
|
|
365
|
+
+ readError = res->getErrorMessage();
|
|
366
|
+
+ return;
|
|
367
|
+
+ }
|
|
368
|
+
+ while (res->hasNext()) {
|
|
369
|
+
+ readNames.push_back(res->getNext()->getValue(0)->getValue<std::string>());
|
|
370
|
+
+ }
|
|
371
|
+
+ });
|
|
372
|
+
+ reader.join();
|
|
373
|
+
+ };
|
|
374
|
+
+ FlakyCheckpointer checkpointer([&](main::ClientContext& clientContext) {
|
|
375
|
+
+ return std::make_unique<CheckpointerWithReadBeforeApplyingShadowPages>(clientContext,
|
|
376
|
+
+ readFunc);
|
|
377
|
+
+ });
|
|
378
|
+
+ checkpointer.setCheckpointer(*context);
|
|
379
|
+
+ auto res = conn->query("CHECKPOINT;");
|
|
380
|
+
+ ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
|
|
381
|
+
+ ASSERT_TRUE(readRan);
|
|
382
|
+
+ ASSERT_GT(numShadowedNamePages, 0u);
|
|
383
|
+
+ ASSERT_TRUE(readError.empty()) << readError;
|
|
384
|
+
+ ASSERT_EQ(readNames.size(), numRows);
|
|
385
|
+
+ for (auto i = 0u; i < numRows; i++) {
|
|
386
|
+
+ EXPECT_EQ(readNames[i], getName(i));
|
|
387
|
+
+ }
|
|
388
|
+
+}
|
|
389
|
+
+#endif // __SINGLE_THREADED__
|
|
390
|
+
+
|
|
391
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
392
|
+
// ReviewFixesTest
|
|
393
|
+
// Targeted regression tests for the three fixes made in response to adsharma's
|