summaryrefslogtreecommitdiffstats
path: root/Include
diff options
context:
space:
mode:
authorCarl Meyer <carl@oddbird.net>2022-10-07 00:08:00 (GMT)
committerGitHub <noreply@github.com>2022-10-07 00:08:00 (GMT)
commita4b7794887929f82c532fcd055326954ff1197ce (patch)
tree257e2dc783858251f893d75c17663913b05a0fad /Include
parent683ab859554c34831fcecc854de35745d7fd603c (diff)
downloadcpython-a4b7794887929f82c532fcd055326954ff1197ce.zip
cpython-a4b7794887929f82c532fcd055326954ff1197ce.tar.gz
cpython-a4b7794887929f82c532fcd055326954ff1197ce.tar.bz2
GH-91052: Add C API for watching dictionaries (GH-31787)
Diffstat (limited to 'Include')
-rw-r--r--Include/cpython/dictobject.h23
-rw-r--r--Include/internal/pycore_dict.h27
-rw-r--r--Include/internal/pycore_interp.h2
3 files changed, 51 insertions, 1 deletions
diff --git a/Include/cpython/dictobject.h b/Include/cpython/dictobject.h
index 565ad791..f8a74a5 100644
--- a/Include/cpython/dictobject.h
+++ b/Include/cpython/dictobject.h
@@ -83,3 +83,26 @@ typedef struct {
PyAPI_FUNC(PyObject *) _PyDictView_New(PyObject *, PyTypeObject *);
PyAPI_FUNC(PyObject *) _PyDictView_Intersect(PyObject* self, PyObject *other);
+
+/* Dictionary watchers */
+
+typedef enum {
+ PyDict_EVENT_ADDED,
+ PyDict_EVENT_MODIFIED,
+ PyDict_EVENT_DELETED,
+ PyDict_EVENT_CLONED,
+ PyDict_EVENT_CLEARED,
+ PyDict_EVENT_DEALLOCATED,
+} PyDict_WatchEvent;
+
+// Callback to be invoked when a watched dict is cleared, dealloced, or modified.
+// In clear/dealloc case, key and new_value will be NULL. Otherwise, new_value will be the
+// new value for key, NULL if key is being deleted.
+typedef int(*PyDict_WatchCallback)(PyDict_WatchEvent event, PyObject* dict, PyObject* key, PyObject* new_value);
+
+// Register/unregister a dict-watcher callback
+PyAPI_FUNC(int) PyDict_AddWatcher(PyDict_WatchCallback callback);
+PyAPI_FUNC(int) PyDict_ClearWatcher(int watcher_id);
+
+// Mark given dictionary as "watched" (callback will be called if it is modified)
+PyAPI_FUNC(int) PyDict_Watch(int watcher_id, PyObject* dict);
diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h
index 4640929..ae4094a 100644
--- a/Include/internal/pycore_dict.h
+++ b/Include/internal/pycore_dict.h
@@ -154,7 +154,32 @@ struct _dictvalues {
extern uint64_t _pydict_global_version;
-#define DICT_NEXT_VERSION() (++_pydict_global_version)
+#define DICT_MAX_WATCHERS 8
+#define DICT_VERSION_INCREMENT (1 << DICT_MAX_WATCHERS)
+#define DICT_VERSION_MASK (DICT_VERSION_INCREMENT - 1)
+
+#define DICT_NEXT_VERSION() (_pydict_global_version += DICT_VERSION_INCREMENT)
+
+void
+_PyDict_SendEvent(int watcher_bits,
+ PyDict_WatchEvent event,
+ PyDictObject *mp,
+ PyObject *key,
+ PyObject *value);
+
+static inline uint64_t
+_PyDict_NotifyEvent(PyDict_WatchEvent event,
+ PyDictObject *mp,
+ PyObject *key,
+ PyObject *value)
+{
+ int watcher_bits = mp->ma_version_tag & DICT_VERSION_MASK;
+ if (watcher_bits) {
+ _PyDict_SendEvent(watcher_bits, event, mp, key, value);
+ return DICT_NEXT_VERSION() | watcher_bits;
+ }
+ return DICT_NEXT_VERSION();
+}
extern PyObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values);
extern PyObject *_PyDict_FromItems(
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index b21708a..8cecd5a 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -144,6 +144,8 @@ struct _is {
// Initialized to _PyEval_EvalFrameDefault().
_PyFrameEvalFunction eval_frame;
+ PyDict_WatchCallback dict_watchers[DICT_MAX_WATCHERS];
+
Py_ssize_t co_extra_user_count;
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];