{"record":{"id":"4328945e1f44e227","repo":"gofr-dev/gofr","slug":"writer-already-closed","errorCode":null,"errorMessage":"writer already closed","messagePattern":"writer already closed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gofr/datasource/file/ftp/storage_adapter.go","lineNumber":32,"sourceCode":"\t\"gofr.dev/pkg/gofr/datasource/file\"\n)\n\nvar (\n\t// Storage adapter errors.\n\terrFTPConfigNil            = errors.New(\"FTP config is nil\")\n\terrFTPClientNotInitialized = errors.New(\"FTP client is not initialized\")\n\terrEmptyObjectName         = errors.New(\"object name is empty\")\n\terrInvalidOffset           = errors.New(\"invalid offset: must be >= 0\")\n\terrEmptySourceOrDest       = errors.New(\"source and destination names cannot be empty\")\n\terrSameSourceAndDest       = errors.New(\"source and destination are the same\")\n\terrFailedToCreateReader    = errors.New(\"failed to create reader\")\n\terrFailedToCreateWriter    = errors.New(\"failed to create writer\")\n\terrObjectNotFound          = errors.New(\"object not found\")\n\terrFailedToGetObjectAttrs  = errors.New(\"failed to get object attrs\")\n\terrFailedToDeleteObject    = errors.New(\"failed to delete object\")\n\terrFailedToListObjects     = errors.New(\"failed to list objects\")\n\terrFailedToListDirectory   = errors.New(\"failed to list directory\")\n\terrWriterAlreadyClosed     = errors.New(\"writer already closed\")\n\terrFTPConfigInvalid        = errors.New(\"invalid FTP configuration: host and port are required\")\n)\n\n// Config represents the FTP configuration.\ntype Config struct {\n\tHost        string        // FTP server hostname\n\tUser        string        // FTP username\n\tPassword    string        // FTP password\n\tPort        int           // FTP port\n\tRemoteDir   string        // Remote directory path. Base Path for all FTP Operations.\n\tDialTimeout time.Duration // FTP connection timeout\n}\n\n// storageAdapter adapts FTP client to implement file.StorageProvider.\ntype storageAdapter struct {\n\tcfg  *Config\n\tconn *ftp.ServerConn\n}","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/datasource/file/ftp/storage_adapter.go#L14-L50","documentation":"errWriterAlreadyClosed is returned by the writer's Write method after Close (or Commit) has already been called. FTP writers are single-use: once closed, the underlying transfer connection is finished and cannot accept more data. This is a client-side usage error, not a server error.","triggerScenarios":"Calling Write on an ftp writer after Close/Commit; calling Write twice across a code path where Close is invoked in a defer and then written to again; reusing a cached writer object for a second upload.","commonSituations":"defer w.Close() placed before a later write in the same function; error-retry loops that reuse the closed writer instead of creating a new one; long-lived writer structs stored in application state and reused.","solutions":["Create a new writer via NewWriter for each upload instead of reusing a closed one","Reorder code so all Write calls happen before Close","Remove duplicate Close/defer-Close combinations that close the writer early","Check writer state (closed flag) before writing in wrapper code"],"exampleFix":"// before\nw, _ := fs.NewWriter(ctx, name, nil)\ndefer w.Close()\nw.Write(data1)\nw.Write(data2) // panics/error: writer already closed\n// after\nw, _ := fs.NewWriter(ctx, name, nil)\nw.Write(data1)\nw.Write(data2)\nw.Close()","handlingStrategy":"type-guard","validationCode":"// track writer lifecycle explicitly\ntype trackedWriter struct { w *ftp.Writer; closed bool }","typeGuard":"func isClosedWriterError(err error) bool { return errors.Is(err, ftp.ErrWriterAlreadyClosed) }","tryCatchPattern":"if _, err := w.Write(data); errors.Is(err, ftp.ErrWriterAlreadyClosed) {\n    // create a fresh writer and redo the upload\n    w, werr = fs.NewWriter(ctx, name, nil)\n}","preventionTips":["Treat writers as single-use: one writer per upload, never reuse","Close exactly once, after all writes; avoid mixing defer Close with later writes","Wrap the write-then-close sequence in a helper function to enforce ordering","On retry after failure, create a new writer rather than reusing the closed one"],"tags":["ftp","io","lifecycle"],"backgroundTag":"writer-already-closed","analyzedSha":"187eb24962502e91f1fee856230670958b66e89c","analyzedAt":"2026-09-01T20:34:54.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}