{"record":{"id":"770491c0a97454ad","repo":"shadow1ng/fscan","slug":"writer-is-closed","errorCode":null,"errorMessage":"writer is closed","messagePattern":"writer is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/output/writers.go","lineNumber":124,"sourceCode":"\t\tbufWriter:    bufio.NewWriter(file),\n\t\tbuffer:       NewResultBuffer(),\n\t\trealtimeFile: realtimeFile,\n\t\trealtimePath: realtimePath,\n\t}, nil\n}\n\n// WriteHeader 写入头部\nfunc (w *TXTWriter) WriteHeader() error {\n\treturn nil\n}\n\n// Write 收集扫描结果到分类缓冲，同时实时备份\nfunc (w *TXTWriter) Write(result *ScanResult) error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tif w.closed {\n\t\treturn fmt.Errorf(\"writer is closed\")\n\t}\n\tif result == nil {\n\t\treturn fmt.Errorf(\"result cannot be nil\")\n\t}\n\n\t// 1. 加入内存分类缓冲（用于最终有序输出）\n\tw.buffer.Add(result)\n\n\t// 2. 实时写入备份文件（防崩溃丢数据）\n\tif w.realtimeFile != nil {\n\t\tline := w.formatLine(result)\n\t\tif _, err := w.realtimeFile.WriteString(line + \"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write realtime backup: %w\", err)\n\t\t}\n\t\tif err := w.realtimeFile.Sync(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to sync realtime backup: %w\", err)\n\t\t}\n\t}","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/common/output/writers.go#L106-L142","documentation":"State guard in TXTWriter.Write: an attempt was made to write a scan result after the writer had already been closed (Close sets w.closed). Once closed the buffered file and realtime backup file are no longer valid for writes, so any further Write is rejected.","triggerScenarios":"Calling Write after TXTWriter.Close(); manager Close() cascading to writer close while workers still emit results; reusing a writer created via NewTXTWriter across runs.","commonSituations":"Producer goroutines outliving writer shutdown, double-close-then-write patterns, using the Manager after Flush/Close while writers are already closed.","solutions":["Stop all producers before closing the writer (use WaitGroup or done channel)","Check writer lifecycle: create a new writer for a new run","Reorder shutdown so Close is the last operation after all writes"],"exampleFix":"// before\ngo writeResults(w); w.Close() // workers may write after close\n// after\nwg.Wait() // producers done\nw.Close()","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := w.Write(res); err != nil {\n    if strings.Contains(err.Error(), \"writer is closed\") {\n        return errors.New(\"write after close: producer lifecycle bug\")\n    }\n    return err\n}","preventionTips":["Coordinate producer goroutines with WaitGroup before Close","Create a fresh TXTWriter per run instead of resurrecting closed writers","Make Close the final step in a single shutdown routine"],"tags":["output","closed-resource","concurrency"],"backgroundTag":"invalid-state-transition","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}