Динамический поиск гонок в Javaпрограммах на основе
синхронизационных контрактов
Дмитрий Цителов, Devexperts LLC
Виталий Т...
Состояния гонки
• 2+ потоков
• к одним и тем же данным
• хотя бы одно из них - запись

• Обычно это ошибка
Пример гонки
public class Account {
private int amount = 0;
public void deposit(int x) {amount += x;}
public int getAmount...
Свойства гонок
• Опасны
– Повреждают глобальные данные
– Не приводят к немедленному отказу
программы

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

• Ограниченная глубина анализ...
Динамический подход
• Анализирует только текущий путь
выполнения
• Поддерживает все средства синхронизации
• Огромные накл...
Java Memory Model
• На синхронизационных событиях есть
отношение порядка «synchronized-with»
• Synchronized-with + порядок...
Идея
• Приложения используют библиотеки через API
• API хорошо документировано
– Класс XXX потокобезопасен
– Класс YYY неп...
Пример синхронизационного
контракта
Типы связей
• Явные
• Простые
• Сигнатура метода:
retval owner.method(parameters)

• Любая комбинация простых связей
• Усл...
Примитивные простые явные связи
Owner

Owner

Param

Return value

Param

Return value

chm.put(k, v)
↓
chm.get(k)

ex.sum...
Пример описания контракта
Пример описания контракта
Язык контрактов
• Happens-before контракты
– пары методов
– всех методов класса

• Потокобезопасность метода
– ну, или все...
Области анализа

(SD)

(RD)
Алгоритм
• В основе – отслеживание happens-before
• Операции синхронизации в SD:
– synchronized, volatile, thread start/jo...
Реализация

Application classes

Instrumented app
classes
Детали реализации
• Не сломать сериализацию
• Не давать разрастаться часам
• Хранение контрактов

• Не генерировать garbag...
Экспериментальные результаты
Приложение

JTT

QD

MARS client

MARS Server

Режим

base

juc

base

juc

base

juc

base

...
Ограничения
• Трактуем контракты как атомарные
– это неизбежно для неблокирующих средств
синхронизации – volatile, etc.

•...
Заключение
•
•
•
•
•
•

Модифицировали happens-before
Научились легко описывать контракты
В экспериментах не потеряли точн...
Спасибо!
• Пишите нам:
– drd-support@devexperts.com
– vitaly.trifanov@gmail.com
– tsitelov@acm.org

• Можно скачать и попр...
Upcoming SlideShare
Loading in …5
×

TMPA-2013 Tsytelov Trifanov Devexperts

643 views

Published on

Tools & Methods of Program Analysis (TMPA-2013)
Tsytelov, D., Trifanov, V., Devexperts LLC, St. Petersburg State University
Search of Race Conditions in Java Programs Based on Synchronization Contracts

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
643
On SlideShare
0
From Embeds
0
Number of Embeds
175
Actions
Shares
0
Downloads
2
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide
  • То есть без синхронизации конкуррентно кто-то пишет, а кто-то пишет и читаетСтрого говоря, есть race condition и data raceБывают benign races, но их тоже надо искать
  • Поджойнили 2 потока, смотрим, что будет. Хотели 11, но если перепишут друг друга – может быть 5 или 6
  • Рассказывали на пленарном докладе, так что краткоХуже дедлоков, те сразу видно (глазами, thread dump)Сложно по факту видимой ошибки отследить место, где она произошла гейзенбаг, очень сильно зависит от интерливинга не найти во время тестирования еще и потому что часто тестовое окружение немногоядерно
  • Model checking, верификацияСтатический анализРасширение системы типов или иное сообщение информацииБорются за точность и полноту, по факту и с тем и с тем проблемы, потому что NP-трудна и вынуждены ограничивать область анализаПлохо работают с нестатическими конструкциями – как только используем средства синхронизации, отличные от критических секций, начинаются проблемыЕсть готовые утилиты, как в индустрии, так и в ресерче
  • Не полон по определениюБорется за точность, небезуспешноПоддержка любых средств синхронизацииОгромные накладные расходы – нужно обрабатывать в рантайме дикие объемы данныхНет готовых инструментов. Нашли Tsan 4 Java, IBM MSDK, но они совершенно неадекватны
  • Есть потоки, они работают с данными и публикуют измененияМожно рассматривать как распределенную систему, где операции «публикации» (синхронизации) – сообщенияНа множестве синхронизационных операций задано отношение порядкаНа множестве всех операций в одном потоке – естественное отношение времениОбъединяем, транзитивно замыкаем, получаем частичное отношение порядка на всех события в программе. Если 2 обращения к данным упорядочены этим отношением, то между ними была синхронизация, и гонки нет. Если не упорядочены – гонка есть (определение из спецификации Java).Отслеживаем это отношение с помощью векторных часов, проверяем все операцииВекторные часы имеют размер O(N) и сложность O(N), отсюда огромные накладные расходыХотим снизить расходы, но точность не потерять
  • Объем кода приложения, как правило, сопоставим с объемом кода используемых библиотек. Библиотеки выбираются на этапе проектирования системы, поэтому предполагаются надежными. Мы не хотим искать в них гонки, хотим только в нашем коде. Ищем по нашим полям, и по вызовам методов чужих объектов.Не хотим отслеживать в них операции синхронизации. Нужно уметь описать не только факт потокобезопасности, но и передачу hb. Ровно это позволит избежать потери точности. Выкидываем код, описываем поведение на границе.Также опишем все примитивы синхронизации.Итого хотим описать частичную спецификацию. Есть подход контрактов, но там объективные сложности, плюс у нас нет исходников, мы не хотим править код, хотим описать аккуратно в сторонке и без деталей вроде пред\пост-условий и инвариантов.
  • Что мы хотим? Есть chm, обещает, что put предшествует последующему get. Надо уметь этот факт описать и обработать, а саму map исключить, т.е. Рассматривать как черный ящик. Связь через объект-владелец (одна и та же map) и первый параметр метода (один и тот же ключ)
  • В рамках нашего подхода работаем только с явными (через сигнатуру метода) простыми (1 к 1) связями.Итого их 6 типов. Умеем описать любую комбинацию + булево условие на возвращаемое значение, потому что некоторые контракты могут сработать и не сработать (CAS), что сообщается через возвращаемое значение
  • Работаем только с 3 типами, без возвращаемых значений. Для каждого типа есть примеры. С возвращаемыми значениями не сталкивались, знаем, что их мало. Повод для дальнейшего исследования.
  • Пример описания контракта пары методов. Вообще, контракты надо описывать целиком, так что можно описать контракт класса.
  • Итого умеем:Контракт пары методовКонтракт классаФакт того, что метод потокобезопасен (умеем wildcard)Тип непотокобезопасного метода (пример с list привести)
  • Крактко про алгоритм. Обычно RD == весь код. Мы выделяем 2 области, они вот так вклдываются.
  • В основе алгоритма лежит отслеживаниеhappens-beforeОперации синхронизации отслеживаем только в соответствующей области, в ней же – контракты. Если в контракте – поднимаем флажок и не отслеживаем операции синхронизации.Обращения к данным – только в нашем коде: поля и вызовы foreign-методовНе отслеживаем потокобезопасные вызовы
  • Чтобы встроиться в программу, мы используем технику инструментирования (трансформирования) байт-кода. Техника аналогична аспектному программированию. Java позволяет подключить к ней агента, который получает в виде массива байт каждый новый загружаемый класс и может его аккуратно модифицировать. Это мы и делаем – идем по байт-коду и находим интересующие нас операции. Видя их, спрашиваем у модуля конфигурации, нужно ли их обрабатывать и как. Если нужно – вставляем соответствующие вызовы модуля поиска гонок, в который вшит алгоритм happens-before.
  • Как я говорил, динамических детекторов нет. Одна из причин тому – существенные технические сложности, возникающие в процессе. Нам удалось решить многие в достаточной степени, чтоб поставить эксперименты.Так, модифицирование байт-кода сразу же ломает сериализацию. Исправили.Часы имеют размер, пропорциональный количеству потоков в программе, а значит регулярное порождение новых потоков приводит к неконтроллируемому росту потребления памяти, а завершение потоков – к утечкам. Решили.Наконец, контракты нужно хранить в каком-то месте, из которого мы их сможем достать, то есть нужны некоторые ключи. Поскольку контракты в общем случае имеют произвольный вид, то и ключи произвольные – их нужно генерировать на лету, отсюда тоже множество проблем с загрузкой классов.Наконец, важно не плодить новые сущности, потому что из-за количества операций мигом взлетит потребление мусора.
  • Мы запускались на трёх приложениях, которые на наш взгляд позволили в достаточной степени проверить детектор. JTT – небольшое клиентское приложение к баг-трекеру JIRA, 10 потоков, 400 классов, 2.5-3 мб библиотек.QD – нагрузочный тест адского протокола доставки котировок, весь на волатайлах и ансейфах – 15 потоков, 700 классовMARS – клиент-серверная мониторинговая система, в совокупности порядка 30 потоков, 2000 классов, около 10-11 мб библиотек.2 режима, в обоих гонки искали только в com.devexperts.*В базовом режиме отслеживали только базовые примитивы + unsafe, в juc – описали и принудительно исключили jucОсновные результаты: часов стало меньше, операций стало меньше, точность не потеряли.
  • Самый неприятный слайдНаш подход скругляет углы у happens-before подхода и безусловно имеет ряд ограничений.Эти ограничения не помешали нам получить прирост производительности и избежать потери точности в наших экспериментах, но, безусловно, мы отдаем себе отчёт в том, что можно придумать класс программ, для которого мы будем терять точность. Работа над классификацией таких программ и постановкой дальнейших экспериментов является одной из наших дальнейших ключевых задач.Во-первых, мы трактуем вызовы контрактных методов как атомарные. С этим связан неотъемлимый вопрос – отслеживать их до вызова или после вызова? Тут возможны потери точности, но это essential проблема обработки любых средств синхронизации, не связанных с блокировками и fork/join.Во-вторых, мы отслеживаем контракты на основе явных простых связей. Поскльку мы умеем отслеживать все низкоуровневые средства синхронизации Java, то можно просто не отслеживать контракты, которые мы не можем описать и тогда детектор шагнёт вглубь и обработает события внутри них. Такой подход не сработает, если контракт класса уже частично описан, потому что контракт надо описать полностью.Наконец, мы трактуем контракты как черный ящик с внешними входами и выходами, но у него могут быть внутренние входы и выходы. Практические наблюдения показывают, что таких мало, но наверняка они есть – какой-нибудь алгоритм, который работает с live view wait-free структуры данных. Это важное направление дальнейших исследований.С другой стороны, по каждому из ограничений есть понимание, что оно подтверждено практическими наблюдениями.
  • Мы представили модификацию алгоритма happens-before, в которой предлагаем ограничить область отслеживания гонок и область отслеживания операций синхронизации, чтобы получить прирост производительности. Чтобы избежать потери точности мы описываем частичную спецификацию поведения методов в многопоточной среде с помощью специально созданного языка. Это не требует модификации исходного кода программы, поэтому подход применим к любым программам.Контракты можно переиспользовать, и в этом смысле их составление выгодно. Также данный подход может быть применим в модульном тестировании.Эксперименты подтвердили наши ожидания – точность не теряем, производительность растет, накладные расходы падают. В нашем подходе отслеживает меньше операций «случайной» синхронизации, что опосредованно увеличивает точность поиска гонок.Наконец, нам удалось получить решить ряд существенных технических сложностей и получить рабочий детектор.Результаты нас радуют и обнадеживают, однако впереди много работы. Во-первых, необходимо доработать код и открыть исходники. Во-вторых, нужно доработать язык описания контрактов (интерфейсы, описание «внутренних» точек выхода из контрактов) и доработать детектор – внедрить поддержку контрактов с возвращаемым значением.Нужно исследовать неявные контракты – что мы с ними вообще можем сделать.Наконец, необходимо поставить больше экспериментов на крупных опен-сорс проектах (Eclipse).Однако мы понимаем, что наш подход обладает рядом существенных ограничений, и вполне может быть класс программ, на которых мы получим потерю точности.
  • Спасибо за внимание, надеюсь вам понравилось!Вопросы можно задавать нам или писать на указанные email-адреса.Сейчас бета-версия утилиты находится в открытом доступе и готова к использованию, есть базовая документация. В принципе, по запросу можем предоставить исходники.
  • TMPA-2013 Tsytelov Trifanov Devexperts

    1. 1. Динамический поиск гонок в Javaпрограммах на основе синхронизационных контрактов Дмитрий Цителов, Devexperts LLC Виталий Трифанов, Devexperts LLC, мат-мех СПбГУ
    2. 2. Состояния гонки • 2+ потоков • к одним и тем же данным • хотя бы одно из них - запись • Обычно это ошибка
    3. 3. Пример гонки public class Account { private int amount = 0; public void deposit(int x) {amount += x;} public int getAmount() {return amount;} } public class TestRace { public static void main (String[] args) { final Account a = new Account(); Thread t1 = depositAccountInNewThread(a, 5); Thread t2 = depositAccountInNewThread(a, 6); t1.join(); t2.join(); System.out.println(account.getAmount()); //5? 6? 11?. } }
    4. 4. Свойства гонок • Опасны – Повреждают глобальные данные – Не приводят к немедленному отказу программы • Сложно обнаружить вручную • Трудно воспроизводимы
    5. 5. Статический подход • Анализ кода программы без её запуска • Расширение языка, система типов • Ограниченная глубина анализа • Есть много утилит для Java – FindBugs, jChord, etc.
    6. 6. Динамический подход • Анализирует только текущий путь выполнения • Поддерживает все средства синхронизации • Огромные накладные расходы • Нет готовых детекторов для Java
    7. 7. Java Memory Model • На синхронизационных событиях есть отношение порядка «synchronized-with» • Synchronized-with + порядок событий в одном потоке = частичное отношение порядка happens-before • Отслеживаем happens-before с помощью векторных(логических) часов Ламперта • Метод точный, но много накладных расходов
    8. 8. Идея • Приложения используют библиотеки через API • API хорошо документировано – Класс XXX потокобезопасен – Класс YYY непотокобезопасен – ZZZ.get() синхронизирован с ZZZ.put() • Опишем поведение API • Исключим библиотеку из анализа
    9. 9. Пример синхронизационного контракта
    10. 10. Типы связей • Явные • Простые • Сигнатура метода: retval owner.method(parameters) • Любая комбинация простых связей • Условия на возвращаемое значение
    11. 11. Примитивные простые явные связи Owner Owner Param Return value Param Return value chm.put(k, v) ↓ chm.get(k) ex.sumbit(task) ↓ task.run() Наверное, тоже бывает chm.put(k, v) ↓ chm.get(k) Наверное, тоже бывает Наверное, тоже бывает
    12. 12. Пример описания контракта
    13. 13. Пример описания контракта
    14. 14. Язык контрактов • Happens-before контракты – пары методов – всех методов класса • Потокобезопасность метода – ну, или всех методов класса (умеем «*») • Тип непотокобезопасного метода – Read – Write (по умолчанию)
    15. 15. Области анализа (SD) (RD)
    16. 16. Алгоритм • В основе – отслеживание happens-before • Операции синхронизации в SD: – synchronized, volatile, thread start/join, … – happens-before контракты – не отслеживаем, если в контрактном методе • Обращения к данным в RD (RD ∈ SD): – к полям классов из RD – вызовы непотокобезопасных методов классов, не принадлежащих RD
    17. 17. Реализация Application classes Instrumented app classes
    18. 18. Детали реализации • Не сломать сериализацию • Не давать разрастаться часам • Хранение контрактов • Не генерировать garbage
    19. 19. Экспериментальные результаты Приложение JTT QD MARS client MARS Server Режим base juc base juc base juc base juc Синхр. оп-ий/мин 115К 28К 15М 7.2М 7.4 М 4.3 М 1650К 800К Кол-во синх. часов 13К 7К 6.1K 0.2K 85K 72K 15К 14К Контрактов/мин 2.3K 0.6K 209К 130К 980К 730К 360К 904К 0.75K 1.4М 1.4М 17К 24К 5.5К 5.5К 6 1 5 2 2 Кол-во контр. часов 8.5K Найдено гонок 8 10 1
    20. 20. Ограничения • Трактуем контракты как атомарные – это неизбежно для неблокирующих средств синхронизации – volatile, etc. • Только контракты на основе простых связей – Если сложней, то просто не описывать, детектор шагнёт «внутрь» метода – Lock.newCondition().await() – печаль – ConcurrentMap.entrySet().iterator() – печаль • Только контракты без сайд-эффектов – Грубо говоря, только контейнеры – Если Executor вызовет task.foo() вместо run() – печаль.
    21. 21. Заключение • • • • • • Модифицировали happens-before Научились легко описывать контракты В экспериментах не потеряли точность Нашли больше гонок Стало меньше часов Стало меньше шума • Хорошо, но всё еще впереди – Open source – Доработка языка и детектора – Эксперименты
    22. 22. Спасибо! • Пишите нам: – drd-support@devexperts.com – vitaly.trifanov@gmail.com – tsitelov@acm.org • Можно скачать и попробовать: – http://code.devexperts.com/display/DRD/ – Это еще бета-версия

    ×