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  }