github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/vfs/syncing_file_test.go (about) 1 // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package vfs 6 7 import ( 8 "bytes" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "sync/atomic" 13 "testing" 14 15 "github.com/stretchr/testify/require" 16 ) 17 18 func TestSyncingFile(t *testing.T) { 19 const mb = 1 << 20 20 21 tmpf, err := ioutil.TempFile("", "bitalostable-db-syncing-file-") 22 require.NoError(t, err) 23 24 filename := tmpf.Name() 25 require.NoError(t, tmpf.Close()) 26 defer os.Remove(filename) 27 28 f, err := Default.Create(filename) 29 require.NoError(t, err) 30 31 s := NewSyncingFile(f, SyncingFileOptions{}) 32 if s == f { 33 t.Fatalf("failed to wrap: %p != %p", f, s) 34 } 35 s = NewSyncingFile(f, SyncingFileOptions{BytesPerSync: 8 << 10 /* 8 KB */}) 36 s = s.(*fdFileWrapper).File 37 s.(*syncingFile).fd = 1 38 s.(*syncingFile).syncTo = func(offset int64) error { 39 s.(*syncingFile).ratchetSyncOffset(offset) 40 return nil 41 } 42 43 t.Logf("sync_file_range=%t", s.(*syncingFile).useSyncRange) 44 45 testCases := []struct { 46 n int64 47 expectedSyncTo int64 48 }{ 49 {mb, -1}, 50 {mb, mb}, 51 {4 << 10, mb}, 52 {4 << 10, mb + 8<<10}, 53 {8 << 10, mb + 16<<10}, 54 {16 << 10, mb + 32<<10}, 55 } 56 for i, c := range testCases { 57 _, err := s.Write(make([]byte, c.n)) 58 require.NoError(t, err) 59 60 syncTo := atomic.LoadInt64(&s.(*syncingFile).atomic.syncOffset) 61 if c.expectedSyncTo != syncTo { 62 t.Fatalf("%d: expected sync to %d, but found %d", i, c.expectedSyncTo, syncTo) 63 } 64 } 65 } 66 67 func TestSyncingFileClose(t *testing.T) { 68 testCases := []struct { 69 syncToEnabled bool 70 expected string 71 }{ 72 {true, `sync-to(1048576): test [<nil>] 73 sync-to(2097152): test [<nil>] 74 sync-to(3145728): test [<nil>] 75 pre-close: test [offset=4194304 sync-offset=3145728] 76 sync: test [<nil>] 77 close: test [<nil>] 78 `}, 79 // When SyncFileRange is not being used, the last sync call ends up syncing 80 // all of the data causing syncingFile.Close to elide the sync. 81 {false, `sync: test [<nil>] 82 sync: test [<nil>] 83 pre-close: test [offset=4194304 sync-offset=4194304] 84 close: test [<nil>] 85 `}, 86 } 87 for _, c := range testCases { 88 t.Run("", func(t *testing.T) { 89 tmpf, err := ioutil.TempFile("", "bitalostable-db-syncing-file-") 90 require.NoError(t, err) 91 92 filename := tmpf.Name() 93 require.NoError(t, tmpf.Close()) 94 defer os.Remove(filename) 95 96 f, err := Default.Create(filename) 97 require.NoError(t, err) 98 99 var buf bytes.Buffer 100 lf := loggingFile{f, "test", &buf} 101 102 s := NewSyncingFile(lf, SyncingFileOptions{BytesPerSync: 8 << 10 /* 8 KB */}).(*syncingFile) 103 if c.syncToEnabled { 104 s.fd = 1 105 s.syncData = lf.Sync 106 s.syncTo = func(offset int64) error { 107 s.ratchetSyncOffset(offset) 108 fmt.Fprintf(lf.w, "sync-to(%d): %s [%v]\n", offset, lf.name, err) 109 return nil 110 } 111 } else { 112 s.fd = 0 113 } 114 115 write := func(n int64) { 116 t.Helper() 117 _, err := s.Write(make([]byte, n)) 118 require.NoError(t, err) 119 } 120 121 const mb = 1 << 20 122 write(2 * mb) 123 write(mb) 124 write(mb) 125 126 fmt.Fprintf(lf.w, "pre-close: %s [offset=%d sync-offset=%d]\n", 127 lf.name, atomic.LoadInt64(&s.atomic.offset), atomic.LoadInt64(&s.atomic.syncOffset)) 128 require.NoError(t, s.Close()) 129 130 if s := buf.String(); c.expected != s { 131 t.Fatalf("expected\n%s\nbut found\n%s", c.expected, s) 132 } 133 }) 134 } 135 } 136 137 func TestSyncingFileNoSyncOnClose(t *testing.T) { 138 testCases := []struct { 139 useSyncRange bool 140 expectBefore int64 141 expectAfter int64 142 }{ 143 {false, 2 << 20, 2 << 20}, 144 {true, 2 << 20, 3<<20 + 128}, 145 } 146 147 for _, c := range testCases { 148 t.Run(fmt.Sprintf("useSyncRange=%v", c.useSyncRange), func(t *testing.T) { 149 tmpf, err := ioutil.TempFile("", "bitalostable-db-syncing-file-") 150 require.NoError(t, err) 151 152 filename := tmpf.Name() 153 require.NoError(t, tmpf.Close()) 154 defer os.Remove(filename) 155 156 f, err := Default.Create(filename) 157 require.NoError(t, err) 158 159 var buf bytes.Buffer 160 lf := loggingFile{f, "test", &buf} 161 162 s := NewSyncingFile(lf, SyncingFileOptions{NoSyncOnClose: true, BytesPerSync: 8 << 10}).(*syncingFile) 163 s.useSyncRange = c.useSyncRange 164 165 write := func(n int64) { 166 t.Helper() 167 _, err := s.Write(make([]byte, n)) 168 require.NoError(t, err) 169 } 170 171 const mb = 1 << 20 172 write(2 * mb) // Sync first 2MB 173 write(mb) // No sync because syncToOffset = 3M-1M = 2M 174 write(128) // No sync for the same reason 175 176 syncToBefore := atomic.LoadInt64(&s.atomic.syncOffset) 177 require.NoError(t, s.Close()) 178 syncToAfter := atomic.LoadInt64(&s.atomic.syncOffset) 179 180 if syncToBefore != c.expectBefore || syncToAfter != c.expectAfter { 181 t.Fatalf("Expected syncTo before and after closing are %d %d but found %d %d", 182 c.expectBefore, c.expectAfter, syncToBefore, syncToAfter) 183 } 184 }) 185 } 186 } 187 188 func BenchmarkSyncWrite(b *testing.B) { 189 const targetSize = 16 << 20 190 191 var wsizes []int 192 if testing.Verbose() { 193 wsizes = []int{64, 512, 1 << 10, 2 << 10, 4 << 10, 8 << 10, 16 << 10, 32 << 10} 194 } else { 195 wsizes = []int{64} 196 } 197 198 run := func(b *testing.B, wsize int, newFile func(string) File) { 199 tmpf, err := ioutil.TempFile("", "bitalostable-db-syncing-file-") 200 if err != nil { 201 b.Fatal(err) 202 } 203 filename := tmpf.Name() 204 _ = tmpf.Close() 205 defer os.Remove(filename) 206 207 var f File 208 var size int 209 buf := make([]byte, wsize) 210 211 b.SetBytes(int64(len(buf))) 212 b.ResetTimer() 213 for i := 0; i < b.N; i++ { 214 if f == nil { 215 b.StopTimer() 216 f = newFile(filename) 217 size = 0 218 b.StartTimer() 219 } 220 if _, err := f.Write(buf); err != nil { 221 b.Fatal(err) 222 } 223 if err := f.Sync(); err != nil { 224 b.Fatal(err) 225 } 226 size += len(buf) 227 if size >= targetSize { 228 _ = f.Close() 229 f = nil 230 } 231 } 232 b.StopTimer() 233 } 234 235 b.Run("no-prealloc", func(b *testing.B) { 236 for _, wsize := range wsizes { 237 b.Run(fmt.Sprintf("wsize=%d", wsize), func(b *testing.B) { 238 run(b, wsize, func(filename string) File { 239 _ = os.Remove(filename) 240 t, err := os.Create(filename) 241 if err != nil { 242 b.Fatal(err) 243 } 244 return NewSyncingFile(t, SyncingFileOptions{PreallocateSize: 0}) 245 }) 246 }) 247 } 248 }) 249 250 b.Run("prealloc-4MB", func(b *testing.B) { 251 for _, wsize := range wsizes { 252 b.Run(fmt.Sprintf("wsize=%d", wsize), func(b *testing.B) { 253 run(b, wsize, func(filename string) File { 254 _ = os.Remove(filename) 255 t, err := os.Create(filename) 256 if err != nil { 257 b.Fatal(err) 258 } 259 return NewSyncingFile(t, SyncingFileOptions{PreallocateSize: 4 << 20}) 260 }) 261 }) 262 } 263 }) 264 265 b.Run("reuse", func(b *testing.B) { 266 for _, wsize := range wsizes { 267 b.Run(fmt.Sprintf("wsize=%d", wsize), func(b *testing.B) { 268 init := true 269 run(b, wsize, func(filename string) File { 270 if init { 271 init = false 272 273 t, err := os.OpenFile(filename, os.O_RDWR, 0755) 274 if err != nil { 275 b.Fatal(err) 276 } 277 if _, err := t.Write(make([]byte, targetSize)); err != nil { 278 b.Fatal(err) 279 } 280 if err := t.Sync(); err != nil { 281 b.Fatal(err) 282 } 283 t.Close() 284 } 285 286 t, err := os.OpenFile(filename, os.O_RDWR, 0755) 287 if err != nil { 288 b.Fatal(err) 289 } 290 return NewSyncingFile(t, SyncingFileOptions{PreallocateSize: 0}) 291 }) 292 }) 293 } 294 }) 295 }