diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index 07a15d27216e92..8f1ad80fe75dd8 100644 --- a/Doc/library/fcntl.rst +++ b/Doc/library/fcntl.rst @@ -129,7 +129,7 @@ The module defines the following functions: .. audit-event:: fcntl.flock fd,operation fcntl.flock -.. function:: lockf(fd, cmd, len=0, start=0, whence=0) +.. function:: lockf(fd, cmd, len=0, start=0, whence=0, /, open_file_descriptor=False) This is essentially a wrapper around the :func:`~fcntl.fcntl` locking calls. *fd* is the file descriptor (file objects providing a :meth:`~io.IOBase.fileno` @@ -160,9 +160,16 @@ The module defines the following functions: The default for *start* is 0, which means to start at the beginning of the file. The default for *len* is 0 which means to lock to the end of the file. The default for *whence* is also 0. + The default for *open_file_descriptor* is False, if *open_file_descriptor* is + set to True, open file description locks are used. Note that open file description + locks features are supported on Linux(>=3.15) otherwise it will raise :exc:`NotImplementedError` .. audit-event:: fcntl.lockf fd,cmd,len,start,whence fcntl.lockf + .. versionchanged:: 3.9 + The *open_file_descriptor* keyword argument was added. + ref: https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html + Examples (all on a SVR4 compliant system):: import struct, fcntl, os diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index ef499f504682d5..13f0a16b78da62 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -214,6 +214,10 @@ Added constants :data:`~fcntl.F_OFD_GETLK`, :data:`~fcntl.F_OFD_SETLK` and :data:`~fcntl.F_OFD_SETLKW`. (Contributed by Dong-hee Na in :issue:`38602`.) +Added an optional keyword argument *open_file_descriptor* +to :func:`fcntl.lockf` function. +(Contributed by Dong-hee Na in :issue:`22367`.) + ftplib ------- diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 9ab68c67241f46..48895c807ebace 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -4,6 +4,8 @@ import os import struct import sys +import threading +import time import unittest from multiprocessing import Process from test.support import (verbose, TESTFN, unlink, run_unittest, import_module, @@ -164,6 +166,43 @@ def test_lockf_exclusive(self): fcntl.lockf(self.f, fcntl.LOCK_UN) self.assertEqual(p.exitcode, 0) + @unittest.skipUnless(hasattr(fcntl, 'F_OFD_GETLK'), 'requires open file description locks') + def test_lockf_open_file_descriptor(self): + self.f = open(TESTFN, 'wb+') + fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB, open_file_descriptor=True) + fcntl.lockf(self.f, fcntl.LOCK_UN, open_file_descriptor=True) + fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB, open_file_descriptor=True) + fcntl.lockf(self.f, fcntl.LOCK_UN, open_file_descriptor=True) + + @unittest.skipUnless(hasattr(fcntl, 'F_OFD_GETLK'), 'requires open file description locks') + def test_open_file_descriptor_example(self): + threads = [] + num_threads = 3 + num_iterations = 5 + self.f = open(TESTFN, 'w+') + def thread_start(thread_id): + for i in range(num_iterations): + fcntl.lockf(self.f, fcntl.LOCK_EX, open_file_descriptor=True) + self.f.seek(0, os.SEEK_END) + self.f.write(f'{i}: tid={thread_id}, fd={self.f.fileno()}\n') + fcntl.lockf(self.f, fcntl.LOCK_UN, open_file_descriptor=True) + time.sleep(0.05) + + for i in range(num_threads): + t = threading.Thread(target=thread_start, args=(i,)) + threads.append(t) + + for t in threads: + t.start() + + for t in threads: + t.join() + + self.f.close() + self.f = open(TESTFN, 'r') + res = self.f.readlines() + self.assertEqual(len(res), num_threads*num_iterations) + @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") def test_lockf_share(self): self.f = open(TESTFN, 'wb+') diff --git a/Misc/NEWS.d/next/Library/2019-11-09-23-05-07.bpo-22367.Ygahn1.rst b/Misc/NEWS.d/next/Library/2019-11-09-23-05-07.bpo-22367.Ygahn1.rst new file mode 100644 index 00000000000000..8b49b59ec58d7b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-11-09-23-05-07.bpo-22367.Ygahn1.rst @@ -0,0 +1,2 @@ +Add an optional keyword argument *open_file_descriptor* to +:func:`fcntl.lockf` function. Patch by Dong-hee Na. diff --git a/Modules/clinic/fcntlmodule.c.h b/Modules/clinic/fcntlmodule.c.h index 024a44cfbf8bc6..4c923ecc14965c 100644 --- a/Modules/clinic/fcntlmodule.c.h +++ b/Modules/clinic/fcntlmodule.c.h @@ -184,7 +184,8 @@ fcntl_flock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(fcntl_lockf__doc__, -"lockf($module, fd, cmd, len=0, start=0, whence=0, /)\n" +"lockf($module, fd, cmd, len=0, start=0, whence=0, /,\n" +" open_file_descriptor=False)\n" "--\n" "\n" "A wrapper around the fcntl() locking calls.\n" @@ -208,26 +209,34 @@ PyDoc_STRVAR(fcntl_lockf__doc__, "\n" " 0 - relative to the start of the file (SEEK_SET)\n" " 1 - relative to the current buffer position (SEEK_CUR)\n" -" 2 - relative to the end of the file (SEEK_END)"); +" 2 - relative to the end of the file (SEEK_END)\n" +"\n" +"if `open_file_descriptor` is set to True, open file description locks are used."); #define FCNTL_LOCKF_METHODDEF \ - {"lockf", (PyCFunction)(void(*)(void))fcntl_lockf, METH_FASTCALL, fcntl_lockf__doc__}, + {"lockf", (PyCFunction)(void(*)(void))fcntl_lockf, METH_FASTCALL|METH_KEYWORDS, fcntl_lockf__doc__}, static PyObject * fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, - PyObject *startobj, int whence); + PyObject *startobj, int whence, int open_file_descriptor); static PyObject * -fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + static const char * const _keywords[] = {"", "", "", "", "", "open_file_descriptor", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "lockf", 0}; + PyObject *argsbuf[6]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; int fd; int code; PyObject *lenobj = NULL; PyObject *startobj = NULL; int whence = 0; + int open_file_descriptor = 0; - if (!_PyArg_CheckPositional("lockf", nargs, 2, 5)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 6, 0, argsbuf); + if (!args) { goto exit; } if (!conv_descriptor(args[0], &fd)) { @@ -243,16 +252,19 @@ fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) goto exit; } if (nargs < 3) { - goto skip_optional; + goto skip_optional_posonly; } + noptargs--; lenobj = args[2]; if (nargs < 4) { - goto skip_optional; + goto skip_optional_posonly; } + noptargs--; startobj = args[3]; if (nargs < 5) { - goto skip_optional; + goto skip_optional_posonly; } + noptargs--; if (PyFloat_Check(args[4])) { PyErr_SetString(PyExc_TypeError, "integer argument expected, got float" ); @@ -262,10 +274,18 @@ fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) if (whence == -1 && PyErr_Occurred()) { goto exit; } -skip_optional: - return_value = fcntl_lockf_impl(module, fd, code, lenobj, startobj, whence); +skip_optional_posonly: + if (!noptargs) { + goto skip_optional_pos; + } + open_file_descriptor = PyObject_IsTrue(args[5]); + if (open_file_descriptor < 0) { + goto exit; + } +skip_optional_pos: + return_value = fcntl_lockf_impl(module, fd, code, lenobj, startobj, whence, open_file_descriptor); exit: return return_value; } -/*[clinic end generated code: output=e912d25e28362c52 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=80a3bf605624c78f input=a9049054013a1b77]*/ diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index 43f9b22f672070..d0bf1d3b863870 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -352,6 +352,7 @@ fcntl.lockf start as startobj: object(c_default='NULL') = 0 whence: int = 0 / + open_file_descriptor: bool = False A wrapper around the fcntl() locking calls. @@ -375,12 +376,14 @@ starts. `whence` is as with fileobj.seek(), specifically: 0 - relative to the start of the file (SEEK_SET) 1 - relative to the current buffer position (SEEK_CUR) 2 - relative to the end of the file (SEEK_END) + +if `open_file_descriptor` is set to True, open file description locks are used. [clinic start generated code]*/ static PyObject * fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, - PyObject *startobj, int whence) -/*[clinic end generated code: output=4985e7a172e7461a input=3a5dc01b04371f1a]*/ + PyObject *startobj, int whence, int open_file_descriptor) +/*[clinic end generated code: output=eba798e67a9aee86 input=83190651f560f35e]*/ { int ret; int async_err = 0; @@ -435,7 +438,18 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, l.l_whence = whence; do { Py_BEGIN_ALLOW_THREADS - ret = fcntl(fd, (code & LOCK_NB) ? F_SETLK : F_SETLKW, &l); + if (open_file_descriptor) { +#ifdef F_OFD_SETLK + l.l_pid = 0; + ret = fcntl(fd, (code & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &l); +#else + PyErr_SetString(PyExc_NotImplementedError, + "lockf: open_file_descriptor is not supported on this platform"); + return NULL; +#endif + } else { + ret = fcntl(fd, (code & LOCK_NB) ? F_SETLK : F_SETLKW, &l); + } Py_END_ALLOW_THREADS } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals())); }