diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index b2a299ed172967..83def9c31799af 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1136,6 +1136,18 @@ def test_access_violations(self): self.assertEqual(stdout.strip(), b'') self.assertEqual(stderr.strip(), b'') + def test_flush_parameters(self): + with open(TESTFN, 'wb+') as f: + f.write(b'x' * PAGESIZE * 3) + f.flush() + + m = mmap.mmap(f.fileno(), PAGESIZE * 3) + self.addCleanup(m.close) + + m.flush() + m.flush(PAGESIZE) + m.flush(PAGESIZE, PAGESIZE) + class LargeMmapTests(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2025-08-24-02-04-32.gh-issue-138092.V4-wTO.rst b/Misc/NEWS.d/next/Library/2025-08-24-02-04-32.gh-issue-138092.V4-wTO.rst new file mode 100644 index 00000000000000..21c28c49628cba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-24-02-04-32.gh-issue-138092.V4-wTO.rst @@ -0,0 +1,2 @@ +Fixed a bug in :meth:`mmap.mmap.flush` where calling with only an offset +parameter would fail. diff --git a/Modules/clinic/mmapmodule.c.h b/Modules/clinic/mmapmodule.c.h new file mode 100644 index 00000000000000..ec4282e8082ab7 --- /dev/null +++ b/Modules/clinic/mmapmodule.c.h @@ -0,0 +1,70 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_abstract.h" // _PyNumber_Index() +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +PyDoc_STRVAR(mmap_mmap_flush__doc__, +"flush($self, offset=0, size=None, /)\n" +"--\n" +"\n" +"Flushes changes made to the in-memory copy of a file back to disk.\n" +"\n" +"If offset and size are specified, only the specified range will\n" +"be flushed. If not specified, the entire mapped region will be\n" +"flushed."); + +#define MMAP_MMAP_FLUSH_METHODDEF \ + {"flush", _PyCFunction_CAST(mmap_mmap_flush), METH_FASTCALL, mmap_mmap_flush__doc__}, + +static PyObject * +mmap_mmap_flush_impl(mmap_object *self, Py_ssize_t offset, Py_ssize_t size); + +static PyObject * +mmap_mmap_flush(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_ssize_t offset = 0; + Py_ssize_t size = -1; + + if (!_PyArg_CheckPositional("flush", nargs, 0, 2)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[0]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + offset = ival; + } + if (nargs < 2) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[1]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + size = ival; + } +skip_optional: + return_value = mmap_mmap_flush_impl((mmap_object *)self, offset, size); + +exit: + return return_value; +} +/*[clinic end generated code: output=c95c7ffe05f26c3a input=a9049054013a1b77]*/ diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index 0cb4b62d734550..f4d739320952f1 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -28,6 +28,12 @@ #include "pycore_fileutils.h" // _Py_stat_struct #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() +/*[clinic input] +module mmap +class mmap.mmap "mmap_object *" "mmap_object_type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=4ebde54549b9daa7]*/ + #include // offsetof() #ifndef MS_WINDOWS # include // close() @@ -128,6 +134,8 @@ typedef struct { #define mmap_object_CAST(op) ((mmap_object *)(op)) +#include "clinic/mmapmodule.c.h" + static int mmap_object_traverse(PyObject *op, visitproc visit, void *arg) { @@ -924,15 +932,31 @@ mmap_tell_method(PyObject *op, PyObject *Py_UNUSED(ignored)) return PyLong_FromSize_t(self->pos); } +/*[clinic input] +mmap.mmap.flush + + offset: Py_ssize_t = 0 + size: Py_ssize_t(c_default="-1") = None + / + +Flushes changes made to the in-memory copy of a file back to disk. + +If offset and size are specified, only the specified range will +be flushed. If not specified, the entire mapped region will be +flushed. +[clinic start generated code]*/ + static PyObject * -mmap_flush_method(PyObject *op, PyObject *args) +mmap_mmap_flush_impl(mmap_object *self, Py_ssize_t offset, Py_ssize_t size) +/*[clinic end generated code: output=956ced67466149cf input=07c2c6d4e69263a4]*/ { - Py_ssize_t offset = 0; - mmap_object *self = mmap_object_CAST(op); - Py_ssize_t size = self->size; CHECK_VALID(NULL); - if (!PyArg_ParseTuple(args, "|nn:flush", &offset, &size)) - return NULL; + + // If size is -1 (default), calculate size from offset to end. + if (size == -1) { + size = self->size - offset; + } + if (size < 0 || offset < 0 || self->size - offset < size) { PyErr_SetString(PyExc_ValueError, "flush values out of range"); return NULL; @@ -1194,7 +1218,7 @@ static struct PyMethodDef mmap_object_methods[] = { {"close", mmap_close_method, METH_NOARGS}, {"find", mmap_find_method, METH_VARARGS}, {"rfind", mmap_rfind_method, METH_VARARGS}, - {"flush", mmap_flush_method, METH_VARARGS}, + MMAP_MMAP_FLUSH_METHODDEF #ifdef HAVE_MADVISE {"madvise", mmap_madvise_method, METH_VARARGS}, #endif