简体   繁体   English

linux fcntl 文件锁定超时

[英]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 线程,其他线程不应该受到影响,但是信号是进程级的,我不确定如何处理这个情况。

Added:添加:

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 . 信号的最明显选择是SIGUSR1SIGUSR2 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. 当选定的信号传递到进程时,当前没有阻塞的线程将接收该信号(如果有这样的线程可用)。

Example implementation 示例实施

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. 符合我的预期。

Notes: 笔记:

  • signal dispositions are process-wide properties, not per-thread properties, so you need to coordinate your use of signals throughout the whole program. 信号处理是进程范围的属性,而不是每个线程的属性,因此您需要在整个程序中协调对信号的使用。 With that being the case, it is not useful (and it might be dangerous) for the try_lock function itself to modify the disposition of its chosen signal. 在这种情况下, try_lock函数本身修改其选择信号的配置没有用(可能很危险)。
  • The 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特定的。
  • On Linux, you'll need to link with -lrt for the timer_* functions. 在Linux上,您需要与-lrt链接以使用timer_*函数。
  • The above works around the fact that Glibc's struct sigevent does not conform to its own docs (at least in relatively old version 2.17). 上面的工作解决了以下事实:Glibc的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. 因此,同一进程的不同线程无法通过这种锁定相互排斥。 Moreover, different threads of the same process can modify fcntl() locks obtained via other threads without any special effort or any notification to either thread. 而且,同一进程的不同线程可以修改通过其他线程获得的fcntl()锁,而无需任何特殊的努力或对任何一个线程的任何通知。
  • You could consider creating and maintaining a per-thread static timer for this purpose instead of creating and then destroying a new one on each call. 您可以考虑为此目的创建和维护一个单线程静态计时器,而不是在每次调用时创建然后销毁一个新的计时器。
  • Be aware that 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. 因此,您可能想使用一个设置了每个线程肯定标志的信号处理程序,通过该标志程序可以验证是否收到了实际的计时器信号,以便在锁被其他信号中断的情况下重试锁定。
  • It's up to you to ensure that the thread does not receive the chosen signal for some other reason, or else to confirm by some other means that time actually expired in the event that locking fails with 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM