github.com/uber/kraken@v0.1.4/lib/store/base/file_map_test.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package base
    15  
    16  import (
    17  	"fmt"
    18  	"os"
    19  	"path"
    20  	"path/filepath"
    21  	"sync"
    22  	"sync/atomic"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/uber/kraken/core"
    27  	"github.com/uber/kraken/lib/store/metadata"
    28  
    29  	"github.com/andres-erbsen/clock"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  func TestFileMapTryStore(t *testing.T) {
    34  	require := require.New(t)
    35  	bundle, cleanup := fileMapLRUFixture()
    36  	defer cleanup()
    37  
    38  	fe := bundle.entry
    39  	s1 := bundle.state1
    40  	fm := bundle.fm
    41  
    42  	require.False(fm.Contains(fe.GetName()))
    43  
    44  	var wg sync.WaitGroup
    45  	var successCount, skippedCount, errorCount uint32
    46  	for i := 0; i < 100; i++ {
    47  		wg.Add(1)
    48  		go func() {
    49  			defer wg.Done()
    50  			var err error
    51  			stored := fm.TryStore(fe.GetName(), fe, func(name string, entry FileEntry) bool {
    52  				err = fe.Create(s1, 0)
    53  				return err == nil
    54  			})
    55  			if err != nil {
    56  				atomic.AddUint32(&errorCount, 1)
    57  			} else if !stored {
    58  				atomic.AddUint32(&skippedCount, 1)
    59  			} else {
    60  				atomic.AddUint32(&successCount, 1)
    61  			}
    62  		}()
    63  	}
    64  	wg.Wait()
    65  
    66  	// Only one goroutine successfully stored the entry.
    67  	require.Equal(errorCount, uint32(0))
    68  	require.Equal(skippedCount, uint32(99))
    69  	require.Equal(successCount, uint32(1))
    70  
    71  	require.True(fm.Contains(fe.GetName()))
    72  }
    73  
    74  func TestFileMapTryStoreAborts(t *testing.T) {
    75  	require := require.New(t)
    76  	bundle, cleanup := fileMapLRUFixture()
    77  	defer cleanup()
    78  
    79  	fe := bundle.entry
    80  	s1 := bundle.state1
    81  	fm := bundle.fm
    82  
    83  	err := fe.Create(s1, 0)
    84  	require.NoError(err)
    85  
    86  	var wg sync.WaitGroup
    87  	var successCount, skippedCount, errorCount uint32
    88  	for i := 0; i < 100; i++ {
    89  		wg.Add(1)
    90  		go func() {
    91  			defer wg.Done()
    92  			var err error
    93  			stored := fm.TryStore(fe.GetName(), fe, func(name string, entry FileEntry) bool {
    94  				// Exit right away.
    95  				err = os.ErrNotExist
    96  				return false
    97  			})
    98  			if err != nil {
    99  				atomic.AddUint32(&errorCount, 1)
   100  			} else if !stored {
   101  				atomic.AddUint32(&skippedCount, 1)
   102  			} else {
   103  				atomic.AddUint32(&successCount, 1)
   104  			}
   105  		}()
   106  	}
   107  	wg.Wait()
   108  
   109  	// Some goroutines successfully stored the entry, executed f, encountered
   110  	// failure and removed the entry.
   111  	// Others might have loaded the temp entries and skipped.
   112  	require.True(errorCount >= uint32(1))
   113  	require.True(errorCount+skippedCount == uint32(100))
   114  	require.Equal(successCount, uint32(0))
   115  }
   116  
   117  func TestFileMapLoadForRead(t *testing.T) {
   118  	require := require.New(t)
   119  	bundle, cleanup := fileMapLRUFixture()
   120  	defer cleanup()
   121  
   122  	fe := bundle.entry
   123  	s1 := bundle.state1
   124  	fm := bundle.fm
   125  
   126  	err := fe.Create(s1, 0)
   127  	require.NoError(err)
   128  
   129  	// Loading an non-existent entry does nothing.
   130  	testInt := 1
   131  	loaded := fm.LoadForWrite(fe.GetName(), func(name string, entry FileEntry) {
   132  		testInt = 2
   133  		return
   134  	})
   135  	require.False(loaded)
   136  	require.Equal(testInt, 1)
   137  
   138  	// Put entry into map.
   139  	stored := fm.TryStore(fe.GetName(), fe, func(name string, entry FileEntry) bool {
   140  		return true
   141  	})
   142  	require.True(stored)
   143  
   144  	var wg sync.WaitGroup
   145  	for i := 0; i < 100; i++ {
   146  		wg.Add(1)
   147  		go func() {
   148  			defer wg.Done()
   149  			loaded := fm.LoadForRead(fe.GetName(), func(name string, entry FileEntry) {
   150  				_, err := fe.GetStat()
   151  				require.NoError(err)
   152  			})
   153  			require.True(loaded)
   154  		}()
   155  	}
   156  	wg.Wait()
   157  }
   158  
   159  func TestFileMapLoadForWrite(t *testing.T) {
   160  	require := require.New(t)
   161  	bundle, cleanup := fileMapLRUFixture()
   162  	defer cleanup()
   163  
   164  	fe := bundle.entry
   165  	s1 := bundle.state1
   166  	s2 := bundle.state2
   167  	fm := bundle.fm
   168  
   169  	err := fe.Create(s1, 0)
   170  	require.NoError(err)
   171  
   172  	// Loading an non-existent entry does nothing.
   173  	testInt := 1
   174  	loaded := fm.LoadForWrite(fe.GetName(), func(name string, entry FileEntry) {
   175  		testInt = 2
   176  		return
   177  	})
   178  	require.False(loaded)
   179  	require.Equal(testInt, 1)
   180  
   181  	// Put entry into map.
   182  	stored := fm.TryStore(fe.GetName(), fe, func(name string, entry FileEntry) bool {
   183  		return true
   184  	})
   185  	require.True(stored)
   186  
   187  	var wg sync.WaitGroup
   188  	var successCount, stateErrorCount, otherErrorCount uint32
   189  	for i := 0; i < 100; i++ {
   190  		wg.Add(1)
   191  		go func() {
   192  			defer wg.Done()
   193  			var err error
   194  			loaded := fm.LoadForWrite(fe.GetName(), func(name string, entry FileEntry) {
   195  				if fe.GetState() == s2 {
   196  					atomic.AddUint32(&stateErrorCount, 1)
   197  				} else {
   198  					err = fe.Move(s2)
   199  					if err == nil {
   200  						atomic.AddUint32(&successCount, 1)
   201  					} else {
   202  						atomic.AddUint32(&otherErrorCount, 1)
   203  					}
   204  				}
   205  			})
   206  			require.True(loaded)
   207  		}()
   208  	}
   209  	wg.Wait()
   210  
   211  	// Only first goroutine successfully executed Move(), the others encountered
   212  	// FileStateError.
   213  	require.Equal(otherErrorCount, uint32(0))
   214  	require.Equal(stateErrorCount, uint32(99))
   215  	require.Equal(successCount, uint32(1))
   216  }
   217  
   218  func TestFileMapDelete(t *testing.T) {
   219  	require := require.New(t)
   220  	bundle, cleanup := fileMapLRUFixture()
   221  	defer cleanup()
   222  
   223  	fe := bundle.entry
   224  	s1 := bundle.state1
   225  	fm := bundle.fm
   226  
   227  	// Put entry into map.
   228  	var err error
   229  	stored := fm.TryStore(fe.GetName(), fe, func(name string, entry FileEntry) bool {
   230  		err = fe.Create(s1, 0)
   231  		return err == nil
   232  	})
   233  	require.True(stored)
   234  	require.NoError(err)
   235  
   236  	var wg sync.WaitGroup
   237  	var successCount, skippedCount, errorCount uint32
   238  	for i := 0; i < 100; i++ {
   239  		wg.Add(1)
   240  		go func() {
   241  			defer wg.Done()
   242  			var err error
   243  
   244  			deleted := fm.Delete(fe.GetName(), func(name string, entry FileEntry) bool {
   245  				err = fe.Delete()
   246  				return err == nil
   247  			})
   248  			if err != nil {
   249  				atomic.AddUint32(&errorCount, 1)
   250  			} else if deleted {
   251  				atomic.AddUint32(&successCount, 1)
   252  			} else {
   253  				atomic.AddUint32(&skippedCount, 1)
   254  			}
   255  		}()
   256  	}
   257  	wg.Wait()
   258  
   259  	// Only the first goroutine successfully deleted the entry, the others skipped.
   260  	require.Equal(errorCount, uint32(0))
   261  	require.Equal(skippedCount, uint32(99))
   262  	require.Equal(successCount, uint32(1))
   263  }
   264  
   265  func TestFileMapDeleteAbort(t *testing.T) {
   266  	require := require.New(t)
   267  	bundle, cleanup := fileMapLRUFixture()
   268  	defer cleanup()
   269  
   270  	fe := bundle.entry
   271  	s1 := bundle.state1
   272  	fm := bundle.fm
   273  
   274  	// Put entry into map.
   275  	var err error
   276  	stored := fm.TryStore(fe.GetName(), fe, func(name string, entry FileEntry) bool {
   277  		err = fe.Create(s1, 0)
   278  		return err == nil
   279  	})
   280  	require.True(stored)
   281  	require.NoError(err)
   282  
   283  	var wg sync.WaitGroup
   284  	var successCount, skippedCount, errorCount uint32
   285  	for i := 0; i < 100; i++ {
   286  		wg.Add(1)
   287  		go func() {
   288  			defer wg.Done()
   289  			var err error
   290  
   291  			deleted := fm.Delete(fe.GetName(), func(name string, entry FileEntry) bool {
   292  				err = os.ErrNotExist
   293  				return true
   294  			})
   295  			if err != nil {
   296  				atomic.AddUint32(&errorCount, 1)
   297  			} else if deleted {
   298  				atomic.AddUint32(&successCount, 1)
   299  			} else {
   300  				atomic.AddUint32(&skippedCount, 1)
   301  			}
   302  		}()
   303  	}
   304  	wg.Wait()
   305  
   306  	// The first goroutine encountered error, but removed the entry from map
   307  	// anyway. Other goroutines skipped.
   308  	require.Equal(errorCount, uint32(1))
   309  	require.Equal(skippedCount, uint32(99))
   310  	require.Equal(successCount, uint32(0))
   311  }
   312  
   313  func TestLRUFileMapSizeLimit(t *testing.T) {
   314  	require := require.New(t)
   315  	bundle, cleanup := fileStoreLRUFixture(100)
   316  	defer cleanup()
   317  
   318  	fm := bundle.store.fileMap
   319  	state := bundle.state1
   320  
   321  	insert := func(name string) {
   322  		entry, err := NewLocalFileEntryFactory().Create(name, state)
   323  		require.NoError(err)
   324  		stored := fm.TryStore(name, entry, func(name string, entry FileEntry) bool {
   325  			require.NoError(entry.Create(state, 0))
   326  			return true
   327  		})
   328  		require.True(stored)
   329  	}
   330  
   331  	// Generate 101 file names.
   332  	var names []string
   333  	for i := 0; i < 101; i++ {
   334  		names = append(names, fmt.Sprintf("test_file_%d", i))
   335  	}
   336  
   337  	// After inserting 100 files, the first file still exists in map.
   338  	for _, name := range names[:100] {
   339  		insert(name)
   340  	}
   341  	require.True(fm.Contains(names[0]))
   342  
   343  	// Insert one more file entry beyond size limit.
   344  	insert(names[100])
   345  
   346  	// The first file should have been removed.
   347  	require.False(fm.Contains(names[0]))
   348  }
   349  
   350  func TestLRUCreateLastAccessTimeOnCreateFile(t *testing.T) {
   351  	require := require.New(t)
   352  	bundle, cleanup := fileStoreLRUFixture(100)
   353  	defer cleanup()
   354  
   355  	store := bundle.store
   356  	clk := bundle.clk.(*clock.Mock)
   357  
   358  	t0 := time.Now()
   359  	clk.Set(t0)
   360  
   361  	fn := "testfile123"
   362  	s1 := bundle.state1
   363  
   364  	require.NoError(store.NewFileOp().AcceptState(s1).CreateFile(fn, s1, 5))
   365  
   366  	// Verify file exists.
   367  	_, err := os.Stat(path.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   368  	require.NoError(err)
   369  
   370  	var lat metadata.LastAccessTime
   371  	require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, &lat))
   372  	require.Equal(t0.Truncate(time.Second), lat.Time)
   373  }
   374  
   375  func TestLRUUpdateLastAccessTimeOnMoveFrom(t *testing.T) {
   376  	require := require.New(t)
   377  	bundle, cleanup := fileStoreLRUFixture(100)
   378  	defer cleanup()
   379  
   380  	store := bundle.store
   381  	clk := bundle.clk.(*clock.Mock)
   382  
   383  	t0 := time.Now()
   384  	clk.Set(t0)
   385  
   386  	s1, s2 := bundle.state1, bundle.state2
   387  
   388  	name := core.DigestFixture().Hex()
   389  	fp := filepath.Join(s1.GetDirectory(), name)
   390  	f, err := os.Create(fp)
   391  	require.NoError(err)
   392  	f.Close()
   393  
   394  	require.NoError(store.NewFileOp().AcceptState(s2).MoveFileFrom(name, s2, fp))
   395  
   396  	var lat metadata.LastAccessTime
   397  	require.NoError(store.NewFileOp().AcceptState(s2).GetFileMetadata(name, &lat))
   398  	require.Equal(t0.Truncate(time.Second), lat.Time)
   399  }
   400  
   401  func TestLRUUpdateLastAccessTimeOnMove(t *testing.T) {
   402  	require := require.New(t)
   403  	bundle, cleanup := fileStoreLRUFixture(100)
   404  	defer cleanup()
   405  
   406  	store := bundle.store
   407  	clk := bundle.clk.(*clock.Mock)
   408  
   409  	t0 := time.Now()
   410  	clk.Set(t0)
   411  
   412  	fn := "testfile123"
   413  	s1, s2 := bundle.state1, bundle.state2
   414  
   415  	require.NoError(store.NewFileOp().AcceptState(s1).CreateFile(fn, s1, 1))
   416  
   417  	clk.Add(time.Hour)
   418  	require.NoError(store.NewFileOp().AcceptState(s1).MoveFile(fn, s2))
   419  
   420  	var lat metadata.LastAccessTime
   421  	require.NoError(store.NewFileOp().AcceptState(s2).GetFileMetadata(fn, &lat))
   422  	require.Equal(clk.Now().Truncate(time.Second), lat.Time)
   423  }
   424  
   425  func TestLRUUpdateLastAccessTimeOnOpen(t *testing.T) {
   426  	require := require.New(t)
   427  	bundle, cleanup := fileStoreLRUFixture(100)
   428  	defer cleanup()
   429  
   430  	store := bundle.store
   431  	clk := bundle.clk.(*clock.Mock)
   432  
   433  	t0 := time.Now()
   434  	clk.Set(t0)
   435  
   436  	fn := "testfile123"
   437  	s1 := bundle.state1
   438  
   439  	require.NoError(store.NewFileOp().AcceptState(s1).CreateFile(fn, s1, 1))
   440  
   441  	checkLAT := func(op FileOp, expected time.Time) {
   442  		var lat metadata.LastAccessTime
   443  		require.NoError(op.GetFileMetadata(fn, &lat))
   444  		require.Equal(expected.Truncate(time.Second), lat.Time)
   445  	}
   446  
   447  	// No LAT change below resolution.
   448  	clk.Add(time.Minute)
   449  	_, err := store.NewFileOp().AcceptState(s1).GetFileReader(fn)
   450  	require.NoError(err)
   451  	checkLAT(store.NewFileOp().AcceptState(s1), t0)
   452  
   453  	clk.Add(time.Hour)
   454  	_, err = store.NewFileOp().AcceptState(s1).GetFileReader(fn)
   455  	require.NoError(err)
   456  	checkLAT(store.NewFileOp().AcceptState(s1), clk.Now())
   457  
   458  	clk.Add(time.Hour)
   459  	_, err = store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn)
   460  	require.NoError(err)
   461  	checkLAT(store.NewFileOp().AcceptState(s1), clk.Now())
   462  }
   463  
   464  func TestLRUKeepLastAccessTimeOnPeek(t *testing.T) {
   465  	require := require.New(t)
   466  	bundle, cleanup := fileStoreLRUFixture(100)
   467  	defer cleanup()
   468  
   469  	store := bundle.store
   470  	clk := bundle.clk.(*clock.Mock)
   471  
   472  	t0 := time.Now()
   473  	clk.Set(t0)
   474  
   475  	fn := "testfile123"
   476  	s1 := bundle.state1
   477  
   478  	require.NoError(store.NewFileOp().AcceptState(s1).CreateFile(fn, s1, 1))
   479  
   480  	clk.Add(time.Hour)
   481  	_, err := store.NewFileOp().AcceptState(s1).GetFileStat(fn)
   482  	require.NoError(err)
   483  
   484  	var lat metadata.LastAccessTime
   485  	require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, &lat))
   486  	require.Equal(t0.Truncate(time.Second), lat.Time)
   487  
   488  	clk.Add(time.Hour)
   489  	_, err = store.NewFileOp().AcceptState(s1).GetFilePath(fn)
   490  	require.NoError(err)
   491  
   492  	require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, &lat))
   493  	require.Equal(t0.Truncate(time.Second), lat.Time)
   494  }