Skip to content

Commit 9ad278f

Browse files
committed
Fixes hanging when using FifoLogWriter
This change fixes the issue with the FifoLogWriter during the captureFifoToFile call. During os.OpenFile, the operation would block indefinitely waiting for firecracker to write to the fifo since that logic was moved before firecracker could do so. This fix will now call syscall.Open with the syscall.O_NONBLOCK flag instead. Signed-off-by: xibz <[email protected]>
1 parent e472053 commit 9ad278f

File tree

2 files changed

+115
-27
lines changed

2 files changed

+115
-27
lines changed

machine.go

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

579-
func captureFifoToFile(logger *log.Entry, fifoPath string, fifo io.Writer) error {
580-
// create the fifo pipe which will be used
579+
func captureFifoToFile(logger *log.Entry, fifoPath string, w io.Writer) error {
580+
// open the fifo pipe which will be used
581581
// to write its contents to a file.
582-
fifoPipe, err := os.OpenFile(fifoPath, os.O_RDONLY, 0600)
582+
fd, err := syscall.Open(fifoPath, syscall.O_RDONLY|syscall.O_NONBLOCK, 0600)
583583
if err != nil {
584584
return fmt.Errorf("Failed to open fifo path at %q: %v", fifoPath, err)
585585
}
586586

587-
if err := syscall.Unlink(fifoPath); err != nil {
588-
logger.Warnf("Failed to unlink %s", fifoPath)
587+
fifoPipe := os.NewFile(uintptr(fd), fifoPath)
588+
if fifoPipe == nil {
589+
return fmt.Errorf("Invalid file descriptor")
589590
}
590591

591592
logger.Debugf("Capturing %q to writer", fifoPath)
592593

593-
// Uses a go routine to do a non-blocking io.Copy. The fifo
594-
// file should be closed when the appication has finished, since
595-
// the forked firecracker application will be closed resulting
596-
// in the pipe to return an io.EOF
594+
// Uses a goroutine to copy the contents of the fifo pipe to the io.Writer.
595+
// In the event that the goroutine finishes, which is caused by either the
596+
// context being closed or the application being closed, we will close the
597+
// pipe and unlink the fifo path.
597598
go func() {
598-
defer fifoPipe.Close()
599+
defer func() {
600+
if err := fifoPipe.Close(); err != nil {
601+
logger.Warnf("Failed to close fifo pipe: %v", err)
602+
}
603+
604+
if err := syscall.Unlink(fifoPath); err != nil {
605+
logger.Warnf("Failed to unlink %s: %v", fifoPath, err)
606+
}
607+
}()
599608

600-
if _, err := io.Copy(fifo, fifoPipe); err != nil {
609+
if _, err := io.Copy(w, fifoPipe); err != nil {
601610
logger.Warnf("io.Copy failed to copy contents of fifo pipe: %v", err)
602611
}
603612
}()

machine_test.go

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"path/filepath"
2626
"strconv"
2727
"strings"
28+
"sync"
2829
"syscall"
2930
"testing"
3031
"time"
@@ -146,7 +147,12 @@ func TestJailerMicroVMExecution(t *testing.T) {
146147
socketPath := filepath.Join(jailerTestPath, "firecracker", "TestJailerMicroVMExecution.socket")
147148
logFifo := filepath.Join(tmpDir, "firecracker.log")
148149
metricsFifo := filepath.Join(tmpDir, "firecracker-metrics")
150+
capturedLog := filepath.Join(tmpDir, "writer.fifo")
151+
fw, err := os.OpenFile(capturedLog, os.O_CREATE|os.O_RDWR, 0600)
152+
require.NoError(t, err, "failed to open fifo writer file")
149153
defer func() {
154+
fw.Close()
155+
os.Remove(capturedLog)
150156
os.Remove(socketPath)
151157
os.Remove(logFifo)
152158
os.Remove(metricsFifo)
@@ -183,6 +189,7 @@ func TestJailerMicroVMExecution(t *testing.T) {
183189
ExecFile: getFirecrackerBinaryPath(),
184190
ChrootStrategy: NewNaiveChrootStrategy(jailerFullRootPath, vmlinuxPath),
185191
},
192+
FifoLogWriter: fw,
186193
}
187194

188195
if _, err := os.Stat(vmlinuxPath); err != nil {
@@ -250,6 +257,10 @@ func TestJailerMicroVMExecution(t *testing.T) {
250257
}
251258

252259
m.StopVMM()
260+
261+
info, err := os.Stat(capturedLog)
262+
assert.NoError(t, err, "failed to stat captured log file")
263+
assert.NotEqual(t, 0, info.Size())
253264
}
254265

255266
func TestMicroVMExecution(t *testing.T) {
@@ -263,7 +274,12 @@ func TestMicroVMExecution(t *testing.T) {
263274
socketPath := filepath.Join(testDataPath, "TestMicroVMExecution.sock")
264275
logFifo := filepath.Join(testDataPath, "firecracker.log")
265276
metricsFifo := filepath.Join(testDataPath, "firecracker-metrics")
277+
capturedLog := filepath.Join(testDataPath, "writer.fifo")
278+
fw, err := os.OpenFile(capturedLog, os.O_CREATE|os.O_RDWR, 0600)
279+
require.NoError(t, err, "failed to open fifo writer file")
266280
defer func() {
281+
fw.Close()
282+
os.Remove(capturedLog)
267283
os.Remove(socketPath)
268284
os.Remove(logFifo)
269285
os.Remove(metricsFifo)
@@ -292,6 +308,7 @@ func TestMicroVMExecution(t *testing.T) {
292308
Debug: true,
293309
DisableValidation: true,
294310
NetworkInterfaces: networkIfaces,
311+
FifoLogWriter: fw,
295312
}
296313

297314
ctx := context.Background()
@@ -353,6 +370,10 @@ func TestMicroVMExecution(t *testing.T) {
353370
// didn't for some reason, we still need to terminate it:
354371
m.StopVMM()
355372
m.Wait(vmmCtx)
373+
374+
info, err := os.Stat(capturedLog)
375+
assert.NoError(t, err, "failed to stat captured log file")
376+
assert.NotEqual(t, 0, info.Size())
356377
}
357378

358379
func TestStartVMM(t *testing.T) {
@@ -774,7 +795,7 @@ func TestLogFiles(t *testing.T) {
774795
}
775796

776797
func TestCaptureFifoToFile(t *testing.T) {
777-
fifoPath := filepath.Join(testDataPath, "fifo")
798+
fifoPath := filepath.Join(testDataPath, "TestCaptureFifoToFile")
778799

779800
if err := syscall.Mkfifo(fifoPath, 0700); err != nil {
780801
t.Fatalf("Unexpected error during syscall.Mkfifo call: %v", err)
@@ -786,32 +807,90 @@ func TestCaptureFifoToFile(t *testing.T) {
786807
t.Fatalf("Failed to open file, %q: %v", fifoPath, err)
787808
}
788809

789-
f.Write([]byte("Hello world!"))
810+
expectedBytes := []byte("Hello world!")
811+
f.Write(expectedBytes)
790812
defer f.Close()
791813

792-
go func() {
793-
t := time.NewTicker(250 * time.Millisecond)
794-
select {
795-
case <-t.C:
796-
f.Close()
797-
}
798-
}()
814+
time.AfterFunc(250*time.Millisecond, func() { f.Close() })
815+
816+
logPath := fifoPath + ".log"
817+
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
818+
if err != nil {
819+
t.Fatalf("Failed to create log file: %v", err)
820+
}
821+
822+
var wg sync.WaitGroup
823+
wg.Add(1)
824+
825+
testWriter := &fctesting.TestWriter{
826+
WriteFn: func(b []byte) (int, error) {
827+
defer wg.Done()
828+
829+
return logFile.Write(b)
830+
},
831+
}
832+
833+
if err := captureFifoToFile(fctesting.NewLogEntry(t), fifoPath, testWriter); err != nil {
834+
t.Errorf("Unexpected error: %v", err)
835+
}
836+
837+
defer os.Remove(logPath)
838+
839+
wg.Wait()
840+
_, err = os.Stat(logPath)
841+
assert.NoError(t, err, "failed to stat file")
842+
b, err := ioutil.ReadFile(logPath)
843+
assert.NoError(t, err, "failed to read logPath")
844+
assert.Equal(t, expectedBytes, b)
845+
}
846+
847+
func TestCaptureFifoToFile_nonblock(t *testing.T) {
848+
fifoPath := filepath.Join(testDataPath, "TestCaptureFifoToFile_nonblock")
799849

800-
fifoLogPath := fifoPath + ".log"
801-
fifo, err := os.OpenFile(fifoLogPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
850+
if err := syscall.Mkfifo(fifoPath, 0700); err != nil {
851+
t.Fatalf("Unexpected error during syscall.Mkfifo call: %v", err)
852+
}
853+
defer os.Remove(fifoPath)
854+
855+
logPath := fifoPath + ".log"
856+
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
802857
if err != nil {
803-
t.Fatalf("Failed to create fifo file: %v", err)
858+
t.Fatalf("Failed to create log file: %v", err)
859+
}
860+
861+
var wg sync.WaitGroup
862+
wg.Add(1)
863+
864+
testWriter := &fctesting.TestWriter{
865+
WriteFn: func(b []byte) (int, error) {
866+
defer wg.Done()
867+
868+
return logFile.Write(b)
869+
},
804870
}
805871

806-
if err := captureFifoToFile(fctesting.NewLogEntry(t), fifoPath, fifo); err != nil {
872+
if err := captureFifoToFile(fctesting.NewLogEntry(t), fifoPath, testWriter); err != nil {
807873
t.Errorf("Unexpected error: %v", err)
808874
}
809875

810-
defer os.Remove(fifoLogPath)
876+
defer os.Remove(logPath)
811877

812-
if _, err := os.Stat(fifoLogPath); err != nil {
813-
t.Errorf("Failed to stat file: %v", err)
878+
f, err := os.OpenFile(fifoPath, os.O_RDWR, 0600)
879+
if err != nil {
880+
t.Fatalf("Failed to open file, %q: %v", fifoPath, err)
814881
}
882+
expectedBytes := []byte("Hello world!")
883+
f.Write(expectedBytes)
884+
defer f.Close()
885+
886+
time.AfterFunc(250*time.Millisecond, func() { f.Close() })
887+
888+
wg.Wait()
889+
_, err = os.Stat(logPath)
890+
assert.NoError(t, err, "failed to stat file")
891+
b, err := ioutil.ReadFile(logPath)
892+
assert.NoError(t, err, "failed to read logPath")
893+
assert.Equal(t, expectedBytes, b)
815894
}
816895

817896
func TestSocketPathSet(t *testing.T) {

0 commit comments

Comments
 (0)