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.
Files changed (451) hide show
  1. package/core/.agents/skills/typesafe-ai/LICENSE +21 -21
  2. package/core/.agents/skills/typesafe-ai/SKILL.md +149 -149
  3. package/core/.claude/commands/Ansuz.md +15 -2
  4. package/core/.claude/hooks/__tests__/task-2395-project-registration.test.js +84 -0
  5. package/core/.claude/hooks/handoff-staleness.js +2 -2
  6. package/core/.claude/hooks/session-init.ps1 +49 -49
  7. package/core/.claude/hooks/turn-context.js +50 -2
  8. package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -26
  9. package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -97
  10. package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -101
  11. package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -31
  12. package/core/.claude/skills/ui-ux-pro-max/data/products.csv +96 -96
  13. package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  14. package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  15. package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  16. package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  17. package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  18. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  19. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  20. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  21. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  22. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  23. package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  24. package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  25. package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  26. package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  27. package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -68
  28. package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +57 -57
  29. package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  30. package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  31. package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  32. package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -253
  33. package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  34. package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -106
  35. package/core/.collaboration/core/task-api.js +1 -4
  36. package/core/.formation/README.md +21 -146
  37. package/core/.formation/bifrost/codex-doctor.js +37 -2
  38. package/core/.formation/bifrost/codex-doctor.test.js +18 -0
  39. package/core/.formation/bifrost/launch.js +1 -1
  40. package/core/.formation/bifrost/openai/codex-hook-shim.js +6 -0
  41. package/core/.formation/bifrost/openai/codex-hook-shim.test.js +11 -1
  42. package/core/.formation/bifrost/setup-codex.js +3 -0
  43. package/core/.formation/native/claude/README.md +1 -1
  44. package/core/.formation/role-map.yaml +3 -3
  45. package/core/.formation/spec/task_operations.parity.test.js +0 -1
  46. package/core/.formation/spec/task_operations.yaml +3 -3
  47. package/core/.formation/tests/hello.txt +2 -2
  48. package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +4 -4
  49. package/core/.formation/tools/dashboard-models-chain.test.js +0 -6
  50. package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +1 -9
  51. package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +2 -2
  52. package/core/.formation/tools/dispatch.js +33 -554
  53. package/core/.formation/tools/mcp-allowlist.test.js +0 -1
  54. package/core/.formation/tools/post-complete/task2345.test.js +0 -5
  55. package/core/.formation/tools/pre-dispatch-snapshot.test.js +0 -100
  56. package/core/.formation/tools/prompt.js +33 -1623
  57. package/core/.formation/tools/prompt.test.js +26 -594
  58. package/core/.formation/unit/do.yaml +2 -4
  59. package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -163
  60. package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -393
  61. package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -102
  62. package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -412
  63. package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -144
  64. package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -86
  65. package/core/.kenaz/muninn_episode_write.ps1 +17 -17
  66. package/core/.kenaz/specialist/field_report_watermark.json +1 -1
  67. package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
  68. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
  69. package/core/.kenaz/specialist/t1777_probe.txt +1 -1
  70. package/core/.matrix/index.matrix +5 -5
  71. package/core/.rule/general/agent-workflow.yaml +4 -4
  72. package/core/.rule/general/file-size-threshold.yaml +16 -4
  73. package/core/.rule/general/rust-verify-target.yaml +0 -1
  74. package/core/.rule/general/system-invariants.yaml +8 -25
  75. package/core/.specialist/Ansuz/SYSTEM_DOC.md +17 -42
  76. package/core/.specialist/Ansuz/capability.yaml +7 -14
  77. package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +4 -11
  78. package/core/.specialist/Ansuz/elixir.yaml +3 -4
  79. package/core/.specialist/Ansuz/identity_contract.yaml +1 -1
  80. package/core/.specialist/Ansuz/knowledge.yaml +7 -10
  81. package/core/.specialist/Ansuz/rule.yaml +2 -2
  82. package/core/.specialist/Ansuz/tools/verify-and-land.js +22 -0
  83. package/core/.specialist/Ansuz/tools/verify-and-land.test.js +59 -5
  84. package/core/.specialist/Ansuz/workflow.yaml +7 -11
  85. package/core/.specialist/Forseti/core.yaml +1 -1
  86. package/core/.specialist/Heimdall/SKILL.md +1 -1
  87. package/core/.specialist/Heimdall/core.yaml +2 -2
  88. package/core/.specialist/Huginn/rule.yaml +1 -1
  89. package/core/.specialist/tools/README.md +2 -2
  90. package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +0 -1
  91. package/core/.system/collaboration/README.md +1 -1
  92. package/core/.system/formation/LEGACY_SKILLS.md +2 -0
  93. package/core/.system/formation/POST_COMPLETE.md +2 -0
  94. package/core/.system/formation/README.md +2 -0
  95. package/core/KENAZ_CORE_VERSION +1 -1
  96. package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -355
  97. package/core/dev/JEV_INTEGRATION_PLAN.md +189 -189
  98. package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -13
  99. package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -9
  100. package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -14
  101. package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -67
  102. package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -52
  103. package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -31
  104. package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -39
  105. package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -33
  106. package/core/dev/jev-eval/README.md +99 -99
  107. package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -58
  108. package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -32
  109. package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -48
  110. package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -52
  111. package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -47
  112. package/core/dev/pipeline/tasks/.gitkeep +1 -1
  113. package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -27
  114. package/core/dev/scripts/check-file-size.js +44 -17
  115. package/core/dev/scripts/init_git.sh +0 -0
  116. package/core/dev/scripts/init_project.sh +0 -0
  117. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -536
  118. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -1978
  119. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -140
  120. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -173
  121. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -168
  122. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -126
  123. package/core/dev/scripts/multi_agent_scheduler.sh +0 -0
  124. package/core/dev/scripts/run_once.sh +0 -0
  125. package/core/dev/scripts/setup_mcp.sh +0 -0
  126. package/core/dev/scripts/setup_rag.sh +0 -0
  127. package/core/dev/scripts/start_dev_agent.sh +0 -0
  128. package/core/dev/scripts/start_multi_agent.sh +0 -0
  129. package/core/dev/scripts/sync_task_queue.sh +0 -0
  130. package/core/k-cli/README.md +1 -94
  131. package/core/k-cli/test/TEST_SPEC.md +5 -22
  132. package/core/k-cli/test/run.sh +1 -67
  133. package/core/plugins/kenaz/commands/Amelia.md +1 -1
  134. package/core/plugins/kenaz/commands/Ansuz.md +16 -3
  135. package/core/plugins/kenaz/scripts/kenaz-mode.js +11 -1
  136. package/core/plugins/kenaz/scripts/sync.js +6 -2
  137. package/core/plugins/kenaz/scripts/ygg.js +13 -1
  138. package/core/plugins/kenaz/skills/product_manager/SKILL.md +12 -12
  139. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -26
  140. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -97
  141. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -101
  142. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -31
  143. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +96 -96
  144. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  145. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  146. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  147. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  148. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  149. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  150. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  151. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  152. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  153. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  154. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  155. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  156. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  157. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  158. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -68
  159. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +57 -57
  160. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  161. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  162. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  163. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -253
  164. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  165. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -106
  166. package/lib/cli.js +11 -4
  167. package/package.json +11 -7
  168. package/vendor/install-kenaz-cli.js +18 -7
  169. package/core/.formation/legion/forge.yaml +0 -299
  170. package/core/.formation/legion/hive.yaml +0 -238
  171. package/core/.formation/squad/blitz.yaml +0 -192
  172. package/core/.formation/squad/strategy-tribunal.yaml +0 -354
  173. package/core/.formation/squad/strategy.yaml +0 -349
  174. package/core/.formation/squad/sweep.yaml +0 -208
  175. package/core/.formation/tools/broadcast.js +0 -103
  176. package/core/.formation/tools/dispatch-dryrun.test.js +0 -54
  177. package/core/.formation/tools/dispatch-notifier.js +0 -84
  178. package/core/.formation/tools/formations.js +0 -1203
  179. package/core/.formation/tools/headless-branch-recovery.test.js +0 -64
  180. package/core/.formation/tools/headless-dryrun.test.js +0 -79
  181. package/core/.formation/tools/headless-v2.js +0 -659
  182. package/core/.formation/tools/headless.js +0 -1071
  183. package/core/.formation/tools/legacy-scheduler.js +0 -1994
  184. package/core/.formation/tools/logger.js +0 -273
  185. package/core/.formation/tools/memory-loader.js +0 -422
  186. package/core/.formation/tools/process-state.js +0 -33
  187. package/core/.formation/tools/remote-conflict.js +0 -98
  188. package/core/.formation/tools/runner.js +0 -1332
  189. package/core/.formation/tools/task-utils.js +0 -344
  190. package/core/.formation/tools/task2028-headless-worktree.test.js +0 -112
  191. package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +0 -123
  192. package/core/.formation/tools/worktree-leak.test.js +0 -162
  193. package/core/k-cli/dist/alaya/run.js +0 -91
  194. package/core/k-cli/dist/core/output.js +0 -24
  195. package/core/k-cli/dist/core/process.js +0 -38
  196. package/core/k-cli/dist/core/project.js +0 -80
  197. package/core/k-cli/dist/core/spawn.js +0 -268
  198. package/core/k-cli/dist/core/types.js +0 -2
  199. package/core/k-cli/dist/dashboard/update.js +0 -53
  200. package/core/k-cli/dist/dashboard/version.js +0 -20
  201. package/core/k-cli/dist/dispatch/run.js +0 -241
  202. package/core/k-cli/dist/dispatch/status.js +0 -103
  203. package/core/k-cli/dist/index.js +0 -283
  204. package/core/k-cli/dist/project/initialize.js +0 -301
  205. package/core/k-cli/dist/project/install.js +0 -89
  206. package/core/k-cli/dist/project/status.js +0 -138
  207. package/core/k-cli/dist/specialist/list.js +0 -100
  208. package/core/k-cli/dist/specialist/run.js +0 -93
  209. package/core/k-cli/dist/task/create.js +0 -194
  210. package/core/k-cli/dist/task/delete.js +0 -98
  211. package/core/k-cli/dist/task/get.js +0 -55
  212. package/core/k-cli/dist/task/list.js +0 -58
  213. package/core/k-cli/dist/task/submit.js +0 -156
  214. package/core/k-cli/dist/ygg/run.js +0 -51
  215. package/core/k-cli/node_modules/.bin/yaml +0 -16
  216. package/core/k-cli/node_modules/.bin/yaml.cmd +0 -17
  217. package/core/k-cli/node_modules/.bin/yaml.ps1 +0 -28
  218. package/core/k-cli/node_modules/.package-lock.json +0 -23
  219. package/core/k-cli/node_modules/yaml/LICENSE +0 -13
  220. package/core/k-cli/node_modules/yaml/README.md +0 -172
  221. package/core/k-cli/node_modules/yaml/bin.mjs +0 -11
  222. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-collection.js +0 -88
  223. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -43
  224. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-node.js +0 -109
  225. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-scalar.js +0 -86
  226. package/core/k-cli/node_modules/yaml/browser/dist/compose/composer.js +0 -217
  227. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -115
  228. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +0 -198
  229. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +0 -49
  230. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-end.js +0 -37
  231. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +0 -207
  232. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +0 -223
  233. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-props.js +0 -146
  234. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-contains-newline.js +0 -34
  235. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +0 -26
  236. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +0 -15
  237. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-map-includes.js +0 -13
  238. package/core/k-cli/node_modules/yaml/browser/dist/doc/Document.js +0 -335
  239. package/core/k-cli/node_modules/yaml/browser/dist/doc/anchors.js +0 -71
  240. package/core/k-cli/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -55
  241. package/core/k-cli/node_modules/yaml/browser/dist/doc/createNode.js +0 -88
  242. package/core/k-cli/node_modules/yaml/browser/dist/doc/directives.js +0 -176
  243. package/core/k-cli/node_modules/yaml/browser/dist/errors.js +0 -57
  244. package/core/k-cli/node_modules/yaml/browser/dist/index.js +0 -17
  245. package/core/k-cli/node_modules/yaml/browser/dist/log.js +0 -11
  246. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Alias.js +0 -114
  247. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Collection.js +0 -147
  248. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Node.js +0 -38
  249. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Pair.js +0 -36
  250. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Scalar.js +0 -24
  251. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLMap.js +0 -144
  252. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +0 -113
  253. package/core/k-cli/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +0 -63
  254. package/core/k-cli/node_modules/yaml/browser/dist/nodes/identity.js +0 -36
  255. package/core/k-cli/node_modules/yaml/browser/dist/nodes/toJS.js +0 -37
  256. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-scalar.js +0 -214
  257. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-stringify.js +0 -61
  258. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-visit.js +0 -97
  259. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst.js +0 -98
  260. package/core/k-cli/node_modules/yaml/browser/dist/parse/lexer.js +0 -717
  261. package/core/k-cli/node_modules/yaml/browser/dist/parse/line-counter.js +0 -39
  262. package/core/k-cli/node_modules/yaml/browser/dist/parse/parser.js +0 -967
  263. package/core/k-cli/node_modules/yaml/browser/dist/public-api.js +0 -102
  264. package/core/k-cli/node_modules/yaml/browser/dist/schema/Schema.js +0 -37
  265. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/map.js +0 -17
  266. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/null.js +0 -15
  267. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/seq.js +0 -17
  268. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/string.js +0 -14
  269. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/bool.js +0 -19
  270. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/float.js +0 -43
  271. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/int.js +0 -38
  272. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/schema.js +0 -23
  273. package/core/k-cli/node_modules/yaml/browser/dist/schema/json/schema.js +0 -62
  274. package/core/k-cli/node_modules/yaml/browser/dist/schema/tags.js +0 -96
  275. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +0 -58
  276. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +0 -26
  277. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +0 -46
  278. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +0 -71
  279. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -64
  280. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +0 -74
  281. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +0 -78
  282. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -39
  283. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +0 -93
  284. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +0 -101
  285. package/core/k-cli/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +0 -146
  286. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringify.js +0 -129
  287. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +0 -153
  288. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyComment.js +0 -20
  289. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +0 -85
  290. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +0 -24
  291. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyPair.js +0 -150
  292. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyString.js +0 -336
  293. package/core/k-cli/node_modules/yaml/browser/dist/util.js +0 -11
  294. package/core/k-cli/node_modules/yaml/browser/dist/visit.js +0 -233
  295. package/core/k-cli/node_modules/yaml/browser/index.js +0 -5
  296. package/core/k-cli/node_modules/yaml/browser/package.json +0 -3
  297. package/core/k-cli/node_modules/yaml/dist/cli.d.ts +0 -8
  298. package/core/k-cli/node_modules/yaml/dist/cli.mjs +0 -201
  299. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.d.ts +0 -11
  300. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.js +0 -90
  301. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.d.ts +0 -7
  302. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.js +0 -45
  303. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.d.ts +0 -29
  304. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.js +0 -112
  305. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.d.ts +0 -5
  306. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.js +0 -88
  307. package/core/k-cli/node_modules/yaml/dist/compose/composer.d.ts +0 -63
  308. package/core/k-cli/node_modules/yaml/dist/compose/composer.js +0 -222
  309. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.d.ts +0 -6
  310. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.js +0 -117
  311. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +0 -11
  312. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.js +0 -200
  313. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +0 -6
  314. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.js +0 -51
  315. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.d.ts +0 -6
  316. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.js +0 -39
  317. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +0 -7
  318. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.js +0 -209
  319. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +0 -10
  320. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.js +0 -225
  321. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.d.ts +0 -23
  322. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.js +0 -148
  323. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.d.ts +0 -2
  324. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.js +0 -36
  325. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +0 -2
  326. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.js +0 -28
  327. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +0 -3
  328. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.js +0 -17
  329. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.d.ts +0 -4
  330. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.js +0 -15
  331. package/core/k-cli/node_modules/yaml/dist/doc/Document.d.ts +0 -141
  332. package/core/k-cli/node_modules/yaml/dist/doc/Document.js +0 -337
  333. package/core/k-cli/node_modules/yaml/dist/doc/anchors.d.ts +0 -24
  334. package/core/k-cli/node_modules/yaml/dist/doc/anchors.js +0 -76
  335. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.d.ts +0 -9
  336. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.js +0 -57
  337. package/core/k-cli/node_modules/yaml/dist/doc/createNode.d.ts +0 -17
  338. package/core/k-cli/node_modules/yaml/dist/doc/createNode.js +0 -90
  339. package/core/k-cli/node_modules/yaml/dist/doc/directives.d.ts +0 -49
  340. package/core/k-cli/node_modules/yaml/dist/doc/directives.js +0 -178
  341. package/core/k-cli/node_modules/yaml/dist/errors.d.ts +0 -21
  342. package/core/k-cli/node_modules/yaml/dist/errors.js +0 -62
  343. package/core/k-cli/node_modules/yaml/dist/index.d.ts +0 -25
  344. package/core/k-cli/node_modules/yaml/dist/index.js +0 -50
  345. package/core/k-cli/node_modules/yaml/dist/log.d.ts +0 -3
  346. package/core/k-cli/node_modules/yaml/dist/log.js +0 -19
  347. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.d.ts +0 -29
  348. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.js +0 -116
  349. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.d.ts +0 -73
  350. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.js +0 -151
  351. package/core/k-cli/node_modules/yaml/dist/nodes/Node.d.ts +0 -53
  352. package/core/k-cli/node_modules/yaml/dist/nodes/Node.js +0 -40
  353. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.d.ts +0 -22
  354. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.js +0 -39
  355. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.d.ts +0 -43
  356. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.js +0 -27
  357. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.d.ts +0 -53
  358. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.js +0 -147
  359. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +0 -60
  360. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.js +0 -115
  361. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +0 -4
  362. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.js +0 -65
  363. package/core/k-cli/node_modules/yaml/dist/nodes/identity.d.ts +0 -23
  364. package/core/k-cli/node_modules/yaml/dist/nodes/identity.js +0 -53
  365. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.d.ts +0 -29
  366. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.js +0 -39
  367. package/core/k-cli/node_modules/yaml/dist/options.d.ts +0 -350
  368. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.d.ts +0 -64
  369. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.js +0 -218
  370. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.d.ts +0 -8
  371. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.js +0 -63
  372. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.d.ts +0 -39
  373. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.js +0 -99
  374. package/core/k-cli/node_modules/yaml/dist/parse/cst.d.ts +0 -109
  375. package/core/k-cli/node_modules/yaml/dist/parse/cst.js +0 -112
  376. package/core/k-cli/node_modules/yaml/dist/parse/lexer.d.ts +0 -87
  377. package/core/k-cli/node_modules/yaml/dist/parse/lexer.js +0 -719
  378. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.d.ts +0 -22
  379. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.js +0 -41
  380. package/core/k-cli/node_modules/yaml/dist/parse/parser.d.ts +0 -84
  381. package/core/k-cli/node_modules/yaml/dist/parse/parser.js +0 -972
  382. package/core/k-cli/node_modules/yaml/dist/public-api.d.ts +0 -44
  383. package/core/k-cli/node_modules/yaml/dist/public-api.js +0 -107
  384. package/core/k-cli/node_modules/yaml/dist/schema/Schema.d.ts +0 -17
  385. package/core/k-cli/node_modules/yaml/dist/schema/Schema.js +0 -39
  386. package/core/k-cli/node_modules/yaml/dist/schema/common/map.d.ts +0 -2
  387. package/core/k-cli/node_modules/yaml/dist/schema/common/map.js +0 -19
  388. package/core/k-cli/node_modules/yaml/dist/schema/common/null.d.ts +0 -4
  389. package/core/k-cli/node_modules/yaml/dist/schema/common/null.js +0 -17
  390. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.d.ts +0 -2
  391. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.js +0 -19
  392. package/core/k-cli/node_modules/yaml/dist/schema/common/string.d.ts +0 -2
  393. package/core/k-cli/node_modules/yaml/dist/schema/common/string.js +0 -16
  394. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.d.ts +0 -4
  395. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.js +0 -21
  396. package/core/k-cli/node_modules/yaml/dist/schema/core/float.d.ts +0 -4
  397. package/core/k-cli/node_modules/yaml/dist/schema/core/float.js +0 -47
  398. package/core/k-cli/node_modules/yaml/dist/schema/core/int.d.ts +0 -4
  399. package/core/k-cli/node_modules/yaml/dist/schema/core/int.js +0 -42
  400. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.d.ts +0 -1
  401. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.js +0 -25
  402. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.d.ts +0 -2
  403. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.js +0 -64
  404. package/core/k-cli/node_modules/yaml/dist/schema/json-schema.d.ts +0 -69
  405. package/core/k-cli/node_modules/yaml/dist/schema/tags.d.ts +0 -48
  406. package/core/k-cli/node_modules/yaml/dist/schema/tags.js +0 -99
  407. package/core/k-cli/node_modules/yaml/dist/schema/types.d.ts +0 -92
  408. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +0 -2
  409. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.js +0 -70
  410. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +0 -7
  411. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.js +0 -29
  412. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +0 -4
  413. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.js +0 -50
  414. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +0 -5
  415. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.js +0 -76
  416. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
  417. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -68
  418. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +0 -22
  419. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.js +0 -77
  420. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +0 -10
  421. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +0 -82
  422. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +0 -1
  423. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -41
  424. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +0 -28
  425. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.js +0 -96
  426. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +0 -6
  427. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +0 -105
  428. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +0 -34
  429. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.js +0 -151
  430. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.d.ts +0 -21
  431. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.js +0 -132
  432. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +0 -17
  433. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.js +0 -155
  434. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.d.ts +0 -10
  435. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.js +0 -24
  436. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +0 -4
  437. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.js +0 -87
  438. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +0 -2
  439. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.js +0 -26
  440. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.d.ts +0 -3
  441. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.js +0 -152
  442. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.d.ts +0 -9
  443. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.js +0 -338
  444. package/core/k-cli/node_modules/yaml/dist/test-events.d.ts +0 -4
  445. package/core/k-cli/node_modules/yaml/dist/test-events.js +0 -134
  446. package/core/k-cli/node_modules/yaml/dist/util.d.ts +0 -16
  447. package/core/k-cli/node_modules/yaml/dist/util.js +0 -28
  448. package/core/k-cli/node_modules/yaml/dist/visit.d.ts +0 -102
  449. package/core/k-cli/node_modules/yaml/dist/visit.js +0 -236
  450. package/core/k-cli/node_modules/yaml/package.json +0 -97
  451. 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