Entendendo e solucionando o bloqueio do Event Loop no NodeJs [Parte 1]
Tags: node, javascript, eventdriven, eventloop
Published: 2022-01-30 20:04:45

 Olá 😀.
   Espero que estejam todos bem nesses tempos difíceis.
   Com o passar dos anos o volume de informação disponível para consulta na internet aumentou exponencialmente. Falando especialmente de programação, o número de comunidades e locais de consultas que estão disponíveis para que possam ser acessados a fim de tentar solucionar os mais diversos tipos de problemas cresceu absurdos. 

  Isso é muito bom porque para nós, programadores, perder tempo com um problema é muito frustante e prejudicial também. Comunidades como StackOverflow por exemplo possuem um vasto conteúdo com descrições e soluções dos mais diversos tipos de problemas. É de fato uma mão na roda.

  No entanto, essa grande disponibilidade de informações acabou tornando as pessoas preguiçosas. A maioria dos programadores, quando se deparam com um bug, correm para o Stackoverflow ou Quora e pesquisam pelo problema, acham uma solução e a copiam deliberadamente, sem nem mesmo tentar entender o que foi feito ou porquê aquela solução funciona. Esse hábito tem gerado códigos com uma qualidade cada vez pior.

   Por isso é importante entendermos o que estamos fazendo e porquê, pois assim além de conseguirmos produzir códigos melhores, conseguiremos resolver uma gama maior de problemas.

Como eu tentei ser em didático durante o artigo ele acabou ficando um tanto quanto grande então ele será dividido em duas partes. Ao final desse aqui você vai encontrar um link para a segunda parte.

   Então bora entender o que é o bloqueio do loop de eventos do NodeJs e como podemos resolver esse problema ? 

## Event Loop: Uma breve introdução e como funciona
   O Event Loop é o mecanismo que possibilita o NodeJs executar operações que poderiam demorar muito de forma assíncrona, não prejudicando assim o desempenho geral do sistema. Uma vez que o processo do node se inicia inicia-se também o Event Loop que roda na thread principal ou main thread, a partir disso ele fica rodando enquanto o processo do node viver.

   Ele é formado, não somente, mas principalmente por 5 fases. Em cada fase ele realiza operações específicas visando o não comprometimento da thread principal, delegando tarefas que demandam mais tempo para serem executadas para a libuv. 

   A libuv é a biblioteca escrita em C que permite ao node executar tarefas relacionadas ao kernel do SO de forma assíncrona.  Ela é a responsável por lidar com Thread Pool. O Thread Pool(como o nome já sugere) é um conjunto de threads que ficam disponíveis para executar tarefas que serão entregues a elas pela libuv.

  **Pera pera pera, parou tudo!!!**

   Como assim conjunto de threads ??? Não havia uma thread só ?

   Calma jovem padawan, eu explico. Ser single thread é uma característica do javascript. Isso se deve à história por trás do Javascript e como e para o quê ele foi concebido. Não vou entrar em detalhes aqui, mas deixarei nas referências onde você pode ler mais sobre isso.

  Então, voltando ao assunto principal. O javascript é single thread e o NodeJs utiliza essa única thread que o javascript possui para executar o Event Loop.

   Ele por sua vez entrega as tarefas para a libuv e fica ouvindo as respostas, esperando que as tarefas fiquem prontas, quando as tarefas terminam de executar, como por exemplo uma leitura de arquivos, o Event Loop então executa a callback associada àquela tarefa. 

   Isso é o que chamamos de Event-Driven Patern, o que é muito forte no node devido à essa característica de ele executar o loop de eventos em uma única thread. Event-Driven é um padrão de projetos baseado em eventos, onde uma tarefa é disparada após o término de outra. Mais ou menos assim, "Pegue essa tarefa demorada/pesada e mande processar, e assim que terminar, dispare um evento informando do fim dessa tarefa".

   Um conceito importante que precisamos ter em mente para entender o problema que será mostrado, é a CallStack. A CallStack é uma fila do tipo LIFO (Last In Firt Out) ou (Último a entrar, Primeiro a sair). O Loop de eventos checa a todo instante a CallStack verificando se existe algo para ser processado, e caso tenha, ele a processa e então segue para a próxima função, caso exista.

  O Event Loop pode ser dividido, principalmente mas não somente, em 5 fases. São elas ( explicação retirada da documentação oficial: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ )
  
  **Timers:**
  Nesta fase são executadas as callbacks agendadas por setTimeout e setInterval
  	
  **Pedinding Calbacks**:
  Nesta fase estão as callbacks que foram agendadas para a próxima iteração do loop
 
 **idle, prepare:**
   Esta fase é usada internamente pelo Node. Ou seja, é uma fase que realiza operações internas ao node e não interfere de forma geral no fluxo de execução das tasks que é o que nos interessa para entende o problema de bloqueio do loop de eventos.
 	
**poll:**
   É nessa fase que o NodeJs checa por eventos de IO, como entrada de novas requisições por exemplo. Essa fase é muito importante para entendermos o impacto do bloqueio de eventos na aplicação como um todo.
 
 **check:**
   Nesta fase as callbacks que são agendadas com a função setImediate são executadas. Note que existe uma fase do loop de eventos somente para executar as callbacks agendadas por essa função, e de fato, ela é extremamente importante, inclusive a usaremos para desbloquear o loop de ventos.

 **close callbacks:**
   Nesta fase são executadas as callbacks de fechamento, por exemplo quando fechamos um socket com socket.on('close').

   Isso foi um breve resumo mas já será o suficiente para entendermos o problema que quero mostrar e principalmente entender as soluções que serão apresentadas, ou seja, entender o porquê e como cada uma dessas soluções age no NodeJs permitindo o desbloqueio do loop de eventos. 
   No entanto deixarei na seção referências artigos e links da documentação contento explicações muito mais detalhadas sobre o NodeJs como um todo e principalmente sobre o Event Loop.

   Recomendo fortemente a leitura de cada um deles pois esse é um dos principais e mais importantes conceitos sobre o NodeJs, além é claro de conter explicações sobre outros conceitos extremanente importantes como a MessageQueue, Libuv, web_workers, micro e macro tasks dentre outros.

## Como ocorre o bloqueio do Event Loop ?

   Em suma, esse bloqueio ocorre quando realizamos descuidadamente alguma operação bloqueante na thread principal, ou seja na main thread, que por sua vez é a thread sobre a qual o Event Loop executa. Quando bloqueamos essa thread o loop de eventos não consegue avançar para as outras fases, e com isso ele fica travado, ou seja, bloqueado, em uma única parte. Isso compromete toda a sua aplicação. 

   Lembra que dissemos que a fase de poll é a responsável por processar as requisições que chegam para a sua aplicação ? Pois então, imagine que a sua aplicação fique travada uma fase antes dela, se a fase de Pool não puder ser atingida, novas requisições nunca serão processadas, assim como respostas de outras possíveis requisições que ficaram prontas nesse meio tempo em que o loop estava bloqueado também não serão enviadas de volta para os usuários que as solicitaram.

   Vamos ver na prática como podemos simular o bloqueio de Event Loop. Para demonstrar isso vamos utilizar as seguintes ferramentas:
 NodeJs
 VsCode ( ou qualquer outro editor de sua preferência).          Lembrando que deixarei o projeto completo e do VsCode.
##  O projeto de testes
   De forma resumida,essa é a estrutura do projeto que vamos utilizar
Projeto Node:
       Vamos utilizar o express para servir 5 rotas. São elas:
       **/rota-bloqueante:**  Rota que vai bloquear todo o nosso sistema, será a nossa grande vilã.
       **/rota-bloqueante-com-chield-process:** Executa a mesma operação da rota acima, porém de forma a não bloquear o loop de events se valendo de child_process para isso. É uma das soluções que vamos analisar.
       **/rota-bloqueante-com-setImediate:**  Assim como a rota anterior, executa uma opreção bloqueante, mas se utilizando da função setImediate para impedir o bloqueio do event-loop.
       **/rota-bloqueante-com-worker-thread:** Executa a mesma operação bloqueante, mas se utiliza de workers_threads para evitar o bloqueio do event-loop.
       **/rota-nao-bloqueante:** Rota que possui retorno imediato, será utilizada para testar a responsividade do nosso servidor. 
       ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o11hndxa2t4u64vuivho.png)

## Bloqueando o Event Loop
 
   Para começar vamos simular uma situação na qual ocorre o bloqueio do loop de eventos. Com ele bloqueado vamos ver o que acontece com o resto do sistema.
   Primeiro vamos fazer a requisição que não oferece bloqueio.
 
 ![requisição-normal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3vfux6fg6yzh2ifyw7k5.png)

   Repare que esta rota leva apenas 22 ms em média para responder.

   Agora vamos bloquear o event-loop e ver o que acontece se eu tentar chamar essa rota novamente.
   Primeiro chamamos a rota **/rota-bloqueante**, ela leva mais ou menos 2 minutos e 50 segundos para responder.
![rota-bloqueante](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22ilh4yjpbye445elh6a.png)

   E para nossa surpresa(ou não rss), se tentamos fazer uma requisição para a rota **nao-bloqueante**, que a princípio deveria levar apenas alguns milissegundos para responder, temos uma desagradável surpresa.
 ![requisicao-travada](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/maxkhu3bze12fnlk00wc.png)

   Como podemos perceber, a requisição não-bloqueante demorou 2 minutos e 53 segundos para responder, isso é mais ou menos 7879 vezes mais lento do que deveria 😯.

   Vamos trazer esse problema para uma situação real. Imagine que **/rota-nao-bloqueante** é uma rota de pagamento em sua api. Se nesse momento milhares de usuários tentassem efetuar um pagamento eles não iriam conseguir e você poderia perder milhares de vendas. Nada legal certo ?

## Mas afinal, o que aconteceu ?

Vamos analisar o código atrás de respostas.

```javascript
//Esse é a declaração da nossa rota bloqueante, ou seja,a  //rota que compromete nosso sistema
router.get('/rota-bloqueante', async (request, response) => {
  const generatedString = operacaoLenta();
  response.status(200).send(generatedString);
});
```
Vamos analisar o código dessa função chamada **operação lenta**

```javascript
function operacaoLenta() {
  const stringHash = crypto.createHash('sha512');
  // eslint-disable-next-line no-plusplus
  for (let i = 0; i < 10e6; i++) {
    stringHash.update(generateRandomString()); // operação extremamente custosa
  }
  return `${stringHash.digest('hex')}\n`;
}
```
Vamos por partes.

```javascript   
const stringHash = crypto.createHash('sha512');
```
Nesta linha nós criamos um hash vazio utilizando o algoritmo **[SHA512](https://pt.wikipedia.org/wiki/SHA-2)**.

```javascript   
for (let i = 0; i < 10e6; i++) {
    stringHash.update(generateRandomString()); // operação extremamente custosa
  }
```
   Nesta linha nós fazemos 10^6 iterações atualizando o hash que criamos com uma função **generateRandomString** que gera uma string aleatória em hexadecimal. Aqui utilizamos a função randomBytes do módulo **Crypto** do NodeJs para deixar o processamento ainda mais pesado. Só por curiosidade esse é o código da função.

```javascript
function generateRandomString() {
  return crypto.randomBytes(200).toString('hex');
}
```
   Claramente esse loop é o grande culpado pela lentidão.    Mas vamos entender o porque esse loop aparentemente inofensivo afetou tão negativamente nosso sistema. 

   O problema aqui é que esse loop extremamente custoso, tanto em tempo como em processador, está rodando na Main Thead.

 Lembra que dissemos que o Javascript possui apenas uma única thread e que era essa thread que  o NodeJs utilizava para executar o event-loop ? Pois então, ao fazer essa operação, nós ocupamos essa thread totalmente, e isso impediu o Event Loop de seguir para as próximas fases, e por consequência ele não conseguiu processar a nossa requisição da rota **/rota-nao-bloqueante**. 

   Com isso dizemos que o Event Loop ficou bloqueado, ou seja incapaz de fazer qualquer outra coisa até que o trabalho que ocupava a thread principal terminasse. 

   Por isso que da segunda vez nossa requisição que deveria ser rápida levou 2 minutos e 53 segundos, porque a requisição que enviamos para essa rota ficou esperando até que o Event Loop chegasse na fase de **Poll** para que ele pegasse essa requisição e colocasse ela na fila para ser processada.

 Beleza! Já vimos o que pode acontecer se não respeitarmos essas característica do NodeJs. No próximo artigo vamos ver como resolver esse problema!

Segue o link para a segunda parte e te aguardo lá  😃 😃 😃

##Segunda parte
[Clique aqui para ir para a segunda parte](https://dev.to/r9n/entendendo-e-solucionando-o-bloqueio-do-event-loop-no-nodejs-parte-2-67m)