[FAQFlow #3]: Hello, SaltStack (for Arch?)

Salt is what you want when you have to manage several servers. It looks like a good option for open source servers (=servers with Unices), so I’m giving it a try. A common alternative is the community version of puppet.

Motivation(s)

I installed it on my two Arch Linux machines (a notebook and a desktop). Now, let me explain something to you. Historically, I am always using Arch in my notebook, because it is the machine I most use, customize, play with, etc. Arch is the perfect distro for it, because I’m always with the latest software, always fiddling with the system and so on.

But what about my desktop? I rarely use it — at least compared to my notebook. Historically, I tried to install Arch on it, but I always wiped it a few days later. Why? Because I tend to over-customize my Arch boxes, but I don’t wanted to duplicate my work across two machines.

In the end, this means I always tried to search for the perfect distro for “non-mainstream” personal use. I tried Gentoo, Funtoo, Ubuntu, Fedora, openSUSE, alpine, grml. Oh man, no, simply no. What constitutes a good distro for personal use but that you won’t use too much? Each of those has its own problems.

Funtoo/Gentoo: too many compilation times. You don’t want that for a machine you don’t use often. Ubuntu and openSUSE would probably be OK, but Ubuntu is often out-of-date compared to other distros and openSUSE’s zypper package manager plus its packages are way too bloated for my purposes — although it is also awesome. Fedora changes releases too often: you don’t want to reinstall it. Alpine and grml, I concluded, are very nice for other purposes other than “my-quiet-desktop-out-there”.

So what? Does every distro in the planet has its problems for my simple use case, or maybe the problem is me? The last option is probably the right answer, I think I got way too strict about my personal environments ;)

Then I thought about something else. The problem is clearly lack of automation: if you don’t want to touch on machines you don’t use often, then you should automate them. Upgrades, maintenance, etc.

So…there is SaltStack for the rescue [1].

SaltStack HelloWorld

We begin everything with a hello world, and what is the equivalent of it for network environments? A ping, of course.

So I begin installing salt in my Arch notebook and in my (now new) Arch desktop.

Salt uses a master-minions hierarchy for organization. So, my master is my notebook and my only minion (for now) is my desktop. I could be using other distros: I’m not limited to the exactly same one to use salt: it is cross-platform. So, got a new Ubuntu desktop? No problem, just salt’-it.

Oh, you have to love (or hate) your init program, so systemd eases everything for me:

$ systemctl enable salt-master # (in my master machine)
$ systemctl enable salt-minion # (in my minion machine)

And period, salt is now set up. I’m not entering in technical details here, but after installing salt you have to connect your two machines somehow. This is well documented, so RTFM. I connected them in the same LAN network and they continue to recognize each other across reboots.

Now let’s go to the best part. From my master (notebook), I do

# root@arch /home/thiago # salt '*' test.ping 
<$MYHOSTNAME>:
 True

where <$MYHOSTNAME> is the hostname of the minion.

So everything is properly set up. Now what? I don’t know. I will have to discover what to do with it :)

But hey, I tried automating a system upgrade:

root@arch /home/thiago # salt '*' pkg.install refresh=True sysupgrade=True feh
<$MYHOSTNAME>:
 ----------
 feh:
 ----------
 new:
 2.12-1
 old:
 
 giflib:
 ----------
 new:
 5.1.1-1
 old:
 
 imlib2:
 ----------
 new:
 1.4.6-3
 old:
 
 libexif:
 ----------
 new:
 0.6.21-2
 old:
 
 libid3tag:
 ----------
 new:
 0.15.1b-8
 old:

This is really nice, I can install or remove any package.

You see: this is all about automation. I’ll see in the next days if I will keep using salt.

Thanks for reading.

Footnotes

[1]: why salt? I don’t know, I just picked it first. I could use other technologies. I don’t recommend it right now, I’m just testing it and its choice as my automation service was pure serendipity.


Filed under: Sem categoria

Zica no Kernel?!

Depois de muito tempo sem nenhum tipo de problema com meu Arch, após a última atualização meu sistema não desligava, não entrava em espera e nem reiniciava.

11078756_1073190649361470_1730053512_o

A solução encontrada, proposta pelo amigo Sylvio Lima, foi substituir o kernel padrão pelo LTS.

Obs.: Também fiz o mesmo teste com o kernel-ck, mas o problema não foi resolvido com o mesmo.

Primeiramente precisamos instalar o kernel:

#pacman -S kernel-lts

Depois precisamos “marcar” o kernel-lts como padrão e adicioná-lo a entrada no grub:

#mkinitcpio -p linux-lts

#grub-mkconfig -o /boot/grub/grub.cfg

Agora você vai precisar forçar a reinicialização da sua máquina e depois disso a entrada do kernel-lts já vai estar como padrão no seu grub.

Feito isso já será possível reiniciar e desligar o sistema.


Hack ‘n’ Cast v0.12 - Sobrevivendo a Uma Falha de HD

Se tem algo que é certo, e que um dia vai acontecer com todos nós é a morte... de um HD, seja ela por mau uso ou por uma falha de hardware.

Baixe o episódio e leia o shownotes

Lançamento Tails 1.3.1

A versão 1.3.1 do Tails, o Sistema Live Incógnito e Amnésico, foi lançada.

Mudanças e atualizações:

- Geração de uma nova chave OpenPGP

- Lançador Tor 0.2.7.2 melhorando a usuabilidade de conexão relacionado a censura, proxy e filtros.

Veja mais melhorias, bugs corrigidos e atualizações no Changelog.

Faça o download.

Fonte: https://tails.boum.org/news/version_1.3.1/index.en.html

Tradução livre.

Recording screencasts on Linux

One-line self-documenting functions, put this into your shell init file:

# ffmpeg x11 screencasting
# it will just record video: no audio
# dimensions of your screen are automatically calculated
_ffmpeg-screencast() {
 ffmpeg -f x11grab -s $(xdpyinfo | awk '/dimensions:/ {print $2}') -r 25 -i :0.0 -qscale 0 $HOME/ffmpeg-out-$(date '+%Y-%m-%d-%H-%M-%S').mkv
}

# ffmpeg x11 screencasting with audio
# it supersedes the previous function
# source of the audio is determined with `pavucontrol`: open it then go to the "recording" tab. You can select between the microphone or your computer as audio sources
_ffmpeg-screencast-audio() {
 ffmpeg -f alsa -i pulse -acodec pcm_s16le -f x11grab -s $(xdpyinfo | awk '/dimensions:/ {print $2}') -r 25 -i :0.0 -qscale 0 $HOME/ffmpeg-out-$(date '+%Y-%m-%d-%H-%M-%S').mkv
}

Observations

  1. If you want to get this automatically, you might be interested in my dotfiles repo. In particular, inspect the .alias file, where those functions are located (at least at the time of this post; things change).
  2. Audio recording works best with PulseAudio [citation needed]. I haven’t tested with alsa only.
  3. Pavucontrol is just a fancy interface for configuring pulseaudio, but there are others.
  4. Tweak those parameters…ffmpeg is a really powerful tool.
  5. I tested this on the i3 window manager, and it works beautifully. I tested some GUI screencasting programs on it before and the results were poor.
  6. Source of this script: a merge of several ones on the web.

Oh yes, and you can stop the screen capturing with the ‘q’ key or with a classical Control-C.

Edit: thanks Victor for a small improvement with awk in the above functions.


Filed under: Sem categoria Tagged: Linux, screencasting

gnu-social-mode: instalação e dicas de comandos

 

O grupo LibrePlanet Brasil possui um espaço social desenvolvido pelo GNU Social. Para participar da rede é preciso fazer parte do grupo e/ou pedi convite pela lista de discussão LP-BR-SP.

Baixe o código no Savannah:

https://savannah.nongnu.org/projects/gnu-social-mode/

Edite seu .emacs e cópie o arquivo gnu-social-mode.el para o diretório .emacs.d/

Abra o terminal e digite:

$ emacs  #Aqui rodará com modo gráfico
$ emacs -nw  #No terminal (prefiro esse)

[crie um alias no .bashrc]

Após executar o editor, para executar o gnu-social-mode:

  • M-x gnu-social 

Alt + x = M-x (no seu teclado)

Alguns comandos:

  • Para ver os ícones para cada usuário, pressione a tecla i.
  • Para enviar uma nova mensagem, use C-c C-s (C-c como Ctrl + C no seu teclado, em seguida, a mesma coisa com o s).
  • Para enviar uma re-dent, pressione C-c, ?? depois então: ENTER - aparecerá a mensagem e assim você veja se cabe nos 140 caracteres.
  • Se você gostaria de ver a lista de respostas que foram escritas, tem que dar o comando: C-c C-r (pressione Ctrl + C, Ctrl + r).
  • Para ver a lista de mensagens públicas: C-c c-a
  • Para começar a ver as mensagens de seus contatos (o inicial): C-c C-f
  • Para começar a ver as mensagens de um usuário em particular: C-c C-u, seguido do nome do usuário.
  • Para ver as mensagens de um determinado grupo: C-c C-g, seguido do nome do grupo.
  • Para começar a ver as mensagens com uma marca especial: C-c C-t, seguido pelo nome da marca.
  • Para atualizar a lista de mensagens (sem esperar por ele para recarregar automaticamente), pressione a tecla g.
  • Seguido da mensagem que deseja enviar (C-c C-s) e ENTER para publicar. Se a mensagem contém uma URL, para introduzi-lá, pressionando a tecla F4, com o ponto (cursor) colocada sobre o URL.
  • Se você quiser enviar uma mensagem para um usuário, digite o comando: C-c C-d, digite o nome de usuário, pressione ENTER, e digite sua mensagem, pressionando ENTER.
  • Para republicar um post escrito por outra pessoa colocar o ponto (cursor) sobre a mensagem que você deseja publicar e digite o comando: C-c, depois ENTER.
  • Para colocar uma mensagem nos favoritos, basta colocar o ponto (cursor) sobre a mensagem e pressione F (f minúscula, quando shift+F).

Atualmente o mantenedor do gnu-social-mode é @sergiodj, se quiser contribuir participe da lista de discussão e envie seu patch:

https://lists.nongnu.org/mailman/listinfo/gnu-social-mode-devel

Dica atualizada do identica-mode.

Feliz hacking

Emacs - Comandos Basicos

Iniciar o Emacs

$ emacs

$ emacs -nw (caso ele inicie em outra janela além do terminal)

Salvar

ctrl X + ctrl S

Copiar e Colar

ctrl W + ctrl Y

Apagando linha por linha

ctrl K

Selecionar texto ou palavra

shift + seta

A primeira letra da palavra maiusculo

alt C

Fechar

ctrl X + ctrl C

[FAQFlow #2]: Android Upgrading/Rooting Notes

I just [successfully] upgraded my Android phone [1] from Jelly Bean (4.1.2) to KitKat (4.4.2). Here are some notes about my upgrade process.

  1. Backup your data before trying anything! In particular, make sure it is available elsewhere: either in the cloud or in a computer.
  2. Make screenshots of the apps you have installed: use your launcher plus <VolumeDown> + <Power> buttons pressed together for a while. And…save these screenshots somewhere!
  3. Did you backup your data??????
  4. Is your bootloader unlocked? If not, then you should probably unlock it first. A good reference for this is [2].
  5. Is your device rooted? Maybe you should do that first. See [2].
  6. What do you want do to? Upgrade your android (version)? Then search for “OTA upgrade”. Customize it? Then search for “custom ROM”.
  7. Make sure to get a decent recovery. TWRP (TeamWin) looks like one of the best today. Another option is CWM. A recovery is something that will be useful if you want to flash ZIPs, wipe your cache, install custom ROMs…in other words, for maintenance and customization tasks (anything, for that matter).
  8. To reboot your phone with adb directly into the bootloader: `adb reboot-bootloader`
  9. Or maybe into the recovery: `adb reboot recovery`
  10. While you are in fastboot, you cannot use adb.
  11. Here is a good source to get an updated adb: ftp://ftp.mozilla.org/pub/labs/android-tools/
  12. Usually you can upgrade your phone either from Windows or Linux. I usually use Windows for this task, since it looks like what most power android developers and users use. Is this true? I don’t know. But the method should be reliable: use what is available and recommended by the tutorial you’re reading.
  13. Don’t simply install blindly: read, read and read before anything else! In particular, see opinions of other users.
  14. If you need support, use IRC (on Freenode: see the #android-root channel), Facebook Groups or Google+ groups. Reddit is another good place. What is the best one? That depends on the size and the activity of each community. Try to search for a group about your phone model or family first. XDA forums is usually a technical/advanced place: don’t ask newbie questions in there, unless there is specifically a section for them — or, unless if  you have a specific question about a specific thread –> in this case, ask for support in the thread.
  15. Debloating is a good term to search for after you finish installing/upgrading your device.
  16. Google Play is not the only thing out there; see also F-Droid (it contains FOSS software).
  17. Only upgrade your phone with a charger connected.
  18. Don’t install untested or poorly tested ROMs. It is better not to install anything instead of installing something that doesn’t work very well –> this won’t be always true, use your head and inspect the context carefully.

I guess those items sums up some key points. If you want to complement this post, feel free to leave a comment. Happy hacking!

Footnotes

[1]: a Motorola Razr I XT890, with an Intel Atom (x86) CPU and 1GB of RAM

[2]: XDA Developer Forums: http://forum.xda-developers.com/


Filed under: Sem categoria Tagged: Android, Tips

Running Ogre3d locally (without installing it)

simple build systems, huh?
simple build systems, huh?

Hello there. In this post we will run the “Basic 1″ example from the Ogre3d wiki without installing ogre in the system. For that, I’ll assume you’re using a *nix system.

First things first: grab the latest ogre version. At the time of this post, it is the 1.9 release, and you can find its repository here. Note: you want to download the 1.9 version using the tag download feature from Bitbucket; do not simply download the master branch. After that, extract it, create a build directory inside its directory, cd into it, and do a

cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/.lib -DOGRE_INSTALL_SAMPLES=TRUE -DOGRE_INSTALL_DOCS=TRUE -DOGRE_INSTALL_SAMPLES_SOURCE=TRUE -DCMAKE_BUILD_TYPE=Release

This is a pretty much standard cmake build. If your system is missing some of its dependencies, you will be warned and you should install them. Then:

make OgreDoc install

Now it is a good moment to read xkcd.

The usual difference here is that we don’t do a normal make install. Why? Because we don’t want our stuff ending up on /usr/local. Instead, we want it somewhere where we have full access as a normal (non-root) user — in this example, $HOME/.lib/ogre.

Basic 1

Now, head to the basic 1 page of the ogre wiki and download its example. You should try to build it, but take care to change all ogre3d references from /usr/local to your new install directory ($HOME/.lib/ogre). Yeah, that’s it.

Note: if the basic 1 download doesn’t come with a CMakeLists.txt file, you can download it from here (clean ogre cmake project). You can use it verbatim; just put it in the same directory as the basic 1 files and – again — change any references to /usr/local to your new install directory. Also, copy the dist/ directory too (if it is missing from the Basic 1 project),

I’ll add a small observation: in the case cmake complains about pkg-config, try adding this line to the top of your CMakeLists.txt (but after the project version):

set(ENV{PKG_CONFIG_PATH} "$ENV{HOME}/.lib/ogre/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")

Credits: the build style I adopted here was derived from the Ogre Arch Linux package. Thanks Sven for your excellent packaging skills! :)

Update: in your plugins.cfgdo not use “$HOME” as your PluginFolder. This seems to cause issues when you run your ogre app. Instead, expand $HOME manually (in my case, to ‘/home/thiago’). This is not much portable, I know, but there must be some workaround for this I haven’t discovered yet.

Footnotes

  • Image credit: a screenshot from the “Clean Code” software engineering book by Robert C. Martin (a good one, by the way).

Filed under: Sem categoria Tagged: build system, C++, cmake, ogre3d

Estilos de multitasking cooperativo, loop de eventos e programação assíncrona

introUm dos assuntos que mais me interessa em programação é programação assíncrona. Esse é um tema ao qual eu fui introduzido desde que comecei a brincar com Qt (framework para criar interfaces gráficas) lá por volta de 2010, mas com o tempo fui tendo experiências em vários “paradigmas” de programação assíncrona, passando, por exemplo, por Node.js e Boost.Asio. O tema muito me interessa e foi, por exemplo, o motivador para eu estudar sistemas operacionais e arquitetura de computadores.

Esse é um trabalho que eu tive a ideia de fazer já faz muito tempo. Passou até ideia de, em vez de fazer um post, evoluir mais a ideia e transformar em contribuição e também a ideia de transformar em episódio de podcast. Acho que evolui muito durante esse tempo, para quem antes não entendia nem o porquê de uma troca de contexto ser lenta.

Motivação

Muitas vezes, nos deparamos com problemas que exigem o tratamento contínuo de várias tarefas, como tratar eventos de rede e manipular arquivos locais, por exemplo. Uma intuição ao encarar esse problema seria o uso de threads, já que há várias tarefas em “paralelo”. Entretanto, nem todos os trabalhos efetuados pelo computador são executados exclusivamente pela CPU.

Há outros componentes além da CPU, mas não programáveis e que não executam seu algoritmo, que costumam ser mais lentos e possuem outras tarefas, como transformar um sinal digital em analógico, por exemplo. Além disso, a comunicação com esses componentes ocorre de forma serial, através do ciclo buscar-decodificar-executar-checar-interrupção. Há uma simplificação nesse argumento, mas o fato é que você não lê dois arquivos em paralelo do mesmo HD. Em suma, usar threads não é uma abstração natural ao problema e só introduz gargalo desnecessário e complexo.

“Para quem só sabe usar martelo, tudo parece um prego.” — velho provérbio Klingon

Outro motivo para evitar o uso de threads como resposta ao problema é que logo você terá mais threads que CPUs/núcleos e irá se deparar com o que é conhecido como problema C10K. Mesmo que não fosse um gargalo absurdo, só o fato de você necessitar de mais threads que CPUs disponíves já torna o seu programa mais restrito, pois ele não mais poderá funcionar em um ambiente bare metal, sem a presença de um sistema operacional moderno ou um scheduler.

E o grande problema de desempenho que threads introduzem decorre do fato de elas exigirem uma troca de contexto com o kernel. É claro que esse não é o único problema, pois há também o custo de criação da thread. O programa estaria realizando a criação de threads, que podem acabar tendo um tempo de vida curto e, além disso, passarem a maior parte de seu tempo de vida dormindo. A própria criação da thread não é completamente escalável, pois requer a alocação de sua própria pilha, porém a memória é um recurso global e já aí temos um ponto de contenção.

O problema de desempenho de uma troca de contexto do sistema operacional lembra o problema de desempenho da convenção de chamada de funções usada por compiladores, mas pior.

Funções são unidades isoladas, encapsuladas, e de tal forma deveriam trabalhar. Ao realizar uma chamada de função, a função atual não tem conhecimento de quais registradores serão usados pela nova função. A função atual não detém a informação de quais registradores terão seus valores sobrescritos pela nova função. Assim, as convenções de chamadas de função exigem dois pontos de processamento extra, um para salvar os valores dos registradores na pilha, e outro para restaurar os valores. É por isso que alguns programadores são tão fissurados em fazer function inlining.

O problema de desempenho da troca de contexto é maior, porque ele deve salvar o valor de todos os registradores e ainda sofre com o gargalo do scheduler e da própria troca de contexto. E se processos estiverem envolvidos, então, ainda tem o gargalo de reconfigurar a unidade de gerenciamento de memória. Esse estilo de processamento multitarefa recebe o nome de multitarefa preemptivo. Você pode aprender mais sobre esses custos estudando arquitetura de computadores e sistemas operacionais.

É possível obter concorrência, que é a propriedade de executar várias tarefas no mesmo período de tempo, sem recorrer a paralelismo real. Para essas tarefas que estão mais atreladas a I/O, é interessante abandonarmos o paralelismo durante o tratamento de I/O para alcançarmos mais escalabilidade, evitando o problema do C10K. E já que um novo design se faz necessário, é bom levar em conta multitarefa cooperativo e obter um resultado até melhor do que o inicialmente planejado.

O loop de eventos

E uma abordagem para o problema de programação, que eu vejo sendo usada mais em jogos do que em qualquer outro lugar, é a abordagem de loop de eventos. Há essas peculiaridades de que em jogos você não manipula arquivos como se fosse um banco de dados, com ambiciosos requisitos de desempenho, e também de desenvolver o jogo para quando completar, não mudar o código nunca mais, sem qualquer compromisso com flexibilidade e manutenção. E é com essa abordagem, a abordagem do loop de eventos, que começamos.

Há uma biblioteca, a biblioteca de baixo nível SDL, que tem como objetivo ser apenas uma camada de abstração multimídia, para suprir o que já não é suprido pela própria especificação da linguagem C, focando no desenvolvedor de jogos. A SDL faz uso de um sistema de eventos para tratar da comunicação entre o processo e o mundo externo (teclado, mouse, janelas, …), que é comumente usada em algum loop que o programador prepara. Essa mesma estrutura é utilizada em vários outros locais, incluindo na Allegro, que foi o maior competidor da SDL, no passado.

A ideia é ter um conjunto de funções que faz a ponte da comunicação entre o processo e o mundo externo. No mundo SDL, eventos são descritos através do tipo não-extensível SDL_Event. Então você usa funções como a SDL_PollEvent para receber os eventos e funções dedicadas para iniciar operações que atuem no mundo externo. O suporte a programação assíncrona na biblioteca SDL é fraco, mas esse mesmo princípio de loop de eventos poderia ser usado em uma biblioteca que fornecesse um suporte mais forte a programação assíncrona. Abaixo você pode ver o exemplo de um código que faz uso de eventos da SDL:

Há as bibliotecas de interface gráfica e widgets, como a GTK+, a EFL e a Qt, que levam essa ideia um passo adiante, abstraindo o loop de eventos, antes escrito e reescrito por você, em um objeto. A Boost.Asio, que não é focada em interface gráficas, mas foca exclusivamente em operações assíncronas, possui uma classe de propósito similar, a classe io_service.

Para que você deixe de fazer o código boilerplate de rotear eventos a ações específicas, essas classes tratam essa parte para você, possivelmente através de callbacks, que é uma abstração antiga. A ideia é que você associe eventos a funções que podem estar interessadas em tratar tais eventos. Toda a lógica de rotear os eventos, agora, passa a fazer parte do objeto “loop de eventos”, em vez de um loop de eventos bruto. Esse estilo de programação assíncrona é um estilo do modelo passivo, pois você só registra os callbacks e cede o controle para a framework.

Agora que estamos em um nível de abstração superior ao do loop de eventos, vamos parar a discussão sobre loop de eventos. E esses objetos que estamos mencionando como objetos “loop de eventos”, serão mencionados, a partir de agora, como executors.

O executor

O executor é um objeto que pode executar unidades de trabalho encapsuladas. Utilizando somente C++11, podemos implementar um executor que gerencie o agendamento de operações relacionadas a espera de alguma duração de tempo. Há o recurso global RTC e, em vez de criar várias e várias threads para a execução de operações bloqueantes como a operação sleep_for, vamos usar um executor, que irá agendar e tratar todos os eventos de tempo que se façam necessário. Abaixo está o código para uma possível implementação de tal executor:

Esse código de exemplo me faz lembrar do sleepsort.

No exemplo, sem o uso de threads, foi possível realizar as várias tarefas concorrentes de espera de tempo. Para tal, ao objeto executor, foi dada a responsabilidade de compartilhar o recurso global RTC. Como a CPU é mais rápida que as tarefas requisitadas, uma única thread foi suficiente e, além disso, mesmo assim houve um período de tempo no qual o programa ficou ocioso.

Há alguns conceitos que já podem ser extraídos desse exemplo. Primeiro, vamos considerar que o executor seja uma abstração padrão, já fornecida de alguma forma e interoperável entre todos os códigos que façam uso de operações assíncronas. Quando o programa deseja fazer a operação assíncrona “esperar”, ele requisita o início da operação a alguma abstração (nesse caso é o próprio executor, mas é mais comum encontrar tais interações através de “objetos I/O”) através da função que inicia a operação. O controle é passado para o executor (através do método run), que continuamente verifica notificações de tarefas concluídas. Quando uma tarefa é concluída, o executor empresta o controle da thread para o código do usuário, executando a função que havia sido registrada para tratar a notificação de finalização da tarefa. Quando não há mais nenhuma tarefa na fila, o executor cede o controle da thread, por completo, para o código do usuário.

Como só existe uma thread de execução, mas várias tarefas a executar, nós temos o problema de compartilhamento de recursos, que nesse caso é a própria CPU/thread. O controle deveria ir e voltar entre alguma abstração e o código do usuário. E aí está o princípio do estilo multitarefa cooperativo. Há pontos de customização de comportamento entre os algoritmos responsáveis por executar as tarefas, fazendo com que eles cedam e emprestem o tempo da CPU para realização de outras tarefas.

O estilo de multitarefa cooperativo tem a vantagem de que os pontos de paradas são bem definidos, pois você sabe exatamente quando o controle passa de uma tarefa a outra. Então não há aquele grande gargalo que vimos com trocas de contextos, onde todos os registradores precisam ser salvos, entre outros. Uma solução mais elegante, eficiente e verde.

O objeto que passamos como último argumento da função add_sleep_for_callback é um callback, também conhecido como completion handler. Reflita sobre o que aconteceria se uma nova operação de espera fosse requisitada dentro de um dos callbacks que registramos. Abaixo há uma versão evoluída do executor que implementamos.

Esse detalhe de implementação me lembra a variável de SHELL SHLVL.

Um caso interessante é o da linguagem JavaScript, que possui um tipo de “executor implícito”, que passa a funcionar quando chega ao fim do seu código. Nesse caso, você não precisa escrever códigos como “while (true) executor.run_one()” ou “executor.run()“, mas apenas registrar os callbacks e se assegurar que não há nenhum loop infinito que impeça que o controle passe ao executor.

Introduzida a motivação, o texto passou a prosseguir reduzindo menções a I/O, por motivos de simplicidade e foco, mas tenha em mente que usamos operações assíncronas, majoritariamente, para realizar interações com o mundo externo. Então muitas das operações são agendadas condicionalmente em resposta a notificação de término de uma tarefa anterior (ex.: se protocolo inválido, feche o socket, do contrário, agende mais uma operação de leitura). Propostas como a N3785 e a N4046 definem executors também para gerenciar thread pools, não somente timeouts dentro de uma única thread. E por último, também é possível implementar executors que agendem a execução de operações de I/O dentro de uma mesma thread.

Algoritmos assíncronos descritos de forma síncrona

O problema com essa abordagem de callbacks é que antes, possuíamos código limpo e legível. O código podia ser lido sequencialmente, pois é isso que o código era, uma sequência de instruções. Entretanto, agora precisamos espalhar a lógica entre múltiplos e múltiplos callbacks. Agora você passa a ter blocos de código relacionados longe uns dos outros. Lambdas ajudam um pouco com o problema, mas não o suficiente. O problema é conhecido como callback/nesting hell e é similar ao problema do espaguete. Não sendo o bastante, o fluxo de execução do código se tornou controvertido pela própria natureza assíncrona das operações e construções como estruturas de condição e repetição e até mesmo código de tratamento de erro ficam representáveis de formas longe do ideal, obscuras e difíceis de ler.

Uma abstração de procedimentos muito importante para programação assíncrona é a corotina. Existe a abstração de procedimentos a qual nos referimos por função, que é uma implementação do conceito de subrotina. E temos a corotina, que é uma generalização do conceito de subrotina. A corotina possui duas operações a mais que a subrotina, sendo elas suspender e resumir.

Quando sua função é uma corotina (possível quando a linguagem dá suporte a corotinas), ela pode suspender antes de chegar ao final de sua execução, possivelmente devolvendo um valor durante essa ação de suspender. Um exemplo de onde corotinas são úteis é na implementação de uma suposta função geradora fibonacci e um programa que use essa função para imprimir os dez primeiros números dessa sequência infinita. O código Python abaixo demonstra uma implementação de tal exemplo, onde se percebe a elegância, legibilidade e reaproveitamento que o conceito de corotina permite quando temos o problema de multitarefa cooperativa:

Esse código aí me lembra das funções setjmp/longjmp.

Uma característica a qual se deve dar atenção caso você não esteja familiarizado com o conceito é que o valor das variáveis locais a função fibonacci foi preservado entre as várias “chamadas”. Mais precisamente, quando a função era resumida, ela possuía a mesma pilha de execução de quando foi suspensa. Essa é a uma implementação “completa” do conceito de corotina, uma corotina com pilha. Há também implementações de corotinas sem pilha de execução, onde somente o “número da linha de código” que estava executando é restaurado.

A proposta N4286 introduz uma nova palavra-chave, await, para identificar um ponto para suspender uma corotina. Fazendo uso de tal funcionalidade, é apresentado o seguinte exemplo, que elegantemente define um algoritmo assíncrono, descrito, na minha humilde opinião, de forma bastante síncrona, e fazendo uso das várias construções da linguagem para controle de fluxo de execução (estrutura de seleção, repetição…).

Corotinas resolvem o problema de complexidade que algoritmos assíncronos demandam. Entretanto, há várias propostas para implementação de corotinas e nenhuma delas foi padronizada ainda. Um caso interessante é o da biblioteca Asio, que, usando macros e um mecanismo similar ao Duff’s device, dá suporte a corotinas que não guardem uma pilha de execução. Dentre todo o trabalho que está sendo investido, o que eu espero que aconteça na comunidade de C++ é que a padronização siga o princípio de “você só paga pelo que usa” e que a especificação permita implementações bem performáticas.

Enquanto a padronização de corotinas não acontece e ficamos sem a solução a nível de linguagem, podemos optar por soluções a nível de biblioteca. Como C++ é uma linguagem que suporta abstrações de baixo nível, você consegue acesso a várias facilidades que podem ser usadas para implementar suporte a corotinas, tipo setjmp e longjmp e até mais coisas indo para o mundo não-portável fora da especificação. Uma biblioteca que parece bem promissora e que espero ver sendo incluída na Boost esse ano é a biblioteca Fiber, que imita a API de threads a qual estamos acostumados para fornecer “threads” agendadas cooperativamente, em espaço de usuário. A biblioteca usa o conceito de fibra, análogo ao conceito de thread, e em uma thread, você pode executar várias fibras. Tal biblioteca fornece a expressividade que precisamos para escrever algoritmos assíncronos de forma síncrona.

Outra solução enquanto você não pode esperar, é não usar corotinas, pois ainda será possível conseguir performance excelente através das técnicas comentadas ao longo do texto. O grande porém vai ser o fluxo embaralhado do código-fonte (ofuscação de código, para quê te quero?).

Completion tokens

E nesse período de tempo incerto quanto a que solução tornará-se o padrão para programação assíncrona na linguagem C++, a Boost.Asio, desde a versão 1.54, adotou um princípio interessante, de implementar uma solução extensível. Tal modelo extensível é muito bem documentado na proposta N4045 e aqui nessa seção há somente um resumo do que está contido em tal proposta.

A proposta é que, dado que o modelo de callbacks é confuso, ele seja evoluído para suportar outros modelos. Então, em vez da função receber como argumento um completion handler (o callback), ela deve receber um completion token, que é a solução de customização/extensibilidade proposta.

A proposta N4045 usa uma abordagem top-down, de primeiro mostrar como a proposta é usada, para depois mostrar como ela é implementada. Abaixo há um código de exemplo retirado da proposta:

No código de exemplo anterior, cada vez que a variável yield é passada a alguma operação assíncrona (ex.: open e read), a função é suspensa até que a operação seja concluída. Quando a operação é concluída, a função é resumida no ponto em que foi suspensa e a função que iniciou a operação assíncrona retorna o resultado da operação. A biblioteca Fiber, mencionada anteriormente, fornece um yield_context para o modelo extensível da Asio, o boost::fibers::asio::yield. Código assíncrono escrito de uma forma síncrona. Entretanto, um modelo extensível é adotado, pois não sabemos qual será o padrão para operações assíncronas, então não podemos forçar o uso da biblioteca Fiber goela abaixo.

Para tornar o modelo extensível, o tipo do retorno da função precisa ser deduzido (a partir do token) e o valor do retorno da função também precisa ser deduzido (também a partir do token). O tipo do retorno da função é deduzido a partir do tipo do token e o valor retornado é criado a partir do objeto token passado como argumento. E você ainda tem o handler, que deve ser chamado quando a operação for concluída. O handler também é extraído a partir do token. O modelo de completion tokens faz uso de type traits para extrair todas essas informações e, caso tais traits não sejam especializados, o comportamento padrão é tratar o token como um handler, tornando a abordagem retrocompatível com o modelo de callbacks.

Vários exemplos de token são dados no documento N4045:

  • use_future
  • boost::fibers::asio::use_future
  • boost::fibers::asio::yield
  • block

A abordagem de usar std::future tem impactos significativos na performance e não é uma abstração legal, como o próprio N4045 explica em suas seções iniciais, então vamos evitá-la. É até por isso que eu nem comentei sobre ela até então nesse texto.

Sinais e slots

Uma alternativa que foi proposta ao modelo de callbacks é a abordagem de “signals & slots”. Essa abordagem é implementada na libsigc++, na Boost, na Qt e em várias outras bibliotecas.

A proposta de usar sinais introduz esse conceito, de sinal, que é usado para notificar algum evento, mas abstrai o processo de entregar as notificações e registrar as funções que tratem o evento. O código que notifica os eventos só precisa se preocupar em emitir o sinal toda vez que o evento acontecer, pois o próprio sinal vai cuidar do conjunto de slots, que são as porções de código a serem executadas para tratar os eventos.

Essa abordagem costuma permitir um grande desacoplamento, em oposição a abordagem verbosa muito usada em Java. Um efeito interessante, também, a depender da implementação, é que você pode conectar um sinal a outro sinal, para evitar o trabalho de escrever você próprio o código que sincronize a emissão de um sinal a emissão de outro sinal. É possível também ter muitos sinais conectados a um único slot, assim como um único sinal conectado a múltiplos slots.

O sinal costuma ser associado a um objeto, e quando tal objeto é destruído, as conexões que haviam sido feitas também o são. Assim como também é possível ter slots como métodos de um objeto, que são desconectados de todos os sinais tão logo o objeto é destruído.

Como os sinais são abstrações independentes e operantes assim que se tornam expostos, é natural ser incentivado a remover o argumento de callback das funções que iniciam operações assíncronas, pois haveria duplicação de esforços. Se você for até mais longe e remover também a própria função que inicie a operação assíncrona, expondo ao usuário apenas os sinais para receber as notificações, sua framework deixará de seguir o modelo ativo para seguir o modelo passivo. Exemplos de tais modelos passivos é o socket da biblioteca Qt, que não possui uma função explícita para iniciar a operação de leitura, e a biblioteca POCO, que não possui uma função explícita para iniciar o recebimento de uma requisição HTTP.

Outro detalhe que temos no caso da ideia de sinais, é o controle de acesso. No caso da biblioteca Qt, sinais são implementados de uma forma que exige a cooperação de um pré-processador, o executor da própria Qt e a classe QObject. No caso da biblioteca Qt, a emissão de sinais segue as regras de controle de acesso de métodos protegidos em C++, onde todas as classes-filha podem realizar a emissão de sinais declaradas nas classes-pai. Enquanto a operação de conectar um sinal a outro sinal ou a um slot segue as mesmas regras de membros públicos, onde todo mundo pode realizar.

No caso das bibliotecas que implementam o conceito de sinais como um tipo, é comum ver um tipo sinal que englobe tanto a operação de emitir o sinal, quanto a operação de conectar o sinal a algum slot (diferente do que vemos na proposta de futures e promises, onde é possível ter um controle de acesso separado para as diferentes responsabilidades/operações/entidades).

A abordagem de sinais é legal, mas ela não resolve o problema de complexidade que é resolvido por corotinas. Eu descrevi essa abordagem com o intuito de facilitar a explicação sobre a diferença entre os modelos ativo e passivo.

Modelo ativo versus passivo

No modelo passivo, você não agenda o início das operações e, apesar de ser mais comum em frameworks de produtividade, há muitas perguntas que esse modelo não responde bem (isso é só uma opinião e não um fato), que acabam exigindo o projeto de bem mais abstrações para contornar o problema.

Fazendo um contraste rápido entre a biblioteca Qt e a Boost.Asio. Em ambas as bibliotecas, você possui classes para abstraírem o conceito de socket, mas, enquanto na Qt você trata o evento readyRead e usa o método readAll para receber o buffer com o conteúdo, na Boost.Asio você inicia a operação async_read_some e passa o buffer a ser utilizado como argumento. A Qt usa o modelo passivo e a Boost.Asio usa o modelo ativo.

O evento readyRead, da Qt, age independente do usuário e requer a alocação do buffer toda vez que ocorre. Como, então, você responde a perguntas como “como eu posso customizar o algoritmo de alocação do buffer?”, “como eu posso customizar a aplicação para fazer reaproveitamento de buffers?”, “como eu faço para usar um buffer pré-alocado na stack?” e outras. Como o modelo passivo não responde a perguntas como essa, você precisa inflar a abstração de socket com mais pontos de customização para que comportamentos como esses sejam possíveis. É uma explosão combinatória que acontece para cada abstração que lide com operações assíncronas. No modelo ativo, fica bem natural. Se há algum recurso que a operação que o programador está prestes a iniciar necessita, só precisa passar o recurso como argumento, pois no modelo ativo, o programador explicitamente inicia as operações. E não é só sobre customizar obtenção de recursos. Mais um exemplo de pergunta que o modelo passivo não responde bem é “como eu decido se vou ou não aceitar uma conexão (ou adiar para depois, durante cenários de sobrecarga do servidor)?”. É um grande poder para aplicações sérias que estão seriamente preocupadas com performance e necessitam de ajustes meticulosos.

Além da questão de performance e ajuste fino, o modelo passivo também é problemático para realizar depuração e testes, pois ele faz uma inversão de controle. Esse é um dos motivos pelos quais, até hoje, eu vejo frameworks e frameworks introduzindo race condition nos testes ao depender de timeout para garantir a “robustez” de suas implementações. Essa solução de timeout não resolve um problema que ocorre somente no modelo passivo, mas no modelo passivo, ele ocorre com muito mais frequência. No modelo ativo, seu callback pode ser chamado mesmo quando o erro acontece. No modelo passivo é tão problemático, que já vi eventos de erro serem declarados como notificações separadas, que podem ser facilmente ignoradas, característica indesejável quando você quer diminuir e evitar os bugs no seu código.

Graças ao modelo ativo, muito da biblioteca que estou desenvolvendo com o intuito de submeter a Boost foi simplificado.

O modelo passivo, no entanto, é ótimo para prototipações rápidas. Felizmente podemos implementar abstrações que introduzam o modelo passivo em termos do modelo ativo e ter o melhor (ou o pior, se você for um projetista de C#) dos dois mundos.

Referências bônus

Uns links aleatórios que também me ajudaram como referência ou podem servir de leitura aprofundada para quem quer mais:

Comentários bônus

Espero ter introduzido você, estimado leitor que não comenta, as principais práticas utilizadas no universo da programação assíncrona. E agora que você já está armado com uma visão geral da “área”, se jogar nesse mar da internet para buscar mais referências e cavar mais fundo é só o segundo passo.

Esses dois meses que não postei nada e fui perdendo acessos/visitantes… valeram a pena, pois estou bastante satisfeito com esse trabalho e esse blog é sobre minha satisfação pessoal, por isso que eu nem coloco propaganda.

Há alguns outros textos separados em minha pasta de rascunho com temas variando desde ideias originais até assuntos “manjados” que eu pretendo usar quando bater aquela preguiça de investir muito tempo para um post só.

Agora que eu finalmente discuti concorrência sem paralelismo nesse blog, eu devia atualizar meu notebook (ou corrigir meu desktop) para ter um hardware suficientemente bom e começar a estudar OpenCL para fazer uns posts sobre paralelismo massivo.

EDIT (2015/3/11):

  • Ressaltar sugestão de estudar arquitetura de computadores e sistemas operacionais na seção de motivação.
  • Menção sobre “executor implícito” que ocorre em JavaScript.
  • Adicionado exemplo de motivação não relacionado a buffers na discussão de modelo ativo versus modelo passivo.

Arquivado em:computação, pt-BR Tagged: C++, javascript, programação, Python, Qt