вторник, 11 июня 2019 г.

libuv internals и как эта библиотека интегрирована в Node.js

       С libuv сложилась такая парадоксальная ситуация - про нее все одновременно знают и не знают. Все знают что это нечто используемое Node.js для работы с сокетами и файловой системой и нечто обладающее магическим event loop. Ну а дальше показания расходятся. Причем даже у "экспертов" выступающих на конференциях. 
        В документации libuv есть довольно красивая и но малоинформативная диаграмма:

На мой взгляд функциональность предоставляемая libuv очень просто и понятно описана вот тут https://github.com/danbev/learning-libuv libuv можно использовать для того чтобы:
  • Работать с сетью (tcp, upd, pipe и тд)
  • Асинхронно работать с файловой системой
  • Асинхронно работать с DNS
  • Работать с нитями
  • Обрабатывать сигналы
  • Работать с таймерами

Это довольно большой список, но на самом деле интерфейс libuv основывается всего на трех концепциях: event loop(uv_loop_t), handle (uv_handle_t), request(uv_request_t):
  • event loop - это то, чем собственно занят поток выполнения. бегаем по кругу, дергаем callback-и. Детальнее опишу ниже.
  • handle  представляет собой некий ресурс. Это может быть обертка вокруг ресурса операционной системы (сокет, файловый дескриптор) или просто нечто вроде таймера умеющее дергать callback   
  • request - это некий запрос. Он может использовать хендлер (запрос на запись в файл) или может не использовать хендлеры.
Также внутри libuv сидит thread pool но я бы рассматривал его как деталь реализации а не часть интерфейса. Thread pool используется для превращения синхронных действий в асинхронные и в настоящее время используется только при работе с файловой системой и DNS. Ну еще мы сами момжем попросить libuv выполнить какую-то работу асинхронно в thread pool а не в основном потоке исполнения.
        Так вот, что же из себя представляет event loop ? На мой взгляд его проще всего описать вот такой диаграммой: 
Все прямоугольнички нарисованные на этой диаграмме детально описаны вот тут - https://github.com/libuv/libuv/blob/v1.x/docs/src/design.rst#the-io-loop
Я же хочу остановится на том как это все интегрировано в Node.js. Практически вся работа с libuv сосредоточена в evn.cc. Начинается все с void Environment::InitializeLibuv() - тут мы инициализируем таймеры, check handles и тд. 
        Теперь собственно посмотрим что из прямоугольничков оригинального event loop'а используется в Node.js:
  • Первое это таймеры. При инициализации Node.js создает один libuv таймер который запускает все JS таймеры. Время срабатывания таймера - минимальное среди всех установленных JS таймеров
  • Idle handlers в мир JavaScript никак не транслируются. Они используются внутри для управления логикой которую libuv использует для рассчета времени которое она должна провести заблокированной в poll вызове
  • Prepare handles - также в мир JavaScript не транслируются. Там стоит системный хендлер который служит для измерения времени которое мы фактически проведем заблокированными в poll вызове
  • Check handles - здесь Node.js также вешает один хэндлер который выполняет все callback-и установленные через setImmediate()
  • Close handlers - также не транслируются в JS. Здесь libuv запускает on close callback-и закрываемых ресурсов (хендлеров). 
В общем как-то так. Вообще по моему скроменому мнению - libuv одна из самых понятно написанных библиотек. Тут довольно интересно организорвано наследование структур - через макросы. Все поля структуры объявляются внутри макроса. Соответсвенно в дочерней структуре сначала идет макрос объявляющий родительские поля, а потом макрос объявляющий собственные поля:







Комментариев нет:

Отправить комментарий