os.NewFile(0) вызывает bad file descriptor при io.Copy в тестах периодически
В коде, приведенном ниже, регулярно возникает ошибки
- bad file descriptor
- write /tmp/test-file: copy_file_range: bad file descriptor
Обычно они возвращаются при io.Copy()
Но если закомментировать строки, описыващие открытие файлового дескриптора с номером 0 - stdin, то все работает хорошо.
const (
_numLevels = 5
_countersPerLevel = 4096
)
type counter struct {
resetAt atomic.Int64
counter atomic.Uint64
}
type counters [_numLevels][_countersPerLevel]counter
func newCounters() *counters {
return &counters{}
}
func Test(t *testing.T) {
for n := range 50 {
t.Run(strconv.Itoa(n), func(t *testing.T) {
// если закомментировать эти две строчки, то
// ошибка пропадет
outFile := os.NewFile(0, "some-file")
require.NotNil(t, outFile)
// или если закомментировать эту строку, то
// ошибка пропадет
newCounters()
require.NoError(t, copyFiles(t))
})
}
}
func copyFiles(t *testing.T) error {
content, err := os.Open("video.webm")
require.NoError(t, err)
defer func() {
require.NoError(t, content.Close())
}()
out := os.TempDir() + "/test-file"
f, err := os.Create(out)
require.NoError(t, err)
defer func() {
require.NoError(t, f.Close())
}()
_, err = io.Copy(f, content)
require.NoError(t, err)
require.NoError(t, f.Sync())
return nil
}
Возникает ошибка не регулярно, поэтому есть цикл for n := range 50
Не могу понять в чем тут дело, ведь эти операции - открытие файлового дескрипотора и создание счетчика и копирование файла, вроде как не зависимы друг от друга?
Окружение
- go: 1.24
- os: UBUNTU 24.04.2 LTS
Ответы (2 шт):
Когда outFile выходит из области видимости, соответствующий файл может быть собран сборщиком мусора. Когда сборщик удаляет объект-файл, он закрывает дескриптор файла. После этого новые вызовы os.NewFile будут завершатся ошибками.
То есть, если сборщик мусора "успеет раньше", ваш код завершиться с ошибкой. Иначе всё пройдёт без видимых проблем.
Если вы берётесь создавать несколько файлов на одном файловом дескрипторе, удерживайте их от удаления сборщиком, например складывайте в массив.
Ещё лучше так не делать. Как может быть польза от нескольких файлов на одном дескрипторе?
"строки, описыващие открытие файлового дескриптора с номером 0" - эти строки делают не то, что вы думаете. Ничего не открывается. Создается новый экземпляр File с передачей владения указанным файловым дескриптором. При явном вызове Close, либо при финализации объекта, дескриптор будет закрыт. Соответственно все другие части программы, по-прежнему использующие дескриптор со значением 0 (хотя бы ваш же код на следующией итерации), будут иметь дело с потенциально невалидным дескриптором.
Вызов os.NewFile корректен только когда передаваемый файловый дескриптор валиден и вы монопольно им владеете. Это имеет смысл например при получении файлового дескриптора со стороны C кода.