Skip to content

Mitogen + sudo + log_stdin/log_stdout leads to preamble read error in first stage (EAGAIN, EWOULDBLOCK, None) #1306

@moreati

Description

@moreati

Under certain conditions (e.g. sudo with the log_input or log_output flag) the stdin of Mitogen's first stage can be set non-blocking (O_NONBLOCK) this results in fp.read() returning None if the buffer is empty.

The error seen by the user looks like

MITO000
Traceback … TypeError: a bytes-like object is required, not 'NoneType'

This may be related to #712, #1185.

Credit to @Forgetyk for the original error and diagnosis, their words follow.

Environment

Component Version
OS Linux 5247089-it57222 5.15.0-142-generic #152-Ubuntu SMP Mon May 19 10:54:31 UTC 2025 x86_64 x86_64 x86_64 x86_64 GNU/Linux
Python  3.10.12
sudo  1.9.9
OpenSSH  OpenSSH_8.9p1 Ubuntu-3ubuntu0.10,
ansible-core  2.12.10

All tests were run with strategy = mitogen_linear and pipelining = true.

Reproducer

  1. Target host – enable sudo I/O logging:

    # /etc/sudoers
    Defaults log_output
    Defaults!/usr/bin/sudoreplay !log_output
    Defaults!/usr/local/bin/sudoreplay !log_output
    Defaults!/sbin/reboot !log_output
    Defaults loglinelen=0
    Defaults logfile=/var/log/sudo.log
    #Defaults:ansible !log_output, !log_input
    root    ALL=(ALL:ALL) ALL
    %admin ALL=(ALL) ALL
    %sudo   ALL=(ALL:ALL) ALL
    @includedir /etc/sudoers.d
    
  2. Target host – ssh settings:

    # /etc/ssh/sshd_config
    Protocol 2
    SyslogFacility AUTH
    LogLevel INFO
    LoginGraceTime 60
    PermitRootLogin no
    PermitEmptyPasswords no
    IgnoreRhosts yes
    HostbasedAuthentication no
    X11Forwarding no
    ClientAliveInterval 900
    ClientAliveCountMax 0
    AllowTcpForwarding no
    ChallengeResponseAuthentication no
    UsePAM yes
    Subsystem    sftp    /usr/lib/openssh/sftp-server
    PrintMotd no
    ListenAddress 0.0.0.
    
  3. Controller – standard Mitogen config (only the interesting bits):

    # ansible.cfg
    [defaults]
    strategy_plugins = /…/ansible_mitogen/plugins/strategy
    strategy         = mitogen_linear
    [ssh_connection]
    pipelining = true
  4. Inventory

    [target]
    iac-test-2 ansible_user=ansible ansible_python_interpreter=/usr/bin/python3
  5. Playbook

     become: yes
     gather_facts: no
     hosts:
       - iac-test-2
     roles:
       - some_role
     vars:
       some_var: "{{ var }}"
  6. Result

    BlockingIOError: [Errno 35] Resource temporarily unavailable
    mitogen.core.py", line 2247, in write
    

    The crash appears within a few hundred ms, long before any module code is executed.

What is happening

  • sudo log_output/log_input spawns a pseudo‑terminal in front of the command it runs and copies all I/O through that PTY.
    The PTY’s kernel buffer is only ~64 KiB.
  • Mitogen opens both ends of its pipes O_NONBLOCK and uses an event loop.
  • During the bootstrap it pushes ~35 KiB of compressed preamble in one go; a couple of other writes follow immediately.
    When the PTY buffer fills up, write() returns EAGAINBlockingIOError, which is not retried and kills the stream.

Plain Ansible (no Mitogen) performs the same copy through sudo, but its os.write() is blocking, so it simply waits and survives.

Work‑around that mask the issue

Defaults:ansible !log_output, !log_input might be a solution for other similar problems, but it is forbidden within our production regulations.

Because disabling I/O logging is off‑limits for compliance, we need Mitogen to gracefully retry EAGAIN (or temporarily switch to blocking writes).

-- Originally posted by @Forgetyk in #1299 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions