Skip to content

bpo-22367: Add open_file_descriptor parameter to fcntl.lockf() #17099

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
9 changes: 8 additions & 1 deletion Doc/library/fcntl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------

Expand Down
39 changes: 39 additions & 0 deletions Lib/test/test_fcntl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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+')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add an optional keyword argument *open_file_descriptor* to
:func:`fcntl.lockf` function. Patch by Dong-hee Na.
44 changes: 32 additions & 12 deletions Modules/clinic/fcntlmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 17 additions & 3 deletions Modules/fcntlmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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;
Expand Down Expand Up @@ -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()));
}
Expand Down