Skip to content

FileIO.readinto() breaks the contract on io.RawIOBase.read() #118276

Closed
@OlekTk

Description

@OlekTk

Bug report

Bug description:

The documentation of class io.RawIOBase claims that the 'read' method, when called with a specified transfer size, will perform only one system call ("Otherwise, only one system call is ever made."). However, in these circumstances, it relies on the 'readinto' implementation of a particular RawIOBase subclass. In the case of FileIO the 'readinto' method internally calls '_Py_read' (from fileutils.c) which contains a while loop around the 'read' system call, repeating it if it is interrupted by a signal and the current thread cannot handle the interrupt.

This looping behavior is surely convenient for the user, but it is also troubling in some corner cases. I work with a Linux device driver having a blocking 'read' method, which can only be interrupted by a signal. There are several data streams to capture, so I delegate them to different threads. When I have to interrupt the read (e.g. the machine readout trigger has not arrived) I send the signal to those threads (signal.pthread_kill). I see the driver intercepting it and aborting the transfer, then immediately the '_Py_read' repeats the system call starting another read and, in turn, causing my 'read' call never to return.

CPython versions tested on:

3.10

Operating systems tested on:

Linux

Activity

added
type-bugAn unexpected behavior, bug, or error
on Apr 25, 2024
Ryan-Carrier

Ryan-Carrier commented on May 1, 2024

@Ryan-Carrier

I am working on this issue.

cmaloney

cmaloney commented on Apr 30, 2025

@cmaloney
Contributor

The retry loop was added for PEP-475 in Python 3.5. The docs should be updated to match the current behavior, I have been working on that in the implementation comments, will work on the more general I/O docs shortly. In particular, read() without a size will read until a zero-length return from the read underlying system call. The code comments in https://github.com/python/cpython/pull/129012/files are up to date for main.

OlekTk

OlekTk commented on May 19, 2025

@OlekTk
Author

Well, if this is a desired behavior, then there is probably no issue anymore.

In case anyone finds this discussion looking for a solution to a similar problem, here is how to produce an interruptible 'read' syscall:

import ctypes

cread = ctypes.CDLL(None).read
cread.restype = ctypes.c_ssize_t
cread.argtypes = ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t
cmaloney

cmaloney commented on May 19, 2025

@cmaloney
Contributor

Another option is to raise an exception in the signal handler which will cause a "blocking" read/write to exit (https://docs.python.org/3/library/signal.html#execution-of-python-signal-handlers, write cpython/Python/fileutils.c at 7bb1e1a23634bae81bf76fdb34e9f9f7e59b3793 · python/cpython · GitHub, read cpython/Python/fileutils.c at 7bb1e1a23634bae81bf76fdb34e9f9f7e59b3793 · python/cpython · GitHub), should be able to use that to exit reliably on a signal (Signal handler → exception).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      FileIO.readinto() breaks the contract on io.RawIOBase.read() · Issue #118276 · python/cpython

      Follow Lee on X/Twitter - Father, Husband, Serial builder creating AI, crypto, games & web tools. We are friends :) AI Will Come To Life!

      Check out: eBank.nz (Art Generator) | Netwrck.com (AI Tools) | Text-Generator.io (AI API) | BitBank.nz (Crypto AI) | ReadingTime (Kids Reading) | RewordGame | BigMultiplayerChess | WebFiddle | How.nz | Helix AI Assistant