Index: Include/dictobject.h =================================================================== --- Include/dictobject.h (revision 70870) +++ Include/dictobject.h (working copy) @@ -138,6 +138,10 @@ PyAPI_FUNC(int) PyDict_SetItemString(PyObject *dp, const char *key, PyObject *item); PyAPI_FUNC(int) PyDict_DelItemString(PyObject *dp, const char *key); +typedef int (*PyDict_SetItemHook)(PyObject *, PyObject *, PyObject*, PyObject*); +int PyDict_AddSetItemHook(PyDict_SetItemHook hook, PyObject *ctx); +int PyDict_RemoveSetItemHook(PyDict_SetItemHook hook, PyObject *ctx); + #ifdef __cplusplus } #endif Index: setup.py =================================================================== --- setup.py (revision 70870) +++ setup.py (working copy) @@ -452,6 +452,8 @@ # profilers (_lsprof is for cProfile.py) exts.append( Extension('_hotshot', ['_hotshot.c']) ) exts.append( Extension('_lsprof', ['_lsprof.c', 'rotatingtree.c']) ) + + exts.append( Extension('_debuggerhooks', ['_debuggerhooks.c']) ) # static Unicode character database if have_unicode: exts.append( Extension('unicodedata', ['unicodedata.c']) ) Index: Objects/dictobject.c =================================================================== --- Objects/dictobject.c (revision 70870) +++ Objects/dictobject.c (working copy) @@ -9,7 +9,84 @@ #include "Python.h" +typedef struct PyDict_SetItemHookPair { + PyDict_SetItemHook hook; + PyObject *ctx; + struct PyDict_SetItemHookPair *next; +} PyDict_SetItemHookPair; +static PyDict_SetItemHookPair *set_item_hook_pairs = NULL; + +int PyDict_AddSetItemHook(PyDict_SetItemHook hook, PyObject *ctx) +{ + PyDict_SetItemHookPair* pair; + + pair = PyMem_Malloc(sizeof(PyDict_SetItemHookPair)); + if (pair == NULL) { + PyErr_NoMemory(); + return -1; + } + pair->hook = hook; + Py_INCREF(ctx); + pair->ctx = ctx; + pair->next = NULL; + + if (set_item_hook_pairs == NULL) + set_item_hook_pairs = pair; + else { + PyDict_SetItemHookPair* existing = set_item_hook_pairs; + while (existing->next != NULL) + existing = existing->next; + existing->next = pair; + } + + return 0; +} + +int PyDict_RemoveSetItemHook(PyDict_SetItemHook hook, PyObject *ctx) +{ + PyDict_SetItemHookPair* prev = NULL; + PyDict_SetItemHookPair* existing = set_item_hook_pairs; + + while (existing != NULL) { + if (existing->hook == hook && existing->ctx == ctx) { + if (prev == NULL) + set_item_hook_pairs = existing->next; + else + prev->next = existing->next; + Py_CLEAR(existing->ctx); + PyMem_Free(existing); + + if (prev == NULL) + existing = NULL; + else + existing = prev->next; + } + else { + prev = existing; + existing = existing->next; + } + } + + return 0; +} + +int call_set_item_hooks(PyObject *dict, PyObject *key, PyObject *value) +{ + PyDict_SetItemHookPair *pair; + + if (set_item_hook_pairs == NULL) + return 0; + for (pair = set_item_hook_pairs; pair != NULL; pair = pair->next) { + if (pair->hook(pair->ctx, dict, key, value) != 0) + return -1; + } + + return 0; +} + + + /* Set a key error with the specified argument, wrapping it in a * tuple automatically so that tuple keys are not unpacked as the * exception arguments. */ @@ -755,7 +832,14 @@ } assert(key); assert(value); + + mp = (PyDictObject *)op; + if (set_item_hook_pairs != NULL) { + if (call_set_item_hooks(op, key, value) != 0) + return -1; + } + if (PyString_CheckExact(key)) { hash = ((PyStringObject *)key)->ob_shash; if (hash == -1) @@ -818,6 +902,12 @@ set_key_error(key); return -1; } + + if (set_item_hook_pairs != NULL) { + if (call_set_item_hooks(op, key, NULL) != 0) + return -1; + } + old_key = ep->me_key; Py_INCREF(dummy); ep->me_key = dummy; Index: Lib/test/test_multiprocessing.py =================================================================== --- Lib/test/test_multiprocessing.py (revision 70870) +++ Lib/test/test_multiprocessing.py (working copy) @@ -1228,6 +1228,13 @@ ALLOWED_TYPES = ('processes', 'threads') + def _echo_large(self, conn): + recv_list = [] + for msg in iter(conn.recv_bytes, SENTINEL): + recv_list.append(msg) + conn.send_bytes(latin('').join(recv_list)) + conn.close() + def _echo(self, conn): for msg in iter(conn.recv_bytes, SENTINEL): conn.send_bytes(msg) @@ -1291,15 +1298,11 @@ self.assertEqual(poll(TIMEOUT1), True) self.assertTimingAlmostEqual(poll.elapsed, 0) + conn.send_bytes(SENTINEL) # tell child to quit self.assertEqual(conn.recv(), None) - really_big_msg = latin('X') * (1024 * 1024 * 16) # 16Mb - conn.send_bytes(really_big_msg) - self.assertEqual(conn.recv_bytes(), really_big_msg) - - conn.send_bytes(SENTINEL) # tell child to quit child_conn.close() - + if self.TYPE == 'processes': self.assertEqual(conn.readable, True) self.assertEqual(conn.writable, True) @@ -1307,7 +1310,25 @@ self.assertRaises(EOFError, conn.recv_bytes) p.join() + + def test_large_msg(self): + + conn, child_conn = self.Pipe() + p = self.Process(target=self._echo_large, args=(child_conn,)) + p.daemon = True + p.start() + + really_big_msg = latin('X') * (1024 * 1024 * 128) # 128Mb + conn.send_bytes(really_big_msg) + conn.send_bytes(SENTINEL) # tell child to quit + self.assertEqual(conn.recv_bytes(), really_big_msg) + + child_conn.close() + + p.join() + + def test_duplex_false(self): reader, writer = self.Pipe(duplex=False) self.assertEqual(writer.send(1), None) Index: Modules/_debuggerhooks.c =================================================================== --- Modules/_debuggerhooks.c (revision 0) +++ Modules/_debuggerhooks.c (revision 0) @@ -0,0 +1,86 @@ +#include "Python.h" + +static int set_item_hook(PyObject *ctx, PyObject *dict, PyObject *key, PyObject *value) +{ + PyObject *valid; + PyObject *callback; + PyObject *py_id; + PyObject *ret; + int contains; + + py_id = PyLong_FromVoidPtr(dict); + if (py_id == NULL) + return -1; + valid = PyTuple_GetItem(ctx, 0); + callback = PyTuple_GetItem(ctx, 1); + if (valid == NULL || callback == NULL) + return -1; + + contains = PySet_Contains(valid, py_id); + Py_CLEAR(py_id); + if (contains == -1 && PyErr_Occurred()) + return -1; + + if (contains) { + // XXX Do something reasonable on delitem + if (value == NULL) + value = Py_None; + ret = PyObject_CallFunction(callback, "OOO", dict, key, value); + if (ret == NULL) + return -1; + Py_CLEAR(ret); + } + return 0; +} + + +PyDoc_STRVAR(adddicthook_doc, "add dict hook; 1st argument is a set of id(dict) to stop on and the 2nd is the callback"); + +static PyObject * +adddicthook(PyObject *self, PyObject *args) +{ + PyObject *valid; + PyObject *callback; + PyObject *pair = NULL; + + if (!PyArg_ParseTuple(args, "OO:adddicthook", &valid, &callback)) { + return NULL; + } + + pair = PyTuple_New(2); + if (pair == NULL) + return NULL; + if (PyTuple_SetItem(pair, 0, valid) != 0) { + Py_CLEAR(pair); + return NULL; + } + if (PyTuple_SetItem(pair, 1, callback) != 0) { + Py_CLEAR(pair); + return NULL; + } + if (PyDict_AddSetItemHook(set_item_hook, pair) != 0) { + Py_CLEAR(pair); + return NULL; + } + Py_CLEAR(pair); + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyMethodDef +debuggerhooks_functions[] = { + {"adddicthook", adddicthook, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + + +PyMODINIT_FUNC +init_debuggerhooks(void) +{ + PyObject *m; + + m = Py_InitModule3("_debuggerhooks", debuggerhooks_functions, + "Debugger support module."); +}