Я реализую файловую систему FUSE, предназначенную для обеспечения доступа через знакомые вызовы POSIX к файлам, которые фактически хранятся за API RESTful. Файловая система кэширует файлы, как только они были получены в первый раз, так что они становятся более доступными при последующих обращениях.
Я запускаю файловую систему в многопоточном режиме (это FUSE по умолчанию), но обнаружил, что вызовы getattr, похоже, сериализованы, хотя другие вызовы могут происходить параллельно.
При открытии файла FUSE всегда сначала вызывает getattr, а клиент, который я поддерживаю, требует, чтобы размер файла, возвращенный этим первоначальным вызовом, был точным (у меня нет никакого контроля над этим поведением). Это означает, что если у меня нет кэшированного файла, мне нужно получить информацию через вызовы API RESTful. Иногда эти вызовы происходят в сетях с высокой задержкой, время прохождения которых составляет около 600 мс.
В результате очевидного последовательного характера вызова getattr любой доступ к файлу, который в настоящее время не кэшируется, заставит всю файловую систему блокировать любые новые операции, пока этот getattr будет обслуживаться.
Я придумал несколько способов обойти это, но все кажутся уродливыми или длинными, я просто хочу, чтобы вызовы getattr запускались параллельно, как и все остальные вызовы.
Глядя на исходный код, я не понимаю, почему getattr должен вести себя следующим образом: FUSE блокирует мутекс tree_lock, но только для чтения, и в то же время нет записи.
Для публикации чего-то простого в этом вопросе я выполнил невероятно базовую реализацию, которая просто поддерживает getattr и позволяет легко продемонстрировать проблему.
#ifndef FUSE_USE_VERSION
#define FUSE_USE_VERSION 22
#endif
#include <fuse.h>
#include <iostream>
static int GetAttr(const char *path, struct stat *stbuf)
{
std::cout << "Before: " << path << std::endl;
sleep(5);
std::cout << "After: " << path << std::endl;
return -1;
}
static struct fuse_operations ops;
int main(int argc, char *argv[])
{
ops.getattr = GetAttr;
return fuse_main(argc, argv, &ops);
}
Используя пару терминалов для вызова ls на пути в (примерно) в то же время, показывает, что второй вызов getattr запускается только после того, как первый закончен, это приводит к тому, что второй ls займет ~ 10 секунд вместо 5.
Терминал 1
$ date; sudo ls /mnt/cachefs/file1.ext; date
Tue Aug 27 16:56:34 BST 2013
ls: /mnt/cachefs/file1.ext: Operation not permitted
Tue Aug 27 16:56:39 BST 2013
Терминал 2
$ date; sudo ls /mnt/cachefs/file2.ext; date
Tue Aug 27 16:56:35 BST 2013
ls: /mnt/cachefs/file2.ext: Operation not permitted
Tue Aug 27 16:56:44 BST 2013
Как вы можете видеть, разница во времени с двумя выходами date
до ls
отличается только на одну секунду, но две из них после ls
отличаются на 5 секунд, что соответствует задержке в GetAttr
. Это говорит о том, что второй вызов блокируется где-то глубоко в FUSE.
Выход
$ sudo ./cachefs /mnt/cachefs -f -d
unique: 1, opcode: INIT (26), nodeid: 0, insize: 56
INIT: 7.10
flags=0x0000000b
max_readahead=0x00020000
INIT: 7.8
flags=0x00000000
max_readahead=0x00020000
max_write=0x00020000
unique: 1, error: 0 (Success), outsize: 40
unique: 2, opcode: LOOKUP (1), nodeid: 1, insize: 50
LOOKUP /file1.ext
Before: /file1.ext
After: /file1.ext
unique: 2, error: -1 (Operation not permitted), outsize: 16
unique: 3, opcode: LOOKUP (1), nodeid: 1, insize: 50
LOOKUP /file2.ext
Before: /file2.ext
After: /file2.ext
unique: 3, error: -1 (Operation not permitted), outsize: 16
Приведенный выше код и примеры не похожи на фактическое приложение или как приложение используется, но демонстрирует то же поведение. Я не показал это в приведенном выше примере, но я обнаружил, что после завершения вызова getattr последующие открытые вызовы могут выполняться параллельно, как я и ожидал.
Я просмотрел документы, чтобы попытаться объяснить это поведение и попытался найти кого-то другого, сообщающего подобный опыт, но не может ничего найти. Возможно потому, что большинство реализаций getattr были бы такими быстрыми, что вы не заметили бы и не заботились бы о том, было ли это сериализовано, или, может быть, потому, что я делаю что-то глупое в конфигурации. Я использую версию 2.7.4 от FUSE, поэтому возможно, что это была старая ошибка, которая с тех пор была исправлена.
Если у кого-то есть понимание этого, мы будем очень благодарны!