// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pipe import ( "testing" "time" "gvisor.googlesource.com/gvisor/pkg/sentry/context" "gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest" "gvisor.googlesource.com/gvisor/pkg/sentry/fs" "gvisor.googlesource.com/gvisor/pkg/sentry/usermem" "gvisor.googlesource.com/gvisor/pkg/syserror" ) type sleeper struct { context.Context ch chan struct{} } func newSleeperContext(t *testing.T) context.Context { return &sleeper{ Context: contexttest.Context(t), ch: make(chan struct{}), } } func (s *sleeper) SleepStart() <-chan struct{} { return s.ch } func (s *sleeper) SleepFinish(bool) { } func (s *sleeper) Cancel() { s.ch <- struct{}{} } type openResult struct { *fs.File error } func testOpenOrDie(ctx context.Context, t *testing.T, n fs.InodeOperations, flags fs.FileFlags, doneChan chan<- struct{}) (*fs.File, error) { file, err := n.GetFile(ctx, nil, flags) if err != nil { t.Fatalf("open with flags %+v failed: %v", flags, err) } if doneChan != nil { doneChan <- struct{}{} } return file, err } func testOpen(ctx context.Context, t *testing.T, n fs.InodeOperations, flags fs.FileFlags, resChan chan<- openResult) (*fs.File, error) { file, err := n.GetFile(ctx, nil, flags) if resChan != nil { resChan <- openResult{file, err} } return file, err } func newNamedPipe(t *testing.T) *Pipe { return NewPipe(contexttest.Context(t), true, DefaultPipeSize, usermem.PageSize) } func newAnonPipe(t *testing.T) *Pipe { return NewPipe(contexttest.Context(t), false, DefaultPipeSize, usermem.PageSize) } // assertRecvBlocks ensures that a recv attempt on c blocks for at least // blockDuration. This is useful for checking that a goroutine that is supposed // to be executing a blocking operation is actually blocking. func assertRecvBlocks(t *testing.T, c <-chan struct{}, blockDuration time.Duration, failMsg string) { select { case <-c: t.Fatalf(failMsg) case <-time.After(blockDuration): // Ok, blocked for the required duration. } } func TestReadOpenBlocksForWriteOpen(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) rDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) // Verify that the open for read is blocking. assertRecvBlocks(t, rDone, time.Millisecond*100, "open for read not blocking with no writers") wDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) <-wDone <-rDone } func TestWriteOpenBlocksForReadOpen(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) wDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) // Verify that the open for write is blocking assertRecvBlocks(t, wDone, time.Millisecond*100, "open for write not blocking with no readers") rDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) <-rDone <-wDone } func TestMultipleWriteOpenDoesntCountAsReadOpen(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) rDone1 := make(chan struct{}) rDone2 := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone1) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone2) assertRecvBlocks(t, rDone1, time.Millisecond*100, "open for read didn't block with no writers") assertRecvBlocks(t, rDone2, time.Millisecond*100, "open for read didn't block with no writers") wDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) <-wDone <-rDone2 <-rDone1 } func TestClosedReaderBlocksWriteOpen(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) rFile, _ := testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil) rFile.DecRef() wDone := make(chan struct{}) // This open for write should block because the reader is now gone. go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) assertRecvBlocks(t, wDone, time.Millisecond*100, "open for write didn't block with no concurrent readers") // Open for read again. This should unblock the open for write. rDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) <-rDone <-wDone } func TestReadWriteOpenNeverBlocks(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) rwDone := make(chan struct{}) // Open for read-write never wait for a reader or writer, even if the // nonblocking flag is not set. go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true, NonBlocking: false}, rwDone) <-rwDone } func TestReadWriteOpenUnblocksReadOpen(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) rDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) rwDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true}, rwDone) <-rwDone <-rDone } func TestReadWriteOpenUnblocksWriteOpen(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) wDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) rwDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true}, rwDone) <-rwDone <-wDone } func TestBlockedOpenIsCancellable(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) done := make(chan openResult) go testOpen(ctx, t, f, fs.FileFlags{Read: true}, done) select { case <-done: t.Fatalf("open for read didn't block with no writers") case <-time.After(time.Millisecond * 100): // Ok. } ctx.(*sleeper).Cancel() // If the cancel on the sleeper didn't work, the open for read would never // return. res := <-done if res.error != syserror.ErrInterrupted { t.Fatalf("Cancellation didn't cause GetFile to return fs.ErrInterrupted, got %v.", res.error) } } func TestNonblockingReadOpenNoWriters(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil); err != nil { t.Fatalf("Nonblocking open for read failed with error %v.", err) } } func TestNonblockingWriteOpenNoReaders(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true, NonBlocking: true}, nil); err != syserror.ENXIO { t.Fatalf("Nonblocking open for write failed unexpected error %v.", err) } } func TestNonBlockingReadOpenWithWriter(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) wDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) // Open for write blocks since there are no readers yet. assertRecvBlocks(t, wDone, time.Millisecond*100, "Open for write didn't block with no reader.") if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil); err != nil { t.Fatalf("Nonblocking open for read failed with error %v.", err) } // Open for write should now be unblocked. <-wDone } func TestNonBlockingWriteOpenWithReader(t *testing.T) { f := NewInodeOperations(nil, newNamedPipe(t)) ctx := newSleeperContext(t) rDone := make(chan struct{}) go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) // Open for write blocked, since no reader yet. assertRecvBlocks(t, rDone, time.Millisecond*100, "Open for reader didn't block with no writer.") if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true, NonBlocking: true}, nil); err != nil { t.Fatalf("Nonblocking open for write failed with error %v.", err) } // Open for write should now be unblocked. <-rDone } func TestAnonReadOpen(t *testing.T) { f := NewInodeOperations(nil, newAnonPipe(t)) ctx := newSleeperContext(t) if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true}, nil); err != nil { t.Fatalf("open anon pipe for read failed: %v", err) } } func TestAnonWriteOpen(t *testing.T) { f := NewInodeOperations(nil, newAnonPipe(t)) ctx := newSleeperContext(t) if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true}, nil); err != nil { t.Fatalf("open anon pipe for write failed: %v", err) } }