Skip to content

Commit

Permalink
qlog: defer creation of the qlog file in QLOGDIR until the first write
Browse files Browse the repository at this point in the history
If the syscall to create the qlog file takes a non-negligible amount of
time, this can block the connection's run loop, or, even worse, the
server's run loop.

By creating the qlog file on the first write, it will be created by a
the qlogger's Go routine, and not be able to block the run loop. The
qlogger uses a buffered channel (capacity: 50). The qlogger will
therefore only block if more than 50 qlog events are queued before the
syscall returns.
  • Loading branch information
marten-seemann committed Feb 4, 2024
1 parent dc49f56 commit 6991981
Showing 1 changed file with 54 additions and 12 deletions.
66 changes: 54 additions & 12 deletions qlog/qlog_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,55 @@ import (
"bufio"
"context"
"fmt"
"log"
"io"
"os"
"strings"

"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/logging"
)

// lazyWriteCloser creates the qlog file when the first log line is written.
// This means that creating the qlog (there's no guarantee how long the syscalls will take)
// won't block the connection, since qlogs are serialized from a separate Go routine,
// that takes events from a buffered channel (size: eventChanSize).
type lazyWriteCloser struct {
createFile func() (*os.File, error)
createFileErr error
io.WriteCloser
}

func (w *lazyWriteCloser) init() error {
if w.createFileErr != nil {
return w.createFileErr
}
if w.createFile == nil {
return nil
}
f, err := w.createFile()
if err != nil {
w.createFileErr = err
return err
}
w.createFile = nil
w.WriteCloser = utils.NewBufferedWriteCloser(bufio.NewWriter(f), f)
return nil
}

func (w *lazyWriteCloser) Write(b []byte) (int, error) {
if err := w.init(); err != nil {
return 0, err
}
return w.WriteCloser.Write(b)
}

func (w *lazyWriteCloser) Close() error {
if err := w.init(); err != nil {
return err
}
return w.WriteCloser.Close()
}

// DefaultTracer creates a qlog file in the qlog directory specified by the QLOGDIR environment variable.
// File names are <odcid>_<perspective>.qlog.
// Returns nil if QLOGDIR is not set.
Expand All @@ -34,16 +75,17 @@ func qlogDirTracer(p logging.Perspective, connID logging.ConnectionID, label str
if qlogDir == "" {
return nil
}
if _, err := os.Stat(qlogDir); os.IsNotExist(err) {
if err := os.MkdirAll(qlogDir, 0o755); err != nil {
log.Fatalf("failed to create qlog dir %s: %v", qlogDir, err)
return NewConnectionTracer(&lazyWriteCloser{createFile: func() (*os.File, error) {
if _, err := os.Stat(qlogDir); os.IsNotExist(err) {
if err := os.MkdirAll(qlogDir, 0o755); err != nil {
return nil, fmt.Errorf("failed to create qlog dir %s: %v", qlogDir, err)
}
}
}
path := fmt.Sprintf("%s/%s_%s.qlog", strings.TrimRight(qlogDir, "/"), connID, label)
f, err := os.Create(path)
if err != nil {
log.Printf("Failed to create qlog file %s: %s", path, err.Error())
return nil
}
return NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), p, connID)
path := fmt.Sprintf("%s/%s_%s.qlog", strings.TrimRight(qlogDir, "/"), connID, label)
f, err := os.Create(path)
if err != nil {
return nil, fmt.Errorf("failed to create qlog file %s: %s", path, err.Error())
}
return f, nil
}}, p, connID)
}

0 comments on commit 6991981

Please sign in to comment.