Skip to content
8 changes: 4 additions & 4 deletions Doc/library/multiprocessing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ To show the individual process IDs involved, here is an expanded example::
For an explanation of why the ``if __name__ == '__main__'`` part is
necessary, see :ref:`multiprocessing-programming`.

The arguments to :class:`Process` usually need to be unpickleable from within
the child process. If you tried typing the above example directly into a REPL it
could lead to an :exc:`AttributeError` in the child process trying to locate the
*f* function in the ``__main__`` module.
The arguments to :class:`Process` usually need to be picklable so they can be
passed to the child process. If you tried typing the above example directly
into a REPL it could lead to an :exc:`AttributeError` in the child process
trying to locate the *f* function in the ``__main__`` module.


.. _multiprocessing-start-methods:
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_cmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,10 @@ def test_complex_near_zero(self):
self.assertIsNotClose(0.001-0.001j, 0.001+0.001j, abs_tol=1e-03)

def test_complex_special(self):
self.assertIsClose(complex(INF, INF), complex(INF, INF))
self.assertIsClose(complex(-INF, -INF), complex(-INF, -INF))
self.assertIsNotClose(complex(NAN, NAN), complex(NAN, NAN))
self.assertIsNotClose(complex(INF, INF), complex(-INF, -INF))
self.assertIsNotClose(INF, INF*1j)
self.assertIsNotClose(INF*1j, INF)
self.assertIsNotClose(INF, -INF)
Expand Down
11 changes: 2 additions & 9 deletions Lib/test/test_email/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -4995,15 +4995,8 @@ def test_body_encode(self):
# Try the convert argument, where input codec != output codec
c = Charset('euc-jp')
# With apologies to Tokio Kikuchi ;)
# XXX FIXME
## try:
## eq('\x1b$B5FCO;~IW\x1b(B',
## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
## eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
## except LookupError:
## # We probably don't have the Japanese codecs installed
## pass
eq('\x1b$B5FCO;~IW\x1b(B',
c.body_encode('\u83ca\u5730\u6642\u592b'))
# Testing SF bug #625509, which we have to fake, since there are no
# built-in encodings where the header encoding is QP but the body
# encoding is not.
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_free_threading/test_pickle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import pickle
import threading
import unittest

from test.support import threading_helper


@threading_helper.requires_working_threading()
class TestPickleFreeThreading(unittest.TestCase):

def test_pickle_dumps_with_concurrent_dict_mutation(self):
# gh-146452: Pickling a dict while another thread mutates it
# used to segfault. batch_dict_exact() iterated dict items via
# PyDict_Next() which returns borrowed references, and a
# concurrent pop/replace could free the value before Py_INCREF
# got to it.
shared = {str(i): list(range(20)) for i in range(50)}

def dumper():
for _ in range(1000):
try:
pickle.dumps(shared)
except RuntimeError:
# "dictionary changed size during iteration" is expected
pass

def mutator():
for j in range(1000):
key = str(j % 50)
shared[key] = list(range(j % 20))
if j % 10 == 0:
shared.pop(key, None)
shared[key] = [j]

threads = []
for _ in range(10):
threads.append(threading.Thread(target=dumper))
threads.append(threading.Thread(target=mutator))

with threading_helper.start_threads(threads):
pass

if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix segfault in :mod:`pickle` when pickling a dictionary concurrently
mutated by another thread in the free-threaded build.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix reference leaks in error paths of the :mod:`!_interpchannels` and
:mod:`!_interpqueues` extension modules.
4 changes: 2 additions & 2 deletions Modules/_interpchannelsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2586,6 +2586,7 @@ static PyObject *
_channelid_from_xid(_PyXIData_t *data)
{
struct _channelid_xid *xid = (struct _channelid_xid *)_PyXIData_DATA(data);
PyObject *cidobj = NULL;

// It might not be imported yet, so we can't use _get_current_module().
PyObject *mod = PyImport_ImportModule(MODULE_NAME_STR);
Expand All @@ -2595,11 +2596,10 @@ _channelid_from_xid(_PyXIData_t *data)
assert(mod != Py_None);
module_state *state = get_module_state(mod);
if (state == NULL) {
return NULL;
goto done;
}

// Note that we do not preserve the "resolve" flag.
PyObject *cidobj = NULL;
int err = newchannelid(state->ChannelIDType, xid->cid, xid->end,
_global_channels(), 0, 0,
(channelid **)&cidobj);
Expand Down
1 change: 1 addition & 0 deletions Modules/_interpqueuesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,7 @@ _queueobj_from_xid(_PyXIData_t *data)
if (mod == NULL) {
mod = PyImport_ImportModule(MODULE_NAME_STR);
if (mod == NULL) {
Py_DECREF(qidobj);
return NULL;
}
}
Expand Down
14 changes: 13 additions & 1 deletion Modules/_pickle.c
Original file line number Diff line number Diff line change
Expand Up @@ -3452,7 +3452,7 @@ batch_dict(PickleState *state, PicklerObject *self, PyObject *iter, PyObject *or
* Note that this currently doesn't work for protocol 0.
*/
static int
batch_dict_exact(PickleState *state, PicklerObject *self, PyObject *obj)
batch_dict_exact_impl(PickleState *state, PicklerObject *self, PyObject *obj)
{
PyObject *key = NULL, *value = NULL;
int i;
Expand Down Expand Up @@ -3525,6 +3525,18 @@ batch_dict_exact(PickleState *state, PicklerObject *self, PyObject *obj)
return -1;
}

/* gh-146452: Wrap the dict iteration in a critical section to prevent
concurrent mutation from invalidating PyDict_Next() iteration state. */
static int
batch_dict_exact(PickleState *state, PicklerObject *self, PyObject *obj)
{
int ret;
Py_BEGIN_CRITICAL_SECTION(obj);
ret = batch_dict_exact_impl(state, self, obj);
Py_END_CRITICAL_SECTION();
return ret;
}

static int
save_dict(PickleState *state, PicklerObject *self, PyObject *obj)
{
Expand Down
Loading