Thanks to visit codestin.com
Credit goes to pkg.go.dev

ctxio

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 5, 2026 License: MIT Imports: 13 Imported by: 0

README

ctxio

Context-aware, cancelable I/O for Go.

Release Go Doc Software License Go Report Card

ctxio wraps files, network connections, pipes, TTYs, etc. so that reads and writes can be canceled via a context.Context — without consuming data from the underlying file descriptor. This is useful when you need to interrupt a blocking Read or Write and then continue using the same connection or file afterwards.

Cancelation Mechanisms

The standard library does not support canceling a blocking read without closing the file descriptor. ctxio solves this by using OS-level I/O multiplexing primitives to wait for data before issuing the actual read or write:

Platform Mechanism
Linux epoll, select, deadlines
macOS, BSD kqueue, select, deadline
Windows Overlapped I/O, WaitForMultipleObjects, deadlines, CancelSynchronousIo
Generic Unix select , deadlines
Others deadlines

When the context is canceled, the wait is interrupted and ErrCanceled is returned. No data is lost because the actual read(2) / write(2) never happened. Note that the io_uring mechanism for Linux was conciously not implemented because it is disabled by default or compiled out of the kernel on some distributions due to security concerns while the epoll backend achieves the same results reliably.

Usage

Use a suitable Wrap* for your IO object and use either the context-aware ReadContext/WriteContext methods or the regular io.ReadWriter interface together with Cancel(), CancelReads(), CancelWrites(), Reset(), ResetReader() and ResetWriter().

rawTTY, _ := os.Open("/dev/tty")
tty, _ := ctxio.WrapFile(rawTTY)
defer tty.Close()

buf := make([]byte, 256)

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// cancel with a context
n, err := tty.ReadContext(ctx, buf)
if errors.Is(err, ctxio.ErrCanceled) {
    fmt.Println("read timed out, but the file is still usable")
}

tty.Reset()

// or cancel explicitly
go func() {
  time.Sleep(1 * time.Second)
  tty.Cancel()
} ()

_, err = tty.Read(buf)

Cancelation Methods

There are two ways to cancel an operation:

  1. Context-based: Use ReadContext / WriteContext with a cancelable context.
  2. Explicit: Call Cancel() directly. This is useful when working with APIs that expect a plain io.Reader / io.Writer — the Read and Write methods on ContextIO can be interrupted by calling Cancel(), CancelReads() or CancelWrites() from another goroutine.

Cancelation and Reset Semantics

When Cancel() (or CancelReads() / CancelWrites()) is called, it affects subsequent operations differently depending on which method you use:

  • Read / Write return ErrCanceled immediately until the corresponding Reset() (or ResetReader() / ResetWriter()) is called.
  • ReadContext / WriteContext with a fresh context automatically clear the canceled state before starting, so a fresh context is sufficient to resume I/O without an explicit Reset() call.
cio, _ := ctxio.WrapFile(file)

cio.Cancel()
cio.Read(buf)  // returns ErrCanceled (sticky)

// ReadContext auto-clears the canceled state and proceeds normally:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cio.ReadContext(ctx, buf)  // proceeds normally

// To resume plain Read/Write after Cancel, call Reset explicitly:
cio.Cancel()
cio.Reset()
cio.Read(buf)  // now works again

Bidirectional proxying

Connect copies data between two ContextIO values in both directions and respects context cancelation and in case an error occurs, it returns this error instead of subsequent ones that result from the broken connection.

cioA, _ := ctxio.WrapFile(fileA)
cioB, _ := ctxio.WrapFile(fileB)

err := ctxio.Connect(ctx, cioA, cioB)

ConnectAndClose does the same for arbitrary io.ReadWriteClosers. It achieves this by closing both sides when an error occurs or the context is cancelled.

err := ctxio.ConnectAndClose(ctx, connA, connB)

Closable Backend

For objects that don't support the standard mechanisms (e.g., pipes, streams from external libraries), the closable backend with WrapClosableReader, WrapClosableWriter, or WrapClosableReadWriter, which provides the same API with some caveats:

  • Cancellation works by closing the underlying object, making it permanently unusable after cancellation.
  • The object cannot be reused after Cancel() is called, even after calling Reset()Reset() only clears the cancellation flag.
  • This backend is best used for one-shot operations or when the object is expected to be closed anyway.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrCanceled is returned when an I/O operation was canceled.
	ErrCanceled = fmt.Errorf("I/O operation canceled")
	// ErrNotSupported is returned when wrapping an object that is not supported
	// by ctxio.
	ErrNotSupported = fmt.Errorf("not supported")
)

Functions

func Connect

func Connect(ctx context.Context, a ContextIO, b ContextIO) error

Connect copies data between a and b in both directions until an error occurs or ctx is canceled. It uses the ContextIO cancellation mechanism so that no data is consumed from either side when the context expires. If both copy directions fail, Connect returns the first error.

func ConnectAndClose

func ConnectAndClose(ctx context.Context, a io.ReadWriteCloser, b io.ReadWriteCloser) error

ConnectAndClose is a convenience wrapper around Connect that accepts plain io.ReadWriteCloser values and closes both sides when it returns.

Types

type ContextIO

type ContextIO interface {
	// ReadWriteCloser is the interface for interfacing with APIs that are not
	// context aware. In this case, cancelation can be achieved explicitly using
	// the Cancel* and Reset* methods. It is recommended not to mix IO using the
	// Read()/Write() API and the explicit cancelation methods with the
	// context-aware IO methods.
	io.ReadWriteCloser

	// ReadContext is like Read but it returns ErrCanceled as soon as the
	// context expires. Calling it with a fresh context resets the ContextIO
	// reader. It is recommended not to mix IO using the Read()/Write() API and
	// the explicit cancelation methods with the context-aware IO methods.
	ReadContext(ctx context.Context, buffer []byte) (int, error)
	// WriteContext is like Write but it returns ErrCanceled as soon as the
	// context expires. Calling it with a fresh context resets the ContextIO
	// writer. It is recommended not to mix IO using the Read()/Write() API and
	// the explicit cancelation methods with the context-aware IO methods.
	WriteContext(ctx context.Context, buffer []byte) (int, error)

	// Name returns the name of object wrapped by the ContextIO.
	Name() string

	// Cancel cancels any ongoing and future Read and Write calls. All
	// subsequent calls to Read/Write will return ErrCanceled until Reset is
	// called. ReadContext/WriteContext are not affected: they clear the
	// canceled state automatically before starting a new operation.
	// It is equivalent to calling both CancelReads and CancelWrites.
	Cancel()
	// CancelReads cancels any ongoing and future Read calls. All subsequent
	// calls to Read will return ErrCanceled until ResetReader is called.
	// ReadContext is not affected as it clears the canceled state automatically
	// before starting a new operation when a fresh context is provided.
	CancelReads()
	// CancelWrites cancels any ongoing and future Write calls. All subsequent
	// calls to Write will return ErrCanceled until ResetWriter is called.
	// WriteContext is not affected as it clears the canceled state
	// automatically before starting a new operation when a fresh context is
	// provided.
	CancelWrites()

	// ResetReader clears the read cancellation state, allowing subsequent Reads
	// to proceed. Must not be called while a Read is in progress.
	ResetReader()
	// ResetWriter clears the write cancellation state, allowing subsequent
	// Writes to proceed. Must not be called while a Write is in progress.
	ResetWriter()
	// Reset clears both read and write cancellation state. Reset must not be
	// called while a Read or Write is in progress.
	Reset()
}

ContextIO provides context-aware, cancelable I/O operations for objects like files, sockets, pipes and more.

func WrapClosableReadWriter

func WrapClosableReadWriter(rwc io.ReadWriteCloser, name string) (ContextIO, error)

WrapClosableReadWriter returns a ContextIO wrapping an io.ReadWriteCloser using close-based cancellation. Cancellation works by closing the underlying object, which means the object cannot be reused after cancellation. Reset clears the cancellation flag but does not reopen the object.

func WrapClosableReader

func WrapClosableReader(rc io.ReadCloser, name string) (ContextIO, error)

WrapClosableReader returns a ContextIO wrapping an io.ReadCloser using close-based cancellation. Cancellation works by closing the underlying object, which means the object cannot be reused after cancellation. Reset clears the cancellation flag but does not reopen the object. Write and WriteContext always return ErrNotSupported.

func WrapClosableWriter

func WrapClosableWriter(wc io.WriteCloser, name string) (ContextIO, error)

WrapClosableWriter returns a ContextIO wrapping an io.WriteCloser using close-based cancellation. Cancellation works by closing the underlying object, which means the object cannot be reused after cancellation. Reset clears the cancellation flag but does not reopen the object. Read and ReadContext always return ErrNotSupported.

func WrapConn

func WrapConn(conn net.Conn) (ContextIO, error)

WrapConn returns a ContextIO interface wrapping the given net.Conn with context-aware and explicitly cancelable I/O based on the epoll mechanism, falling back to deadline-based cancelation if epoll is not supported for the net.Conn.

func WrapDeadliner

func WrapDeadliner(d Deadliner, name string) (ContextIO, error)

WrapDeadliner returns a ContextIO wrapping any value that implements the Deadliner interface. This is useful for types that are not an *os.File or net.Conn but still support deadline-based I/O (e.g. TLS connections, QUIC streams). Returns an error if the value does not actually support deadlines at runtime.

func WrapFd

func WrapFd(fd uintptr, name string) (ContextIO, error)

WrapFd returns a ContextIO interface wrapping the given file descriptor with context-aware and explicitly cancelable I/O based on the epoll mechanism, falling back to deadline-based cancelation if epoll is not supported for the file descriptor.

func WrapFile

func WrapFile(file *os.File) (ContextIO, error)

WrapFile returns a ContextIO interface wrapping the given *os.File with context-aware and explicitly cancelable I/O. The linux implementation is based on the epoll mechanism, falling back to deadline-based cancelation for files that support it but cannot be used with epoll (e.g. some device files).

type Deadliner

type Deadliner interface {
	io.ReadWriter
	SetReadDeadline(t time.Time) error
	SetWriteDeadline(t time.Time) error
}

Deadliner is implemented by types that support deadline-based I/O, such as *os.File (for pollable file descriptors) and net.Conn.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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