(ctx context.Context, driver Driver, dataStore, address string, getContainerWait ContainerWaitFunc, config *logging.Config)
| 251 | type ContainerWaitFunc func(ctx context.Context, address string, config *logging.Config, outputSeen func() bool) (<-chan containerd.ExitStatus, error) |
| 252 | |
| 253 | func loggingProcessAdapter(ctx context.Context, driver Driver, dataStore, address string, getContainerWait ContainerWaitFunc, config *logging.Config) error { |
| 254 | if err := driver.PreProcess(ctx, dataStore, config); err != nil { |
| 255 | return err |
| 256 | } |
| 257 | |
| 258 | stdoutR, err := cancelreader.NewReader(config.Stdout) |
| 259 | if err != nil { |
| 260 | return err |
| 261 | } |
| 262 | stderrR, err := cancelreader.NewReader(config.Stderr) |
| 263 | if err != nil { |
| 264 | return err |
| 265 | } |
| 266 | go func() { |
| 267 | <-ctx.Done() // delivered on SIGTERM |
| 268 | stdoutR.Cancel() |
| 269 | stderrR.Cancel() |
| 270 | }() |
| 271 | |
| 272 | // copiedBytes counts how much container output has been read so far. It lets |
| 273 | // getContainerWait tell "the task has not been created yet" apart from "the |
| 274 | // task has already exited and been removed" when it sees a missing task. |
| 275 | var copiedBytes atomic.Int64 |
| 276 | outputSeen := func() bool { return copiedBytes.Load() > 0 } |
| 277 | |
| 278 | stdout := make(chan string, 10000) |
| 279 | stderr := make(chan string, 10000) |
| 280 | |
| 281 | // If the driver can write synchronously, emit writes each log entry directly |
| 282 | // from the goroutine that reads the container's stdio. Otherwise it hands the |
| 283 | // entry to the driver's Process method over a buffered channel, which keeps a |
| 284 | // slow (e.g. network-backed) driver from blocking the container. |
| 285 | // |
| 286 | // The synchronous path matters because, when a container exits, containerd |
| 287 | // closes the stdio FIFOs and then tears the logging process down almost |
| 288 | // immediately. Handing the final chunk to another goroutine to write races |
| 289 | // that teardown and can lose a trailing chunk that has no newline; writing it |
| 290 | // inline does not. https://github.com/containerd/nerdctl/issues/5006 |
| 291 | syncDriver, isSync := driver.(SyncDriver) |
| 292 | emit := func(stream, line string) { |
| 293 | if isSync { |
| 294 | if err := syncDriver.WriteLogEntry(stream, line); err != nil { |
| 295 | log.G(ctx).WithError(err).Error("failed to write log entry") |
| 296 | } |
| 297 | return |
| 298 | } |
| 299 | if stream == streamStdout { |
| 300 | stdout <- line |
| 301 | } else { |
| 302 | stderr <- line |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | var wg sync.WaitGroup |
| 307 | |
| 308 | // processStream reads a container stdio FIFO directly and emits its output |
| 309 | // split into newline-terminated lines. Complete lines are emitted as they are |
| 310 | // read; a trailing fragment without a newline is buffered until more output |
searching dependent graphs…