github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/tlf_journal_test.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "math" 9 "os" 10 "reflect" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/golang/mock/gomock" 16 "github.com/keybase/client/go/kbfs/data" 17 "github.com/keybase/client/go/kbfs/idutil" 18 "github.com/keybase/client/go/kbfs/ioutil" 19 "github.com/keybase/client/go/kbfs/kbfsblock" 20 "github.com/keybase/client/go/kbfs/kbfscodec" 21 "github.com/keybase/client/go/kbfs/kbfscrypto" 22 "github.com/keybase/client/go/kbfs/kbfshash" 23 "github.com/keybase/client/go/kbfs/kbfsmd" 24 "github.com/keybase/client/go/kbfs/test/clocktest" 25 "github.com/keybase/client/go/kbfs/tlf" 26 "github.com/keybase/client/go/libkb" 27 "github.com/keybase/client/go/protocol/keybase1" 28 "github.com/pkg/errors" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 "golang.org/x/net/context" 32 ) 33 34 // testBWDelegate is a delegate we pass to tlfJournal to get info 35 // about its state transitions. 36 type testBWDelegate struct { 37 t *testing.T 38 // Store a context so that the tlfJournal's background context 39 // will also obey the test timeout. 40 testCtx context.Context 41 stateCh chan bwState 42 shutdownCh chan struct{} 43 testDoneCh chan struct{} 44 } 45 46 func (d testBWDelegate) GetBackgroundContext() context.Context { 47 return d.testCtx 48 } 49 50 func (d testBWDelegate) OnNewState(ctx context.Context, bws bwState) { 51 select { 52 case d.stateCh <- bws: 53 case <-d.testDoneCh: 54 // The test is over, so anything waiting on a state change is 55 // likely some errant race that's probably not worth fixing. 56 case <-ctx.Done(): 57 assert.Fail(d.t, ctx.Err().Error()) 58 } 59 } 60 61 func (d testBWDelegate) OnShutdown(ctx context.Context) { 62 select { 63 case d.shutdownCh <- struct{}{}: 64 case <-ctx.Done(): 65 assert.Fail(d.t, ctx.Err().Error()) 66 } 67 } 68 69 func (d testBWDelegate) requireNextState( 70 ctx context.Context, expectedState ...bwState) bwState { 71 select { 72 case bws := <-d.stateCh: 73 require.Contains(d.t, expectedState, bws) 74 return bws 75 case <-ctx.Done(): 76 assert.Fail(d.t, ctx.Err().Error()) 77 return bwIdle 78 } 79 } 80 81 // testTLFJournalConfig is the config we pass to the tlfJournal, and 82 // also contains some helper functions for testing. 83 type testTLFJournalConfig struct { 84 codecGetter 85 logMaker 86 *testSyncedTlfGetterSetter 87 t *testing.T 88 tlfID tlf.ID 89 splitter data.BlockSplitter 90 crypto *CryptoLocal 91 bcache data.BlockCache 92 bops BlockOps 93 mdcache MDCache 94 ver kbfsmd.MetadataVer 95 reporter Reporter 96 uid keybase1.UID 97 verifyingKey kbfscrypto.VerifyingKey 98 ekg singleEncryptionKeyGetter 99 nug idutil.NormalizedUsernameGetter 100 mdserver MDServer 101 dlTimeout time.Duration 102 subscriptionManagerPublisher SubscriptionManagerPublisher 103 } 104 105 func (c testTLFJournalConfig) SubscriptionManagerPublisher() SubscriptionManagerPublisher { 106 return c.subscriptionManagerPublisher 107 } 108 109 func (c testTLFJournalConfig) BlockSplitter() data.BlockSplitter { 110 return c.splitter 111 } 112 113 func (c testTLFJournalConfig) Clock() Clock { 114 return data.WallClock{} 115 } 116 117 func (c testTLFJournalConfig) Crypto() Crypto { 118 return c.crypto 119 } 120 121 func (c testTLFJournalConfig) BlockCache() data.BlockCache { 122 return c.bcache 123 } 124 125 func (c testTLFJournalConfig) BlockOps() BlockOps { 126 return c.bops 127 } 128 129 func (c testTLFJournalConfig) MDCache() MDCache { 130 return c.mdcache 131 } 132 133 func (c testTLFJournalConfig) MetadataVersion() kbfsmd.MetadataVer { 134 return c.ver 135 } 136 137 func (c testTLFJournalConfig) Reporter() Reporter { 138 return c.reporter 139 } 140 141 func (c testTLFJournalConfig) cryptoPure() cryptoPure { 142 return c.crypto 143 } 144 145 func (c testTLFJournalConfig) encryptionKeyGetter() encryptionKeyGetter { 146 return c.ekg 147 } 148 149 func (c testTLFJournalConfig) mdDecryptionKeyGetter() mdDecryptionKeyGetter { 150 return c.ekg 151 } 152 153 func (c testTLFJournalConfig) usernameGetter() idutil.NormalizedUsernameGetter { 154 return c.nug 155 } 156 157 func (c testTLFJournalConfig) resolver() idutil.Resolver { 158 return nil 159 } 160 161 func (c testTLFJournalConfig) MDServer() MDServer { 162 return c.mdserver 163 } 164 165 func (c testTLFJournalConfig) teamMembershipChecker() kbfsmd.TeamMembershipChecker { 166 // TODO: support team TLF tests. 167 return nil 168 } 169 170 func (c testTLFJournalConfig) diskLimitTimeout() time.Duration { 171 return c.dlTimeout 172 } 173 174 func (c testTLFJournalConfig) BGFlushDirOpBatchSize() int { 175 return 1 176 } 177 178 func (c testTLFJournalConfig) makeBlock(data []byte) ( 179 kbfsblock.ID, kbfsblock.Context, kbfscrypto.BlockCryptKeyServerHalf) { 180 id, err := kbfsblock.MakePermanentID(data, kbfscrypto.EncryptionSecretboxWithKeyNonce) 181 require.NoError(c.t, err) 182 bCtx := kbfsblock.MakeFirstContext( 183 c.uid.AsUserOrTeam(), keybase1.BlockType_DATA) 184 serverHalf, err := kbfscrypto.MakeRandomBlockCryptKeyServerHalf() 185 require.NoError(c.t, err) 186 return id, bCtx, serverHalf 187 } 188 189 func (c testTLFJournalConfig) makeMD( 190 revision kbfsmd.Revision, prevRoot kbfsmd.ID) *RootMetadata { 191 return makeMDForTest(c.t, c.ver, c.tlfID, revision, c.uid, c.crypto, prevRoot) 192 } 193 194 func (c testTLFJournalConfig) checkMD(rmds *RootMetadataSigned, 195 extra kbfsmd.ExtraMetadata, expectedRevision kbfsmd.Revision, 196 expectedPrevRoot kbfsmd.ID, expectedMergeStatus kbfsmd.MergeStatus, 197 expectedBranchID kbfsmd.BranchID) { 198 verifyingKey := c.crypto.SigningKeySigner.Key.GetVerifyingKey() 199 checkBRMD(c.t, c.uid, verifyingKey, c.Codec(), 200 rmds.MD, extra, expectedRevision, expectedPrevRoot, 201 expectedMergeStatus, expectedBranchID) 202 err := rmds.IsValidAndSigned( 203 context.Background(), c.Codec(), nil, extra, 204 keybase1.OfflineAvailability_NONE) 205 require.NoError(c.t, err) 206 err = rmds.IsLastModifiedBy(c.uid, verifyingKey) 207 require.NoError(c.t, err) 208 } 209 210 func (c testTLFJournalConfig) checkRange(rmdses []rmdsWithExtra, 211 firstRevision kbfsmd.Revision, firstPrevRoot kbfsmd.ID, 212 mStatus kbfsmd.MergeStatus, bid kbfsmd.BranchID) { 213 c.checkMD(rmdses[0].rmds, rmdses[0].extra, firstRevision, 214 firstPrevRoot, mStatus, bid) 215 216 for i := 1; i < len(rmdses); i++ { 217 prevID, err := kbfsmd.MakeID(c.Codec(), rmdses[i-1].rmds.MD) 218 require.NoError(c.t, err) 219 c.checkMD(rmdses[i].rmds, rmdses[i].extra, 220 firstRevision+kbfsmd.Revision(i), prevID, mStatus, bid) 221 err = rmdses[i-1].rmds.MD.CheckValidSuccessor( 222 prevID, rmdses[i].rmds.MD) 223 require.NoError(c.t, err) 224 } 225 } 226 227 func setupTLFJournalTest( 228 t *testing.T, ver kbfsmd.MetadataVer, bwStatus TLFJournalBackgroundWorkStatus) ( 229 tempdir string, config *testTLFJournalConfig, ctx context.Context, 230 cancel context.CancelFunc, tlfJournal *tlfJournal, 231 delegate testBWDelegate) { 232 // Set up config and dependencies. 233 bsplitter, err := data.NewBlockSplitterSimpleExact( 234 64*1024, int(64*1024/data.BPSize), 8*1024) 235 require.NoError(t, err) 236 codec := kbfscodec.NewMsgpack() 237 signingKey := kbfscrypto.MakeFakeSigningKeyOrBust("client sign") 238 cryptPrivateKey := kbfscrypto.MakeFakeCryptPrivateKeyOrBust("client crypt private") 239 crypto := NewCryptoLocal( 240 codec, signingKey, cryptPrivateKey, makeBlockCryptV1()) 241 uid := keybase1.MakeTestUID(1) 242 verifyingKey := signingKey.GetVerifyingKey() 243 ekg := singleEncryptionKeyGetter{kbfscrypto.MakeTLFCryptKey([32]byte{0x1})} 244 245 cig := singleCurrentSessionGetter{ 246 idutil.SessionInfo{ 247 Name: "fake_user", 248 UID: uid, 249 VerifyingKey: verifyingKey, 250 }, 251 } 252 mdserver, err := NewMDServerMemory(newTestMDServerLocalConfig(t, cig)) 253 require.NoError(t, err) 254 255 mockPublisher := NewMockSubscriptionManagerPublisher(gomock.NewController(t)) 256 config = &testTLFJournalConfig{ 257 newTestCodecGetter(), newTestLogMakerWithVDebug(t, libkb.VLog1String), 258 newTestSyncedTlfGetterSetter(), t, 259 tlf.FakeID(1, tlf.Private), bsplitter, crypto, 260 nil, nil, NewMDCacheStandard(10), ver, 261 NewReporterSimple(clocktest.NewTestClockNow(), 10), uid, verifyingKey, ekg, nil, 262 mdserver, defaultDiskLimitMaxDelay + time.Second, 263 mockPublisher, 264 } 265 mockPublisher.EXPECT().PublishChange( 266 keybase1.SubscriptionTopic_FAVORITES).AnyTimes() 267 mockPublisher.EXPECT().PublishChange( 268 keybase1.SubscriptionTopic_JOURNAL_STATUS).AnyTimes() 269 mockPublisher.EXPECT().PublishChange( 270 keybase1.SubscriptionTopic_FILES_TAB_BADGE).AnyTimes() 271 272 ctx, cancel = context.WithTimeout( 273 context.Background(), individualTestTimeout) 274 275 // Clean up the context if the rest of the setup fails. 276 setupSucceeded := false 277 defer func() { 278 if !setupSucceeded { 279 cancel() 280 } 281 }() 282 283 delegate = testBWDelegate{ 284 t: t, 285 testCtx: ctx, 286 stateCh: make(chan bwState), 287 shutdownCh: make(chan struct{}), 288 testDoneCh: make(chan struct{}), 289 } 290 291 tempdir, err = ioutil.TempDir(os.TempDir(), "tlf_journal") 292 require.NoError(t, err) 293 294 // Clean up the tempdir if anything in the rest of the setup 295 // fails. 296 defer func() { 297 if !setupSucceeded { 298 err := ioutil.RemoveAll(tempdir) 299 assert.NoError(t, err) 300 } 301 }() 302 303 delegateBlockServer := NewBlockServerMemory(config.MakeLogger("")) 304 305 diskLimitSemaphore := newSemaphoreDiskLimiter( 306 math.MaxInt64, math.MaxInt64, math.MaxInt64) 307 tlfJournal, err = makeTLFJournal(ctx, uid, verifyingKey, 308 tempdir, config.tlfID, uid.AsUserOrTeam(), config, delegateBlockServer, 309 bwStatus, delegate, nil, nil, diskLimitSemaphore, tlf.NullID) 310 require.NoError(t, err) 311 312 switch bwStatus { 313 case TLFJournalBackgroundWorkEnabled: 314 // Same as the single op case. 315 fallthrough 316 case TLFJournalSingleOpBackgroundWorkEnabled: 317 // Read the state changes triggered by the initial 318 // work signal. 319 delegate.requireNextState(ctx, bwIdle) 320 delegate.requireNextState(ctx, bwBusy) 321 delegate.requireNextState(ctx, bwIdle) 322 323 case TLFJournalBackgroundWorkPaused: 324 delegate.requireNextState(ctx, bwPaused) 325 326 default: 327 require.FailNow(t, "Unknown bwStatus %s", bwStatus) 328 } 329 330 setupSucceeded = true 331 return tempdir, config, ctx, cancel, tlfJournal, delegate 332 } 333 334 func teardownTLFJournalTest( 335 ctx context.Context, tempdir string, config *testTLFJournalConfig, 336 cancel context.CancelFunc, tlfJournal *tlfJournal, 337 delegate testBWDelegate) { 338 // If there are any errant state changes left in the journal, this 339 // will cause them to be aborted. 340 close(delegate.testDoneCh) 341 342 // Shutdown first so we don't get the Done() signal (from the 343 // cancel() call) spuriously. 344 tlfJournal.shutdown(ctx) 345 select { 346 case <-delegate.shutdownCh: 347 case <-ctx.Done(): 348 assert.Fail(config.t, ctx.Err().Error()) 349 } 350 351 cancel() 352 353 select { 354 case bws := <-delegate.stateCh: 355 assert.Fail(config.t, "Unexpected state %s", bws) 356 default: 357 } 358 359 config.mdserver.Shutdown() 360 tlfJournal.delegateBlockServer.Shutdown(ctx) 361 362 err := ioutil.RemoveAll(tempdir) 363 assert.NoError(config.t, err) 364 } 365 366 func putOneMD(ctx context.Context, config *testTLFJournalConfig, 367 tlfJournal *tlfJournal) { 368 md := config.makeMD(kbfsmd.RevisionInitial, kbfsmd.ID{}) 369 _, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 370 require.NoError(config.t, err) 371 } 372 373 // The tests below primarily test the background work thread's 374 // behavior. 375 376 func testTLFJournalBasic(t *testing.T, ver kbfsmd.MetadataVer) { 377 tempdir, config, ctx, cancel, tlfJournal, delegate := 378 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 379 defer teardownTLFJournalTest( 380 ctx, tempdir, config, cancel, tlfJournal, delegate) 381 382 putOneMD(ctx, config, tlfJournal) 383 384 // Wait for it to be processed. 385 386 delegate.requireNextState(ctx, bwBusy) 387 delegate.requireNextState(ctx, bwIdle) 388 } 389 390 func testTLFJournalPauseResume(t *testing.T, ver kbfsmd.MetadataVer) { 391 tempdir, config, ctx, cancel, tlfJournal, delegate := 392 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 393 defer teardownTLFJournalTest( 394 ctx, tempdir, config, cancel, tlfJournal, delegate) 395 396 tlfJournal.pauseBackgroundWork() 397 delegate.requireNextState(ctx, bwPaused) 398 399 putOneMD(ctx, config, tlfJournal) 400 401 // Unpause and wait for it to be processed. 402 403 tlfJournal.resumeBackgroundWork() 404 delegate.requireNextState(ctx, bwIdle) 405 delegate.requireNextState(ctx, bwBusy) 406 delegate.requireNextState(ctx, bwIdle) 407 } 408 409 func testTLFJournalPauseShutdown(t *testing.T, ver kbfsmd.MetadataVer) { 410 tempdir, config, ctx, cancel, tlfJournal, delegate := 411 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 412 defer teardownTLFJournalTest( 413 ctx, tempdir, config, cancel, tlfJournal, delegate) 414 415 tlfJournal.pauseBackgroundWork() 416 delegate.requireNextState(ctx, bwPaused) 417 418 putOneMD(ctx, config, tlfJournal) 419 420 // Should still be able to shut down while paused. 421 } 422 423 type hangingBlockServer struct { 424 BlockServer 425 // Closed on put. 426 onPutCh chan struct{} 427 } 428 429 func (bs hangingBlockServer) Put( 430 ctx context.Context, tlfID tlf.ID, id kbfsblock.ID, 431 context kbfsblock.Context, 432 buf []byte, serverHalf kbfscrypto.BlockCryptKeyServerHalf, 433 _ DiskBlockCacheType) error { 434 close(bs.onPutCh) 435 // Hang until the context is cancelled. 436 <-ctx.Done() 437 return ctx.Err() 438 } 439 440 func (bs hangingBlockServer) waitForPut(ctx context.Context, t *testing.T) { 441 select { 442 case <-bs.onPutCh: 443 case <-ctx.Done(): 444 require.FailNow(t, ctx.Err().Error()) 445 } 446 } 447 448 func putBlock(ctx context.Context, 449 t *testing.T, config *testTLFJournalConfig, 450 tlfJournal *tlfJournal, data []byte) { 451 id, bCtx, serverHalf := config.makeBlock(data) 452 err := tlfJournal.putBlockData(ctx, id, bCtx, data, serverHalf) 453 require.NoError(t, err) 454 } 455 456 func testTLFJournalBlockOpBasic(t *testing.T, ver kbfsmd.MetadataVer) { 457 tempdir, config, ctx, cancel, tlfJournal, delegate := 458 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 459 defer teardownTLFJournalTest( 460 ctx, tempdir, config, cancel, tlfJournal, delegate) 461 462 putBlock(ctx, t, config, tlfJournal, []byte{1, 2, 3, 4}) 463 numFlushed, rev, converted, err := 464 tlfJournal.flushBlockEntries(ctx, firstValidJournalOrdinal+1, 465 kbfsmd.ID{}) 466 require.NoError(t, err) 467 require.Equal(t, 1, numFlushed) 468 require.Equal(t, rev, kbfsmd.RevisionUninitialized) 469 require.False(t, converted) 470 } 471 472 func testTLFJournalBlockOpBusyPause(t *testing.T, ver kbfsmd.MetadataVer) { 473 tempdir, config, ctx, cancel, tlfJournal, delegate := 474 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 475 defer teardownTLFJournalTest( 476 ctx, tempdir, config, cancel, tlfJournal, delegate) 477 478 bs := hangingBlockServer{tlfJournal.delegateBlockServer, 479 make(chan struct{})} 480 tlfJournal.delegateBlockServer = bs 481 482 putBlock(ctx, t, config, tlfJournal, []byte{1, 2, 3, 4}) 483 484 bs.waitForPut(ctx, t) 485 delegate.requireNextState(ctx, bwBusy) 486 487 // Should still be able to pause while busy. 488 489 tlfJournal.pauseBackgroundWork() 490 delegate.requireNextState(ctx, bwPaused) 491 } 492 493 func testTLFJournalBlockOpBusyShutdown(t *testing.T, ver kbfsmd.MetadataVer) { 494 tempdir, config, ctx, cancel, tlfJournal, delegate := 495 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 496 defer teardownTLFJournalTest( 497 ctx, tempdir, config, cancel, tlfJournal, delegate) 498 499 bs := hangingBlockServer{tlfJournal.delegateBlockServer, 500 make(chan struct{})} 501 tlfJournal.delegateBlockServer = bs 502 503 putBlock(ctx, t, config, tlfJournal, []byte{1, 2, 3, 4}) 504 505 bs.waitForPut(ctx, t) 506 delegate.requireNextState(ctx, bwBusy) 507 508 // Should still be able to shut down while busy. 509 } 510 511 func testTLFJournalSecondBlockOpWhileBusy(t *testing.T, ver kbfsmd.MetadataVer) { 512 tempdir, config, ctx, cancel, tlfJournal, delegate := 513 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 514 defer teardownTLFJournalTest( 515 ctx, tempdir, config, cancel, tlfJournal, delegate) 516 517 bs := hangingBlockServer{tlfJournal.delegateBlockServer, 518 make(chan struct{})} 519 tlfJournal.delegateBlockServer = bs 520 521 putBlock(ctx, t, config, tlfJournal, []byte{1, 2, 3, 4}) 522 523 bs.waitForPut(ctx, t) 524 delegate.requireNextState(ctx, bwBusy) 525 526 // Should still be able to put a second block while busy. 527 putBlock(ctx, t, config, tlfJournal, []byte{1, 2, 3, 4, 5}) 528 } 529 530 func testTLFJournalBlockOpDiskByteLimit(t *testing.T, ver kbfsmd.MetadataVer) { 531 tempdir, config, ctx, cancel, tlfJournal, delegate := 532 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 533 defer teardownTLFJournalTest( 534 ctx, tempdir, config, cancel, tlfJournal, delegate) 535 536 tlfJournal.diskLimiter.onJournalEnable( 537 ctx, math.MaxInt64-6, 0, 0, tlfJournal.uid.AsUserOrTeam()) 538 539 putBlock(ctx, t, config, tlfJournal, []byte{1, 2, 3, 4}) 540 541 errCh := make(chan error, 1) 542 go func() { 543 data2 := []byte{5, 6, 7} 544 id, bCtx, serverHalf := config.makeBlock(data2) 545 errCh <- tlfJournal.putBlockData( 546 ctx, id, bCtx, data2, serverHalf) 547 }() 548 549 numFlushed, rev, converted, err := 550 tlfJournal.flushBlockEntries(ctx, firstValidJournalOrdinal+1, 551 kbfsmd.ID{}) 552 require.NoError(t, err) 553 require.Equal(t, 1, numFlushed) 554 require.Equal(t, rev, kbfsmd.RevisionUninitialized) 555 require.False(t, converted) 556 557 // Fake an MD flush. 558 md := config.makeMD(kbfsmd.RevisionInitial, kbfsmd.ID{}) 559 err = tlfJournal.doOnMDFlushAndRemoveFlushedMDEntry( 560 ctx, kbfsmd.ID{}, &RootMetadataSigned{RootMetadataSigned: kbfsmd.RootMetadataSigned{MD: md.bareMd}}) 561 require.Error(t, err) 562 require.Equal(t, "mdJournal unexpectedly empty", err.Error()) 563 564 select { 565 case err := <-errCh: 566 require.NoError(t, err) 567 case <-ctx.Done(): 568 t.Fatal(ctx.Err()) 569 } 570 } 571 572 func testTLFJournalBlockOpDiskFileLimit(t *testing.T, ver kbfsmd.MetadataVer) { 573 tempdir, config, ctx, cancel, tlfJournal, delegate := 574 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 575 defer teardownTLFJournalTest( 576 ctx, tempdir, config, cancel, tlfJournal, delegate) 577 578 tlfJournal.diskLimiter.onJournalEnable( 579 ctx, 0, 0, math.MaxInt64-2*filesPerBlockMax+1, 580 tlfJournal.uid.AsUserOrTeam()) 581 582 putBlock(ctx, t, config, tlfJournal, []byte{1, 2, 3, 4}) 583 584 errCh := make(chan error, 1) 585 go func() { 586 data2 := []byte{5, 6, 7} 587 id, bCtx, serverHalf := config.makeBlock(data2) 588 errCh <- tlfJournal.putBlockData( 589 ctx, id, bCtx, data2, serverHalf) 590 }() 591 592 numFlushed, rev, converted, err := 593 tlfJournal.flushBlockEntries(ctx, firstValidJournalOrdinal+1, 594 kbfsmd.ID{}) 595 require.NoError(t, err) 596 require.Equal(t, 1, numFlushed) 597 require.Equal(t, rev, kbfsmd.RevisionUninitialized) 598 require.False(t, converted) 599 600 // Fake an MD flush. 601 md := config.makeMD(kbfsmd.RevisionInitial, kbfsmd.ID{}) 602 err = tlfJournal.doOnMDFlushAndRemoveFlushedMDEntry( 603 ctx, kbfsmd.ID{}, &RootMetadataSigned{RootMetadataSigned: kbfsmd.RootMetadataSigned{MD: md.bareMd}}) 604 require.Error(t, err) 605 require.Equal(t, "mdJournal unexpectedly empty", err.Error()) 606 607 select { 608 case err := <-errCh: 609 require.NoError(t, err) 610 case <-ctx.Done(): 611 t.Fatal(ctx.Err()) 612 } 613 } 614 615 func testTLFJournalBlockOpDiskQuotaLimit(t *testing.T, ver kbfsmd.MetadataVer) { 616 tempdir, config, ctx, cancel, tlfJournal, delegate := 617 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 618 defer teardownTLFJournalTest( 619 ctx, tempdir, config, cancel, tlfJournal, delegate) 620 621 tlfJournal.diskLimiter.onJournalEnable( 622 ctx, 0, math.MaxInt64-6, 0, tlfJournal.uid.AsUserOrTeam()) 623 624 data1 := []byte{1, 2, 3, 4} 625 putBlock(ctx, t, config, tlfJournal, data1) 626 627 usedQuotaBytes, quotaBytes := 628 tlfJournal.diskLimiter.getQuotaInfo(tlfJournal.uid.AsUserOrTeam()) 629 require.Equal(t, 630 int64(math.MaxInt64-6)+int64(len(data1)), usedQuotaBytes) 631 require.Equal(t, int64(math.MaxInt64), quotaBytes) 632 633 data2 := []byte{5, 6, 7} 634 errCh := make(chan error, 1) 635 go func() { 636 id, bCtx, serverHalf := config.makeBlock(data2) 637 errCh <- tlfJournal.putBlockData( 638 ctx, id, bCtx, data2, serverHalf) 639 }() 640 641 numFlushed, rev, converted, err := 642 tlfJournal.flushBlockEntries(ctx, firstValidJournalOrdinal+1, 643 kbfsmd.ID{}) 644 require.NoError(t, err) 645 require.Equal(t, 1, numFlushed) 646 require.Equal(t, rev, kbfsmd.RevisionUninitialized) 647 require.False(t, converted) 648 649 select { 650 case err := <-errCh: 651 require.NoError(t, err) 652 case <-ctx.Done(): 653 t.Fatal(ctx.Err()) 654 } 655 656 usedQuotaBytes, quotaBytes = 657 tlfJournal.diskLimiter.getQuotaInfo(tlfJournal.uid.AsUserOrTeam()) 658 require.Equal(t, 659 int64(math.MaxInt64-6)+int64(len(data2)), usedQuotaBytes) 660 require.Equal(t, int64(math.MaxInt64), quotaBytes) 661 } 662 663 func testTLFJournalBlockOpDiskQuotaLimitResolve(t *testing.T, ver kbfsmd.MetadataVer) { 664 tempdir, config, ctx, cancel, tlfJournal, delegate := 665 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 666 defer teardownTLFJournalTest( 667 ctx, tempdir, config, cancel, tlfJournal, delegate) 668 669 tlfJournal.diskLimiter.onJournalEnable( 670 ctx, 0, math.MaxInt64-6, 0, tlfJournal.uid.AsUserOrTeam()) 671 672 data1 := []byte{1, 2, 3, 4} 673 id1, bCtx1, serverHalf1 := config.makeBlock(data1) 674 err := tlfJournal.putBlockData(ctx, id1, bCtx1, data1, serverHalf1) 675 require.NoError(t, err) 676 677 usedQuotaBytes, quotaBytes := 678 tlfJournal.diskLimiter.getQuotaInfo(tlfJournal.uid.AsUserOrTeam()) 679 require.Equal(t, 680 int64(math.MaxInt64-6)+int64(len(data1)), usedQuotaBytes) 681 require.Equal(t, int64(math.MaxInt64), quotaBytes) 682 683 data2 := []byte{5, 6, 7} 684 errCh := make(chan error, 1) 685 go func() { 686 id2, bCtx2, serverHalf2 := config.makeBlock(data2) 687 errCh <- tlfJournal.putBlockData( 688 ctx, id2, bCtx2, data2, serverHalf2) 689 }() 690 691 md1 := config.makeMD(kbfsmd.RevisionInitial, kbfsmd.ID{}) 692 irmd, err := tlfJournal.putMD(ctx, md1, tlfJournal.key, nil) 693 require.NoError(t, err) 694 mdID1 := irmd.mdID 695 696 err = tlfJournal.convertMDsToBranch(ctx) 697 require.NoError(t, err) 698 699 bid, err := tlfJournal.getBranchID() 700 require.NoError(t, err) 701 702 // Ignore the block instead of flushing it. 703 md2 := config.makeMD(kbfsmd.RevisionInitial+1, mdID1) 704 _, retry, err := tlfJournal.doResolveBranch( 705 ctx, bid, []kbfsblock.ID{id1}, md2, 706 unflushedPathMDInfo{}, unflushedPathsPerRevMap{}, tlfJournal.key, nil) 707 require.NoError(t, err) 708 require.False(t, retry) 709 710 select { 711 case err := <-errCh: 712 require.NoError(t, err) 713 case <-ctx.Done(): 714 t.Fatal(ctx.Err()) 715 } 716 717 usedQuotaBytes, quotaBytes = 718 tlfJournal.diskLimiter.getQuotaInfo(tlfJournal.uid.AsUserOrTeam()) 719 require.Equal(t, 720 int64(math.MaxInt64-6)+int64(len(data2)), usedQuotaBytes) 721 require.Equal(t, int64(math.MaxInt64), quotaBytes) 722 } 723 724 func testTLFJournalBlockOpDiskLimitDuplicate(t *testing.T, ver kbfsmd.MetadataVer) { 725 tempdir, config, ctx, cancel, tlfJournal, delegate := 726 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 727 defer teardownTLFJournalTest( 728 ctx, tempdir, config, cancel, tlfJournal, delegate) 729 730 tlfJournal.diskLimiter.onJournalEnable( 731 ctx, math.MaxInt64-8, 0, math.MaxInt64-2*filesPerBlockMax, 732 tlfJournal.uid.AsUserOrTeam()) 733 734 data := []byte{1, 2, 3, 4} 735 id, bCtx, serverHalf := config.makeBlock(data) 736 err := tlfJournal.putBlockData(ctx, id, bCtx, data, serverHalf) 737 require.NoError(t, err) 738 739 // This should acquire some bytes and files, but then release 740 // them. 741 err = tlfJournal.putBlockData(ctx, id, bCtx, data, serverHalf) 742 require.NoError(t, err) 743 744 // If the above incorrectly does not release bytes or files, 745 // this will hang. 746 err = tlfJournal.putBlockData(ctx, id, bCtx, data, serverHalf) 747 require.NoError(t, err) 748 } 749 750 func testTLFJournalBlockOpDiskLimitCancel(t *testing.T, ver kbfsmd.MetadataVer) { 751 tempdir, config, ctx, cancel, tlfJournal, delegate := 752 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 753 defer teardownTLFJournalTest( 754 ctx, tempdir, config, cancel, tlfJournal, delegate) 755 756 tlfJournal.diskLimiter.onJournalEnable( 757 ctx, math.MaxInt64, 0, 0, tlfJournal.uid.AsUserOrTeam()) 758 759 ctx2, cancel2 := context.WithCancel(ctx) 760 cancel2() 761 762 data := []byte{1, 2, 3, 4} 763 id, bCtx, serverHalf := config.makeBlock(data) 764 err := tlfJournal.putBlockData(ctx2, id, bCtx, data, serverHalf) 765 require.Equal(t, context.Canceled, errors.Cause(err)) 766 } 767 768 func testTLFJournalBlockOpDiskLimitTimeout(t *testing.T, ver kbfsmd.MetadataVer) { 769 tempdir, config, ctx, cancel, tlfJournal, delegate := 770 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 771 defer teardownTLFJournalTest( 772 ctx, tempdir, config, cancel, tlfJournal, delegate) 773 774 tlfJournal.diskLimiter.onJournalEnable( 775 ctx, math.MaxInt64, 0, math.MaxInt64-1, tlfJournal.uid.AsUserOrTeam()) 776 config.dlTimeout = 3 * time.Microsecond 777 778 data := []byte{1, 2, 3, 4} 779 id, bCtx, serverHalf := config.makeBlock(data) 780 putCtx := context.Background() // rely on default disk limit timeout 781 err := tlfJournal.putBlockData(putCtx, id, bCtx, data, serverHalf) 782 timeoutErr, ok := errors.Cause(err).(*ErrDiskLimitTimeout) 783 require.True(t, ok) 784 require.Error(t, timeoutErr.err) 785 timeoutErr.err = nil 786 require.Equal(t, ErrDiskLimitTimeout{ 787 3 * time.Microsecond, int64(len(data)), 788 filesPerBlockMax, 0, 1, 0, 1, math.MaxInt64, math.MaxInt64, nil, false, 789 }, *timeoutErr) 790 } 791 792 func testTLFJournalBlockOpDiskLimitPutFailure(t *testing.T, ver kbfsmd.MetadataVer) { 793 tempdir, config, ctx, cancel, tlfJournal, delegate := 794 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 795 defer teardownTLFJournalTest( 796 ctx, tempdir, config, cancel, tlfJournal, delegate) 797 798 tlfJournal.diskLimiter.onJournalEnable( 799 ctx, math.MaxInt64-6, 0, math.MaxInt64-filesPerBlockMax, 800 tlfJournal.uid.AsUserOrTeam()) 801 802 data := []byte{1, 2, 3, 4} 803 id, bCtx, serverHalf := config.makeBlock(data) 804 err := tlfJournal.putBlockData(ctx, id, bCtx, []byte{1}, serverHalf) 805 require.IsType(t, kbfshash.HashMismatchError{}, errors.Cause(err)) 806 807 // If the above incorrectly does not release bytes or files from 808 // diskLimiter on error, this will hang. 809 err = tlfJournal.putBlockData(ctx, id, bCtx, data, serverHalf) 810 require.NoError(t, err) 811 } 812 813 type hangingMDServer struct { 814 MDServer 815 // Closed on put. 816 onPutCh chan struct{} 817 } 818 819 func (md hangingMDServer) Put(ctx context.Context, rmds *RootMetadataSigned, 820 _ kbfsmd.ExtraMetadata, _ *keybase1.LockContext, _ keybase1.MDPriority) error { 821 close(md.onPutCh) 822 // Hang until the context is cancelled. 823 <-ctx.Done() 824 return ctx.Err() 825 } 826 827 func (md hangingMDServer) waitForPut(ctx context.Context, t *testing.T) { 828 select { 829 case <-md.onPutCh: 830 case <-ctx.Done(): 831 require.FailNow(t, ctx.Err().Error()) 832 } 833 } 834 835 func testTLFJournalMDServerBusyPause(t *testing.T, ver kbfsmd.MetadataVer) { 836 tempdir, config, ctx, cancel, tlfJournal, delegate := 837 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 838 defer teardownTLFJournalTest( 839 ctx, tempdir, config, cancel, tlfJournal, delegate) 840 841 mdserver := hangingMDServer{config.MDServer(), make(chan struct{})} 842 config.mdserver = mdserver 843 844 md := config.makeMD(kbfsmd.RevisionInitial, kbfsmd.ID{}) 845 _, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 846 require.NoError(t, err) 847 848 mdserver.waitForPut(ctx, t) 849 delegate.requireNextState(ctx, bwBusy) 850 851 // Should still be able to pause while busy. 852 853 tlfJournal.pauseBackgroundWork() 854 delegate.requireNextState(ctx, bwPaused) 855 } 856 857 func testTLFJournalMDServerBusyShutdown(t *testing.T, ver kbfsmd.MetadataVer) { 858 tempdir, config, ctx, cancel, tlfJournal, delegate := 859 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 860 defer teardownTLFJournalTest( 861 ctx, tempdir, config, cancel, tlfJournal, delegate) 862 863 mdserver := hangingMDServer{config.MDServer(), make(chan struct{})} 864 config.mdserver = mdserver 865 866 md := config.makeMD(kbfsmd.RevisionInitial, kbfsmd.ID{}) 867 _, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 868 require.NoError(t, err) 869 870 mdserver.waitForPut(ctx, t) 871 delegate.requireNextState(ctx, bwBusy) 872 873 // Should still be able to shutdown while busy. 874 } 875 876 func testTLFJournalBlockOpWhileBusy(t *testing.T, ver kbfsmd.MetadataVer) { 877 tempdir, config, ctx, cancel, tlfJournal, delegate := 878 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkEnabled) 879 defer teardownTLFJournalTest( 880 ctx, tempdir, config, cancel, tlfJournal, delegate) 881 882 mdserver := hangingMDServer{config.MDServer(), make(chan struct{})} 883 config.mdserver = mdserver 884 885 md := config.makeMD(kbfsmd.RevisionInitial, kbfsmd.ID{}) 886 _, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 887 require.NoError(t, err) 888 889 mdserver.waitForPut(ctx, t) 890 delegate.requireNextState(ctx, bwBusy) 891 892 // Should still be able to put a block while busy. 893 putBlock(ctx, t, config, tlfJournal, []byte{1, 2, 3, 4}) 894 } 895 896 type rmdsWithExtra struct { 897 rmds *RootMetadataSigned 898 extra kbfsmd.ExtraMetadata 899 } 900 901 type shimMDServer struct { 902 MDServer 903 rmdses []rmdsWithExtra 904 nextGetRange []*RootMetadataSigned 905 nextErr error 906 getForTLFCalled bool 907 } 908 909 func (s *shimMDServer) GetRange( 910 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus, 911 start, stop kbfsmd.Revision, _ *keybase1.LockID) ([]*RootMetadataSigned, error) { 912 rmdses := s.nextGetRange 913 s.nextGetRange = nil 914 return rmdses, nil 915 } 916 917 func (s *shimMDServer) Put(ctx context.Context, rmds *RootMetadataSigned, 918 extra kbfsmd.ExtraMetadata, _ *keybase1.LockContext, _ keybase1.MDPriority) error { 919 if s.nextErr != nil { 920 err := s.nextErr 921 s.nextErr = nil 922 return err 923 } 924 s.rmdses = append(s.rmdses, rmdsWithExtra{rmds, extra}) 925 926 // Pretend all cancels happen after the actual put. 927 select { 928 case <-ctx.Done(): 929 return ctx.Err() 930 default: 931 } 932 return nil 933 } 934 935 func (s *shimMDServer) GetForTLF( 936 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus, _ *keybase1.LockID) ( 937 *RootMetadataSigned, error) { 938 s.getForTLFCalled = true 939 if len(s.rmdses) == 0 { 940 return nil, nil 941 } 942 return s.rmdses[len(s.rmdses)-1].rmds, nil 943 } 944 945 func (s *shimMDServer) IsConnected() bool { 946 return true 947 } 948 949 func (s *shimMDServer) Shutdown() { 950 } 951 952 func requireJournalEntryCounts(t *testing.T, j *tlfJournal, 953 expectedBlockEntryCount, expectedMDEntryCount uint64) { 954 blockEntryCount, mdEntryCount, err := j.getJournalEntryCounts() 955 require.NoError(t, err) 956 require.Equal(t, expectedBlockEntryCount, blockEntryCount) 957 require.Equal(t, expectedMDEntryCount, mdEntryCount) 958 } 959 960 // The tests below test tlfJournal's MD flushing behavior. 961 962 func testTLFJournalFlushMDBasic(t *testing.T, ver kbfsmd.MetadataVer) { 963 tempdir, config, ctx, cancel, tlfJournal, delegate := 964 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 965 defer teardownTLFJournalTest( 966 ctx, tempdir, config, cancel, tlfJournal, delegate) 967 968 firstRevision := kbfsmd.Revision(10) 969 firstPrevRoot := kbfsmd.FakeID(1) 970 mdCount := 10 971 972 prevRoot := firstPrevRoot 973 for i := 0; i < mdCount; i++ { 974 revision := firstRevision + kbfsmd.Revision(i) 975 md := config.makeMD(revision, prevRoot) 976 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 977 require.NoError(t, err) 978 prevRoot = irmd.mdID 979 } 980 981 // Flush all entries. 982 var mdserver shimMDServer 983 config.mdserver = &mdserver 984 985 _, mdEnd, _, err := tlfJournal.getJournalEnds(ctx) 986 require.NoError(t, err) 987 988 for i := 0; i < mdCount; i++ { 989 flushed, err := tlfJournal.flushOneMDOp(ctx, mdEnd, defaultFlushContext()) 990 require.NoError(t, err) 991 require.True(t, flushed) 992 } 993 flushed, err := tlfJournal.flushOneMDOp(ctx, mdEnd, defaultFlushContext()) 994 require.NoError(t, err) 995 require.False(t, flushed) 996 requireJournalEntryCounts(t, tlfJournal, uint64(mdCount), 0) 997 testMDJournalGCd(t, tlfJournal.mdJournal) 998 999 // Check RMDSes on the server. 1000 1001 rmdses := mdserver.rmdses 1002 require.Equal(t, mdCount, len(rmdses)) 1003 config.checkRange( 1004 rmdses, firstRevision, firstPrevRoot, kbfsmd.Merged, kbfsmd.NullBranchID) 1005 } 1006 1007 func testTLFJournalFlushMDConflict(t *testing.T, ver kbfsmd.MetadataVer) { 1008 tempdir, config, ctx, cancel, tlfJournal, delegate := 1009 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1010 defer teardownTLFJournalTest( 1011 ctx, tempdir, config, cancel, tlfJournal, delegate) 1012 1013 firstRevision := kbfsmd.Revision(10) 1014 firstPrevRoot := kbfsmd.FakeID(1) 1015 mdCount := 10 1016 1017 prevRoot := firstPrevRoot 1018 for i := 0; i < mdCount/2; i++ { 1019 revision := firstRevision + kbfsmd.Revision(i) 1020 md := config.makeMD(revision, prevRoot) 1021 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1022 require.NoError(t, err) 1023 prevRoot = irmd.mdID 1024 } 1025 1026 var mdserver shimMDServer 1027 mdserver.nextErr = kbfsmd.ServerErrorConflictRevision{} 1028 config.mdserver = &mdserver 1029 1030 _, mdEnd, _, err := tlfJournal.getJournalEnds(ctx) 1031 require.NoError(t, err) 1032 1033 // Simulate a flush with a conflict error halfway through. 1034 { 1035 flushed, err := tlfJournal.flushOneMDOp(ctx, mdEnd, defaultFlushContext()) 1036 require.NoError(t, err) 1037 require.False(t, flushed) 1038 1039 revision := firstRevision + kbfsmd.Revision(mdCount/2) 1040 md := config.makeMD(revision, prevRoot) 1041 _, err = tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1042 require.IsType(t, MDJournalConflictError{}, err) 1043 1044 md.SetUnmerged() 1045 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1046 require.NoError(t, err) 1047 prevRoot = irmd.mdID 1048 } 1049 1050 for i := mdCount/2 + 1; i < mdCount; i++ { 1051 revision := firstRevision + kbfsmd.Revision(i) 1052 md := config.makeMD(revision, prevRoot) 1053 md.SetUnmerged() 1054 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1055 require.NoError(t, err) 1056 prevRoot = irmd.mdID 1057 } 1058 1059 // The journal won't flush anything while on a branch. 1060 requireJournalEntryCounts(t, tlfJournal, uint64(mdCount), uint64(mdCount)) 1061 } 1062 1063 // orderedBlockServer and orderedMDServer appends onto their shared 1064 // puts slice when their Put() methods are called. 1065 1066 type orderedBlockServer struct { 1067 BlockServer 1068 lock *sync.Mutex 1069 puts *[]interface{} 1070 onceOnPut func() 1071 } 1072 1073 func (s *orderedBlockServer) Put( 1074 ctx context.Context, tlfID tlf.ID, id kbfsblock.ID, 1075 context kbfsblock.Context, 1076 buf []byte, serverHalf kbfscrypto.BlockCryptKeyServerHalf, 1077 _ DiskBlockCacheType) error { 1078 s.lock.Lock() 1079 defer s.lock.Unlock() 1080 *s.puts = append(*s.puts, id) 1081 if s.onceOnPut != nil { 1082 s.onceOnPut() 1083 s.onceOnPut = nil 1084 } 1085 return nil 1086 } 1087 1088 func (s *orderedBlockServer) Shutdown(context.Context) {} 1089 1090 type orderedMDServer struct { 1091 MDServer 1092 lock *sync.Mutex 1093 puts *[]interface{} 1094 onceOnPut func() error 1095 } 1096 1097 func (s *orderedMDServer) Put( 1098 ctx context.Context, rmds *RootMetadataSigned, _ kbfsmd.ExtraMetadata, 1099 _ *keybase1.LockContext, _ keybase1.MDPriority) error { 1100 s.lock.Lock() 1101 defer s.lock.Unlock() 1102 *s.puts = append(*s.puts, rmds.MD.RevisionNumber()) 1103 if s.onceOnPut != nil { 1104 err := s.onceOnPut() 1105 s.onceOnPut = nil 1106 if err != nil { 1107 return err 1108 } 1109 } 1110 return nil 1111 } 1112 1113 func (s *orderedMDServer) Shutdown() {} 1114 1115 func testTLFJournalGCd(t *testing.T, tlfJournal *tlfJournal) { 1116 // The root dir shouldn't exist. 1117 _, err := ioutil.Stat(tlfJournal.dir) 1118 require.True(t, ioutil.IsNotExist(err)) 1119 1120 func() { 1121 tlfJournal.journalLock.Lock() 1122 defer tlfJournal.journalLock.Unlock() 1123 unflushedPaths := tlfJournal.unflushedPaths.getUnflushedPaths() 1124 require.Nil(t, unflushedPaths) 1125 require.Equal(t, uint64(0), tlfJournal.unsquashedBytes) 1126 require.Equal(t, 0, len(tlfJournal.flushingBlocks)) 1127 }() 1128 1129 requireJournalEntryCounts(t, tlfJournal, 0, 0) 1130 1131 // Check child journals. 1132 testBlockJournalGCd(t, tlfJournal.blockJournal) 1133 testMDJournalGCd(t, tlfJournal.mdJournal) 1134 } 1135 1136 // testTLFJournalFlushOrdering tests that we respect the relative 1137 // orderings of blocks and MD ops when flushing, i.e. if a block op 1138 // was added to the block journal before an MD op was added to the MD 1139 // journal, then that block op will be flushed before that MD op. 1140 func testTLFJournalFlushOrdering(t *testing.T, ver kbfsmd.MetadataVer) { 1141 tempdir, config, ctx, cancel, tlfJournal, delegate := 1142 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1143 defer teardownTLFJournalTest( 1144 ctx, tempdir, config, cancel, tlfJournal, delegate) 1145 1146 bid1, bCtx1, serverHalf1 := config.makeBlock([]byte{1}) 1147 bid2, bCtx2, serverHalf2 := config.makeBlock([]byte{2}) 1148 bid3, bCtx3, serverHalf3 := config.makeBlock([]byte{3}) 1149 1150 md1 := config.makeMD(kbfsmd.Revision(10), kbfsmd.FakeID(1)) 1151 1152 var lock sync.Mutex 1153 var puts []interface{} 1154 1155 bserver := orderedBlockServer{ 1156 lock: &lock, 1157 puts: &puts, 1158 } 1159 1160 tlfJournal.delegateBlockServer.Shutdown(ctx) 1161 tlfJournal.delegateBlockServer = &bserver 1162 1163 mdserver := orderedMDServer{ 1164 lock: &lock, 1165 puts: &puts, 1166 } 1167 1168 config.mdserver = &mdserver 1169 1170 // bid1 is-put-before kbfsmd.Revision(10). 1171 err := tlfJournal.putBlockData( 1172 ctx, bid1, bCtx1, []byte{1}, serverHalf1) 1173 require.NoError(t, err) 1174 irmd, err := tlfJournal.putMD(ctx, md1, tlfJournal.key, nil) 1175 require.NoError(t, err) 1176 prevRoot := irmd.mdID 1177 1178 bserver.onceOnPut = func() { 1179 // bid2 is-put-before kbfsmd.Revision(11). 1180 err := tlfJournal.putBlockData( 1181 ctx, bid2, bCtx2, []byte{2}, serverHalf2) 1182 require.NoError(t, err) 1183 md2 := config.makeMD(kbfsmd.Revision(11), prevRoot) 1184 irmd, err := tlfJournal.putMD(ctx, md2, tlfJournal.key, nil) 1185 require.NoError(t, err) 1186 prevRoot = irmd.mdID 1187 } 1188 1189 mdserver.onceOnPut = func() error { 1190 // bid3 is-put-before kbfsmd.Revision(12). 1191 err := tlfJournal.putBlockData( 1192 ctx, bid3, bCtx3, []byte{3}, serverHalf3) 1193 require.NoError(t, err) 1194 md3 := config.makeMD(kbfsmd.Revision(12), prevRoot) 1195 irmd, err := tlfJournal.putMD(ctx, md3, tlfJournal.key, nil) 1196 require.NoError(t, err) 1197 prevRoot = irmd.mdID 1198 return nil 1199 } 1200 1201 err = tlfJournal.flush(ctx) 1202 require.NoError(t, err) 1203 testTLFJournalGCd(t, tlfJournal) 1204 1205 // These two orderings depend on the exact flushing process, 1206 // but there are other possible orderings which respect the 1207 // above is-put-before constraints and also respect the 1208 // kbfsmd.Revision ordering. 1209 expectedPuts1 := []interface{}{ 1210 bid1, kbfsmd.Revision(10), bid2, bid3, 1211 kbfsmd.Revision(11), kbfsmd.Revision(12), 1212 } 1213 // This is possible since block puts are done in parallel. 1214 expectedPuts2 := []interface{}{ 1215 bid1, kbfsmd.Revision(10), bid3, bid2, 1216 kbfsmd.Revision(11), kbfsmd.Revision(12), 1217 } 1218 require.True(t, reflect.DeepEqual(puts, expectedPuts1) || 1219 reflect.DeepEqual(puts, expectedPuts2), 1220 "Expected %v or %v, got %v", expectedPuts1, 1221 expectedPuts2, puts) 1222 } 1223 1224 // testTLFJournalFlushOrderingAfterSquashAndCR tests that after a 1225 // branch is squashed multiple times, and then hits a conflict, the 1226 // blocks are flushed completely before the conflict-resolving MD. 1227 func testTLFJournalFlushOrderingAfterSquashAndCR( 1228 t *testing.T, ver kbfsmd.MetadataVer) { 1229 tempdir, config, ctx, cancel, tlfJournal, delegate := 1230 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1231 defer teardownTLFJournalTest( 1232 ctx, tempdir, config, cancel, tlfJournal, delegate) 1233 tlfJournal.forcedSquashByBytes = 20 1234 1235 firstRev := kbfsmd.Revision(10) 1236 firstPrevRoot := kbfsmd.FakeID(1) 1237 md1 := config.makeMD(firstRev, firstPrevRoot) 1238 1239 var lock sync.Mutex 1240 var puts []interface{} 1241 1242 bserver := orderedBlockServer{ 1243 lock: &lock, 1244 puts: &puts, 1245 } 1246 1247 tlfJournal.delegateBlockServer.Shutdown(ctx) 1248 tlfJournal.delegateBlockServer = &bserver 1249 1250 var mdserverShim shimMDServer 1251 mdserver := orderedMDServer{ 1252 MDServer: &mdserverShim, 1253 lock: &lock, 1254 puts: &puts, 1255 } 1256 1257 config.mdserver = &mdserver 1258 1259 // Put almost a full batch worth of block before revs 10 and 11. 1260 blockEnd := uint64(maxJournalBlockFlushBatchSize - 1) 1261 for i := uint64(0); i < blockEnd; i++ { 1262 data := []byte{byte(i)} 1263 bid, bCtx, serverHalf := config.makeBlock(data) 1264 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1265 require.NoError(t, err) 1266 } 1267 1268 irmd, err := tlfJournal.putMD(ctx, md1, tlfJournal.key, nil) 1269 require.NoError(t, err) 1270 prevRoot := irmd.mdID 1271 md2 := config.makeMD(firstRev+1, prevRoot) 1272 require.NoError(t, err) 1273 irmd, err = tlfJournal.putMD(ctx, md2, tlfJournal.key, nil) 1274 require.NoError(t, err) 1275 1276 // Squash revs 10 and 11. No blocks should actually be flushed 1277 // yet. 1278 err = tlfJournal.flush(ctx) 1279 require.NoError(t, err) 1280 require.Equal( 1281 t, kbfsmd.PendingLocalSquashBranchID, tlfJournal.mdJournal.getBranchID()) 1282 requireJournalEntryCounts(t, tlfJournal, blockEnd+2, 2) 1283 1284 squashMD := config.makeMD(firstRev, firstPrevRoot) 1285 irmd, err = tlfJournal.resolveBranch( 1286 ctx, kbfsmd.PendingLocalSquashBranchID, []kbfsblock.ID{}, squashMD, 1287 tlfJournal.key, nil) 1288 require.NoError(t, err) 1289 prevRoot = irmd.mdID 1290 requireJournalEntryCounts(t, tlfJournal, blockEnd+3, 1) 1291 1292 // Another revision 11, with a squashable number of blocks to 1293 // complete the initial batch. 1294 for i := blockEnd; i < blockEnd+20; i++ { 1295 data := []byte{byte(i)} 1296 bid, bCtx, serverHalf := config.makeBlock(data) 1297 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1298 require.NoError(t, err) 1299 } 1300 blockEnd += 20 1301 md2 = config.makeMD(firstRev+1, prevRoot) 1302 require.NoError(t, err) 1303 irmd, err = tlfJournal.putMD(ctx, md2, tlfJournal.key, nil) 1304 require.NoError(t, err) 1305 1306 // Let it squash (avoiding a branch this time since there's only one MD). 1307 err = tlfJournal.flush(ctx) 1308 require.NoError(t, err) 1309 require.Equal(t, kbfsmd.NullBranchID, tlfJournal.mdJournal.getBranchID()) 1310 requireJournalEntryCounts(t, tlfJournal, blockEnd+4, 2) 1311 1312 // Simulate an MD conflict and try to flush again. This will 1313 // flush a full batch of blocks before hitting the conflict, as 1314 // well as the marker for rev 10. 1315 mdserver.onceOnPut = func() error { 1316 return kbfsmd.ServerErrorConflictRevision{} 1317 } 1318 mergedBare := config.makeMD(md2.Revision(), firstPrevRoot).bareMd 1319 mergedBare.SetSerializedPrivateMetadata([]byte{1}) 1320 rmds, err := SignBareRootMetadata( 1321 ctx, config.Codec(), config.Crypto(), config.Crypto(), 1322 mergedBare, time.Now()) 1323 require.NoError(t, err) 1324 mdserverShim.nextGetRange = []*RootMetadataSigned{rmds} 1325 err = tlfJournal.flush(ctx) 1326 require.NoError(t, err) 1327 branchID := tlfJournal.mdJournal.getBranchID() 1328 require.NotEqual(t, kbfsmd.PendingLocalSquashBranchID, branchID) 1329 require.NotEqual(t, kbfsmd.NullBranchID, branchID) 1330 // Blocks: All the unflushed blocks, plus two unflushed rev markers. 1331 requireJournalEntryCounts( 1332 t, tlfJournal, blockEnd-maxJournalBlockFlushBatchSize+2, 2) 1333 1334 // More blocks that are part of the resolution. 1335 blockEnd2 := blockEnd + maxJournalBlockFlushBatchSize + 2 1336 for i := blockEnd; i < blockEnd2; i++ { 1337 data := []byte{byte(i)} 1338 bid, bCtx, serverHalf := config.makeBlock(data) 1339 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1340 require.NoError(t, err) 1341 } 1342 1343 // Use revision 11 (as if two revisions had been merged by another 1344 // device). 1345 resolveMD := config.makeMD(md2.Revision(), firstPrevRoot) 1346 _, err = tlfJournal.resolveBranch( 1347 ctx, branchID, []kbfsblock.ID{}, resolveMD, tlfJournal.key, nil) 1348 require.NoError(t, err) 1349 // Blocks: the ones from the last check, plus the new blocks, plus 1350 // the resolve rev marker. 1351 requireJournalEntryCounts( 1352 t, tlfJournal, blockEnd2-maxJournalBlockFlushBatchSize+3, 1) 1353 1354 // Flush everything remaining. All blocks should be flushed after 1355 // `resolveMD`. 1356 err = tlfJournal.flush(ctx) 1357 require.NoError(t, err) 1358 testTLFJournalGCd(t, tlfJournal) 1359 1360 require.Equal(t, resolveMD.Revision(), puts[len(puts)-1]) 1361 } 1362 1363 // testTLFJournalFlushInterleaving tests that we interleave block and 1364 // MD ops while respecting the relative orderings of blocks and MD ops 1365 // when flushing. 1366 func testTLFJournalFlushInterleaving(t *testing.T, ver kbfsmd.MetadataVer) { 1367 tempdir, config, ctx, cancel, tlfJournal, delegate := 1368 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1369 defer teardownTLFJournalTest( 1370 ctx, tempdir, config, cancel, tlfJournal, delegate) 1371 1372 var lock sync.Mutex 1373 var puts []interface{} 1374 1375 bserver := orderedBlockServer{ 1376 lock: &lock, 1377 puts: &puts, 1378 } 1379 1380 tlfJournal.delegateBlockServer.Shutdown(ctx) 1381 tlfJournal.delegateBlockServer = &bserver 1382 1383 var mdserverShim shimMDServer 1384 mdserver := orderedMDServer{ 1385 MDServer: &mdserverShim, 1386 lock: &lock, 1387 puts: &puts, 1388 } 1389 1390 config.mdserver = &mdserver 1391 1392 // Revision 1 1393 var bids []kbfsblock.ID 1394 rev1BlockEnd := maxJournalBlockFlushBatchSize * 2 1395 for i := 0; i < rev1BlockEnd; i++ { 1396 data := []byte{byte(i)} 1397 bid, bCtx, serverHalf := config.makeBlock(data) 1398 bids = append(bids, bid) 1399 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1400 require.NoError(t, err) 1401 } 1402 md1 := config.makeMD(kbfsmd.Revision(10), kbfsmd.FakeID(1)) 1403 irmd, err := tlfJournal.putMD(ctx, md1, tlfJournal.key, nil) 1404 require.NoError(t, err) 1405 prevRoot := irmd.mdID 1406 1407 // Revision 2 1408 rev2BlockEnd := rev1BlockEnd + maxJournalBlockFlushBatchSize*2 1409 for i := rev1BlockEnd; i < rev2BlockEnd; i++ { 1410 data := []byte{byte(i)} 1411 bid, bCtx, serverHalf := config.makeBlock(data) 1412 bids = append(bids, bid) 1413 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1414 require.NoError(t, err) 1415 } 1416 md2 := config.makeMD(kbfsmd.Revision(11), prevRoot) 1417 irmd, err = tlfJournal.putMD(ctx, md2, tlfJournal.key, nil) 1418 require.NoError(t, err) 1419 1420 err = tlfJournal.flush(ctx) 1421 require.NoError(t, err) 1422 testTLFJournalGCd(t, tlfJournal) 1423 1424 // Make sure the flusher checks in between block flushes for 1425 // conflicting MDs on the server. 1426 require.True(t, mdserverShim.getForTLFCalled) 1427 1428 // Make sure that: before revision 1, all the rev1 blocks were 1429 // put; rev2 comes last; some blocks are put between the two. 1430 bidsSeen := make(map[kbfsblock.ID]bool) 1431 md1Slot := 0 1432 md2Slot := 0 1433 for i, put := range puts { 1434 if bid, ok := put.(kbfsblock.ID); ok { 1435 t.Logf("Saw bid %s at %d", bid, i) 1436 bidsSeen[bid] = true 1437 continue 1438 } 1439 1440 mdID, ok := put.(kbfsmd.Revision) 1441 require.True(t, ok) 1442 if mdID == md1.Revision() { 1443 md1Slot = i 1444 for j := 0; j < rev1BlockEnd; j++ { 1445 t.Logf("Checking bid %s at %d", bids[j], i) 1446 require.True(t, bidsSeen[bids[j]]) 1447 } 1448 } else if mdID == md2.Revision() { 1449 md2Slot = i 1450 require.NotZero(t, md1Slot) 1451 require.True(t, md1Slot+1 < i) 1452 require.Equal(t, i, len(puts)-1) 1453 } 1454 } 1455 require.NotZero(t, md1Slot) 1456 require.NotZero(t, md2Slot) 1457 } 1458 1459 type testBranchChangeListener struct { 1460 c chan<- struct{} 1461 } 1462 1463 func (tbcl testBranchChangeListener) onTLFBranchChange(_ tlf.ID, _ kbfsmd.BranchID) { 1464 tbcl.c <- struct{}{} 1465 } 1466 1467 func testTLFJournalPauseBlocksAndConvertBranch(ctx context.Context, 1468 t *testing.T, tlfJournal *tlfJournal, config *testTLFJournalConfig) ( 1469 firstRev kbfsmd.Revision, firstRoot kbfsmd.ID, 1470 retUnpauseBlockPutCh chan<- struct{}, retErrCh <-chan error, 1471 blocksLeftAfterFlush uint64, mdsLeftAfterFlush uint64) { 1472 branchCh := make(chan struct{}, 1) 1473 tlfJournal.onBranchChange = testBranchChangeListener{branchCh} 1474 1475 var lock sync.Mutex 1476 var puts []interface{} 1477 1478 unpauseBlockPutCh := make(chan struct{}) 1479 noticeBlockPutCh := make(chan struct{}) 1480 bserver := orderedBlockServer{ 1481 lock: &lock, 1482 puts: &puts, 1483 onceOnPut: func() { 1484 noticeBlockPutCh <- struct{}{} 1485 <-unpauseBlockPutCh 1486 }, 1487 } 1488 1489 tlfJournal.delegateBlockServer.Shutdown(ctx) 1490 tlfJournal.delegateBlockServer = &bserver 1491 1492 // Revision 1 1493 rev1BlockEnd := maxJournalBlockFlushBatchSize * 2 1494 for i := 0; i < rev1BlockEnd; i++ { 1495 data := []byte{byte(i)} 1496 bid, bCtx, serverHalf := config.makeBlock(data) 1497 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1498 require.NoError(t, err) 1499 } 1500 firstRev = kbfsmd.Revision(10) 1501 firstRoot = kbfsmd.FakeID(1) 1502 md1 := config.makeMD(firstRev, firstRoot) 1503 irmd, err := tlfJournal.putMD(ctx, md1, tlfJournal.key, nil) 1504 require.NoError(t, err) 1505 prevRoot := irmd.mdID 1506 rev := firstRev 1507 1508 // Now start the blocks flushing. One of the block puts will be 1509 // stuck. During that time, put a lot more MD revisions, enough 1510 // to trigger branch conversion. However, no pause should be 1511 // called. 1512 1513 errCh := make(chan error, 1) 1514 go func() { 1515 errCh <- tlfJournal.flush(ctx) 1516 }() 1517 1518 <-noticeBlockPutCh 1519 1520 markers := uint64(1) 1521 for i := 0; i < ForcedBranchSquashRevThreshold+1; i++ { 1522 rev++ 1523 md := config.makeMD(rev, prevRoot) 1524 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1525 if isRevisionConflict(err) { 1526 // Branch conversion is done, we can stop now. 1527 break 1528 } 1529 require.NoError(t, err) 1530 prevRoot = irmd.mdID 1531 markers++ 1532 } 1533 1534 // Wait for the local squash branch to appear. 1535 select { 1536 case <-branchCh: 1537 case <-ctx.Done(): 1538 t.Fatalf("Timeout while waiting for branch change") 1539 } 1540 1541 return firstRev, firstRoot, unpauseBlockPutCh, errCh, 1542 maxJournalBlockFlushBatchSize + markers, markers 1543 } 1544 1545 // testTLFJournalConvertWhileFlushing tests that we can do branch 1546 // conversion while blocks are still flushing. 1547 func testTLFJournalConvertWhileFlushing(t *testing.T, ver kbfsmd.MetadataVer) { 1548 tempdir, config, ctx, cancel, tlfJournal, delegate := 1549 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1550 defer teardownTLFJournalTest( 1551 ctx, tempdir, config, cancel, tlfJournal, delegate) 1552 1553 _, _, unpauseBlockPutCh, errCh, blocksLeftAfterFlush, mdsLeftAfterFlush := 1554 testTLFJournalPauseBlocksAndConvertBranch(ctx, t, tlfJournal, config) 1555 1556 // Now finish the block put, and let the flush finish. We 1557 // should be on a local squash branch after this. 1558 unpauseBlockPutCh <- struct{}{} 1559 err := <-errCh 1560 require.NoError(t, err) 1561 1562 // Should be a full batch worth of blocks left, plus all the 1563 // revision markers above. No squash has actually happened yet, 1564 // so all the revisions should be there now, just on a branch. 1565 requireJournalEntryCounts( 1566 t, tlfJournal, blocksLeftAfterFlush, mdsLeftAfterFlush) 1567 require.Equal( 1568 t, kbfsmd.PendingLocalSquashBranchID, tlfJournal.mdJournal.getBranchID()) 1569 } 1570 1571 // testTLFJournalSquashWhileFlushing tests that we can do journal 1572 // coalescing while blocks are still flushing. 1573 func testTLFJournalSquashWhileFlushing(t *testing.T, ver kbfsmd.MetadataVer) { 1574 tempdir, config, ctx, cancel, tlfJournal, delegate := 1575 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1576 defer teardownTLFJournalTest( 1577 ctx, tempdir, config, cancel, tlfJournal, delegate) 1578 1579 firstRev, firstPrevRoot, unpauseBlockPutCh, errCh, 1580 blocksLeftAfterFlush, _ := 1581 testTLFJournalPauseBlocksAndConvertBranch(ctx, t, tlfJournal, config) 1582 1583 // While it's paused, resolve the branch. 1584 resolveMD := config.makeMD(firstRev, firstPrevRoot) 1585 _, err := tlfJournal.resolveBranch( 1586 ctx, tlfJournal.mdJournal.getBranchID(), []kbfsblock.ID{}, resolveMD, 1587 tlfJournal.key, nil) 1588 require.NoError(t, err) 1589 requireJournalEntryCounts( 1590 t, tlfJournal, blocksLeftAfterFlush+maxJournalBlockFlushBatchSize+1, 1) 1591 1592 // Now finish the block put, and let the flush finish. We 1593 // shouldn't be on a branch anymore. 1594 unpauseBlockPutCh <- struct{}{} 1595 err = <-errCh 1596 require.NoError(t, err) 1597 1598 // Since flush() never saw the branch in conflict, it will finish 1599 // flushing everything. 1600 testTLFJournalGCd(t, tlfJournal) 1601 require.Equal(t, kbfsmd.NullBranchID, tlfJournal.mdJournal.getBranchID()) 1602 } 1603 1604 type testImmediateBackOff struct { 1605 numBackOffs int 1606 resetCh chan<- struct{} 1607 } 1608 1609 func (t *testImmediateBackOff) NextBackOff() time.Duration { 1610 t.numBackOffs++ 1611 return 1 * time.Nanosecond 1612 } 1613 1614 func (t *testImmediateBackOff) Reset() { 1615 close(t.resetCh) 1616 } 1617 1618 func testTLFJournalFlushRetry(t *testing.T, ver kbfsmd.MetadataVer) { 1619 tempdir, config, ctx, cancel, tlfJournal, delegate := 1620 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1621 defer teardownTLFJournalTest( 1622 ctx, tempdir, config, cancel, tlfJournal, delegate) 1623 1624 // Stop the current background loop; replace with one that retries 1625 // immediately. 1626 tlfJournal.needShutdownCh <- struct{}{} 1627 <-tlfJournal.backgroundShutdownCh 1628 resetCh := make(chan struct{}) 1629 b := &testImmediateBackOff{resetCh: resetCh} 1630 tlfJournal.backgroundShutdownCh = make(chan struct{}) 1631 go tlfJournal.doBackgroundWorkLoop(TLFJournalBackgroundWorkPaused, b) 1632 select { 1633 case <-delegate.shutdownCh: 1634 case <-ctx.Done(): 1635 assert.Fail(config.t, ctx.Err().Error()) 1636 } 1637 1638 firstRevision := kbfsmd.Revision(10) 1639 firstPrevRoot := kbfsmd.FakeID(1) 1640 mdCount := 10 1641 1642 prevRoot := firstPrevRoot 1643 for i := 0; i < mdCount; i++ { 1644 revision := firstRevision + kbfsmd.Revision(i) 1645 md := config.makeMD(revision, prevRoot) 1646 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1647 require.NoError(t, err) 1648 prevRoot = irmd.mdID 1649 } 1650 1651 var mdserver shimMDServer 1652 mdserver.nextErr = errors.New("Error to force a retry") 1653 config.mdserver = &mdserver 1654 1655 delegate.requireNextState(ctx, bwPaused) 1656 tlfJournal.resumeBackgroundWork() 1657 delegate.requireNextState(ctx, bwIdle) 1658 delegate.requireNextState(ctx, bwBusy) 1659 delegate.requireNextState(ctx, bwIdle) 1660 delegate.requireNextState(ctx, bwBusy) 1661 delegate.requireNextState(ctx, bwIdle) 1662 <-resetCh 1663 1664 require.Equal(t, b.numBackOffs, 1) 1665 testTLFJournalGCd(t, tlfJournal) 1666 } 1667 1668 func testTLFJournalResolveBranch(t *testing.T, ver kbfsmd.MetadataVer) { 1669 tempdir, config, ctx, cancel, tlfJournal, delegate := 1670 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1671 defer teardownTLFJournalTest( 1672 ctx, tempdir, config, cancel, tlfJournal, delegate) 1673 1674 var bids []kbfsblock.ID 1675 for i := 0; i < 3; i++ { 1676 data := []byte{byte(i)} 1677 bid, bCtx, serverHalf := config.makeBlock(data) 1678 bids = append(bids, bid) 1679 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1680 require.NoError(t, err) 1681 } 1682 1683 firstRevision := kbfsmd.Revision(10) 1684 firstPrevRoot := kbfsmd.FakeID(1) 1685 mdCount := 3 1686 1687 prevRoot := firstPrevRoot 1688 for i := 0; i < mdCount; i++ { 1689 revision := firstRevision + kbfsmd.Revision(i) 1690 md := config.makeMD(revision, prevRoot) 1691 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1692 require.NoError(t, err) 1693 prevRoot = irmd.mdID 1694 } 1695 1696 var mdserver shimMDServer 1697 mdserver.nextErr = kbfsmd.ServerErrorConflictRevision{} 1698 config.mdserver = &mdserver 1699 1700 _, mdEnd, _, err := tlfJournal.getJournalEnds(ctx) 1701 require.NoError(t, err) 1702 1703 // This will convert to a branch. 1704 flushed, err := tlfJournal.flushOneMDOp(ctx, mdEnd, defaultFlushContext()) 1705 require.NoError(t, err) 1706 require.False(t, flushed) 1707 1708 // The background worker was already paused, so we won't get a 1709 // paused signal here. But resume the background work now so that 1710 // later when the conflict resolves, it will be able to send a 1711 // resume signal. 1712 tlfJournal.resumeBackgroundWork() 1713 1714 // Resolve the branch. 1715 resolveMD := config.makeMD(firstRevision, firstPrevRoot) 1716 _, err = tlfJournal.resolveBranch( 1717 ctx, tlfJournal.mdJournal.getBranchID(), []kbfsblock.ID{bids[1]}, 1718 resolveMD, tlfJournal.key, nil) 1719 require.NoError(t, err) 1720 1721 blockEnd, newMDEnd, _, err := tlfJournal.getJournalEnds(ctx) 1722 require.NoError(t, err) 1723 require.Equal(t, firstRevision+1, newMDEnd) 1724 1725 blocks, b, maxMD, err := tlfJournal.getNextBlockEntriesToFlush( 1726 ctx, blockEnd, kbfsmd.ID{}) 1727 require.NoError(t, err) 1728 require.Equal(t, firstRevision, maxMD) 1729 // 3 blocks, 3 old MD markers, 1 new MD marker 1730 require.Equal(t, 7, blocks.length()) 1731 require.Equal(t, 2, blocks.puts.numBlocks()) 1732 require.Equal(t, 0, blocks.adds.numBlocks()) 1733 // 1 ignored block, 3 ignored MD markers, 1 real MD marker 1734 require.Len(t, blocks.other, 5) 1735 ptrs := blocks.puts.Ptrs() 1736 ids := make([]kbfsblock.ID, len(ptrs)) 1737 for i, ptr := range ptrs { 1738 ids[i] = ptr.ID 1739 } 1740 require.Contains(t, ids, bids[0]) 1741 require.Contains(t, ids, bids[2]) 1742 // 2 bytes of data in 2 unignored blocks. 1743 require.Equal(t, int64(2), b) 1744 1745 // resolveBranch resumes background work. 1746 delegate.requireNextState(ctx, bwIdle) 1747 delegate.requireNextState(ctx, bwBusy) 1748 } 1749 1750 func testTLFJournalSquashByBytes(t *testing.T, ver kbfsmd.MetadataVer) { 1751 tempdir, config, ctx, cancel, tlfJournal, delegate := 1752 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1753 defer teardownTLFJournalTest( 1754 ctx, tempdir, config, cancel, tlfJournal, delegate) 1755 tlfJournal.forcedSquashByBytes = 10 1756 1757 data := make([]byte, tlfJournal.forcedSquashByBytes+1) 1758 bid, bCtx, serverHalf := config.makeBlock(data) 1759 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1760 require.NoError(t, err) 1761 1762 firstRevision := kbfsmd.Revision(10) 1763 firstPrevRoot := kbfsmd.FakeID(1) 1764 mdCount := 3 1765 1766 prevRoot := firstPrevRoot 1767 for i := 0; i < mdCount; i++ { 1768 revision := firstRevision + kbfsmd.Revision(i) 1769 md := config.makeMD(revision, prevRoot) 1770 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1771 require.NoError(t, err) 1772 prevRoot = irmd.mdID 1773 } 1774 1775 // This should convert it to a branch, based on the number of 1776 // outstanding bytes. 1777 err = tlfJournal.flush(ctx) 1778 require.NoError(t, err) 1779 require.Equal( 1780 t, kbfsmd.PendingLocalSquashBranchID, tlfJournal.mdJournal.getBranchID()) 1781 } 1782 1783 // Test that the first revision of a TLF doesn't get squashed. 1784 func testTLFJournalFirstRevNoSquash(t *testing.T, ver kbfsmd.MetadataVer) { 1785 tempdir, config, ctx, cancel, tlfJournal, delegate := 1786 setupTLFJournalTest(t, ver, TLFJournalBackgroundWorkPaused) 1787 defer teardownTLFJournalTest( 1788 ctx, tempdir, config, cancel, tlfJournal, delegate) 1789 tlfJournal.forcedSquashByBytes = 10 1790 1791 data := make([]byte, tlfJournal.forcedSquashByBytes+1) 1792 bid, bCtx, serverHalf := config.makeBlock(data) 1793 err := tlfJournal.putBlockData(ctx, bid, bCtx, data, serverHalf) 1794 require.NoError(t, err) 1795 1796 firstRevision := kbfsmd.RevisionInitial 1797 mdCount := 4 1798 1799 var firstMdID, prevRoot kbfsmd.ID 1800 for i := 0; i < mdCount; i++ { 1801 revision := firstRevision + kbfsmd.Revision(i) 1802 md := config.makeMD(revision, prevRoot) 1803 irmd, err := tlfJournal.putMD(ctx, md, tlfJournal.key, nil) 1804 require.NoError(t, err) 1805 prevRoot = irmd.mdID 1806 if i == 0 { 1807 firstMdID = irmd.mdID 1808 } 1809 } 1810 1811 // This should convert it to a branch, based on the number of 1812 // outstanding bytes. 1813 err = tlfJournal.flush(ctx) 1814 require.NoError(t, err) 1815 require.Equal( 1816 t, kbfsmd.PendingLocalSquashBranchID, tlfJournal.mdJournal.getBranchID()) 1817 requireJournalEntryCounts(t, tlfJournal, 5, 4) 1818 unsquashedRange, err := tlfJournal.getMDRange( 1819 ctx, kbfsmd.NullBranchID, firstRevision, firstRevision+3) 1820 require.NoError(t, err) 1821 require.Len(t, unsquashedRange, 1) 1822 require.Equal(t, firstRevision, unsquashedRange[0].RevisionNumber()) 1823 require.Equal(t, firstMdID, unsquashedRange[0].mdID) 1824 squashRange, err := tlfJournal.getMDRange( 1825 ctx, kbfsmd.PendingLocalSquashBranchID, firstRevision, firstRevision+3) 1826 require.NoError(t, err) 1827 require.Len(t, squashRange, 3) 1828 require.Equal(t, firstRevision+1, squashRange[0].RevisionNumber()) 1829 } 1830 1831 // testTLFJournalSingleOp tests that when the journal is in single op 1832 // mode, it doesn't flush any MDs until `finishSingleOp()` is called, 1833 // and then it only flushes one squashed MD. 1834 func testTLFJournalSingleOp(t *testing.T, ver kbfsmd.MetadataVer) { 1835 tempdir, config, ctx, cancel, tlfJournal, delegate := 1836 setupTLFJournalTest(t, ver, TLFJournalSingleOpBackgroundWorkEnabled) 1837 defer teardownTLFJournalTest( 1838 ctx, tempdir, config, cancel, tlfJournal, delegate) 1839 1840 var mdserver shimMDServer 1841 config.mdserver = &mdserver 1842 1843 tlfJournal.pauseBackgroundWork() 1844 delegate.requireNextState(ctx, bwPaused) 1845 1846 putBlock(ctx, t, config, tlfJournal, []byte{1, 2}) 1847 putBlock(ctx, t, config, tlfJournal, []byte{3, 4}) 1848 putBlock(ctx, t, config, tlfJournal, []byte{5, 6}) 1849 1850 md1 := config.makeMD(kbfsmd.Revision(10), kbfsmd.FakeID(1)) 1851 irmd, err := tlfJournal.putMD(ctx, md1, tlfJournal.key, nil) 1852 require.NoError(t, err) 1853 prevRoot := irmd.mdID 1854 1855 putBlock(ctx, t, config, tlfJournal, []byte{7, 8}) 1856 putBlock(ctx, t, config, tlfJournal, []byte{9, 10}) 1857 1858 md2 := config.makeMD(kbfsmd.Revision(11), prevRoot) 1859 _, err = tlfJournal.putMD(ctx, md2, tlfJournal.key, nil) 1860 require.NoError(t, err) 1861 1862 tlfJournal.resumeBackgroundWork() 1863 delegate.requireNextState(ctx, bwIdle) 1864 delegate.requireNextState(ctx, bwBusy) 1865 delegate.requireNextState(ctx, bwIdle) 1866 1867 requireJournalEntryCounts(t, tlfJournal, 0, 2) 1868 1869 // The `finishSingleOp` call below blocks, so we have to do it in 1870 // a background goroutine to avoid deadlock. 1871 errCh := make(chan error, 1) 1872 go func() { 1873 errCh <- tlfJournal.finishSingleOp(ctx, nil, keybase1.MDPriorityNormal) 1874 }() 1875 1876 // Background loop awakens after the finish is signaled. Should 1877 // now be on a conflict branch. The pause signal sent by the 1878 // branch-converter races with the background work finishing 1879 // (KBFS-2440), and so the second state could be either idle or 1880 // paused, depending on what gets processed first. 1881 delegate.requireNextState(ctx, bwBusy) 1882 nextState := delegate.requireNextState(ctx, bwPaused, bwIdle) 1883 if nextState == bwIdle { 1884 delegate.requireNextState(ctx, bwPaused) 1885 } 1886 1887 require.Equal( 1888 t, kbfsmd.PendingLocalSquashBranchID, tlfJournal.mdJournal.getBranchID()) 1889 resolveMD := config.makeMD(kbfsmd.Revision(10), kbfsmd.FakeID(1)) 1890 _, err = tlfJournal.resolveBranch( 1891 ctx, tlfJournal.mdJournal.getBranchID(), nil, resolveMD, tlfJournal.key, 1892 nil) 1893 require.NoError(t, err) 1894 1895 // Now the flushing should complete. 1896 delegate.requireNextState(ctx, bwIdle) 1897 delegate.requireNextState(ctx, bwBusy) 1898 delegate.requireNextState(ctx, bwIdle) 1899 1900 select { 1901 case err := <-errCh: 1902 require.NoError(t, err) 1903 case <-ctx.Done(): 1904 t.Fatal(ctx.Err().Error()) 1905 } 1906 requireJournalEntryCounts(t, tlfJournal, 0, 0) 1907 1908 require.Len(t, mdserver.rmdses, 1) 1909 } 1910 1911 func TestTLFJournal(t *testing.T) { 1912 tests := []func(*testing.T, kbfsmd.MetadataVer){ 1913 testTLFJournalBasic, 1914 testTLFJournalPauseResume, 1915 testTLFJournalPauseShutdown, 1916 testTLFJournalBlockOpBasic, 1917 testTLFJournalBlockOpBusyPause, 1918 testTLFJournalBlockOpBusyShutdown, 1919 testTLFJournalSecondBlockOpWhileBusy, 1920 testTLFJournalMDServerBusyPause, 1921 testTLFJournalMDServerBusyShutdown, 1922 testTLFJournalBlockOpWhileBusy, 1923 testTLFJournalBlockOpDiskByteLimit, 1924 testTLFJournalBlockOpDiskFileLimit, 1925 testTLFJournalBlockOpDiskQuotaLimit, 1926 testTLFJournalBlockOpDiskQuotaLimitResolve, 1927 testTLFJournalBlockOpDiskLimitDuplicate, 1928 testTLFJournalBlockOpDiskLimitCancel, 1929 testTLFJournalBlockOpDiskLimitTimeout, 1930 testTLFJournalBlockOpDiskLimitPutFailure, 1931 testTLFJournalFlushMDBasic, 1932 testTLFJournalFlushMDConflict, 1933 testTLFJournalFlushOrdering, 1934 testTLFJournalFlushOrderingAfterSquashAndCR, 1935 testTLFJournalFlushInterleaving, 1936 testTLFJournalConvertWhileFlushing, 1937 testTLFJournalSquashWhileFlushing, 1938 testTLFJournalFlushRetry, 1939 testTLFJournalResolveBranch, 1940 testTLFJournalSquashByBytes, 1941 testTLFJournalFirstRevNoSquash, 1942 testTLFJournalSingleOp, 1943 } 1944 runTestsOverMetadataVers(t, "testTLFJournal", tests) 1945 }