Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,9 @@ how the command-line arguments should be handled. The supplied actions are:
>>> parser.parse_args(['-vvv'])
Namespace(verbose=3)

Note, the *default* will be ``None`` unless explicitly set to *0*.
Unless explicitly set, the *default* will be ``None``. If the default
value is a non-zero number, the count starts from that number rather
than from zero.

* ``'help'`` - This prints a complete help message for all the options in the
current parser and then exits. By default a help action is automatically
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ enum _PyCompile_FBlockType {
COMPILE_FBLOCK_EXCEPTION_HANDLER,
COMPILE_FBLOCK_EXCEPTION_GROUP_HANDLER,
COMPILE_FBLOCK_ASYNC_COMPREHENSION_GENERATOR,
COMPILE_FBLOCK_INLINED_COMPREHENSION,
COMPILE_FBLOCK_STOP_ITERATION,
};

Expand Down
21 changes: 18 additions & 3 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1497,7 +1497,12 @@ async def create_datagram_endpoint(self, protocol_factory,
else:
raise exceptions[0]

protocol = protocol_factory()
try:
protocol = protocol_factory()
except:
# gh-156400: no transport owns the socket yet, so close it.
sock.close()
raise
waiter = self.create_future()
transport = self._make_datagram_transport(
sock, protocol, r_addr, waiter)
Expand Down Expand Up @@ -1714,7 +1719,12 @@ async def connect_accepted_socket(
return transport, protocol

async def connect_read_pipe(self, protocol_factory, pipe):
protocol = protocol_factory()
try:
protocol = protocol_factory()
except:
# gh-156400: no transport owns the pipe yet, so close it.
pipe.close()
raise
waiter = self.create_future()
transport = self._make_read_pipe_transport(pipe, protocol, waiter)

Expand All @@ -1730,7 +1740,12 @@ async def connect_read_pipe(self, protocol_factory, pipe):
return transport, protocol

async def connect_write_pipe(self, protocol_factory, pipe):
protocol = protocol_factory()
try:
protocol = protocol_factory()
except:
# gh-156400: no transport owns the pipe yet, so close it.
pipe.close()
raise
waiter = self.create_future()
transport = self._make_write_pipe_transport(pipe, protocol, waiter)

Expand Down
4 changes: 3 additions & 1 deletion Lib/asyncio/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ def capture_call_graph(
f = sys._getframe(depth) if limit != 0 else None
try:
while f is not None:
is_async = f.f_generator is not None
# gh-156988: sync gen should not clear the call chain
is_async = isinstance(
f.f_generator, (types.CoroutineType, types.AsyncGeneratorType))
call_stack.append(FrameCallGraphEntry(f))

if is_async:
Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,43 @@ def test_create_datagram_endpoint_sock(self):
self.loop.run_until_complete(protocol.done)
self.assertEqual('CLOSED', protocol.state)

def test_create_datagram_endpoint_transport_error_closes_sock(self):
# gh-156400: the socket is closed if the transport is never created.
sock = mock.Mock()
sock.type = socket.SOCK_DGRAM

def factory():
raise ZeroDivisionError

coro = self.loop.create_datagram_endpoint(factory, sock=sock)
with self.assertRaises(ZeroDivisionError):
self.loop.run_until_complete(coro)
self.assertTrue(sock.close.called)

def test_connect_read_pipe_transport_error_closes_pipe(self):
# gh-156400: the pipe is closed if the transport is never created.
pipe = mock.Mock()

def factory():
raise ZeroDivisionError

coro = self.loop.connect_read_pipe(factory, pipe)
with self.assertRaises(ZeroDivisionError):
self.loop.run_until_complete(coro)
self.assertTrue(pipe.close.called)

def test_connect_write_pipe_transport_error_closes_pipe(self):
# gh-156400: the pipe is closed if the transport is never created.
pipe = mock.Mock()

def factory():
raise ZeroDivisionError

coro = self.loop.connect_write_pipe(factory, pipe)
with self.assertRaises(ZeroDivisionError):
self.loop.run_until_complete(coro)
self.assertTrue(pipe.close.called)

@unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'No UNIX Sockets')
def test_create_datagram_endpoint_sock_unix(self):
fut = self.loop.create_datagram_endpoint(
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_asyncio/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,26 @@ async def main():
await main()
self.assertRegex(output[0], r'in generator [\w.<>]+\.gen\(\)')

async def test_capture_call_graph_generator_keeps_caller_frames(self):
# gh-156988: sync gen should not clear the call chain
stack = None

def gen():
nonlocal stack
graph = asyncio.capture_call_graph()
stack = [entry.frame.f_code.co_name for entry in graph.call_stack]
yield

def middle():
for _ in gen():
pass

async def main():
middle()

await main()
self.assertEqual(stack[:3], ['gen', 'middle', 'main'])


@unittest.skipIf(
not hasattr(asyncio.futures, "_c_future_add_to_awaited_by"),
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -3519,6 +3519,21 @@ def test_syntax_error_on_deeply_nested_blocks(self):
"""
self._check_error(source, "too many statically nested blocks")

@support.cpython_only
def test_nested_inlined_comprehensions_block_limit(self):
# Each inlined comprehension with locals emits SETUP_FINALLY, which
# must count toward CO_MAXBLOCKS (gh-156091).
def src(depth):
e = "i for i in r"
for _ in range(depth - 1):
e = "[" + e + "] for i in r"
return "x = [" + e + "]"

CO_MAXBLOCKS = 21
compile(src(CO_MAXBLOCKS), "<testcase>", "exec")
self._check_error(src(CO_MAXBLOCKS + 1),
"too many statically nested blocks")

@support.cpython_only
def test_error_on_parser_stack_overflow(self):
source = "-" * 100000 + "4"
Expand Down
2 changes: 1 addition & 1 deletion Lib/unittest/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ def __set_return_value(self, value):

__return_value_doc = "The value to be returned when the mock is called."
return_value = property(__get_return_value, __set_return_value,
__return_value_doc)
doc=__return_value_doc)


@property
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a crash when compiling deeply nested inlined list, set, or dict
comprehensions. A :exc:`SyntaxError` is now raised when the nesting exceeds
the compiler's static block limit.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fix socket and pipe leaks in :mod:`asyncio` when ``protocol_factory()`` raises
in :meth:`loop.create_datagram_endpoint
<asyncio.loop.create_datagram_endpoint>`, :meth:`loop.connect_read_pipe
<asyncio.loop.connect_read_pipe>`, and :meth:`loop.connect_write_pipe
<asyncio.loop.connect_write_pipe>`. The socket or pipe is now closed instead
of leaking until garbage collection.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Set the docstring of :attr:`unittest.mock.Mock.return_value`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`asyncio.print_call_graph` no longer truncates the call stack at a
synchronous generator.
10 changes: 8 additions & 2 deletions Python/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ codegen_unwind_fblock(compiler *c, location *ploc,
case COMPILE_FBLOCK_EXCEPTION_HANDLER:
case COMPILE_FBLOCK_EXCEPTION_GROUP_HANDLER:
case COMPILE_FBLOCK_ASYNC_COMPREHENSION_GENERATOR:
case COMPILE_FBLOCK_INLINED_COMPREHENSION:
case COMPILE_FBLOCK_STOP_ITERATION:
return SUCCESS;

Expand Down Expand Up @@ -4976,8 +4977,11 @@ codegen_push_inlined_comprehension_locals(compiler *c, location loc,
NEW_JUMP_TARGET_LABEL(c, cleanup);
state->cleanup = cleanup;

// no need to push an fblock for this "virtual" try/finally; there can't
// be return/continue/break inside a comprehension
// Count against CO_MAXBLOCKS: SETUP_FINALLY consumes an except-stack
// slot even though return/continue/break cannot appear here.
RETURN_IF_ERROR(_PyCompile_PushFBlock(
c, loc, COMPILE_FBLOCK_INLINED_COMPREHENSION,
cleanup, NO_LABEL, NULL));
ADDOP_JUMP(c, loc, SETUP_FINALLY, cleanup);
}
return SUCCESS;
Expand Down Expand Up @@ -5023,6 +5027,8 @@ codegen_pop_inlined_comprehension_locals(compiler *c, location loc,
{
if (state->pushed_locals) {
ADDOP(c, NO_LOCATION, POP_BLOCK);
_PyCompile_PopFBlock(c, COMPILE_FBLOCK_INLINED_COMPREHENSION,
state->cleanup);

NEW_JUMP_TARGET_LABEL(c, end);
ADDOP_JUMP(c, NO_LOCATION, JUMP_NO_INTERRUPT, end);
Expand Down
Loading