Асинхронний кінцевий автомат ідеологія та технологія
Вступ
Добре, коли твої підлеглі ніколи не хворіють, не вмирають, завжди присутні на роботі і виконують твої розпорядження без попереднього приготування: «Викликали — встань». Такі, наприклад, веб-сервіси, що дотримуються моделі REST (яка, якщо відкинути спеціальну HTTP-термінологію, зводиться до того, що інтерфейс сервісу є інтерфейсом контейнера даних).
У реальному житті у підлеглих бувають нежить і декретна відпустка, у мережевих з'єднань - таймаути, у авіарейсів - погода, а у автомобільних двигунів у мороз - необхідний час холостого прогріву.
Асинхронний кінцевий автомат - це зручна абстракція верхнього рівня для управління сутностями з багатим і не завжди передбачуваним внутрішнім світом. Такою сутністю може бути апаратний пристрій, сесія мережного протоколу або просто паралельно запущений процес, код якого ви не контролюєте.
Описувана нижче архітектура асинхронного кінцевого автомата вирішує ряд стандартних проблем, що виникають при «лобовій» інтеграції підсистем з урахуванням їхнього внутрішнього стану. Найпомітніша з таких проблем – це недостатня рознесеність (я б навіть сказав – недостатня «гальванічна розв'язка») сутностей сигналу та переходу між станами, через що автомат стає нестійким до DoS-атак. Є й інші, менш очевидні — наприклад, «недостатньо атомарна» заміна вузла підсистеми або ресурсу, що використовується.
Анатомія (об'єктна декомпозиція)
Модель кінцевого автомата включає такі базові сутності:
- Стан - це режим функціонування керованої системи, відмінний від інших за можливостями, що надаються. Таким чином, снапшоти кешів та буферів,варіанти циклів «від забору до обіду» та інші акциденції керованої системи до поняття «стану» не входять. У нормі станів мають бути лічені одиниці; якщо рахунок пішов на другий десяток — швидше за все, керовану систему слід роздробити чи ієрархізувати.
- Умова — це логічне значення (true чи false) одному з «входів» системи. Суперпозиція станів всіх входів автомата однозначно визначає цільовий стан автомата. Таким чином, будь-який вхідний сигнал, значимий для стану автомата, зрештою зводиться до встановлення значення однієї або декількох умов.
- Реакція - це відгук автомата на відміну від поточного стану від цільового. Принципово різних видів реакції ми нарахували два з половиною: прямий перехід між станами, маршрут та стоп-маршрут («цегла»). Прямий перехід може бути порожньою операцією (NOP) — наприклад, якщо зміна входів викликана повідомленням про завершення асинхронної операції.
Ще раз:стан визначається набором умов, реакція визначається парою станів. Будь-якому набору входів можна поставити у відповідність будь-який стан — тобто при жодному наборі входів бажаний стан системи не повинен бути невизначеним.
Маршрути задаються "ліниво" (lazily) - для кожної пари станів, між якими не заданий прямий перехід, встановлено проміжний цільовий стан. Воно аналогічне шлюзу у маршрутизації мережевих пакетів — з однією відмінністю: маршрути можуть бути вкладеними.
Бажаний стан перераховується після кожного виконаного переходу. Таким чином, немає гарантії, що маршрут буде пройдено до кінця. Це знижує "мрійливість" кінцевого автомата, тобто скорочує час відгуку на несподівану зміну обстановки.
«Стоп-маршрут» — це маршрут, початковий та проміжний стан, в якому збігаються. Він використовується для маркування пар станів (дійсного та бажаного), в яких нічого не слід робити, а слід чекати, поки бажаний стан не зміниться.
Якщо ви пишете на Java, задайте набори умов і станів як enum, а абстрактної реалізації автомата використовуйте параметризацію, EnumSet для «слова стану» і EnumMap для таблиць маршрутів і переходів. Це не догма. Напевно, можна якось інакше. Але, як каже вірменське радіо, шкода.
Фізіологія (threading та сигналізація)
Асинхронний кінцевий автомат зручно будувати над чергою повідомлень. На Android (пакет android.os) є інфраструктура MessageQueue + Looper + Handler, що ідеально відповідає нашим вимогам; У «дорослому» Java можна використовувати ThreadPoolExecutor з одного потоку або просто цикл, що розбирає LinkedBlockingQueue. Якщо у вас немає блокадних ресурсних обмежень, не шкодуйте заварки і виділіть кожній керованій системі потоку.
Якщо ви задаєте якісь входи самі - у коді переходу між станами - це можна робити як синхронно (прямим присвоєнням), так і асинхронно (постановкою сигналів у чергу). Повна асинхронність усуває цикл з оброблювача вичерпання черги, але вимагає явного «пінгу» (надсилання порожнього повідомлення) у разі, якщо стан системи змінилося (тобто ми не зупинилися в петлі стоп-маршруту), але маршрут не пройдено до кінця. Можна обійтися без пінгів, організувавши цикл з умовою «бажаний стан не рівний дійсному, і при цьому ми не вперлися в стоп-маршрут». В обох випадках стоп-маршрут доводиться вирізняти явно.
Педагогіка (runlevels та паніка)
А тут можна отримати грант на тестовий періодЯндекс.Хмари. Варто лише у полі «секретний пароль» запровадити «Хабр»