Skip to content

Commit e6563ee

Browse files
authored
Merge pull request #443 from ahrtr/surgery_abandon_freelist_20230330
cmd: add `surgery abandon-freelist` command
2 parents 3e560db + dc50a72 commit e6563ee

File tree

3 files changed

+85
-6
lines changed

3 files changed

+85
-6
lines changed

cmd/bbolt/command_surgery_cobra.go

+54-4
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ var (
1919
)
2020

2121
func newSurgeryCobraCommand() *cobra.Command {
22-
cmd := &cobra.Command{
22+
surgeryCmd := &cobra.Command{
2323
Use: "surgery <subcommand>",
2424
Short: "surgery related commands",
2525
}
2626

27-
cmd.AddCommand(newSurgeryClearPageElementsCommand())
27+
surgeryCmd.AddCommand(newSurgeryClearPageElementsCommand())
28+
surgeryCmd.AddCommand(newSurgeryFreelistCommand())
2829

29-
return cmd
30+
return surgeryCmd
3031
}
3132

3233
func newSurgeryClearPageElementsCommand() *cobra.Command {
@@ -42,7 +43,6 @@ func newSurgeryClearPageElementsCommand() *cobra.Command {
4243
}
4344
return nil
4445
},
45-
4646
RunE: surgeryClearPageElementFunc,
4747
}
4848

@@ -78,3 +78,53 @@ func surgeryClearPageElementFunc(cmd *cobra.Command, args []string) error {
7878
fmt.Fprintf(os.Stdout, "All elements in [%d, %d) in page %d were cleared\n", surgeryStartElementIdx, surgeryEndElementIdx, surgeryPageId)
7979
return nil
8080
}
81+
82+
// TODO(ahrtr): add `bbolt surgery freelist rebuild/check ...` commands,
83+
// and move all `surgery freelist` commands into a separate file,
84+
// e.g command_surgery_freelist.go.
85+
func newSurgeryFreelistCommand() *cobra.Command {
86+
cmd := &cobra.Command{
87+
Use: "freelist <subcommand>",
88+
Short: "freelist related surgery commands",
89+
}
90+
91+
cmd.AddCommand(newSurgeryFreelistAbandonCommand())
92+
93+
return cmd
94+
}
95+
96+
func newSurgeryFreelistAbandonCommand() *cobra.Command {
97+
abandonFreelistCmd := &cobra.Command{
98+
Use: "abandon <bbolt-file> [options]",
99+
Short: "Abandon the freelist from both meta pages",
100+
Args: func(cmd *cobra.Command, args []string) error {
101+
if len(args) == 0 {
102+
return errors.New("db file path not provided")
103+
}
104+
if len(args) > 1 {
105+
return errors.New("too many arguments")
106+
}
107+
return nil
108+
},
109+
RunE: surgeryFreelistAbandonFunc,
110+
}
111+
112+
abandonFreelistCmd.Flags().StringVar(&surgeryTargetDBFilePath, "output", "", "path to the target db file")
113+
114+
return abandonFreelistCmd
115+
}
116+
117+
func surgeryFreelistAbandonFunc(cmd *cobra.Command, args []string) error {
118+
srcDBPath := args[0]
119+
120+
if err := copyFile(srcDBPath, surgeryTargetDBFilePath); err != nil {
121+
return fmt.Errorf("[abandon-freelist] copy file failed: %w", err)
122+
}
123+
124+
if err := surgeon.ClearFreelist(surgeryTargetDBFilePath); err != nil {
125+
return fmt.Errorf("abandom-freelist command failed: %w", err)
126+
}
127+
128+
fmt.Fprintf(os.Stdout, "The freelist was abandoned in both meta pages.\nIt may cause some delay on next startup because bbolt needs to scan the whole db to reconstruct the free list.\n")
129+
return nil
130+
}

cmd/bbolt/command_surgery_cobra_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
bolt "go.etcd.io/bbolt"
1212
main "go.etcd.io/bbolt/cmd/bbolt"
1313
"go.etcd.io/bbolt/internal/btesting"
14+
"go.etcd.io/bbolt/internal/common"
1415
"go.etcd.io/bbolt/internal/guts_cli"
1516
)
1617

@@ -428,3 +429,31 @@ func testSurgeryClearPageElementsWithOverflow(t *testing.T, startIdx, endIdx int
428429

429430
compareDataAfterClearingElement(t, srcPath, output, pageId, false, startIdx, endIdx)
430431
}
432+
433+
func TestSurgery_Freelist_Abandon(t *testing.T) {
434+
pageSize := 4096
435+
db := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: pageSize})
436+
srcPath := db.Path()
437+
438+
defer requireDBNoChange(t, dbData(t, srcPath), srcPath)
439+
440+
rootCmd := main.NewRootCommand()
441+
output := filepath.Join(t.TempDir(), "db")
442+
rootCmd.SetArgs([]string{
443+
"surgery", "freelist", "abandon", srcPath,
444+
"--output", output,
445+
})
446+
err := rootCmd.Execute()
447+
require.NoError(t, err)
448+
449+
meta0 := loadMetaPage(t, output, 0)
450+
assert.Equal(t, common.PgidNoFreelist, meta0.Freelist())
451+
meta1 := loadMetaPage(t, output, 1)
452+
assert.Equal(t, common.PgidNoFreelist, meta1.Freelist())
453+
}
454+
455+
func loadMetaPage(t *testing.T, dbPath string, pageID uint64) *common.Meta {
456+
_, buf, err := guts_cli.ReadPage(dbPath, 0)
457+
require.NoError(t, err)
458+
return common.LoadPageMeta(buf)
459+
}

internal/surgeon/surgeon.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,15 @@ func ClearPageElements(path string, pgId common.Pgid, start, end int, abandonFre
104104

105105
if preOverflow != p.Overflow() || p.IsBranchPage() {
106106
if abandonFreelist {
107-
return false, clearFreelist(path)
107+
return false, ClearFreelist(path)
108108
}
109109
return true, nil
110110
}
111111

112112
return false, nil
113113
}
114114

115-
func clearFreelist(path string) error {
115+
func ClearFreelist(path string) error {
116116
if err := clearFreelistInMetaPage(path, 0); err != nil {
117117
return fmt.Errorf("clearFreelist on meta page 0 failed: %w", err)
118118
}

0 commit comments

Comments
 (0)