Что может вызвать «плохой дескриптор файла» в многопоточной среде?

Этот вопрос чем-то похож на Неверный дескриптор файла но это не одно и то же. Я знаю, что это «плохой вопрос» (возможно, «слишком локализованный»), но я не могу понять это, и теперь у меня нет никаких идей.


У меня есть менеджер потока, который запускает 75 других потоков. Каждый из этих потоков делает много вещей, поэтому я опишу только соответствующие.

пожалуйста, обратите внимание: если я запускаю только несколько потоков — например, 3, 5 или 10, эта ошибка не появляется! Это заставляет меня думать, что это какая-то проблема многопоточности, но, похоже, она не такова. И вы поймете почему в следующих разделах.

Итак, в следующих 2 случаях ИНОГДА Я получаю эту ошибку Bad file descriptor:


Ошибка появляется в TinyXML

Есть XML-файл, который нужен всем потокам. Все эти темы используют TinyXML разобрать файл. ВСЕ из этих тем используют этот файл READ-ONLY! (Я знаю, что это можно оптимизировать, но что угодно).

Итак, код, который вызывает Bad file descriptor ошибка заключается в следующем:

// ...
// NOTE: this is LOCAL, other threads do NOT have access to it
TiXmlDocument   doc;
doc.LoadFile( filename );

// and here's the LoadFile:
bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )
{
//...
FILE* file = fopen( value.c_str (), "rb" );
if ( file )
{
// this IS executed, so file is NOT NULL for sure
bool result = LoadFile( file, encoding );
//...
}
//...
}

bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
{
// ...
long length = 0;
fseek( file, 0, SEEK_END );
// from the code above, we are SURE that file is NOT NULL, it's valid, but
length = ftell( file ); // RETURNS -1 with errno: 9 (BAD FILE DESCRIPTOR)
// how is this possible, as "file" is not NULL and it appears to be valid?
// ...
}

Это немного сложнее. Я удалил проверку возвращаемых значений, но они есть в моем реальном коде, так что это не проблема

int hFileR = open( sAlarmFileName.c_str(), O_CREAT | O_RDONLY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
// hFileR is > 0 for sure, so success

flock( hFileR, LOCK_EX ) /* the result is > 0 for sure, so success*/

// read the file into a string
while( (nRes = read(hFileR, BUFF, MAX_RW_BUFF_SIZE)) > 0 ) // ...

//Write new data to file: reopen/create file - write and truncate mode
int hFileW = open( sAlarmFileName.c_str(),
O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR |
S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
// hFileW is > 0 for sure, so success

do
{
int nWrtRes = write( hFileW,
szText + nBytesWritten, nSize - nBytesWritten );
// nWrtRes is always >= 0, so success
nBytesWritten +=  nWrtRes;
}
while( nSize > nBytesWritten );

close( hFileW );    // this one is successful too

if( flock(hFileR, LOCK_UN) == -1 )
{
// THIS FAILS and executes _Exit( FAILURE );
}

if( close( hFileR ) < 0 )
{
// if the previous one do not fail, this one is successful too
}

Извините за длинный вопрос. Есть идеи?

1

Решение

Несколько слов о понимании файловых дескрипторов:

Файлы являются глобальными ресурсами. Для этого используется (процессная) глобальная индексация: целочисленные значения, называемые дескрипторами файлов. Если поток открывает файл, этот открытый файл указывается индексом. Этот индекс уникален для процесса (не в потоке). Если файл закрыт, дескриптор файла (целочисленный индекс) больше не используется и может быть повторно использован процессом (и любым из его потоков).

Пример:

Любой поток в процессе 1-й вызов open() может вернуть 3, 2 может вернуть 4.

Если тогда 3 закрыто, третий вызов open() может вернуть 3 снова.

Если первый вызов выполняется потоком 1, второй — потоком 2, а третий — потоком 3, легко понять, что поток 1 не должен снова закрывать свой файловый дескриптор, так как значение 3, возможно, уже было переработано и использовать потоком 3, который будет пытаться получить доступ к недопустимому дескриптору файла, так как он мог быть закрыт 2-м (ошибочным) вызовом close() по теме 1. Хорошо? 😉

Попробуйте настроить пример кода и проверить / записать целочисленные значения, возвращаемые вызовами open() и назначены в качестве файловых дескрипторов, чтобы получить представление о том, как это работает.

Заметка:

Это также может относиться к stdin, stdout а также stderr«предопределенные» файловые дескрипторы 0, 1 а также 2, Под недавним закрытием Linux stdin с последующим вызовом int fd = open("myfoofile.bar", ...) вполне может вернуться 0 как дескриптор файла fd, Во всяком случае, либо ядро ​​или glibc не в состоянии справиться с таким 0 как и ожидалось. Неясные ошибки могут возникнуть при использовании lseek(fd, ...) например. Попытайся! ; — >>

3

Другие решения

Нужно искать код, который дважды закрывает один и тот же дескриптор файла.

В однопоточной программе это безобидная ошибка программирования, потому что вторая close() ничего не делает кроме возврата EBADFи много кода не удосуживается проверить close() возвращаемое значение в любом случае. Однако в многопоточной программе номер дескриптора закрытого дескриптора может быть распределен в другом потоке между двумя вызовами close()итак второе close() закроет несвязанный сокет из другого потока. Дальнейшее чтение и запись дескриптора другого потока приведет к ошибке «неверный дескриптор файла».

5

Если приложение является многопоточным, это может произойти, если один поток закрыл файл, а другой все еще пытается получить к нему доступ.

(потому что файловые дескрипторы, такие как адресное пространство, являются глобальными & общий для всех потоков процесса)

Вы могли бы использовать strace чтобы понять, что системные вызовы сделаны.

2
По вопросам рекламы [email protected]