written on Tuesday, October 30, 2012
Коли машини були великі, а юнікс молодий, програми виконувалися лінійно. Запустився, зробив що треба і вийшов у сад. У нашій же сучасній постпостюніксовій реальності все трохи веселіше.
У функцію main() вривається якась мутна хріновина, забирає собі управління, а мій код запускає, коли сама захоче по всяких подіях і таймерах. Таймери у неї внутре, події припливають самі , загалом тріумф чорної магії та культу міфічного користувача.
У гуєвих програмах евентлуп зазвичай йде в навантаження до графічного фреймворку. У хитрих бібліотеках, що працюють з асинхронним io — в навантаження до асинхронного io. У світі мейнстрімового лінукса всі якось домовилися і юзають glib, але крок вправо, крок вліво — і починається ой.
У бібліотеки libre, так само як у бібліотеки sofia-sip, є свій евентлуп. Sofia-sip ще якось інтегрувалася з glib, але він мені теж нафіг не потрібен. Насетапив коду, дернув функцію re_main() і все, вона заблочилася навіки вічні, а мені треба, щоб рідний евентлуп графічного тулкіта теж жив.
Два евентлупи в одному треді — це ой. У випадку з sofia-sip я робив простий хак: ставив хендлер на початок кожної ітерації рідного евентлупа, а всередині хендлера дергав su_step — функцію, яка продергувала одну ітерацію сіпового евентлупа. Загалом це працювало і для прототипу було достатньо.
Працювало звісно фігово — якщо у рідного евентлупа не було таймерів і не приходило ніяких подій, то він просто спав і мій хендлер ніхуя не викликався, скільки б таймерів він всередині себе не мав і скільки б застряглих даних не накопичилося в буфері мережевого сокета.
Як тимчасове рішення для стадії прототипу, я просто ганяв у рідному евентлупі паразитний таймер кожні 100ms.
Наступний варіант був веселішим. Графіка спокійно працювала в рідному треді, а для куска, що працював з протоколом і мережею, відстрілювався окремий тред, який одразу блочився у виклику re_main() і ганяв сіповий евентлуп по-справжньому.
Коли в графічному інтерфейсі тикалася якась кнопка, код мережевої частини виконувався в контексті графічного треда. Це фігово, бо блокування. Коли в мережевій частині прилітав якийсь івент, типу вхідного дзвінка, вона намагалася дергати у своєму потоці стан графічних віджетів і це теж було погано, бо не працювало.
Ясне діло, довелося скочуватися до мессадж-пассінгу. Розділення на графічну та сіпову частини було дуже чітким, викликів, що перетинали цей borderline, було буквально три штуки і вони всі повертали void.
Прямі виклики дуже просто і прозоро загорнулися в асинхронну відправку повідомлення, повідомлення пхалося в mailbox потрібного потоку, виконання йшло далі. Потік з того боку дивився в mailbox, діставав з повідомлення інфу про те, кого треба пнути і фактичне виконання відбувалося вже в правильному потоці.
Глибоководні степові космонавти ООП, що писали реалізацію цього механізму, чомусь обзивали це "системою акторів". Ну та ладно, богиня Лейн все одно запихає кожному з них по тридцять-три обсервери в кожне вухо і ніхто не піде недиспатченим.
Проблема з цією реалізацією мессадж-пассінгу була в тому, що хтось ще мав ходити і дивитися в мейлбокс. Всі звісно були дуже розумні, майже як я, бо ходилка-дивлялка мейлбокса була засунута в початок кожної ітерації евентлупа, а евентлуп міг спати. Як мега-варіант, для кожного мейлбокса запускався свій тред, в якому евентлупа не було взагалі, а був нескінченний цикл діставання повідомлень з мейлбокса.
Загалом довелося робити той самий воркараунд — в обох процесах ганявся паразитний таймер, який ходив дивитися, чи нема чого повиконувати в мейлбоксі.
Сьогодні я побачив ще більш магічну штуку — чергу виконання, зроблену на пайпі. Дешево-сердито і threadsafe без блокувань. Реалізація є в самій libre.
Працює досить просто — системний pipe() повертає два файлових дескриптори, на один з яких нацьковується приймаючий повідомлення евентлуп, а в другий пишуться бінарні структурки, що містять magic і вказівник на що дернути.
У другий дескриптор можна писати повідомлення з будь-якого потоку — в приймаючому потоці одразу спрацьовує хендлер. Писати вказівники на функції через пайпи я не став, але ідея використання fd-лістенера, як способу дати пинок потрібному евентлупу, мені сподобалася і я її заюзав замість дурних таймерів.
Загалом миші плакали, кололися, але продовжували не юзати ERLAИГ.