16 июня 2008 г.

Блокирующие вызовы и ошибка EINTR

Недавно столкнулся с ошибкой, связанной с неправильной обработкой возвращаемого значения функции sem_wait().

Функция sem_wait() уменьшает текущее значение семафора на 1. Если прежнее значение семафора было 0, то поток, выполняющий функцию sem_wait, блокируется до изменения значения семафора. При успешном выполнении — функция возвращает 0; при ошибке — вернет -1, а errno будет содержать код ошибки.

Вот пример неправильной обработки возвращаемого значения:

int res = sem_wait(&sem);
if (0 != res)
{
  // Ошибка!
  // Сообщаем об ошибке и выходим.
  ...
  return;
}

// Ошибки нет - продолжаем работу.
...


Что здесь неправильно? Дело в том, что функция sem_wait() является блокирующим системным вызовом. Поэтому выполнение этой функции может быть прервано, если выполняющийся поток получит какой-нибудь сигнал (например, сигнал о завершении другого потока). В этом случае функция вернет -1, а errno будет равна EINTR.

При этом никакой ошибки на самом деле нет, это просто способ дать программе обработать пришедший сигнал. Это необходимо учитывать, и вызов sem_wait() лучше всего делать в цикле. Например, таком:

int res;
do
{
  res = sem_wait(&sem);
}
while (0 != res && EINTR == errno);

if (0 != res)
{
  // Ошибка!
  // Сообщаем об ошибке и выходим.
  ...
  return;
}

// Ошибки нет - продолжаем работу.
...

Это касается и других блокирующих функций. Например, select, wait, waitpid и др. Возможность прерывания функции с ошибкой EINTR обычно описана в man'е.

20 мая 2008 г.

Ошибки кодирования

Очередной багфиксинг показал, что за 30 лет мало что изменилось :)

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

И вот, модуль логгирования вдруг стал время от времени "падать" в совершенно непредсказуемые моменты. Оказалось, что виноват в этом один из компонентов, который вместо осмысленного текста посылает в модуль логгирования мусор. У компонента проблема, но почему модуль-то падает?..

Ошибка стара, как Си :) Разработчик модуля логгирования реализовал функцию примерно так:

void logMessage (const char *str)
{
  printf(str);
}

Видно ошибку? Проблема тут в том, что текст сообщения напрямую подается в функцию printf, минуя использование форматных строк типа "%s\n". Если подавать на вход функции осмысленный текст, то все будет хорошо, до тех пор пока в тексте сообщения не встретится "%s". В этом случае функция printf попытается сформировать новую строку, предполагая, что в стеке находятся дополнителльные аргументы. А аргументов-то там и нет. Вот тут-то printf и упадет с большой долей вероятности (скорее всего это произойдет, когда printf попытается вызвать strlen для значения, которое достанет из стека).

В нашем случае на вход функции подавался случайный мусор, поэтому время от времени в нем встречалась последовательность "%s", и модуль вываливался с ошибкой Segmentation fault.

Вот правильное использование printf для нашей функции логгирования:

void logMessage (const char *str)
{
  printf("%s", str);
}

Вызов printf и ей подобных должен выглядеть именно так и никак иначе.

Читать об уязвимостях printf.

Технологии мчатся вперед, а ошибки не меняются :)

16 марта 2008 г.

Проблема импорта в Mozilla Thunderbird

Столкнулся сегодня с проблемой импорта почтовых сообщений из Microsoft Outlook в Mozilla Thunderbird 2.0.0.12.

В процессе импорта вдруг возникают сообщения об ошибках, типа "Невозможно сохранить сообщение как черновик. Невозможно создать временный файл. Проверьте настройки временной папки." В результате из Outlook импортируется только часть сообщений.

Поиск в сети ничего не дал. С подобной проблемой люди уже сталкивались, но все эти проблемы относятся к версиям Thunderbird не выше 1.5 и связаны с тем, что старые версии Thunderbird очень плохо относились к именам временных папок которые содержали пробелы. Это не мой случай, потому что изменение имени временной папки ничего не дало — проблема осталась.

После трехчасовой возни я все-таки выяснил, в чем дело :) Оказалось, что Антивирус Касперского мешает нормальной работе программы импорта. Я не знаю почему это происходит, и почему Касперский ничего не сообщал о том, что он там что-то блокировал, но факт налицо — после приостановки работы Касперского процедура импорта выполнилась без единой ошибки.

18 февраля 2008 г.

Цитата: Хаак

Бывший MVP (Most Valuable Professional – Самый Ценный Специалист) Microsoft Фил Хаак о повторных использованиях и обобщениях при разработке ПО
«Избегайте преждевременного обобщения», советует Хаак. «Не создавайте систему способную предсказать любое изменение. Пусть она будет достаточно гибкой для внесения изменений».

Чтобы определить, когда требуется обобщение, Хаак использует Правило Трех: «Когда вы в первый раз увидите, что что-либо может повторяться, не обобщайте это. В следующий раз сделайте все аналогичным способом, возможно даже при помощи копипастинга, но только не обобщайте. В третий раз хорошенько подумайте, как это можно обобщить».

Updated: Знакомство с Vim

По просьбе читателей поправил текст старой статьи "Знакомство с Vim. Настройки". Добавлено краткое описание режимов, а сам контент сделан более последовательным.

6 февраля 2008 г.

Еще раз об MDA

Возвращаясь к напечатанному, хотелось бы добавить...

Разработка серьезного программного продукта обычно ведется с использованием средств версионного контроля. При таком подходе довольно часто возникает необходимость выполнять какие-то работы (например, исправление ошибок) на дополнительных ветках, чтобы по окончании этих работ аккуратно слить результат на основную ветку.

Так вот. Инструмента для выполнения merge UML-моделей я еще не встречал. В результате, все слияния и разрешения конфликтов выполняются визуально и вручную. Слияние элементарного изменения может потребовать в разы (а то и в десятки раз) больше времени, чем само внесение этого изменения в модель. Поэтому, я не понимаю, как можно нескольким разработчикам эффективно работать по MDA-технологии.

1 февраля 2008 г.

Как сделать merge в cleartool

В ClearCase сделать слияние (merge) для файлов с разных веток довольно просто. Проблемы начинаются, если на сливаемых ветках добавлялись или удалялись файлы. Оказывается, слияние директорий в ClearCase — задача непростая.

Выполнить слияние двух веток можно следующей командой:

cleartool findmerge . -fversion <version/label> -co -nc -merge -gmerge

Эта команда выполнит слияние с ветки version или label на текущую ветку для всех файлов, содержащих изменения. Ключ -gmerge удобно использовать при работе в графической среде — при возникновении конфликтов cleartool запустит визуальный инструмент для разрешения конфликтов вручную. Но при работе, например, по SSH, указывать этот ключ смысла не имеет; в этом случае используется консольный инструмент разрешения конфликтов. Тем, кто пользуется консольным, можно только посочувствовать.

Пример. У меня в текущей папке есть файлы file1, file2 и file3. На ветке /main/mybranch1 выполнены изменения для файлов file1 и file3. Чтобы слить изменения с ветки /main/mybranch1 на текущую ветку, мне необходимо выполнить команду:

cleartool findmerge . -fversion /main/mybranch1/LATEST -co -nc -merge -gmerge

В результате выполнения файлы file1 и file3 будут вычекнуты (checkout) на текущую ветку и модифицированы в соответствии с изменениями на ветке /main/mybranch1. Мне останется лишь выполнить checkin для измененных файлов. Файл file2 останется нетронутым.

Но вот, предположим, что на ветке /main/mybranch1 добавлен еще один файл — file4. Выполнение findmerge никак не коснется этого файла, потому что на текущей ветке этого файла нет. Значит, нужно сначала сделать merge для директории.

Описание команды merge говорит о том, что слияние директорий происходит в три шага:
  1. убедиться, что на "той" ветке нет вычекнутых элементов;
  2. вычекнуть директорию в текущей ветке;
  3. выполнить команду cleartool merge для директории.

Только после этого можно будет безболезненно выполнить cleartool findmerge.

Итак, синхронизируем текщую директорию с веткой /main/mybranch1:

ct co -nc .
ct merge -to ./ ./@@/main/mybranch1/LATEST
  Added file file4 to ./.

Теперь можно зачекать измененную директорию (а можно сделать это потом). И наконец-то можно выполнить долгожданный findmerge:

cleartool findmerge . -fversion /main/mybranch1/LATEST -co -nc -merge -gmerge

Будет выполнено слияние для файлов file1, file3 и file4. Теперь нужно внести в репозиторий результаты слияния веток. Заносим всё сразу — зачем мелочиться? :)

ct ci -c "Merged from /main/mybranch1." `ct lsco -r -cview -short`

Ух. Кажется, несложно. Но когда у нас нужно синхронизировать не одну директорию, а, скажем, десяток? Все эти действия превращаются в пытку :( К счастью, действия, необходимые для синхронизации директорий, довольно легко автоматизировать каким-нибудь простеньким скриптом. Что я и сделаю на досуге :)

25 января 2008 г.

Цитата: Papadimoulis

Alex Papadimoulis о разработке корпоративного ПО (" Avoiding Development Disasters")
Единственный способ избежать (...) провалов, быть реалистом насчет успеха. И если взглянуть на вещи здраво, то корпоративное ПО обязано проработать минимум 15 лет. Все, что не может продержаться это время, должно считаться неудачным.

Основная причина, по которой заменяется специализированное корпоративное ПО, заключается в том, что его поддержка начинает обходиться слишком дорого. В особенности это касается старинного и очень удачного софта. (...) В подобных случаях замена старой системы, или хотя бы переделка ее основных частей, обычно единственный разумный способ снизить до приемлемого уровня издержки на обслуживание.

Грамотно спроектированная система, созданная на протяжении последних восьми–десяти лет не требует масштабной модернизации.

23 января 2008 г.

Использование OSS в своих разработках

В очередной раз пересортировывал гигабайты статей и заметок, собранных на моем жестком диске, и наткнулся на текстовый файл с некоторыми правилами использования Open Source Software в своих разработках. Думаю, здесь этой заметке самое место.

Когда ваш продукт обязан быть Open Source

- если вы изменяли чужой GPL-код или исправляли в нем ошибки;
- если расширяли функциональность GPL-кода добавляя свой код;
- копировали фрагменты кода из GPL-продуктов в свой код;
- использовали заголовочные файлы из GPL-кода;
- статическая линковка вашего кода с GPL-кодом или LGPL;
- динамическая линковка с GPL-библиотеками или LGPL;


Когда ваш продукт не обязан быть Open Source

- при использовании GCC для компиляции вашего кода;
- при использовании открытых стандартов (HTTP, TCP/IP, SOAP, POSIX и т. д.);
- при взаимодействии вашей программы с OSS с помощью командной строки, пайпов или сокетов;
- при использовании архитектуры ПО, изолированной от OSS.

18 декабря 2007 г.

MDA: ловушка для разработчиков

Основной смысл MDA (Model Driven Architecture) заключается в том, чтобы вся разработка программного обеспечения выполнялась на уровне модели. То есть, грубо говоря, разработчик вносит все изменения в продукт посредством рисования квадратиков, стрелочек и прочих графических элементов. А дальнейшее преобразование из схемы в конкретную реализацию для конкретной платформы должно выполняться некими автоматизированными инструментами практически без вмешательства человека.

В то же время, не секрет, что в подавляющем большинстве команд, занимающихся разработкой ПО, существует серьезная проблема документирования выполняемых изменений.

Частично эта проблема решается путем спекуляции терминами типа "самодокументирующийся код", хотя на самом деле, самодокументирующийся код — это миф. Однако, я признаю, что с помощью жесткого(!) контроля над соблюдением стандартов кодирования, возможно попытаться свести к минимуму проблемы документирования изменений.

При отсутствии документации, получение информации об изменениях в конце концов сводится к сравнению файлов кода предыдущей и новой версии для того, чтобы понять, что именно было изменено. После получения более-менее полного списка изменений кода уже можно попытаться ответить на следующий вопрос — "для чего?"

Теперь вернемся к MDA. Как правило, для моделирования структуры и поведения ПО обычно используется язык UML или подобные ему. На текущий момент существует довольно много инструментов, которые позволяют сгенерировать из UML-модели некий код на каком-нибудь языке программирования. И уже довольно много команд используют эти инструменты для разработки своего ПО. Это, конечно, еще ненастоящий MDA, но суть не в этом.

Суть в том, что при использовании UML-моделей проблема отсутствия документирования изменений никуда не уходит. Но в отличие от текстовых файлов, сравнение двух UML-моделей превращается в нетривиальную задачу.

Да, некоторые UML-инструменты позволяют выполнять сравнение моделей и представлять результат в каком-то читаемом виде. Однако, каждый производитель пытается сделать это своим неповторимым способом, и на сегодняшний день работоспособность, мягко говоря, оставляет желать лучшего. И что, на мой взгляд, более важно, совместимость UML-инструментов различных производителей практически равна нулю. Даже стандарт XMI каждый производитель умудряется трактовать по-своему, хотя сама цель существования стандарта — облегчить импорт/экпорт UML-моделей между инструментами различных производителей.

В результате всего этого, проекты, перешедшие на использование таких инструментов, но не сменившие политику документирования изменений, как правило, погружаются в хаос изменений, потому что выяснить, как выглядела модель три версии назад, становится возможным только визуальным сравнением схем. Обычно внесениями изменений в модель занимается один человек — архитектор, — и поэтому контроль изменений происходит у него в голове. Критический случай — когда этот человек покидает проект. Всё. Конец фильма. Заключительные титры.

Мое мнение. На текущий момент, за отсутствием других средств, поддерживать MDA-проекты в контролируемом состоянии поможет только тщательное документирование изменений, как бы скучно это не звучало. Иного способа я пока не вижу.