Sunday, December 13, 2009

3D ускорение на radeon3200

Включение 3d ускорения на r600 для ubuntu 9.10 Karmic Koala.

Добавляем в дополнительный источник в репозиторий:
  • xorg-edgers. Это позволит установить последнюю версию mesa.
    Версия установленная по умолчанию имеет одну неприятную особенность отсутствует библиотека поддержки r600, хоть версия mesa 7.6 уже содержит поддержку. Но установка полной версии, в моем случае ничего не дала - только отключила поддержку программного рендеренга и выдавала ошибку рендеренга - неправильный порядок пакетов. В новой версии эта проблема решена.
  • и установить новое ядро отсюда:
    2.6.32, для 64 битной платформы нужно установить пакеты:


    • linux-headers-2.6.32-020632-generic_2.6.32-020632_amd64.deb
    • linux-headers-2.6.32-020632_2.6.32-020632_all.deb
    • linux-image-2.6.32-020632-generic_2.6.32-020632_amd64.deb



Как результат 1432.709 FPS в glxgears, и glxinfo:
direct rendering: Yes
OpenGL vendor string: Advanced Micro Devices, Inc.
OpenGL renderer string: Mesa DRI R600 (RS780 9612) 20090101  TCL
OpenGL version string: 1.5 Mesa 7.7-rc2. 


Но пока в quake2 играть с opengl нельзя достаточно чувствительно притормаживает. Но прогресс очевиден:-)

Sunday, November 29, 2009

Иcправление DTD

Некоторые пакеты генерируются документацию через xmlto которая в свою очередь проверяет xml на валидность, иногда там прописаны более новые файлы чем установленные в дистрибутиве, чтобы исправить это мелкое неудобство можно использовать такой скриптик:
list=`grep 'OASIS//DTD DocBook XML V4.4' * -r | sed 's/:/ /' | awk '{print $1}'| grep '.xml'`
for file in $list
do
    sed -i 's/DTD DocBook XML V4.4/DTD DocBook XML V4.2/' $file
    sed -i 's/http\:\/\/www.oasis-open.org\/docbook\/xml\/4.4\/docbookx.dtd/http\:\/\/www.oasis-open.org\/docbook\/xml\/4.2\/docbookx.dtd/' $file
    echo 'fix for '$file'done'
done

Friday, November 27, 2009

symfony мелкие советы


Symfony logo

Создание меню загрузки файлов:

Создаем простое текстовое поле(logo например) для хранения пути файла и в наследнике от автоматически созданной формы в функцию configure дописываем:
$this->widgetSchema['logo'] = new sfWidgetFormInputFileEditable(array(
          'label'     => 'Attachments',
          'file_src'  => '/uploads/'.$this->getObject()->getLogo(),
          'is_image'  => true,
          'edit_mode' => !$this->isNew(),
          'template'  => '
          %input%
          %delete% %delete_label%
          %file%
',
        ));

      $this->validatorSchema['logo'] = new sfValidatorFile(array(
        //если указан путь, то кроме проверки еще и сохраняет,
        //путь относительно uploads
        'path' => sfConfig::get('sf_upload_dir').'/',
        'required' => false,
        'mime_types' => 'web_images'
      ));
      $this->validatorSchema['logo_delete'] = new sfValidatorPass();

создание дополнительной проверки по полю(не стандартной):

Добавлять туда же:
$this->validatorSchema['name'] =  new sfValidatorAnd(
            array(
                new sfValidatorCallback(
                    array('callback'=>
                            array($this->getObject(), 'check')
                    )
                ),
                $this->validatorSchema['name'] /*previous value*/
            )
       );
В модели создаем дополнительную функцию check вида:
public function check($validator, $value) {
       if(haveError($value))
            throw new sfValidatorError($validator, $message);
        }
        return $value;
   }

для проверки более чем одного поля одновременно:

(Приходят все поля после проверки по каждому полю отдельно):
form:
 $this->mergePostValidator( new sfValidatorCallback(
                array('callback'=>
                        array($this->getObject(), 'check')
                )
model:
  public function check($validator, $values) {
       if(haveError($values))
            throw new sfValidatorError($validator, $message);
        }
        return $values;
   }

sfPropelPager

Особенность в sfPropelPager - при подсчете количества страниц он сбрасывает поля группировки - результат может быть больше чем реально существует.

Формирование сложных запросов:

Нахождение имени в нескольких полях:
select * from people where (name like ('%abc%') ) OR (last_name like ('%abc%') );
Выглядит таким образом:
$name = $c->getNewCriterion(PeoplePeer::NAME, '%abc%', Criteria::LIKE);
 $last = $c->getNewCriterion(PeoplePeer::LAST_NAME, '%abc%', Criteria::LIKE);
 $last->addOr($name);
 $c->addOr($last);
Основная особенность нужно объединить сначала части запроса, а уже потом добавлять в основной запрос.

Thursday, October 22, 2009

TCMalloc отличие от стандартного алгоритма

Отличие от стандартного алгоритма управления выделением памяти glibc(ptmalloc2)от tcmalloc(реализация разработчиков google):
  • разделенная между потоками память, у каждого потока собственный пул памяти(уменьшена вероятность взаимоблокировок);
  • все объекты одинакового размера хранятся вместе (позволяет эффективно использовать память в процессе выделения - освобождения памяти).
  • скорость 50 наносекунд против 300 наносекунд (единичное создание удаление объекта (2.8 GHz P4).
Общий принцип работы: при каждом запросе на выделение памяти - память выделяется из общего пула и привязывается к потоку, который эту память запросил, в потоку регистрируется пул под объекты этого размера(класса) размером кратным размеру страницы, в результате следующее выделение будет выполнено в уже привязанной к потоку памяти. Когда пул под объекты определенного класса освобождается он возвращается в основной пул. Для объектов больше определенного размера выделение полностью проводиться в основном пуле без привязке к конкретному потоку (большая вероятность что этот объект будет использован в другом потоке).

Saturday, October 17, 2009

переехал на berlios.de

BerliOS Developer LogoРешил переместить свою тестовую ветку для gtkhtml на berlios.org. Ранее использовал github, но там отсутствует трекер ошибок. При переезде заметил одну неприятную особенность github скрывал имя пользователя в комите использовав вместо него регистрационное имя пользователя(определяется по почтовому ящику). В результате обнаружил, что у меня некоторые комиты по разному подписаны и как следствие решил полностью сбросить репозиторий(полностью реинициализировать).
Так пришлось немного повозиться(проект не отображался через GitWeb), маленькая как это решить: для этого нужно зайти на git.berlios.de и выполнить git init --bare --shared=all /gitroot/(имя проекта)/ После этого нужно отредактировать файлы: description и README.html и создать пустой файл export_ok.

config
Конфигурация генерируются автоматически
[core]
        repositoryformatversion = 0
        filemode = true
        bare = true
        logallrefupdates = true
[gitweb]
 owner = имя пользователя
[hooks]
        mailinglist = 
        announcelist = 
        envelopesender = 
        emailprefix = [Git]
description
описание проекта (генериться атоматически вида Project ghtml at BerliOS - весит сразу под заголовком в GitWeb
export_ok
существование этого файла разрешает просмотр проекта в GitWeb
README.html
описание висит под description
*
остальные элементы стандартны для git реозитория

Thursday, October 8, 2009

Создание простейшего дерева элементов в gtkhtml

Создание простейшего дерева элементов в gtkhtml:
HTMLObject *o,*flow,* table, *cell,*flowcell,*flowtable;
// отступ clue по отношению к левой стороне экрана, используется при отображении коментариям к записям
GByteArray *levels;
//создание текста
o = text_new (e, "АААААА", current_font_style (e), current_color (e));
html_text_set_font_face (HTML_TEXT (o), current_font_face (e));     
levels = g_byte_array_new ();
//создание clue
flow = html_clueflow_new (current_clueflow_style (e), levels, HTML_LIST_TYPE_BLOCKQUOTE, 0, HTML_CLEAR_NONE);
//создание таблицы
table = html_table_new (0, 0, 3, 2, 1);
//Открыть новую строку
html_table_start_row (HTML_TABLE(table));
//создать новую ячейку
cell = html_table_cell_new (0, 0, 1);
levels = g_byte_array_new ();
//создать clue для ячейки в который засунуть текст
flowcell = html_clueflow_new (current_clueflow_style (e), levels, HTML_LIST_TYPE_BLOCKQUOTE, 0, HTML_CLEAR_NONE);
html_clue_append (HTML_CLUE (flowcell), o);
//привязать к ячейке clue
html_clue_append (HTML_CLUE (cell), flowcell);
//привязать ячейку к таблице
html_table_add_cell (HTML_TABLE(table), HTML_TABLE_CELL (cell));
//закрыть строку
html_table_end_row (HTML_TABLE(table));
levels = g_byte_array_new ();
flowtable = html_clueflow_new (current_clueflow_style (e), levels, HTML_LIST_TYPE_BLOCKQUOTE, 0, HTML_CLEAR_NONE);     
//упаковать все друг в друга
html_clue_append (HTML_CLUE (flowtable), table);     
html_clue_append (HTML_CLUE (flow), flowtable);
html_clue_append (HTML_CLUE (e->parser_clue), flow); 
Внутри clue парсера должен обязательно находится еще один clue в внутри которого расположены все остальные элементы. В clue добавляется все иерархия элементов.

Wednesday, October 7, 2009

Скрытие сообщений при компиляции

В новой версии automake доступна возможность скрытия подробностей компиляции, выводятся только ошибки и имя текущего компилируемого файла. Включается через указание в configure.{in,ac}:
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])

Это возможно использовать и в более старых скриптах, в них этот макрос AM_SILENT_RULES не определен и как следствие не используется. Взято от сюда NicerBuilds, более подробно здесь.

Friday, October 2, 2009

Cоздание простейшего форума с тегами

Для реализации категорий для деления по темам - можно указывать в виде тегов - в результате мы получим возможность отображать темы в нескольких категориях и возможность пользователям присоединять к тему сразу к нескольким категориям. Теги подчиненных сообщений берутся из родительской темы + указанные непосредственно пользователем.
Право добавлять сообщения можно только если текущих тегов хватает на какую нибудь категорию, то есть это решает задачу определения возможности добавления элементов присоединённых к теме.
Из этого вытекает возможность на основе appengine хранить псевдоurl для записи(например: время) и только список тегов с автоматической привязкой к каталогизатору через теги.

CSS + gtkhtml

Продолжение поста.
Заменил в своей тестовой ветви парсер на libxml, и с минимальными изменениями заменил использование старой логики на новую. В результате получил полу работоспособную систему с такими проблемами:
  1. текст выводится мимо полей таблицы;
  2. стили применяются не полностью - отсутствие свойств у элементов;
  3. сейчас выдается двойное дерево - без стиля и под ним с стилем.
В общем почти удачно - но нужно переписать код преобразования xml дерева в htmlobject дерево, или хотя бы найти причину вывода мимо элемента.

Wednesday, September 30, 2009

Портируемость С#(ложка дегтя)

Продолжение поста .
Портируемость С# достаточно иллюзорна, да есть реализация под linux, но основа любого приложения не язык на котором она написана, а логика работы и интерфейс общения с внешним миром. Вот в это и есть проблемы приложение под C#(десктоп, про ASP судить не могу) пишется обычно с использованием WinForms который является сугубо пропретарным продуктом. В результате мы получаем приложение которое под Mono не только выглядит по другому (выглядит как белая ворона под Linux, не интегрируется не в один диспетчер окон), имеет другую логику работы и другие глюки фичи(частично это вызвано тем что сделать точно также как Microsoft нельзя: закрыты полные спецификации, но и нежелательно, сразу попадаем под область возможных судебных тяжб, реализуем все заново). В общем это не так страшно - но может привести к большому проценту переписанного кода.

Проблемы лицензии при создании приложений на C#.

Сам фреймворк бесплатен для использования в программах, отчислений за его использование как мне известно не нужно. Разрабатывать можно в SharpDevelop с использованием для создания пакетов установки Wix это покрывает 90% потребностей при разработке. Если нужно студия можно использовать Express версию (для которой есть одно существенное отличие - отсутствие пакетов установки) в сумме это дает полное покрытие по средствам разработки.

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

В общем как временное решение использовать можно - но лучше использовать действительно портируемые языки без возможных изменений лицензирования или хотя-бы использовать Gtk# вместо WinForms.

P.S. :
  • Обратите внимание стили окошек под mono отличаются от подразумеваемых в WinForms (BorderStyle), могут быть проблемы с диалогами они появляются под формочками, а так как на панели задач они не отображаются, может казаться, что приложение наглухо зависло.
  • Может и не нужно никакое портирование попробуйте запустить под wine с установленным .Net(у меня не получилось - приложение просто выходило без ошибок , но с большим количеством warn - но попробовать стоит).

Saturday, September 19, 2009

HAL и монтирование устройств

В последнее время у меня при попытке при попытке монтирования устройств пользователем появлялось сообщение:
org.freedesktop.hal.storage.mount-removable no <-- (action, result)
Лечиться исправление файла /etc/PolicyKit/PolicyKit.conf.

Thursday, September 10, 2009

Неоднозначности Google AppEngine

Мелкие несоответствия в документации Google AppEngine
или мелкие, но неприятные особености.

Загрузка файлов.

  • В документации в примере работы с фотографиями указано, что по умолчанию для параметров загрузки файлов выдается их содержимое. В действительности выдается исходное имя файла, поэтому нужно вместо:
     self.request.get('uploadfile') 
    , использовать
     self.request.params.get('cvs').file.read() 

Кодировка строк.

  • Параметры возвращаемые self.request.get() возвращаются в виде unicode строк;
  • В базу данных тоже нужно сохранять unicode строки;
  • При редиректе нужно использовать для строк содержащих символы не входящие в ASCII выражение вида:
    sub_path = self.request.get('path')
    self.redirect('/forum?path=' + cgi.escape(sub_path.encode('utf-8')))
    , то есть перед выполнением редиректа нужно преобразовать в обычную строку.

Особенности базы данных

  • Пока обнаружил только, что желательно добавлять и удалять маленькими порциями. В первом случае можно наткнуться на ограничение во времени (5 секунд) и нагрузке при обработке запроса. В втором случае удалить более 300 объектов за раз нельзя. Цифры примерные - точные цифры получить не пытался, известно только, что за зараз можно добавить 600 объектов и раза в два меньше объектов удалить.
  • Нагрузки на процессорное время при первом получении результатов (не созданы индексы) может достигать на простых операциях вида: получить первые 25 результатов с сортировкой по дате ключу, может занять до 10 абстрактных секунд(30 секунд реальных) при последующих запросах значения падают на порядок.
  • Добавление 600 записей может использовать до 1% процессорного времени выданного для бесплатного аккаунта.
  • База основана на технологии map/reduce, и не поддерживает команды SQL кроме select. Но удобнее, как по мне, использовать нативные методы order, filter, put, fetch.
  • Можно только сравнивать на равенство больше/меньше и включение(in), операции like и под запросы не поддерживаются.
  • Для реализации операций соответствия признаку (или поиск слов, тегов) можно использовать разбиение исходного предложения на минимальные единицы с последующим сохранением этого списка, как отдельного поля в записи (можно в поле записи сохранять список), а потом при поиске использовать вхождение. Конечно, это немного не то, если нужен like, но вполне быстро срабатывает.
  • Включение работает как пересечение, то есть если есть общие элементы, то возвращается истина.
В общей сумме бесплатного аккаунта вполне хватает для маленького тестового сайтика, чтобы поэкспериментировать. В минимальной поставке идет(без дополнительных телодвижений): Django 0.96(используется как шаблонизатор), поддержка базы данных, memcached и библиотека преобразования изображений.

Monday, September 7, 2009

Уникальные идентификаторы для контента

В принципе можно решать как общую задачу создания уникальных идентификаторов:
  • использовать данные генератора случайных чисел (не удобно так как не возможно четко контролировать результат);
  • использовать псевдослучайные значения, например время, процесс/поток с последующим преобразованием в символьное имя;
  • символьное имя очень полезно - так как позволяет лучше при отладке отличать идентификаторы (числа в любом виде хуже отображают отличия);
, а можно учитывать специфику:
  • имена должны поддерживаться файловой системой(запрет на использование спец символов);
  • максимально короткие эффективные имена(снижение затрат памяти), использование всего пространства допустимых символов,
  • близкие по времени создания и исходному значению имена должны быть максимально случайны по результату - то есть использовать максимально эффективный хеш-функцию - обычно достигается использованием хеш значений от псевдослучайных факторов времени создания, процесса/потока и идентификатора хранилища;
  • решение противоречивой в общем-то задачи:
    • максимального разброса имени контента для сохранения;
    • и возможности по имени определить максимально быстро наиболее вероятное хранилище в распределенной системе можно использовать 2 имени - внутреннее и внешнее;
    • внутреннее должно быть максимально эффективно дробить множество доступных вариантов размещения в локальной системе с разбиением на подкатологи с минимальной вероятностью превышения пределов на одновременное количество вложенных каталогов. Это ограничение возникает как из-за временных затрат при работе с большими каталогами, вытекающее иногда в невозможность добавления данных в каталог, и удобства конечного владельца системы, хоть и редко, но удобнее ходить по маленьким каталогам. Также при максимальном разбросе можно разместить часть каталогов на другом разделе и реализовать дешёвое подобие RAID.
    • внешнее - не противоречить правилам для протокола доступа, например не содержать спец символов специфичных для http и html разметки. Позволять легко определить максимально вероятное место расположения в распределенной системе для быстрого выявления не корректно отвечающей части, также при дублировании на другие ресурсы - можно легко удалить данные не специфичные узлу при окончании ресурсов.
Очень желательно, чтобы в внутреннем имени присутствовали хеш функция от размера данных (можно просто выделение остатка от деления на какое-то число с получением base64 + как подкаталог base64 от полного размера) и его контента (для снижение количества коллизий лучше сразу два хеш значения от контента). В результате можно быстро найти подобный контент найдя подобную запись в списке преобразований внутреннее имя <-> внешнее имя или, что должно работать гораздо быстрее при большом количестве разнообразного контента, присутствия такого каталога на диске. В дальнейшем можно проверить его содержимого на совпадение с добавляемыми данными. Как результат мы не будем забивать хранилище дубликатами и при желании сможем сообщать, что пользователь пытается снизить энтропию вселенной.

Tuesday, August 18, 2009

Оптимизация размера картинок

Для оптимизации размера фотографий без потери качества и сохранением размеров можно использовать jpegoptim - на маленьких фотографиях получаеться около 60% уменьшения занимаемого под фото места, для больших фотографий полученых без преобразований - экономия около 1%.

Например:
find . -name data-\* -exec jpegoptim --strip-all '{}' \;
Более подробно можно посмотреть рекомендации google или воспользоваться их плагином для анализа произодительности сайтов (достаточное большое количество проверяемых параметров, не только размер фотографий).

P.S.:
  • Примерно техже результов можно добиться используя jpegtran -optimize, размер файла в результате получаеться один и тотже.
  • Для png можно использовать optipng:
    find . -name \*.png -exec optipng -o7 '{}' \;

Friday, August 14, 2009

Рейтинг запросов

Маленькая последовательность команд чтобы узнать встречаемость строки в файле:

cat result.log |sort > sorted.log
uniq -c sorted.log |sort -n -r |more


Можно использовать чтобы узнать наиболее популярные запросы к серверу.

Wednesday, July 29, 2009

Добавление поддержки CSS в GtkHtml

В общем идея:
  • сейчас в gtkhtml - используется некое подобие SAX парсера, на основе которого сложно создать что-то подобное поддержке CSS. Конечно можно, так как все таки дерево элементов существует, но долго и есть другой вариант;
  • нужно заменить парсер на использование libxml;
  • заменить рендеринг на использующий XmlTree
  • использовать рендеринг в два прохода:
    • первичный рендеринг только html без css c созданием запросов на css
    • вторичный при доступности css применять его к дереву и вызывать перерисовку;
  • в теории мы получим немного более медленный рендеринг - запускающийся только после получения полного дерева, сейчас редеринг идет в параллель с парсингом.
Сейчас реализовал только замену парсера(мой fork на github) и тестовые приложения для проверки применения css. Для парсинга используется libcroco. За основу тестовых приложений взят код - позволяющий применить css к xml c использованием пространств имен.

Tuesday, July 28, 2009

Толстенный клиент

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

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

Основные характеристики:
  • полностью написан на C#;
  • есть возможность конструировать и редактировать отображения внутри приложения;
  • протокол взаимодействия полностью основан на передача XML через HTTP;
  • может при запуске после регистрации полностью обновить справочники и загрузить новую версию самого себя и предложить переустановить;
  • есть возможность удаленного подключения к компоненту связи с сервером и просмотра рабочего стола пользователя(удаленное управления и debug), позволяет разобраться с проблемами клиента, если он в локальной сети, работает только с разрешения пользователя(пункт меню в трее);
  • полностью динамическая генерация визуальных форм на основе xml.

Подсистема отображения

  1. Общается с подсистемой связи с сервером и хранения словарей через протокол описанный в За и Против URI-based interface.
  2. общие принципы описаны здесь.

Система разбита не несколько частей:
  1. логика работы с динамическим интерфейсом, позволяет сконструировать компоненты на основе их описания в xml (сериализованного в объекты, но более простые чем полное описание компонента Windows Forms c некоторыми дополнительными свойствами);
  2. класс простейшей формы - Конструктор принимает ссылку на уже загруженные описания всех форм (словарь визуальных элементов) и ссылку на компонент связи с сервером (тот который имеет интерфейс на основе URI), содержит свойство текущая отображенная форма, позволяет узнать и изменить текущую отображенную форму. Также содержит внутренние виртуальные функции вызываемые при смене отображения, возврата делегата для события и получения списка для компонента с определенным тегом. Общий принцип работы этого компонента: при получении события о смене формы, получить описание новой формы, представляет собой список мета-компонент и их позиций с описанием расположения на форме. Все компоненты которые отсутствуют на новой форме делаются скрытыми. Мета-компоненты (содержат простые компоненты: кнопочки, поля для ввода) отсутствующие на форме создаются - через вызов виртуальной функции которая должна вернуть этот компонент (в базовом классе она ищет описание компонента в словаре визуальных элементов), в этой функции можно добавлять нестандартные компоненты, которые невозможно описать, как простейший элемент или реализовать привязку начальных обработчиков к элементам. При создании нового элемента ему привязывается обработчик полученный из виртуальной функции, которую должны все потомки перегрузить, чтобы обрабатывать специфическую для тега(идентификатора элемента) логику. (К тегу привязывает делегат вызываемый по событию смены содержимого элемента). Сбрасывается привязанный контент в элементе - вызывается виртуальная функция, которая возвращает список по умолчанию для элемента, который устанавливается (все элементы имеют свойство список по содержимого). Затем вызывается виртуальная функция для сообщения всем потомкам этого класса о том, что сменился вид отображения.
  3. Наследники предыдущего - реализуют вся логику реакции на смены отображения и содержимого. При смене содержимого элемента вызывается делегат в котором выполняется специфическая для данного элемента(тега) логика, например перегрузить содержимое элемента, так как связь с базой осуществляется через URI, то формируется запрос содержащий для простой логики тег изменившегося элемента и зависимые от него с значениями выбранными пользователем и передается на обработку которая возвращает список который сразу можно присвоить элементу. Для некоторых сложных элементов с циклическими зависимостями в классах есть булевские переменные состояния которые блокируют циклический вызов обработчиков. Основная сложность для простых элементов - для специфичного тега всегда одна логика(один обработчик) - и как следствие все одинаковые элементы всегда работают одинаково, и нужно помнить, что для специфичной логики лучше создавать новый тег. (Баги в этой логике - на новой форме с элементами у которых совпал тег с прежним значением(например: марка) будет вести себя по старой логике и сбрасывать значение модели - и можно долго искать причину сброса модели. В общем нужно четко следить за соответствием предполагаемой сущности и тегом элемента к которому привязываешься. И в логике обработке события быть готовым, что какого-то элемента не окажется.) Для упрощения отображения сущностей, каждый объект сущности имеет функции устанавливающие значение элементов сущности в зависимости от тега, можно пройтись по списку отображенных тегов на форме и получить значение компонента из сущности по тегу компонента, в этих функциях можно также преобразовывать значения элементов и тегов(если значение не текстовое или не четко преобразуется в элемент-значение). Для компонентов логику которых сложно отобразить через событие смены содержимого - список (например: таблица) - можно при смене отображения добавлять нужные обработчики и удалять лишние. (Эти элементы добавляются через перегрузку виртуальной функции поиска мета-компонента). При удалении элемента(случается при превышении количества скрытых элементов) - отвязываются все обработчики(опять таки через виртуальные функции - можно при желании перегрузить). Также остался атавизм - описание мета-компонента через UserControl, для таких форм существует возможность указать еще и отображение внутри элемента - ранее использовалось для сложных компонентов с различным расположением элементов в зависимости от формы, сейчас почти негде не используется, но возможность добавления таких мета-компонентов есть. Основной их недостаток их невозможно отредактировать внутри программы, нужно перекомпилироать исходники;
  4. Форма - содержит одного из наследников выше указанного класса. Должна содержать 2 основных обработчика - привязка делегата для выполнения команды выполнить в основной программе описанной через URI и функцию вызова команды в элементе отображения выполнить в починенном (перегружаемая функция в предыдущем классе);
  5. Программа - должна создать(загрузить) описание форм и связь с сервером и создать контролер окон. В нем вызвать команду создать одно окно с формой по умолчанию полученное из компонента связи сервером. Также есть простейшее меню в system tray - для простейших команд, можно передать различные команды компонентам: скрыть все окна, отключиться от сервера, разрешить удаленное управление программой и обновить справочники(внутреннюю базу);
  6. Контролер окон - позволяет создать окно выполнив команду выполнить в основной программе передав в окно через вызовов выполнить в починенном команду, закрыть все окна с определенным отображением(реализовано через вызовы в починенных окнах - закройся, если ты...), закрыть окно - обычно подчинеенное окно просит систему об этом.
Логика работы обработки взаимосвязей, похожа на Декоратор, на данный момент в основном завязана на использование WindowsForms, так как иногда обращается к элементам и событиям не через интерфейс, а непосредственно.

Связь с сервером:
  • Содержит описание всех сущностей;
  • отвечает за сериализацию и передачу объектов на сервер;
  • позволяет патчить XML(нужно при частичном обновлении справочников, позволяет избежать копирования всех сущностей перед наложением изменений, чтобы откатиться назад);
  • также выполняет всю бизнес логику общения с сервером;
  • может выполнять запросы синхронно и асинхронно(сейчас используется первый метод). При асинхронном методе возвращается номер сообщения по которому можно узнать результат и можно добавлять делегаты для реакции на завершение обработки;
  • реализует процедуру регистрации на сервере;
  • реализует процедуру выборки сущности из словаря (хранилища сущностей) по нечетким правилам(подобие SELECT) и кэширование результатов.
Сущности хранятся в иерархиях(выше обозначены как словари, реализую клиентскую копию базы данных), Общая структура обработки реализована через перекрытие виртуальных функций с добавление обработки новых типов запросов(Подобие шаблона Цепочка обязанностей).

Вот в общем-то и вся логика:-)

Wednesday, July 8, 2009

Концерты c The Gardner Museum

Концерты классической музыки с сайта The Gardner Museum. Вся музыка выложена под лицензией Creative Commons Music Sharing License(Разрешено свободное распространение с сохранением авторства).

Мелкие заметки:
  • Простенькая команде получения всех файлов где встречается /revision/, пропустив каталог .svn:
    grep "/revision/" -r . | grep -v -e "\.svn" | sed 's/:/\t/g'| awk '{ print $1 }' |sort -u

Friday, July 3, 2009

Поддержка entity в XML Parser(PHP)

По-умолчанию xml parser не поддерживает символические имена для спец символов(entity), при парсинге XML в котором встречаются такие символы - они отбрасываются. Для того чтобы этого избежать нужно заменить все символические имена на цифровые коды, которые он нормально обрабатывает.

Примерчик:
...

/* replace some named entity
amp|#38
quot|#34
gt|#62
nbsp|#160
lt|#60
iexcl|#161
cent|#162
pound|#163
copy|#169
*/
function replace_entity($document)
{
$search = array (
"'&'",
"'"'",
"'>'",
"' '",
"'<'",
"'¡'",
"'¢'",
"'£'",
"'©'"
);
$replace = array (
"&",
""",
">",
" ",
"<",
"¡",
"¢",
"£",
"©"
);
return preg_replace($search, $replace, $document);
}

function parse($data, $logs)
{
$this->logfile= $logs;
$this->parser = xml_parser_create('UTF-8');
xml_set_object($this->parser, $this);
xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false);
xml_set_element_handler($this->parser, 'tag_open', 'tag_close');
xml_set_character_data_handler($this->parser, 'cdata');
$this->log_all($data."\n");
if (!xml_parse($this->parser, $this->replace_entity($data)))
{
$this->error_code = xml_get_error_code($this->parser);
$this->error_string = xml_error_string($this->error_code);
$this->current_line = xml_get_current_line_number($this->parser);
$this->current_column = xml_get_current_column_number($this->parser);
}
xml_parser_free($this->parser);
}
...


P. S. Полгода - год назад это было так, сейчас может быть уже поддерживает, но решение вполне универсально, так как помогает добавить поддержку новых спецсимволов, если это потребуется.

Wednesday, June 24, 2009

Параметры мобильного телефона используя wurfl

Для того чтобы определить параметры мобильного телефона можно использовать 2 пути:
  • определение свойств на основе мобильного профайла возвращенного в заголовках запроса (CC/PP);
  • определение на основе данных переданных в качестве UserAgent.
Первый случай дает возможность сделав несколько запросов на профайл и изменения в нем по протоколу CC/PP получить полные официальные данные об устройстве, но этот случай имеет несколько недостатков, указанный в профайле ресурс может быть недоступен и мы терем время на эти запросы.

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

Принцип работы с базой wurfl:
  1. найти максимально похоже имя user agent по xpath пути:"/wurfl/devices/device/@user_agent. (В первой версии искалось полное соответствие.)
  2. определить идентификатор этого описания: "/wurfl/devices/device[@user_agent='UserAgent']/@id"
  3. попытаться получить описание нужного нам свойства используя: "/wurfl/devices/device[@id='идентификатор']/group[@id='markup']/capability[@name='html_wi_oma_xhtmlmp_1_0']/@value" . markup - имя группы свойств, html_wi_oma_xhtmlmp_1_0 - свойство.
  4. Если получить не удалось получаем идентификатор подобного устройства: "/wurfl/devices/device[@id='идентификатор']/@fall_back".
  5. Если получить описание устройства не удается через fall_back мы всегда попадем на описание generic устройства, у которого значение всех свойств равно false.
Ссылки:

Wednesday, June 17, 2009

Патчи к gtkhtml

Описание моих патчей к gtkhtml:
  • Get rid of deprecated libgnomecanvas Удалена зависимость от libgnomecanvas (полностью удален bonobo компонент, не устанавливаемый по умолчанию в систему) и переписан код конструирования панелей использовавших bonobo (теперь код использует только функции gtk+). Доступно в версии 3.26.
  • Удаление использования libgnome Замена на использование GtkBuilder для генерации меню и автоматический поиск каталога с тестами. Доступно в версии 3.27.3.
  • Поддержка data URI Добавляет поддержку datauri, также:
  1. исправляет код формирования url на основе относительного и базового url страницы с которой вызывается запрос (теперь в);
  2. добавлена автоматическая замена содержимого при получении, если при вызове получения данных ожидается html, но была получена картинка, автоматически формируется html с ссылкой на эту картинку;
  3. добавлено два вызова для gtkhtmlstream установки базовой страницы и content type для конкретного потока, ранее эти параметры устанавливались для всего документа и он мог отобразиться не корректно.
  • Использование GtkBuilder вместо libglade. Заменяет устаревший(deprecated) libglade на более универсальный код использующий GtkBuilder. (3.07.2009 - применено в основной ветви, будет доступно в следующем релизе).
  • Поддержка cookies - включает поддержку cookies при установленном libsoup версии > 2.26, включается только в тестовом приложении не устанавливаемом в систему. Доступно в версии 3.27.3.
P.S.: поддержка этих изменений в skybrowser я не добавил.
  1. Код со всеми моими изменениями.
  2. Официальный репозиторий.

Friday, June 5, 2009

timeouts in merle

Добавил в мою ветвь кода merle возможность работы с timeout.
  1. В функцию connect новый параметр Options, передаваемый в функцию gen_server:start_link . Этот параметр позволяет установить timeout и включить режим отладки:
  • Options ::= [{timeout, Timeout} | {debug, [Flag]}]
  • Flag ::= trace | log | {logfile, File} | statistics | debug(debug == log && statistics)
  1. В getkeylist(Key, Timeout) добавлен параметр максимального ожидания ответа, предыдущий вариант тоже остался, его поведения также можно эмулировать - указав значение 0
  2. Функция setlist теперь ожидает установки только время указанное как значение времени кеширования (гарантия что. если время кэширования было превышено, сохранение не произойдет). При значении 0 поведение сохраняется, как было до изменения.
Эти изменения позволяют снизить количество одновреммено запущеных запросов к merle во время повышенной нагрузки на систему временем timeout.

Monday, May 25, 2009

Изменил мою ветвь с Merle.

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

Чтобы отключить использование gen_server2 - нужно только переименовать все gen_server2 -> gen_server.

Friday, May 22, 2009

Повышение надежности системы

Повышение надежности предыдущей схемы (используя nginx).

В предыдущей схему существует один потенциально ненадежный элемент backend сервер. При его сбое система через некоторое время утрачивает возможность отрабатывать запросы, так как время кэширования на memcahed заканчивается и все запросы начинают идти на этот сервер. В результате система не может выдать никакого контента пока он не будет перезапущен. Чтобы этого избежать можно дать возможность frontend возвращать данные с просроченным сроком давности, реализовать это на уровне memcached усложняет способ работы с ним:
  • по умолчанию, если закончилось время хранения контент удаляется;
  • и при смене поведения усложняется алгоритм работы с ним из frontend.
В результате наиболее простым методом, если используется в качестве frontend nginx, можно разрешить ему при сбоях использовать собственный кэш (опция proxy_cache_use_stale). Пример:

proxy_cache_path /var/tmp/nginx/cache levels=1:2 keys_zone=one:10m inactive=20m max_size=500m;

server {
listen 80;

location / {
if ( $args = "" ){

set $memcached_key "$uri";
memcached_pass 127.0.0.1:11211;
error_page 404 502 503 504 = @backend;
add_header "Content-Type" "text/html; charset=UTF-8";
add_header "Cache-Control" "max-age=15";
}

proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_buffers 8 32k;

proxy_cache_key "$scheme://$host$uri$is_args$args:";
proxy_cache one;
proxy_cache_min_uses 1;
proxy_cache_valid 200 30s;
proxy_cache_use_stale error timeout http_500 http_502 http_503;

}

location @backend {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_buffers 8 32k;

proxy_cache_key "$scheme://$host$uri$is_args$args:";
proxy_cache one;
proxy_cache_min_uses 1;
proxy_cache_valid 200 30s;
proxy_cache_use_stale error timeout http_500 http_502 http_503;
}
...............
В данном случае frontend не обязательно слабое звено - его можно продублировать с меньшими затратами по сравнению с дублированием backend + slaveDB.

Thursday, May 21, 2009

Код корректного перезапуска merle

При использование кода связи с memcached возможен случай когда соединение с memcached по каким-то причинам потеряно, например: рестарт memcached.
  • В данном случае возможен случай когда выполнить вызов не удается, но пересоединение не получается (закрытие (disconnect) сообщает - что закрывать нечего, а открытие (connect) сообщает - что уже есть соединение). В данном случае нужно убить это псевдо-рабочее соединение и запустить наново.
  • Другой случай когда соединяться не с кем - тогда метод connect просто выходит, не вызывая исключение, что очень плохо, так как не понятно, что произошло с системой - чтобы отловить эту ситуацию нужно разрешить посылку сигнала о завершении потока и ожидать получение этого сообщения.
Пример кода:

%%use with restart
try_with_restart(LU, F) ->
try
F()
catch
Sub : Reson ->
debug_log(LU, "Start procedure restart connect:~p~n",[{Sub,erlang:get_stacktrace(),Reson}]),
spawn(fun() ->
process_flag(trap_exit, true),
Connect = merle:connect(?MEMCACHED_HOST,?MEMCACHED_PORT),
case Connect of
{error, {already_started, Client}} ->
debug_log(LU, "try kill client status:~p~n",[Connect]),
exit(Client,kill);
Value ->
debug_log(LU, "Some other error:~p~n",[Value])
end,
receive
{'EXIT', Pid, Why} ->
debug_log(LU, "Me killed~p~n", [{Pid, Why}])
end
end),
undefined
end.

Примечание:
  1. F - функция обращающаяся к merle;
  2. debug_log() - вывод в логи отладочной информации.

Sunday, May 17, 2009

merle совместимый с nginx

Выложил коды проектов используемых мною с некоторыми изменениях в них.

Изменения в коде merle (memcached erlang interface) нацелено на совместимость с другими языками программирования, в частности возможность получения сохраненного контента из nginx.

Стандартный код использует binary_to_term для сохранения контента в результате при попытке получить контент из memcahed из других программ, ожидающих возврат обычной строки, в начале контента невозможно так как появляются служебные данные erlang.

В код добавлены функции сохранения как строка merle:setlist( url, time, content), и получения контента в виде строки merle:getkeylist (url). Также удалено использование нестандартного gen_server. Удален только для упрощения поддержки кода, так как усовершенствования по очередям сообщений в проекте были не нужны.

В результате этих изменений удалось организовать систему когда frontend server(использовался nginx) мог не запрашивая backend получить сразу данные из кэша, также получилось использовать как общих кэш и для php.

Остальные функции постарался оставить без изменений их поведения и формата сохраняемых ими данных(term_to_binary), измения заключаются в переносе функций преобразования term_to_binary(getkey) и binary_to_list (getkeylist) из вызовов gen_server.

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

Friday, May 15, 2009

Я тебя породил, я тебя и убью!

Я тебя породил, я тебя и убью!
Н. Гоголь "Тарас Бульба".
Старые ошибки или почему иногда, большое удобство плохо.

Если человеку дают очень удобное и красивое средство он теряет нюх и начинает ошибаться, конечно, потом он находит свои ошибки, и не считает, что все что блестит золото , но все таки. Как пример можно привести мелкие ошибки в решении которых я когдато участвовал (примеры относяться к .Net).

Как известно эта платформа обладает очень хорошим свойством как сборщик мусора, вот понимание его работы и вызвало ошибку: когда теряется ссылка на объект через некоторое время этот объект будет удален, но бывают некоторые особенности. Иногда остаются скрытые ссылки на этот объект, и как следствие он остается на вечно и скрытно жрет ресурсы: например окошко с созданным в нем таймером по которому в этот окошке , что то изменяется, вызывает такую особенность: система обрабатывает это окошко по особенному. В системе есть команда: 'выполнять по таймеру событие' и вся эта система, когда вы думаете уже мертва, начинает жить по собственным правилам, и не давать сборщику мусора удалить себя, так как есть круговая зависимость окошко->таймер->система и через некоторое время все ресурсы съедены и система уже не может создавать новые компоненты. Но и умереть она не может, так как не понятно, что происходит и нужно сообщить разработчикам или умереть, одно выполнить не можем, а другое не красиво, а решение простое: когда вам что то уже не нужно разрывайте и останавливаете связи сами, а не ждите, пока это сделает система, так хоть немного дольше проживете(в смысле программы).

Я тебя породил я тебя и убью... всегда сохраняйте взаимосвязь внутри приложения родитель -> потомок. Так как поток созданный в программе обладающей выше указанным багом некогда не умрет при исключении в основном потоке и программа постепенно 'захватит мир или моск'. В данном случае поток выполняющийся в программе был создан, как отдельный процесс не связанный с основным, в деструкторе основного потока он не уничтожался и потихоньку держал ресурсы всей программы. Решение было простое создавать не новый поток, а вызывать асинхронно делегат, который заменил этот поток.

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

Wednesday, May 6, 2009

Маленькие выводы по кэшированию

Или временное завершение этой темы...
  • Проверить качество кэширования можно используя эту ссылку, на этом сайте также даются рекомендации по сжатию контента и приводятся данные относительно времени загрузки через различные каналы;
  • статья относительно указания параметров кэширования на примере использования Apache, даются достаточно универсальные методы указания параметров кэширования;
  • эталонное описание как должно работать кэширование rfc2616;
  • идеи относительно времени кэширования, построения гибкой версии указания этого времени и перенаправления во время обработки для увеличения степени валидности кэша;
  • и как заключение пример архитектуры.

Friday, April 17, 2009

Пример архитектуры кэширования(Caching Architecture)

Общую структуру кэширования и получения контента можно представить в таком виде:

  • Красным цветом отображены наиболее критичные части без которых система не может функционировать в нормальном состоянии.
  • Синим цветом соответственно представлены все части отвечающие за стабильное(нормальное) состояние сервиса.
  • Зеленым цветом - наиболее производительный вариант работы.
Каждый овал может представлять как отдельный сервер, так и несколько овалов может быть размещено на одном сервере, кроме вариантов дублирования веток обработки.

Контент любого сервиса можно разделить на 3 составляющие: статический контент, медиа контент и динамический контент.

Статический контент


Статический контент - изменяется редко, и создается разработчиками ресурса и изменяется только при смене версии ресурса. И как следствие его желательно отдавать легковесным сервером с максимальной производительностью и поддержкой системных вызовов(writev, sendfile). Для запросов на эти файлы вебсервер должен выдавать последнюю дату изменения, при последующих запросах сервер выдает Not Changed.

Медиафайлы


Медиафайлы - политика кэширования подобна, с некоторыми уточнениями: файл может изменяться только по запросу пользователя, на данный момент используется только процедуры добавления и удаления файлов. И политика заключается в том чтобы снизить количество запросов единичного пользователя для получения файлов.

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

Методы кэширования: при приходе запроса на получение файла без заголовков(HTTP_IF_MODIFIED_SINCE) - выдать файл с указанием времени создания(Last-Modified) для локального файла или указания даты формирования ответа для файла полученного с другого зеркала, и указания для дополнительных заголовков (Cache-Control: max-age=28800). При приходе заголовка (HTTP_IF_MODIFIED_SINCE), если файл с того времени не обновился выдается что контент не изменился(HTTP/1.x 304 Not Modified). Это позволяет дать возможность закешировать файл на месяц и более - причем первый месяц браузер не будет делать никаких запросов к серверу. Желательно иметь одно запасное синхронизируемое зеркало, чтобы при проблемах с основным на него можно было перевести запросы.

Динамические страницы


Запрос поступает на frontend сервер, для всех запросов которые могут попасть под кэшируемость, который запрашивает этот файл с Memcached. Если файл там присутствует, то он возвращается с указанием, что его можно в течении 15 секунд не запрашивать - это блокирует повторных запрос от браузера на это контент на 15 секунд (Cache-Control: max-age=15). Если контент в кэше отсутствует, то выполняется запрос к backend, который повторно запрашивает контент от кеша, так как исходный url может не совпадать с результирующим url идущим на обработку. При отсутствии данных в кеше или не кэшируемости запроса запрос перенаправляется в SlaveDb. Возвращенный от туда запрос при возможности кэширования сохраняется в кэше с указанием времени кэширования определяемого для этого url, для примера: 30 секунд главная страница, 90 - страницы второго уровня. В ответе для кэшируемых станиц указывается в значении max-age тоже время, что указывалось для времени хранения. Как следствие мы получаем, что все станицы кэшироватся в браузере пользователя первым подавшим запрос на полное время, все последующие до окончания времени кэширования на 15 секунд.

Вторая ветвь(зеленая) нужна для снижения нагрузки на slave, так как на всех slave должен быть одинаковый контент, то при разнесении запросов на несколько сервером мы должны получить ~2 увеличение скорости обработки. Memcached - один, так как при использовании 2 экземпляров для каждого slave приведет к дублированию контента на каждом.

Пример кода


function exitIfNotModifiedSince($last_modified) {
if(@array_key_exists("HTTP_IF_MODIFIED_SINCE",$_SERVER)) {
$if_modified_since=@strtotime(@preg_replace('/;.*$/','',$_SERVER["HTTP_IF_MODIFIED_SINCE"]));
if($if_modified_since >= $last_modified) {
header("HTTP/1.x 304 Not Modified");
header("Last-Modified: ".date("D, j M Y G:i:s T", $last_modified));
exit();
}
}
}


function dump_foto_content($photo) {
exitIfNotModifiedSince(strtotime("now")- 100000);
// send the right headers
header("Content-Type: ".$photo["mime"]);
header("Content-Length: ".strlen($photo["photo_data"]));
header("Last-Modified: ".date("D, j M Y G:i:s T"));
header("Cache-Control: max-age=28800");
echo $photo["photo_data"];
exit;
}


P.S.: Для разрешения кэширования на промежуточных серверах нужно добавить параметр:
header("Cache-control: public");

Thursday, April 9, 2009

Be Linux

Наиболее понравившийся мне видеоролик к конкурсу We're Linux



И ролик BBC использовавшийся при создании этого ролика:

Sunday, April 5, 2009

Оптимизация времени кэширования

  1. Оптимизация времени кэширования под количество запросов, обычно пользователей загрузивших страницу можно разделить на 2 группы: зашедших на страницу с другой станицы или непосредственно на страницу и другую группу обновляющих станицу в ожидании изменений. Первой группе не столь важно сколько кэшируеться страница им более важна скорость получения страницы и можно использовать максимальное допустимое время кэширования. Для второй же группы нужно кэшировать минимальное время - их можно определить по referer и тому что у них будет стоять заголовок "if not modifined since" - используя эти особенности можно при приходе первого типа запроса использовать удвоенное время кэширования, а для вторых одинарное - в результате вторая группа получает всегда обновленной контент, а первая скорость(и при совпадении запросов обоих групп еще и более быстрое обновлении, но они этого не замечают так видят страницу первый раз).
  2. При появлении запроса с заголовками не изменилось ли что то ("if not modifined since"), если еще существует кэш этого запроса можно сравнивать указанный в запросе хеш с возвращенным после обработки и стараться возвратить не изменилось так как это позволит быстрее освободить ресурсы.
  3. При варианте, когда контент уже существует на диске и его расположение высчитывается скриптом, более разумно использовать не хеш, а дату изменения и стараться, если это возможно не выдавать, а перенаправлять пользователя на это контент, в результате мы получаем минимальное потребление памяти и скорость отработки близкую к отображению статики. Перенаправлять желательно даже, если результат находиться на другом ресурсе, меньше посредников быстрее ресурс освободиться.
  4. Очень желательно кэшировать результаты запросов не на самодельных скриптах кэширования, а использовать более распостраннённые системы кэширования написаные на компилируемых языках - так больше вероятность, что выдача будет максимально быстро отдаваться и не нужно тратить время на создание велосипеда и его тестирование. Как следствие лучше перенаправить на кэш или файловую систему, чем генерировать контент скриптом - так больше вероятность, что будет использована максимально эффективная стратегия отдачи контента.

Friday, April 3, 2009

Недостаток в коде модуля балансировки

Обнаружилась маленький недостаток в модуле балансировки - не контролируется количество поставленных в ожидание освобождения ресурсов (очередь на основе семафора) - при большом превышении количества запросов по отношению к ограничению - запрос может быть выполнен даже после того как клиент отсоединиться от сервера - время ожидание ничем не ограничено. То есть существует вероятность: что модуль при очень большом количестве запросов будет давать запросы ответы на которые пользователь уже не ждет. Этот недостаток не должен сильно повлиять не производительность системы - так как на DOS атаку он не был рассчитан, он предназначен только для выравнивания и снижения единовремменой пиковой нагрузки.

Friday, March 27, 2009

Nothing is always absolutely so

Сменил заголовок своего блога на утверждение высказанное писателем-фантастом Theodore_Sturgeon, думаю оно максимально выразит идею, что все записаное в блоге является только моим субъективным мнением и на истину в полной мере не претендует.

Простейшая реализация OpenID

Документация изначально написана для внутреннего пользования при создании модуля работы с OpenId. Модуль поддерживает только часть функционала openid (только регистрацию по первой версии OpenId). Результат можно посмотреть в здесь. Поддерживает только идентификаторы вида http://имя пользователя с именем провайдера - на этот идентификатор все openid провайдеры должны возвращать свое описание в заголовках страницы. Решил выложить, так как за пол года не появилось более полной реализации по сравнению с той, что есть у меня.

Общие шаги(OpenID *)

Полученному идентификатору запрашиваем страницу в ней ищем значения openid2.provider(OpenId 2.0), openid.delegate (OpenId 1.0 в общем случае должен совпадать с введенным именем пользователя), openid2.local_id(OpenId 2.0 в общем случае должен совпадать с введенным именем пользователя), openid.server (OpenId 1.0) и X-XRDS-Location (OpenId 2.0).

Например:
  • <link rel="openid2.provider" href="http://openid.yandex.ru/server/" />
  • <link rel="openid2.local_id" href="http://openid.yandex.ru/имя пользователя/" />
  • <link rel="openid.server" href="http://openid.yandex.ru/server/" />
  • <meta http-equiv="X-XRDS-Location" content="http://openid.yandex.ru/имя пользователя/yadis/" />
Для OpenId 1.0 Перенаправление пользователя для проверки его валидности

Сделать запрос к серверу (openid.server) на проверку пользователя (openid2.local_id) режим checkid_setup - так как это простейший режим и требующий минимальной реализации с нашей стороны:
  1. [openid.server]?
  2. openid.mode=checkid_setup&
  3. openid.identity=[openid2.local_id]&
  4. openid.return_to=[Путь на который нужно вернуться]&
  5. openid.trust_root=[Проверяем доверие серверу(доменное имя)]&
  6. openid.claimed_id=[openid2.local_id]
Для OpenId 2.0 Получение дополнительной информации

По полученному X-XRDS-Location получаем Yadis документ:
<?xml version="1.0" encoding="UTF-8" ?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="20">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<Type>http://openid.net/signon/1.0</Type>
<URI>http://openid.yandex.ru/server/</URI>
<LocalID>http://openid.yandex.ru/имя пользователя/</LocalID>
</Service>
</XRD>
</xrds:XRDS>

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

Возвращаемый сайтом ответ через обратное переполнение
В результате проверки на сайт мы должны вернуться в любом случае с такими параметрами:
  1. Удачный:
  • [Вернутся на такой url]?
  • openid.assoc_handle=[Тип подписи]&
  • openid.identity=[openid2.local_id]&
  • openid.mode=id_res& (нам доверяют)
  • openid.op_endpoint=[openid.server]&
  • openid.response_nonce=[временной штамп]&
  • openid.return_to=[Вернутся на такой url]&
  • openid.sig=&(подпись)
  • openid.signed=(от каких полей подпись)
  1. Не удачный
  • [Вернутся на такой url]?
  • openid.mode=cancel (вам не доверяют)
Проверка подписи
Для яндекса проверка не сработала, когда я тестировал модуль, возможно я что-то делаю не так. Стандартный код проверки OpenId тоже не делал эту проверку.

После получения мы должны проверить, а те ли нам ответили и запросить с сервера дополнительные сведения:
  • [openid.server]?
  • openid.mode=check_authentication&
  • openid.assoc_handle=[Значение openid.assoc_handle из предыдущего запроса]&
  • openid.sig=[Значение openid.sig из предыдущего запроса]&
  • openid.signed=[Значение openid.signed из предыдущего запроса]&
  • openid.identity=[Значение openid.identity из предыдущего запроса]&
  • openid.return_to=[Значение openid.return_to из предыдущего запроса]

Saturday, March 21, 2009

Продолжая тему кэширования: идеи относительно ответов

Еще пара идей:
  • Если пришел запрос на проверку и у нас есть закэшированный вариант(и время кэширования его истекло) ответа можно сравнить результат ответа на запрос с кэшем, если контент не изменился - дату кэширования оставить тем же(дата создания) - но увеличить время критического и валидного кэширования - ответ не изменилось гораздо предпочтителен по сравнению с полным результатом.
  • Станицу ответа - желательно разбивать на несколько частей - чтобы они грузились через отдельные запросы, тогда можно для разных частей указывать разные политики кэширования и кэшировать более маленькие кусочки.
  • Уменьшить количество запросов дающих одинаковый результат с разными url, например a=b/c=d === c=d/a=b. Если избежать их по каким то причинам нельзя - нужно всегда на уровне сервера перенаправлять на наиболее популярный (по каким то причинам) вариант запроса.

Sunday, March 15, 2009

Strange proxy или неправильное кэширование.

Любое кэширование это спор между выдачей реальных данных и выдачей немного устаревших данных. Если выдавать реальные данные то мы тратим ресурсы на получение результата, который уже был ранее посчитан и выдачей немного старых данных. Пользователь получит данные созданные 5 минут назад или полчаса назад - так ли это существенно? Иногда да, иногда нет.. Но не в этом суть - метод определения нужно ли запрашивать заново результат или подойдет уже закешированный - сейчас (для стандартного протокола кэширования http) - может указывать время которое можно считать результат валидным после запроса и дает возможность уточнить валидны ли данные в кэше (если нет получить все данные). Как следствие - прокси вне зависимости от загруженности ресурсов (пропускной канал и сервер) будет спрашивать: а валидны ли данные?

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

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

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

Saturday, March 14, 2009

Strange server

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

Saturday, March 7, 2009

Изменения в разрабатываемом браузере

Привел к нормальному внешнему виду в разрабатываемый мною браузер:
  • одиночное окно отображавшее сразу компонент для отображения отрендеренного кода - заменил на классическое отображение содержащее над компонентом отображения страницы панель с отображением текущего адреса, кнопки назад, напечатать и выйти. В результате реализовано полный функционал доступный из публичных интерфейсов библиотеки gtkhtml, связанный с рендеренгом страниц. В внутри еще доступен дамп внутреннего состояния текущего отображенной страницы - но вовне он не доступен.
  • не реализовал: автоматическое перенаправление через определенное время (сообщение в консоль) и анимацию при загрузке страницы;
  • в остальном можно уже использовать как нормальный браузер, простой POST без передачи файлов реализован, есть поддержка cookies - как основной браузер использовать не рекомендую из-за проблем отображения, но для тестов вполне.
Приложение наследует особености библиотеки gtkhtml:
  • не поддерживает стили css;
  • не корректный рендеринг страниц - может расползаться отображение фреймов и дивов;
  • не поддерживает javascript;
  • алгоритм рендеринга отличается от классического: преобразования HTML -> DOM -> Rendering HTML + Styles(просчет стили для каждого элемента DOM с последующим отображением. На данный момент структуру формирования дерева подобного DOM я не обнаружил - страница разбивается на список открывающихся и закрывающихся тегов, и как следствие сложно добавить применение стилей.
В новых версиях библиотеки с присутствует мой патч решающий пролему с поддержкой кодирово отличных от utf8, и исправлена ошибка с неправильным отображением entity в некоторых случаях.

Sunday, March 1, 2009

Использование общего отображения

В отношении кросплатформенных систем,
например: приложение и веб.

Достоинства:
  • Возможность описания поведения на псевдоязыке(если нет общего языка) общего для всех видов отображения;
  • Возможность быстро переносить логику между системами,
  • Достаточно быстрое создание логики на новой системе, если она где-то уже используется.
Недостатки полное следствие достоинств:
  • Нужно искать или создавать общий язык для описания логики и зависимостей между формами - для простой логики создать достаточно просто, но зачем для простой логики городить сложности - можно просто написать на языке наиболее удобном для каждой платформы, но при этом получиться, что логика реализована 2 раза и исправление ошибки или изменение автоматически не отобразиться на другой платформе;
  • Проблемы отображения - нужно преобразовывать отображения с одного вида в другой - не всегда это удается получить отображение которое будет отображаться в удобном виде. Пример: шаблон отображения приложения использует позиции элементов друг в друга - но в случае веб отображения или другой системы отображения нужно указывать порядок вложенности и\или размеры - и для поддержки нового отображения нужно переделывать всю систему преобразования шаблона в разметку на всех платформах для поддержки всех ограничений или особенностей системы. В результате получается, что кроссплатформенная система отображения содержит все ограничения и общности всех платформ (возможно одна из проблем проблем из-за которой google отказался от идеи использовать кроссплатформенное отображения для своего браузера).

А в остальном получается удобная система получения новой функциональности на другой платформе(если она уже есть на одой из версий системы), но можно портировать только ограниченное количество функциональности - так как функциональность существующая в одной из версий приложения может быть слишком сложной для другой - регистрация на сайте и возможности настроек для веб отображения гораздо проще чем в системе в виде установленного приложения с указанием кучи специфических, не нужных обычному пользователю с веба возможностей. А создание новой системы быстрого создания офисных приложений вряд ли окупит себя, так нужно конкурировать с 1C или Microsoft, и проще создать простое, но максимально эффективное приложения для каждой платформы. Или простое максимально эфективное ограничено кросплатформенное приложение максимально соответствующее требованиям, но без излишней универсальности.

Saturday, January 31, 2009

GPS на основе GSM

Обычно для определения расположения объектов (точного) используется системы основанные на получении сигналов со спутников(ГЛОНАС и GPS). Существует также возможность определения расположения через использования других источников данных - уровень магнитного поля(компас) и других физических параметров - но определение положения через эти данные не очень точное, так как со временем эти параметры могут изменяться. Наиболее удобное решение использовать позиционирование относительно радиопередатчиков - они со временем свое место не меняют и можно с более дешевой аппаратурой получить данные. Наиболее удобный метод это использование данных доступных в мобильным телефонам: уровень сигнала базовых станций и их видимости - они уникальны для каждой точки поверхности. В дополнение можно еще использовать FM приемники встроенные в часть мобильных телефонов - но эти параметр не очень точный.

Подобная логика уже реализована в некоторых проектах http://gsmloc.org/ - единственная проблема, что нет возможности получить эти данные на некоторых мобильных телефонах, а для более функциональных эта задача не столь важна - так как есть встроенный приемник GPS, но и эта система может помочь например внутри зданий, где сигнал со спутников очень слаб.

Saturday, January 24, 2009

Упрощенно Protothreads(KISS)

Упрощенная версия предыдущего поста или мои комментарии.

В данном случае создается только видимость многопотоковость - в системе в любой момент времени исполняется только один поток и только, без прерывания исполнения - поток должен сам отдать управление. Планировщик достаточно прост: кто первый заблокировался первым и разблокируется - в общем удобно и предсказуемо. Для программирования основным неудобством остается хранение всех переменных в специальной структуре описателе контекста, все остальные моменты вполне хорошо скрываются от программиста. Используется переход при вызове функции сразу на адрес остановки - создается видимость одновременного непрерывного исполнения кода - код в принципе должен быть простым. При переносимости могут возникнуть проблемы только с метками - но их можно решить создавая, например: скрытый свич - где каждая точка будет отмечаться какой то меткой. Так обычно пишут программы демонстрирующие работы многопоточных алгоритмов - при определенной доле фантазии можно, что то придумать - единственная проблема возможно будет ограничено количество блокировок в функции - можно ожидать только одного сигнала, то есть функцию нужно разбивать на 3 ветви:
  • обычное исполнение(старт с начала),
  • блокировка,
  • код после блокировки.

В данном алгоритме блокировки осуществляются достаточно хорошо - сложно обратиться к ресурсу несколько раз, если поток всегда один без прерываний. Основная проблема возможен случай тупика при не корректном занимании ресурсов, каждый поток захватит только часть, а для окончания работы нужно полный объем, но до таких сложностей еще долго, как мне кажется. Но если начать разбивать потоки по ядрам - возможны проблемы, так как нужно более надежно блокировать ресурсы к которым может быть обращение в нескольких потоках на разных ядрах, появиться требование описывать в контексте какие ресурсы могут понадобиться. Такая идея была частично в Singularity, но только для описания потоков с блокировкой получения не указанных в описании ресурсов. В общем случае, эта реализация ничем не хуже по описанию чем остальные - даже лучше, так как позволяет без использования языков оптимизированных под реализацию событично-ориентированной модели получить ее достоинства в С. Если добавить еще возможность использования хоть самого простой реализации сборщика мусора - очень хороший алгоритм для реализации надежных систем (не обязательно в контексте использования) с обязательным детерменизмом исполнения. С памятью особых проблем быть не должно так как в контексте исполнения указывается ее максимальный объем и можно посчитать реальное использование - для случая когда этот момент очень важен.

Protothreads(перевод)

Перевод описания Protothreads, пытался перевести
максимально близко к тексту.

Введение

Protothreads - программная модель разработанная Adam Dunkels объединяющая достоинства событие-ориентированной модели (event-driven) (иногда называемый конечным автоматом (state machine) и потокового программирования. Основным достоинством событийной модели является эффективное использование памяти и скорости исполнения. Основным достоинством потоковой модели является простота алгоритма (algorithm clarity). Протопотоки получают достоинства обоих моделей, предоставляя также экстремально легковесные потоки. Как и в событийно-ориентированным программировании мы имеет единый стек, но также благодаря потоковому программированию функция может (хотя-бы в теории) содержать примитивы взаимоблокировки. В текущей реализации мы имеем:

  • не реализует стандартное API (POSIX или какие-либо другие);
  • не требуется ассемблерный код или использование setjmp/longjmp;
  • кросс-платформенная реализация;
  • не вытесняющий (non-preemptibly) планировщик и четко определенный порядок(deterministically) выполнения;
  • не использует возможностей SMP.

Я решил переписать эту версию с нуля без совместимости с другими более ранними версиями протопотоков. Реализация имеет только одну не определенную стандартом С особенность - использование переменных как меток перехода (gcc label variables).

Проект включает:

  • Полный исходный код (около 400 строк включая комментарии);
  • реализацию двух примитивов синхронизации (семафоры и блокировки);
  • около 800 строчек тестового кода;
  • gdb макросы для отображения стека потока или потоков.

Потоки без стека

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

Операции return и goto скрыты макросами, так что код выглядит как обычный код потоков. Реализации использует малоизвестную возможность компилятора создавать переходы на адресу обозначенный через переменную, в результате мы можем вернуться обратно. Производительность примерно равна другим событийно-ориентированным приложениям, смена контекста представляет собой простой С код.

Вы можете считать протопоток обычной событийно-ориентированной моделью. В такой модели(если обрабатывается запрос) обычно сохраняется только указатель на функцию (или состояние( state)) которое используется в больших операциях условных переходов (switch) вместо отдельных функций) и контекст(context), включающих текущее состояние обработки. В протопотоках не используется указатель на функции и контекст, переход осуществляется непосредственно на адрес(без функции). Этот адрес гораздо лучше описывает состояние чем указатель на функцию. Действительно, протопотоки более универсальны; если функция A вызывает функцию B, B вызывает C и C блокируется, тогда поток сохраняет только три указателя на реальное место запуска в середине этих функций.

Пример производитель-потребитель

Далее показана версия известного алгоритма производитель-потребитель с двумя потоками и одним общим почтовым ящиком. Здесь описана основная идея, детали описаны далее. Все функции и типы протопотоков начинаются с префикса pt_ или для системных функций protothread_. Каждый поток требует описания контекста (context structure):
 typedef struct {
pt_thread_t pt_thread;
pt_func_t pt_func;
int i;
int * mailbox;
} pc_thread_context_t;

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

Пример 2 описание потоков производителя и потребителя:

 static pt_t
producer_thr(void * const env)
{
pc_thread_context_t * const c = env;
pt_resume(c);

for (c->i = 1; c->i <= 100; c->i++) {
while (*c->mailbox) {
/* mailbox is full */
pt_wait(c, c->mailbox);
}
*c->mailbox = c->i;
pt_signal(pt_get_pt(c), c->mailbox);
}
return PT_DONE;
}

static pt_t
consumer_thr(void * const env)
{
pc_thread_context_t * const c = env;
pt_resume(c);

for (c->i = 1; c->i <= 100; c->i++) {
while (*c->mailbox == 0) {
/* mailbox is empty */
pt_wait(c, c->mailbox);
}
assert(*c->mailbox == c->i);
*c->mailbox = 0;
pt_signal(pt_get_pt(c), c->mailbox);
}
return PT_DONE ;
}

Производитель ждет пока почтовый ящик освободится и записывает следующее значение и посылает событие потребителю. Потребитель ждет изменения состояния почтового ящика, проверяет его состояние, освобождает ящик(записывает 0) и разблокирует производителя. Для передачи сигналов как канал используется указатель на почтовый ящик. Это стандартное использование адреса структуры, состояние которой интересует потоки, как канал. В качестве канала, адрес никогда не освобождается, и используется для привязки к ожидаемым сигналам.

Эта технология синхронизации потоков (и термин канал "channel") впервые использовалась в ядре UNIX. Концепция подобна переменным состояния (condition variable) в POSIX потоках -- в данном случае память не ассоциируется с каналом или переменной состояния(как в POSIX); сигнал или смена состояние переменной для ожидающего потока не изменяет его состояния. Основной причиной выбора реализации использующей канал, является простота реализации, так как не требуется выделение переменной состояния. API также включает pt_broadcast(), подобный pt_signal() отличающийся от него только тем что он пробуждает все ожидающие потоки, не только наиболее длительно ожидающие.

Тестовая функция создает служебный объект и контекст для каждого потока, инициализирует почтовый ящик, создает потоки, и выполняет цикл пока выполняется условие:

 static void
test_pc(void)
{
protothread_t const pt = protothread_create();
pc_thread_context_t * const cc = malloc(sizeof(*cc));
pc_thread_context_t * const pc = malloc(sizeof(*pc));
int mailbox = 0;

/* set up consumer context, start consumer thread */
cc->mailbox = &mailbox;
cc->i = 0;
pt_create(pt, &cc->pt_thread, consumer_thr, cc);

/* set up producer context, start producer thread */
pc->mailbox = &mailbox;
pc->i = 0;
pt_create(pt, &pc->pt_thread, producer_thr, pc);

/* while threads are available to run ... */
while (protothread_run(pt)) {
}

/* threads have completed */
assert(cc->i == 101);
assert(pc->i == 101);

free(cc);
free(pc);
protothread_free(pt);
}

Как это работает?

А теперь детали. Две наиболее интересные функции в этом примере pt_resume() и pt_wait(). Раскроем эти функции потока производителя чтобы изучить как они работают. (Полное описание API можно посмотреть в конце статьи.) Поле pt_func структуры контекста содержит приватные данные протопотоков(function context); макрос позволяет обращаться к полю по имени.
 static pt_t
producer_thr(void * const env)
{
pc_thread_context_t * const c = env;

/* pt_resume(c) expanded: *****/
if ((c)->pt_func.label) goto *(c)->pt_func.label;
/* pt_resume end *****/

for (c->i = 1; c->i <= 100; c->i++) {
while (*c->mailbox) {
/* mailbox is full */

/* pt_wait(c, c->mailbox) expanded: *****/
do {
(c)->pt_func.label = &&pt_label_18;
pt_enqueue_wait((c)->pt_func.thread, c->mailbox);
return PT_WAIT;
pt_label_18:;
} while (0);
/* pt_wait end *****/
}
*c->mailbox = c->i;
pt_signal(pt_get_pt(c), c->mailbox);
}
return PT_DONE;
}

В начале работы потока, переменная содержит NULL, и функция не выполняет переход на сохраненный адрес(goto) -- код входит в выполнение цикла for в начале функции. Достигнув вызова pt_wait(), сохраняет значение адреса переменной (ее имя имя получяется из номера строки __LINE__; уточнение: двойной амперсанд сообщает, что нужно использовать расширение получения адреса по имени переменной), ставит поток в очередь ожидания внутри описания потока, сохраняет адрес возврата, что равносильно ожиданию сигнала связанного с адресом общей переменой, в данном случае почтового ящика, и возвращает PT_WAIT. (Результат возврата в данном примере не используется; как будет описано ранее он используется только для вложенных функций protothread.)

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

Структура protothread

Структура описания протопотока обязательно содержит одну или больше протопотоковых функций. Только функции протопотоков могут блокироваться или вызывать блокируемые функции. Всем протопотокам требуется pt_thread_t структура для хранения внутренних состояний потоков. Кроме этого, каждый уровень вложенности функций нуждается в pt_func(тип pt_func_t), которая содержит внутреннюю(служебную) информацию для функций. Если A вызывает B которая вызывает CC блокируется), каждая из них нуждается в собственном экземпляре структуры pt_func.

Эта структура также содержит определенные пользователем состояния специфичные для функции, обычно это внутренние переменные если используется потоки POSIX. Функции протопотоков обычно не содержат внутренних переменных, так как эти переменные не сохраняются во время ожиданий событий. (Вы можете посмотреть в примере используется c->i вместо просто i для организации циклов.) Существует одно исключение, однако в обычно в C коде есть несколько локальных переменных инициализируемые при описании (на основе аргументов) и неизменные во время исполнения. Вы можете использовать этот принцип и при создании потоков protothreads также (перед вызовом pt_resume()), так как эта инициализация не имеет побочных эффектов. Пример 2 содержит подобную переменную c.

Существует только 2 пути запуска функций в протопотоках; протопоточные функции нельзя запускать непосредственно.

  • Любой код(обычная функция или протопоточная функция) вызывается через pt_create() для создания нового потока. При передаче управления указатель на функцию и контекст исполнения передается одним аргументом. Этот вызовы планирует вызов потока (но не запускает его непосредственно). Не существует метода отмены выполнения потока. Систем пропотоков не сообщает когда он выполниться; он только запланирует выполнение. Вызов pt_create() также требует уникальной структуры (для данного потока) pt_thread_t, которая может быть выделена где угодно, но обычно это делают в вызывающей функции и включают в контекст вызывающей функции (как структура pc_thread_context_t указанная выше).
  • Функция (внутри потока) может вызывать pt_call(). Последовательность обработки подобна обычному вызову функции, кроме того что вы используете pt_call() для вызова функции. Любое количество аргументов любых типов может быть передано при вызове функции ( проверку типов обычно осуществляет компилятор), но первым аргументом обязательно должен быть указатель на контекст исполнения (содержащий поле pt_func) для сохранения состояния функции после ее вызова.

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

Вложенные функции в Protothread

Как работает вложенность? Когда функция A вызывает (используя pt_call()) потоковую функцию B, и B ожидает события (pt_wait()), B сохраняет текущее адрес исполнения и возвращает PT_WAIT в pt_call() в A, что вызывает сохранение его в контекст исполнения A как адрес восстановления где будет вызвана B. A возвращает PT_WAIT функции которая ее вызывала. Когда планировщик продолжит исполнение A , вызовется pt_resume() вызывающая B, так как A вызвала B, и B's pt_resume() перейдет на адрес после блокировки и продолжит исполнение. Так стек разматывается, когда поток блокируется и восстанавливается при снятии блокировки. Так можно описать как реализуется использование единого стека. Также мы должны осветить как обрабатываются параметры которые A передает B без побочных эффектов -- A всегда вызывает B при возобновлении работы.

Когда B полностью завершиться и возвратит PT_DONE, A будет знать что нужно продолжить выполнение кода после вызова pt_call() для B.

Контекст исполнения функции A может включать контекст B's внутри себя, или динамически создавать контекст функции B; выделение контекста возложено на пользователя. Для получения скрытия данных, A может выделять очень минимальную копию структуры pt_func и указатель не скрытую контекст B(для A). И в начале обработки B динамически выделяет память под полную структуру данных и связывает ее с переданной от A структурой.

Другая интересная идея заключается в том что,если A вызовет B, затем B вернет A вызов C (так что B и C не может работать одновременно), контекст для B и C могут быть полями(union) внутри контекста A. Эта разделение памяти между B и C подобна разделению стека внутри POSIX потока.

Четкий порядок исполнения

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

Конечно, исходное окружение должно быть полностью контролируемо, так как при жестком порядке исполнения(no nondeterminism) система подстраиваеться под окружение. Это важно например для случая, во время эмуляции; код не должен принимать решения на основе реального времени, так реальное время может изменяться от запуска к запуску. Генераратор случайных чисел должен использоваться для всех элементов имеющих случайные значения параметров(таких как задержки в передаче данных по сети или задержки при обращении к дискам).

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

Использование памяти и производительность

Наиболее известная реализация протопотоков (Adam Dunkels) использует 2 байта на каждый поток. Эта реализация не такая оптимальная ( в основном благодаря тому что реализация включает планировщик: потоки могут быть в списке ожидающих или работающих потоков); наше окружение не столь ограничено в использовании памяти. Каждый описатель потока содержит структуру pt_func_t , содержащую 2 указателя. Полное описание содержит структуру pt_thread_t, содержащую 5 указателей. Но все равно это гораздо меньше чем у стандартного потока POSIX.

Время создания и уничтожения нерабочих потоков на моем компьютере около 12.2 nanoseconds. Время создания и уничтожения при использовании POSIX реализации 7.85 microseconds, то есть различие в 643 раз. Для сравнения времени переключения контекста для модели производитель-потребитель, каждый поток переключался за 22.2 nanoseconds. Смена контекста в стандартной реализации 3.0 microseconds, то есть разница в 135 раз.

Заключение

Использование protothreads для приложений реального времени или с ограничением на использование ресурсов может дать значительный прирост производительности по сравнению с стандартными реализациями POSIX потоков. В тоже время алгоритм получается более прозрачным, чем при использовании стандартных реализаций событийно-ориентированной модели.

Описание API

Контекст исполнения потока

Далее указаны используемые макросы ( реализованные чтобы выглядеть и работать подобно функциям) первым аргументов выступает определенная пользователем структура описатель контекста выполнения, c (подобная context_t, но описанная пользователем). Для всех служебных функций используется тип pt_f_t.

void pt_resume(struct context_t *c)

Каждая функция должна вызвать этот макрос перед выполнением основной логики, но после того как выполнила инициализацию своих внутренних переменных (которые инициализируются из аргументов, но не из глобального состояния). Если выполнение поток будет восстановлено, pt_resume() вызовет переход (goto) на точку где прервалось выполнение. Если функция вызвана первый раз (еще не разу выполнение не блокировалось), pt_resume() ничего делать не будет.

void pt_wait(struct context_t *c, void *channel)

Заблокировать на время отправки сигнала в указанный канал. Канал интерпретируется как void * и обычно им выступает адрес структуры изменение состояния которой интересует функцию. Сам канал не имеет состояния; система протоптоков никогда не обращается по указанному адресу. Обычно, после возврата значения этой функцией функция будет вызвана еще раз. Аналог для POSIX pthread_cond_wait().

void pt_yield(struct context_t *c)

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

void pt_call(struct context_t *c, pt_f_t child_func, struct child_context_t *child_context, arg...)

Непосредственно вызвать функцию протопотока, передать ей значение аргументов и контекст исполнения и подождать окончания ее выполнения. Это позволяет вызвать потомка без смены контекста исполнения. Будьте внимательны, чтобы не было побочных эффектов при обработке параметров, так как этот вызов осуществляется при возврате управление ожидавшему исполнения потоку. Обычная для C проверка типов будет осуществлена для всех аргументов.

bool_t pt_call_waited(struct context_t *c)

Возвращает TRUE если выполнение функции pt_call() было заблокировано (непосредственно в вызывающей функции или в ее потомке). Если функция A вызывает B и B блокируется, тогда при возврате в A, это поможет понять что должен исполняться другой поток, это позволит заново пересчитать значение глобального состояния. Но если B не заблокирована, тогда A знает, что нужно только изменение локального состояния (независимо от того, что B выполнил).

protothread_t pt_get_pt(struct context_t *c)

Возвращает указатель на описание потока(protothread_t). Используется для вызова функций требующих для исполнение описания потока, таких как: pt_create() или pt_signal().

Контекст исполнения других реализаций потоков

void pt_create(protothread_t, pt_thread_t, pt_f_t func, void *env)

Планирует указанный поток на выполнение, передает ему его аргументы и значение окружения. Эта функция предок всех потоков. Никакой смены контекста между этим вызовом и следующим состоянием вызвавшей функции. Новый список потоков добавляется в конец очереди готовых к исполнению потов. Аналог для POSIX pthread_create().

void pt_broadcast(protothread_t, void *channel)

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

void pt_signal(protothread_t, void *channel)

Подобно pt_broadcast() но пробуждаеться только наиболее долго ждавший поток. Аналог для POSIX pthread_cond_signal().

protothread_t protothread_create(void)

Этот вызов обычно вызывается одни раз для создание дескриптора для потока. Возвращает дескриптор потока. Система протопотоков не использует глобальные переменные. Все состояния потоков сохраняются в этом объекте; множество экземпляров протопотоков независимы. Эта функция только вызывает выделение памяти под объект.

void protothread_free(protothread_t)

Очистка состояния созданного с использованием protothread_create(). Перед запуском не должно быть ни одного связанного с этим объектов потоков.

Планирование

bool_t protothread_run(protothread_t)

Запуск следующего готового процесса (если он конечно есть). Возвращает TRUE, если нет ждущих исполнения потоков.

void protothread_set_ready_function(protothread_t, void (*ready_function)(void *), void *env)

Эту функция предназначена для использования с уже существующим планировщиком (если нет возможности его заменить, или не хотим его заменять). Вам эта функция не нужна, если вы используете собственный планировщик. Эта функция обычно запускается один раз во время инициализации. Результатом вызова этой функции является вызов для всех свободных потоков ready_function (с передачей ей env) когда поток будет готов (и нет рабочих потоков), и нет (в данный момент ) запущенных потоков. Вы можете передать NULL вместо ready_function для отключения этой возможности.
Указанная ready_function обычно планирует (используя методы доступные в вашей системе). Другая функция вызывает protothread_run() пока не закончатся потоки для выполнения (protothread_run() returns FALSE). ready_function не должна вызывать protothread_run() непосредственно.
Для того чтобы последовательность выполнения потоков не заняла слишком много времени, функция может ограничить количество запусков protothread_run(); для примера: она запускает не более 20 потоков до того как вернуть управление планировщику для выполнения других задач (вне протопотоков). Она это делает (если последний вызов protothread_run() вернет TRUE), только если нужно выполнить перепланировку, так как осталась не завершенные задачи.

Источники

Wikipedia protothreads

Описание потоков POSIX

Большая благодарность Adam Dunkels (с поддержкой от Oliver Schmidt) за блестящую идею. Пожалуйста, зайдите на его сайт.

Большое спасибо также за помощь Paul Soulier в разработке концепции потоков, Marshall McMullen и John Rockenfeller за рецензию черновиков этой статьи.

Larry Ruane программист в LeftHand Networks, Inc., an HP Company