cat mindflow.txt > /dev/null – Telegram
cat mindflow.txt > /dev/null
180 subscribers
14 photos
87 links
Поток сознания о программировании, технологиях, жизни и вообще
Download Telegram
Окей, ещё одна житейская мудрость... Если у тебя есть пул коннектов к какому угодно серверу (БД, HTTP, etc), проверь дважды, что keep-alive таймаут на твоей стороне хотя бы на несколько секунд меньше keep-alive таймаута этих соединений на стороне сервера. Иначе ошибок записи в сокет по причине race conditions в закрывающихся TCP соединениях не избежать.
Когда накрыла ностальгия - старый добрый C, но на этот раз в браузере, спасибо WebAssembly! Играть тут http://micromind.me/golife/?preset=spaceship, код смотреть тут https://github.com/iximiuz/golife.c.
О вычислительной сложности... Все конечно слышали про класс задач NP (задача коммивояжёра, укладка рюкзака, и пр.). Задачи, которые *похоже* (если P != NP) не возможно решить за полиномиальное время на обычном компе. Но очень много людей ошибочно считают, что NP расшифровывается как Non-Polynomial, ожидаемо противопоставляя этот класс классу задач P, aka Polynomial. Так вот, оказывается, был вариант назвать NP класс вовсе не NP, а PET (i.e. Probably Exponential Time). И в случае доказательства P != NP переименовать PET в Provably Exponentioal Time. Или в Previously Exponential Time, если вдруг P == NP. Но нет, назвали-таки Non-deterministic Polynomial...
После долгих лет в разработке, начавшихся с абсолютно бездарного теоретического старта основ ООП в универе, картинка сложилась!

Чему учили в универе: ООП - это абстракция, инкапсуляция, полиморфизм и наследование.

Что говорит Alan Kay (https://en.wikipedia.org/wiki/Alan_Kay): ООП - это общение посредством передачи сообщений, состояние [объектов в широком понимании этого слова], его (состояния) сокрытие и защита [aka инкапсуляция], и [максимально] позднее связывание.

И вот со вторым определением никаких проблем. Все, что обладает состоянием - объект. Как такие объекты создаются (с помощью классов, модулей, копирования и цепочек прототипов, etc) - абсолютно не важно. Как объекты общаются между собой? Передачей сообщений. По сути, даже простой myUser.setAge(42) - это передача сообщения setAge объекту myUser. В некоторых языках это прослеживается лучше, в некоторых хуже. Объекты должны иметь возможность прятать и защищать (от модификации) свое внутреннее состояние (C++/Java private, protected атрибуты классов такой же способ сокрытия и защиты, как JavaScript замыкания в module pattern или заглавные/строчные буквы в Go). Сообщения, передаваемые между объектами должны быть диспетчеризуемыми. На основе имени сообщения (связывание на этапе компиляции), типа получателя (single dispatching, т.е. связывание происходит уже в runtime), типа получателя и аргументов (double dispatching, опять же runtime). И из общения посредством передачи сообщений и позднего связывания возникает полиморфизм и дополнительный уровень абстракции.

Что мне не нравится в первом (и общеизвестном) определении ООП: полиморфизм и наследование представлены как объекты одного уровня. По факту же, наследование - это лишь один из вариантов реализации полиморфизма (https://en.wikipedia.org/wiki/Subtyping_polymorphism). Существуют и другие - ad hoc polymorphism (https://en.wikipedia.org/wiki/Ad_hoc_polymorphism), parametric polymorphism (https://en.wikipedia.org/wiki/Parametric_polymorphism). При этом полиморфизм сам по себе является одним из вариантов создания абстракций, делая вышеупомянутое определение еще более наивным.
Немного мыслей про readable streams в современном Node.js. В этот раз в форме статьи http://micromind.me/posts/nodejs-readable-streams-distilled
Продолжаем про Node.js стримы - коротко про writable streams https://micromind.me/posts/nodejs-writable-streams-distilled?msrc=tc
И в завершение недели постов разродился статьей о всевозможных способах обработки запросов на сервере с примерами на python https://micromind.me/posts/writing-python-web-server-part-2?utm_medium=social&utm_source=tchannel
Идея even loop проста, но программные платформы на основе циклов событий получаются очень мощными. Про внутреннее устройство циклов событий и то, как можно написать свой всего в 100 строках https://micromind.me/en/posts/explain-event-loop-in-100-lines-of-code/?utm_medium=social&utm_source=tchannel
У Python очень богатая стандартная библиотека. Собрал в одном месте десяток способов запустить TCP и/или HTTP сервер с использованием только стандартных средств.

https://micromind.me/ru/posts/over-9000-ways-to-make-web-server-in-python/?utm_medium=social&utm_source=tchannel
Хоть практической пользы в этом видео наверное и нет, это интервью просто обязаны посмотреть все, кто имеет хоть какое-то отношение к программированию. Кен Томпсон: "К счастью, моя жена в тот момент уехала в трехнедельный отпуск с нашим годовалым ребенком, и - неделя, неделя, еще неделя - и получился Unix" https://youtu.be/EY6q5dv_B-o?t=1357
Минутка занимательной этимологии. Возможно, это оказалось новостью только для меня, но глагол "to boot" в компьютерном мире означает далеко не просто "загружать что-либо". Достаточно взглянуть на список значений - увольнять, надевать ботинки, ударить сапогом, помогать https://translate.google.com/#view=home&op=translate&sl=en&tl=ru&text=boot. В компьютерный же мир это слово пришло скорее из более длинной его формы "bootstrap", имеющей в настоящее время значение "get (oneself or something) into or out of a situation using existing resources". То есть справиться с задачей исключительно своими собственными ресурсами, без внешнего воздействия, а в компьютерном мире - ввода дополнительных данных. Вероятно, все началось пару сотен лет назад с гиперболы "pull oneself over a fence by one's bootstraps" (https://en.wikipedia.org/wiki/Bootstrapping), означающей "перебраться через изгородь, потянув самого себя за задники ботинок (bootstrap-ы)", американского варианта подвига барона Мюнхгаузена, вытянувшего себя из болота за свои же волосы.
Docker несколько лет был для меня не более, чем инструментом, чёрным ящиком с чёрной магией внутри, позволяющим запустить своё приложение в воспроизводимой среде. А на самом деле, стоило всего лишь потыкать в него палочкой, чтобы осознать, что, например, image - это не более, чем папка с файлами, обычно с корневой файловой системой какой-нибудь версии Linux.

Аналогичная история с Linux OS. Какая часть сервера - это Linux kernel, а какая - userland? Как происходит загрузка ОС? И опять же, просто нужно было собрать свой дистрибутив и запустить его на виртуальной машине.

https://micromind.me/en/posts/from-docker-container-to-bootable-linux-disk-image/?utm_medium=social&utm_source=tchannel
Используешь себе какой-нибудь линуксовый тул каждый день и беды не знаешь. Но вдруг накрывает, и становится очень интересно, как он работает под капотом. Вот сегодня, например, настала очередь ip из iproute2. Стандартно полез гуглить исходники и уж было начал их курить... В общем, не знаю, почему мне пришло это в голову только сейчас, но... Куда быстрее просто обернуть вызов ip в strace:

strace -e trace=network,file ip addr
execve("/usr/sbin/ip", ["ip", "addr"], [/* 35 vars */]) = 0
...
socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE) = 3
setsockopt(3, SOL_SOCKET, SO_SNDBUF, [32768], 4) = 0
setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0
setsockopt(3, SOL_NETLINK, 11, [1], 4) = -1 ENOPROTOOPT (Protocol not available)
bind(3, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0
getsockname(3, {sa_family=AF_NETLINK, pid=4727, groups=00000000}, [12]) = 0
sendto(3, ..., 40, 0, NULL, 0) = 40
recvmsg(3, ..., MSG_PEEK|MSG_TRUNC) = 3752
...
open("/etc/iproute2/group", O_RDONLY) = 4

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:26:10:60 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
valid_lft 76662sec preferred_lft 76662sec
inet6 fe80::5054:ff:fe26:1060/64 scope link
valid_lft forever preferred_lft forever


Вот так за 5 секунд мы узнали, что пора читать про AF_NETLINK сокеты https://en.wikipedia.org/wiki/Netlink.
Что такое "операционная система Linux"? Это ядро (Linux kernel, бинарник в несколько мегабайт, хитрая прослойка между железом и софтом) и пользовательское пространство (user space, десяки или сотни мегабайт разных бинарников утилит, помогающих работать с ядром). Ядро всегда одно, отличается лишь версиями, а пользовательских пространств много (Debian, Ubuntu, CentOS, Alpine, busybox, кастомные сборки и т.п.).

Что такое "контейнеризация"? Это способ виртуализации на уровне операционной системы. В частности, это когда на запущенной Linux ОС можно положить разные пользовательские пространства в разные папки и потом запустить их выполняться. В качестве ядра будет использовано ядро родительской ОС, но программы, выполняющиеся в разных контейнерах, будут думать, что каждая из них имеет свою собственную ОСь вокруг. Изменения, сделанные в рамках одного контейнера, могут быть скрыты от остальных контейнеров и родительской ОС. Как это работает? Ядро Linux поддерживает т.н. namespaces, то есть способ изоляции ресурсов. Например, Process ID namespace позволяет иметь несколько деревьев процессов, начинающихся каждое со своего PID 1 и все это в рамках одного и того же экземпляра выполняющегося ядра. Также существуют network, mount, user ID и прочите изолированные пространства. Системный вызов clone(), являющийся основой канонического fork() принимает флаги, позволяющие изолировать новый запускаемый процесс в различных пространствах. Зачем это нужно? Здорово уметь запускать на одной железке как можно больше изолированных окружений, это круто повышает утилизацию ресурсов (альтернативный подход - запускать полностью излоированные ОСи на одной и той же железке - обычно менее эффективен [см. kata containers], правда и более безопасен).

Что такое Docker? Когда-то давно Docker был большим демоном-монолитом (и клиентом, со всеми знакомым нам CLI API), решающим комплексную задачу запуска и обслуживания контейнеров. Что же именно это значит? Когда на диске уже есть папка с набором файлов какого-либо user space, остается лишь создать нужные namespace и запустить бинарник, указанный как entry point в качестве процесса с PID 1. Но после запуска необходимо мониторить состояние контейнера, возможно перезапускать его при падении, очищать ресусры при остановке и т.п. Папку с user space на диске тоже нужно создать умело. Многие контейнеры используют на 100% схожий user space, отличаясь лишь полезной нагрузкой (кто-то хочет запускать nginx в debian:latest, кто-то свой node.js app но тоже в debian:latest и т.п.). Так как речь идет о десятках и сотнях мегабайт, неплохо бы уметь реиспользовать повторяющиеся части. Я могу выделить следующие основные куски Docker-а: 1) обслуживание жизненного цикла контейнеров (запуск, мониторинг состояния, очистка ресурсов после остановки) 2) работа с образами (images), т.е. жонглирование всеми этими большими папками, в частности загрузка их из реестров по сети 3) мощный API для запуска и запроса состояния контейнеров и образов. Так как каждый из этапов вполне себе самодостаточный, а релизить все синхронно - сложно, в какой-то момент Docker решили распилить на runc (запускатор контейнеров, консольная утилита, один запус == один контейнер), containerd (демон, делает все, чтобы runc мог запускать новые контейнеры, в частности работу с образами) и dockerd ("лицевой" демон, общающийся с containerd, чтобы мы могли исползьовать вот эти все удобные плюшки Docker-а в командной строке, через docker client). С момента распила каждый из проектов живет своей отдельной жизнью. Например, runc используется и другими менеджерами контейнеров (см. cri-o), а его интерфейс стандартизирован и он является канонической реалзиацией Container Runtime Specification.
Почему даже после распила на части Docker - все еще монолит? Одна из причин в том, что containerd - это демон, да еще и требующий root (ибо он запускает runc, которому нужен root для создания namespaces). Но работа с образами не требует ни демона ни тем более root. Однако, в случае Docker альтернативы (пока?) нет. Если хочется скачать какой-либо image из сети или собрать свой - придется запускать containerd. К счастью, ребятам из Red Hat это не понравилось и они решили запилить свой тулсет контейнеризации с блекджеком и барышнями. Так, запустить контейнер, используя докеро-подобный CLI можно утилитой podman (https://github.com/containers/libpod), которая под капотом использует опять же runc. Собрать свой образ или модифицировать существующий можно утилитой buildah (https://github.com/containers/buildah), которая под капотом испоьзует крутую либу https://github.com/containers/storage, которую также, например, использует демон cri-o, реализующий Kubernetes Container Runtime Interface (CRI). На мой взгляд, альтернативная (от Docker-вселенной) реализация Red Hat-ом всех этих контейнерных дел получилась куда более гибкой и структурированной. Но Docker все еще очень сильный игрок, благодаря исторически сформировавшемуся полчищу конечных пользователей.