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
2 changes: 1 addition & 1 deletion Doc/c-api/conversion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ The following functions provide locale-independent string to number conversions.

If ``s`` represents a value that is too large to store in a float
(for example, ``"1e500"`` is such a string on many platforms) then
if ``overflow_exception`` is ``NULL`` return ``Py_INFINITY`` (with
if ``overflow_exception`` is ``NULL`` return :c:macro:`!INFINITY` (with
an appropriate sign) and don't set any exception. Otherwise,
``overflow_exception`` must point to a Python exception object;
raise that exception and return ``-1.0``. In both cases, set
Expand Down
7 changes: 5 additions & 2 deletions Doc/c-api/float.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,11 @@ Floating-Point Objects
This macro expands a to constant expression of type :c:expr:`double`, that
represents the positive infinity.

On most platforms, this is equivalent to the :c:macro:`!INFINITY` macro from
the C11 standard ``<math.h>`` header.
It is equivalent to the :c:macro:`!INFINITY` macro from the C11 standard
``<math.h>`` header.

.. deprecated:: 3.15
The macro is soft deprecated.


.. c:macro:: Py_NAN
Expand Down
4 changes: 4 additions & 0 deletions Doc/c-api/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ complete listing.

Return the absolute value of ``x``.

If the result cannot be represented (for example, if ``x`` has
:c:macro:`!INT_MIN` value for :c:expr:`int` type), the behavior is
undefined.

.. versionadded:: 3.3

.. c:macro:: Py_ALWAYS_INLINE
Expand Down
3 changes: 3 additions & 0 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,9 @@ Retrieving source code
.. versionchanged:: next
Added parameters *inherit_class_doc* and *fallback_to_class_doc*.

Documentation strings on :class:`~functools.cached_property`
objects are now inherited if not overriden.


.. function:: getcomments(object)

Expand Down
2 changes: 1 addition & 1 deletion Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3045,7 +3045,7 @@ Deprecated C APIs
-----------------

* The :c:macro:`!Py_HUGE_VAL` macro is now :term:`soft deprecated`.
Use :c:macro:`!Py_INFINITY` instead.
Use :c:macro:`!INFINITY` instead.
(Contributed by Sergey B Kirpichev in :gh:`120026`.)

* The :c:macro:`!Py_IS_NAN`, :c:macro:`!Py_IS_INFINITY`,
Expand Down
8 changes: 7 additions & 1 deletion Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,9 @@ mimetypes
* Add ``application/node`` MIME type for ``.cjs`` extension. (Contributed by John Franey in :gh:`140937`.)
* Add ``application/toml``. (Contributed by Gil Forcada in :gh:`139959`.)
* Rename ``application/x-texinfo`` to ``application/texinfo``.
(Contributed by Charlie Lin in :gh:`140165`)
(Contributed by Charlie Lin in :gh:`140165`.)
* Changed the MIME type for ``.ai`` files to ``application/pdf``.
(Contributed by Stan Ulbrych in :gh:`141239`.)


mmap
Expand Down Expand Up @@ -1095,6 +1097,10 @@ Deprecated C APIs
since 3.15 and will be removed in 3.17.
(Contributed by Nikita Sobolev in :gh:`136355`.)

* :c:macro:`!Py_INFINITY` macro is :term:`soft deprecated`,
use the C11 standard ``<math.h>`` :c:macro:`!INFINITY` instead.
(Contributed by Sergey B Kirpichev in :gh:`141004`.)

* :c:macro:`!Py_MATH_El` and :c:macro:`!Py_MATH_PIl` are deprecated
since 3.15 and will be removed in 3.20.
(Contributed by Sergey B Kirpichev in :gh:`141004`.)
Expand Down
16 changes: 8 additions & 8 deletions Include/floatobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ PyAPI_DATA(PyTypeObject) PyFloat_Type;

#define Py_RETURN_NAN return PyFloat_FromDouble(Py_NAN)

#define Py_RETURN_INF(sign) \
do { \
if (copysign(1., sign) == 1.) { \
return PyFloat_FromDouble(Py_INFINITY); \
} \
else { \
return PyFloat_FromDouble(-Py_INFINITY); \
} \
#define Py_RETURN_INF(sign) \
do { \
if (copysign(1., sign) == 1.) { \
return PyFloat_FromDouble(INFINITY); \
} \
else { \
return PyFloat_FromDouble(-INFINITY); \
} \
} while(0)

PyAPI_FUNC(double) PyFloat_GetMax(void);
Expand Down
6 changes: 3 additions & 3 deletions Include/internal/pycore_pymath.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extern "C" {
static inline void _Py_ADJUST_ERANGE1(double x)
{
if (errno == 0) {
if (x == Py_INFINITY || x == -Py_INFINITY) {
if (x == INFINITY || x == -INFINITY) {
errno = ERANGE;
}
}
Expand All @@ -44,8 +44,8 @@ static inline void _Py_ADJUST_ERANGE1(double x)

static inline void _Py_ADJUST_ERANGE2(double x, double y)
{
if (x == Py_INFINITY || x == -Py_INFINITY ||
y == Py_INFINITY || y == -Py_INFINITY)
if (x == INFINITY || x == -INFINITY ||
y == INFINITY || y == -INFINITY)
{
if (errno == 0) {
errno = ERANGE;
Expand Down
3 changes: 2 additions & 1 deletion Include/pymath.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@
#define Py_IS_FINITE(X) isfinite(X)

// Py_INFINITY: Value that evaluates to a positive double infinity.
// Soft deprecated since Python 3.15, use INFINITY instead.
#ifndef Py_INFINITY
# define Py_INFINITY ((double)INFINITY)
#endif

/* Py_HUGE_VAL should always be the same as Py_INFINITY. But historically
* this was not reliable and Python did not require IEEE floats and C99
* conformity. The macro was soft deprecated in Python 3.14, use Py_INFINITY instead.
* conformity. The macro was soft deprecated in Python 3.14, use INFINITY instead.
*/
#ifndef Py_HUGE_VAL
# define Py_HUGE_VAL HUGE_VAL
Expand Down
6 changes: 6 additions & 0 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,12 @@ def _finddoc(obj, *, search_in_class=True):
cls = _findclass(obj.fget)
if cls is None or getattr(cls, name) is not obj:
return None
# Should be tested before ismethoddescriptor()
elif isinstance(obj, functools.cached_property):
name = obj.attrname
cls = _findclass(obj.func)
if cls is None or getattr(cls, name) is not obj:
return None
elif ismethoddescriptor(obj) or isdatadescriptor(obj):
name = obj.__name__
cls = obj.__objclass__
Expand Down
2 changes: 1 addition & 1 deletion Lib/mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,9 @@ def _default_mime_types():
'.oda' : 'application/oda',
'.ogx' : 'application/ogg',
'.pdf' : 'application/pdf',
'.ai' : 'application/pdf',
'.p7c' : 'application/pkcs7-mime',
'.ps' : 'application/postscript',
'.ai' : 'application/postscript',
'.eps' : 'application/postscript',
'.texi' : 'application/texinfo',
'.texinfo': 'application/texinfo',
Expand Down
60 changes: 52 additions & 8 deletions Lib/multiprocessing/resource_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
# this resource tracker process, "killall python" would probably leave unlinked
# resources.

import base64
import os
import signal
import sys
import threading
import warnings
from collections import deque

import json

from . import spawn
from . import util

Expand Down Expand Up @@ -196,6 +199,17 @@ def _launch(self):
finally:
os.close(r)

def _make_probe_message(self):
"""Return a JSON-encoded probe message."""
return (
json.dumps(
{"cmd": "PROBE", "rtype": "noop"},
ensure_ascii=True,
separators=(",", ":"),
)
+ "\n"
).encode("ascii")

def _ensure_running_and_write(self, msg=None):
with self._lock:
if self._lock._recursion_count() > 1:
Expand All @@ -207,7 +221,7 @@ def _ensure_running_and_write(self, msg=None):
if self._fd is not None:
# resource tracker was launched before, is it still running?
if msg is None:
to_send = b'PROBE:0:noop\n'
to_send = self._make_probe_message()
else:
to_send = msg
try:
Expand All @@ -234,7 +248,7 @@ def _check_alive(self):
try:
# We cannot use send here as it calls ensure_running, creating
# a cycle.
os.write(self._fd, b'PROBE:0:noop\n')
os.write(self._fd, self._make_probe_message())
except OSError:
return False
else:
Expand All @@ -253,11 +267,25 @@ def _write(self, msg):
assert nbytes == len(msg), f"{nbytes=} != {len(msg)=}"

def _send(self, cmd, name, rtype):
msg = f"{cmd}:{name}:{rtype}\n".encode("ascii")
if len(msg) > 512:
# posix guarantees that writes to a pipe of less than PIPE_BUF
# bytes are atomic, and that PIPE_BUF >= 512
raise ValueError('msg too long')
# POSIX guarantees that writes to a pipe of less than PIPE_BUF (512 on Linux)
# bytes are atomic. Therefore, we want the message to be shorter than 512 bytes.
# POSIX shm_open() and sem_open() require the name, including its leading slash,
# to be at most NAME_MAX bytes (255 on Linux)
# With json.dump(..., ensure_ascii=True) every non-ASCII byte becomes a 6-char
# escape like \uDC80.
# As we want the overall message to be kept atomic and therefore smaller than 512,
# we encode encode the raw name bytes with URL-safe Base64 - so a 255 long name
# will not exceed 340 bytes.
b = name.encode('utf-8', 'surrogateescape')
if len(b) > 255:
raise ValueError('shared memory name too long (max 255 bytes)')
b64 = base64.urlsafe_b64encode(b).decode('ascii')

payload = {"cmd": cmd, "rtype": rtype, "base64_name": b64}
msg = (json.dumps(payload, ensure_ascii=True, separators=(",", ":")) + "\n").encode("ascii")

# The entire JSON message is guaranteed < PIPE_BUF (512 bytes) by construction.
assert len(msg) <= 512, f"internal error: message too long ({len(msg)} bytes)"

self._ensure_running_and_write(msg)

Expand Down Expand Up @@ -290,7 +318,23 @@ def main(fd):
with open(fd, 'rb') as f:
for line in f:
try:
cmd, name, rtype = line.strip().decode('ascii').split(':')
try:
obj = json.loads(line.decode('ascii'))
except Exception as e:
raise ValueError("malformed resource_tracker message: %r" % (line,)) from e

cmd = obj["cmd"]
rtype = obj["rtype"]
b64 = obj.get("base64_name", "")

if not isinstance(cmd, str) or not isinstance(rtype, str) or not isinstance(b64, str):
raise ValueError("malformed resource_tracker fields: %r" % (obj,))

try:
name = base64.urlsafe_b64decode(b64).decode('utf-8', 'surrogateescape')
except ValueError as e:
raise ValueError("malformed resource_tracker base64_name: %r" % (b64,)) from e

cleanup_func = _CLEANUP_FUNCS.get(rtype, None)
if cleanup_func is None:
raise ValueError(
Expand Down
2 changes: 1 addition & 1 deletion Lib/profiling/sampling/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""

LINUX_PERMISSION_ERROR = """
🔒 Tachyon was unable to acess process memory. This could be because tachyon
🔒 Tachyon was unable to access process memory. This could be because tachyon
has insufficient privileges (the required capability is CAP_SYS_PTRACE).
Unprivileged processes cannot trace processes that they cannot send signals
to or those running set-user-ID/set-group-ID programs, for security reasons.
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7364,3 +7364,46 @@ def test_forkpty(self):
res = assert_python_failure("-c", code, PYTHONWARNINGS='error')
self.assertIn(b'DeprecationWarning', res.err)
self.assertIn(b'is multi-threaded, use of forkpty() may lead to deadlocks in the child', res.err)

@unittest.skipUnless(HAS_SHMEM, "requires multiprocessing.shared_memory")
class TestSharedMemoryNames(unittest.TestCase):
def test_that_shared_memory_name_with_colons_has_no_resource_tracker_errors(self):
# Test script that creates and cleans up shared memory with colon in name
test_script = textwrap.dedent("""
import sys
from multiprocessing import shared_memory
import time

# Test various patterns of colons in names
test_names = [
"a:b",
"a:b:c",
"test:name:with:many:colons",
":starts:with:colon",
"ends:with:colon:",
"::double::colons::",
"name\\nwithnewline",
"name-with-trailing-newline\\n",
"\\nname-starts-with-newline",
"colons:and\\nnewlines:mix",
"multi\\nline\\nname",
]

for name in test_names:
try:
shm = shared_memory.SharedMemory(create=True, size=100, name=name)
shm.buf[:5] = b'hello' # Write something to the shared memory
shm.close()
shm.unlink()

except Exception as e:
print(f"Error with name '{name}': {e}", file=sys.stderr)
sys.exit(1)

print("SUCCESS")
""")

rc, out, err = assert_python_ok("-c", test_script)
self.assertIn(b"SUCCESS", out)
self.assertNotIn(b"traceback", err.lower(), err)
self.assertNotIn(b"resource_tracker.py", err, err)
54 changes: 44 additions & 10 deletions Lib/test/test_capi/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@
NAN = float("nan")


def make_nan(size, sign, quiet, payload=None):
if size == 8:
payload_mask = 0x7ffffffffffff
i = (sign << 63) + (0x7ff << 52) + (quiet << 51)
elif size == 4:
payload_mask = 0x3fffff
i = (sign << 31) + (0xff << 23) + (quiet << 22)
elif size == 2:
payload_mask = 0x1ff
i = (sign << 15) + (0x1f << 10) + (quiet << 9)
else:
raise ValueError("size must be either 2, 4, or 8")
if payload is None:
payload = random.randint(not quiet, payload_mask)
return i + payload


class CAPIFloatTest(unittest.TestCase):
def test_check(self):
# Test PyFloat_Check()
Expand Down Expand Up @@ -202,16 +219,7 @@ def test_pack_unpack_roundtrip_for_nans(self):
# HP PA RISC uses 0 for quiet, see:
# https://en.wikipedia.org/wiki/NaN#Encoding
signaling = 1
quiet = int(not signaling)
if size == 8:
payload = random.randint(signaling, 0x7ffffffffffff)
i = (sign << 63) + (0x7ff << 52) + (quiet << 51) + payload
elif size == 4:
payload = random.randint(signaling, 0x3fffff)
i = (sign << 31) + (0xff << 23) + (quiet << 22) + payload
elif size == 2:
payload = random.randint(signaling, 0x1ff)
i = (sign << 15) + (0x1f << 10) + (quiet << 9) + payload
i = make_nan(size, sign, not signaling)
data = bytes.fromhex(f'{i:x}')
for endian in (BIG_ENDIAN, LITTLE_ENDIAN):
with self.subTest(data=data, size=size, endian=endian):
Expand All @@ -221,6 +229,32 @@ def test_pack_unpack_roundtrip_for_nans(self):
self.assertTrue(math.isnan(value))
self.assertEqual(data1, data2)

@unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754")
@unittest.skipUnless(sys.maxsize != 2147483647, "requires 64-bit mode")
def test_pack_unpack_nans_for_different_formats(self):
pack = _testcapi.float_pack
unpack = _testcapi.float_unpack

for endian in (BIG_ENDIAN, LITTLE_ENDIAN):
with self.subTest(endian=endian):
byteorder = "big" if endian == BIG_ENDIAN else "little"

# Convert sNaN to qNaN, if payload got truncated
data = make_nan(8, 0, False, 0x80001).to_bytes(8, byteorder)
snan_low = unpack(data, endian)
qnan4 = make_nan(4, 0, True, 0).to_bytes(4, byteorder)
qnan2 = make_nan(2, 0, True, 0).to_bytes(2, byteorder)
self.assertEqual(pack(4, snan_low, endian), qnan4)
self.assertEqual(pack(2, snan_low, endian), qnan2)

# Preserve NaN type, if payload not truncated
data = make_nan(8, 0, False, 0x80000000001).to_bytes(8, byteorder)
snan_high = unpack(data, endian)
snan4 = make_nan(4, 0, False, 16384).to_bytes(4, byteorder)
snan2 = make_nan(2, 0, False, 2).to_bytes(2, byteorder)
self.assertEqual(pack(4, snan_high, endian), snan4)
self.assertEqual(pack(2, snan_high, endian), snan2)


if __name__ == "__main__":
unittest.main()
Loading
Loading