问题
I am currently building something that uses epoll. It works pretty nice, but it would be good to have a notification when a file descriptor gets removed from epoll when the underlying fd is closed.
Is there a way to get a notification from epoll as soon as an fd is closed?
回答1:
No. Here's a Zig program to demonstrate:
const std = @import("std");
pub fn main() !void {
const epollfd = blk: {
const rc = std.os.linux.epoll_create1(std.os.linux.EPOLL_CLOEXEC);
const err = std.os.linux.getErrno(rc);
if (err != 0) return error.EpollCreateFailed;
break :blk @intCast(i32, rc);
};
defer std.os.close(epollfd);
var pipe: [2]i32 = undefined;
{
const rc = std.os.linux.pipe2(&pipe, std.os.linux.O_CLOEXEC);
const err = std.os.linux.getErrno(rc);
if (err != 0) return error.PipeCreateFailed;
}
var ev: std.os.linux.epoll_event = undefined;
ev.events = std.os.linux.EPOLLIN | std.os.linux.EPOLLET;
ev.data.fd = pipe[0];
{
const rc = std.os.linux.epoll_ctl(epollfd, std.os.linux.EPOLL_CTL_ADD, pipe[0], &ev);
const err = std.os.linux.getErrno(rc);
if (err != 0) return error.EpollCtlFailed;
}
const thread = try std.os.spawnThread(pipe[0], threadStart);
var events: [10]std.os.linux.epoll_event = undefined;
const nfds = blk: {
std.debug.warn("epoll wait\n");
const rc = std.os.linux.epoll_wait(epollfd, &events, @intCast(u32, events.len), -1);
std.debug.warn("epoll wakeup\n");
const err = std.os.linux.getErrno(rc);
if (err != 0) return error.EpollWaitFailed;
break :blk rc;
};
if (nfds != 1) return error.NotExactly1FileDescriptor;
std.debug.assert(events[0].data.fd == pipe[0]);
thread.wait();
}
fn threadStart(fd: i32) void {
std.os.time.sleep(1, 0);
std.debug.warn("close fd\n");
std.os.close(fd);
}
It outputs:
epoll wait
close fd
^C
(I had to kill it with Ctrl+C because it never woke up.)
Here's the strace output:
execve("./test", ["./test"], 0x7fff311ff208 /* 122 vars */) = 0
epoll_create1(EPOLL_CLOEXEC) = 3
pipe2([4, 5], O_CLOEXEC) = 0
epoll_ctl(3, EPOLL_CTL_ADD, 4, {EPOLLIN|EPOLLET, {u32=4, u64=4}}) = 0
mmap(NULL, 8388608, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0) = 0x7fd082ed9000
clone(child_stack=0x7fd0836d8fd8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID|0x400000, parent_tidptr=0x7fd0836d8fe4, child_tidptr=0x7fd0836d8fe4) = 13547
strace: Process 13547 attached
[pid 13546] write(2, "epoll wait\n", 11epoll wait
<unfinished ...>
[pid 13547] nanosleep({tv_sec=1, tv_nsec=0}, <unfinished ...>
[pid 13546] <... write resumed> ) = 11
[pid 13546] epoll_wait(3, <unfinished ...>
[pid 13547] <... nanosleep resumed> 0x7fd0836d8f20) = 0
[pid 13547] write(2, "close fd\n", 9close fd
) = 9
[pid 13547] close(4) = 0
[pid 13547] exit(0) = ?
[pid 13547] +++ exited with 0 +++
^Cstrace: Process 13546 detached
回答2:
You need to watch out for when epoll_wait() returns -1, then check the errno value to see if it matches EBADF.
See man epoll_wait(), man errno for details.
回答3:
Not. As soon as the file descriptor is closed, it is no longer available for anything but to be used by the kernel in a new file descriptor. As soon as you don't get it again as a result of a new open(2), create(2), socket(2), etc. system call, it will be invalid to use it as a parameter for any system call (and this includes select(2) and epoll(2))
The kernel has freed the file descriptor entry in your process' structure, so it's not valid to use it.
Sorry, I said:
The only way you can get something from
epoll(2)in case you close it is if you have two threads and youclose(2)the file descriptor in one thread while you are waiting for it in another thread. This time you'll get immediate return from theepoll(2)system call with probably some kind of error (I have not tested this) but I think this is not your scenario.
As I then said, I had not tested it, but now, I can say that in course system calls are not interrupted, but left to finish. Anyway, the file descriptor is not valid in any thread to be used for a new system call anymore. The code in the NOTE below shows how this happens. But the conclusion is that after close(2) you cannot use the file descriptor passed to the system call anymore in the process, as I said before. (of course, you can if you get the same file descriptor value as a consequence of a new open(2), socket(2), etc. system call)
NOTE
just tested with the following code: if you close(2) a file descriptor and other threads are blocked in system calls related to that file descriptor, the system calls are not aborted, but the file descriptor is not valid anymore. On termination of those system calls, the file descriptor is unusable anymore, neither in the thread that close(2)d it, nor in the the other threads.
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
char buffer[100];
void *f(void* arg)
{
int res;
printf("f: waiting on read()\n");
res = read(0, buffer, sizeof buffer);
printf("f: read %d bytes\n", res);
if (res < 0) {
printf("this was an error: %s\n", strerror(errno));
} else {
printf("f: read: %.*s", res, buffer);
}
printf("f: waiting on read()\n");
res = read(0, buffer, sizeof buffer);
printf("f: read %d bytes\n", res);
if (res < 0) {
printf("f: this was an error: %s\n", strerror(errno));
} else {
printf("f: read: %.*s", res, buffer);
}
pthread_exit(NULL);
}
int main()
{
pthread_t t;
int res = pthread_create(&t, NULL, f, NULL);
printf("main: waiting 10 seconds\n");
sleep(10);
printf("main: closing stdin\n");
close(0);
printf("main: waiting another 10s.\n");
sleep(10);
printf("main: waiting for thread.\n");
pthread_join(t, NULL);
printf("main: reading from stdin\n");
res = read(0, buffer, sizeof buffer);
printf("main: read %d bytes\n", res);
if (res < 0) {
printf("main: this was an error: %s\n", strerror(errno));
} else {
printf("main: read: %.*s", res, buffer);
}
printf("main: exiting.\n");
}
This code results in this sequence:
$ a.out
main: waiting 10 seconds
f: waiting on read()
main: closing stdin <--- now the fd is closed
main: waiting another 10s.
lorem ipsum <--- input
f: read 12 bytes
f: read: lorem ipsum
f: waiting on read()
f: read -1 bytes <--- file descriptor is not valid in the second read(2) call.
f: this was an error: Bad file descriptor
main: waiting for thread. <--- joining thread f
main: reading from stdin
main: read -1 bytes <--- invalid also for main thread.
main: this was an error: Bad file descriptor
main: exiting.
来源:https://stackoverflow.com/questions/43034535/do-i-get-a-notification-from-epoll-when-a-fd-is-closed