lib/model: Prevent cleanup-race in testing (ref #6152) (#6155)

This commit is contained in:
Simon Frei 2019-11-14 23:08:40 +01:00 committed by GitHub
parent a5699d40a8
commit 5edf4660e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 53 deletions

View File

@ -490,17 +490,21 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
t.Fatal("Expected file in progress") t.Fatal("Expected file in progress")
} }
copyChan := make(chan copyBlocksState)
pullChan := make(chan pullBlockState) pullChan := make(chan pullBlockState)
finisherBufferChan := make(chan *sharedPullerState) finisherBufferChan := make(chan *sharedPullerState)
finisherChan := make(chan *sharedPullerState) finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1) dbUpdateChan := make(chan dbUpdateJob, 1)
go f.copierRoutine(copyChan, pullChan, finisherBufferChan) copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan)
go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string)) go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string))
defer close(copyChan)
defer close(pullChan) defer func() {
defer close(finisherChan) close(copyChan)
copyWg.Wait()
close(pullChan)
close(finisherBufferChan)
close(finisherChan)
}()
f.handleFile(file, copyChan, dbUpdateChan) f.handleFile(file, copyChan, dbUpdateChan)
@ -508,15 +512,15 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
// loop has been performed. // loop has been performed.
toPull := <-pullChan toPull := <-pullChan
// Close the file, causing errors on further access
toPull.sharedPullerState.fail(os.ErrNotExist)
// Unblock copier // Unblock copier
go func() { go func() {
for range pullChan { for range pullChan {
} }
}() }()
// Close the file, causing errors on further access
toPull.sharedPullerState.fail(os.ErrNotExist)
select { select {
case state := <-finisherBufferChan: case state := <-finisherBufferChan:
// At this point the file should still be registered with both the job // At this point the file should still be registered with both the job
@ -580,66 +584,90 @@ func TestDeregisterOnFailInPull(t *testing.T) {
t.Fatal("Expected file in progress") t.Fatal("Expected file in progress")
} }
copyChan := make(chan copyBlocksState)
pullChan := make(chan pullBlockState) pullChan := make(chan pullBlockState)
finisherBufferChan := make(chan *sharedPullerState) finisherBufferChan := make(chan *sharedPullerState)
finisherChan := make(chan *sharedPullerState) finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1) dbUpdateChan := make(chan dbUpdateJob, 1)
go f.copierRoutine(copyChan, pullChan, finisherBufferChan) copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan)
go f.pullerRoutine(pullChan, finisherBufferChan) pullWg := sync.NewWaitGroup()
pullWg.Add(1)
go func() {
f.pullerRoutine(pullChan, finisherBufferChan)
pullWg.Done()
}()
go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string)) go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string))
defer close(copyChan) defer func() {
defer close(pullChan) // Unblock copier and puller
defer close(finisherChan) go func() {
for range finisherBufferChan {
}
}()
close(copyChan)
copyWg.Wait()
close(pullChan)
pullWg.Wait()
close(finisherBufferChan)
close(finisherChan)
}()
f.handleFile(file, copyChan, dbUpdateChan) f.handleFile(file, copyChan, dbUpdateChan)
// Receive at finisher, we should error out as puller has nowhere to pull // Receive at finisher, we should error out as puller has nowhere to pull
// from. // from.
timeout = time.Second timeout = time.Second
select {
case state := <-finisherBufferChan: // Both the puller and copier may send to the finisherBufferChan.
// At this point the file should still be registered with both the job var state *sharedPullerState
// queue, and the progress emitter. Verify this. after := time.After(5 * time.Second)
if f.model.progressEmitter.lenRegistry() != 1 || f.queue.lenProgress() != 1 || f.queue.lenQueued() != 0 { for {
t.Fatal("Could not find file") select {
case state = <-finisherBufferChan:
case <-after:
t.Fatal("Didn't get failed state to the finisher")
} }
if state.failed() != nil {
// Pass the file down the real finisher, and give it time to consume break
finisherChan <- state
t0 := time.Now()
if ev, err := s.Poll(time.Minute); err != nil {
t.Fatal("Got error waiting for ItemFinished event:", err)
} else if n := ev.Data.(map[string]interface{})["item"]; n != state.file.Name {
t.Fatal("Got ItemFinished event for wrong file:", n)
} }
t.Log("event took", time.Since(t0)) }
state.mut.Lock() // At this point the file should still be registered with both the job
stateWriter := state.writer // queue, and the progress emitter. Verify this.
state.mut.Unlock() if f.model.progressEmitter.lenRegistry() != 1 || f.queue.lenProgress() != 1 || f.queue.lenQueued() != 0 {
if stateWriter != nil { t.Fatal("Could not find file")
t.Fatal("File not closed?") }
}
if f.model.progressEmitter.lenRegistry() != 0 || f.queue.lenProgress() != 0 || f.queue.lenQueued() != 0 { // Pass the file down the real finisher, and give it time to consume
t.Fatal("Still registered", f.model.progressEmitter.lenRegistry(), f.queue.lenProgress(), f.queue.lenQueued()) finisherChan <- state
}
// Doing it again should have no effect t0 := time.Now()
finisherChan <- state if ev, err := s.Poll(time.Minute); err != nil {
t.Fatal("Got error waiting for ItemFinished event:", err)
} else if n := ev.Data.(map[string]interface{})["item"]; n != state.file.Name {
t.Fatal("Got ItemFinished event for wrong file:", n)
}
t.Log("event took", time.Since(t0))
if _, err := s.Poll(time.Second); err != events.ErrTimeout { state.mut.Lock()
t.Fatal("Expected timeout, not another event", err) stateWriter := state.writer
} state.mut.Unlock()
if stateWriter != nil {
t.Fatal("File not closed?")
}
if f.model.progressEmitter.lenRegistry() != 0 || f.queue.lenProgress() != 0 || f.queue.lenQueued() != 0 { if f.model.progressEmitter.lenRegistry() != 0 || f.queue.lenProgress() != 0 || f.queue.lenQueued() != 0 {
t.Fatal("Still registered", f.model.progressEmitter.lenRegistry(), f.queue.lenProgress(), f.queue.lenQueued()) t.Fatal("Still registered", f.model.progressEmitter.lenRegistry(), f.queue.lenProgress(), f.queue.lenQueued())
} }
case <-time.After(5 * time.Second):
t.Fatal("Didn't get anything to the finisher") // Doing it again should have no effect
finisherChan <- state
if _, err := s.Poll(time.Second); err != events.ErrTimeout {
t.Fatal("Expected timeout, not another event", err)
}
if f.model.progressEmitter.lenRegistry() != 0 || f.queue.lenProgress() != 0 || f.queue.lenQueued() != 0 {
t.Fatal("Still registered", f.model.progressEmitter.lenRegistry(), f.queue.lenProgress(), f.queue.lenQueued())
} }
} }
@ -830,11 +858,14 @@ func TestCopyOwner(t *testing.T) {
// comes the finisher is done. // comes the finisher is done.
finisherChan := make(chan *sharedPullerState) finisherChan := make(chan *sharedPullerState)
defer close(finisherChan) copierChan, copyWg := startCopier(f, nil, finisherChan)
copierChan := make(chan copyBlocksState)
defer close(copierChan)
go f.copierRoutine(copierChan, nil, finisherChan)
go f.finisherRoutine(finisherChan, dbUpdateChan, nil) go f.finisherRoutine(finisherChan, dbUpdateChan, nil)
defer func() {
close(copierChan)
copyWg.Wait()
close(finisherChan)
}()
f.handleFile(file, copierChan, nil) f.handleFile(file, copierChan, nil)
<-dbUpdateChan <-dbUpdateChan
@ -993,3 +1024,14 @@ func cleanupSharedPullerState(s *sharedPullerState) {
s.writer.fd.Close() s.writer.fd.Close()
s.writer.mut.Unlock() s.writer.mut.Unlock()
} }
func startCopier(f *sendReceiveFolder, pullChan chan<- pullBlockState, finisherChan chan<- *sharedPullerState) (chan copyBlocksState, sync.WaitGroup) {
copyChan := make(chan copyBlocksState)
wg := sync.NewWaitGroup()
wg.Add(1)
go func() {
f.copierRoutine(copyChan, pullChan, finisherChan)
wg.Done()
}()
return copyChan, wg
}

View File

@ -66,6 +66,7 @@ func TestProgressEmitter(t *testing.T) {
p := NewProgressEmitter(c, evLogger) p := NewProgressEmitter(c, evLogger)
go p.Serve() go p.Serve()
defer p.Stop()
p.interval = 0 p.interval = 0
expectTimeout(w, t) expectTimeout(w, t)