gofr-dev/gofr · error

writer already closed

Error message

writer already closed

What it means

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.

Source

Thrown at pkg/gofr/datasource/file/ftp/storage_adapter.go:32

	"gofr.dev/pkg/gofr/datasource/file"
)

var (
	// Storage adapter errors.
	errFTPConfigNil            = errors.New("FTP config is nil")
	errFTPClientNotInitialized = errors.New("FTP client is not initialized")
	errEmptyObjectName         = errors.New("object name is empty")
	errInvalidOffset           = errors.New("invalid offset: must be >= 0")
	errEmptySourceOrDest       = errors.New("source and destination names cannot be empty")
	errSameSourceAndDest       = errors.New("source and destination are the same")
	errFailedToCreateReader    = errors.New("failed to create reader")
	errFailedToCreateWriter    = errors.New("failed to create writer")
	errObjectNotFound          = errors.New("object not found")
	errFailedToGetObjectAttrs  = errors.New("failed to get object attrs")
	errFailedToDeleteObject    = errors.New("failed to delete object")
	errFailedToListObjects     = errors.New("failed to list objects")
	errFailedToListDirectory   = errors.New("failed to list directory")
	errWriterAlreadyClosed     = errors.New("writer already closed")
	errFTPConfigInvalid        = errors.New("invalid FTP configuration: host and port are required")
)

// Config represents the FTP configuration.
type Config struct {
	Host        string        // FTP server hostname
	User        string        // FTP username
	Password    string        // FTP password
	Port        int           // FTP port
	RemoteDir   string        // Remote directory path. Base Path for all FTP Operations.
	DialTimeout time.Duration // FTP connection timeout
}

// storageAdapter adapts FTP client to implement file.StorageProvider.
type storageAdapter struct {
	cfg  *Config
	conn *ftp.ServerConn
}

View on GitHub (pinned to 187eb24962)

Solutions

  1. Create a new writer via NewWriter for each upload instead of reusing a closed one
  2. Reorder code so all Write calls happen before Close
  3. Remove duplicate Close/defer-Close combinations that close the writer early
  4. Check writer state (closed flag) before writing in wrapper code

Example fix

// before
w, _ := fs.NewWriter(ctx, name, nil)
defer w.Close()
w.Write(data1)
w.Write(data2) // panics/error: writer already closed
// after
w, _ := fs.NewWriter(ctx, name, nil)
w.Write(data1)
w.Write(data2)
w.Close()
Defensive patterns

Strategy: type-guard

Validate before calling

// track writer lifecycle explicitly
type trackedWriter struct { w *ftp.Writer; closed bool }

Type guard

func isClosedWriterError(err error) bool { return errors.Is(err, ftp.ErrWriterAlreadyClosed) }

Try / catch

if _, err := w.Write(data); errors.Is(err, ftp.ErrWriterAlreadyClosed) {
    // create a fresh writer and redo the upload
    w, werr = fs.NewWriter(ctx, name, nil)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01). Data as JSON: /api/errors/4328945e1f44e227. Report an issue: GitHub.