iox

package module
v0.0.0-...-b5f387f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2026 License: GPL-3.0 Imports: 5 Imported by: 1

README

Context-Aware Golang io Code

GoDoc Build Status codecov

The iox Go package contains context-aware extensions for the io package.

For example:

import (
	"context"
	"bytes"
	"net/http"

	"github.com/bassosimone/iox"
	"github.com/bassosimone/runtimex"
)

// The iox package is context aware
var ctx context.Context

// We assume you somehow created the context possibly binding it to signals
// ...

// Create a request bound to an existing context
req := runtimex.PanicOnError1(http.NewRequestWithContext(ctx, "GET", "https://example.com", nil))

// Perform the round trip, whose maximum duration is context bounded
resp := runtimex.PanicOnError1(http.DefaultClient.Do(req))
defer resp.Body.Close()

// Use iox.CopyContext to read the body in such a way that, if the context is
// canceled, we immediately interrupt reading.
//
// CopyContext only closes resp.Body on context cancellation. On success,
// we rely on the `defer resp.Body.Close()` statement provided above.
buff := &bytes.Buffer{}
writer := iox.NewLockedWriteCloser(iox.NopWriteCloser(buff))
count, err := iox.CopyContext(ctx, writer, resp.Body)

See also the example_test.go example.

Installation

To add this package as a dependency to your module:

go get github.com/bassosimone/iox

Development

To run the tests:

go test -v .

To measure test coverage:

go test -v -cover .

License

SPDX-License-Identifier: GPL-3.0-or-later

History

Inspired by ooni/probe-cli.

Documentation

Overview

Package iox contains small, context-aware extensions for the io package.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrClosed = errors.New("locked writer is closed")

ErrClosed is returned when writing on a closed *LockedWriteCloser.

Functions

func CopyContext

func CopyContext(ctx context.Context, lwc *LockedWriteCloser, rc io.ReadCloser) (int, error)

CopyContext is a context-interruptible variant of io.Copy.

It copies from rc into lwc in a background goroutine. On return, it always closes lwc. It only closes rc when the context is canceled, to unblock any in-flight Read in the background goroutine.

On success, rc is NOT closed. The caller MUST ensure rc is closed after CopyContext returns (e.g., via defer).

The returned error is either caused by I/O or by the context.

Example

ExampleCopyContext shows how to copy an HTTP response body into a buffer using iox.

Note: the caller is responsible for closing resp.Body after iox.CopyContext returns. CopyContext only closes the reader when the context is canceled.

package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/bassosimone/iox"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		fmt.Fprint(w, "hello from httptest\n")
	}))
	defer server.Close()

	resp, err := http.Get(server.URL)
	if err != nil {
		log.Println("request error:", err)
		return
	}
	defer resp.Body.Close()

	buff := &bytes.Buffer{}
	writer := iox.NewLockedWriteCloser(iox.NopWriteCloser(buff))
	if _, err := iox.CopyContext(context.Background(), writer, resp.Body); err != nil {
		log.Println("copy error:", err)
		return
	}

	fmt.Println(buff.String())
}
Output:
hello from httptest

func LimitReadCloser

func LimitReadCloser(rc io.ReadCloser, n int64) io.ReadCloser

LimitReadCloser wraps rc such that reads are limited to n bytes while Close forwards to the underlying rc.

func NopWriteCloser

func NopWriteCloser(w io.Writer) io.WriteCloser

NopWriteCloser wraps an io.Writer and returns a no-op io.WriteCloser.

This is useful when CopyContext needs to stream into a writer that does not require closing, such as a *bytes.Buffer.

func ReadAllContext

func ReadAllContext(ctx context.Context, rc io.ReadCloser) ([]byte, error)

ReadAllContext is a context-interruptible variant of io.ReadAll.

It reads from rc into an internal buffer using CopyContext. On success, rc is NOT closed (the caller MUST close it, e.g., via defer). On context cancellation, rc is closed to unblock any in-flight Read.

The returned byte slice contains whatever was read before the error (if any), so partial results are available even when the context is canceled.

Types

type LockedWriteCloser

type LockedWriteCloser struct {
	// contains filtered or unexported fields
}

LockedWriteCloser is a concurrency safe io.WriteCloser wrapper.

It serializes writes, makes Close idempotent, and keeps track of the number of bytes successfully written.

All methods are safe for concurrent use.

Close is serialized with Write, so it may block until an in-flight Write returns.

Construct using NewLockedWriteCloser.

func NewLockedWriteCloser

func NewLockedWriteCloser(w io.WriteCloser) *LockedWriteCloser

NewLockedWriteCloser wraps an io.WriteCloser and returns a concurrency-safe wrapper.

func (*LockedWriteCloser) Close

func (w *LockedWriteCloser) Close() error

Close ensures that subsequent writes would fail with ErrClosed.

Returns nil, ErrClosed, or the error occurred when closing the io.WriteCloser.

func (*LockedWriteCloser) Count

func (w *LockedWriteCloser) Count() int

Count returns the number of bytes successfully written so far.

func (*LockedWriteCloser) LockedWrite

func (w *LockedWriteCloser) LockedWrite(data []byte) (int, error)

LockedWrite writes the given bytes to the underlying io.WriteCloser.

The returned error is nil, ErrClosed when closed, or the error ocurred when attempting to write into the underlying io.WriteCloser.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL