{"record":{"id":"093d3614fd95271d","repo":"kubernetes/kops","slug":"already-closed","errorCode":null,"errorMessage":"already closed","messagePattern":"already closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/otel/otlptracefile/writer.go","lineNumber":129,"sourceCode":"\n// writeObjectWithTypeCode is the key function here.  We encode and write the object.\n// We include a header that identifies the object using the provided typeCode.\nfunc (w *writer) writeObjectWithTypeCode(ctx context.Context, typeCode TypeCode, obj proto.Message) error {\n\tbuf, err := proto.Marshal(obj)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"converting to proto: %w\", err)\n\t}\n\n\tcrc32q := crc32.MakeTable(crc32.Castagnoli)\n\tchecksum := crc32.Checksum(buf, crc32q)\n\n\tflags := uint32(0)\n\n\tw.fileMutex.Lock()\n\tdefer w.fileMutex.Unlock()\n\n\tif w.f == nil {\n\t\treturn fmt.Errorf(\"already closed\")\n\t}\n\n\t// write the object with a header.\n\theader := make([]byte, 16)\n\tbinary.BigEndian.PutUint32(header[0:4], uint32(len(buf)))\n\tbinary.BigEndian.PutUint32(header[4:8], checksum)\n\tbinary.BigEndian.PutUint32(header[8:12], flags)\n\tbinary.BigEndian.PutUint32(header[12:16], uint32(typeCode))\n\n\tif _, err := w.f.Write(header); err != nil {\n\t\treturn fmt.Errorf(\"writing header: %w\", err)\n\t}\n\tif _, err := w.f.Write(buf); err != nil {\n\t\t// TODO: Rotate file?\n\t\treturn fmt.Errorf(\"writing body: %w\", err)\n\t}\n\n\treturn nil","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/otel/otlptracefile/writer.go#L111-L147","documentation":"Before writing each framed object, writeObjectWithTypeCode checks w.f under fileMutex; if it is nil the writer has been closed (Close sets f to nil) and any further write is rejected with \"already closed\". This is a lifecycle misuse error, not an I/O failure.","triggerScenarios":"Writing spans after client.Shutdown/Close; a background batch exporter flushing queued spans after shutdown completed; concurrent Shutdown racing with in-flight exports.","commonSituations":"App shutting down while a background exporter still holds buffered spans; tests tearing down the exporter before flushing.","solutions":["Ensure Shutdown is only called after all tracing activity stops; flush spans before Close.","Guard writes: recreate the client (and Start) if you need to export again after Close.","Serialize shutdown ordering so the tracer provider stops producing spans before the file exporter closes."],"exampleFix":"// before\nclient.Shutdown(ctx)\nexportSpan(ctx, span) // already closed\n// after\nexportSpan(ctx, span)\nclient.Shutdown(ctx)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := w.writeObject(ctx, obj); err != nil {\n    if strings.Contains(err.Error(), \"already closed\") {\n        // discard the object; the exporter has been shut down\n        return nil\n    }\n    return err\n}","preventionTips":["Flush/force-export spans before calling Close/Shutdown","Stop span production before closing the writer","Do not reuse a client after Shutdown; create a new one","Order shutdown: tracer provider first, exporter last"],"tags":["opentelemetry","lifecycle","file-io"],"backgroundTag":"write-after-close","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}