gvisor/pkg/sentry/kernel/pipe/node_test.go

309 lines
8.7 KiB
Go

// 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)
}
}