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 }