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
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
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