Skip to content

Commit f77db1d

Browse files
authored
Merge pull request #1321 from moreati/release-v0.3.26
Release v0.3.26
2 parents 021d712 + fd1d455 commit f77db1d

File tree

21 files changed

+382
-147
lines changed

21 files changed

+382
-147
lines changed

.github/workflows/tests.yml

Lines changed: 31 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,17 @@ jobs:
2525
fail-fast: false
2626
matrix:
2727
include:
28-
- name: Ans_27_210
29-
tox_env: py27-mode_ansible-ansible2.10
30-
- name: Ans_27_4
31-
tox_env: py27-mode_ansible-ansible4
28+
- tox_env: py27-m_ans-ans2.10
29+
- tox_env: py27-m_ans-ans4
3230

33-
- name: Ans_36_210
31+
- tox_env: py36-m_ans-ans2.10
3432
python_version: '3.6'
35-
tox_env: py36-mode_ansible-ansible2.10
36-
- name: Ans_36_4
33+
- tox_env: py36-m_ans-ans4
3734
python_version: '3.6'
38-
tox_env: py36-mode_ansible-ansible4
3935

40-
- name: Mito_27
41-
tox_env: py27-mode_mitogen
42-
- name: Mito_36
36+
- tox_env: py27-m_mtg
37+
- tox_env: py36-m_mtg
4338
python_version: '3.6'
44-
tox_env: py36-mode_mitogen
4539

4640
steps:
4741
- uses: actions/checkout@v4
@@ -98,7 +92,7 @@ jobs:
9892
run: |
9993
set -o errexit -o nounset -o pipefail
10094
101-
# Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12)
95+
# Tox environment name (e.g. py312-m_mtg) -> Python executable name (e.g. python3.12)
10296
PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))')
10397
10498
if [[ -z $PYTHON ]]; then
@@ -123,7 +117,7 @@ jobs:
123117
run: |
124118
set -o errexit -o nounset -o pipefail
125119
126-
# Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12)
120+
# Tox environment name (e.g. py312-m_mtg) -> Python executable name (e.g. python3.12)
127121
PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))')
128122
129123
if [[ -z $PYTHON ]]; then
@@ -147,50 +141,36 @@ jobs:
147141
fail-fast: false
148142
matrix:
149143
include:
150-
- name: Ans_311_210
144+
- tox_env: py311-m_ans-ans2.10
151145
python_version: '3.11'
152-
tox_env: py311-mode_ansible-ansible2.10
153-
- name: Ans_311_3
146+
- tox_env: py311-m_ans-ans3
154147
python_version: '3.11'
155-
tox_env: py311-mode_ansible-ansible3
156-
- name: Ans_311_4
148+
- tox_env: py311-m_ans-ans4
157149
python_version: '3.11'
158-
tox_env: py311-mode_ansible-ansible4
159-
- name: Ans_311_5
150+
- tox_env: py311-m_ans-ans5
160151
python_version: '3.11'
161-
tox_env: py311-mode_ansible-ansible5
162-
- name: Ans_313_6
152+
- tox_env: py313-m_ans-ans6
163153
python_version: '3.13'
164-
tox_env: py313-mode_ansible-ansible6
165-
- name: Ans_313_7
154+
- tox_env: py313-m_ans-ans7
166155
python_version: '3.13'
167-
tox_env: py313-mode_ansible-ansible7
168-
- name: Ans_313_8
156+
- tox_env: py313-m_ans-ans8
169157
python_version: '3.13'
170-
tox_env: py313-mode_ansible-ansible8
171-
- name: Ans_313_9
158+
- tox_env: py313-m_ans-ans9
172159
python_version: '3.13'
173-
tox_env: py313-mode_ansible-ansible9
174-
- name: Ans_313_10
160+
- tox_env: py313-m_ans-ans10
175161
python_version: '3.13'
176-
tox_env: py313-mode_ansible-ansible10
177-
- name: Ans_313_11
162+
- tox_env: py313-m_ans-ans11
178163
python_version: '3.13'
179-
tox_env: py313-mode_ansible-ansible11
180-
- name: Ans_313_12
164+
- tox_env: py313-m_ans-ans12
181165
python_version: '3.13'
182-
tox_env: py313-mode_ansible-ansible12
183166

184-
- name: Van_313_11
167+
- tox_env: py313-m_ans-ans11-s_lin
185168
python_version: '3.13'
186-
tox_env: py313-mode_ansible-ansible11-strategy_linear
187-
- name: Van_313_12
169+
- tox_env: py313-m_ans-ans12-s_lin
188170
python_version: '3.13'
189-
tox_env: py313-mode_ansible-ansible12-strategy_linear
190171

191-
- name: Mito_313
172+
- tox_env: py313-m_mtg
192173
python_version: '3.13'
193-
tox_env: py313-mode_mitogen
194174

195175
steps:
196176
- uses: actions/checkout@v4
@@ -234,7 +214,7 @@ jobs:
234214
run: |
235215
set -o errexit -o nounset -o pipefail
236216
237-
# Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12)
217+
# Tox environment name (e.g. py312-m_mtg) -> Python executable name (e.g. python3.12)
238218
PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))')
239219
240220
if [[ -z $PYTHON ]]; then
@@ -250,7 +230,7 @@ jobs:
250230
run: |
251231
set -o errexit -o nounset -o pipefail
252232
253-
# Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12)
233+
# Tox environment name (e.g. py312-m_mtg) -> Python executable name (e.g. python3.12)
254234
PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))')
255235
256236
if [[ -z $PYTHON ]]; then
@@ -270,22 +250,14 @@ jobs:
270250
fail-fast: false
271251
matrix:
272252
include:
273-
- name: Mito_313
274-
tox_env: py313-mode_mitogen
275-
276-
- name: Loc_313_11
253+
- tox_env: py313-m_lcl-ans11
277254
sshpass_version: "1.10"
278-
tox_env: py313-mode_localhost-ansible11
279-
280-
- name: Van_313_11
255+
- tox_env: py313-m_lcl-ans11-s_lin
281256
sshpass_version: "1.10"
282-
tox_env: py313-mode_localhost-ansible11-strategy_linear
283-
284-
- name: Loc_313_12
285-
tox_env: py313-mode_localhost-ansible12
257+
- tox_env: py313-m_lcl-ans12
258+
- tox_env: py313-m_lcl-ans12-s_lin
286259

287-
- name: Van_313_12
288-
tox_env: py313-mode_localhost-ansible12-strategy_linear
260+
- tox_env: py313-m_mtg
289261

290262
steps:
291263
- uses: actions/checkout@v4
@@ -325,7 +297,7 @@ jobs:
325297
run: |
326298
set -o errexit -o nounset -o pipefail
327299
328-
# Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12)
300+
# Tox environment name (e.g. py312-m_mtg) -> Python executable name (e.g. python3.12)
329301
PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))')
330302
331303
if [[ -z $PYTHON ]]; then
@@ -341,7 +313,7 @@ jobs:
341313
run: |
342314
set -o errexit -o nounset -o pipefail
343315
344-
# Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12)
316+
# Tox environment name (e.g. py312-m_mtg) -> Python executable name (e.g. python3.12)
345317
PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))')
346318
347319
if [[ -z $PYTHON ]]; then

ansible_mitogen/connection.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ def _connect_mitogen_doas(spec):
409409
#: generating ContextService keyword arguments matching a connection
410410
#: specification.
411411
CONNECTION_METHOD = {
412+
# Ansible connection plugins
412413
'buildah': _connect_buildah,
413414
'docker': _connect_docker,
414415
'kubectl': _connect_kubectl,
@@ -421,9 +422,14 @@ def _connect_mitogen_doas(spec):
421422
'setns': _connect_setns,
422423
'ssh': _connect_ssh,
423424
'smart': _connect_ssh, # issue #548.
425+
426+
# Ansible become plugins
427+
'community.general.doas': _connect_doas,
424428
'su': _connect_su,
425429
'sudo': _connect_sudo,
426430
'doas': _connect_doas,
431+
432+
# Mitogen specific methods
427433
'mitogen_su': _connect_mitogen_su,
428434
'mitogen_sudo': _connect_mitogen_sudo,
429435
'mitogen_doas': _connect_mitogen_doas,

ansible_mitogen/process.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ def __init__(self, _init_logging=True):
426426

427427
common_setup(_init_logging=_init_logging)
428428

429-
self.parent_sock, self.child_sock = socket.socketpair()
429+
self.parent_sock, self.child_sock = mitogen.core.socketpair()
430430
mitogen.core.set_cloexec(self.parent_sock.fileno())
431431
mitogen.core.set_cloexec(self.child_sock.fileno())
432432

docs/changelog.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ To avail of fixes in an unreleased version, please download a ZIP file
1818
`directly from GitHub <https://github.com/mitogen-hq/mitogen/>`_.
1919

2020

21+
v0.3.26 (2025-08-04)
22+
--------------------
23+
24+
* :gh:issue:`1318` CI: Abbreviate Github Actions job names
25+
* :gh:issue:`1309` :mod:`ansible_mitogen`: Fix ``become_method: doas``
26+
* :gh:issue:`712` :mod:`mitogen`: Fix :exc:`BlockingIOError` & ``EAGAIN``
27+
errors in subprocesses that write to stdio
28+
29+
2130
v0.3.25 (2025-07-29)
2231
--------------------
2332

mitogen/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636

3737
#: Library version as a tuple.
38-
__version__ = (0, 3, 25)
38+
__version__ = (0, 3, 26)
3939

4040

4141
#: This is :data:`False` in slave contexts. Previously it was used to prevent

mitogen/core.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ def _path_importer_cache(cls, path):
8787
import weakref
8888
import zlib
8989

90+
if sys.version_info > (3,5):
91+
from os import get_blocking, set_blocking
92+
else:
93+
def get_blocking(fd):
94+
return not fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_NONBLOCK
95+
96+
def set_blocking(fd, blocking):
97+
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
98+
if blocking: fcntl.fcntl(fd, fcntl.F_SETFL, fl & ~os.O_NONBLOCK)
99+
else: fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
100+
90101
try:
91102
# Python >= 3.4, PEP 451 ModuleSpec API
92103
import importlib.machinery
@@ -559,26 +570,6 @@ def set_cloexec(fd):
559570
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
560571

561572

562-
def set_nonblock(fd):
563-
"""
564-
Set the file descriptor `fd` to non-blocking mode. For most underlying file
565-
types, this causes :func:`os.read` or :func:`os.write` to raise
566-
:class:`OSError` with :data:`errno.EAGAIN` rather than block the thread
567-
when the underlying kernel buffer is exhausted.
568-
"""
569-
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
570-
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
571-
572-
573-
def set_block(fd):
574-
"""
575-
Inverse of :func:`set_nonblock`, i.e. cause `fd` to block the thread when
576-
the underlying kernel buffer is exhausted.
577-
"""
578-
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
579-
fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
580-
581-
582573
def io_op(func, *args):
583574
"""
584575
Wrap `func(*args)` that may raise :class:`select.error`, :class:`IOError`,
@@ -720,20 +711,30 @@ def import_module(modname):
720711
return __import__(modname, None, None, [''])
721712

722713

723-
def pipe():
714+
def pipe(blocking=None):
724715
"""
725716
Create a UNIX pipe pair using :func:`os.pipe`, wrapping the returned
726717
descriptors in Python file objects in order to manage their lifetime and
727718
ensure they are closed when their last reference is discarded and they have
728719
not been closed explicitly.
729720
"""
730721
rfd, wfd = os.pipe()
722+
for fd in rfd, wfd:
723+
if blocking is not None: set_blocking(fd, blocking) # noqa: E701
731724
return (
732725
os.fdopen(rfd, 'rb', 0),
733726
os.fdopen(wfd, 'wb', 0)
734727
)
735728

736729

730+
def socketpair(blocking=None):
731+
fp1, fp2 = socket.socketpair()
732+
for fp in fp1, fp2:
733+
fd = fp.fileno()
734+
if blocking is not None: set_blocking(fd, blocking) # noqa: E701
735+
return fp1, fp2
736+
737+
737738
def iter_split(buf, delim, func):
738739
"""
739740
Invoke `func(s)` for each `delim`-delimited chunk in the potentially large
@@ -1879,8 +1880,7 @@ def accept(self, rfp, wfp):
18791880
"""
18801881
Attach a pair of file objects to :attr:`receive_side` and
18811882
:attr:`transmit_side`, after wrapping them in :class:`Side` instances.
1882-
:class:`Side` will call :func:`set_nonblock` and :func:`set_cloexec`
1883-
on the underlying file descriptors during construction.
1883+
:class:`Side` will call :func:`set_cloexec` on them.
18841884
18851885
The same file object may be used for both sides. The default
18861886
:meth:`on_disconnect` is handles the possibility that only one
@@ -2155,14 +2155,11 @@ class Side(object):
21552155
:param bool keep_alive:
21562156
If :data:`True`, the continued existence of this side will extend the
21572157
shutdown grace period until it has been unregistered from the broker.
2158-
:param bool blocking:
2159-
If :data:`False`, the descriptor has its :data:`os.O_NONBLOCK` flag
2160-
enabled using :func:`fcntl.fcntl`.
21612158
"""
21622159
_fork_refs = weakref.WeakValueDictionary()
21632160
closed = False
21642161

2165-
def __init__(self, stream, fp, cloexec=True, keep_alive=True, blocking=False):
2162+
def __init__(self, stream, fp, cloexec=True, keep_alive=True):
21662163
#: The :class:`Stream` for which this is a read or write side.
21672164
self.stream = stream
21682165
# File or socket object responsible for the lifetime of its underlying
@@ -2180,8 +2177,6 @@ def __init__(self, stream, fp, cloexec=True, keep_alive=True, blocking=False):
21802177
self._fork_refs[id(self)] = self
21812178
if cloexec:
21822179
set_cloexec(self.fd)
2183-
if not blocking:
2184-
set_nonblock(self.fd)
21852180

21862181
def __repr__(self):
21872182
return '<Side of %s fd %s>' % (
@@ -2785,7 +2780,7 @@ def _get_socketpair(self):
27852780
try:
27862781
return self._cls_idle_socketpairs.pop() # pop() must be atomic
27872782
except IndexError:
2788-
rsock, wsock = socket.socketpair()
2783+
rsock, wsock = socketpair()
27892784
rsock.setblocking(False)
27902785
set_cloexec(rsock.fileno())
27912786
set_cloexec(wsock.fileno())
@@ -2958,7 +2953,8 @@ class Waker(Protocol):
29582953
@classmethod
29592954
def build_stream(cls, broker):
29602955
stream = super(Waker, cls).build_stream(broker)
2961-
stream.accept(*pipe())
2956+
rfp, wfp = pipe(blocking=False)
2957+
stream.accept(rfp, wfp)
29622958
return stream
29632959

29642960
def __init__(self, broker):
@@ -3056,7 +3052,8 @@ def build_stream(cls, name, dest_fd):
30563052
prevent break :meth:`on_shutdown` from calling :meth:`shutdown()
30573053
<socket.socket.shutdown>` on it.
30583054
"""
3059-
rsock, wsock = socket.socketpair()
3055+
# Leave wsock & dest_fd blocking, so the subprocess will have sane stdio
3056+
rsock, wsock = socketpair()
30603057
os.dup2(wsock.fileno(), dest_fd)
30613058
stream = super(IoLoggerProtocol, cls).build_stream(name)
30623059
stream.name = name
@@ -4038,6 +4035,9 @@ def _setup_master(self):
40384035
local_id=self.config['context_id'],
40394036
parent_ids=self.config['parent_ids']
40404037
)
4038+
for f in in_fp, out_fp:
4039+
fd = f.fileno()
4040+
set_blocking(fd, False)
40414041
self.stream.accept(in_fp, out_fp)
40424042
self.stream.name = 'parent'
40434043
self.stream.receive_side.keep_alive = False

0 commit comments

Comments
 (0)