[英]linux fcntl file lock with timeout
the standard linux fcntl
call doesn't provide a timeout option.标准的 linux
fcntl
调用不提供超时选项。 I'm considering implement a timeout lock with signal.我正在考虑用信号实现超时锁定。
Here is the description of blocking lock:下面是阻塞锁的描述:
F_SETLKW F_SETLKW
This command shall be equivalent to F_SETLK except that if a shared or exclusive lock is blocked by other locks, the thread shall wait until the request can be satisfied.此命令等同于 F_SETLK,除了如果共享锁或排他锁被其他锁阻塞,线程将等待直到请求得到满足。 If a signal that is to be caught is received while fcntl() is waiting for a region, fcntl() shall be interrupted.
如果在 fcntl() 等待区域时接收到要捕获的信号,则 fcntl() 将被中断。 Upon return from the signal handler, fcntl() shall return -1 with errno set to [EINTR], and the lock operation shall not be done.
从信号处理程序返回时,fcntl() 应返回 -1,errno 设置为 [EINTR],并且不应执行锁定操作。
So what kind of signal I need to use to indicate the lock to be interrupted?那么我需要用什么样的信号来表示锁被中断呢? And since there're multiple threads running in my process, I only want to interrupt this IO thread who is blokcing for the file lock, other threads should not be affected, but signal is process-level, I'm not sure how to handle this situation.
而且由于我的进程中有多个线程在运行,我只想中断正在阻塞文件锁的这个 IO 线程,其他线程不应该受到影响,但是信号是进程级的,我不确定如何处理这个情况。
I've written a simple imple.netation using signal.我已经使用信号编写了一个简单的 imple.netation。
int main(int argc, char **argv) {
std::string lock_path = "a.lck";
int fd = open(lock_path.c_str(), O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
if (argc > 1) {
signal(SIGALRM, [](int sig) {});
std::thread([](pthread_t tid, unsigned int seconds) {
sleep(seconds);
pthread_kill(tid, SIGALRM);
}, pthread_self(), 3).detach();
int ret = file_rwlock(fd, F_SETLKW, F_WRLCK);
if (ret == -1) std::cout << "FAIL to acquire lock after waiting 3s!" << std::endl;
} else {
file_rwlock(fd, F_SETLKW, F_WRLCK);
while (1);
}
return 0;
}
by running ./main
followed by ./main a
, I expect the first process holding the lock forever, and second process try to get the lock and interrupted after 3s, but the second process just terminated.通过运行
./main
后跟./main a
,我希望第一个进程永远持有锁,第二个进程尝试获取锁并在 3 秒后中断,但第二个进程刚刚终止。
Could anyone tell me what's wrong with my code?谁能告诉我我的代码有什么问题?
So what kind of signal I need to use to indicate the lock to be interrupted?
那么我需要使用哪种信号来表示要中断的锁?
The most obvious choice of signal would be SIGUSR1
or SIGUSR2
. 信号的最明显选择是
SIGUSR1
或SIGUSR2
。 These are provided to serve user-defined purposes. 提供这些是为了满足用户定义的目的。
There is also SIGALRM
, which would be natural if you're using a timer that produces such a signal to do your timekeeping, and which makes some sense even to generate programmatically, as long as you are not using it for other purposes. 还有
SIGALRM
,如果您使用的计时器会产生这样的信号来进行计时,那将是很自然的,只要您不将其用于其他目的,即使通过编程方式生成,这也很有意义。
And since there're multiple threads running in my process, I only want to interrupt this IO thread who is blokcing for the file lock, other threads should not be affected, but signal is process-level, I'm not sure how to handle this situation.
而且,由于我的进程中正在运行多个线程,所以我只想中断占用大量文件锁的IO线程,其他线程不应受到影响,但是信号是进程级的,我不确定如何处理这个情况。
You can deliver a signal to a chosen thread in a multithreaded process via the pthread_kill()
function. 您可以通过
pthread_kill()
函数将信号传递给多线程进程中的选定线程。 This also stands up well to the case where more than one thread is waiting on a lock at the same time. 这也适用于多个线程同时等待锁的情况。
With regular kill()
, you also have the alternative of making all threads block the chosen signal ( sigprocmask()
), and then having the thread making the lock attempt unblock it immediately prior. 使用常规的
kill()
,您还可以选择使所有线程都阻塞所选信号( sigprocmask()
),然后让进行锁定的线程立即尝试对其进行解锁。 When the chosen signal is delivered to the process, a thread that is not presently blocking it will receive it, if any such thread is available. 当选定的信号传递到进程时,当前没有阻塞的线程将接收该信号(如果有这样的线程可用)。
This supposes that a signal handler has already been set up to handle the chosen signal (it doesn't need to do anything), and that the signal number to use is available via the symbol LOCK_TIMER_SIGNAL
. 假设已经设置了一个信号处理程序来处理选定的信号(它不需要执行任何操作),并且可以通过符号
LOCK_TIMER_SIGNAL
获得要使用的信号编号。 It provides the wanted timeout behavior as a wrapper function around fcntl()
, with command F_SETLKW
as described in the question. 它提供了所需的超时行为,作为
fcntl()
的包装函数,使用问题中所述的命令F_SETLKW
。
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/syscall.h>
// glibc does not provide a wrapper function for this syscall:
static pid_t gettid(void) {
return syscall(SYS_gettid);
}
/**
* Attempt to acquire an fcntl() lock, with timeout
*
* fd: an open file descriptor identifying the file to lock
* lock_info: a pointer to a struct flock describing the wanted lock operation
* to_secs: a time_t representing the amount of time to wait before timing out
*/
int try_lock(int fd, struct flock *lock_info, time_t to_secs) {
int result;
timer_t timer;
result = timer_create(CLOCK_MONOTONIC,
& (struct sigevent) {
.sigev_notify = SIGEV_THREAD_ID,
._sigev_un = { ._tid = gettid() },
// note: gettid() conceivably can fail
.sigev_signo = LOCK_TIMER_SIGNAL },
&timer);
// detect and handle errors ...
result = timer_settime(timer, 0,
& (struct itimerspec) { .it_value = { .tv_sec = to_secs } },
NULL);
result = fcntl(fd, F_SETLKW, lock_info);
// detect and handle errors (other than EINTR) ...
// on EINTR, may want to check that the timer in fact expired
result = timer_delete(timer);
// detect and handle errors ...
return result;
}
That works as expected for me. 符合我的预期。
try_lock
function itself to modify the disposition of its chosen signal. try_lock
函数本身修改其选择信号的配置没有用(可能很危险)。 timer_*
interfaces provide POSIX interval timers, but the provision for designating a specific thread to receive signals from such a timer is Linux-specific. timer_*
接口提供POSIX间隔计时器,但是指定特定线程以从此类计时器接收信号的规定是Linux特定的。 -lrt
for the timer_*
functions. -lrt
链接以使用timer_*
函数。 struct sigevent
does not conform to its own docs (at least in relatively old version 2.17). struct sigevent
不符合其自己的文档(至少在相对较旧的版本2.17中)。 The docs claim that struct sigevent
has a member sigev_notify_thread_id
, but in fact it does not. struct sigevent
有一个成员sigev_notify_thread_id
,但实际上并非如此。 Instead, it has an undocumented union containing a corresponding member, and it provides a macro to patch up the difference -- but that macro does not work as a member designator in a designated initializer. fcntl
locks operate on a per-process basis . fcntl
锁基于每个进程进行操作 。 Thus, different threads of the same process cannot exclude each other via this kind of lock. fcntl()
locks obtained via other threads without any special effort or any notification to either thread. fcntl()
锁,而无需任何特殊的努力或对任何一个线程的任何通知。 fcntl()
will return EINTR
if interrupted by any signal that does not terminate the thread. fcntl()
被未终止线程的任何信号中断,则它将返回EINTR
。 You might, therefore, want to use a signal handler that sets an affirmative per-thread flag by which you can verify that the actual timer signal was received, so as to retry the lock if it was interrupted by a different signal. EINTR
. EINTR
锁定失败的情况下时间实际上已经到期。 A better solution might be to use select() : 更好的解决方案可能是使用select() :
https://www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html
https://www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html
#include <errno.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/time.h> int input_timeout (int filedes, unsigned int seconds) { fd_set set; struct timeval timeout; /* Initialize the file descriptor set. */ FD_ZERO (&set); FD_SET (filedes, &set); /* Initialize the timeout data structure. */ timeout.tv_sec = seconds; timeout.tv_usec = 0; /* select returns 0 if timeout, 1 if input available, -1 if error. */ return TEMP_FAILURE_RETRY (select (FD_SETSIZE, &set, NULL, NULL, &timeout)); } int main (void) { fprintf (stderr, "select returned %d.\\n", input_timeout (STDIN_FILENO, 5)); return 0; }
I've had some difficulty with this.我在这方面遇到了一些困难。 Finally got it working.
终于让它工作了。
// main1.cpp
#include <thread>
#include <chrono>
#include <iostream>
int main(int argc, char *argv[]) {
int fd = open(argv[1],O_RDWR|O_CREAT,S_IRWXU | S_IRWXG | S_IRWXO);
struct flock fd_lock;
fd_lock.l_type = F_WRLCK; /* read/write (exclusive) fd_lock_lock */
fd_lock.l_whence = SEEK_SET; /* base for seek offsets */
fd_lock.l_start = 0; /* 1st byte in file */
fd_lock.l_len = 0; /* 0 here means 'until EOF' */
fd_lock.l_pid = getpid();
std::cout << "locked file\n";
fcntl(fd, F_SETLKW, &fd_lock);
std::cout << "file locked\n";
std::this_thread::sleep_for(std::chrono::seconds(100));
}
// main2.cpp
#include <cstring>
#include <chrono>
#include <thread>
#include <iostream>
struct signal_trigger_thread_args {
int signum;
pthread_t tid;
unsigned int seconds;
};
void alarm_handler(int signum, siginfo_t *x, void *y) {
// std::cout << "Alarm Handler!\n";
}
void *trigger_signal_after_time(void *arg) {
struct signal_trigger_thread_args *_arg = (struct signal_trigger_thread_args*)arg;
std::this_thread::sleep_for(std::chrono::seconds(_arg->seconds));
std::cout << "triggering signal!\n";
pthread_kill(_arg->tid,_arg->signum);
return NULL;
}
int fcntl_wait_for(int fd, int cmd, struct flock *_flock, int signum, unsigned int _seconds) {
// Create a thread to trigger the signal.
pthread_t signal_trigger_thread;
struct signal_trigger_thread_args args;
args.signum = signum;
args.tid = pthread_self();
args.seconds = _seconds;
int return_value = pthread_create(&signal_trigger_thread, NULL, &trigger_signal_after_time,(void *)&args);
if ( return_value ) {
std::cout << "pthread creation failed\n";
return -2;
}
return_value = fcntl(fd, cmd, _flock);
if ( return_value == 0 ) { return 0; }
if ( return_value = -1 && errno == EINTR ) {
return 1;
}
return -1;
}
int main(int argc, char *argv[]) {
// initialize_signal_handlers();
static struct sigaction _sigact;
memset(&_sigact,0,sizeof(_sigact));
_sigact.sa_sigaction = alarm_handler;
_sigact.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1,&_sigact,NULL);
int fd = open(argv[1],O_RDWR|O_CREAT,S_IRWXU | S_IRWXG | S_IRWXO);
struct flock fd_lock;
fd_lock.l_type = F_WRLCK; /* read/write (exclusive) fd_lock_lock */
fd_lock.l_whence = SEEK_SET; /* base for seek offsets */
fd_lock.l_start = 0; /* 1st byte in file */
fd_lock.l_len = 0; /* 0 here means 'until EOF' */
fd_lock.l_pid = getpid();
std::cout << "waiting for file to be freed for 5 seconds\n";
int return_value = fcntl_wait_for(fd, F_SETLKW, &fd_lock, SIGUSR1, 5);
if ( return_value == 1 ) {
std::cout << "fcntl was interrupted!\n";
} else if ( return_value == 0 ) {
std::cout << "fcntl obtained lock!\n";
} else {
std::cout << "fcntl failed!\n";
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.