Разница в kqueue обработке fifo между Mac OS и FreeBSD?
Я работаю над приложением, которое использует fifo для IPC и использует API уведомления о событиях (например, epoll или kqueue), чтобы отслеживать эти данные для чтения.
Приложение ожидает, что, если писатель для fifo завершит работу, читатель получит событие через API уведомления о событии, что позволит читателю заметить, что писатель завершил работу.
В настоящее время я портирую это приложение на macos и сталкиваюсь с каким-то странным поведением с помощью kqueue. Я был в состоянии создать воспроизводителя этого поведения:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/errno.h>
static int child() {
char child_fifo_path[64];
char parent_fifo_path[64];
printf("Child %d\n", getpid());
sprintf(child_fifo_path, "/tmp/child-%d", getpid());
sprintf(parent_fifo_path, "/tmp/parent-%d", getpid());
mkfifo(child_fifo_path, 0644);
mkfifo(parent_fifo_path, 0644);
int parent_fd = open(parent_fifo_path, O_RDONLY);
if (parent_fd == -1) {
perror("open");
return EXIT_FAILURE;
}
unsigned char parent_val;
read(parent_fd, &parent_val, 1);
printf("Received %hhx from parent\n", parent_val);
int child_fd = open(child_fifo_path, O_WRONLY);
if (child_fd == -1) {
perror("open");
return EXIT_FAILURE;
}
write(child_fd, &parent_val, 1);
printf("Wrote %hhx to parent\n", parent_val);
close(parent_fd);
close(child_fd);
return EXIT_SUCCESS;
}
static int parent(pid_t child_pid) {
char child_fifo_path[64];
char parent_fifo_path[64];
printf("Parent %d\n", getpid());
sprintf(child_fifo_path, "/tmp/child-%d", child_pid);
sprintf(parent_fifo_path, "/tmp/parent-%d", child_pid);
int result = -1;
while (result == -1) {
struct stat buf;
result = stat(child_fifo_path, &buf);
if (result == -1) {
if (errno != ENOENT) {
perror("open");
return EXIT_FAILURE;
}
}
}
unsigned char val = 20;
int parent_fd = open(parent_fifo_path, O_WRONLY);
if (parent_fd == -1) {
perror("open");
return EXIT_FAILURE;
}
write(parent_fd, &val, 1);
printf("Wrote %hhx to child\n", val);
int child_fd = open(child_fifo_path, O_RDONLY);
if (child_fd == -1) {
perror("open");
close(parent_fd);
return EXIT_FAILURE;
}
int kq = kqueue();
struct kevent event;
EV_SET(&event, child_fd, EVFILT_READ, EV_ADD, 0, 0, 0);
result = kevent(kq, &event, 1, NULL, 0, NULL);
if (result == -1) {
perror("kevent");
close(child_fd);
close(parent_fd);
return EXIT_FAILURE;
}
int done = 0;
while (!done) {
memset(&event, 0, sizeof(event));
printf("Waiting for events\n");
result = kevent(kq, NULL, 0, &event, 1, NULL);
if (result == -1) {
perror("kevent");
close(child_fd);
close(parent_fd);
return EXIT_FAILURE;
}
if (event.ident == child_fd) {
if (event.flags & EV_EOF) {
printf("Child exited\n");
done = 1;
}else if ( event.data > 0 ) {
unsigned char child_val;
result = read(child_fd, &child_val, 1);
if (result == -1) {
perror("read");
return EXIT_FAILURE;
}
printf("Received %hhx from child\n", child_val);
}
}
}
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
pid_t child_pid = fork();
if (child_pid == -1) {
perror("fork");
return EXIT_FAILURE;
}
if (child_pid) {
return parent(child_pid);
} else {
return child();
}
}
Этот репродуктор разветвляет дочерний процесс, который создает 2 fifo: /tmp/parent-$CHILD_PID
а также /tmp/child-$CHILD_PID
, Родитель ждет /tmp/parent-$CHILD_PID
создается и затем записывает в него байт. Ребенок открывает /tmp/parent-$CHILD_PID
и блоки для чтения байта, написанного родителем. После завершения дочерний процесс отправляет тот же байт в родительский /tmp/child-$CHILD_PID
, Родитель использует kqueue для наблюдения за записью в /tmp/child-$CHILD_PID
,
Эта последовательность событий работает отлично.
Проблема возникает, когда ребенок закрывает свой файл, ссылаясь на /tmp/child-$CHILD_PID
, Я вижу, что об этом событии не сообщается родителю через kqueue.
Самая интересная часть: этот код работает так, как и следовало ожидать на FreeBSD.
Информация о версии:
Mac OS X: 10.11.6
FreeBSD 10.4-RELEASE-p3
Есть ли разница между kqueue на macos и FreeBSD в этом контексте? Если да, есть ли документация, которая документирует эту разницу?
1 ответ
Это не лучший ответ на ваш вопрос, но я надеюсь, что он поможет вам найти другие различия, которые могут повлиять на поведение вашего кода при использовании kqueue между macOS и FreeBSD
В моем случае я использую kqueue EVFILT_VNODE
чтобы проверить изменения, но в зависимости от операционной системы мне нужно определить различные флаги openModeDir при использовании syscall.Open
Для macOS ( openmode_darwin.go) я использую это:
openModeDir = syscall.O_EVTONLY | syscall.O_DIRECTORY
openModeFile = syscall.O_EVTONLY
А для FreeBSD ( openmode.go) я использую:
openModeDir = syscall.O_NONBLOCK | syscall.O_RDONLY | syscall.O_DIRECTORY
openModeFile = syscall.O_NONBLOCK | syscall.O_RDONLY
Из документации macOS open(2) это описание флага:
O_EVTONLY descriptor requested for event notifications only
А из FreeBSD open(2) нет O_EVTONLY
,
Собирая все вместе, вот как я называю kqueue:
...
watchfd, err := syscall.Open(dir, openModeDir, 0700)
if err != nil {
return err
}
kq, err := syscall.Kqueue()
if err != nil {
syscall.Close(watchfd)
return err
}
ev1 := syscall.Kevent_t{
Ident: uint64(watchfd),
Filter: syscall.EVFILT_VNODE,
Flags: syscall.EV_ADD | syscall.EV_ENABLE | syscall.EV_CLEAR,
Fflags: syscall.NOTE_WRITE | syscall.NOTE_ATTRIB,
Data: 0,
}
...
Я использую Go, но, как упоминалось ранее, надежда может дать вам представление о Kqueue
, В моем случае это простое изменение флагов имело значение.