python/cpython · error · Error

Unimplemented ioctl request

Error message

Unimplemented ioctl request

What it means

Raised by _ProactorBaseWritePipeTransport.sendto() when the transport was created for a fixed peer address but sendto() is called with a different non-None address. A datagram transport created with an explicit address is 'connected' to that peer, so the destination can only be None (use the bound address) or exactly the bound address. Any other value means the API is being used against its contract.

Source

Thrown at Platforms/emscripten/web_example_pyrepl_jspi/src.mjs:163

    );
    const toWrite = Array.from(buffer.subarray(offset, offset + length));
    PTY.write(toWrite);
    return length;
  },

  async pollAsync(stream, timeout) {
    if (!PTY.readable && timeout) {
      await waitForReadable(timeout);
    }
    return (PTY.readable ? POLLIN : 0) | (PTY.writable ? POLLOUT : 0);
  },
  ioctl(stream, request, varargs) {
    if (request === FIONREAD) {
      const res = PTY.fromLdiscToUpperBuffer.length;
      Module.HEAPU32[varargs / 4] = res;
      return 0;
    }
    throw new Error("Unimplemented ioctl request");
  },
};

async function setupStdio(Module) {
  Object.assign(Module.TTY.default_tty_ops, tty_ops);
  Object.assign(Module.TTY.stream_ops, tty_stream_ops);
}

const emscriptenSettings = {
  async preRun(Module) {
    Module.addRunDependency("pre-run");
    Module.ENV.TERM = "xterm-256color";
    // Uncomment next line to turn on tracing (messages go to browser console).
    // Module.ENV.PYREPL_TRACE = "1";

    // Leak module so we can try to show traceback if we crash on startup
    globalThis.Module = Module;
    await Promise.all([setupStdlib(Module), setupStdio(Module)]);

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Create the transport without remote_addr if you need to send to multiple destinations, then pass the full address on every sendto
  2. If the transport is connected, call sendto(data) or sendto(data, None) and let it use the bound address
  3. Cache the exact address tuple you passed to create_datagram_endpoint and reuse that object for sends
  4. For server-style reply logic, use transport.sendto(data, addr) on a transport created with local_addr only

Example fix

# before
_, tr = await loop.create_datagram_endpointProto, remote_addr=('127.0.0.1', 9999))
tr.sendto(data, ('10.0.0.1', 9999))  # ValueError
# after
_, tr = await loop.create_datagram_endpoint(Proto, local_addr=('0.0.0.0', 0))
tr.sendto(data, ('10.0.0.1', 9999))  # ok: unconnected transport
Defensive patterns

Strategy: validation

Validate before calling

def send(tr, data, addr, bound):
    if bound is not None and addr not in (None, bound):
        raise ValueError(f'transport is connected to {bound}')
    tr.sendto(data, addr)

Try / catch

try:
    tr.sendto(data, addr)
except ValueError as e:
    if 'Invalid address' in str(e):
        # either use the bound peer or recreate as unconnected
        tr.sendto(data)  # falls back to the connected address
    else:
        raise

Prevention

When it happens

Trigger: loop.create_datagram_endpoint(protocol, remote_addr=(host, port)) then transport.sendto(data, other_addr). Also when an equal-looking address differs in normalization (e.g. ('127.0.0.1', 9999) vs an IPv6 tuple or a resolved duplicate with different family/type fields, since tuple equality includes every element).

Common situations: Reusing a client datagram transport to 'reply' to multiple peers (it was connected to one); DNS re-resolution producing a tuple that compares unequal to the bound one; code copied from an unconnected-socket pattern (where sendto with any address is fine) into a connected transport.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/d192b41e601c86f4. Report an issue: GitHub.