Skip to content

Commit 9d143a3

Browse files
committed
Fixes leak of open file during capture fifo
This change fixes a leak that occurs when using context cancel or stopping the VM. The goroutine in `captureFifoToFile` spins up a goroutine to copy the contents of the file, but will continue reading from the file even if the VM shuts down. To prevent this, we now look at if the machine has closed its exit channel Signed-off-by: xibz <[email protected]>
1 parent 1ee9961 commit 9d143a3

File tree

3 files changed

+64
-4
lines changed

3 files changed

+64
-4
lines changed

handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ var CreateLogFilesHandler = Handler{
152152
)
153153

154154
if m.Cfg.FifoLogWriter != nil {
155-
if err := captureFifoToFile(m.logger, logFifoPath, m.Cfg.FifoLogWriter); err != nil {
155+
if err := m.captureFifoToFile(m.logger, logFifoPath, m.Cfg.FifoLogWriter); err != nil {
156156
m.logger.Warnf("captureFifoToFile() returned %s. Continuing anyway.", err)
157157
}
158158
}

machine.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ func (m *Machine) setupLogging(ctx context.Context) error {
576576
return nil
577577
}
578578

579-
func captureFifoToFile(logger *log.Entry, fifoPath string, w io.Writer) error {
579+
func (m *Machine) captureFifoToFile(logger *log.Entry, fifoPath string, w io.Writer) error {
580580
// open the fifo pipe which will be used
581581
// to write its contents to a file.
582582
fd, err := syscall.Open(fifoPath, syscall.O_RDONLY|syscall.O_NONBLOCK, 0600)
@@ -591,6 +591,16 @@ func captureFifoToFile(logger *log.Entry, fifoPath string, w io.Writer) error {
591591

592592
logger.Debugf("Capturing %q to writer", fifoPath)
593593

594+
// this goroutine is to track the life of the application along with whether
595+
// or not the context has been cancelled which is signified by the exitCh. In
596+
// the event that the exitCh has been closed, we will close the fifo file.
597+
go func() {
598+
<-m.exitCh
599+
if err := fifoPipe.Close(); err != nil {
600+
logger.WithError(err).Debug("failed to close fifo")
601+
}
602+
}()
603+
594604
// Uses a goroutine to copy the contents of the fifo pipe to the io.Writer.
595605
// In the event that the goroutine finishes, which is caused by either the
596606
// context being closed or the application being closed, we will close the

machine_test.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package firecracker
1515

1616
import (
17+
"bytes"
1718
"context"
1819
"errors"
1920
"flag"
@@ -30,6 +31,7 @@ import (
3031
"testing"
3132
"time"
3233

34+
"github.com/sirupsen/logrus"
3335
"github.com/stretchr/testify/assert"
3436
"github.com/stretchr/testify/require"
3537

@@ -830,7 +832,10 @@ func TestCaptureFifoToFile(t *testing.T) {
830832
},
831833
}
832834

833-
if err := captureFifoToFile(fctesting.NewLogEntry(t), fifoPath, testWriter); err != nil {
835+
m := &Machine{
836+
exitCh: make(chan struct{}),
837+
}
838+
if err := m.captureFifoToFile(fctesting.NewLogEntry(t), fifoPath, testWriter); err != nil {
834839
t.Errorf("Unexpected error: %v", err)
835840
}
836841

@@ -869,7 +874,10 @@ func TestCaptureFifoToFile_nonblock(t *testing.T) {
869874
},
870875
}
871876

872-
if err := captureFifoToFile(fctesting.NewLogEntry(t), fifoPath, testWriter); err != nil {
877+
m := &Machine{
878+
exitCh: make(chan struct{}),
879+
}
880+
if err := m.captureFifoToFile(fctesting.NewLogEntry(t), fifoPath, testWriter); err != nil {
873881
t.Errorf("Unexpected error: %v", err)
874882
}
875883

@@ -1025,3 +1033,45 @@ func TestPID(t *testing.T) {
10251033
}
10261034

10271035
}
1036+
1037+
func TestCaptureFifoToFile_leak(t *testing.T) {
1038+
m := &Machine{
1039+
exitCh: make(chan struct{}),
1040+
}
1041+
1042+
fifoPath := filepath.Join(testDataPath, "TestCaptureFifoToFileLeak.fifo")
1043+
err := syscall.Mkfifo(fifoPath, 0700)
1044+
require.NoError(t, err, "failed to make fifo")
1045+
defer os.Remove(fifoPath)
1046+
1047+
fd, err := syscall.Open(fifoPath, syscall.O_RDWR|syscall.O_NONBLOCK, 0600)
1048+
require.NoError(t, err, "failed to open fifo path")
1049+
f := os.NewFile(uintptr(fd), fifoPath)
1050+
assert.NotNil(t, f, "failed to create new file")
1051+
go func() {
1052+
for {
1053+
select {
1054+
case <-m.exitCh:
1055+
break
1056+
default:
1057+
_, err := f.Write([]byte("A"))
1058+
assert.NoError(t, err, "failed to write bytes to fifo")
1059+
}
1060+
}
1061+
}()
1062+
1063+
buf := bytes.NewBuffer(nil)
1064+
1065+
loggerBuffer := bytes.NewBuffer(nil)
1066+
logger := fctesting.NewLogEntry(t)
1067+
logger.Logger.Level = logrus.WarnLevel
1068+
logger.Logger.Out = loggerBuffer
1069+
err = m.captureFifoToFile(logger, fifoPath, buf)
1070+
assert.NoError(t, err, "failed to capture fifo to file")
1071+
close(m.exitCh)
1072+
1073+
// wait sometime for the logs to populate
1074+
time.Sleep(250 * time.Millisecond)
1075+
expected := `io.Copy failed to copy contents of fifo pipe: read testdata/TestCaptureFifoToFileLeak.fifo: file already closed`
1076+
assert.Contains(t, loggerBuffer.String(), expected)
1077+
}

0 commit comments

Comments
 (0)