RDM/2 The Russian Electronic Developer Magazine  
RDM/2 Русский электронный журнал разработчика  
ДомойОт редактораПишите намОбратная связьRU/2

"Крутые" компайлеры, или 1,5 года потеряного времени

Эта история берет свое начало ещё от моего дебюта в программировании под OS/2. Передо мной стояла конкретная задача -- для упрощения в организации узла Интернет необходимо было заменить sendmail на предмет приема почты по протоколу SMTP. За несколько недель был написан основной модуль, который поддерживал многотредовость и вызывал rmail для передачи принятого письма в UUCP-спул. После установки и запуска в работу обнаружился странный и непредсказуемый момент: периодически rmail кричал, что не в состоянии открыть предназначенный ему файл. Все попытки обнаружить ошибку в программе, а также всевозможные затычки в виде задержек, семафоров и т.п. не помогали. Глюк был весьма устойчив и в течении дня возникал несколько раз.

Потеряв надежду отыскать ошибку, я принялся за другую программу. Как мне показалось тогда, задача была намного проще. Необходимо было брать почту из некоторого количества каталогов 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 Hohutkin
IRC nick: Orlik
P.S. Прилагаю тестовую программу (closetest.zip, 76k), иллюстрирующую эту ошибку. При запуске программы, скомпилированной с помощью BCC, ошибка удаления файла возникает практически сразу. Код, полученный с помощью ICC, такой ошибки не даёт -- возможно, слишком мала нагрузка или размеры запускаемой через spawnlp() программы.

---

От редакции: Недавно нам пришло интересное письмо, проливающее свет на истоки ошибок компиляторов:

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 | Добавить комментарий
---
Редактор: Дмитрий Бан
Оформление: Евгений Кулешов
(C) Russian Underground/2