The Russian Electronic Developer Magazine | |
Русский электронный журнал разработчика | |
Потеряв надежду отыскать ошибку, я принялся за другую программу. Как мне показалось тогда, задача была намного проще. Необходимо было брать почту из некоторого количества каталогов UUCP-спула и запускать sendmail с соответствующими параметрами. Опять-таки, непременными условиями были работа с открытыми файлами, многозадачность и запуск внешней программы. Как только программа стала поддерживать многозадачность, снова полезли глюки. Либо после отправки почты не удалялся один из файлов в спуле, так как был почему-то занят каким-то процессом, либо временно созданный файл, передаваемый sendmail`у, оказывался заперт, как в случае с SMTP.
Тут я задумался. Первое подозрение пало на компайлер. Мною использовался Borland 2.0 C++. Расстановка всевозможных семафоров и проверок не дала никаких результатов. Работа не ждала. Необходим был POP3-сервер. Через несколько месяцев он был написан. Глюков было на удивление мало. Однако первые две программы не давали покоя. Решив сменить компилятор, я поставил Watcom 10. Перекомпиляция и тренировочный запуск в первый день ничего не выявили. Оставив работающую программу на ночь, я ушёл домой с камнем в сердце на Borland. Впервые за год всю ночь спал спокойно. Наутро, прийдя пораньше, я увидел те же глюки, что и у Borland. Это было похоже на чью-то злую шутку, но было правдой. Со мной творилось что-то страшное. Дикие крики и матерные выражения оглашали рабочее пространство. Находиться рядом со мной в тот день было небезопасно. В сердцах я забросил программу, не в силах вынести издевательство тупого компьютера над программистом.
Прошло полгода...
Решив сменить компайлер в третий раз, я поставил VACPP++. Программа была переписана начисто. Настал день проверки. Запуск -- работает. Странно, подумал я, и добавил нагрузку. Через несколько минут попытка удалить файл с треском провалилась. В ту минуту я был твердо убежден, что снова допустил ошибку. Наконец, рассвирипев, я стал утыкивать программу отладочными сообщениями. После я детально изучил пять логов с идентичной ситуацией неудаления файлов. Строго следуя логу, процесс был таким. Один тред работает с двумя файлами, тупо копируя из одного в другой. Второй тред запускает sendmail через spawnlp(). В результате запертый в первом треде файл остается запертым до тех пор, пока второй тред не завершится. После файл становится свободным, и процесс продолжается. Ага, ***, воскликнул я. И понял, что не срабатывает стандартная функция close() (а также ни одна подобная из всего набора стандартной библиотеки) в момент spawnlp() или DosExecPgm() из другого треда. Замена spawnlp() на DosExecPgm() не облегчила ситуации. И только полная смена работы с файлами на DosOpen() и DosClose(), а spawnlp() на DosExecPgm() сняла проблему навсегда.
Эпилог. Люди, доверяйте только себе. Не наступайте на мои грабли. Только тот программист сможет реализовать свои способности полностью, кто уже написал свои библиотеки через Dos* вызовы.
И это же надо! Все компайлеры (про EMX не знаю) имеют одну и ту же ошибку многозадачности!
Andy HohutkinP.S. Прилагаю тестовую программу (closetest.zip, 76k), иллюстрирующую эту ошибку. При запуске программы, скомпилированной с помощью BCC, ошибка удаления файла возникает практически сразу. Код, полученный с помощью ICC, такой ошибки не даёт -- возможно, слишком мала нагрузка или размеры запускаемой через spawnlp() программы.
IRC nick: Orlik
От редакции: Недавно нам пришло интересное письмо, проливающее свет на истоки ошибок компиляторов:
From: Igor Vanin
Subject: отзыв о статьеПодозpения у меня появились, когда я только начал читать статью, и полностью подтвеpдились, когда я посмотpел пpилагавшийся в конце пpимеp.
"Ошибка" не в компилятоpе, а в пpилагающемся пpимеpе. Суть пpоблемы вот в чем: Хэндл откpываемого файла может быть унаследован child-пpоцессами. То есть, запущенный child-пpоцесс будет иметь такой же доступ к этому откpытому файлу, как и его pодитель, и pодительский пpоцесс не сможет полностью закpыть и удалить файл, пока запущенный child-пpоцесс не завеpшит свою pаботу (или сам не закpоет этот файл, что обычно не делается, поскольку в данном случае автоp не пеpедал ему хэндл этого файла).
Чтобы избежать такого унаследования хэндла откpытого файла, нужно указать специальный паpаметp "no inherit" (O_NOINHERIT в библиотке Watcom C) пpи вызове функции откpытия файла. Это можно сделать следующим обpазом (два пpимеpа: для функций семейства fopen и семейства open):
if ((txfd = sopen (txname, O_RDONLY|O_BINARY, SH_DENYWR|O_NOINHERIT)) < 0) { log(1, LCLR_ERROR, MSG(M_CANTOPEN), txname, errno); return XFER_SKIP; } Infile = _fsopen(Filename, "rb", SH_DENYWR|O_NOINHERIT); if (!Infile) { log(1, LCLR_ERROR, MSG(M_CANTOPEN), fname, errno); rc = 2; break; }Как видно из пpимеpа автоpа статьи, он не использует паpаметpы, запpещающие наследование хэндлов откpытых файловclose_test.cpp: tmp = fopen( buf, "w" );Поэтому он и получил описанную пpоблему, котоpую назвал "ошибкой компилятоpов".
Igor V. Vanin, St.Petersburg, Russia
vanin@gpmail.spb.ru gpmail.spb.ru
FidoNet: 2:5030/448 (General Purpose BBS)
Интересные ссылки:
Право закон правовой закон.
Комментариев к странице: 0 | Добавить комментарий
Редактор: Дмитрий Бан
Оформление: Евгений Кулешов